diff --git a/.fpm_openwrt b/.fpm_openwrt new file mode 100644 index 0000000000..3223ec8ad5 --- /dev/null +++ b/.fpm_openwrt @@ -0,0 +1,31 @@ +-s dir +--name sing-box +--category net +--license GPL-3.0-or-later +--description "The universal proxy platform." +--url "https://sing-box.sagernet.org/" +--maintainer "nekohasekai " +--no-deb-generate-changes + +--config-files /etc/config/sing-box +--config-files /etc/sing-box/config.json + +--depends ca-bundle +--depends kmod-inet-diag +--depends kmod-tun +--depends firewall4 +--depends kmod-nft-queue + +--before-remove release/config/openwrt.prerm + +release/config/config.json=/etc/sing-box/config.json + +release/config/openwrt.conf=/etc/config/sing-box +release/config/openwrt.init=/etc/init.d/sing-box +release/config/openwrt.keep=/lib/upgrade/keep.d/sing-box + +release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash +release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish +release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box + +LICENSE=/usr/share/licenses/sing-box/LICENSE diff --git a/.fpm_pacman b/.fpm_pacman new file mode 100644 index 0000000000..8c86dfd91a --- /dev/null +++ b/.fpm_pacman @@ -0,0 +1,23 @@ +-s dir +--name sing-box +--category net +--license GPL-3.0-or-later +--description "The universal proxy platform." +--url "https://sing-box.sagernet.org/" +--maintainer "nekohasekai " +--config-files etc/sing-box/config.json +--after-install release/config/sing-box.postinst + +release/config/config.json=/etc/sing-box/config.json + +release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service +release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service +release/config/sing-box.sysusers=/usr/lib/sysusers.d/sing-box.conf +release/config/sing-box.rules=usr/share/polkit-1/rules.d/sing-box.rules +release/config/sing-box-split-dns.xml=/usr/share/dbus-1/system.d/sing-box-split-dns.conf + +release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash +release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish +release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box + +LICENSE=/usr/share/licenses/sing-box/LICENSE diff --git a/.fpm_systemd b/.fpm_systemd new file mode 100644 index 0000000000..9b455da9f6 --- /dev/null +++ b/.fpm_systemd @@ -0,0 +1,26 @@ +-s dir +--name sing-box +--category net +--license GPL-3.0-or-later +--description "The universal proxy platform." +--url "https://sing-box.sagernet.org/" +--vendor SagerNet +--maintainer "nekohasekai " +--deb-field "Bug: https://github.com/SagerNet/sing-box/issues" +--no-deb-generate-changes +--config-files /etc/sing-box/config.json +--after-install release/config/sing-box.postinst + +release/config/config.json=/etc/sing-box/config.json + +release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service +release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service +release/config/sing-box.sysusers=/usr/lib/sysusers.d/sing-box.conf +release/config/sing-box.rules=usr/share/polkit-1/rules.d/sing-box.rules +release/config/sing-box-split-dns.xml=/usr/share/dbus-1/system.d/sing-box-split-dns.conf + +release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash +release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish +release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box + +LICENSE=/usr/share/licenses/sing-box/LICENSE diff --git a/.github/CRONET_GO_VERSION b/.github/CRONET_GO_VERSION new file mode 100644 index 0000000000..53d53768ca --- /dev/null +++ b/.github/CRONET_GO_VERSION @@ -0,0 +1 @@ +b3eec8134aec1387d850e0671dd8531e2e6140b0 diff --git a/.github/build_alpine_apk.sh b/.github/build_alpine_apk.sh new file mode 100755 index 0000000000..610103b503 --- /dev/null +++ b/.github/build_alpine_apk.sh @@ -0,0 +1,94 @@ +#!/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" < "$APK_ROOT_DIR/etc/group" < " + 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 " \ + --files "$ROOT_DIR" \ + --output "$OUTPUT_PATH" diff --git a/.github/build_openwrt_apk.sh b/.github/build_openwrt_apk.sh new file mode 100755 index 0000000000..59f07fd8b0 --- /dev/null +++ b/.github/build_openwrt_apk.sh @@ -0,0 +1,93 @@ +#!/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" < "$APK_ROOT_DIR/etc/group" < " + exit 1 +fi + +PROJECT=$(cd "$(dirname "$0")/.."; pwd) + +# Convert version to APK format: +# 1.13.0-beta.8 -> 1.13.0_beta8-r0 +# 1.13.0-rc.3 -> 1.13.0_rc3-r0 +# 1.13.0 -> 1.13.0-r0 +APK_VERSION=$(echo "$VERSION" | sed -E 's/-([a-z]+)\.([0-9]+)/_\1\2/') +APK_VERSION="${APK_VERSION}-r0" + +ROOT_DIR=$(mktemp -d) +prepare_apk_root +trap 'rm -rf "$ROOT_DIR" "$APK_ROOT_DIR"' EXIT + +# Binary +install -Dm755 "$BINARY_PATH" "$ROOT_DIR/usr/bin/sing-box" + +# Config files +install -Dm644 "$PROJECT/release/config/config.json" "$ROOT_DIR/etc/sing-box/config.json" +install -Dm644 "$PROJECT/release/config/openwrt.conf" "$ROOT_DIR/etc/config/sing-box" +install -Dm755 "$PROJECT/release/config/openwrt.init" "$ROOT_DIR/etc/init.d/sing-box" +install -Dm644 "$PROJECT/release/config/openwrt.keep" "$ROOT_DIR/lib/upgrade/keep.d/sing-box" + +# Completions +install -Dm644 "$PROJECT/release/completions/sing-box.bash" "$ROOT_DIR/usr/share/bash-completion/completions/sing-box.bash" +install -Dm644 "$PROJECT/release/completions/sing-box.fish" "$ROOT_DIR/usr/share/fish/vendor_completions.d/sing-box.fish" +install -Dm644 "$PROJECT/release/completions/sing-box.zsh" "$ROOT_DIR/usr/share/zsh/site-functions/_sing-box" + +# License +install -Dm644 "$PROJECT/LICENSE" "$ROOT_DIR/usr/share/licenses/sing-box/LICENSE" + +# APK metadata +PACKAGES_DIR="$ROOT_DIR/lib/apk/packages" +mkdir -p "$PACKAGES_DIR" + +# .conffiles +cat > "$PACKAGES_DIR/.conffiles" <<'EOF' +/etc/config/sing-box +/etc/sing-box/config.json +EOF + +# .conffiles_static (sha256 checksums) +while IFS= read -r conffile; do + sha256=$(sha256sum "$ROOT_DIR$conffile" | cut -d' ' -f1) + echo "$conffile $sha256" +done < "$PACKAGES_DIR/.conffiles" > "$PACKAGES_DIR/.conffiles_static" + +# .list (all files, excluding lib/apk/packages/ metadata) +(cd "$ROOT_DIR" && find . -type f -o -type l) \ + | sed 's|^\./|/|' \ + | grep -v '^/lib/apk/packages/' \ + | sort > "$PACKAGES_DIR/.list" + +# Build APK +apk --root "$APK_ROOT_DIR" mkpkg \ + --info "name:sing-box" \ + --info "version:${APK_VERSION}" \ + --info "description:The universal proxy platform." \ + --info "arch:${ARCHITECTURE}" \ + --info "license:GPL-3.0-or-later" \ + --info "origin:sing-box" \ + --info "url:https://sing-box.sagernet.org/" \ + --info "maintainer:nekohasekai " \ + --info "depends:ca-bundle kmod-inet-diag kmod-tun firewall4 kmod-nft-queue" \ + --info "provider-priority:100" \ + --script "pre-deinstall:${PROJECT}/release/config/openwrt.prerm" \ + --files "$ROOT_DIR" \ + --output "$OUTPUT_PATH" diff --git a/.github/deb2ipk.sh b/.github/deb2ipk.sh new file mode 100755 index 0000000000..0b820533c6 --- /dev/null +++ b/.github/deb2ipk.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# mod from https://gist.github.com/pldubouilh/c5703052986bfdd404005951dee54683 + +set -e -o pipefail + +PROJECT=$(dirname "$0")/../.. +TMP_PATH=`mktemp -d` +cp $2 $TMP_PATH +pushd $TMP_PATH + +DEB_NAME=`ls *.deb` +ar x $DEB_NAME + +mkdir control +pushd control +tar xf ../control.tar.gz +rm md5sums +sed "s/Architecture:\\ \w*/Architecture:\\ $1/g" ./control -i +cat control +tar czf ../control.tar.gz ./* +popd + +DEB_NAME=${DEB_NAME%.deb} +tar czf $DEB_NAME.ipk control.tar.gz data.tar.gz debian-binary +popd + +cp $TMP_PATH/$DEB_NAME.ipk $3 +rm -r $TMP_PATH diff --git a/.github/detect_track.sh b/.github/detect_track.sh new file mode 100755 index 0000000000..124ca6e25c --- /dev/null +++ b/.github/detect_track.sh @@ -0,0 +1,33 @@ +#!/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" diff --git a/.github/renovate.json b/.github/renovate.json index 78d9c96144..e24ff248b3 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -6,7 +6,7 @@ ":disableRateLimiting" ], "baseBranches": [ - "dev-next" + "unstable" ], "golang": { "enabled": false diff --git a/.github/setup_go_for_macos1013.sh b/.github/setup_go_for_macos1013.sh new file mode 100755 index 0000000000..9e501ca949 --- /dev/null +++ b/.github/setup_go_for_macos1013.sh @@ -0,0 +1,45 @@ +#!/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" diff --git a/.github/setup_go_for_windows7.sh b/.github/setup_go_for_windows7.sh new file mode 100755 index 0000000000..01588bca0f --- /dev/null +++ b/.github/setup_go_for_windows7.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +set -euo pipefail + +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" +wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz" +tar -xzf "go${VERSION}.linux-amd64.tar.gz" +mv go go_win7 +cd go_win7 + +# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557 +# these patch URLs only work on golang1.25.x +# that means after golang1.26 release it must be changed +# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.25/ +# revert: +# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng" +# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7" +# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround" +# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries" +# fixes: +# bed309eff415bcb3c77dd4bc3277b682b89a388d: "Fix os.RemoveAll not working on Windows7" +# 34b899c2fb39b092db4fa67c4417e41dc046be4b: "Revert \"os: remove 5ms sleep on Windows in (*Process).Wait\"" + +for patch_commit in "${PATCH_COMMITS[@]}"; do + curl "${CURL_ARGS[@]}" "https://github.com/MetaCubeX/go/commit/${patch_commit}.diff" | patch --verbose -p 1 +done diff --git a/.github/update_cronet.sh b/.github/update_cronet.sh new file mode 100755 index 0000000000..17716b83d8 --- /dev/null +++ b/.github/update_cronet.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e -o pipefail + +SCRIPT_DIR=$(dirname "$0") +PROJECTS=$SCRIPT_DIR/../.. + +git -C $PROJECTS/cronet-go fetch origin main +git -C $PROJECTS/cronet-go fetch origin go +go get -x github.com/sagernet/cronet-go/all@$(git -C $PROJECTS/cronet-go rev-parse origin/go) +go get -x github.com/sagernet/cronet-go@$(git -C $PROJECTS/cronet-go rev-parse origin/go) +go mod tidy +git -C $PROJECTS/cronet-go rev-parse origin/go > "$SCRIPT_DIR/CRONET_GO_VERSION" diff --git a/.github/update_cronet_dev.sh b/.github/update_cronet_dev.sh new file mode 100755 index 0000000000..13f7090cd5 --- /dev/null +++ b/.github/update_cronet_dev.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e -o pipefail + +SCRIPT_DIR=$(dirname "$0") +PROJECTS=$SCRIPT_DIR/../.. + +git -C $PROJECTS/cronet-go fetch origin dev +git -C $PROJECTS/cronet-go fetch origin go_dev +go get -x github.com/sagernet/cronet-go/all@$(git -C $PROJECTS/cronet-go rev-parse origin/go_dev) +go get -x github.com/sagernet/cronet-go@$(git -C $PROJECTS/cronet-go rev-parse origin/go_dev) +go mod tidy +git -C $PROJECTS/cronet-go rev-parse origin/dev > "$SCRIPT_DIR/CRONET_GO_VERSION" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000..42dde88591 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,964 @@ +name: Build + +on: + workflow_dispatch: + inputs: + version: + description: "Version name" + required: true + type: string + build: + description: "Build type" + required: true + type: choice + default: "Binary" + options: + - All + - Binary + - Android + - Apple + - app-store + - iOS + - macOS + - tvOS + - macOS-standalone + # - publish-android + push: + branches: + - main-next + - dev-next + - extended + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }} + cancel-in-progress: true + +jobs: + calculate_version: + name: Calculate version + runs-on: ubuntu-latest + outputs: + version: ${{ steps.outputs.outputs.version }} + steps: + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + with: + fetch-depth: 0 + submodules: 'recursive' + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ^1.25.6 + - name: Check input version + if: github.event_name == 'workflow_dispatch' + run: |- + echo "version=${{ inputs.version }}" + echo "version=${{ inputs.version }}" >> "$GITHUB_ENV" + - name: Calculate version + if: github.event_name != 'workflow_dispatch' + run: |- + go run -v ./cmd/internal/read_tag --ci --nightly + - name: Set outputs + id: outputs + run: |- + echo "version=$version" >> "$GITHUB_OUTPUT" + build: + name: Build binary + if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary' + runs-on: ubuntu-latest + needs: + - calculate_version + strategy: + fail-fast: false + matrix: + 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, 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, 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, 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, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" } + + # # - { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" } + # # - { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" } + # # - { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl, openwrt: "arm_arm1176jzf-s_vfp" } + # # - { os: linux, arch: mips, gomips: softfloat, openwrt: "mips_24kc mips_4kec mips_mips32" } + # # - { os: linux, arch: mipsle, gomips: hardfloat, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc_24kf" } + # # - { os: linux, arch: mipsle, gomips: softfloat, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" } + # # - { os: linux, arch: mips64, gomips: softfloat, openwrt: "mips64_mips64r2 mips64_octeonplus" } + # # - { os: linux, arch: mips64le, gomips: hardfloat, debian: mips64el, rpm: mips64el } + # # - { os: linux, arch: mips64le, gomips: softfloat, openwrt: "mips64el_mips64r2" } + # # - { os: linux, arch: s390x, debian: s390x, rpm: s390x } + # # - { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le } + # # - { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" } + # # - { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" } + + # - { os: windows, arch: amd64, legacy_win7: true, legacy_name: "windows-7" } + # - { os: windows, arch: "386", legacy_win7: true, legacy_name: "windows-7" } + + # # - { os: android, arch: arm64, ndk: "aarch64-linux-android23" } + # # - { os: android, arch: arm, ndk: "armv7a-linux-androideabi23" } + # # - { os: android, arch: amd64, ndk: "x86_64-linux-android23" } + # # - { os: android, arch: "386", ndk: "i686-linux-android23" } + steps: + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + with: + fetch-depth: 0 + submodules: 'recursive' + - name: Setup Go + if: ${{ ! (matrix.legacy_win7 || matrix.legacy_go124) }} + uses: actions/setup-go@v5 + with: + go-version: ^1.25.6 + - 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 + uses: actions/cache@v4 + with: + path: | + ~/go/go_win7 + key: go_win7_1255 + - name: Setup Go for Windows 7 + if: matrix.legacy_win7 && steps.cache-go-for-windows7.outputs.cache-hit != 'true' + run: |- + .github/setup_go_for_windows7.sh + - name: Setup Go for Windows 7 + if: matrix.legacy_win7 + run: |- + echo "PATH=$HOME/go/go_win7/bin:$PATH" >> $GITHUB_ENV + echo "GOROOT=$HOME/go/go_win7" >> $GITHUB_ENV + - name: Setup Android NDK + if: matrix.os == 'android' + uses: nttld/setup-ndk@v1 + with: + ndk-version: r28 + local-cache: true + - name: Clone cronet-go + if: matrix.naive + run: | + set -xeuo pipefail + CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION) + git init ~/cronet-go + git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git + git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION" + git -C ~/cronet-go checkout FETCH_HEAD + git -C ~/cronet-go submodule update --init --recursive --depth=1 + - name: Cache Chromium toolchain + if: matrix.naive + id: cache-chromium-toolchain + uses: actions/cache@v4 + with: + path: | + ~/cronet-go/naiveproxy/src/third_party/llvm-build/Release+Asserts + ~/cronet-go/naiveproxy/src/out/sysroot-build + key: chromium-toolchain-${{ matrix.arch }}-${{ matrix.variant }}-${{ hashFiles('.github/CRONET_GO_VERSION') }} + - name: Download Chromium toolchain + if: matrix.naive + run: | + set -xeuo pipefail + cd ~/cronet-go + if [[ "${{ matrix.variant }}" == "musl" ]]; then + go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain + else + go run ./cmd/build-naive --target=linux/${{ matrix.arch }} download-toolchain + fi + - name: Set Chromium toolchain environment + if: matrix.naive + run: | + set -xeuo pipefail + cd ~/cronet-go + if [[ "${{ matrix.variant }}" == "musl" ]]; then + go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $GITHUB_ENV + else + go run ./cmd/build-naive --target=linux/${{ matrix.arch }} env >> $GITHUB_ENV + fi + - name: Set tag + run: |- + git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" + git tag v${{ needs.calculate_version.outputs.version }} -f + - name: Set build tags + run: | + set -xeuo pipefail + TAGS='with_v2ray_api,with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0' + if [[ "${{ matrix.naive }}" == "true" ]]; then + TAGS="${TAGS},with_naive_outbound" + fi + if [[ "${{ matrix.variant }}" == "purego" ]]; then + TAGS="${TAGS},with_purego" + elif [[ "${{ matrix.variant }}" == "musl" ]]; then + TAGS="${TAGS},with_musl" + fi + echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" + - name: Build (purego) + if: matrix.variant == 'purego' + run: | + set -xeuo pipefail + mkdir -p dist + go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ + -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \ + ./cmd/sing-box + env: + CGO_ENABLED: "0" + GOOS: ${{ matrix.os }} + GOARCH: ${{ matrix.arch }} + GO386: ${{ matrix.go386 }} + GOARM: ${{ matrix.goarm }} + GOMIPS: ${{ matrix.gomips }} + GOMIPS64: ${{ matrix.gomips }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Extract libcronet.so + if: matrix.variant == 'purego' && matrix.naive + run: | + cd ~/cronet-go + CGO_ENABLED=0 go run -v ./cmd/build-naive extract-lib --target ${{ matrix.os }}/${{ matrix.arch }} -o $GITHUB_WORKSPACE/dist + - name: Build (glibc) + if: matrix.variant == 'glibc' + run: | + set -xeuo pipefail + mkdir -p dist + go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ + -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \ + ./cmd/sing-box + env: + CGO_ENABLED: "1" + GOOS: linux + GOARCH: ${{ matrix.arch }} + GO386: ${{ matrix.go386 }} + GOARM: ${{ matrix.goarm }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Build (musl) + if: matrix.variant == 'musl' + run: | + set -xeuo pipefail + mkdir -p dist + go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ + -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \ + ./cmd/sing-box + env: + CGO_ENABLED: "1" + GOOS: linux + GOARCH: ${{ matrix.arch }} + GO386: ${{ matrix.go386 }} + GOARM: ${{ matrix.goarm }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Build (non-variant) + if: matrix.os != 'android' && matrix.variant == '' + run: | + set -xeuo pipefail + mkdir -p dist + go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ + -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \ + ./cmd/sing-box + env: + CGO_ENABLED: "0" + GOOS: ${{ matrix.os }} + GOARCH: ${{ matrix.arch }} + GO386: ${{ matrix.go386 }} + GOARM: ${{ matrix.goarm }} + GOMIPS: ${{ matrix.gomips }} + GOMIPS64: ${{ matrix.gomips }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Build Android + if: matrix.os == 'android' + run: | + set -xeuo pipefail + go install -v ./cmd/internal/build + export CC='${{ matrix.ndk }}-clang' + export CXX="${CC}++" + mkdir -p dist + GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ + -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \ + ./cmd/sing-box + env: + CGO_ENABLED: "1" + BUILD_GOOS: ${{ matrix.os }} + BUILD_GOARCH: ${{ matrix.arch }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Set name + run: |- + DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}" + if [[ -n "${{ matrix.goarm }}" ]]; then + DIR_NAME="${DIR_NAME}v${{ matrix.goarm }}" + elif [[ -n "${{ matrix.go386 }}" && "${{ matrix.go386 }}" != 'sse2' ]]; then + DIR_NAME="${DIR_NAME}-${{ matrix.go386 }}" + elif [[ -n "${{ matrix.gomips }}" && "${{ matrix.gomips }}" != 'hardfloat' ]]; then + DIR_NAME="${DIR_NAME}-${{ matrix.gomips }}" + elif [[ -n "${{ matrix.legacy_name }}" ]]; then + DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}" + fi + if [[ "${{ matrix.variant }}" == "glibc" ]]; then + DIR_NAME="${DIR_NAME}-glibc" + elif [[ "${{ matrix.variant }}" == "musl" ]]; then + DIR_NAME="${DIR_NAME}-musl" + fi + echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}" + PKG_VERSION="${{ needs.calculate_version.outputs.version }}" + PKG_VERSION="${PKG_VERSION//-/\~}" + echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}" + - name: Package DEB + if: matrix.debian != '' + run: | + set -xeuo pipefail + sudo gem install fpm + sudo apt-get update + sudo apt-get install -y debsigs + cp .fpm_systemd .fpm + fpm -t deb \ + -v "$PKG_VERSION" \ + -p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.debian }}.deb" \ + --architecture ${{ matrix.debian }} \ + dist/sing-box=/usr/bin/sing-box + curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff' + sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff' + rm -rf $HOME/.gnupg + gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import < $HOME/.rpmmacros <> "$GITHUB_ENV" + # git tag v${{ needs.calculate_version.outputs.version }} -f + # - name: Set build tags + # run: | + # set -xeuo pipefail + # TAGS='with_v2ray_api,with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0' + # if [[ "${{ matrix.legacy_go124 }}" != "true" ]]; then + # TAGS="${TAGS},with_naive_outbound" + # fi + # echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" + # - name: Build + # run: | + # set -xeuo pipefail + # mkdir -p dist + # go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ + # -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \ + # ./cmd/sing-box + # env: + # CGO_ENABLED: "1" + # GOOS: darwin + # GOARCH: ${{ matrix.arch }} + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # - name: Set name + # run: |- + # DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-darwin-${{ matrix.arch }}" + # if [[ -n "${{ matrix.legacy_name }}" ]]; then + # DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}" + # fi + # echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}" + # - name: Archive + # run: | + # set -xeuo pipefail + # cd dist + # mkdir -p "${DIR_NAME}" + # cp ../LICENSE "${DIR_NAME}" + # cp sing-box "${DIR_NAME}" + # tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}" + # rm -r "${DIR_NAME}" + # - name: Cleanup + # run: rm dist/sing-box + # - name: Upload artifact + # uses: actions/upload-artifact@v4 + # with: + # name: binary-darwin_${{ matrix.arch }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }} + # path: "dist" + # retention-days: 1 + # build_windows: + # name: Build Windows binaries + # if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary' + # runs-on: windows-latest + # needs: + # - calculate_version + # strategy: + # matrix: + # include: + # - { arch: amd64, naive: true } + # - { arch: "386" } + # - { arch: arm64, naive: true } + # steps: + # - name: Checkout + # uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + # with: + # fetch-depth: 0 + # submodules: 'recursive' + # - name: Setup Go + # uses: actions/setup-go@v5 + # with: + # go-version: ^1.25.4 + # - name: Set tag + # run: |- + # git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$env:GITHUB_ENV" + # git tag v${{ needs.calculate_version.outputs.version }} -f + # - name: Build + # if: matrix.naive + # run: | + # mkdir -p dist + # go build -v -trimpath -o dist/sing-box.exe -tags "with_v2ray_api,with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_naive_outbound,with_purego,badlinkname,tfogo_checklinkname0" ` + # -ldflags "-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0" ` + # ./cmd/sing-box + # env: + # CGO_ENABLED: "0" + # GOOS: windows + # GOARCH: ${{ matrix.arch }} + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # - name: Build + # if: ${{ !matrix.naive }} + # run: | + # mkdir -p dist + # go build -v -trimpath -o dist/sing-box.exe -tags "with_v2ray_api,with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0" ` + # -ldflags "-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0" ` + # ./cmd/sing-box + # env: + # CGO_ENABLED: "0" + # GOOS: windows + # GOARCH: ${{ matrix.arch }} + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # - name: Extract libcronet.dll + # if: matrix.naive + # run: | + # $CRONET_GO_VERSION = Get-Content .github/CRONET_GO_VERSION + # $env:CGO_ENABLED = "0" + # go run -v "github.com/sagernet/cronet-go/cmd/build-naive@$CRONET_GO_VERSION" extract-lib --target windows/${{ matrix.arch }} -o dist + # - name: Archive + # if: matrix.naive + # run: | + # $DIR_NAME = "sing-box-${{ needs.calculate_version.outputs.version }}-windows-${{ matrix.arch }}" + # mkdir "dist/$DIR_NAME" + # Copy-Item LICENSE "dist/$DIR_NAME" + # Copy-Item "dist/sing-box.exe" "dist/$DIR_NAME" + # Copy-Item "dist/libcronet.dll" "dist/$DIR_NAME" + # Compress-Archive -Path "dist/$DIR_NAME" -DestinationPath "dist/$DIR_NAME.zip" + # Remove-Item -Recurse "dist/$DIR_NAME" + # - name: Archive + # if: ${{ !matrix.naive }} + # run: | + # $DIR_NAME = "sing-box-${{ needs.calculate_version.outputs.version }}-windows-${{ matrix.arch }}" + # mkdir "dist/$DIR_NAME" + # Copy-Item LICENSE "dist/$DIR_NAME" + # Copy-Item "dist/sing-box.exe" "dist/$DIR_NAME" + # Compress-Archive -Path "dist/$DIR_NAME" -DestinationPath "dist/$DIR_NAME.zip" + # Remove-Item -Recurse "dist/$DIR_NAME" + # - name: Cleanup + # if: matrix.naive + # run: Remove-Item dist/sing-box.exe, dist/libcronet.dll + # - name: Cleanup + # if: ${{ !matrix.naive }} + # run: Remove-Item dist/sing-box.exe + # - name: Upload artifact + # uses: actions/upload-artifact@v4 + # with: + # name: binary-windows_${{ matrix.arch }} + # path: "dist" + # retention-days: 1 + # build_android: + # name: Build Android + # # if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android' + # if: false + # runs-on: ubuntu-latest + # needs: + # - calculate_version + # steps: + # - name: Checkout + # uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + # with: + # fetch-depth: 0 + # submodules: 'recursive' + # - name: Setup Go + # uses: actions/setup-go@v5 + # with: + # go-version: ^1.25.6 + # - name: Setup Android NDK + # id: setup-ndk + # uses: nttld/setup-ndk@v1 + # with: + # ndk-version: r28 + # - name: Setup OpenJDK + # run: |- + # sudo apt update && sudo apt install -y openjdk-17-jdk-headless + # /usr/lib/jvm/java-17-openjdk-amd64/bin/java --version + # - name: Set tag + # run: |- + # git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" + # git tag v${{ needs.calculate_version.outputs.version }} -f + # - name: Build library + # run: |- + # make lib_install + # export PATH="$PATH:$(go env GOPATH)/bin" + # make lib_android + # env: + # JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64 + # ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} + # - name: Checkout main branch + # if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch' + # run: |- + # cd clients/android + # git checkout main + # - name: Checkout dev branch + # if: github.ref == 'refs/heads/dev-next' + # run: |- + # cd clients/android + # git checkout dev + # - name: Gradle cache + # uses: actions/cache@v4 + # with: + # path: ~/.gradle + # key: gradle-${{ hashFiles('**/*.gradle') }} + # - name: Update version + # if: github.event_name == 'workflow_dispatch' + # run: |- + # go run -v ./cmd/internal/update_android_version --ci + # - name: Update nightly version + # if: github.event_name != 'workflow_dispatch' + # run: |- + # go run -v ./cmd/internal/update_android_version --ci --nightly + # - name: Build + # run: |- + # mkdir clients/android/app/libs + # cp *.aar clients/android/app/libs + # cd clients/android + # ./gradlew :app:assembleOtherRelease :app:assembleOtherLegacyRelease + # env: + # JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64 + # ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} + # LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }} + # - name: Prepare upload + # run: |- + # mkdir -p dist + # #cp clients/android/app/build/outputs/apk/play/release/*.apk dist + # cp clients/android/app/build/outputs/apk/other/release/*.apk dist + # cp clients/android/app/build/outputs/apk/otherLegacy/release/*.apk dist + # VERSION_CODE=$(grep VERSION_CODE clients/android/version.properties | cut -d= -f2) + # VERSION_NAME=$(grep VERSION_NAME clients/android/version.properties | cut -d= -f2) + # cat > dist/SFA-version-metadata.json << EOF + # { + # "version_code": ${VERSION_CODE}, + # "version_name": "${VERSION_NAME}" + # } + # EOF + # cat dist/SFA-version-metadata.json + # - name: Upload artifact + # uses: actions/upload-artifact@v4 + # with: + # name: binary-android-apks + # path: 'dist' + # retention-days: 1 + # publish_android: + + # name: Publish Android + # # if: github.event_name == 'workflow_dispatch' && inputs.build == 'publish-android' + # if: false + # runs-on: ubuntu-latest + # needs: + # - calculate_version + # steps: + # - name: Checkout + # uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + # with: + # fetch-depth: 0 + # submodules: 'recursive' + # - name: Setup Go + # uses: actions/setup-go@v5 + # with: + # go-version: ^1.25.6 + # - name: Setup Android NDK + # id: setup-ndk + # uses: nttld/setup-ndk@v1 + # with: + # ndk-version: r28 + # - name: Setup OpenJDK + # run: |- + # sudo apt update && sudo apt install -y openjdk-17-jdk-headless + # /usr/lib/jvm/java-17-openjdk-amd64/bin/java --version + # - name: Set tag + # run: |- + # git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" + # git tag v${{ needs.calculate_version.outputs.version }} -f + # - name: Build library + # run: |- + # make lib_install + # export PATH="$PATH:$(go env GOPATH)/bin" + # make lib_android + # env: + # JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64 + # ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} + # - name: Checkout main branch + # if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch' + # run: |- + # cd clients/android + # git checkout main + # - name: Checkout dev branch + # if: github.ref == 'refs/heads/dev-next' + # run: |- + # cd clients/android + # git checkout dev + # - name: Gradle cache + # uses: actions/cache@v4 + # with: + # path: ~/.gradle + # key: gradle-${{ hashFiles('**/*.gradle') }} + # - name: Build + # run: |- + # go run -v ./cmd/internal/update_android_version --ci + # mkdir clients/android/app/libs + # cp *.aar clients/android/app/libs + # cd clients/android + # echo -n "$SERVICE_ACCOUNT_CREDENTIALS" | base64 --decode > service-account-credentials.json + # ./gradlew :app:publishPlayReleaseBundle + # env: + # JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64 + # ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} + # LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }} + # SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }} + # build_apple: + # name: Build Apple clients + # runs-on: macos-26 + # if: false # github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store' || inputs.build == 'iOS' || inputs.build == 'macOS' || inputs.build == 'tvOS' || inputs.build == 'macOS-standalone' + # needs: + # - calculate_version + # strategy: + # matrix: + # include: + # - name: iOS + # if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'iOS' }} + # platform: ios + # scheme: SFI + # destination: 'generic/platform=iOS' + # archive: build/SFI.xcarchive + # upload: SFI/Upload.plist + # - name: macOS + # if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'macOS' }} + # platform: macos + # scheme: SFM + # destination: 'generic/platform=macOS' + # archive: build/SFM.xcarchive + # upload: SFI/Upload.plist + # - name: tvOS + # if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'tvOS' }} + # platform: tvos + # scheme: SFT + # destination: 'generic/platform=tvOS' + # archive: build/SFT.xcarchive + # upload: SFI/Upload.plist + # - name: macOS-standalone + # if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone' }} + # platform: macos + # scheme: SFM.System + # destination: 'generic/platform=macOS' + # archive: build/SFM.System.xcarchive + # export: SFM.System/Export.plist + # export_path: build/SFM.System + # steps: + # - name: Checkout + # if: matrix.if + # uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + # with: + # fetch-depth: 0 + # submodules: 'recursive' + # - name: Setup Go + # if: matrix.if + # uses: actions/setup-go@v5 + # with: + # go-version: ^1.25.6 + # - name: Set tag + # if: matrix.if + # run: |- + # git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" + # git tag v${{ needs.calculate_version.outputs.version }} -f + # echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV" + # - name: Checkout main branch + # if: matrix.if && github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch' + # run: |- + # cd clients/apple + # git checkout main + # - name: Checkout dev branch + # if: matrix.if && github.ref == 'refs/heads/dev-next' + # run: |- + # cd clients/apple + # git checkout dev + # - name: Setup certificates + # if: matrix.if + # run: |- + # CERTIFICATE_PATH=$RUNNER_TEMP/Certificates.p12 + # KEYCHAIN_PATH=$RUNNER_TEMP/certificates.keychain-db + # echo -n "$CERTIFICATES_P12" | base64 --decode -o $CERTIFICATE_PATH + # security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + # security set-keychain-settings -lut 21600 $KEYCHAIN_PATH + # security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + # security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + # security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + # security list-keychain -d user -s $KEYCHAIN_PATH + + # PROFILES_ZIP_PATH=$RUNNER_TEMP/Profiles.zip + # echo -n "$PROVISIONING_PROFILES" | base64 --decode -o $PROFILES_ZIP_PATH + + # PROFILES_PATH="$HOME/Library/MobileDevice/Provisioning Profiles" + # mkdir -p "$PROFILES_PATH" + # unzip $PROFILES_ZIP_PATH -d "$PROFILES_PATH" + + # ASC_KEY_PATH=$RUNNER_TEMP/Key.p12 + # echo -n "$ASC_KEY" | base64 --decode -o $ASC_KEY_PATH + + # xcrun notarytool store-credentials "notarytool-password" \ + # --key $ASC_KEY_PATH \ + # --key-id $ASC_KEY_ID \ + # --issuer $ASC_KEY_ISSUER_ID + + # echo "ASC_KEY_PATH=$ASC_KEY_PATH" >> "$GITHUB_ENV" + # echo "ASC_KEY_ID=$ASC_KEY_ID" >> "$GITHUB_ENV" + # echo "ASC_KEY_ISSUER_ID=$ASC_KEY_ISSUER_ID" >> "$GITHUB_ENV" + # env: + # CERTIFICATES_P12: ${{ secrets.CERTIFICATES_P12 }} + # P12_PASSWORD: ${{ secrets.P12_PASSWORD }} + # KEYCHAIN_PASSWORD: ${{ secrets.P12_PASSWORD }} + # PROVISIONING_PROFILES: ${{ secrets.PROVISIONING_PROFILES }} + # ASC_KEY: ${{ secrets.ASC_KEY }} + # ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} + # ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }} + # - name: Build library + # if: matrix.if + # run: |- + # make lib_install + # export PATH="$PATH:$(go env GOPATH)/bin" + # go run ./cmd/internal/build_libbox -target apple -platform ${{ matrix.platform }} + # mv Libbox.xcframework clients/apple + # - name: Update macOS version + # if: matrix.if && matrix.name == 'macOS' && github.event_name == 'workflow_dispatch' + # run: |- + # MACOS_PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_macos_project_version) + # echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" + # echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" >> "$GITHUB_ENV" + # - name: Update version + # if: matrix.if && matrix.name != 'iOS' + # run: |- + # go run -v ./cmd/internal/update_apple_version --ci + # - name: Build + # if: matrix.if + # run: |- + # cd clients/apple + # xcodebuild archive \ + # -scheme "${{ matrix.scheme }}" \ + # -configuration Release \ + # -destination "${{ matrix.destination }}" \ + # -archivePath "${{ matrix.archive }}" \ + # -allowProvisioningUpdates \ + # -authenticationKeyPath $ASC_KEY_PATH \ + # -authenticationKeyID $ASC_KEY_ID \ + # -authenticationKeyIssuerID $ASC_KEY_ISSUER_ID + # - name: Upload to App Store Connect + # if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch' + # run: |- + # go run -v ./cmd/internal/app_store_connect cancel_app_store ${{ matrix.platform }} + # cd clients/apple + # xcodebuild -exportArchive \ + # -archivePath "${{ matrix.archive }}" \ + # -exportOptionsPlist ${{ matrix.upload }} \ + # -allowProvisioningUpdates \ + # -authenticationKeyPath $ASC_KEY_PATH \ + # -authenticationKeyID $ASC_KEY_ID \ + # -authenticationKeyIssuerID $ASC_KEY_ISSUER_ID + # - name: Publish to TestFlight + # if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch' && github.ref =='refs/heads/dev-next' + # run: |- + # go run -v ./cmd/internal/app_store_connect publish_testflight ${{ matrix.platform }} + # - name: Build image + # if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch' + # run: |- + # pushd clients/apple + # xcodebuild -exportArchive \ + # -archivePath "${{ matrix.archive }}" \ + # -exportOptionsPlist ${{ matrix.export }} \ + # -exportPath "${{ matrix.export_path }}" + # brew install create-dmg + # create-dmg \ + # --volname "sing-box" \ + # --volicon "${{ matrix.export_path }}/SFM.app/Contents/Resources/AppIcon.icns" \ + # --icon "SFM.app" 0 0 \ + # --hide-extension "SFM.app" \ + # --app-drop-link 0 0 \ + # --skip-jenkins \ + # SFM.dmg "${{ matrix.export_path }}/SFM.app" + # xcrun notarytool submit "SFM.dmg" --wait --keychain-profile "notarytool-password" + # cd "${{ matrix.archive }}" + # zip -r SFM.dSYMs.zip dSYMs + # popd + + # mkdir -p dist + # cp clients/apple/SFM.dmg "dist/SFM-${VERSION}-universal.dmg" + # cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/SFM-${VERSION}-universal.dSYMs.zip" + # - name: Upload image + # if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch' + # uses: actions/upload-artifact@v4 + # with: + # name: binary-macos-dmg + # path: 'dist' + # retention-days: 1 + # upload: + # name: Upload builds + # if: "!failure() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')" + # runs-on: ubuntu-latest + # needs: + # - calculate_version + # - build + # - build_darwin + # - build_windows + # # - build_android + # # - build_apple + # steps: + # - name: Checkout + # uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + # with: + # fetch-depth: 0 + # submodules: 'recursive' + # - name: Cache ghr + # uses: actions/cache@v4 + # id: cache-ghr + # with: + # path: | + # ~/go/bin/ghr + # key: ghr + # - name: Setup ghr + # if: steps.cache-ghr.outputs.cache-hit != 'true' + # run: |- + # cd $HOME + # git clone https://github.com/nekohasekai/ghr ghr + # cd ghr + # go install -v . + # - name: Set tag + # run: |- + # git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" + # git tag v${{ needs.calculate_version.outputs.version }} -f + # echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV" + # - name: Download builds + # uses: actions/download-artifact@v5 + # with: + # path: dist + # merge-multiple: true + # - name: Upload builds + # if: ${{ env.PUBLISHED == 'false' }} + # run: |- + # export PATH="$PATH:$HOME/go/bin" + # ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # - name: Replace builds + # if: ${{ env.PUBLISHED != 'false' }} + # run: |- + # export PATH="$PATH:$HOME/go/bin" + # ghr --replace -p 5 "v${VERSION}" dist + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/debug.yml b/.github/workflows/debug.yml deleted file mode 100644 index 003caf5564..0000000000 --- a/.github/workflows/debug.yml +++ /dev/null @@ -1,220 +0,0 @@ -name: Debug build - -on: - push: - branches: - - stable-next - - main-next - - dev-next - paths-ignore: - - '**.md' - - '.github/**' - - '!.github/workflows/debug.yml' - pull_request: - branches: - - stable-next - - main-next - - dev-next - -jobs: - build: - name: Debug build - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 - with: - fetch-depth: 0 - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: ^1.22 - continue-on-error: true - - name: Run Test - run: | - go test -v ./... - build_go118: - name: Debug build (Go 1.18) - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 - with: - fetch-depth: 0 - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: ~1.18 - - name: Cache go module - uses: actions/cache@v4 - with: - path: | - ~/go/pkg/mod - key: go118-${{ hashFiles('**/go.sum') }} - - name: Run Test - run: make ci_build_go118 - build_go120: - name: Debug build (Go 1.20) - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 - with: - fetch-depth: 0 - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: ~1.20 - - name: Cache go module - uses: actions/cache@v4 - with: - path: | - ~/go/pkg/mod - key: go120-${{ hashFiles('**/go.sum') }} - - name: Run Test - run: make ci_build_go120 - build_go121: - name: Debug build (Go 1.21) - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 - with: - fetch-depth: 0 - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: ~1.21 - - name: Cache go module - uses: actions/cache@v4 - with: - path: | - ~/go/pkg/mod - key: go121-${{ hashFiles('**/go.sum') }} - - name: Run Test - run: make ci_build - cross: - strategy: - matrix: - include: - # windows - - name: windows-amd64 - goos: windows - goarch: amd64 - goamd64: v1 - - name: windows-amd64-v3 - goos: windows - goarch: amd64 - goamd64: v3 - - name: windows-386 - goos: windows - goarch: 386 - - name: windows-arm64 - goos: windows - goarch: arm64 - - name: windows-arm32v7 - goos: windows - goarch: arm - goarm: 7 - - # linux - - name: linux-amd64 - goos: linux - goarch: amd64 - goamd64: v1 - - name: linux-amd64-v3 - goos: linux - goarch: amd64 - goamd64: v3 - - name: linux-386 - goos: linux - goarch: 386 - - name: linux-arm64 - goos: linux - goarch: arm64 - - name: linux-armv5 - goos: linux - goarch: arm - goarm: 5 - - name: linux-armv6 - goos: linux - goarch: arm - goarm: 6 - - name: linux-armv7 - goos: linux - goarch: arm - goarm: 7 - - name: linux-mips-softfloat - goos: linux - goarch: mips - gomips: softfloat - - name: linux-mips-hardfloat - goos: linux - goarch: mips - gomips: hardfloat - - name: linux-mipsel-softfloat - goos: linux - goarch: mipsle - gomips: softfloat - - name: linux-mipsel-hardfloat - goos: linux - goarch: mipsle - gomips: hardfloat - - name: linux-mips64 - goos: linux - goarch: mips64 - - name: linux-mips64el - goos: linux - goarch: mips64le - - name: linux-s390x - goos: linux - goarch: s390x - # darwin - - name: darwin-amd64 - goos: darwin - goarch: amd64 - goamd64: v1 - - name: darwin-amd64-v3 - goos: darwin - goarch: amd64 - goamd64: v3 - - name: darwin-arm64 - goos: darwin - goarch: arm64 - # freebsd - - name: freebsd-amd64 - goos: freebsd - goarch: amd64 - goamd64: v1 - - name: freebsd-amd64-v3 - goos: freebsd - goarch: amd64 - goamd64: v3 - - name: freebsd-386 - goos: freebsd - goarch: 386 - - name: freebsd-arm64 - goos: freebsd - goarch: arm64 - fail-fast: true - runs-on: ubuntu-latest - env: - GOOS: ${{ matrix.goos }} - GOARCH: ${{ matrix.goarch }} - GOAMD64: ${{ matrix.goamd64 }} - GOARM: ${{ matrix.goarm }} - GOMIPS: ${{ matrix.gomips }} - CGO_ENABLED: 0 - TAGS: with_clash_api,with_quic - steps: - - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 - with: - fetch-depth: 0 - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: ^1.21 - - name: Build - id: build - run: make \ No newline at end of file diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 8e75954d22..0ef3fe969f 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,16 +1,41 @@ -name: Build Docker Images +name: Publish Docker Images on: + #push: + # branches: + # - stable + # - testing release: types: - - released + - published workflow_dispatch: inputs: tag: description: "The tag version you want to build" + +env: + REGISTRY_IMAGE: ghcr.io/sagernet/sing-box + jobs: - build: + build_binary: + name: Build binary runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + include: + # Naive-enabled builds (musl) + - { arch: amd64, naive: true, docker_platform: "linux/amd64" } + - { arch: arm64, naive: true, docker_platform: "linux/arm64" } + - { arch: "386", naive: true, docker_platform: "linux/386" } + - { arch: arm, goarm: "7", naive: true, docker_platform: "linux/arm/v7" } + - { arch: mipsle, gomips: softfloat, naive: true, docker_platform: "linux/mipsle" } + - { arch: riscv64, naive: true, docker_platform: "linux/riscv64" } + - { arch: loong64, naive: true, docker_platform: "linux/loong64" } + # Non-naive builds + - { arch: arm, goarm: "6", docker_platform: "linux/arm/v6" } + - { arch: ppc64le, docker_platform: "linux/ppc64le" } + - { arch: s390x, docker_platform: "linux/s390x" } steps: - name: Get commit to build id: ref @@ -22,41 +47,249 @@ jobs: fi echo "ref=$ref" echo "ref=$ref" >> $GITHUB_OUTPUT - if [[ $ref == *"-"* ]]; then - latest=latest-beta + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + with: + ref: ${{ steps.ref.outputs.ref }} + fetch-depth: 0 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ~1.25.10 + - name: Clone cronet-go + if: matrix.naive + run: | + set -xeuo pipefail + CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION) + git init ~/cronet-go + git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git + git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION" + git -C ~/cronet-go checkout FETCH_HEAD + git -C ~/cronet-go submodule update --init --recursive --depth=1 + - name: Regenerate Debian keyring + if: matrix.naive + run: | + set -xeuo pipefail + rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg + cd ~/cronet-go + GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh + - name: Cache Chromium toolchain + if: matrix.naive + id: cache-chromium-toolchain + uses: actions/cache@v4 + with: + path: | + ~/cronet-go/naiveproxy/src/third_party/llvm-build/ + ~/cronet-go/naiveproxy/src/gn/out/ + ~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/ + ~/cronet-go/naiveproxy/src/out/sysroot-build/ + key: chromium-toolchain-${{ matrix.arch }}-musl-${{ hashFiles('.github/CRONET_GO_VERSION') }} + - name: Download Chromium toolchain + if: matrix.naive + run: | + set -xeuo pipefail + cd ~/cronet-go + go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain + - name: Set version + run: | + set -xeuo pipefail + VERSION=$(go run ./cmd/internal/read_tag) + echo "VERSION=${VERSION}" >> "${GITHUB_ENV}" + - name: Set Chromium toolchain environment + if: matrix.naive + run: | + set -xeuo pipefail + cd ~/cronet-go + go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $GITHUB_ENV + - name: Set build tags + run: | + set -xeuo pipefail + if [[ "${{ matrix.naive }}" == "true" ]]; then + TAGS="$(cat release/DEFAULT_BUILD_TAGS),with_musl" + else + TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS) + fi + echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" + - name: Set shared ldflags + run: | + echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}" + - name: Build (naive) + if: matrix.naive + run: | + set -xeuo pipefail + go build -v -trimpath -o sing-box -tags "${BUILD_TAGS}" \ + -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${VERSION}' ${LDFLAGS_SHARED} -s -w -buildid=" \ + ./cmd/sing-box + env: + CGO_ENABLED: "1" + GOOS: linux + GOARCH: ${{ matrix.arch }} + GOARM: ${{ matrix.goarm }} + GOMIPS: ${{ matrix.gomips }} + - name: Build (non-naive) + if: ${{ ! matrix.naive }} + run: | + set -xeuo pipefail + go build -v -trimpath -o sing-box -tags "${BUILD_TAGS}" \ + -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${VERSION}' ${LDFLAGS_SHARED} -s -w -buildid=" \ + ./cmd/sing-box + env: + CGO_ENABLED: "0" + GOOS: linux + GOARCH: ${{ matrix.arch }} + GOARM: ${{ matrix.goarm }} + - name: Prepare artifact + run: | + platform=${{ matrix.docker_platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + # Rename binary to include arch info for Dockerfile.binary + BINARY_NAME="sing-box-${{ matrix.arch }}" + if [[ -n "${{ matrix.goarm }}" ]]; then + BINARY_NAME="${BINARY_NAME}v${{ matrix.goarm }}" + fi + mv sing-box "${BINARY_NAME}" + echo "BINARY_NAME=${BINARY_NAME}" >> $GITHUB_ENV + - name: Upload binary + uses: actions/upload-artifact@v4 + with: + name: binary-${{ env.PLATFORM_PAIR }} + path: ${{ env.BINARY_NAME }} + if-no-files-found: error + retention-days: 1 + build_docker: + name: Build Docker image + runs-on: ubuntu-latest + needs: + - build_binary + strategy: + fail-fast: true + matrix: + include: + - { platform: "linux/amd64" } + - { platform: "linux/arm/v6" } + - { platform: "linux/arm/v7" } + - { platform: "linux/arm64" } + - { platform: "linux/386" } + # mipsle: no base Docker image available for this platform + - { platform: "linux/ppc64le" } + - { platform: "linux/riscv64" } + - { platform: "linux/s390x" } + - { platform: "linux/loong64", base_image: "ghcr.io/loong64/alpine:edge" } + steps: + - name: Get commit to build + id: ref + run: |- + if [[ -z "${{ github.event.inputs.tag }}" ]]; then + ref="${{ github.ref_name }}" else - latest=latest + ref="${{ github.event.inputs.tag }}" fi - echo "latest=$latest" - echo "latest=$latest" >> $GITHUB_OUTPUT + echo "ref=$ref" + echo "ref=$ref" >> $GITHUB_OUTPUT - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: ref: ${{ steps.ref.outputs.ref }} + fetch-depth: 0 + - name: Prepare + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + - name: Download binary + uses: actions/download-artifact@v5 + with: + name: binary-${{ env.PLATFORM_PAIR }} + path: . + - name: Prepare binary + run: | + # Find and make the binary executable + chmod +x sing-box-* + ls -la sing-box-* + - name: Setup QEMU + uses: docker/setup-qemu-action@v3 - name: Setup Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Setup QEMU for Docker Buildx - uses: docker/setup-qemu-action@v3 - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Docker metadata - id: metadata + - name: Docker meta + id: meta uses: docker/metadata-action@v5 with: - images: ghcr.io/sagernet/sing-box - - name: Build and release Docker images + images: ${{ env.REGISTRY_IMAGE }} + - name: Build and push by digest + id: build uses: docker/build-push-action@v6 with: - platforms: linux/386,linux/amd64,linux/arm64,linux/s390x + platforms: ${{ matrix.platform }} context: . - target: dist + file: Dockerfile.binary build-args: | - BUILDKIT_CONTEXT_KEEP_GIT_DIR=1 - tags: | - ghcr.io/sagernet/sing-box:${{ steps.ref.outputs.latest }} - ghcr.io/sagernet/sing-box:${{ steps.ref.outputs.ref }} - push: true + BASE_IMAGE=${{ matrix.base_image || 'alpine' }} + labels: ${{ steps.meta.outputs.labels }} + outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ env.PLATFORM_PAIR }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + merge: + if: github.event_name != 'push' + runs-on: ubuntu-latest + needs: + - build_docker + steps: + - name: Get commit to build + id: ref + run: |- + if [[ -z "${{ github.event.inputs.tag }}" ]]; then + ref="${{ github.ref_name }}" + else + ref="${{ github.event.inputs.tag }}" + fi + echo "ref=$ref" + echo "ref=$ref" >> $GITHUB_OUTPUT + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + with: + ref: ${{ steps.ref.outputs.ref }} + fetch-depth: 0 + - name: Detect track + run: bash .github/detect_track.sh + - name: Download digests + uses: actions/download-artifact@v5 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Create manifest list and push + if: github.event_name != 'push' + working-directory: /tmp/digests + run: | + docker buildx imagetools create \ + -t "${{ env.REGISTRY_IMAGE }}:${{ env.DOCKER_TAG }}" \ + -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.ref }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3eb31561a3..05b9e6776a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -3,35 +3,77 @@ name: Lint on: push: branches: - - stable-next - - main-next - - dev-next + - oldstable + - stable + - testing + - unstable paths-ignore: - '**.md' - '.github/**' - '!.github/workflows/lint.yml' pull_request: branches: - - stable-next - - main-next - - dev-next + - oldstable + - stable + - testing + - unstable + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }} + cancel-in-progress: true jobs: build: - name: Build + name: Lint ${{ matrix.goos }}/${{ matrix.goarch }} 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@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.22 + 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@v6 + uses: golangci/golangci-lint-action@v8 + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} with: version: latest args: --timeout=30m - install-mode: binary \ No newline at end of file + install-mode: binary + verify: false diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 1e6aeff363..768ea56800 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -1,39 +1,244 @@ -name: Release to Linux repository +name: Build Linux Packages on: + #push: + # branches: + # - stable + # - testing + workflow_dispatch: + inputs: + version: + description: "Version name" + required: true + type: string release: types: - published jobs: + calculate_version: + name: Calculate version + runs-on: ubuntu-latest + outputs: + version: ${{ steps.outputs.outputs.version }} + steps: + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + with: + fetch-depth: 0 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ~1.25.10 + - name: Check input version + if: github.event_name == 'workflow_dispatch' + run: |- + echo "version=${{ inputs.version }}" + echo "version=${{ inputs.version }}" >> "$GITHUB_ENV" + - name: Calculate version + if: github.event_name != 'workflow_dispatch' + run: |- + go run -v ./cmd/internal/read_tag --ci --nightly + - name: Set outputs + id: outputs + run: |- + echo "version=$version" >> "$GITHUB_OUTPUT" build: + name: Build binary runs-on: ubuntu-latest + needs: + - calculate_version + strategy: + matrix: + include: + # Naive-enabled builds (musl) + - { os: linux, arch: amd64, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64 } + - { os: linux, arch: arm64, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64 } + - { os: linux, arch: "386", naive: true, debian: i386, rpm: i386 } + - { os: linux, arch: arm, goarm: "7", naive: true, debian: armhf, rpm: armv7hl, pacman: armv7hl } + - { os: linux, arch: mipsle, gomips: softfloat, naive: true, debian: mipsel, rpm: mipsel } + - { os: linux, arch: riscv64, naive: true, debian: riscv64, rpm: riscv64 } + - { os: linux, arch: loong64, naive: true, debian: loongarch64, rpm: loongarch64 } + # Non-naive builds (unsupported architectures) + - { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl } + - { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el } + - { os: linux, arch: s390x, debian: s390x, rpm: s390x } + - { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le } steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 0 - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.22 - - name: Extract signing key + go-version: ~1.25.10 + - name: Clone cronet-go + if: matrix.naive + run: | + set -xeuo pipefail + CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION) + git init ~/cronet-go + git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git + git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION" + git -C ~/cronet-go checkout FETCH_HEAD + git -C ~/cronet-go submodule update --init --recursive --depth=1 + - name: Regenerate Debian keyring + if: matrix.naive + run: | + set -xeuo pipefail + rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg + cd ~/cronet-go + GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh + - name: Cache Chromium toolchain + if: matrix.naive + id: cache-chromium-toolchain + uses: actions/cache@v4 + with: + path: | + ~/cronet-go/naiveproxy/src/third_party/llvm-build/ + ~/cronet-go/naiveproxy/src/gn/out/ + ~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/ + ~/cronet-go/naiveproxy/src/out/sysroot-build/ + key: chromium-toolchain-${{ matrix.arch }}-musl-${{ hashFiles('.github/CRONET_GO_VERSION') }} + - name: Download Chromium toolchain + if: matrix.naive + run: | + set -xeuo pipefail + cd ~/cronet-go + go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain + - name: Set Chromium toolchain environment + if: matrix.naive + run: | + set -xeuo pipefail + cd ~/cronet-go + go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $GITHUB_ENV + - name: Set tag + run: |- + git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" + git tag v${{ needs.calculate_version.outputs.version }} -f + - name: Set build tags + run: | + set -xeuo pipefail + if [[ "${{ matrix.naive }}" == "true" ]]; then + TAGS="$(cat release/DEFAULT_BUILD_TAGS),with_musl" + else + TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS) + fi + echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" + - name: Set shared ldflags + run: | + echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}" + - name: Build (naive) + if: matrix.naive + run: | + set -xeuo pipefail + mkdir -p dist + go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ + -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \ + ./cmd/sing-box + env: + CGO_ENABLED: "1" + GOOS: linux + GOARCH: ${{ matrix.arch }} + GOARM: ${{ matrix.goarm }} + GOMIPS: ${{ matrix.gomips }} + GOMIPS64: ${{ matrix.gomips }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Build (non-naive) + if: ${{ ! matrix.naive }} + run: | + set -xeuo pipefail + mkdir -p dist + go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ + -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \ + ./cmd/sing-box + env: + CGO_ENABLED: "0" + GOOS: ${{ matrix.os }} + GOARCH: ${{ matrix.arch }} + GOARM: ${{ matrix.goarm }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Set mtime + run: |- + TZ=UTC touch -t '197001010000' dist/sing-box + - name: Detect track + run: bash .github/detect_track.sh + - name: Set version + run: |- + PKG_VERSION="${{ needs.calculate_version.outputs.version }}" + PKG_VERSION="${PKG_VERSION//-/\~}" + echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}" + - name: Package DEB + if: matrix.debian != '' + run: | + set -xeuo pipefail + sudo gem install fpm + sudo apt-get install -y debsigs + cp .fpm_systemd .fpm + fpm -t deb \ + --name "${NAME}" \ + -v "$PKG_VERSION" \ + -p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.debian }}.deb" \ + --architecture ${{ matrix.debian }} \ + dist/sing-box=/usr/bin/sing-box + curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff' + sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff' + rm -rf $HOME/.gnupg + gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import < $HOME/.gnupg/sagernet.key < $HOME/.rpmmacros <> "$GITHUB_ENV" EOF - echo "HOME=$HOME" >> "$GITHUB_ENV" - - name: Publish release - uses: goreleaser/goreleaser-action@v6 + rpmsign --addsign dist/*.rpm + - name: Cleanup + run: rm dist/sing-box + - name: Upload artifact + uses: actions/upload-artifact@v4 with: - distribution: goreleaser-pro - version: latest - args: release -f .goreleaser.fury.yaml --clean - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} - FURY_TOKEN: ${{ secrets.FURY_TOKEN }} - NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key - NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }} + path: "dist" + retention-days: 1 + upload: + name: Upload builds + runs-on: ubuntu-latest + needs: + - calculate_version + - build + steps: + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + with: + fetch-depth: 0 + - name: Set tag + run: |- + git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" + git tag v${{ needs.calculate_version.outputs.version }} -f + echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV" + - name: Download builds + uses: actions/download-artifact@v5 + with: + path: dist + merge-multiple: true + - name: Publish packages + if: github.event_name != 'push' + run: |- + ls dist | xargs -I {} curl -F "package=@dist/{}" https://${{ secrets.FURY_TOKEN }}@push.fury.io/sagernet/ diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index b6307da29f..1715a9434d 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -12,4 +12,5 @@ jobs: with: stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days' days-before-stale: 60 - days-before-close: 5 \ No newline at end of file + days-before-close: 5 + exempt-issue-labels: 'bug,enhancement' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000..cc9ee0ad80 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,55 @@ +name: Test + +on: + push: + branches: + - stable + - testing + - unstable + paths-ignore: + - '**.md' + - '.github/**' + - '!.github/workflows/test.yml' + pull_request: + branches: + - stable + - testing + - unstable + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }} + cancel-in-progress: true + +jobs: + test: + name: Test + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + go: + - ~1.24 + - ~1.25 + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go }} + - name: Set build tags and ldflags + shell: bash + run: | + echo "BUILD_TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)" >> "$GITHUB_ENV" + echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "$GITHUB_ENV" + - name: Test (unix) + if: matrix.os != 'windows-latest' + run: go test -v -exec sudo -tags "$BUILD_TAGS" -ldflags "$LDFLAGS_SHARED" ./... + - name: Test (windows) + if: matrix.os == 'windows-latest' + shell: bash + run: go test -v -tags "$BUILD_TAGS" -ldflags "$LDFLAGS_SHARED" ./... diff --git a/.gitignore b/.gitignore index 60eb851e10..e8ffb49298 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +_htest/ +/data/* +*.log +*.db /.idea/ /vendor/ /*.json @@ -12,7 +16,12 @@ /*.jar /*.aar /*.xcframework/ +/experimental/libbox/*.aar +/experimental/libbox/*.xcframework/ +/experimental/libbox/*.nupkg .DS_Store /config.d/ /venv/ - +CLAUDE.md +AGENTS.md +/.claude/ diff --git a/.gitmodules b/.gitmodules index 45ffb5639b..0dadb791ea 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,12 @@ -[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 +[submodule "replace/tailscale"] + path = replace/tailscale + url = git@github.com:hiddify/tailscale +[submodule "replace/psiphon-quic-go"] + path = replace/psiphon-quic-go + url = git@github.com:hiddify/psiphon-quic-go +[submodule "replace/psiphon-tls"] + path = replace/psiphon-tls + url = git@github.com:hiddify/psiphon-tls +[submodule "replace/wireguard-go"] + path = replace/wireguard-go + url = git@github.com:hiddify/wireguard-go diff --git a/.golangci.yml b/.golangci.yml index 6cf053f580..4991891518 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,28 +1,55 @@ +version: "2" +run: + go: "1.24" + build-tags: + - with_gvisor + - with_quic + - with_dhcp + - with_wireguard + - with_utls + - with_acme + - with_clash_api + - with_tailscale + - with_ccm + - with_ocm + - badlinkname + - tfogo_checklinkname0 linters: - disable-all: true + default: none enable: - - gofumpt - - govet - - gci - - staticcheck - - paralleltest - ineffassign - -linters-settings: - gci: - custom-order: true - sections: - - standard - - prefix(github.com/sagernet/) - - default - staticcheck: - checks: - - all - - -SA1003 - -run: - go: "1.23" - -issues: - exclude-dirs: - - transport/simple-obfs + - staticcheck + - unused + - modernize + settings: + modernize: + disable: + - omitzero # nested struct omitempty -> omitzero changes JSON output semantics + staticcheck: + checks: + - all + - -QF1008 # could remove embedded field "" from selector + - -ST1003 # should not use ALL_CAPS in Go names; use CamelCase instead + - -QF1001 # could apply De Morgan's law + exclusions: + generated: lax + presets: + - comments + - common-false-positives + paths: + - transport/simple-obfs + - \.pb\.go$ + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gci + - gofumpt + settings: + gci: + sections: + - standard + - prefix(github.com/sagernet/) + - default + custom-order: true diff --git a/.goreleaser.fury.yaml b/.goreleaser.fury.yaml deleted file mode 100644 index 831595d36d..0000000000 --- a/.goreleaser.fury.yaml +++ /dev/null @@ -1,86 +0,0 @@ -project_name: sing-box -builds: - - id: main - main: ./cmd/sing-box - flags: - - -v - - -trimpath - ldflags: - - -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid= - tags: - - with_gvisor - - with_quic - - with_dhcp - - with_wireguard - - with_ech - - with_utls - - with_reality_server - - with_acme - - with_clash_api - env: - - CGO_ENABLED=0 - targets: - - linux_386 - - linux_amd64_v1 - - linux_arm64 - - linux_arm_7 - - linux_s390x - - linux_riscv64 - mod_timestamp: '{{ .CommitTimestamp }}' -snapshot: - name_template: "{{ .Version }}.{{ .ShortCommit }}" -nfpms: - - &template - id: package - package_name: sing-box - file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}' - builds: - - main - homepage: https://sing-box.sagernet.org/ - maintainer: nekohasekai - description: The universal proxy platform. - license: GPLv3 or later - formats: - - deb - - rpm - priority: extra - contents: - - src: release/config/config.json - dst: /etc/sing-box/config.json - type: config - - src: release/config/sing-box.service - dst: /usr/lib/systemd/system/sing-box.service - - src: release/config/sing-box@.service - dst: /usr/lib/systemd/system/sing-box@.service - - src: LICENSE - dst: /usr/share/licenses/sing-box/LICENSE - deb: - signature: - key_file: "{{ .Env.NFPM_KEY_PATH }}" - fields: - Bugs: https://github.com/SagerNet/sing-box/issues - rpm: - signature: - key_file: "{{ .Env.NFPM_KEY_PATH }}" - conflicts: - - sing-box-beta - - id: package_beta - <<: *template - package_name: sing-box-beta - file_name_template: '{{ .ProjectName }}-beta_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}' - formats: - - deb - - rpm - conflicts: - - sing-box -release: - disable: true -furies: - - account: sagernet - ids: - - package - disable: "{{ not (not .Prerelease) }}" - - account: sagernet - ids: - - package_beta - disable: "{{ not .Prerelease }}" diff --git a/.goreleaser.yaml b/.goreleaser.yaml deleted file mode 100644 index 64c23c9d3e..0000000000 --- a/.goreleaser.yaml +++ /dev/null @@ -1,170 +0,0 @@ -project_name: sing-box -builds: - - &template - id: main - main: ./cmd/sing-box - flags: - - -v - - -trimpath - ldflags: - - -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid= - tags: - - with_gvisor - - with_quic - - with_dhcp - - with_wireguard - - with_ech - - with_utls - - with_reality_server - - with_acme - - with_clash_api - env: - - CGO_ENABLED=0 - targets: - - linux_386 - - linux_amd64_v1 - - linux_amd64_v3 - - linux_arm64 - - linux_arm_7 - - linux_s390x - - linux_riscv64 - - windows_amd64_v1 - - windows_amd64_v3 - - windows_386 - - windows_arm64 - - darwin_amd64_v1 - - darwin_arm64 - mod_timestamp: '{{ .CommitTimestamp }}' - - id: legacy - <<: *template - tags: - - with_gvisor - - with_quic - - with_dhcp - - with_wireguard - - with_utls - - with_reality_server - - with_acme - - with_clash_api - env: - - CGO_ENABLED=0 - - GOROOT={{ .Env.GOPATH }}/go1.20.14 - gobinary: "{{ .Env.GOPATH }}/go1.20.14/bin/go" - targets: - - windows_amd64_v1 - - windows_386 - - darwin_amd64_v1 - - id: android - <<: *template - env: - - CGO_ENABLED=1 - overrides: - - goos: android - goarch: arm - goarm: 7 - env: - - CC=armv7a-linux-androideabi21-clang - - CXX=armv7a-linux-androideabi21-clang++ - - goos: android - goarch: arm64 - env: - - CC=aarch64-linux-android21-clang - - CXX=aarch64-linux-android21-clang++ - - goos: android - goarch: 386 - env: - - CC=i686-linux-android21-clang - - CXX=i686-linux-android21-clang++ - - goos: android - goarch: amd64 - goamd64: v1 - env: - - CC=x86_64-linux-android21-clang - - CXX=x86_64-linux-android21-clang++ - targets: - - android_arm_7 - - android_arm64 - - android_386 - - android_amd64 -snapshot: - name_template: "{{ .Version }}.{{ .ShortCommit }}" -archives: - - &template - id: archive - builds: - - main - - android - format: tar.gz - format_overrides: - - goos: windows - format: zip - wrap_in_directory: true - files: - - LICENSE - name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}' - - id: archive-legacy - <<: *template - builds: - - legacy - name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-legacy' -nfpms: - - id: package - package_name: sing-box - file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}' - builds: - - main - homepage: https://sing-box.sagernet.org/ - maintainer: nekohasekai - description: The universal proxy platform. - license: GPLv3 or later - formats: - - deb - - rpm - - archlinux - priority: extra - contents: - - src: release/config/config.json - dst: /etc/sing-box/config.json - type: config - - src: release/config/sing-box.service - dst: /usr/lib/systemd/system/sing-box.service - - src: release/config/sing-box@.service - dst: /usr/lib/systemd/system/sing-box@.service - - src: LICENSE - dst: /usr/share/licenses/sing-box/LICENSE - deb: - signature: - key_file: "{{ .Env.NFPM_KEY_PATH }}" - fields: - Bugs: https://github.com/SagerNet/sing-box/issues - rpm: - signature: - key_file: "{{ .Env.NFPM_KEY_PATH }}" - overrides: - deb: - conflicts: - - sing-box-beta - rpm: - conflicts: - - sing-box-beta - -source: - enabled: false - name_template: '{{ .ProjectName }}-{{ .Version }}.source' - prefix_template: '{{ .ProjectName }}-{{ .Version }}/' -checksum: - disable: true - name_template: '{{ .ProjectName }}-{{ .Version }}.checksum' -signs: - - artifacts: checksum -release: - github: - owner: SagerNet - name: sing-box - draft: true - prerelease: auto - mode: replace - ids: - - archive - - package - skip_upload: true \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index db890fd439..c8600d573f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM golang:1.22-alpine AS builder +FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder LABEL maintainer="nekohasekai " COPY . /go/src/github.com/sagernet/sing-box WORKDIR /go/src/github.com/sagernet/sing-box @@ -12,16 +12,15 @@ RUN set -ex \ && apk add git build-base \ && export COMMIT=$(git rev-parse --short HEAD) \ && export VERSION=$(go run ./cmd/internal/read_tag) \ - && go build -v -trimpath -tags \ - "with_gvisor,with_quic,with_dhcp,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_clash_api" \ + && export TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS) \ + && export LDFLAGS_SHARED=$(cat release/LDFLAGS) \ + && go build -v -trimpath -tags "$TAGS" \ -o /go/bin/sing-box \ - -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \ + -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" $LDFLAGS_SHARED -s -w -buildid=" \ ./cmd/sing-box FROM --platform=$TARGETPLATFORM alpine AS dist LABEL maintainer="nekohasekai " RUN set -ex \ - && apk upgrade \ - && apk add bash tzdata ca-certificates \ - && rm -rf /var/cache/apk/* + && apk add --no-cache --upgrade bash tzdata ca-certificates nftables COPY --from=builder /go/bin/sing-box /usr/local/bin/sing-box ENTRYPOINT ["sing-box"] diff --git a/Dockerfile.binary b/Dockerfile.binary new file mode 100644 index 0000000000..78fc566728 --- /dev/null +++ b/Dockerfile.binary @@ -0,0 +1,14 @@ +ARG BASE_IMAGE=alpine +FROM ${BASE_IMAGE} +ARG TARGETARCH +ARG TARGETVARIANT +LABEL maintainer="nekohasekai " +RUN set -ex \ + && if command -v apk > /dev/null; then \ + apk add --no-cache --upgrade bash tzdata ca-certificates nftables; \ + else \ + apt-get update && apt-get install -y --no-install-recommends bash tzdata ca-certificates nftables \ + && rm -rf /var/lib/apt/lists/*; \ + fi +COPY sing-box-${TARGETARCH}${TARGETVARIANT} /usr/local/bin/sing-box +ENTRYPOINT ["sing-box"] diff --git a/Makefile b/Makefile index f6815470ae..4234ddb178 100644 --- a/Makefile +++ b/Makefile @@ -1,37 +1,37 @@ NAME = sing-box COMMIT = $(shell git rev-parse --short HEAD) -TAGS_GO118 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api -TAGS_GO120 = with_quic,with_utls -TAGS_GO121 = with_ech -TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121) -TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server +TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0,with_grpc,with_awg GOHOSTOS = $(shell go env GOHOSTOS) GOHOSTARCH = $(shell go env GOHOSTARCH) -VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag) +VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest) -PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid=" -MAIN_PARAMS = $(PARAMS) -tags $(TAGS) +LDFLAGS_SHARED = $(shell cat release/LDFLAGS) +PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' $(LDFLAGS_SHARED) -s -w -buildid=" +MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)" MAIN = ./cmd/sing-box PREFIX ?= $(shell go env GOPATH) +SING_FFI ?= sing-ffi +LIBBOX_FFI_CONFIG ?= ./experimental/libbox/ffi.json .PHONY: test release docs build build: + export GOTOOLCHAIN=local && \ go build $(MAIN_PARAMS) $(MAIN) -ci_build_go118: - go build $(PARAMS) $(MAIN) - go build $(PARAMS) -tags "$(TAGS_GO118)" $(MAIN) - -ci_build_go120: - go build $(PARAMS) $(MAIN) - go build $(PARAMS) -tags "$(TAGS_GO118),$(TAGS_GO120)" $(MAIN) +race: + export GOTOOLCHAIN=local && \ + go build -race $(MAIN_PARAMS) $(MAIN) ci_build: - go build $(PARAMS) $(MAIN) + export GOTOOLCHAIN=local && \ + go build $(PARAMS) $(MAIN) && \ go build $(MAIN_PARAMS) $(MAIN) +generate_completions: + go run -v --tags "$(TAGS),generate,generate_completions" $(MAIN) + install: go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN) @@ -40,6 +40,9 @@ fmt: @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 @@ -49,10 +52,10 @@ lint: 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/cmd/golangci-lint@latest + go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest proto: @go run ./cmd/internal/protogen @@ -63,6 +66,9 @@ proto_install: go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest +update_certificates: + go run ./cmd/internal/update_certificates + release: go run ./cmd/internal/build goreleaser release --clean --skip publish mkdir dist/release @@ -71,10 +77,9 @@ release: dist/*.deb \ dist/*.rpm \ dist/*_amd64.pkg.tar.zst \ - dist/*_amd64v3.pkg.tar.zst \ dist/*_arm64.pkg.tar.zst \ dist/release - ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release + ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release rm -r dist/release release_repo: @@ -87,89 +92,136 @@ update_android_version: go run ./cmd/internal/update_android_version build_android: - cd ../sing-box-for-android && ./gradlew :app:clean :app:assemblePlayRelease :app:assembleOtherRelease && ./gradlew --stop + cd ../sing-box-for-android && ./gradlew :app:clean :app:assembleOtherRelease :app:assembleOtherLegacyRelease && ./gradlew --stop upload_android: mkdir -p dist/release_android - cp ../sing-box-for-android/app/build/outputs/apk/play/release/*.apk dist/release_android - cp ../sing-box-for-android/app/build/outputs/apk/other/release/*-universal.apk dist/release_android - ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release_android + cp ../sing-box-for-android/app/build/outputs/apk/other/release/*.apk dist/release_android + cp ../sing-box-for-android/app/build/outputs/apk/otherLegacy/release/*.apk dist/release_android + ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android rm -rf dist/release_android release_android: lib_android update_android_version build_android upload_android publish_android: - cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle - -publish_android_appcenter: - cd ../sing-box-for-android && ./gradlew :app:appCenterAssembleAndUploadPlayRelease + cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle && ./gradlew --stop +# TODO: find why and remove `-destination 'generic/platform=iOS'` +# TODO: remove xcode clean when fix control widget fixed build_ios: cd ../sing-box-for-apple && \ rm -rf build/SFI.xcarchive && \ - xcodebuild archive -scheme SFI -configuration Release -archivePath build/SFI.xcarchive + xcodebuild clean -scheme SFI && \ + xcodebuild archive -scheme SFI -configuration Release -destination 'generic/platform=iOS' -archivePath build/SFI.xcarchive -allowProvisioningUpdates | xcbeautify | grep -A 10 -e "Archive Succeeded" -e "ARCHIVE FAILED" -e "❌" upload_ios_app_store: cd ../sing-box-for-apple && \ xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates +export_ios_ipa: + cd ../sing-box-for-apple && \ + xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFI && \ + cp build/SFI/sing-box.ipa dist/SFI.ipa + +upload_ios_ipa: + cd dist && \ + cp SFI.ipa "SFI-${VERSION}.ipa" && \ + ghr --replace --draft --prerelease "v${VERSION}" "SFI-${VERSION}.ipa" + release_ios: build_ios upload_ios_app_store build_macos: cd ../sing-box-for-apple && \ rm -rf build/SFM.xcarchive && \ - xcodebuild archive -scheme SFM -configuration Release -archivePath build/SFM.xcarchive + xcodebuild archive -scheme SFM -configuration Release -archivePath build/SFM.xcarchive -allowProvisioningUpdates | xcbeautify | grep -A 10 -e "Archive Succeeded" -e "ARCHIVE FAILED" -e "❌" upload_macos_app_store: cd ../sing-box-for-apple && \ - xcodebuild -exportArchive -archivePath build/SFM.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates + xcodebuild -exportArchive -archivePath build/SFM.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates release_macos: build_macos upload_macos_app_store -build_macos_independent: - cd ../sing-box-for-apple && \ - rm -rf build/SFT.System.xcarchive && \ - xcodebuild archive -scheme SFM.System -configuration Release -archivePath build/SFM.System.xcarchive +build_macos_standalone: + $(MAKE) -C ../sing-box-for-apple archive_macos_standalone -notarize_macos_independent: - cd ../sing-box-for-apple && \ - xcodebuild -exportArchive -archivePath "build/SFM.System.xcarchive" -exportOptionsPlist SFM.System/Upload.plist -allowProvisioningUpdates +build_macos_dmg: + $(MAKE) -C ../sing-box-for-apple build_macos_dmg -wait_notarize_macos_independent: - sleep 60 +build_macos_pkg: + $(MAKE) -C ../sing-box-for-apple build_macos_pkg -export_macos_independent: - rm -rf dist/SFM - mkdir -p dist/SFM - cd ../sing-box-for-apple && \ - xcodebuild -exportNotarizedApp -archivePath build/SFM.System.xcarchive -exportPath "../sing-box/dist/SFM" +notarize_macos_dmg: + $(MAKE) -C ../sing-box-for-apple notarize_macos_dmg -upload_macos_independent: - cd dist/SFM && \ - rm -f *.zip && \ - zip -ry "SFM-${VERSION}-universal.zip" SFM.app && \ - ghr --replace --draft --prerelease "v${VERSION}" *.zip +notarize_macos_pkg: + $(MAKE) -C ../sing-box-for-apple notarize_macos_pkg -release_macos_independent: build_macos_independent notarize_macos_independent wait_notarize_macos_independent export_macos_independent upload_macos_independent +upload_macos_dmg: + mkdir -p dist/SFM + cp ../sing-box-for-apple/build/SFM-Apple.dmg "dist/SFM/SFM-${VERSION}-Apple.dmg" + cp ../sing-box-for-apple/build/SFM-Intel.dmg "dist/SFM/SFM-${VERSION}-Intel.dmg" + cp ../sing-box-for-apple/build/SFM-Universal.dmg "dist/SFM/SFM-${VERSION}-Universal.dmg" + ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Apple.dmg" + ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Intel.dmg" + ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Universal.dmg" + +upload_macos_pkg: + mkdir -p dist/SFM + cp ../sing-box-for-apple/build/SFM-Apple.pkg "dist/SFM/SFM-${VERSION}-Apple.pkg" + cp ../sing-box-for-apple/build/SFM-Intel.pkg "dist/SFM/SFM-${VERSION}-Intel.pkg" + cp ../sing-box-for-apple/build/SFM-Universal.pkg "dist/SFM/SFM-${VERSION}-Universal.pkg" + ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Apple.pkg" + ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Intel.pkg" + ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Universal.pkg" + +upload_macos_dsyms: + 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" + +release_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 && \ - xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive + xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive -allowProvisioningUpdates | xcbeautify | grep -A 10 -e "Archive Succeeded" -e "ARCHIVE FAILED" -e "❌" upload_tvos_app_store: cd ../sing-box-for-apple && \ - xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates + xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates + +export_tvos_ipa: + cd ../sing-box-for-apple && \ + xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFT && \ + cp build/SFT/sing-box.ipa dist/SFT.ipa + +upload_tvos_ipa: + cd dist && \ + cp SFT.ipa "SFT-${VERSION}.ipa" && \ + ghr --replace --draft --prerelease "v${VERSION}" "SFT-${VERSION}.ipa" release_tvos: build_tvos upload_tvos_app_store update_apple_version: go run ./cmd/internal/update_apple_version -release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_independent +update_macos_version: + MACOS_PROJECT_VERSION=$(shell go run -v ./cmd/internal/app_store_connect next_macos_project_version) go run ./cmd/internal/update_apple_version + +release_apple: lib_apple update_apple_version release_ios release_macos release_tvos release_macos_standalone release_apple_beta: update_apple_version release_ios release_macos release_tvos +publish_testflight: + go run -v ./cmd/internal/app_store_connect publish_testflight $(filter-out $@,$(MAKECMDGOALS)) + +prepare_app_store: + go run -v ./cmd/internal/app_store_connect prepare_app_store + +publish_app_store: + go run -v ./cmd/internal/app_store_connect publish_app_store + test: @go test -v ./... && \ cd test && \ @@ -185,16 +237,21 @@ test_stdio: lib_android: go run ./cmd/internal/build_libbox -target android -lib_ios: - go run ./cmd/internal/build_libbox -target ios +lib_apple: + go run ./cmd/internal/build_libbox -target apple -lib: - go run ./cmd/internal/build_libbox -target android - go run ./cmd/internal/build_libbox -target ios +lib_windows: + $(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type csharp + +lib_android_new: + $(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type android + +lib_apple_new: + $(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type apple lib_install: - go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.3 - go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.3 + go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.12 + go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.12 docs: venv/bin/mkdocs serve @@ -203,8 +260,8 @@ publish_docs: venv/bin/mkdocs gh-deploy -m "Update" --force --ignore-version --no-history docs_install: - python -m venv venv - source ./venv/bin/activate && pip install --force-reinstall mkdocs-material=="9.*" mkdocs-static-i18n=="1.2.*" + python3 -m venv venv + source ./venv/bin/activate && pip install --force-reinstall mkdocs-material=="9.7.2" mkdocs-static-i18n=="1.2.*" clean: rm -rf bin dist sing-box @@ -213,4 +270,7 @@ clean: update: git fetch git reset FETCH_HEAD --hard - git clean -fdx \ No newline at end of file + git clean -fdx + +%: + @: diff --git a/README.md b/README.md index 27ad0919dd..04ed0abb55 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,21 @@ -# sing-box +# sing-box-extended -The universal proxy platform. +Sing-box with extended features. -[![Packaging status](https://repology.org/badge/vertical-allrepos/sing-box.svg)](https://repology.org/project/sing-box/versions) +## Features -## Support +* Amnezia 1.5 +* WARP +* Tunneling +* Mieru +* XHTTP +* SDNS (DNSCrypt) +* Extended Wireguard options +* Unified delay -https://community.sagernet.org/c/sing-box/ +## Examples + +https://github.com/shtorm-7/sing-box-extended/tree/extended/examples ## License @@ -28,4 +37,4 @@ along with this program. If not, see . In addition, no derivative work may use the name or imply association with this application without prior consent. -``` \ No newline at end of file +``` diff --git a/adapter/certificate.go b/adapter/certificate.go new file mode 100644 index 0000000000..dfed642dfd --- /dev/null +++ b/adapter/certificate.go @@ -0,0 +1,22 @@ +package adapter + +import ( + "context" + "crypto/x509" + + "github.com/sagernet/sing/service" +) + +type CertificateStore interface { + LifecycleService + Pool() *x509.CertPool + ExclusiveAnchors() bool +} + +func RootPoolFromContext(ctx context.Context) *x509.CertPool { + store := service.FromContext[CertificateStore](ctx) + if store == nil { + return nil + } + return store.Pool() +} diff --git a/adapter/certificate/adapter.go b/adapter/certificate/adapter.go new file mode 100644 index 0000000000..802020c1e4 --- /dev/null +++ b/adapter/certificate/adapter.go @@ -0,0 +1,21 @@ +package certificate + +type Adapter struct { + providerType string + providerTag string +} + +func NewAdapter(providerType string, providerTag string) Adapter { + return Adapter{ + providerType: providerType, + providerTag: providerTag, + } +} + +func (a *Adapter) Type() string { + return a.providerType +} + +func (a *Adapter) Tag() string { + return a.providerTag +} diff --git a/adapter/certificate/manager.go b/adapter/certificate/manager.go new file mode 100644 index 0000000000..e4b9b535bb --- /dev/null +++ b/adapter/certificate/manager.go @@ -0,0 +1,158 @@ +package certificate + +import ( + "context" + "os" + "sync" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/taskmonitor" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" +) + +var _ adapter.CertificateProviderManager = (*Manager)(nil) + +type Manager struct { + logger log.ContextLogger + registry adapter.CertificateProviderRegistry + access sync.Mutex + started bool + stage adapter.StartStage + providers []adapter.CertificateProviderService + providerByTag map[string]adapter.CertificateProviderService +} + +func NewManager(logger log.ContextLogger, registry adapter.CertificateProviderRegistry) *Manager { + return &Manager{ + logger: logger, + registry: registry, + providerByTag: make(map[string]adapter.CertificateProviderService), + } +} + +func (m *Manager) Start(stage adapter.StartStage) error { + m.access.Lock() + if m.started && m.stage >= stage { + panic("already started") + } + m.started = true + m.stage = stage + providers := m.providers + m.access.Unlock() + for _, provider := range providers { + name := "certificate-provider/" + provider.Type() + "[" + provider.Tag() + "]" + m.logger.Trace(stage, " ", name) + startTime := time.Now() + err := adapter.LegacyStart(provider, stage) + if err != nil { + return E.Cause(err, stage, " ", name) + } + m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") + } + return nil +} + +func (m *Manager) Close() error { + m.access.Lock() + defer m.access.Unlock() + if !m.started { + return nil + } + m.started = false + providers := m.providers + m.providers = nil + monitor := taskmonitor.New(m.logger, C.StopTimeout) + var err error + for _, provider := range providers { + name := "certificate-provider/" + provider.Type() + "[" + provider.Tag() + "]" + m.logger.Trace("close ", name) + startTime := time.Now() + monitor.Start("close ", name) + err = E.Append(err, provider.Close(), func(err error) error { + return E.Cause(err, "close ", name) + }) + monitor.Finish() + m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") + } + return err +} + +func (m *Manager) CertificateProviders() []adapter.CertificateProviderService { + m.access.Lock() + defer m.access.Unlock() + return m.providers +} + +func (m *Manager) Get(tag string) (adapter.CertificateProviderService, bool) { + m.access.Lock() + provider, found := m.providerByTag[tag] + m.access.Unlock() + return provider, found +} + +func (m *Manager) Remove(tag string) error { + m.access.Lock() + provider, found := m.providerByTag[tag] + if !found { + m.access.Unlock() + return os.ErrInvalid + } + delete(m.providerByTag, tag) + index := common.Index(m.providers, func(it adapter.CertificateProviderService) bool { + return it == provider + }) + if index == -1 { + panic("invalid certificate provider index") + } + m.providers = append(m.providers[:index], m.providers[index+1:]...) + started := m.started + m.access.Unlock() + if started { + return provider.Close() + } + return nil +} + +func (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag string, providerType string, options any) error { + provider, err := m.registry.Create(ctx, logger, tag, providerType, options) + if err != nil { + return err + } + m.access.Lock() + defer m.access.Unlock() + if m.started { + name := "certificate-provider/" + provider.Type() + "[" + provider.Tag() + "]" + for _, stage := range adapter.ListStartStages { + m.logger.Trace(stage, " ", name) + startTime := time.Now() + err = adapter.LegacyStart(provider, stage) + if err != nil { + return E.Cause(err, stage, " ", name) + } + m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") + } + } + if existsProvider, loaded := m.providerByTag[tag]; loaded { + if m.started { + err = existsProvider.Close() + if err != nil { + return E.Cause(err, "close certificate-provider/", existsProvider.Type(), "[", existsProvider.Tag(), "]") + } + } + existsIndex := common.Index(m.providers, func(it adapter.CertificateProviderService) bool { + return it == existsProvider + }) + if existsIndex == -1 { + panic("invalid certificate provider index") + } + m.providers = append(m.providers[:existsIndex], m.providers[existsIndex+1:]...) + } + m.providers = append(m.providers, provider) + m.providerByTag[tag] = provider + return nil +} diff --git a/adapter/certificate/registry.go b/adapter/certificate/registry.go new file mode 100644 index 0000000000..5a080f2ccc --- /dev/null +++ b/adapter/certificate/registry.go @@ -0,0 +1,72 @@ +package certificate + +import ( + "context" + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" +) + +type ConstructorFunc[T any] func(ctx context.Context, logger log.ContextLogger, tag string, options T) (adapter.CertificateProviderService, error) + +func Register[Options any](registry *Registry, providerType string, constructor ConstructorFunc[Options]) { + registry.register(providerType, func() any { + return new(Options) + }, func(ctx context.Context, logger log.ContextLogger, tag string, rawOptions any) (adapter.CertificateProviderService, error) { + var options *Options + if rawOptions != nil { + options = rawOptions.(*Options) + } + return constructor(ctx, logger, tag, common.PtrValueOrDefault(options)) + }) +} + +var _ adapter.CertificateProviderRegistry = (*Registry)(nil) + +type ( + optionsConstructorFunc func() any + constructorFunc func(ctx context.Context, logger log.ContextLogger, tag string, options any) (adapter.CertificateProviderService, error) +) + +type Registry struct { + access sync.Mutex + optionsType map[string]optionsConstructorFunc + constructor map[string]constructorFunc +} + +func NewRegistry() *Registry { + return &Registry{ + optionsType: make(map[string]optionsConstructorFunc), + constructor: make(map[string]constructorFunc), + } +} + +func (m *Registry) CreateOptions(providerType string) (any, bool) { + m.access.Lock() + defer m.access.Unlock() + optionsConstructor, loaded := m.optionsType[providerType] + if !loaded { + return nil, false + } + return optionsConstructor(), true +} + +func (m *Registry) Create(ctx context.Context, logger log.ContextLogger, tag string, providerType string, options any) (adapter.CertificateProviderService, error) { + m.access.Lock() + defer m.access.Unlock() + constructor, loaded := m.constructor[providerType] + if !loaded { + return nil, E.New("certificate provider type not found: " + providerType) + } + return constructor(ctx, logger, tag, options) +} + +func (m *Registry) register(providerType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) { + m.access.Lock() + defer m.access.Unlock() + m.optionsType[providerType] = optionsConstructor + m.constructor[providerType] = constructor +} diff --git a/adapter/certificate_darwin.go b/adapter/certificate_darwin.go new file mode 100644 index 0000000000..ddcdb55f77 --- /dev/null +++ b/adapter/certificate_darwin.go @@ -0,0 +1,17 @@ +//go:build darwin && cgo + +package adapter + +import "unsafe" + +type AppleAnchors interface { + Retain() AppleAnchors + Release() + // Ref returns the underlying CFArrayRef, or nil if the anchor set is empty. + Ref() unsafe.Pointer +} + +type AppleCertificateStore interface { + CertificateStore + AppleAnchors() AppleAnchors +} diff --git a/adapter/certificate_provider.go b/adapter/certificate_provider.go new file mode 100644 index 0000000000..70bdeb8838 --- /dev/null +++ b/adapter/certificate_provider.go @@ -0,0 +1,38 @@ +package adapter + +import ( + "context" + "crypto/tls" + + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" +) + +type CertificateProvider interface { + GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) +} + +type ACMECertificateProvider interface { + CertificateProvider + GetACMENextProtos() []string +} + +type CertificateProviderService interface { + Lifecycle + Type() string + Tag() string + CertificateProvider +} + +type CertificateProviderRegistry interface { + option.CertificateProviderOptionsRegistry + Create(ctx context.Context, logger log.ContextLogger, tag string, providerType string, options any) (CertificateProviderService, error) +} + +type CertificateProviderManager interface { + Lifecycle + CertificateProviders() []CertificateProviderService + Get(tag string) (CertificateProviderService, bool) + Remove(tag string) error + Create(ctx context.Context, logger log.ContextLogger, tag string, providerType string, options any) error +} diff --git a/adapter/conn_router.go b/adapter/conn_router.go deleted file mode 100644 index a87c45e8c5..0000000000 --- a/adapter/conn_router.go +++ /dev/null @@ -1,104 +0,0 @@ -package adapter - -import ( - "context" - "net" - - "github.com/sagernet/sing/common/logger" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -type ConnectionRouter interface { - RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error - RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error -} - -func NewRouteHandler( - metadata InboundContext, - router ConnectionRouter, - logger logger.ContextLogger, -) UpstreamHandlerAdapter { - return &routeHandlerWrapper{ - metadata: metadata, - router: router, - logger: logger, - } -} - -func NewRouteContextHandler( - router ConnectionRouter, - logger logger.ContextLogger, -) UpstreamHandlerAdapter { - return &routeContextHandlerWrapper{ - router: router, - logger: logger, - } -} - -var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil) - -type routeHandlerWrapper struct { - metadata InboundContext - router ConnectionRouter - logger logger.ContextLogger -} - -func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { - myMetadata := w.metadata - if metadata.Source.IsValid() { - myMetadata.Source = metadata.Source - } - if metadata.Destination.IsValid() { - myMetadata.Destination = metadata.Destination - } - return w.router.RouteConnection(ctx, conn, myMetadata) -} - -func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { - myMetadata := w.metadata - if metadata.Source.IsValid() { - myMetadata.Source = metadata.Source - } - if metadata.Destination.IsValid() { - myMetadata.Destination = metadata.Destination - } - return w.router.RoutePacketConnection(ctx, conn, myMetadata) -} - -func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) { - w.logger.ErrorContext(ctx, err) -} - -var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil) - -type routeContextHandlerWrapper struct { - router ConnectionRouter - logger logger.ContextLogger -} - -func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { - myMetadata := ContextFrom(ctx) - if metadata.Source.IsValid() { - myMetadata.Source = metadata.Source - } - if metadata.Destination.IsValid() { - myMetadata.Destination = metadata.Destination - } - return w.router.RouteConnection(ctx, conn, *myMetadata) -} - -func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { - myMetadata := ContextFrom(ctx) - if metadata.Source.IsValid() { - myMetadata.Source = metadata.Source - } - if metadata.Destination.IsValid() { - myMetadata.Destination = metadata.Destination - } - return w.router.RoutePacketConnection(ctx, conn, *myMetadata) -} - -func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) { - w.logger.ErrorContext(ctx, err) -} diff --git a/adapter/connections.go b/adapter/connections.go new file mode 100644 index 0000000000..a0b9c0ef88 --- /dev/null +++ b/adapter/connections.go @@ -0,0 +1,18 @@ +package adapter + +import ( + "context" + "net" + + N "github.com/sagernet/sing/common/network" +) + +type ConnectionManager interface { + Lifecycle + Count() int + CloseAll() + TrackConn(conn net.Conn) net.Conn + TrackPacketConn(conn net.PacketConn) net.PacketConn + NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc) + NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc) +} diff --git a/adapter/dns.go b/adapter/dns.go new file mode 100644 index 0000000000..a613de0c44 --- /dev/null +++ b/adapter/dns.go @@ -0,0 +1,107 @@ +package adapter + +import ( + "context" + "net/netip" + "time" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + "github.com/sagernet/sing/service" + + "github.com/miekg/dns" +) + +type DNSRouter interface { + Lifecycle + Exchange(ctx context.Context, message *dns.Msg, options DNSQueryOptions) (*dns.Msg, error) + Lookup(ctx context.Context, domain string, options DNSQueryOptions) ([]netip.Addr, error) + ClearCache() + LookupReverseMapping(ip netip.Addr) (string, bool) + ResetNetwork() +} + +type DNSClient interface { + Start() + Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(response *dns.Msg) bool) (*dns.Msg, error) + Lookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(response *dns.Msg) bool) ([]netip.Addr, error) + ClearCache() +} + +type DNSQueryOptions struct { + Transport DNSTransport + Strategy C.DomainStrategy + LookupStrategy C.DomainStrategy + DisableCache bool + DisableOptimisticCache bool + RewriteTTL *uint32 + Timeout time.Duration + ClientSubnet netip.Prefix +} + +func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (DNSQueryOptions, error) { + if options == nil || options.Server == "" { + return DNSQueryOptions{}, nil + } + transportManager := service.FromContext[DNSTransportManager](ctx) + transport, loaded := transportManager.Transport(options.Server) + if !loaded { + return DNSQueryOptions{}, E.New("domain resolver not found: " + options.Server) + } + return DNSQueryOptions{ + Transport: transport, + Strategy: C.DomainStrategy(options.Strategy), + DisableCache: options.DisableCache, + DisableOptimisticCache: options.DisableOptimisticCache, + RewriteTTL: options.RewriteTTL, + Timeout: time.Duration(options.Timeout), + ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}), + }, nil +} + +type RDRCStore interface { + LoadRDRC(transportName string, qName string, qType uint16) (rejected bool) + SaveRDRC(transportName string, qName string, qType uint16) error + SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger) +} + +type DNSCacheStore interface { + LoadDNSCache(transportName string, qName string, qType uint16) (rawMessage []byte, expireAt time.Time, loaded bool) + SaveDNSCache(transportName string, qName string, qType uint16, rawMessage []byte, expireAt time.Time) error + SaveDNSCacheAsync(transportName string, qName string, qType uint16, rawMessage []byte, expireAt time.Time, logger logger.Logger) + ClearDNSCache() error +} + +type DNSTransport interface { + Lifecycle + 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) +} + +type DNSTransportWithPreferredDomain interface { + DNSTransport + PreferredDomain(domain string) bool +} + +type DNSTransportRegistry interface { + option.DNSTransportOptionsRegistry + CreateDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) (DNSTransport, error) +} + +type DNSTransportManager interface { + Lifecycle + Transports() []DNSTransport + Transport(tag string) (DNSTransport, bool) + Default() DNSTransport + FakeIP() FakeIPTransport + Remove(tag string) error + Create(ctx context.Context, logger log.ContextLogger, tag string, outboundType string, options any) error +} diff --git a/adapter/endpoint.go b/adapter/endpoint.go new file mode 100644 index 0000000000..f09f08ce75 --- /dev/null +++ b/adapter/endpoint.go @@ -0,0 +1,28 @@ +package adapter + +import ( + "context" + + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" +) + +type Endpoint interface { + Lifecycle + Type() string + Tag() string + Outbound +} + +type EndpointRegistry interface { + option.EndpointOptionsRegistry + Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) (Endpoint, error) +} + +type EndpointManager interface { + Lifecycle + Endpoints() []Endpoint + Get(tag string) (Endpoint, bool) + Remove(tag string) error + Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) error +} diff --git a/adapter/endpoint/adapter.go b/adapter/endpoint/adapter.go new file mode 100644 index 0000000000..dafe4dca01 --- /dev/null +++ b/adapter/endpoint/adapter.go @@ -0,0 +1,54 @@ +package endpoint + +import ( + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" +) + +type Adapter struct { + endpointType string + endpointTag string + network []string + dependencies []string +} + +func NewAdapter(endpointType string, endpointTag string, network []string, dependencies []string) Adapter { + return Adapter{ + endpointType: endpointType, + endpointTag: endpointTag, + network: network, + dependencies: dependencies, + } +} + +func NewAdapterWithDialerOptions(endpointType string, endpointTag string, network []string, dialOptions option.DialerOptions) Adapter { + var dependencies []string + if dialOptions.Detour != "" { + dependencies = []string{dialOptions.Detour} + } + return NewAdapter(endpointType, endpointTag, network, dependencies) +} + +func (a *Adapter) Type() string { + return a.endpointType +} + +func (a *Adapter) Tag() string { + return a.endpointTag +} + +func (a *Adapter) Network() []string { + return a.network +} + +func (a *Adapter) Dependencies() []string { + return a.dependencies +} + +func (a *Adapter) DisplayType() string { + return C.ProxyDisplayName(a.Type()) +} + +func (a *Adapter) IsReady() bool { + return true +} diff --git a/adapter/endpoint/manager.go b/adapter/endpoint/manager.go new file mode 100644 index 0000000000..535c31b42d --- /dev/null +++ b/adapter/endpoint/manager.go @@ -0,0 +1,156 @@ +package endpoint + +import ( + "context" + "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" +) + +var _ adapter.EndpointManager = (*Manager)(nil) + +type Manager struct { + logger log.ContextLogger + registry adapter.EndpointRegistry + access sync.Mutex + started bool + stage adapter.StartStage + endpoints []adapter.Endpoint + endpointByTag map[string]adapter.Endpoint +} + +func NewManager(logger log.ContextLogger, registry adapter.EndpointRegistry) *Manager { + return &Manager{ + logger: logger, + registry: registry, + endpointByTag: make(map[string]adapter.Endpoint), + } +} + +func (m *Manager) Start(stage adapter.StartStage) error { + m.access.Lock() + defer m.access.Unlock() + if m.started && m.stage >= stage { + panic("already started") + } + m.started = true + m.stage = stage + if stage == adapter.StartStateStart { + // started with outbound manager + return nil + } + for _, endpoint := range m.endpoints { + name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]" + done := adapter.LogElapsed(m.logger, stage, " ", name) + err := adapter.LegacyStart(endpoint, stage) + done() + if err != nil { + return E.Cause(err, stage, " ", name) + } + } + return nil +} + +func (m *Manager) Close() error { + m.access.Lock() + defer m.access.Unlock() + if !m.started { + return nil + } + m.started = false + endpoints := m.endpoints + m.endpoints = nil + monitor := taskmonitor.New(m.logger, C.StopTimeout) + var err error + for _, endpoint := range endpoints { + name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]" + done := adapter.LogElapsed(m.logger, "close ", name) + monitor.Start("close ", name) + err = E.Append(err, endpoint.Close(), func(err error) error { + return E.Cause(err, "close ", name) + }) + monitor.Finish() + done() + } + return nil +} + +func (m *Manager) Endpoints() []adapter.Endpoint { + m.access.Lock() + defer m.access.Unlock() + return m.endpoints +} + +func (m *Manager) Get(tag string) (adapter.Endpoint, bool) { + m.access.Lock() + defer m.access.Unlock() + endpoint, found := m.endpointByTag[tag] + return endpoint, found +} + +func (m *Manager) Remove(tag string) error { + m.access.Lock() + endpoint, found := m.endpointByTag[tag] + if !found { + m.access.Unlock() + return os.ErrInvalid + } + delete(m.endpointByTag, tag) + index := common.Index(m.endpoints, func(it adapter.Endpoint) bool { + return it == endpoint + }) + if index == -1 { + panic("invalid endpoint index") + } + m.endpoints = append(m.endpoints[:index], m.endpoints[index+1:]...) + started := m.started + m.access.Unlock() + if started { + return endpoint.Close() + } + return nil +} + +func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) error { + endpoint, err := m.registry.Create(ctx, router, logger, tag, outboundType, options) + if err != nil { + return err + } + m.access.Lock() + defer m.access.Unlock() + if m.started { + name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]" + for _, stage := range adapter.ListStartStages { + done := adapter.LogElapsed(m.logger, stage, " ", name) + err = adapter.LegacyStart(endpoint, stage) + done() + if err != nil { + return E.Cause(err, stage, " ", name) + } + } + } + if existsEndpoint, loaded := m.endpointByTag[tag]; loaded { + if m.started { + err = existsEndpoint.Close() + if err != nil { + return E.Cause(err, "close endpoint/", existsEndpoint.Type(), "[", existsEndpoint.Tag(), "]") + } + } + existsIndex := common.Index(m.endpoints, func(it adapter.Endpoint) bool { + return it == existsEndpoint + }) + if existsIndex == -1 { + panic("invalid endpoint index") + } + m.endpoints = append(m.endpoints[:existsIndex], m.endpoints[existsIndex+1:]...) + } + m.endpoints = append(m.endpoints, endpoint) + m.endpointByTag[tag] = endpoint + return nil +} diff --git a/adapter/endpoint/registry.go b/adapter/endpoint/registry.go new file mode 100644 index 0000000000..92cb9025de --- /dev/null +++ b/adapter/endpoint/registry.go @@ -0,0 +1,72 @@ +package endpoint + +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, logger log.ContextLogger, tag string, options T) (adapter.Endpoint, error) + +func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) { + registry.register(outboundType, func() any { + return new(Options) + }, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Endpoint, error) { + var options *Options + if rawOptions != nil { + options = rawOptions.(*Options) + } + return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options)) + }) +} + +var _ adapter.EndpointRegistry = (*Registry)(nil) + +type ( + optionsConstructorFunc func() any + constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Endpoint, error) +) + +type Registry struct { + access sync.Mutex + optionsType map[string]optionsConstructorFunc + constructor map[string]constructorFunc +} + +func NewRegistry() *Registry { + return &Registry{ + optionsType: make(map[string]optionsConstructorFunc), + constructor: make(map[string]constructorFunc), + } +} + +func (m *Registry) CreateOptions(outboundType string) (any, bool) { + m.access.Lock() + defer m.access.Unlock() + optionsConstructor, loaded := m.optionsType[outboundType] + if !loaded { + return nil, false + } + return optionsConstructor(), true +} + +func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Endpoint, error) { + m.access.Lock() + defer m.access.Unlock() + constructor, loaded := m.constructor[outboundType] + if !loaded { + return nil, E.New("outbound type not found: " + outboundType) + } + return constructor(ctx, router, logger, tag, options) +} + +func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) { + m.access.Lock() + defer m.access.Unlock() + m.optionsType[outboundType] = optionsConstructor + m.constructor[outboundType] = constructor +} diff --git a/adapter/experimental.go b/adapter/experimental.go index 5e1cbd9d9d..1fb51fac46 100644 --- a/adapter/experimental.go +++ b/adapter/experimental.go @@ -4,35 +4,60 @@ import ( "bytes" "context" "encoding/binary" - "io" - "net" "time" - "github.com/sagernet/sing-box/common/urltest" - "github.com/sagernet/sing-dns" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/rw" + "github.com/sagernet/sing-box/hiddify/ipinfo" + "github.com/sagernet/sing/common/observable" + "github.com/sagernet/sing/common/varbin" ) type ClashServer interface { - Service - PreStarter + LifecycleService + ConnectionTracker Mode() string ModeList() []string - HistoryStorage() *urltest.HistoryStorage - RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker) - RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker) + SetModeUpdateHook(hook *observable.Subscriber[struct{}]) + HistoryStorage() URLTestHistoryStorage +} + +type URLTestHistory struct { + Time time.Time `json:"time"` + Delay uint16 `json:"delay"` + IpInfo *ipinfo.IpInfo `json:"ipinfo"` + IsFromCache bool `json:"from_cache"` +} + +type URLTestHistoryStorage interface { + SetHook(hook *observable.Subscriber[struct{}]) + LoadURLTestHistory(tag string) *URLTestHistory + DeleteURLTestHistory(tag string) + StoreURLTestHistory(tag string, history *URLTestHistory) *URLTestHistory + AddOnlyIpToHistory(tag string, history *URLTestHistory) + Close() error +} + +type V2RayServer interface { + LifecycleService + StatsService() ConnectionTracker } type CacheFile interface { - Service - PreStarter + LifecycleService StoreFakeIP() bool FakeIPStorage StoreRDRC() bool - dns.RDRCStore + RDRCStore + + StoreWARPConfig() bool + StoreMASQUEConfig() bool + + StoreDNS() bool + DNSCacheStore + + SetDisableExpire(disableExpire bool) + SetOptimisticTimeout(timeout time.Duration) LoadMode() string StoreMode(mode string) error @@ -40,51 +65,47 @@ type CacheFile interface { StoreSelected(group string, selected string) error LoadGroupExpand(group string) (isExpand bool, loaded bool) StoreGroupExpand(group string, expand bool) error - LoadRuleSet(tag string) *SavedRuleSet - SaveRuleSet(tag string, set *SavedRuleSet) error + LoadRuleSet(tag string) *SavedBinary + SaveRuleSet(tag string, set *SavedBinary) error + LoadBinary(tag string) *SavedBinary + SaveBinary(tag string, set *SavedBinary) error } -type SavedRuleSet struct { +type SavedBinary struct { Content []byte LastUpdated time.Time LastEtag string } -func (s *SavedRuleSet) MarshalBinary() ([]byte, error) { +func (s *SavedBinary) MarshalBinary() ([]byte, error) { var buffer bytes.Buffer err := binary.Write(&buffer, binary.BigEndian, uint8(1)) if err != nil { return nil, err } - err = rw.WriteUVariant(&buffer, uint64(len(s.Content))) + err = varbin.Write(&buffer, binary.BigEndian, s.Content) if err != nil { return nil, err } - buffer.Write(s.Content) err = binary.Write(&buffer, binary.BigEndian, s.LastUpdated.Unix()) if err != nil { return nil, err } - err = rw.WriteVString(&buffer, s.LastEtag) + err = varbin.Write(&buffer, binary.BigEndian, s.LastEtag) if err != nil { return nil, err } return buffer.Bytes(), nil } -func (s *SavedRuleSet) UnmarshalBinary(data []byte) error { +func (s *SavedBinary) UnmarshalBinary(data []byte) error { reader := bytes.NewReader(data) var version uint8 err := binary.Read(reader, binary.BigEndian, &version) if err != nil { return err } - contentLen, err := rw.ReadUVariant(reader) - if err != nil { - return err - } - s.Content = make([]byte, contentLen) - _, err = io.ReadFull(reader, s.Content) + err = varbin.Read(reader, binary.BigEndian, &s.Content) if err != nil { return err } @@ -94,17 +115,13 @@ func (s *SavedRuleSet) UnmarshalBinary(data []byte) error { return err } s.LastUpdated = time.Unix(lastUpdated, 0) - s.LastEtag, err = rw.ReadVString(reader) + err = varbin.Read(reader, binary.BigEndian, &s.LastEtag) if err != nil { return err } return nil } -type Tracker interface { - Leave() -} - type OutboundGroup interface { Outbound Now() string @@ -122,13 +139,3 @@ func OutboundTag(detour Outbound) string { } return detour.Tag() } - -type V2RayServer interface { - Service - StatsService() V2RayStatsService -} - -type V2RayStatsService interface { - RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn - RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn -} diff --git a/adapter/fakeip.go b/adapter/fakeip.go index 51247c32fe..0787c14642 100644 --- a/adapter/fakeip.go +++ b/adapter/fakeip.go @@ -3,12 +3,11 @@ package adapter import ( "net/netip" - "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common/logger" ) type FakeIPStore interface { - Service + SimpleLifecycle Contains(address netip.Addr) bool Create(domain string, isIPv6 bool) (netip.Addr, error) Lookup(address netip.Addr) (string, bool) @@ -27,6 +26,6 @@ type FakeIPStorage interface { } type FakeIPTransport interface { - dns.Transport + DNSTransport Store() FakeIPStore } diff --git a/adapter/handler.go b/adapter/handler.go index bc5bcfbb02..4f54d18666 100644 --- a/adapter/handler.go +++ b/adapter/handler.go @@ -5,28 +5,31 @@ import ( "net" "github.com/sagernet/sing/common/buf" - E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) type ConnectionHandler interface { - NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error + NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc) } type PacketHandler interface { - NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error + NewPacket(buffer *buf.Buffer, source M.Socksaddr) +} + +type PacketBatchHandler interface { + NewPacketBatch(buffers []*buf.Buffer, sources []M.Socksaddr) } type OOBPacketHandler interface { - NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata InboundContext) error + NewPacket(buffer *buf.Buffer, oob []byte, source M.Socksaddr) } type PacketConnectionHandler interface { - NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error + NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc) } type UpstreamHandlerAdapter interface { - N.TCPConnectionHandler - N.UDPConnectionHandler - E.Handler + N.TCPConnectionHandlerEx + N.UDPConnectionHandlerEx } diff --git a/adapter/http.go b/adapter/http.go new file mode 100644 index 0000000000..3de4f1ce33 --- /dev/null +++ b/adapter/http.go @@ -0,0 +1,43 @@ +package adapter + +import ( + "context" + "net/http" + "sync" + + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/logger" +) + +type HTTPTransport interface { + http.RoundTripper + CloseIdleConnections() + Reset() +} + +type HTTPClientManager interface { + ResolveTransport(ctx context.Context, logger logger.ContextLogger, options option.HTTPClientOptions) (HTTPTransport, error) + DefaultTransport() HTTPTransport + ResetNetwork() +} + +type HTTPStartContext struct { + access sync.Mutex + transports []HTTPTransport +} + +func NewHTTPStartContext() *HTTPStartContext { + return &HTTPStartContext{} +} + +func (c *HTTPStartContext) Register(transport HTTPTransport) { + c.access.Lock() + defer c.access.Unlock() + c.transports = append(c.transports, transport) +} + +func (c *HTTPStartContext) Close() { + for _, transport := range c.transports { + transport.CloseIdleConnections() + } +} diff --git a/adapter/inbound.go b/adapter/inbound.go index 063671c1a1..cfdf3d698c 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -4,64 +4,130 @@ import ( "context" "net" "net/netip" + "time" - "github.com/sagernet/sing-box/common/process" + "github.com/sagernet/sing-box/common/tlsspoof" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" + + "github.com/miekg/dns" ) type Inbound interface { - Service + Lifecycle Type() string Tag() string } -type InjectableInbound interface { +type TCPInjectableInbound interface { + Inbound + ConnectionHandler +} + +type UDPInjectableInbound interface { Inbound - Network() []string - NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error - NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error + PacketConnectionHandler +} + +type InboundRegistry interface { + option.InboundOptionsRegistry + Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) (Inbound, error) +} + +type InboundManager interface { + Lifecycle + Inbounds() []Inbound + Get(tag string) (Inbound, bool) + Remove(tag string) error + Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) error } type InboundContext struct { - Inbound string - InboundType string - IPVersion uint8 - Network string - Source M.Socksaddr - Destination M.Socksaddr - Domain string - Protocol string - 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 + + Protocol string + Domain string + Client string + SniffContext any + SnifferNames []string + SniffError error // cache - InboundDetour string - LastInbound string - OriginDestination M.Socksaddr - InboundOptions option.InboundOptions - DestinationAddresses []netip.Addr - SourceGeoIPCode string - GeoIPCode string - ProcessInfo *process.Info - QueryType uint16 - FakeIP bool + // Deprecated: implement in rule action + InboundDetour string + LastInbound string + OriginDestination M.Socksaddr + RouteOriginalDestination M.Socksaddr + // Deprecated: to be removed + //nolint:staticcheck + InboundOptions option.InboundOptions + UDPDisableDomainUnmapping bool + UDPConnect bool + UDPTimeout time.Duration + TLSFragment bool + TLSFragmentFallbackDelay time.Duration + TLSRecordFragment bool + TLSSpoof string + TLSSpoofMethod tlsspoof.Method + + NetworkStrategy *C.NetworkStrategy + NetworkType []C.InterfaceType + FallbackNetworkType []C.InterfaceType + FallbackDelay time.Duration + + DestinationAddresses []netip.Addr + DNSResponse *dns.Msg + DestinationAddressMatchFromResponse bool + SourceGeoIPCode string + GeoIPCode string + ProcessInfo *ConnectionOwner + SourceMACAddress net.HardwareAddr + SourceHostname string + QueryType uint16 + FakeIP bool // rule cache - IPCIDRMatchSource bool + IPCIDRMatchSource bool + IPCIDRAcceptEmpty bool + SourceAddressMatch bool SourcePortMatch bool DestinationAddressMatch bool DestinationPortMatch bool DidMatch bool IgnoreDestinationIPCIDRMatch bool + + RealOutbound string //H } +func (c *InboundContext) SetRealOutbound(tag string) { + c.RealOutbound = tag +} +func (c *InboundContext) GetRealOutbound() string { + return c.RealOutbound +} 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 @@ -69,6 +135,51 @@ func (c *InboundContext) ResetRuleCache() { c.DidMatch = false } +func (c *InboundContext) DNSResponseAddressesForMatch() []netip.Addr { + return DNSResponseAddresses(c.DNSResponse) +} + +func DNSResponseAddresses(response *dns.Msg) []netip.Addr { + if response == nil || response.Rcode != dns.RcodeSuccess { + return nil + } + addresses := make([]netip.Addr, 0, len(response.Answer)) + for _, rawRecord := range response.Answer { + switch record := rawRecord.(type) { + case *dns.A: + addr := M.AddrFromIP(record.A) + if addr.IsValid() { + addresses = append(addresses, addr) + } + case *dns.AAAA: + addr := M.AddrFromIP(record.AAAA) + if addr.IsValid() { + addresses = append(addresses, addr) + } + case *dns.HTTPS: + for _, value := range record.SVCB.Value { + switch hint := value.(type) { + case *dns.SVCBIPv4Hint: + for _, ip := range hint.Hint { + addr := M.AddrFromIP(ip).Unmap() + if addr.IsValid() { + addresses = append(addresses, addr) + } + } + case *dns.SVCBIPv6Hint: + for _, ip := range hint.Hint { + addr := M.AddrFromIP(ip) + if addr.IsValid() { + addresses = append(addresses, addr) + } + } + } + } + } + } + return addresses +} + type inboundContextKey struct{} func WithContext(ctx context.Context, inboundContext *InboundContext) context.Context { @@ -83,15 +194,6 @@ func ContextFrom(ctx context.Context) *InboundContext { return metadata.(*InboundContext) } -func AppendContext(ctx context.Context) (context.Context, *InboundContext) { - metadata := ContextFrom(ctx) - if metadata != nil { - return ctx, metadata - } - metadata = new(InboundContext) - return WithContext(ctx, metadata), metadata -} - func ExtendContext(ctx context.Context) (context.Context, *InboundContext) { var newMetadata InboundContext if metadata := ContextFrom(ctx); metadata != nil { @@ -102,8 +204,7 @@ func ExtendContext(ctx context.Context) (context.Context, *InboundContext) { func OverrideContext(ctx context.Context) context.Context { if metadata := ContextFrom(ctx); metadata != nil { - var newMetadata InboundContext - newMetadata = *metadata + newMetadata := *metadata return WithContext(ctx, &newMetadata) } return ctx diff --git a/adapter/inbound/adapter.go b/adapter/inbound/adapter.go new file mode 100644 index 0000000000..1426104afb --- /dev/null +++ b/adapter/inbound/adapter.go @@ -0,0 +1,21 @@ +package inbound + +type Adapter struct { + inboundType string + inboundTag string +} + +func NewAdapter(inboundType string, inboundTag string) Adapter { + return Adapter{ + inboundType: inboundType, + inboundTag: inboundTag, + } +} + +func (a *Adapter) Type() string { + return a.inboundType +} + +func (a *Adapter) Tag() string { + return a.inboundTag +} diff --git a/adapter/inbound/manager.go b/adapter/inbound/manager.go new file mode 100644 index 0000000000..d6567cdefc --- /dev/null +++ b/adapter/inbound/manager.go @@ -0,0 +1,158 @@ +package inbound + +import ( + "context" + "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" +) + +var _ adapter.InboundManager = (*Manager)(nil) + +type Manager struct { + logger log.ContextLogger + registry adapter.InboundRegistry + endpoint adapter.EndpointManager + access sync.Mutex + started bool + stage adapter.StartStage + inbounds []adapter.Inbound + inboundByTag map[string]adapter.Inbound +} + +func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry, endpoint adapter.EndpointManager) *Manager { + return &Manager{ + logger: logger, + registry: registry, + endpoint: endpoint, + inboundByTag: make(map[string]adapter.Inbound), + } +} + +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 + inbounds := m.inbounds + m.access.Unlock() + for _, inbound := range inbounds { + name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]" + done := adapter.LogElapsed(m.logger, stage, " ", name) + err := adapter.LegacyStart(inbound, stage) + done() + if err != nil { + return E.Cause(err, stage, " ", name) + } + } + return nil +} + +func (m *Manager) Close() error { + m.access.Lock() + defer m.access.Unlock() + if !m.started { + return nil + } + m.started = false + inbounds := m.inbounds + m.inbounds = nil + monitor := taskmonitor.New(m.logger, C.StopTimeout) + var err error + for _, inbound := range inbounds { + name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]" + done := adapter.LogElapsed(m.logger, "close ", name) + monitor.Start("close ", name) + err = E.Append(err, inbound.Close(), func(err error) error { + return E.Cause(err, "close ", name) + }) + monitor.Finish() + done() + } + return nil +} + +func (m *Manager) Inbounds() []adapter.Inbound { + m.access.Lock() + defer m.access.Unlock() + return m.inbounds +} + +func (m *Manager) Get(tag string) (adapter.Inbound, bool) { + m.access.Lock() + inbound, found := m.inboundByTag[tag] + m.access.Unlock() + if found { + return inbound, true + } + return m.endpoint.Get(tag) +} + +func (m *Manager) Remove(tag string) error { + m.access.Lock() + inbound, found := m.inboundByTag[tag] + if !found { + m.access.Unlock() + return os.ErrInvalid + } + delete(m.inboundByTag, tag) + index := common.Index(m.inbounds, func(it adapter.Inbound) bool { + return it == inbound + }) + if index == -1 { + panic("invalid inbound index") + } + m.inbounds = append(m.inbounds[:index], m.inbounds[index+1:]...) + started := m.started + m.access.Unlock() + if started { + return inbound.Close() + } + return nil +} + +func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) error { + inbound, err := m.registry.Create(ctx, router, logger, tag, outboundType, options) + if err != nil { + return err + } + m.access.Lock() + defer m.access.Unlock() + if m.started { + name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]" + for _, stage := range adapter.ListStartStages { + done := adapter.LogElapsed(m.logger, stage, " ", name) + err = adapter.LegacyStart(inbound, stage) + done() + if err != nil { + return E.Cause(err, stage, " ", name) + } + } + } + if existsInbound, loaded := m.inboundByTag[tag]; loaded { + if m.started { + err = existsInbound.Close() + if err != nil { + return E.Cause(err, "close inbound/", existsInbound.Type(), "[", existsInbound.Tag(), "]") + } + } + existsIndex := common.Index(m.inbounds, func(it adapter.Inbound) bool { + return it == existsInbound + }) + if existsIndex == -1 { + panic("invalid inbound index") + } + m.inbounds = append(m.inbounds[:existsIndex], m.inbounds[existsIndex+1:]...) + } + m.inbounds = append(m.inbounds, inbound) + m.inboundByTag[tag] = inbound + return nil +} diff --git a/adapter/inbound/registry.go b/adapter/inbound/registry.go new file mode 100644 index 0000000000..01e367d8b0 --- /dev/null +++ b/adapter/inbound/registry.go @@ -0,0 +1,72 @@ +package inbound + +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, logger log.ContextLogger, tag string, options T) (adapter.Inbound, error) + +func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) { + registry.register(outboundType, func() any { + return new(Options) + }, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Inbound, error) { + var options *Options + if rawOptions != nil { + options = rawOptions.(*Options) + } + return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options)) + }) +} + +var _ adapter.InboundRegistry = (*Registry)(nil) + +type ( + optionsConstructorFunc func() any + constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, error) +) + +type Registry struct { + access sync.Mutex + optionsType map[string]optionsConstructorFunc + constructor map[string]constructorFunc +} + +func NewRegistry() *Registry { + return &Registry{ + optionsType: make(map[string]optionsConstructorFunc), + constructor: make(map[string]constructorFunc), + } +} + +func (m *Registry) CreateOptions(outboundType string) (any, bool) { + m.access.Lock() + defer m.access.Unlock() + optionsConstructor, loaded := m.optionsType[outboundType] + if !loaded { + return nil, false + } + return optionsConstructor(), true +} + +func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) { + m.access.Lock() + defer m.access.Unlock() + constructor, loaded := m.constructor[outboundType] + if !loaded { + return nil, E.New("outbound type not found: " + outboundType) + } + return constructor(ctx, router, logger, tag, options) +} + +func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) { + m.access.Lock() + defer m.access.Unlock() + m.optionsType[outboundType] = optionsConstructor + m.constructor[outboundType] = constructor +} diff --git a/adapter/inbound_test.go b/adapter/inbound_test.go new file mode 100644 index 0000000000..ec8c31289c --- /dev/null +++ b/adapter/inbound_test.go @@ -0,0 +1,45 @@ +package adapter + +import ( + "net" + "net/netip" + "testing" + + "github.com/miekg/dns" + "github.com/stretchr/testify/require" +) + +func TestDNSResponseAddressesUnmapsHTTPSIPv4Hints(t *testing.T) { + t.Parallel() + + ipv4Hint := net.ParseIP("1.1.1.1") + require.NotNil(t, ipv4Hint) + + response := &dns.Msg{ + MsgHdr: dns.MsgHdr{ + Response: true, + Rcode: dns.RcodeSuccess, + }, + Answer: []dns.RR{ + &dns.HTTPS{ + SVCB: dns.SVCB{ + Hdr: dns.RR_Header{ + Name: dns.Fqdn("example.com"), + Rrtype: dns.TypeHTTPS, + Class: dns.ClassINET, + Ttl: 60, + }, + Priority: 1, + Target: ".", + Value: []dns.SVCBKeyValue{ + &dns.SVCBIPv4Hint{Hint: []net.IP{ipv4Hint}}, + }, + }, + }, + }, + } + + addresses := DNSResponseAddresses(response) + require.Equal(t, []netip.Addr{netip.MustParseAddr("1.1.1.1")}, addresses) + require.True(t, addresses[0].Is4()) +} diff --git a/adapter/lifecycle.go b/adapter/lifecycle.go new file mode 100644 index 0000000000..2a6a8ed76c --- /dev/null +++ b/adapter/lifecycle.go @@ -0,0 +1,114 @@ +package adapter + +import ( + "reflect" + "strings" + "time" + + "github.com/sagernet/sing-box/log" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" +) + +type SimpleLifecycle interface { + Start() error + Close() error +} + +type StartStage uint8 + +const ( + StartStateInitialize StartStage = iota + StartStateStart + StartStatePostStart + StartStateStarted +) + +var ListStartStages = []StartStage{ + StartStateInitialize, + StartStateStart, + StartStatePostStart, + StartStateStarted, +} + +func (s StartStage) String() string { + switch s { + case StartStateInitialize: + return "initialize" + case StartStateStart: + return "start" + case StartStatePostStart: + return "post-start" + case StartStateStarted: + return "finish-start" + default: + panic("unknown stage") + } +} + +type Lifecycle interface { + Start(stage StartStage) error + Close() error +} + +type LifecycleService interface { + Name() string + Lifecycle +} + +func getServiceName(service any) string { + if named, ok := service.(interface { + Type() string + Tag() string + }); ok { + tag := named.Tag() + if tag != "" { + return named.Type() + "[" + tag + "]" + } + return named.Type() + } + t := reflect.TypeOf(service) + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + return strings.ToLower(t.Name()) +} + +func Start(logger log.ContextLogger, stage StartStage, services ...Lifecycle) error { + for _, service := range services { + name := getServiceName(service) + done := LogElapsed(logger, stage, " ", name) + err := service.Start(stage) + done() + if err != nil { + return err + } + } + return nil +} + +func StartNamed(logger log.ContextLogger, stage StartStage, services []LifecycleService) error { + for _, service := range services { + done := LogElapsed(logger, stage, " ", service.Name()) + err := service.Start(stage) + done() + if err != nil { + return E.Cause(err, stage.String(), " ", service.Name()) + } + } + 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)") + } +} diff --git a/adapter/lifecycle_legacy.go b/adapter/lifecycle_legacy.go new file mode 100644 index 0000000000..f8b25db6ec --- /dev/null +++ b/adapter/lifecycle_legacy.go @@ -0,0 +1,52 @@ +package adapter + +func LegacyStart(starter any, stage StartStage) error { + if lifecycle, isLifecycle := starter.(Lifecycle); isLifecycle { + return lifecycle.Start(stage) + } + switch stage { + case StartStateInitialize: + if preStarter, isPreStarter := starter.(interface { + PreStart() error + }); isPreStarter { + return preStarter.PreStart() + } + case StartStateStart: + if starter, isStarter := starter.(interface { + Start() error + }); isStarter { + return starter.Start() + } + case StartStateStarted: + if postStarter, isPostStarter := starter.(interface { + PostStart() error + }); isPostStarter { + return postStarter.PostStart() + } + } + return nil +} + +type lifecycleServiceWrapper struct { + SimpleLifecycle + name string +} + +func NewLifecycleService(service SimpleLifecycle, name string) LifecycleService { + return &lifecycleServiceWrapper{ + SimpleLifecycle: service, + name: name, + } +} + +func (l *lifecycleServiceWrapper) Name() string { + return l.name +} + +func (l *lifecycleServiceWrapper) Start(stage StartStage) error { + return LegacyStart(l.SimpleLifecycle, stage) +} + +func (l *lifecycleServiceWrapper) Close() error { + return l.SimpleLifecycle.Close() +} diff --git a/adapter/neighbor.go b/adapter/neighbor.go new file mode 100644 index 0000000000..3115b2935d --- /dev/null +++ b/adapter/neighbor.go @@ -0,0 +1,24 @@ +package adapter + +import ( + "net" + "net/netip" +) + +type NeighborEntry struct { + Address netip.Addr + MACAddress net.HardwareAddr + Hostname string +} + +type NeighborResolver interface { + LookupMAC(address netip.Addr) (net.HardwareAddr, bool) + LookupHostname(address netip.Addr) (string, bool) + LookupAddresses(hostname string) []netip.Addr + Start() error + Close() error +} + +type NeighborUpdateListener interface { + UpdateNeighborTable(entries []NeighborEntry) +} diff --git a/adapter/network.go b/adapter/network.go new file mode 100644 index 0000000000..14fe46c8b6 --- /dev/null +++ b/adapter/network.go @@ -0,0 +1,81 @@ +package adapter + +import ( + "encoding/hex" + "net" + "strings" + "time" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common/control" +) + +type NetworkManager interface { + Lifecycle + Initialize(ruleSets []RuleSet) + InterfaceFinder() control.InterfaceFinder + UpdateInterfaces() error + DefaultNetworkInterface() *NetworkInterface + NetworkInterfaces() []NetworkInterface + AutoDetectInterface() bool + AutoDetectInterfaceFunc() control.Func + ProtectFunc() control.Func + DefaultOptions() NetworkOptions + RegisterAutoRedirectOutputMark(mark uint32) error + AutoRedirectOutputMark() uint32 + AutoRedirectOutputMarkFunc() control.Func + NetworkMonitor() tun.NetworkUpdateMonitor + InterfaceMonitor() tun.DefaultInterfaceMonitor + PackageManager() tun.PackageManager + NeedWIFIState() bool + WIFIState() WIFIState + UpdateWIFIState() + ResetNetwork() +} + +type NetworkOptions struct { + BindInterface string + RoutingMark uint32 + DomainResolver string + DomainResolveOptions DNSQueryOptions + NetworkStrategy *C.NetworkStrategy + NetworkType []C.InterfaceType + FallbackNetworkType []C.InterfaceType + FallbackDelay time.Duration +} + +type InterfaceUpdateListener interface { + InterfaceUpdated() +} + +type WIFIState struct { + SSID string + BSSID string +} + +func NormalizeWIFIBSSID(bssid string) string { + bssid = strings.TrimSpace(bssid) + if bssid == "" { + return "" + } + parsed, err := net.ParseMAC(bssid) + if err == nil && len(parsed) == 6 { + return parsed.String() + } + if len(bssid) == 12 { + decoded, err := hex.DecodeString(bssid) + if err == nil { + return net.HardwareAddr(decoded).String() + } + } + return bssid +} + +type NetworkInterface struct { + control.Interface + Type C.InterfaceType + DNSServers []string + Expensive bool + Constrained bool +} diff --git a/adapter/outbound.go b/adapter/outbound.go index b6980fb97c..b0933d59c6 100644 --- a/adapter/outbound.go +++ b/adapter/outbound.go @@ -2,8 +2,12 @@ package adapter import ( "context" - "net" + "net/netip" + "time" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + tun "github.com/sagernet/sing-tun" N "github.com/sagernet/sing/common/network" ) @@ -14,7 +18,33 @@ type Outbound interface { Tag() string Network() []string Dependencies() []string + DisplayType() string + IsReady() bool + N.Dialer - NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error - NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error +} + +type OutboundWithPreferredRoutes interface { + Outbound + PreferredDomain(domain string) bool + PreferredAddress(address netip.Addr) bool +} + +type DirectRouteOutbound interface { + Outbound + NewDirectRouteConnection(metadata InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) +} + +type OutboundRegistry interface { + option.OutboundOptionsRegistry + CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error) +} + +type OutboundManager interface { + Lifecycle + Outbounds() []Outbound + Outbound(tag string) (Outbound, bool) + Default() Outbound + Remove(tag string) error + Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) error } diff --git a/adapter/outbound/adapter.go b/adapter/outbound/adapter.go new file mode 100644 index 0000000000..c1d1ec33b6 --- /dev/null +++ b/adapter/outbound/adapter.go @@ -0,0 +1,54 @@ +package outbound + +import ( + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" +) + +type Adapter struct { + outboundType string + outboundTag string + network []string + dependencies []string +} + +func NewAdapter(outboundType string, outboundTag string, network []string, dependencies []string) Adapter { + return Adapter{ + outboundType: outboundType, + outboundTag: outboundTag, + network: network, + dependencies: dependencies, + } +} + +func NewAdapterWithDialerOptions(outboundType string, outboundTag string, network []string, dialOptions option.DialerOptions) Adapter { + var dependencies []string + if dialOptions.Detour != "" { + dependencies = []string{dialOptions.Detour} + } + return NewAdapter(outboundType, outboundTag, network, dependencies) +} + +func (a *Adapter) Type() string { + return a.outboundType +} + +func (a *Adapter) Tag() string { + return a.outboundTag +} + +func (a *Adapter) Network() []string { + return a.network +} + +func (a *Adapter) Dependencies() []string { + return a.dependencies +} + +func (a *Adapter) DisplayType() string { + return C.ProxyDisplayName(a.Type()) +} + +func (a *Adapter) IsReady() bool { + return true +} diff --git a/adapter/outbound/manager.go b/adapter/outbound/manager.go new file mode 100644 index 0000000000..1bbad69e0c --- /dev/null +++ b/adapter/outbound/manager.go @@ -0,0 +1,310 @@ +package outbound + +import ( + "context" + "io" + "os" + "strings" + "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.OutboundManager = (*Manager)(nil) + +type Manager struct { + logger log.ContextLogger + registry adapter.OutboundRegistry + endpoint adapter.EndpointManager + defaultTag string + access sync.RWMutex + started bool + stage adapter.StartStage + outbounds []adapter.Outbound + outboundByTag map[string]adapter.Outbound + dependByTag map[string][]string + defaultOutbound adapter.Outbound + defaultOutboundFallback func() (adapter.Outbound, error) +} + +func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager { + return &Manager{ + logger: logger, + registry: registry, + endpoint: endpoint, + defaultTag: defaultTag, + outboundByTag: make(map[string]adapter.Outbound), + dependByTag: make(map[string][]string), + } +} + +func (m *Manager) Initialize(defaultOutboundFallback func() (adapter.Outbound, error)) { + m.defaultOutboundFallback = defaultOutboundFallback +} + +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 + if stage == adapter.StartStateStart { + if m.defaultTag != "" && m.defaultOutbound == nil { + defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag) + if !loaded { + m.access.Unlock() + return E.New("default outbound not found: ", m.defaultTag) + } + m.defaultOutbound = defaultEndpoint + } + if m.defaultOutbound == nil { + directOutbound, err := m.defaultOutboundFallback() + if err != nil { + m.access.Unlock() + return E.Cause(err, "create direct outbound for fallback") + } + m.outbounds = append(m.outbounds, directOutbound) + m.outboundByTag[directOutbound.Tag()] = directOutbound + m.defaultOutbound = directOutbound + } + outbounds := m.outbounds + m.access.Unlock() + return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...)) + } else { + outbounds := m.outbounds + m.access.Unlock() + for _, outbound := range outbounds { + name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]" + done := adapter.LogElapsed(m.logger, stage, " ", name) + err := adapter.LegacyStart(outbound, stage) + done() + if err != nil { + return E.Cause(err, stage, " ", name) + } + } + } + return nil +} + +func (m *Manager) startOutbounds(outbounds []adapter.Outbound) error { + monitor := taskmonitor.New(m.logger, C.StartTimeout) + started := make(map[string]bool) + for { + canContinue := false + startOne: + for _, outboundToStart := range outbounds { + outboundTag := outboundToStart.Tag() + if started[outboundTag] { + continue + } + dependencies := outboundToStart.Dependencies() + for _, dependency := range dependencies { + if !started[dependency] { + continue startOne + } + } + started[outboundTag] = true + canContinue = true + name := "outbound/" + outboundToStart.Type() + "[" + outboundTag + "]" + if starter, isStarter := outboundToStart.(adapter.Lifecycle); isStarter { + done := adapter.LogElapsed(m.logger, "start ", name) + monitor.Start("start ", name) + err := starter.Start(adapter.StartStateStart) + monitor.Finish() + done() + if err != nil { + return E.Cause(err, "start ", name) + } + } else if starter, isStarter := outboundToStart.(interface { + Start() error + }); isStarter { + done := adapter.LogElapsed(m.logger, "start ", name) + monitor.Start("start ", name) + err := starter.Start() + monitor.Finish() + done() + if err != nil { + return E.Cause(err, "start ", name) + } + } + } + if len(started) == len(outbounds) { + break + } + if canContinue { + continue + } + currentOutbound := common.Find(outbounds, func(it adapter.Outbound) bool { + return !started[it.Tag()] + }) + var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error + lintOutbound = func(oTree []string, oCurrent adapter.Outbound) error { + problemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool { + return !started[it] + }) + if common.Contains(oTree, problemOutboundTag) { + return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag) + } + m.access.Lock() + problemOutbound := m.outboundByTag[problemOutboundTag] + m.access.Unlock() + if problemOutbound == nil { + return E.New("dependency[", problemOutboundTag, "] not found for outbound[", oCurrent.Tag(), "]") + } + return lintOutbound(append(oTree, problemOutboundTag), problemOutbound) + } + return lintOutbound([]string{currentOutbound.Tag()}, currentOutbound) + } + 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 + outbounds := m.outbounds + m.outbounds = nil + m.access.Unlock() + var err 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) + monitor.Start("close ", name) + err = E.Append(err, closer.Close(), func(err error) error { + return E.Cause(err, "close ", name) + }) + monitor.Finish() + done() + } + } + return nil +} + +func (m *Manager) Outbounds() []adapter.Outbound { + m.access.RLock() + defer m.access.RUnlock() + return m.outbounds +} + +func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) { + m.access.RLock() + outbound, found := m.outboundByTag[tag] + m.access.RUnlock() + if found { + return outbound, true + } + return m.endpoint.Get(tag) +} + +func (m *Manager) Default() adapter.Outbound { + m.access.RLock() + defer m.access.RUnlock() + return m.defaultOutbound +} + +func (m *Manager) Remove(tag string) error { + m.access.Lock() + defer m.access.Unlock() + outbound, found := m.outboundByTag[tag] + if !found { + return os.ErrInvalid + } + delete(m.outboundByTag, tag) + index := common.Index(m.outbounds, func(it adapter.Outbound) bool { + return it == outbound + }) + if index == -1 { + panic("invalid inbound index") + } + m.outbounds = append(m.outbounds[:index], m.outbounds[index+1:]...) + started := m.started + if m.defaultOutbound == outbound { + if len(m.outbounds) > 0 { + m.defaultOutbound = m.outbounds[0] + m.logger.Info("updated default outbound to ", m.defaultOutbound.Tag()) + } else { + m.defaultOutbound = nil + } + } + dependBy := m.dependByTag[tag] + if len(dependBy) > 0 { + return E.New("outbound[", tag, "] is depended by ", strings.Join(dependBy, ", ")) + } + dependencies := outbound.Dependencies() + for _, dependency := range dependencies { + if len(m.dependByTag[dependency]) == 1 { + delete(m.dependByTag, dependency) + } else { + m.dependByTag[dependency] = common.Filter(m.dependByTag[dependency], func(it string) bool { + return it != tag + }) + } + } + if started { + return common.Close(outbound) + } + return nil +} + +func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, inboundType string, options any) error { + if tag == "" { + return os.ErrInvalid + } + outbound, err := m.registry.CreateOutbound(ctx, router, logger, tag, inboundType, options) + if err != nil { + return err + } + if m.started { + name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]" + for _, stage := range adapter.ListStartStages { + done := adapter.LogElapsed(m.logger, stage, " ", name) + err = adapter.LegacyStart(outbound, stage) + done() + if err != nil { + return E.Cause(err, stage, " ", name) + } + } + } + m.access.Lock() + defer m.access.Unlock() + if existsOutbound, loaded := m.outboundByTag[tag]; loaded { + if m.started { + err = common.Close(existsOutbound) + if err != nil { + return E.Cause(err, "close outbound/", existsOutbound.Type(), "[", existsOutbound.Tag(), "]") + } + } + existsIndex := common.Index(m.outbounds, func(it adapter.Outbound) bool { + return it == existsOutbound + }) + if existsIndex == -1 { + panic("invalid inbound index") + } + m.outbounds = append(m.outbounds[:existsIndex], m.outbounds[existsIndex+1:]...) + } + m.outbounds = append(m.outbounds, outbound) + m.outboundByTag[tag] = outbound + dependencies := outbound.Dependencies() + for _, dependency := range dependencies { + m.dependByTag[dependency] = append(m.dependByTag[dependency], tag) + } + if tag == m.defaultTag || (m.defaultTag == "" && m.defaultOutbound == nil) { + m.defaultOutbound = outbound + if m.started { + m.logger.Info("updated default outbound to ", outbound.Tag()) + } + } + return nil +} diff --git a/adapter/outbound/registry.go b/adapter/outbound/registry.go new file mode 100644 index 0000000000..8743ba1030 --- /dev/null +++ b/adapter/outbound/registry.go @@ -0,0 +1,72 @@ +package outbound + +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, logger log.ContextLogger, tag string, options T) (adapter.Outbound, error) + +func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) { + registry.register(outboundType, func() any { + return new(Options) + }, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Outbound, error) { + var options *Options + if rawOptions != nil { + options = rawOptions.(*Options) + } + return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options)) + }) +} + +var _ adapter.OutboundRegistry = (*Registry)(nil) + +type ( + optionsConstructorFunc func() any + constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Outbound, 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(outboundType string) (any, bool) { + r.access.Lock() + defer r.access.Unlock() + optionsConstructor, loaded := r.optionsType[outboundType] + if !loaded { + return nil, false + } + return optionsConstructor(), true +} + +func (r *Registry) CreateOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Outbound, error) { + r.access.Lock() + defer r.access.Unlock() + constructor, loaded := r.constructors[outboundType] + if !loaded { + return nil, E.New("outbound type not found: " + outboundType) + } + return constructor(ctx, router, logger, tag, options) +} + +func (r *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) { + r.access.Lock() + defer r.access.Unlock() + r.optionsType[outboundType] = optionsConstructor + r.constructors[outboundType] = constructor +} diff --git a/adapter/platform.go b/adapter/platform.go new file mode 100644 index 0000000000..e574b885a8 --- /dev/null +++ b/adapter/platform.go @@ -0,0 +1,78 @@ +package adapter + +import ( + "net/netip" + + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common/logger" +) + +type PlatformInterface interface { + Initialize(networkManager NetworkManager) error + + UsePlatformAutoDetectInterfaceControl() bool + AutoDetectInterfaceControl(fd int) error + + UsePlatformInterface() bool + OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) + + UsePlatformDefaultInterfaceMonitor() bool + CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor + + UsePlatformNetworkInterfaces() bool + NetworkInterfaces() ([]NetworkInterface, error) + + UnderNetworkExtension() bool + NetworkExtensionIncludeAllNetworks() bool + + ClearDNSCache() + RequestPermissionForWIFIState() error + ReadWIFIState() WIFIState + SystemCertificates() []string + + UsePlatformConnectionOwnerFinder() bool + FindConnectionOwner(request *FindConnectionOwnerRequest) (*ConnectionOwner, error) + + UsePlatformWIFIMonitor() bool + + UsePlatformNotification() bool + SendNotification(notification *Notification) error + + MyInterfaceAddress() []netip.Addr + + UsePlatformNeighborResolver() bool + StartNeighborMonitor(listener NeighborUpdateListener) error + CloseNeighborMonitor(listener NeighborUpdateListener) error +} + +type FindConnectionOwnerRequest struct { + IpProtocol int32 + SourceAddress string + SourcePort int32 + DestinationAddress string + DestinationPort int32 +} + +type ConnectionOwner struct { + ProcessID uint32 + UserId int32 + UserName string + ProcessPath string + AndroidPackageNames []string +} + +type Notification struct { + Identifier string + TypeName string + TypeID int32 + Title string + Subtitle string + Body string + OpenURL string +} + +type SystemProxyStatus struct { + Available bool + Enabled bool +} diff --git a/adapter/prestart.go b/adapter/prestart.go index 6a39aec3ff..b8e8da3083 100644 --- a/adapter/prestart.go +++ b/adapter/prestart.go @@ -1,9 +1 @@ package adapter - -type PreStarter interface { - PreStart() error -} - -type PostStarter interface { - PostStart() error -} diff --git a/adapter/router.go b/adapter/router.go index 54dc3396dc..24d45af006 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -2,118 +2,78 @@ package adapter import ( "context" - "net/http" - "net/netip" + "net" + "time" - "github.com/sagernet/sing-box/common/geoip" - "github.com/sagernet/sing-dns" "github.com/sagernet/sing-tun" - "github.com/sagernet/sing/common/control" N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/service" + "github.com/sagernet/sing/common/x/list" - mdns "github.com/miekg/dns" + "go4.org/netipx" ) type Router interface { - Service - PreStarter - PostStarter - - Outbounds() []Outbound - Outbound(tag string) (Outbound, bool) - DefaultOutbound(network string) (Outbound, error) - - FakeIPStore() FakeIPStore - + Lifecycle ConnectionRouter - - GeoIPReader() *geoip.Reader - LoadGeosite(code string) (Rule, error) - + PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration, supportBypass bool) (tun.DirectRouteDestination, error) + ConnectionRouterEx RuleSet(tag string) (RuleSet, bool) - - NeedWIFIState() bool - - Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error) - Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) - LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error) - ClearDNSCache() - - InterfaceFinder() control.InterfaceFinder - UpdateInterfaces() error - DefaultInterface() string - AutoDetectInterface() bool - AutoDetectInterfaceFunc() control.Func - DefaultMark() int - NetworkMonitor() tun.NetworkUpdateMonitor - InterfaceMonitor() tun.DefaultInterfaceMonitor - PackageManager() tun.PackageManager - WIFIState() WIFIState Rules() []Rule - - ClashServer() ClashServer - SetClashServer(server ClashServer) - - V2RayServer() V2RayServer - SetV2RayServer(server V2RayServer) - - ResetNetwork() error + NeedFindProcess() bool + NeedFindNeighbor() bool + NeighborResolver() NeighborResolver + AppendTracker(tracker ConnectionTracker) + ResetNetwork() } -func ContextWithRouter(ctx context.Context, router Router) context.Context { - return service.ContextWith(ctx, router) +type ConnectionTracker interface { + RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) net.Conn + RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) N.PacketConn } -func RouterFromContext(ctx context.Context) Router { - return service.FromContext[Router](ctx) +// Deprecated: Use ConnectionRouterEx instead. +type ConnectionRouter interface { + RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error + RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error } -type HeadlessRule interface { - Match(metadata *InboundContext) bool - String() string -} - -type Rule interface { - HeadlessRule - Service - Type() string - UpdateGeosite() error - Outbound() string -} - -type DNSRule interface { - Rule - DisableCache() bool - RewriteTTL() *uint32 - ClientSubnet() *netip.Prefix - WithAddressLimit() bool - MatchAddressLimit(metadata *InboundContext) bool +type ConnectionRouterEx interface { + ConnectionRouter + RouteConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc) + RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc) } type RuleSet interface { - StartContext(ctx context.Context, startContext RuleSetStartContext) error + Name() string + StartContext(ctx context.Context, startContext *HTTPStartContext) error + PostStart() error Metadata() RuleSetMetadata + ExtractIPSet() []*netipx.IPSet + IncRef() + DecRef() + Cleanup() + RegisterCallback(callback RuleSetUpdateCallback) *list.Element[RuleSetUpdateCallback] + UnregisterCallback(element *list.Element[RuleSetUpdateCallback]) Close() error HeadlessRule } -type RuleSetMetadata struct { - ContainsProcessRule bool - ContainsWIFIRule bool - ContainsIPCIDRRule bool -} +type RuleSetUpdateCallback func(it RuleSet) -type RuleSetStartContext interface { - HTTPClient(detour string, dialer N.Dialer) *http.Client - Close() +type DNSRuleSetUpdateValidator interface { + ValidateRuleSetMetadataUpdate(tag string, metadata RuleSetMetadata) error } -type InterfaceUpdateListener interface { - InterfaceUpdated() -} - -type WIFIState struct { - SSID string - BSSID string +// ip_version is not a headless-rule item, so ContainsIPVersionRule is intentionally absent. +type RuleSetMetadata struct { + ContainsProcessRule bool + ContainsWIFIRule bool + ContainsIPCIDRRule bool + ContainsDNSQueryTypeRule bool + // ContainsNonIPCIDRRule signals that the rule-set carries at least one sub-rule + // with a predicate other than destination ip_cidr / ip_set, so it can contribute + // to DNS pre-response matching. A rule-set where this is false and + // ContainsIPCIDRRule is true is "pure-IP" and matches nothing before a DNS + // response is available. + ContainsNonIPCIDRRule bool } diff --git a/adapter/rule.go b/adapter/rule.go new file mode 100644 index 0000000000..d5612006ea --- /dev/null +++ b/adapter/rule.go @@ -0,0 +1,41 @@ +package adapter + +import ( + C "github.com/sagernet/sing-box/constant" + + "github.com/miekg/dns" +) + +type HeadlessRule interface { + Match(metadata *InboundContext) bool + String() string +} + +type Rule interface { + HeadlessRule + SimpleLifecycle + Type() string + Action() RuleAction +} + +type DNSRule interface { + Rule + LegacyPreMatch(metadata *InboundContext) bool + WithAddressLimit() bool + MatchAddressLimit(metadata *InboundContext, response *dns.Msg) bool + BypassIfFailed() bool +} + +type RuleAction interface { + Type() string + String() string +} + +func IsFinalAction(action RuleAction) bool { + switch action.Type() { + case C.RuleActionTypeSniff, C.RuleActionTypeResolve, C.RuleActionTypeEvaluate: + return false + default: + return true + } +} diff --git a/adapter/service.go b/adapter/service.go index 5ed0798dd2..534bd7ebaf 100644 --- a/adapter/service.go +++ b/adapter/service.go @@ -1,6 +1,27 @@ package adapter +import ( + "context" + + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" +) + type Service interface { - Start() error - Close() error + Lifecycle + Type() string + Tag() string +} + +type ServiceRegistry interface { + option.ServiceOptionsRegistry + Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) (Service, error) +} + +type ServiceManager interface { + Lifecycle + Services() []Service + Get(tag string) (Service, bool) + Remove(tag string) error + Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error } diff --git a/adapter/service/adapter.go b/adapter/service/adapter.go new file mode 100644 index 0000000000..6c6242ea09 --- /dev/null +++ b/adapter/service/adapter.go @@ -0,0 +1,21 @@ +package service + +type Adapter struct { + serviceType string + serviceTag string +} + +func NewAdapter(serviceType string, serviceTag string) Adapter { + return Adapter{ + serviceType: serviceType, + serviceTag: serviceTag, + } +} + +func (a *Adapter) Type() string { + return a.serviceType +} + +func (a *Adapter) Tag() string { + return a.serviceTag +} diff --git a/adapter/service/manager.go b/adapter/service/manager.go new file mode 100644 index 0000000000..1a83c503f1 --- /dev/null +++ b/adapter/service/manager.go @@ -0,0 +1,153 @@ +package service + +import ( + "context" + "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" +) + +var _ adapter.ServiceManager = (*Manager)(nil) + +type Manager struct { + logger log.ContextLogger + registry adapter.ServiceRegistry + access sync.Mutex + started bool + stage adapter.StartStage + services []adapter.Service + serviceByTag map[string]adapter.Service +} + +func NewManager(logger log.ContextLogger, registry adapter.ServiceRegistry) *Manager { + return &Manager{ + logger: logger, + registry: registry, + serviceByTag: make(map[string]adapter.Service), + } +} + +func (m *Manager) Start(stage adapter.StartStage) error { + m.access.Lock() + if m.started && m.stage >= stage { + panic("already started") + } + m.started = true + m.stage = stage + services := m.services + m.access.Unlock() + for _, service := range services { + name := "service/" + service.Type() + "[" + service.Tag() + "]" + done := adapter.LogElapsed(m.logger, stage, " ", name) + err := adapter.LegacyStart(service, stage) + done() + if err != nil { + return E.Cause(err, stage, " ", name) + } + } + return nil +} + +func (m *Manager) Close() error { + m.access.Lock() + defer m.access.Unlock() + if !m.started { + return nil + } + m.started = false + services := m.services + m.services = nil + monitor := taskmonitor.New(m.logger, C.StopTimeout) + var err error + for _, service := range services { + name := "service/" + service.Type() + "[" + service.Tag() + "]" + done := adapter.LogElapsed(m.logger, "close ", name) + monitor.Start("close ", name) + err = E.Append(err, service.Close(), func(err error) error { + return E.Cause(err, "close ", name) + }) + monitor.Finish() + done() + } + return nil +} + +func (m *Manager) Services() []adapter.Service { + m.access.Lock() + defer m.access.Unlock() + return m.services +} + +func (m *Manager) Get(tag string) (adapter.Service, bool) { + m.access.Lock() + service, found := m.serviceByTag[tag] + m.access.Unlock() + return service, found +} + +func (m *Manager) Remove(tag string) error { + m.access.Lock() + service, found := m.serviceByTag[tag] + if !found { + m.access.Unlock() + return os.ErrInvalid + } + delete(m.serviceByTag, tag) + index := common.Index(m.services, func(it adapter.Service) bool { + return it == service + }) + if index == -1 { + panic("invalid service index") + } + m.services = append(m.services[:index], m.services[index+1:]...) + started := m.started + m.access.Unlock() + if started { + return service.Close() + } + return nil +} + +func (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error { + service, err := m.registry.Create(ctx, logger, tag, serviceType, options) + if err != nil { + return err + } + m.access.Lock() + defer m.access.Unlock() + if m.started { + name := "service/" + service.Type() + "[" + service.Tag() + "]" + for _, stage := range adapter.ListStartStages { + done := adapter.LogElapsed(m.logger, stage, " ", name) + err = adapter.LegacyStart(service, stage) + done() + if err != nil { + return E.Cause(err, stage, " ", name) + } + } + } + if existsService, loaded := m.serviceByTag[tag]; loaded { + if m.started { + err = existsService.Close() + if err != nil { + return E.Cause(err, "close service/", existsService.Type(), "[", existsService.Tag(), "]") + } + } + existsIndex := common.Index(m.services, func(it adapter.Service) bool { + return it == existsService + }) + if existsIndex == -1 { + panic("invalid service index") + } + m.services = append(m.services[:existsIndex], m.services[existsIndex+1:]...) + } + m.services = append(m.services, service) + m.serviceByTag[tag] = service + return nil +} diff --git a/adapter/service/registry.go b/adapter/service/registry.go new file mode 100644 index 0000000000..42fec82fc2 --- /dev/null +++ b/adapter/service/registry.go @@ -0,0 +1,72 @@ +package service + +import ( + "context" + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" +) + +type ConstructorFunc[T any] func(ctx context.Context, logger log.ContextLogger, tag string, options T) (adapter.Service, error) + +func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) { + registry.register(outboundType, func() any { + return new(Options) + }, func(ctx context.Context, logger log.ContextLogger, tag string, rawOptions any) (adapter.Service, error) { + var options *Options + if rawOptions != nil { + options = rawOptions.(*Options) + } + return constructor(ctx, logger, tag, common.PtrValueOrDefault(options)) + }) +} + +var _ adapter.ServiceRegistry = (*Registry)(nil) + +type ( + optionsConstructorFunc func() any + constructorFunc func(ctx context.Context, logger log.ContextLogger, tag string, options any) (adapter.Service, error) +) + +type Registry struct { + access sync.Mutex + optionsType map[string]optionsConstructorFunc + constructor map[string]constructorFunc +} + +func NewRegistry() *Registry { + return &Registry{ + optionsType: make(map[string]optionsConstructorFunc), + constructor: make(map[string]constructorFunc), + } +} + +func (m *Registry) CreateOptions(outboundType string) (any, bool) { + m.access.Lock() + defer m.access.Unlock() + optionsConstructor, loaded := m.optionsType[outboundType] + if !loaded { + return nil, false + } + return optionsConstructor(), true +} + +func (m *Registry) Create(ctx context.Context, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Service, error) { + m.access.Lock() + defer m.access.Unlock() + constructor, loaded := m.constructor[outboundType] + if !loaded { + return nil, E.New("outbound type not found: " + outboundType) + } + return constructor(ctx, logger, tag, options) +} + +func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) { + m.access.Lock() + defer m.access.Unlock() + m.optionsType[outboundType] = optionsConstructor + m.constructor[outboundType] = constructor +} diff --git a/adapter/ssm.go b/adapter/ssm.go new file mode 100644 index 0000000000..caab922141 --- /dev/null +++ b/adapter/ssm.go @@ -0,0 +1,18 @@ +package adapter + +import ( + "net" + + N "github.com/sagernet/sing/common/network" +) + +type ManagedSSMServer interface { + Inbound + SetTracker(tracker SSMTracker) + UpdateUsers(users []string, uPSKs []string) error +} + +type SSMTracker interface { + TrackConnection(conn net.Conn, metadata InboundContext) net.Conn + TrackPacketConnection(conn N.PacketConn, metadata InboundContext) N.PacketConn +} diff --git a/adapter/tailscale.go b/adapter/tailscale.go new file mode 100644 index 0000000000..e0ccb8ecda --- /dev/null +++ b/adapter/tailscale.go @@ -0,0 +1,56 @@ +package adapter + +import "context" + +type TailscaleEndpoint interface { + SubscribeTailscaleStatus(ctx context.Context, fn func(*TailscaleEndpointStatus)) error + StartTailscalePing(ctx context.Context, peerIP string, fn func(*TailscalePingResult)) error + SetTailscaleExitNode(ctx context.Context, stableID string) error +} + +type TailscalePingResult struct { + LatencyMs float64 + IsDirect bool + Endpoint string + DERPRegionID int32 + DERPRegionCode string + Error string +} + +type TailscaleEndpointStatus struct { + BackendState string + AuthURL string + NetworkName string + MagicDNSSuffix string + Self *TailscalePeer + ExitNode *TailscalePeer + UserGroups []*TailscaleUserGroup +} + +type TailscaleUserGroup struct { + UserID int64 + LoginName string + DisplayName string + ProfilePicURL string + Peers []*TailscalePeer +} + +type TailscalePeer struct { + StableID string + HostName string + DNSName string + OS string + TailscaleIPs []string + SSHHostKeys []string + Online bool + ExitNode bool + ExitNodeOption bool + ShareeNode bool + Expired bool + Active bool + RxBytes int64 + TxBytes int64 + UserID int64 + KeyExpiry int64 + LastSeen int64 +} diff --git a/adapter/time.go b/adapter/time.go index 3cb13fc1c8..be2631d855 100644 --- a/adapter/time.go +++ b/adapter/time.go @@ -3,6 +3,6 @@ package adapter import "time" type TimeService interface { - Service + SimpleLifecycle TimeFunc() func() time.Time } diff --git a/adapter/upstream.go b/adapter/upstream.go index 95dd98ea1b..348e0ca8dc 100644 --- a/adapter/upstream.go +++ b/adapter/upstream.go @@ -4,27 +4,24 @@ import ( "context" "net" - E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) type ( - ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error - PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error + ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc) + PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc) ) func NewUpstreamHandler( metadata InboundContext, connectionHandler ConnectionHandlerFunc, packetHandler PacketConnectionHandlerFunc, - errorHandler E.Handler, ) UpstreamHandlerAdapter { return &myUpstreamHandlerWrapper{ metadata: metadata, connectionHandler: connectionHandler, packetHandler: packetHandler, - errorHandler: errorHandler, } } @@ -34,82 +31,138 @@ type myUpstreamHandlerWrapper struct { metadata InboundContext connectionHandler ConnectionHandlerFunc packetHandler PacketConnectionHandlerFunc - errorHandler E.Handler } -func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { +func (w *myUpstreamHandlerWrapper) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { myMetadata := w.metadata - if metadata.Source.IsValid() { - myMetadata.Source = metadata.Source + if source.IsValid() { + myMetadata.Source = source } - if metadata.Destination.IsValid() { - myMetadata.Destination = metadata.Destination + if destination.IsValid() { + myMetadata.Destination = destination } - return w.connectionHandler(ctx, conn, myMetadata) + w.connectionHandler(ctx, conn, myMetadata, onClose) } -func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { +func (w *myUpstreamHandlerWrapper) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { myMetadata := w.metadata - if metadata.Source.IsValid() { - myMetadata.Source = metadata.Source + if source.IsValid() { + myMetadata.Source = source } - if metadata.Destination.IsValid() { - myMetadata.Destination = metadata.Destination + if destination.IsValid() { + myMetadata.Destination = destination } - return w.packetHandler(ctx, conn, myMetadata) + w.packetHandler(ctx, conn, myMetadata, onClose) } -func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) { - w.errorHandler.NewError(ctx, err) -} - -func UpstreamMetadata(metadata InboundContext) M.Metadata { - return M.Metadata{ - Source: metadata.Source, - Destination: metadata.Destination, - } -} +var _ UpstreamHandlerAdapter = (*myUpstreamContextHandlerWrapper)(nil) type myUpstreamContextHandlerWrapper struct { connectionHandler ConnectionHandlerFunc packetHandler PacketConnectionHandlerFunc - errorHandler E.Handler } func NewUpstreamContextHandler( connectionHandler ConnectionHandlerFunc, packetHandler PacketConnectionHandlerFunc, - errorHandler E.Handler, ) UpstreamHandlerAdapter { return &myUpstreamContextHandlerWrapper{ connectionHandler: connectionHandler, packetHandler: packetHandler, - errorHandler: errorHandler, } } -func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { - myMetadata := ContextFrom(ctx) - if metadata.Source.IsValid() { - myMetadata.Source = metadata.Source +func (w *myUpstreamContextHandlerWrapper) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + _, myMetadata := ExtendContext(ctx) + if source.IsValid() { + myMetadata.Source = source } - if metadata.Destination.IsValid() { - myMetadata.Destination = metadata.Destination + if destination.IsValid() { + myMetadata.Destination = destination } - return w.connectionHandler(ctx, conn, *myMetadata) + w.connectionHandler(ctx, conn, *myMetadata, onClose) } -func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { - myMetadata := ContextFrom(ctx) - if metadata.Source.IsValid() { - myMetadata.Source = metadata.Source +func (w *myUpstreamContextHandlerWrapper) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + _, myMetadata := ExtendContext(ctx) + if source.IsValid() { + myMetadata.Source = source + } + if destination.IsValid() { + myMetadata.Destination = destination } - if metadata.Destination.IsValid() { - myMetadata.Destination = metadata.Destination + w.packetHandler(ctx, conn, *myMetadata, onClose) +} + +func NewRouteHandler( + metadata InboundContext, + router ConnectionRouterEx, +) UpstreamHandlerAdapter { + return &routeHandlerWrapper{ + metadata: metadata, + router: router, } - return w.packetHandler(ctx, conn, *myMetadata) } -func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) { - w.errorHandler.NewError(ctx, err) +var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil) + +type routeHandlerWrapper struct { + metadata InboundContext + router ConnectionRouterEx +} + +func (r *routeHandlerWrapper) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + if source.IsValid() { + r.metadata.Source = source + } + if destination.IsValid() { + r.metadata.Destination = destination + } + r.router.RouteConnectionEx(ctx, conn, r.metadata, onClose) +} + +func (r *routeHandlerWrapper) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + if source.IsValid() { + r.metadata.Source = source + } + if destination.IsValid() { + r.metadata.Destination = destination + } + r.router.RoutePacketConnectionEx(ctx, conn, r.metadata, onClose) +} + +func NewRouteContextHandler( + router ConnectionRouterEx, +) UpstreamHandlerAdapter { + return &routeContextHandlerWrapper{ + router: router, + } +} + +var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil) + +type routeContextHandlerWrapper struct { + router ConnectionRouterEx +} + +func (r *routeContextHandlerWrapper) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + _, metadata := ExtendContext(ctx) + if source.IsValid() { + metadata.Source = source + } + if destination.IsValid() { + metadata.Destination = destination + } + r.router.RouteConnectionEx(ctx, conn, *metadata, onClose) +} + +func (r *routeContextHandlerWrapper) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + _, metadata := ExtendContext(ctx) + if source.IsValid() { + metadata.Source = source + } + if destination.IsValid() { + metadata.Destination = destination + } + r.router.RoutePacketConnectionEx(ctx, conn, *metadata, onClose) } diff --git a/adapter/upstream_legacy.go b/adapter/upstream_legacy.go new file mode 100644 index 0000000000..c9320ddb80 --- /dev/null +++ b/adapter/upstream_legacy.go @@ -0,0 +1,243 @@ +package adapter + +import ( + "context" + "net" + + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +type ( + // Deprecated + LegacyConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error + // Deprecated + LegacyPacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error +) + +// Deprecated +// +//nolint:staticcheck +type LegacyUpstreamHandlerAdapter interface { + N.TCPConnectionHandler + N.UDPConnectionHandler + E.Handler +} + +// Deprecated +// +//nolint:staticcheck +func NewLegacyUpstreamHandler( + metadata InboundContext, + connectionHandler LegacyConnectionHandlerFunc, + packetHandler LegacyPacketConnectionHandlerFunc, + errorHandler E.Handler, +) LegacyUpstreamHandlerAdapter { + return &legacyUpstreamHandlerWrapper{ + metadata: metadata, + connectionHandler: connectionHandler, + packetHandler: packetHandler, + errorHandler: errorHandler, + } +} + +var _ LegacyUpstreamHandlerAdapter = (*legacyUpstreamHandlerWrapper)(nil) + +// Deprecated: use NewUpstreamHandler instead. +// +//nolint:staticcheck +type legacyUpstreamHandlerWrapper struct { + metadata InboundContext + connectionHandler LegacyConnectionHandlerFunc + packetHandler LegacyPacketConnectionHandlerFunc + errorHandler E.Handler +} + +// Deprecated: use NewUpstreamHandler instead. +func (w *legacyUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { + myMetadata := w.metadata + if metadata.Source.IsValid() { + myMetadata.Source = metadata.Source + } + if metadata.Destination.IsValid() { + myMetadata.Destination = metadata.Destination + } + return w.connectionHandler(ctx, conn, myMetadata) +} + +// Deprecated: use NewUpstreamHandler instead. +func (w *legacyUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { + myMetadata := w.metadata + if metadata.Source.IsValid() { + myMetadata.Source = metadata.Source + } + if metadata.Destination.IsValid() { + myMetadata.Destination = metadata.Destination + } + return w.packetHandler(ctx, conn, myMetadata) +} + +// Deprecated: use NewUpstreamHandler instead. +func (w *legacyUpstreamHandlerWrapper) NewError(ctx context.Context, err error) { + w.errorHandler.NewError(ctx, err) +} + +// Deprecated: removed +func UpstreamMetadata(metadata InboundContext) M.Metadata { + return M.Metadata{ + Source: metadata.Source.Unwrap(), + Destination: metadata.Destination.Unwrap(), + } +} + +// Deprecated: Use NewUpstreamContextHandler instead. +type legacyUpstreamContextHandlerWrapper struct { + connectionHandler LegacyConnectionHandlerFunc + packetHandler LegacyPacketConnectionHandlerFunc + errorHandler E.Handler +} + +// Deprecated: Use NewUpstreamContextHandler instead. +func NewLegacyUpstreamContextHandler( + connectionHandler LegacyConnectionHandlerFunc, + packetHandler LegacyPacketConnectionHandlerFunc, + errorHandler E.Handler, +) LegacyUpstreamHandlerAdapter { + return &legacyUpstreamContextHandlerWrapper{ + connectionHandler: connectionHandler, + packetHandler: packetHandler, + errorHandler: errorHandler, + } +} + +// Deprecated: Use NewUpstreamContextHandler instead. +func (w *legacyUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { + myMetadata := ContextFrom(ctx) + if metadata.Source.IsValid() { + myMetadata.Source = metadata.Source + } + if metadata.Destination.IsValid() { + myMetadata.Destination = metadata.Destination + } + return w.connectionHandler(ctx, conn, *myMetadata) +} + +// Deprecated: Use NewUpstreamContextHandler instead. +func (w *legacyUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { + myMetadata := ContextFrom(ctx) + if metadata.Source.IsValid() { + myMetadata.Source = metadata.Source + } + if metadata.Destination.IsValid() { + myMetadata.Destination = metadata.Destination + } + return w.packetHandler(ctx, conn, *myMetadata) +} + +// Deprecated: Use NewUpstreamContextHandler instead. +func (w *legacyUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) { + w.errorHandler.NewError(ctx, err) +} + +// Deprecated: Use ConnectionRouterEx instead. +func NewLegacyRouteHandler( + metadata InboundContext, + router ConnectionRouter, + logger logger.ContextLogger, +) LegacyUpstreamHandlerAdapter { + return &legacyRouteHandlerWrapper{ + metadata: metadata, + router: router, + logger: logger, + } +} + +// Deprecated: Use ConnectionRouterEx instead. +func NewLegacyRouteContextHandler( + router ConnectionRouter, + logger logger.ContextLogger, +) LegacyUpstreamHandlerAdapter { + return &legacyRouteContextHandlerWrapper{ + router: router, + logger: logger, + } +} + +var _ LegacyUpstreamHandlerAdapter = (*legacyRouteHandlerWrapper)(nil) + +// Deprecated: Use ConnectionRouterEx instead. +// +//nolint:staticcheck +type legacyRouteHandlerWrapper struct { + metadata InboundContext + router ConnectionRouter + logger logger.ContextLogger +} + +// Deprecated: Use ConnectionRouterEx instead. +func (w *legacyRouteHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { + myMetadata := w.metadata + if metadata.Source.IsValid() { + myMetadata.Source = metadata.Source + } + if metadata.Destination.IsValid() { + myMetadata.Destination = metadata.Destination + } + return w.router.RouteConnection(ctx, conn, myMetadata) +} + +// Deprecated: Use ConnectionRouterEx instead. +func (w *legacyRouteHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { + myMetadata := w.metadata + if metadata.Source.IsValid() { + myMetadata.Source = metadata.Source + } + if metadata.Destination.IsValid() { + myMetadata.Destination = metadata.Destination + } + return w.router.RoutePacketConnection(ctx, conn, myMetadata) +} + +// Deprecated: Use ConnectionRouterEx instead. +func (w *legacyRouteHandlerWrapper) NewError(ctx context.Context, err error) { + w.logger.ErrorContext(ctx, err) +} + +var _ LegacyUpstreamHandlerAdapter = (*legacyRouteContextHandlerWrapper)(nil) + +// Deprecated: Use ConnectionRouterEx instead. +type legacyRouteContextHandlerWrapper struct { + router ConnectionRouter + logger logger.ContextLogger +} + +// Deprecated: Use ConnectionRouterEx instead. +func (w *legacyRouteContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { + myMetadata := ContextFrom(ctx) + if metadata.Source.IsValid() { + myMetadata.Source = metadata.Source + } + if metadata.Destination.IsValid() { + myMetadata.Destination = metadata.Destination + } + return w.router.RouteConnection(ctx, conn, *myMetadata) +} + +// Deprecated: Use ConnectionRouterEx instead. +func (w *legacyRouteContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { + myMetadata := ContextFrom(ctx) + if metadata.Source.IsValid() { + myMetadata.Source = metadata.Source + } + if metadata.Destination.IsValid() { + myMetadata.Destination = metadata.Destination + } + return w.router.RoutePacketConnection(ctx, conn, *myMetadata) +} + +// Deprecated: Use ConnectionRouterEx instead. +func (w *legacyRouteContextHandlerWrapper) NewError(ctx context.Context, err error) { + w.logger.ErrorContext(ctx, err) +} diff --git a/adapter/v2ray.go b/adapter/v2ray.go index 5a98d2e55a..d9370807fb 100644 --- a/adapter/v2ray.go +++ b/adapter/v2ray.go @@ -4,7 +4,6 @@ import ( "context" "net" - E "github.com/sagernet/sing/common/exceptions" N "github.com/sagernet/sing/common/network" ) @@ -16,8 +15,7 @@ type V2RayServerTransport interface { } type V2RayServerTransportHandler interface { - N.TCPConnectionHandler - E.Handler + N.TCPConnectionHandlerEx } type V2RayClientTransport interface { diff --git a/box.go b/box.go index 70235fd344..cde22600d9 100644 --- a/box.go +++ b/box.go @@ -9,45 +9,102 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + boxCertificate "github.com/sagernet/sing-box/adapter/certificate" + "github.com/sagernet/sing-box/adapter/endpoint" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/adapter/outbound" + boxService "github.com/sagernet/sing-box/adapter/service" + "github.com/sagernet/sing-box/common/certificate" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/httpclient" + "github.com/sagernet/sing-box/common/monitoring" "github.com/sagernet/sing-box/common/taskmonitor" + "github.com/sagernet/sing-box/common/tls" + "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/experimental" "github.com/sagernet/sing-box/experimental/cachefile" - "github.com/sagernet/sing-box/experimental/libbox/platform" - "github.com/sagernet/sing-box/inbound" + "github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-box/outbound" + "github.com/sagernet/sing-box/protocol/direct" + "github.com/sagernet/sing-box/protocol/hiddify/hinvalid" "github.com/sagernet/sing-box/route" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/pause" ) -var _ adapter.Service = (*Box)(nil) +var _ adapter.SimpleLifecycle = (*Box)(nil) type Box struct { - createdAt time.Time - router adapter.Router - inbounds []adapter.Inbound - outbounds []adapter.Outbound - logFactory log.Factory - logger log.ContextLogger - preServices1 map[string]adapter.Service - preServices2 map[string]adapter.Service - postServices map[string]adapter.Service - done chan struct{} + createdAt time.Time + logFactory log.Factory + logger log.ContextLogger + network *route.NetworkManager + endpoint *endpoint.Manager + inbound *inbound.Manager + outbound *outbound.Manager + service *boxService.Manager + certificateProvider *boxCertificate.Manager + dnsTransport *dns.TransportManager + dnsRouter *dns.Router + connection *route.ConnectionManager + router *route.Router + httpClientService adapter.LifecycleService + internalService []adapter.LifecycleService + done chan struct{} } type Options struct { option.Options Context context.Context - PlatformInterface platform.Interface PlatformLogWriter log.PlatformWriter } +func Context( + ctx context.Context, + inboundRegistry adapter.InboundRegistry, + outboundRegistry adapter.OutboundRegistry, + endpointRegistry adapter.EndpointRegistry, + dnsTransportRegistry adapter.DNSTransportRegistry, + serviceRegistry adapter.ServiceRegistry, + certificateProviderRegistry adapter.CertificateProviderRegistry, +) context.Context { + if service.FromContext[option.InboundOptionsRegistry](ctx) == nil || + service.FromContext[adapter.InboundRegistry](ctx) == nil { + ctx = service.ContextWith[option.InboundOptionsRegistry](ctx, inboundRegistry) + ctx = service.ContextWith[adapter.InboundRegistry](ctx, inboundRegistry) + } + if service.FromContext[option.OutboundOptionsRegistry](ctx) == nil || + service.FromContext[adapter.OutboundRegistry](ctx) == nil { + ctx = service.ContextWith[option.OutboundOptionsRegistry](ctx, outboundRegistry) + ctx = service.ContextWith[adapter.OutboundRegistry](ctx, outboundRegistry) + } + if service.FromContext[option.EndpointOptionsRegistry](ctx) == nil || + service.FromContext[adapter.EndpointRegistry](ctx) == nil { + ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry) + ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry) + } + if service.FromContext[adapter.DNSTransportRegistry](ctx) == nil { + ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry) + ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry) + } + if service.FromContext[adapter.ServiceRegistry](ctx) == nil { + ctx = service.ContextWith[option.ServiceOptionsRegistry](ctx, serviceRegistry) + ctx = service.ContextWith[adapter.ServiceRegistry](ctx, serviceRegistry) + } + if service.FromContext[adapter.CertificateProviderRegistry](ctx) == nil { + ctx = service.ContextWith[option.CertificateProviderOptionsRegistry](ctx, certificateProviderRegistry) + ctx = service.ContextWith[adapter.CertificateProviderRegistry](ctx, certificateProviderRegistry) + } + return ctx +} + func New(options Options) (*Box, error) { createdAt := time.Now() ctx := options.Context @@ -55,9 +112,39 @@ func New(options Options) (*Box, error) { ctx = context.Background() } ctx = service.ContextWithDefaultRegistry(ctx) + + endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx) + inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx) + outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx) + dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx) + serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx) + certificateProviderRegistry := service.FromContext[adapter.CertificateProviderRegistry](ctx) + + if endpointRegistry == nil { + return nil, E.New("missing endpoint registry in context") + } + if inboundRegistry == nil { + return nil, E.New("missing inbound registry in context") + } + if outboundRegistry == nil { + return nil, E.New("missing outbound registry in context") + } + if dnsTransportRegistry == nil { + return nil, E.New("missing DNS transport registry in context") + } + if serviceRegistry == nil { + return nil, E.New("missing service registry in context") + } + if certificateProviderRegistry == nil { + return nil, E.New("missing certificate provider registry in context") + } + ctx = pause.WithDefaultManager(ctx) experimentalOptions := common.PtrValueOrDefault(options.Experimental) - applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug)) + err := applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug)) + if err != nil { + return nil, err + } var needCacheFile bool var needClashAPI bool var needV2RayAPI bool @@ -70,8 +157,12 @@ func New(options Options) (*Box, error) { if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" { needV2RayAPI = true } + if experimentalOptions.UnifiedDelay != nil && experimentalOptions.UnifiedDelay.Enabled { + ctx = urltest.ContextWithIsUnifiedDelay(ctx) + } + platformInterface := service.FromContext[adapter.PlatformInterface](ctx) var defaultLogWriter io.Writer - if options.PlatformInterface != nil { + if platformInterface != nil { defaultLogWriter = io.Discard } logFactory, err := log.New(log.Options{ @@ -85,114 +176,307 @@ func New(options Options) (*Box, error) { if err != nil { return nil, E.Cause(err, "create log factory") } - router, err := route.NewRouter( - ctx, - logFactory, - common.PtrValueOrDefault(options.Route), - common.PtrValueOrDefault(options.DNS), - common.PtrValueOrDefault(options.NTP), - options.Inbounds, - options.PlatformInterface, - ) + + var internalServices []adapter.LifecycleService + routeOptions := common.PtrValueOrDefault(options.Route) + certificateOptions := common.PtrValueOrDefault(options.Certificate) + if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem || + len(certificateOptions.Certificate) > 0 || + len(certificateOptions.CertificatePath) > 0 || + len(certificateOptions.CertificateDirectoryPath) > 0 { + certificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger("certificate"), certificateOptions) + if err != nil { + return nil, err + } + service.MustRegister[adapter.CertificateStore](ctx, certificateStore) + internalServices = append(internalServices, certificateStore) + } + dnsOptions := common.PtrValueOrDefault(options.DNS) + 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) + dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final) + serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry) + certificateProviderManager := boxCertificate.NewManager(logFactory.NewLogger("certificate-provider"), certificateProviderRegistry) + service.MustRegister[adapter.EndpointManager](ctx, endpointManager) + service.MustRegister[adapter.InboundManager](ctx, inboundManager) + service.MustRegister[adapter.OutboundManager](ctx, outboundManager) + service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager) + service.MustRegister[adapter.ServiceManager](ctx, serviceManager) + service.MustRegister[adapter.CertificateProviderManager](ctx, certificateProviderManager) + dnsRouter, err := dns.NewRouter(ctx, logFactory, dnsOptions) + if err != nil { + return nil, E.Cause(err, "initialize DNS router") + } + service.MustRegister[adapter.DNSRouter](ctx, dnsRouter) + service.MustRegister[adapter.DNSRuleSetUpdateValidator](ctx, dnsRouter) + networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions, dnsOptions) + if err != nil { + return nil, E.Cause(err, "initialize network manager") + } + service.MustRegister[adapter.NetworkManager](ctx, networkManager) + connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection")) + service.MustRegister[adapter.ConnectionManager](ctx, connectionManager) + // Must register after ConnectionManager: the Apple HTTP engine's proxy bridge reads it from the context when Manager.Start resolves the default client. + httpClientManager := httpclient.NewManager(ctx, logFactory.NewLogger("httpclient"), options.HTTPClients, routeOptions.DefaultHTTPClient) + service.MustRegister[adapter.HTTPClientManager](ctx, httpClientManager) + httpClientService := adapter.LifecycleService(httpClientManager) + router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions) + service.MustRegister[adapter.Router](ctx, router) + err = router.Initialize(routeOptions.Rules, routeOptions.RuleSet) + if err != nil { + return nil, E.Cause(err, "initialize router") + } + ntpOptions := common.PtrValueOrDefault(options.NTP) + var timeService *tls.TimeServiceWrapper + if ntpOptions.Enabled { + timeService = new(tls.TimeServiceWrapper) + service.MustRegister[ntp.TimeService](ctx, timeService) + } + for i, transportOptions := range dnsOptions.Servers { + var tag string + if transportOptions.Tag != "" { + tag = transportOptions.Tag + } else { + tag = F.ToString(i) + } + err = dnsTransportManager.Create( + ctx, + logFactory.NewLogger(F.ToString("dns/", transportOptions.Type, "[", tag, "]")), + tag, + transportOptions.Type, + transportOptions.Options, + ) + if err != nil { + return nil, E.Cause(err, "initialize DNS server[", i, "]") + } + } + err = dnsRouter.Initialize(dnsOptions.Rules) if err != nil { - return nil, E.Cause(err, "parse route options") + return nil, E.Cause(err, "initialize dns router") + } + for i, endpointOptions := range options.Endpoints { + var tag string + if endpointOptions.Tag != "" { + tag = endpointOptions.Tag + } else { + tag = F.ToString(i) + } + endpointCtx := ctx + if tag != "" { + // TODO: remove this + endpointCtx = adapter.WithContext(endpointCtx, &adapter.InboundContext{ + Outbound: tag, + }) + } + err = endpointManager.Create( + endpointCtx, + router, + logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")), + tag, + endpointOptions.Type, + endpointOptions.Options, + ) + if err != nil { + return nil, E.Cause(err, "initialize endpoint[", i, "]") + } } - inbounds := make([]adapter.Inbound, 0, len(options.Inbounds)) - outbounds := make([]adapter.Outbound, 0, len(options.Outbounds)) for i, inboundOptions := range options.Inbounds { - var in adapter.Inbound var tag string if inboundOptions.Tag != "" { tag = inboundOptions.Tag } else { tag = F.ToString(i) } - in, err = inbound.New( + err = inboundManager.Create( ctx, router, logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")), - inboundOptions, - options.PlatformInterface, + tag, + inboundOptions.Type, + inboundOptions.Options, + ) + if err != nil { + return nil, E.Cause(err, "initialize inbound[", i, "]") + } + } + for i, serviceOptions := range options.Services { + var tag string + if serviceOptions.Tag != "" { + tag = serviceOptions.Tag + } else { + tag = F.ToString(i) + } + err = serviceManager.Create( + ctx, + logFactory.NewLogger(F.ToString("service/", serviceOptions.Type, "[", tag, "]")), + tag, + serviceOptions.Type, + serviceOptions.Options, ) if err != nil { - return nil, E.Cause(err, "parse inbound[", i, "]") + return nil, E.Cause(err, "initialize service[", i, "]") } - inbounds = append(inbounds, in) } for i, outboundOptions := range options.Outbounds { - var out adapter.Outbound var tag string if outboundOptions.Tag != "" { tag = outboundOptions.Tag } else { tag = F.ToString(i) } - out, err = outbound.New( - ctx, + outboundCtx := ctx + if tag != "" { + // TODO: remove this + outboundCtx = adapter.WithContext(outboundCtx, &adapter.InboundContext{ + Outbound: tag, + }) + } + err = outboundManager.Create( + outboundCtx, router, logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")), tag, - outboundOptions) + outboundOptions.Type, + outboundOptions.Options, + ) if err != nil { - return nil, E.Cause(err, "parse outbound[", i, "]") + return nil, E.Cause(err, "initialize outbound[", i, "]") } - outbounds = append(outbounds, out) } - err = router.Initialize(inbounds, outbounds, func() adapter.Outbound { - out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"}) - common.Must(oErr) - outbounds = append(outbounds, out) - return out - }) - if err != nil { - return nil, err + var invalidOutbound *hinvalid.Outbound + for _, outbound := range outboundManager.Outbounds() { + if outbound.Type() == C.TypeURLTest || outbound.Type() == C.TypeSelector || outbound.Type() == C.TypeDirect { + continue + } + if outbound.Type() == C.TypeHInvalidConfig { + invalidOutbound = outbound.(*hinvalid.Outbound) + continue + } + invalidOutbound = nil + break } - if options.PlatformInterface != nil { - err = options.PlatformInterface.Initialize(ctx, router) + if invalidOutbound != nil && invalidOutbound.InvalidOptions.Err != nil { + return nil, E.Cause(invalidOutbound.InvalidOptions.Err) + } + + for i, certificateProviderOptions := range options.CertificateProviders { + var tag string + if certificateProviderOptions.Tag != "" { + tag = certificateProviderOptions.Tag + } else { + tag = F.ToString(i) + } + err = certificateProviderManager.Create( + ctx, + logFactory.NewLogger(F.ToString("certificate-provider/", certificateProviderOptions.Type, "[", tag, "]")), + tag, + certificateProviderOptions.Type, + certificateProviderOptions.Options, + ) + if err != nil { + return nil, E.Cause(err, "initialize certificate provider[", i, "]") + } + } + outboundManager.Initialize(func() (adapter.Outbound, error) { + return direct.NewOutbound( + ctx, + router, + logFactory.NewLogger("outbound/direct"), + "direct", + option.DirectOutboundOptions{}, + ) + }) + dnsTransportManager.Initialize(func() (adapter.DNSTransport, error) { + return dnsTransportRegistry.CreateDNSTransport( + ctx, + logFactory.NewLogger("dns/local"), + "local", + C.DNSTypeLocal, + &option.LocalDNSServerOptions{}, + ) + }) + httpClientManager.Initialize(func() (*httpclient.ManagedTransport, error) { + deprecated.Report(ctx, deprecated.OptionImplicitDefaultHTTPClient) + var httpClientOptions option.HTTPClientOptions + httpClientOptions.DefaultOutbound = true + return httpclient.NewTransport(ctx, logFactory.NewLogger("httpclient"), "", httpClientOptions) + }) + if platformInterface != nil { + err = platformInterface.Initialize(networkManager) if err != nil { return nil, E.Cause(err, "initialize platform interface") } } - preServices1 := make(map[string]adapter.Service) - preServices2 := make(map[string]adapter.Service) - postServices := make(map[string]adapter.Service) if needCacheFile { - cacheFile := service.FromContext[adapter.CacheFile](ctx) - if cacheFile == nil { - cacheFile = cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile)) - service.MustRegister[adapter.CacheFile](ctx, cacheFile) - } - preServices1["cache file"] = cacheFile + cacheFile := cachefile.New(ctx, logFactory.NewLogger("cache-file"), common.PtrValueOrDefault(experimentalOptions.CacheFile)) + service.MustRegister[adapter.CacheFile](ctx, cacheFile) + internalServices = append(internalServices, cacheFile) } if needClashAPI { clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI) clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options) - clashServer, err := experimental.NewClashServer(ctx, router, logFactory.(log.ObservableFactory), clashAPIOptions) + clashServer, err := experimental.NewClashServer(ctx, logFactory.(log.ObservableFactory), clashAPIOptions) if err != nil { - return nil, E.Cause(err, "create clash api server") + return nil, E.Cause(err, "create clash-server") } - router.SetClashServer(clashServer) - preServices2["clash api"] = clashServer + router.AppendTracker(clashServer) + service.MustRegister[adapter.ClashServer](ctx, clashServer) + internalServices = append(internalServices, clashServer) } if needV2RayAPI { v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI)) if err != nil { - return nil, E.Cause(err, "create v2ray api server") + return nil, E.Cause(err, "create v2ray-server") + } + if v2rayServer.StatsService() != nil { + router.AppendTracker(v2rayServer.StatsService()) + internalServices = append(internalServices, v2rayServer) + service.MustRegister[adapter.V2RayServer](ctx, v2rayServer) + } + } + monitor, err := monitoring.NewOutboundMonitoring(ctx, logFactory.NewLogger("monitoring"), common.PtrValueOrDefault(experimentalOptions.Monitoring)) + if err != nil { + return nil, E.Cause(err, "create outbound monitoring") + } + internalServices = append(internalServices, monitor) + service.MustRegisterPtr[monitoring.OutboundMonitoring](ctx, monitor) + + router.AppendTracker(monitor) + + if ntpOptions.Enabled { + ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions, ntpOptions.ServerIsDomain()) + if err != nil { + return nil, E.Cause(err, "create NTP service") } - router.SetV2RayServer(v2rayServer) - preServices2["v2ray api"] = v2rayServer + ntpService := ntp.NewService(ntp.Options{ + Context: ctx, + Dialer: ntpDialer, + Logger: logFactory.NewLogger("ntp"), + Server: ntpOptions.ServerOptions.Build(), + Interval: time.Duration(ntpOptions.Interval), + WriteToSystem: ntpOptions.WriteToSystem, + }) + timeService.TimeService = ntpService + internalServices = append(internalServices, adapter.NewLifecycleService(ntpService, "ntp service")) } return &Box{ - router: router, - inbounds: inbounds, - outbounds: outbounds, - createdAt: createdAt, - logFactory: logFactory, - logger: logFactory.Logger(), - preServices1: preServices1, - preServices2: preServices2, - postServices: postServices, - done: make(chan struct{}), + network: networkManager, + endpoint: endpointManager, + inbound: inboundManager, + outbound: outboundManager, + dnsTransport: dnsTransportManager, + service: serviceManager, + certificateProvider: certificateProviderManager, + dnsRouter: dnsRouter, + connection: connectionManager, + router: router, + httpClientService: httpClientService, + createdAt: createdAt, + logFactory: logFactory, + logger: logFactory.Logger(), + internalService: internalServices, + done: make(chan struct{}), }, nil } @@ -242,35 +526,27 @@ func (s *Box) preStart() error { if err != nil { return E.Cause(err, "start logger") } - for serviceName, service := range s.preServices1 { - if preService, isPreService := service.(adapter.PreStarter); isPreService { - monitor.Start("pre-start ", serviceName) - err := preService.PreStart() - monitor.Finish() - if err != nil { - return E.Cause(err, "pre-start ", serviceName) - } - } + err = adapter.StartNamed(s.logger, adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api + if err != nil { + return err } - for serviceName, service := range s.preServices2 { - if preService, isPreService := service.(adapter.PreStarter); isPreService { - monitor.Start("pre-start ", serviceName) - err := preService.PreStart() - monitor.Finish() - if err != nil { - return E.Cause(err, "pre-start ", serviceName) - } - } + err = adapter.Start(s.logger, adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service, s.certificateProvider) + if err != nil { + return err } - err = s.router.PreStart() + err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.network, s.connection) if err != nil { - return E.Cause(err, "pre-start router") + return err } - err = s.startOutbounds() + err = adapter.StartNamed(s.logger, adapter.StartStateStart, []adapter.LifecycleService{s.httpClientService}) if err != nil { return err } - return s.router.Start() + err = adapter.Start(s.logger, adapter.StartStateStart, s.router, s.dnsRouter) + if err != nil { + return err + } + return nil } func (s *Box) start() error { @@ -278,50 +554,39 @@ func (s *Box) start() error { if err != nil { return err } - for serviceName, service := range s.preServices1 { - err = service.Start() - if err != nil { - return E.Cause(err, "start ", serviceName) - } + err = adapter.StartNamed(s.logger, adapter.StartStateStart, s.internalService) + if err != nil { + return err } - for serviceName, service := range s.preServices2 { - err = service.Start() - if err != nil { - return E.Cause(err, "start ", serviceName) - } + err = adapter.Start(s.logger, adapter.StartStateStart, s.endpoint) + if err != nil { + return err } - for i, in := range s.inbounds { - var tag string - if in.Tag() == "" { - tag = F.ToString(i) - } else { - tag = in.Tag() - } - err = in.Start() - if err != nil { - return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]") - } + err = adapter.Start(s.logger, adapter.StartStateStart, s.certificateProvider) + if err != nil { + return err } - return s.postStart() -} - -func (s *Box) postStart() error { - for serviceName, service := range s.postServices { - err := service.Start() - if err != nil { - return E.Cause(err, "start ", serviceName) - } + err = adapter.Start(s.logger, adapter.StartStateStart, s.inbound, s.service) + if err != nil { + return err } - for _, outbound := range s.outbounds { - if lateOutbound, isLateOutbound := outbound.(adapter.PostStarter); isLateOutbound { - err := lateOutbound.PostStart() - if err != nil { - return E.Cause(err, "post-start outbound/", outbound.Tag()) - } - } + err = adapter.Start(s.logger, adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.endpoint, s.certificateProvider, s.inbound, s.service) + if err != nil { + return err } - - return s.router.PostStart() + err = adapter.StartNamed(s.logger, adapter.StartStatePostStart, s.internalService) + 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.endpoint, s.certificateProvider, s.inbound, s.service) + if err != nil { + return err + } + err = adapter.StartNamed(s.logger, adapter.StartStateStarted, s.internalService) + if err != nil { + return err + } + return nil } func (s *Box) Close() error { @@ -331,58 +596,99 @@ func (s *Box) Close() error { default: close(s.done) } - monitor := taskmonitor.New(s.logger, C.StopTimeout) - var errors error - for serviceName, service := range s.postServices { - monitor.Start("close ", serviceName) - errors = E.Append(errors, service.Close(), func(err error) error { - return E.Cause(err, "close ", serviceName) - }) - monitor.Finish() - } - for i, in := range s.inbounds { - monitor.Start("close inbound/", in.Type(), "[", i, "]") - errors = E.Append(errors, in.Close(), func(err error) error { - return E.Cause(err, "close inbound/", in.Type(), "[", i, "]") + closeTimeout := time.Second * 10 + var err error + for _, closeItem := range []struct { + name string + service adapter.Lifecycle + }{ + {"service", s.service}, + {"inbound", s.inbound}, + {"certificate-provider", s.certificateProvider}, + {"endpoint", s.endpoint}, + {"outbound", s.outbound}, + {"router", s.router}, + {"connection", s.connection}, + {"dns-router", s.dnsRouter}, + {"dns-transport", s.dnsTransport}, + {"network", s.network}, + } { + cerr := s.closeWithTimeout(closeItem.name, closeTimeout, closeItem.service.Close) + err = E.Append(err, cerr, func(err error) error { + return E.Cause(err, "close ", closeItem.name) }) - monitor.Finish() } - for i, out := range s.outbounds { - monitor.Start("close outbound/", out.Type(), "[", i, "]") - errors = E.Append(errors, common.Close(out), func(err error) error { - return E.Cause(err, "close outbound/", out.Type(), "[", i, "]") + if s.httpClientService != nil { + s.logger.Trace("close ", s.httpClientService.Name()) + startTime := time.Now() + err = E.Append(err, s.httpClientService.Close(), func(err error) error { + return E.Cause(err, "close ", s.httpClientService.Name()) }) - monitor.Finish() + s.logger.Trace("close ", s.httpClientService.Name(), " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") } - monitor.Start("close router") - if err := common.Close(s.router); err != nil { - errors = E.Append(errors, err, func(err error) error { - return E.Cause(err, "close router") + for _, lifecycleService := range s.internalService { + cerr := s.closeWithTimeout(lifecycleService.Name(), closeTimeout, lifecycleService.Close) + err = E.Append(err, cerr, func(err error) error { + return E.Cause(err, "close ", lifecycleService.Name()) }) } - monitor.Finish() - for serviceName, service := range s.preServices1 { - monitor.Start("close ", serviceName) - errors = E.Append(errors, service.Close(), func(err error) error { - return E.Cause(err, "close ", serviceName) - }) - monitor.Finish() - } - for serviceName, service := range s.preServices2 { - monitor.Start("close ", serviceName) - errors = E.Append(errors, service.Close(), func(err error) error { - return E.Cause(err, "close ", serviceName) - }) - monitor.Finish() - } - if err := common.Close(s.logFactory); err != nil { - errors = E.Append(errors, err, func(err error) error { - return E.Cause(err, "close logger") - }) + cerr := s.closeWithTimeout("logger", closeTimeout, s.logFactory.Close) + err = E.Append(err, cerr, func(err error) error { + return E.Cause(err, "close logger") + }) + return err +} +func (s *Box) closeWithTimeout(name string, timeout time.Duration, closeFn func() error) (err error) { + s.logger.Trace("closeing ", name) + startTime := time.Now() + defer func() { + if err != nil { + s.logger.Error("close ", name, " error (", F.Seconds(time.Since(startTime).Seconds()), "s)"+": "+err.Error()) + } else { + s.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") + } + }() + done := make(chan error, 1) + + go func() { + done <- closeFn() + }() + + select { + case err = <-done: + return err + case <-time.After(timeout): + return fmt.Errorf("close %s timed out after %s", name, timeout) } - return errors + +} +func (s *Box) Network() adapter.NetworkManager { + return s.network } func (s *Box) Router() adapter.Router { return s.router } + +func (s *Box) Inbound() adapter.InboundManager { + return s.inbound +} + +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 +} + +func (s *Box) AddService(service adapter.LifecycleService) { + s.internalService = append(s.internalService, service) +} + +func (s *Box) Logger() log.ContextLogger { + return s.logger +} diff --git a/box_outbound.go b/box_outbound.go deleted file mode 100644 index 6e3f0617fc..0000000000 --- a/box_outbound.go +++ /dev/null @@ -1,83 +0,0 @@ -package box - -import ( - "strings" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/taskmonitor" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" -) - -func (s *Box) startOutbounds() error { - monitor := taskmonitor.New(s.logger, C.StartTimeout) - outboundTags := make(map[adapter.Outbound]string) - outbounds := make(map[string]adapter.Outbound) - for i, outboundToStart := range s.outbounds { - var outboundTag string - if outboundToStart.Tag() == "" { - outboundTag = F.ToString(i) - } else { - outboundTag = outboundToStart.Tag() - } - if _, exists := outbounds[outboundTag]; exists { - return E.New("outbound tag ", outboundTag, " duplicated") - } - outboundTags[outboundToStart] = outboundTag - outbounds[outboundTag] = outboundToStart - } - started := make(map[string]bool) - for { - canContinue := false - startOne: - for _, outboundToStart := range s.outbounds { - outboundTag := outboundTags[outboundToStart] - if started[outboundTag] { - continue - } - dependencies := outboundToStart.Dependencies() - for _, dependency := range dependencies { - if !started[dependency] { - continue startOne - } - } - started[outboundTag] = true - canContinue = true - if starter, isStarter := outboundToStart.(common.Starter); isStarter { - monitor.Start("initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]") - err := starter.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]") - } - } - } - if len(started) == len(s.outbounds) { - break - } - if canContinue { - continue - } - currentOutbound := common.Find(s.outbounds, func(it adapter.Outbound) bool { - return !started[outboundTags[it]] - }) - var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error - lintOutbound = func(oTree []string, oCurrent adapter.Outbound) error { - problemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool { - return !started[it] - }) - if common.Contains(oTree, problemOutboundTag) { - return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag) - } - problemOutbound := outbounds[problemOutboundTag] - if problemOutbound == nil { - return E.New("dependency[", problemOutboundTag, "] not found for outbound[", outboundTags[oCurrent], "]") - } - return lintOutbound(append(oTree, problemOutboundTag), problemOutbound) - } - return lintOutbound([]string{outboundTags[currentOutbound]}, currentOutbound) - } - return nil -} diff --git a/clients/android b/clients/android index 440aaa9a1a..772879ce9c 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 440aaa9a1a7504293758243b18478592aaf670b7 +Subproject commit 772879ce9cd37c29e377d4d44d0efee12662948d diff --git a/clients/apple b/clients/apple index aa4ce98421..3c3350ef9a 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit aa4ce9842174af85e4d3b2bd5e1651e4f87d3526 +Subproject commit 3c3350ef9a94576cf78983fe115d603625b19651 diff --git a/cmd/hiddify/resolver/main.go b/cmd/hiddify/resolver/main.go new file mode 100644 index 0000000000..4a6d90a8a4 --- /dev/null +++ b/cmd/hiddify/resolver/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "net" + "os" + "strings" + + _ "embed" + + "github.com/oschwald/geoip2-golang" +) + +//go:embed resolvers.txt +var resolvers string + +func main() { + db, err := geoip2.Open("GeoLite2-Country.mmdb") + if err != nil { + log.Fatal(err) + } + defer db.Close() + added := map[string]struct{}{} + result := map[string][]string{} + for _, line := range strings.Split(resolvers, "\n") { + line = strings.TrimSpace(line) + if line == "" || line[0] == '#' { + continue + } + + ip := net.ParseIP(line) + record, err := db.Country(ip) + if err != nil { + continue + } + country := record.Country.IsoCode + if _, ok := added[line]; !ok { + result[country] = append(result[country], line) + added[line] = struct{}{} + } + } + + j, _ := json.MarshalIndent(result, "", " ") + fmt.Println(string(j)) + + os.WriteFile("ips_by_country.json", j, 0644) + +} diff --git a/cmd/hiddify/resolver/resolvers.txt b/cmd/hiddify/resolver/resolvers.txt new file mode 100644 index 0000000000..bfbdfd07f7 --- /dev/null +++ b/cmd/hiddify/resolver/resolvers.txt @@ -0,0 +1,58847 @@ +# Famous Public DNS Servers +8.8.8.8 +8.8.4.4 +1.1.1.1 +1.0.0.1 +208.67.222.222 +208.67.220.220 +4.2.2.1 +4.2.2.2 +4.4.4.4 +156.154.70.1 +156.154.71.1 +80.80.80.80 +80.80.81.81 +9.9.9.9 +149.112.112.112 +9.9.9.10 +149.112.112.10 +9.9.9.11 +149.112.112.11 +94.140.14.14 +94.140.15.15 +94.140.14.15 +94.140.15.16 +94.140.14.140 +94.140.14.141 +194.242.2.2 +194.242.2.3 +1.1.1.2 +1.0.0.2 +1.1.1.3 +1.0.0.3 +76.76.2.0 +76.76.10.0 +76.76.19.19 +76.223.122.150 +76.76.26.0 +76.76.20.0 +8.26.56.26 +8.20.247.20 +185.228.168.9 +185.228.169.9 +185.228.168.168 +185.228.169.168 +185.228.168.10 +185.228.169.11 +77.88.8.8 +77.88.8.1 +223.5.5.5 +223.6.6.6 +180.76.76.76 +119.29.29.29 +101.101.101.101 +101.102.103.104 +64.6.64.6 +64.6.65.6 +84.200.69.80 +84.200.70.40 +109.69.8.51 +194.255.152.10 +194.255.62.80 +195.46.39.39 +195.46.39.40 +# SHUFFLE_BELOW +185.181.182.209 +178.22.122.100 +185.51.200.2 +10.202.10.202 +10.202.10.102 +10.202.10.10 +10.202.10.11 +78.157.42.100 +78.157.42.101 +217.218.26.77 +217.218.26.78 +94.103.125.157 +94.103.125.158 +185.55.226.26 +185.55.225.25 +163.172.141.219 +2.144.4.108 +2.144.6.246 +2.144.6.65 +2.144.4.118 +2.144.7.149 +2.144.20.74 +2.144.20.109 +2.144.20.202 +2.184.239.88 +2.184.239.156 +5.160.12.250 +45.81.18.141 +46.245.90.90 +82.99.213.58 +82.99.213.59 +82.99.213.60 +82.99.213.61 +82.99.214.108 +91.92.208.152 +94.183.52.42 +102.22.81.183 +102.22.81.133 +185.49.84.2 +185.164.74.183 +2.144.4.190 +2.144.5.46 +2.144.5.118 +2.144.20.86 +2.144.22.69 +2.144.23.51 +2.144.23.148 +2.144.235.30 +207.246.121.77 +176.65.231.61 +185.129.118.121 +193.228.136.129 +5.202.255.21 +5.198.178.125 +91.106.67.82 +37.202.225.169 +45.156.185.200 +45.159.150.51 +37.202.225.166 +37.235.17.243 +45.90.74.47 +45.92.95.140 +45.138.132.115 +46.34.165.142 +37.235.17.247 +37.255.138.19 +45.9.255.21 +45.9.255.22 +45.92.93.203 +45.149.77.242 +45.159.150.210 +37.202.225.236 +45.149.79.172 +37.202.225.168 +37.202.237.245 +37.235.17.245 +37.255.219.35 +45.89.139.14 +45.129.38.78 +45.139.11.210 +45.149.77.44 +45.149.79.226 +45.156.185.106 +45.159.150.50 +46.34.162.68 +46.34.163.38 +46.34.165.238 +37.202.186.74 +45.142.191.135 +45.142.191.136 +46.167.142.120 +37.202.155.171 +45.129.39.7 +45.129.39.4 +45.129.39.3 +45.9.145.10 +45.129.39.2 +46.38.138.173 +46.100.12.186 +37.255.231.201 +45.94.214.12 +46.100.13.158 +46.100.5.135 +46.148.41.105 +46.100.94.168 +46.100.95.195 +46.100.11.49 +37.202.156.90 +45.142.191.5 +46.100.62.206 +37.202.237.244 +46.100.52.106 +37.202.180.163 +31.214.251.4 +31.214.251.31 +37.152.181.98 +37.191.85.6 +31.7.133.3 +31.7.133.2 +31.14.117.209 +31.214.174.104 +31.214.174.105 +31.214.251.33 +31.214.251.36 +31.214.251.34 +31.214.251.47 +31.214.251.50 +31.14.117.201 +31.14.117.208 +31.25.91.67 +31.214.168.234 +31.214.170.66 +31.214.170.254 +31.214.174.102 +31.214.174.103 +31.214.174.113 +31.214.251.29 +31.214.251.39 +31.214.251.44 +31.214.251.45 +31.214.251.53 +31.214.251.55 +31.214.251.59 +31.214.251.58 +37.32.13.229 +37.114.204.82 +37.156.144.92 +31.47.33.58 +31.214.168.82 +37.191.95.34 +31.214.174.106 +37.191.95.45 +31.47.46.77 +31.47.46.78 +37.156.31.94 +31.7.70.68 +37.148.82.26 +37.63.247.9 +31.130.181.233 +37.148.85.9 +37.148.57.40 +37.156.144.69 +37.148.51.30 +193.228.90.34 +185.140.58.2 +89.45.89.250 +185.165.30.119 +5.233.205.183 +2.144.3.168 +2.144.3.169 +2.144.3.170 +2.144.3.171 +2.144.3.172 +2.144.3.173 +2.144.4.8 +2.144.4.62 +2.144.4.217 +2.144.5.81 +2.144.5.181 +2.144.5.198 +2.144.6.46 +2.144.7.26 +2.144.7.218 +2.144.7.238 +2.144.20.5 +2.144.20.45 +2.144.20.224 +2.144.21.68 +2.144.21.92 +2.144.21.255 +2.144.22.135 +2.144.198.225 +2.144.231.18 +2.144.233.119 +2.144.245.252 +2.144.247.168 +2.176.71.248 +2.176.144.65 +2.176.160.155 +2.176.161.158 +2.176.163.232 +2.176.195.20 +2.176.195.71 +2.176.196.108 +2.176.203.68 +2.176.205.191 +2.176.207.244 +2.176.209.116 +2.176.211.93 +2.176.219.184 +2.176.219.209 +2.176.220.2 +2.176.231.32 +2.176.232.109 +2.176.235.135 +2.176.240.121 +2.176.246.138 +2.176.247.23 +2.176.250.23 +2.176.254.199 +2.176.255.78 +2.177.7.74 +2.177.11.130 +2.177.14.161 +2.177.24.181 +2.177.40.63 +2.177.42.167 +2.177.42.212 +2.177.43.58 +2.177.48.90 +2.177.50.76 +2.177.54.179 +2.177.58.132 +2.177.58.155 +2.177.69.242 +2.177.73.160 +2.177.73.244 +2.177.78.104 +2.177.80.237 +2.177.85.200 +2.177.96.175 +2.177.99.77 +2.177.109.173 +2.177.113.28 +2.177.117.230 +2.177.118.189 +2.177.120.14 +2.177.121.187 +2.177.126.254 +2.177.129.176 +2.177.133.217 +2.177.135.111 +2.177.139.223 +2.177.140.35 +2.177.152.158 +2.177.153.109 +2.177.155.185 +2.177.155.224 +2.177.156.192 +2.177.158.73 +2.177.158.225 +2.177.160.55 +2.177.164.229 +2.177.167.83 +2.177.167.170 +2.177.173.66 +2.177.173.87 +2.177.173.176 +2.177.175.244 +2.177.178.196 +2.177.183.65 +2.177.184.11 +2.177.187.16 +2.177.188.230 +2.177.189.84 +2.177.190.2 +2.177.192.190 +2.177.192.231 +2.177.195.151 +2.177.196.211 +2.177.197.243 +2.177.198.2 +2.177.198.159 +2.177.204.158 +2.177.204.186 +2.177.207.115 +2.177.213.224 +2.177.218.47 +2.177.220.95 +2.177.224.244 +2.177.228.67 +2.177.230.80 +2.177.237.182 +2.177.238.79 +2.177.239.150 +2.177.240.88 +2.177.242.177 +2.177.244.70 +2.177.245.77 +2.177.253.11 +2.177.254.249 +2.177.255.131 +2.178.0.24 +2.178.3.70 +2.178.4.229 +2.178.11.34 +2.178.11.72 +2.178.11.159 +2.178.11.241 +2.178.15.78 +2.178.15.130 +2.178.22.53 +2.178.22.146 +2.178.26.116 +2.178.26.206 +2.178.33.222 +2.178.36.242 +2.178.46.42 +2.178.49.12 +2.178.53.205 +2.178.61.247 +2.178.63.103 +2.178.66.121 +2.178.66.138 +2.178.68.58 +2.178.85.219 +2.178.98.210 +2.178.99.108 +2.178.104.149 +2.178.105.198 +2.178.106.212 +2.178.107.246 +2.178.111.223 +2.178.111.228 +2.178.114.218 +2.178.126.27 +2.178.139.88 +2.178.160.42 +2.178.173.175 +2.178.206.242 +2.178.212.180 +2.178.218.239 +2.178.225.41 +2.178.225.46 +2.178.240.181 +2.178.244.156 +2.178.245.219 +2.179.17.46 +2.179.73.249 +2.179.73.252 +2.179.164.1 +2.179.164.17 +2.179.164.92 +2.179.164.245 +2.179.165.2 +2.179.165.108 +2.179.166.51 +2.179.166.119 +2.179.186.165 +2.179.186.244 +2.179.194.7 +2.179.223.63 +2.179.245.7 +2.180.0.27 +2.180.1.139 +2.180.1.157 +2.180.2.5 +2.180.2.12 +2.180.2.71 +2.180.2.175 +2.180.2.188 +2.180.3.42 +2.180.7.125 +2.180.7.222 +2.180.7.223 +2.180.8.148 +2.180.10.251 +2.180.17.150 +2.180.17.221 +2.180.18.173 +2.180.18.229 +2.180.19.225 +2.180.25.216 +2.180.26.219 +2.180.29.30 +2.180.30.57 +2.180.30.220 +2.180.34.26 +2.180.34.44 +2.180.35.82 +2.180.35.201 +2.180.36.215 +2.180.39.20 +2.180.39.22 +2.180.39.66 +2.180.39.68 +2.180.40.1 +2.180.40.107 +2.180.42.106 +2.180.47.240 +2.180.140.252 +2.180.142.22 +2.180.145.231 +2.180.189.202 +2.180.201.34 +2.180.215.30 +2.181.30.33 +2.181.30.174 +2.181.30.231 +2.181.34.93 +2.181.80.30 +2.181.87.86 +2.181.105.236 +2.181.129.197 +2.181.195.225 +2.181.222.226 +2.181.234.50 +2.181.235.121 +2.181.247.240 +2.182.99.67 +2.182.109.129 +2.182.113.73 +2.182.128.170 +2.182.152.99 +2.182.154.64 +2.182.177.35 +2.182.189.113 +2.182.190.52 +2.182.194.1 +2.182.195.57 +2.182.213.73 +2.182.214.11 +2.182.214.126 +2.182.240.134 +2.182.242.139 +2.182.250.30 +2.182.250.161 +2.182.253.245 +2.182.253.246 +2.182.253.247 +2.183.8.180 +2.183.9.108 +2.183.9.110 +2.183.32.15 +2.183.32.109 +2.183.32.232 +2.183.95.185 +2.183.230.24 +2.184.30.252 +2.184.48.1 +2.184.49.134 +2.184.50.11 +2.184.70.68 +2.184.90.186 +2.184.112.150 +2.184.155.75 +2.184.236.106 +2.184.236.142 +2.144.2.8 +2.144.2.44 +2.144.2.154 +2.144.2.247 +2.144.4.30 +2.144.4.154 +2.144.4.254 +2.144.5.135 +2.144.5.186 +2.144.5.213 +2.144.5.245 +2.144.6.10 +2.144.6.48 +2.144.6.59 +2.144.6.112 +2.144.6.117 +2.144.6.207 +2.144.7.9 +2.144.7.68 +2.144.7.173 +2.144.13.3 +2.144.13.6 +2.144.14.3 +2.144.20.143 +2.144.20.147 +2.144.20.183 +2.144.20.212 +2.144.21.62 +2.144.21.115 +2.144.21.158 +2.144.21.219 +2.144.22.71 +2.144.22.101 +2.144.22.157 +2.144.22.188 +2.144.22.255 +2.144.23.53 +2.144.23.191 +2.144.23.215 +2.144.23.228 +2.144.24.5 +2.144.129.104 +2.144.129.133 +2.144.129.158 +2.144.129.253 +2.144.198.195 +2.144.198.207 +2.144.198.234 +2.144.198.236 +2.144.198.241 +2.144.198.242 +2.144.198.247 +2.144.230.61 +2.144.230.211 +2.144.231.46 +2.144.231.53 +2.144.231.66 +2.144.231.70 +2.144.231.71 +2.144.231.102 +2.144.231.135 +2.144.231.136 +2.144.231.137 +2.144.231.142 +2.144.231.144 +2.144.231.145 +2.144.231.155 +2.144.231.160 +2.144.231.161 +2.144.231.162 +2.144.231.169 +2.144.231.170 +2.144.231.171 +2.144.231.173 +2.144.231.175 +2.144.231.180 +2.144.231.182 +2.144.231.189 +2.144.231.192 +2.144.231.193 +2.144.231.195 +2.144.231.201 +2.144.231.202 +2.144.231.209 +2.144.231.214 +2.144.231.215 +2.144.231.218 +2.144.231.223 +2.144.231.225 +2.144.231.228 +2.144.231.233 +2.144.231.235 +2.144.231.239 +2.144.231.243 +2.144.231.245 +2.144.231.247 +2.144.231.248 +2.144.231.252 +2.144.232.8 +2.144.232.15 +2.144.232.24 +2.144.232.29 +2.144.232.33 +2.144.232.35 +2.144.232.36 +2.144.232.38 +2.144.232.49 +2.144.232.50 +2.144.232.51 +2.144.232.56 +2.144.232.59 +2.144.232.66 +2.144.232.68 +2.144.232.69 +2.144.232.78 +2.144.232.80 +2.144.232.83 +2.144.232.89 +2.144.232.90 +2.144.232.99 +2.144.232.100 +2.144.232.104 +2.144.232.108 +2.144.232.111 +2.144.232.112 +2.144.232.115 +2.144.232.120 +2.144.232.123 +2.144.232.125 +2.144.232.126 +2.144.232.127 +2.144.232.129 +2.144.232.130 +2.144.232.131 +2.144.232.134 +2.144.232.136 +2.144.232.141 +2.144.232.147 +2.144.232.148 +2.144.232.154 +2.144.232.156 +2.144.232.161 +2.144.232.162 +2.144.232.163 +2.144.232.164 +2.144.232.165 +2.144.232.169 +2.144.232.171 +2.144.232.173 +2.144.232.177 +2.144.232.180 +2.144.232.181 +2.144.232.184 +2.144.232.189 +2.144.232.191 +2.144.232.192 +2.144.232.196 +2.144.232.203 +2.144.232.205 +2.144.232.207 +2.144.232.210 +2.144.232.211 +2.144.232.214 +2.144.232.217 +2.144.232.219 +2.144.232.224 +2.144.233.26 +2.144.233.30 +2.144.233.40 +2.144.233.86 +2.144.233.148 +2.144.233.171 +2.144.233.235 +2.144.234.42 +2.144.234.53 +2.144.234.66 +2.144.234.75 +2.144.234.92 +2.144.234.97 +2.144.234.100 +2.144.234.101 +2.144.234.102 +2.144.234.104 +2.144.234.117 +2.144.234.123 +2.144.234.129 +2.144.234.130 +2.144.234.133 +2.144.234.136 +2.144.234.144 +2.144.234.145 +2.144.234.146 +2.144.234.151 +2.144.234.152 +2.144.234.155 +2.144.234.157 +2.144.234.209 +2.144.234.211 +2.144.234.213 +2.144.234.214 +2.144.234.219 +2.144.234.222 +2.144.234.225 +2.144.234.226 +2.144.234.228 +2.144.234.231 +2.144.234.235 +2.144.234.237 +2.144.234.241 +2.144.234.245 +2.144.234.247 +2.144.234.248 +2.144.234.252 +2.144.235.16 +2.144.235.25 +2.144.235.35 +2.144.235.39 +2.144.235.161 +2.144.235.197 +2.144.235.217 +2.144.235.219 +2.144.236.205 +2.144.236.245 +2.144.242.34 +2.144.242.62 +2.144.242.77 +2.144.242.84 +2.144.242.95 +2.144.242.123 +2.144.242.130 +2.144.242.148 +2.144.242.199 +2.144.242.225 +2.144.243.33 +2.144.243.35 +2.144.243.66 +2.144.243.76 +2.144.243.86 +2.144.243.92 +2.144.243.108 +2.144.243.109 +2.144.243.120 +2.144.243.160 +2.144.243.161 +2.144.243.162 +2.144.243.167 +2.144.243.191 +2.144.243.192 +2.144.243.200 +2.144.244.18 +2.144.244.27 +2.144.244.29 +2.144.244.30 +2.144.244.31 +2.144.244.32 +2.144.244.34 +2.144.244.35 +2.144.244.56 +2.144.244.60 +2.144.244.66 +2.144.244.68 +2.144.244.70 +2.144.244.72 +2.144.244.73 +2.144.244.78 +2.144.244.80 +2.144.244.81 +2.144.244.99 +2.144.244.121 +2.144.244.144 +2.144.244.148 +2.144.244.150 +2.144.244.151 +2.144.244.152 +2.144.244.153 +2.144.244.159 +2.144.244.223 +2.144.244.233 +2.144.245.0 +2.144.245.3 +2.144.245.15 +2.144.245.38 +2.144.245.42 +2.144.245.60 +2.144.245.61 +2.144.245.62 +2.144.245.66 +2.144.245.115 +2.144.245.119 +2.144.245.120 +2.144.245.123 +2.144.245.127 +2.144.245.130 +2.144.245.132 +2.144.245.148 +2.144.245.149 +2.144.245.154 +2.144.245.166 +2.144.245.167 +2.144.245.198 +2.144.245.213 +2.144.245.222 +2.144.245.230 +2.144.246.1 +2.144.246.22 +2.144.246.50 +2.144.246.86 +2.144.246.103 +2.144.246.106 +2.144.246.119 +2.144.246.147 +2.144.246.185 +2.144.246.187 +2.144.246.191 +2.144.246.202 +2.144.246.215 +2.144.246.226 +2.144.246.243 +2.144.247.14 +2.144.247.21 +2.144.247.29 +2.144.247.30 +2.144.247.34 +2.144.247.35 +2.144.247.42 +2.144.247.45 +2.144.247.47 +2.144.247.63 +2.144.247.66 +2.144.247.87 +2.144.247.93 +2.144.247.97 +2.144.247.118 +2.144.247.119 +2.144.247.121 +2.144.247.123 +2.144.247.137 +2.144.247.139 +2.144.247.140 +2.144.247.150 +2.144.247.155 +2.144.247.159 +2.144.247.161 +2.144.247.165 +2.144.247.169 +2.144.247.172 +2.144.247.176 +2.144.247.182 +2.144.247.185 +2.144.247.186 +2.144.247.190 +2.144.247.203 +2.144.247.211 +2.144.247.212 +2.144.247.216 +2.144.247.220 +2.144.247.224 +2.144.247.230 +2.144.247.232 +2.144.247.249 +2.144.248.188 +2.144.248.199 +2.144.248.200 +2.144.248.203 +2.144.248.206 +2.144.248.213 +2.144.248.214 +2.144.248.219 +2.144.248.220 +2.144.248.225 +2.144.248.231 +2.144.248.240 +2.144.248.245 +2.144.248.247 +2.144.248.249 +2.144.248.250 +2.144.248.252 +2.144.251.3 +2.144.251.13 +2.144.251.16 +2.144.251.24 +2.144.251.27 +2.144.251.28 +2.176.21.37 +2.176.42.197 +2.176.116.115 +2.176.128.221 +2.176.164.158 +2.176.165.86 +2.176.179.96 +2.176.186.208 +2.176.187.204 +2.176.193.184 +2.176.194.24 +2.176.195.168 +2.176.199.217 +2.176.199.235 +2.176.201.99 +2.176.201.140 +2.176.201.253 +2.176.203.2 +2.176.205.132 +2.176.207.56 +2.176.208.46 +2.176.208.115 +2.176.209.124 +2.176.210.22 +2.176.210.236 +2.176.210.237 +2.176.215.134 +2.176.216.158 +2.176.216.171 +2.176.217.52 +2.176.217.132 +2.176.218.194 +2.176.218.207 +2.176.219.211 +2.176.223.64 +2.176.227.61 +2.176.227.118 +2.176.229.49 +2.176.231.99 +2.176.233.44 +2.176.234.103 +2.176.236.16 +2.176.237.172 +2.176.238.81 +2.176.238.83 +2.176.240.89 +2.176.240.209 +2.176.240.211 +2.176.244.253 +2.176.245.66 +2.176.245.244 +2.176.247.16 +2.176.247.47 +2.176.247.227 +2.176.247.255 +2.176.248.53 +2.176.248.234 +2.176.249.176 +2.176.251.27 +2.176.253.225 +2.176.254.139 +2.177.2.85 +2.177.7.84 +2.177.7.167 +2.177.8.203 +2.177.10.196 +2.177.18.29 +2.177.18.92 +2.177.19.1 +2.177.19.79 +2.177.21.69 +2.177.22.188 +2.177.22.195 +2.177.23.80 +2.177.24.139 +2.177.25.36 +2.177.25.129 +2.177.26.95 +2.177.26.178 +2.177.27.105 +2.177.30.145 +2.177.31.65 +2.177.31.242 +2.177.33.173 +2.177.33.254 +2.177.36.221 +2.177.38.76 +2.177.38.200 +2.177.39.71 +2.177.40.221 +2.177.42.135 +2.177.45.39 +2.177.46.233 +2.177.47.128 +2.177.47.205 +2.177.48.53 +2.177.48.70 +2.177.48.192 +2.177.51.59 +2.177.51.245 +2.177.52.43 +2.177.55.195 +2.177.55.198 +2.177.56.85 +2.177.58.211 +2.177.62.61 +2.177.62.221 +2.177.64.71 +2.177.64.82 +2.177.65.14 +2.177.65.66 +2.177.65.254 +2.177.67.231 +2.177.68.185 +2.177.70.244 +2.177.71.230 +2.177.72.52 +2.177.72.131 +2.177.73.76 +2.177.75.187 +2.177.75.194 +2.177.76.190 +2.177.77.190 +2.177.78.6 +2.177.78.148 +2.177.81.249 +2.177.82.206 +2.177.84.66 +2.177.87.45 +2.177.88.165 +2.177.90.31 +2.177.92.10 +2.177.95.83 +2.177.97.83 +2.177.98.42 +2.177.98.52 +2.177.98.214 +2.177.99.87 +2.177.100.151 +2.177.101.8 +2.177.102.12 +2.177.102.121 +2.177.102.148 +2.177.103.111 +2.177.107.148 +2.177.108.101 +2.177.108.169 +2.177.109.68 +2.177.109.178 +2.177.112.192 +2.177.113.92 +2.177.113.104 +2.177.113.173 +2.177.114.42 +2.177.114.204 +2.177.120.54 +2.177.121.153 +2.177.122.60 +2.177.123.165 +2.177.123.175 +2.177.123.252 +2.177.124.28 +2.177.130.168 +2.177.130.178 +2.177.134.5 +2.177.134.191 +2.177.135.165 +2.177.136.126 +2.177.137.84 +2.177.137.129 +2.177.140.135 +2.177.142.153 +2.177.143.48 +2.177.147.178 +2.177.150.223 +2.177.151.90 +2.177.154.37 +2.177.154.43 +2.177.154.63 +2.177.155.21 +2.177.155.108 +2.177.156.85 +2.177.156.95 +2.177.158.139 +2.177.160.149 +2.177.161.136 +2.177.161.194 +2.177.162.138 +2.177.163.42 +2.177.165.215 +2.177.167.30 +2.177.167.187 +2.177.170.148 +2.177.174.202 +2.177.179.197 +2.177.179.238 +2.177.181.250 +2.177.183.104 +2.177.184.244 +2.177.186.48 +2.177.188.213 +2.177.189.81 +2.177.193.94 +2.177.193.107 +2.177.193.205 +2.177.194.248 +2.177.195.2 +2.177.195.160 +2.177.196.150 +2.177.200.2 +2.177.200.150 +2.177.201.168 +2.177.202.103 +2.177.203.198 +2.177.205.238 +2.177.205.254 +2.177.209.120 +2.177.210.254 +2.177.212.105 +2.177.213.171 +2.177.214.20 +2.177.217.23 +2.177.218.172 +2.177.219.102 +2.177.220.44 +2.177.221.239 +2.177.222.246 +2.177.223.37 +2.177.223.74 +2.177.223.126 +2.177.225.121 +2.177.230.85 +2.177.230.124 +2.177.236.183 +2.177.238.11 +2.177.240.0 +2.177.240.113 +2.177.241.158 +2.177.242.81 +2.177.245.73 +2.177.250.182 +2.177.250.254 +2.177.253.111 +2.177.254.11 +2.178.1.137 +2.178.1.221 +2.178.2.232 +2.178.6.39 +2.178.8.123 +2.178.10.182 +2.178.11.80 +2.178.19.80 +2.178.20.196 +2.178.23.207 +2.178.27.216 +2.178.29.125 +2.178.36.176 +2.178.39.162 +2.178.40.0 +2.178.42.135 +2.178.43.167 +2.178.43.177 +2.178.50.89 +2.178.52.212 +2.178.54.116 +2.178.56.133 +2.178.56.180 +2.178.57.65 +2.178.57.197 +2.178.58.161 +2.178.60.178 +2.178.61.124 +2.178.64.134 +2.178.67.184 +2.178.71.229 +2.178.74.58 +2.178.75.120 +2.178.77.245 +2.178.78.82 +2.178.79.27 +2.178.83.179 +2.178.83.239 +2.178.84.153 +2.178.85.165 +2.178.85.191 +2.178.86.146 +2.178.88.122 +2.178.88.151 +2.178.88.219 +2.178.91.16 +2.178.93.55 +2.178.94.143 +2.178.97.123 +2.178.98.188 +2.178.98.249 +2.178.99.41 +2.178.100.217 +2.178.102.112 +2.178.105.194 +2.178.107.38 +2.178.114.76 +2.178.115.196 +2.178.118.67 +2.178.118.154 +2.178.120.208 +2.178.123.127 +2.178.124.114 +2.178.124.191 +2.178.125.58 +2.178.127.45 +2.178.128.156 +2.178.128.208 +2.178.129.67 +2.178.129.134 +2.178.129.248 +2.178.133.61 +2.178.133.89 +2.178.133.230 +2.178.134.72 +2.178.134.128 +2.178.135.43 +2.178.135.46 +2.178.135.80 +2.178.135.231 +2.178.137.200 +2.178.138.95 +2.178.138.140 +2.178.139.21 +2.178.140.86 +2.178.141.40 +2.178.141.97 +2.178.142.75 +2.178.142.175 +2.178.142.191 +2.178.142.222 +2.178.143.8 +2.178.143.50 +2.178.143.150 +2.178.143.156 +2.178.144.235 +2.178.145.153 +2.178.146.150 +2.178.146.217 +2.178.147.40 +2.178.148.180 +2.178.149.81 +2.178.149.116 +2.178.149.214 +2.178.150.61 +2.178.150.148 +2.178.150.213 +2.178.151.104 +2.178.153.98 +2.178.153.123 +2.178.154.100 +2.178.155.46 +2.178.155.61 +2.178.155.244 +2.178.156.98 +2.178.156.131 +2.178.157.73 +2.178.157.199 +2.178.157.252 +2.178.158.3 +2.178.158.22 +2.178.159.106 +2.178.160.20 +2.178.160.151 +2.178.161.18 +2.178.162.119 +2.178.163.197 +2.178.165.65 +2.178.165.89 +2.178.165.104 +2.178.165.248 +2.178.166.36 +2.178.167.34 +2.178.167.128 +2.178.168.229 +2.178.169.84 +2.178.169.108 +2.178.170.11 +2.178.170.64 +2.178.170.156 +2.178.171.32 +2.178.171.118 +2.178.172.168 +2.178.176.141 +2.178.177.7 +2.178.178.85 +2.178.179.51 +2.178.179.82 +2.178.179.238 +2.178.180.124 +2.178.181.78 +2.178.183.12 +2.178.183.91 +2.178.185.82 +2.178.186.143 +2.178.186.167 +2.178.188.86 +2.178.189.245 +2.178.190.217 +2.178.192.138 +2.178.193.233 +2.178.194.16 +2.178.196.53 +2.178.198.161 +2.178.198.179 +2.178.199.33 +2.178.199.252 +2.178.200.98 +2.178.200.141 +2.178.200.181 +2.178.202.80 +2.178.202.167 +2.178.203.61 +2.178.203.80 +2.178.205.233 +2.178.206.148 +2.178.207.46 +2.178.207.157 +2.178.208.131 +2.178.209.140 +2.178.212.135 +2.178.212.192 +2.178.212.233 +2.178.213.105 +2.178.213.159 +2.178.214.118 +2.178.215.152 +2.178.216.16 +2.178.216.38 +2.178.217.48 +2.178.217.189 +2.178.217.239 +2.178.218.242 +2.178.219.107 +2.178.219.155 +2.178.221.5 +2.178.222.124 +2.178.223.189 +2.178.224.90 +2.178.225.229 +2.178.227.158 +2.178.228.40 +2.178.228.151 +2.178.228.180 +2.178.229.11 +2.178.229.163 +2.178.230.231 +2.178.230.236 +2.178.231.100 +2.178.231.105 +2.178.233.18 +2.178.233.48 +2.178.235.155 +2.178.235.186 +2.178.236.33 +2.178.237.75 +2.178.237.225 +2.178.238.36 +2.178.240.189 +2.178.241.66 +2.178.241.67 +2.178.241.112 +2.178.242.140 +2.178.243.42 +2.178.243.90 +2.178.244.9 +2.178.244.60 +2.178.245.80 +2.178.246.23 +2.178.247.0 +2.178.247.52 +2.178.248.71 +2.178.250.16 +2.178.250.220 +2.178.251.234 +2.178.253.22 +2.178.253.98 +2.178.253.156 +2.178.255.244 +2.179.12.162 +2.179.14.81 +2.179.15.62 +2.179.15.121 +2.179.25.28 +2.179.25.190 +2.179.26.186 +2.179.26.196 +2.179.27.169 +2.179.27.182 +2.179.27.201 +2.179.28.221 +2.179.40.201 +2.179.41.31 +2.179.41.78 +2.179.41.207 +2.179.42.76 +2.179.42.172 +2.179.42.237 +2.179.43.57 +2.179.44.136 +2.179.45.126 +2.179.46.67 +2.179.46.124 +2.179.46.191 +2.179.47.35 +2.179.47.160 +2.179.60.25 +2.179.60.59 +2.179.60.71 +2.179.60.164 +2.179.60.168 +2.179.60.198 +2.179.60.221 +2.179.64.18 +2.179.64.30 +2.179.64.63 +2.179.64.96 +2.179.64.108 +2.179.66.33 +2.179.67.52 +2.179.67.104 +2.179.70.18 +2.179.70.48 +2.179.72.74 +2.179.72.78 +2.179.72.80 +2.179.73.171 +2.179.76.79 +2.179.78.50 +2.179.80.40 +2.179.86.249 +2.179.87.38 +2.179.87.48 +2.179.87.243 +2.179.91.5 +2.179.93.32 +2.179.93.72 +2.179.93.76 +2.179.93.79 +2.179.93.104 +2.179.95.129 +2.179.95.226 +2.179.100.135 +2.179.108.1 +2.179.109.5 +2.179.109.135 +2.179.111.167 +2.179.113.188 +2.179.114.132 +2.179.114.231 +2.179.117.246 +2.179.118.31 +2.179.118.97 +2.179.118.204 +2.179.125.52 +2.179.125.209 +2.179.127.48 +2.179.162.88 +2.179.162.194 +2.179.163.131 +2.179.164.6 +2.179.164.32 +2.179.164.69 +2.179.164.79 +2.179.164.90 +2.179.164.130 +2.179.164.157 +2.179.164.243 +2.179.165.56 +2.179.165.80 +2.179.165.87 +2.179.165.152 +2.179.165.203 +2.179.165.229 +2.179.166.17 +2.179.166.57 +2.179.166.58 +2.179.166.121 +2.179.166.159 +2.179.166.211 +2.179.167.14 +2.179.167.53 +2.179.167.60 +2.179.167.73 +2.179.167.75 +2.179.167.92 +2.179.167.110 +2.179.167.133 +2.179.167.134 +2.179.167.168 +2.179.167.221 +2.179.167.249 +2.179.176.209 +2.179.176.248 +2.179.176.249 +2.179.176.250 +2.179.176.251 +2.179.176.253 +2.179.178.33 +2.179.178.113 +2.179.179.118 +2.179.179.158 +2.179.179.234 +2.179.179.242 +2.179.179.250 +2.179.184.38 +2.179.186.44 +2.179.186.101 +2.179.186.171 +2.179.186.184 +2.179.186.202 +2.179.186.213 +2.179.187.23 +2.179.187.34 +2.179.189.112 +2.179.189.124 +2.179.189.141 +2.179.193.59 +2.179.194.89 +2.179.194.186 +2.179.194.203 +2.179.194.208 +2.179.194.244 +2.179.194.251 +2.179.209.128 +2.179.209.207 +2.179.210.133 +2.179.210.171 +2.179.210.254 +2.179.211.199 +2.179.212.157 +2.179.213.183 +2.179.215.93 +2.179.216.109 +2.179.216.197 +2.179.219.18 +2.179.220.45 +2.179.223.163 +2.179.223.218 +2.179.235.134 +2.180.0.49 +2.180.0.52 +2.180.0.55 +2.180.0.64 +2.180.0.65 +2.180.0.83 +2.180.0.116 +2.180.0.150 +2.180.0.173 +2.180.0.210 +2.180.1.43 +2.180.1.73 +2.180.1.88 +2.180.1.94 +2.180.1.159 +2.180.1.168 +2.180.1.243 +2.180.2.36 +2.180.2.72 +2.180.2.107 +2.180.2.116 +2.180.2.154 +2.180.3.38 +2.180.3.197 +2.180.3.252 +2.180.4.19 +2.180.4.44 +2.180.4.54 +2.180.4.236 +2.180.4.250 +2.180.5.28 +2.180.5.88 +2.180.5.140 +2.180.5.161 +2.180.5.243 +2.180.6.34 +2.180.6.105 +2.180.6.140 +2.180.6.144 +2.180.6.146 +2.180.7.39 +2.180.7.67 +2.180.7.153 +2.180.7.157 +2.180.7.199 +2.180.7.200 +2.180.7.227 +2.180.8.37 +2.180.8.78 +2.180.8.149 +2.180.8.174 +2.180.8.186 +2.180.8.193 +2.180.8.207 +2.180.9.92 +2.180.9.135 +2.180.9.152 +2.180.10.158 +2.180.10.204 +2.180.10.205 +2.180.10.218 +2.180.10.225 +2.180.11.45 +2.180.11.59 +2.180.11.94 +2.180.11.100 +2.180.11.102 +2.180.11.130 +2.180.11.158 +2.180.11.162 +2.180.12.11 +2.180.12.28 +2.180.12.41 +2.180.12.45 +2.180.12.68 +2.180.12.106 +2.180.12.114 +2.180.12.146 +2.180.12.178 +2.180.12.210 +2.180.13.32 +2.180.13.98 +2.180.13.108 +2.180.13.110 +2.180.13.127 +2.180.15.18 +2.180.15.31 +2.180.15.108 +2.180.15.134 +2.180.15.175 +2.180.16.1 +2.180.16.19 +2.180.16.42 +2.180.16.135 +2.180.16.196 +2.180.16.219 +2.180.17.32 +2.180.17.123 +2.180.17.136 +2.180.17.198 +2.180.17.219 +2.180.18.2 +2.180.18.84 +2.180.18.89 +2.180.18.91 +2.180.18.116 +2.180.18.126 +2.180.18.131 +2.180.18.226 +2.180.18.231 +2.180.18.241 +2.180.19.18 +2.180.19.47 +2.180.19.60 +2.180.19.172 +2.180.19.214 +2.180.19.221 +2.180.19.228 +2.180.19.231 +2.180.20.27 +2.180.20.63 +2.180.21.17 +2.180.21.69 +2.180.21.90 +2.180.21.144 +2.180.21.185 +2.180.21.234 +2.180.21.241 +2.180.22.26 +2.180.22.70 +2.180.22.81 +2.180.22.119 +2.180.23.91 +2.180.23.128 +2.180.23.143 +2.180.23.188 +2.180.23.221 +2.180.24.58 +2.180.24.69 +2.180.24.254 +2.180.25.17 +2.180.25.47 +2.180.25.52 +2.180.25.73 +2.180.25.195 +2.180.25.242 +2.180.26.40 +2.180.26.157 +2.180.27.80 +2.180.27.143 +2.180.28.104 +2.180.28.125 +2.180.28.153 +2.180.28.163 +2.180.28.173 +2.180.28.192 +2.180.28.193 +2.180.29.48 +2.180.29.70 +2.180.29.133 +2.180.29.185 +2.180.29.204 +2.180.29.213 +2.180.29.238 +2.180.30.56 +2.180.30.209 +2.180.30.218 +2.180.31.61 +2.180.31.62 +2.180.31.110 +2.180.31.224 +2.180.32.26 +2.180.32.61 +2.180.32.72 +2.180.32.166 +2.180.32.198 +2.180.33.61 +2.180.33.119 +2.180.33.127 +2.180.33.128 +2.180.33.132 +2.180.33.168 +2.180.35.13 +2.180.35.16 +2.180.35.35 +2.180.35.116 +2.180.35.169 +2.180.35.250 +2.180.36.105 +2.180.36.147 +2.180.36.173 +2.180.36.174 +2.180.36.191 +2.180.36.228 +2.180.37.29 +2.180.37.54 +2.180.37.67 +2.180.37.143 +2.180.37.151 +2.180.37.161 +2.180.37.248 +2.180.39.32 +2.180.39.33 +2.180.39.34 +2.180.39.35 +2.180.39.36 +2.180.39.37 +2.180.39.38 +2.180.39.82 +2.180.39.83 +2.180.40.78 +2.180.40.85 +2.180.40.96 +2.180.41.6 +2.180.41.18 +2.180.41.249 +2.180.42.1 +2.180.42.50 +2.180.42.54 +2.180.42.78 +2.180.42.95 +2.180.42.109 +2.180.42.137 +2.180.42.170 +2.180.42.218 +2.180.42.228 +2.180.43.13 +2.180.43.84 +2.180.43.147 +2.180.43.186 +2.180.43.189 +2.180.43.198 +2.180.44.1 +2.180.44.2 +2.180.44.39 +2.180.44.86 +2.180.44.137 +2.180.44.151 +2.180.44.173 +2.180.44.180 +2.180.44.200 +2.180.44.203 +2.180.47.142 +2.180.47.164 +2.180.47.254 +2.180.130.61 +2.180.131.14 +2.180.132.126 +2.180.133.193 +2.180.135.8 +2.180.135.143 +2.180.138.56 +2.180.138.237 +2.180.139.63 +2.180.141.92 +2.180.146.226 +2.180.147.108 +2.180.148.113 +2.180.150.154 +2.180.151.218 +2.180.151.249 +2.180.154.235 +2.180.155.106 +2.180.155.252 +2.180.156.36 +2.180.159.21 +2.180.161.42 +2.180.162.97 +2.180.163.157 +2.180.166.27 +2.180.167.58 +2.180.167.63 +2.180.167.137 +2.180.169.124 +2.180.170.132 +2.180.171.158 +2.180.172.39 +2.180.172.198 +2.180.174.226 +2.180.175.1 +2.180.175.255 +2.180.177.79 +2.180.178.149 +2.180.178.154 +2.180.180.221 +2.180.186.98 +2.180.188.123 +2.180.197.167 +2.180.198.23 +2.180.198.235 +2.180.199.134 +2.180.199.177 +2.180.208.78 +2.180.210.6 +2.180.211.96 +2.180.215.214 +2.180.216.77 +2.180.216.116 +2.180.219.96 +2.180.223.7 +2.180.228.75 +2.180.230.225 +2.180.236.114 +2.180.238.232 +2.180.242.52 +2.180.243.51 +2.180.243.243 +2.180.248.212 +2.180.249.74 +2.180.251.146 +2.180.252.125 +2.181.0.37 +2.181.0.51 +2.181.0.54 +2.181.0.73 +2.181.0.234 +2.181.1.100 +2.181.1.245 +2.181.30.24 +2.181.30.85 +2.181.30.88 +2.181.30.198 +2.181.30.234 +2.181.31.25 +2.181.31.55 +2.181.31.69 +2.181.31.104 +2.181.31.150 +2.181.31.221 +2.181.34.28 +2.181.34.34 +2.181.34.55 +2.181.34.74 +2.181.34.255 +2.181.35.31 +2.181.35.51 +2.181.35.65 +2.181.35.210 +2.181.56.31 +2.181.58.89 +2.181.61.201 +2.181.61.208 +2.181.62.159 +2.181.78.26 +2.181.78.32 +2.181.78.219 +2.181.80.96 +2.181.81.4 +2.181.81.34 +2.181.81.155 +2.181.82.22 +2.181.82.53 +2.181.82.105 +2.181.82.191 +2.181.83.13 +2.181.84.235 +2.181.85.80 +2.181.85.206 +2.181.86.11 +2.181.86.241 +2.181.87.182 +2.181.87.219 +2.181.88.14 +2.181.89.144 +2.181.91.16 +2.181.92.32 +2.181.92.82 +2.181.92.254 +2.181.93.125 +2.181.93.146 +2.181.93.233 +2.181.94.124 +2.181.96.98 +2.181.96.159 +2.181.98.32 +2.181.98.176 +2.181.99.194 +2.181.100.35 +2.181.100.163 +2.181.101.98 +2.181.101.172 +2.181.102.30 +2.181.102.75 +2.181.102.98 +2.181.102.196 +2.181.102.208 +2.181.103.72 +2.181.104.177 +2.181.104.254 +2.181.105.60 +2.181.106.197 +2.181.107.74 +2.181.108.16 +2.181.108.190 +2.181.109.58 +2.181.109.106 +2.181.110.156 +2.181.110.241 +2.181.129.23 +2.181.195.99 +2.181.195.113 +2.181.198.66 +2.181.201.158 +2.181.202.229 +2.181.204.140 +2.181.206.33 +2.181.206.133 +2.181.208.45 +2.181.210.150 +2.181.211.7 +2.181.213.41 +2.181.213.83 +2.181.215.172 +2.181.216.44 +2.181.216.54 +2.181.216.225 +2.181.217.76 +2.181.217.170 +2.181.221.50 +2.181.222.216 +2.181.222.253 +2.181.224.62 +2.181.225.138 +2.181.225.203 +2.181.225.237 +2.181.226.194 +2.181.227.140 +2.181.227.172 +2.181.227.213 +2.181.228.24 +2.181.228.113 +2.181.228.194 +2.181.229.49 +2.181.229.236 +2.181.230.167 +2.181.231.150 +2.181.232.216 +2.181.234.74 +2.181.234.129 +2.181.234.135 +2.181.234.140 +2.181.234.194 +2.181.234.213 +2.181.234.222 +2.181.235.126 +2.181.236.18 +2.181.236.228 +2.181.237.88 +2.181.237.120 +2.181.238.72 +2.181.239.75 +2.181.239.206 +2.181.239.207 +2.181.240.16 +2.181.240.143 +2.181.240.226 +2.181.241.99 +2.181.241.197 +2.181.242.59 +2.181.243.143 +2.181.244.40 +2.181.245.241 +2.181.246.95 +2.181.249.188 +2.181.251.145 +2.181.254.53 +2.181.254.87 +2.181.254.127 +2.181.255.171 +2.181.255.222 +2.182.40.14 +2.182.40.59 +2.182.40.76 +2.182.43.6 +2.182.43.187 +2.182.44.240 +2.182.46.202 +2.182.47.140 +2.182.48.163 +2.182.48.247 +2.182.51.1 +2.182.51.29 +2.182.51.63 +2.182.51.99 +2.182.52.72 +2.182.53.247 +2.182.54.149 +2.182.66.165 +2.182.79.37 +2.182.89.108 +2.182.90.205 +2.182.112.11 +2.182.112.125 +2.182.114.75 +2.182.115.46 +2.182.115.83 +2.182.115.91 +2.182.116.22 +2.182.116.70 +2.182.116.103 +2.182.117.118 +2.182.124.100 +2.182.125.223 +2.182.152.15 +2.182.152.37 +2.182.152.103 +2.182.152.115 +2.182.152.136 +2.182.152.138 +2.182.152.155 +2.182.152.206 +2.182.152.240 +2.182.152.254 +2.182.153.95 +2.182.153.106 +2.182.153.125 +2.182.153.131 +2.182.153.145 +2.182.153.163 +2.182.153.196 +2.182.154.27 +2.182.154.57 +2.182.154.65 +2.182.154.77 +2.182.154.85 +2.182.154.110 +2.182.154.130 +2.182.154.158 +2.182.154.160 +2.182.154.185 +2.182.154.207 +2.182.155.160 +2.182.163.78 +2.182.176.193 +2.182.179.32 +2.182.179.130 +2.182.179.236 +2.182.180.119 +2.182.180.194 +2.182.181.130 +2.182.184.83 +2.182.186.225 +2.182.187.13 +2.182.188.73 +2.182.188.156 +2.182.189.30 +2.182.189.33 +2.182.189.147 +2.182.189.169 +2.182.190.37 +2.182.191.173 +2.182.192.46 +2.182.196.9 +2.182.196.65 +2.182.196.133 +2.182.196.234 +2.182.198.213 +2.182.201.3 +2.182.201.5 +2.182.201.71 +2.182.201.220 +2.182.201.221 +2.182.201.222 +2.182.204.29 +2.182.204.78 +2.182.204.250 +2.182.205.252 +2.182.206.42 +2.182.206.58 +2.182.206.121 +2.182.207.14 +2.182.207.191 +2.182.209.24 +2.182.210.235 +2.182.212.252 +2.182.214.246 +2.182.215.122 +2.182.216.144 +2.182.223.204 +2.182.228.247 +2.182.231.42 +2.182.232.85 +2.182.233.138 +2.182.236.135 +2.182.236.141 +2.182.236.154 +2.182.236.237 +2.182.237.186 +2.182.237.251 +2.182.239.170 +2.182.241.197 +2.182.243.110 +2.182.243.151 +2.182.243.209 +2.182.243.244 +2.182.244.151 +2.182.245.101 +2.182.245.200 +2.182.245.231 +2.182.246.102 +2.182.246.134 +2.182.249.84 +2.182.249.130 +2.182.250.85 +2.182.250.160 +2.182.253.241 +2.183.8.13 +2.183.8.19 +2.183.8.90 +2.183.8.118 +2.183.8.173 +2.183.8.186 +2.183.9.86 +2.183.9.165 +2.183.9.236 +2.183.32.12 +2.183.32.52 +2.183.32.62 +2.183.32.84 +2.183.32.112 +2.183.80.7 +2.183.80.8 +2.183.82.5 +2.183.82.51 +2.183.83.147 +2.183.83.235 +2.183.84.4 +2.183.86.138 +2.183.86.218 +2.183.87.231 +2.183.87.239 +2.183.97.178 +2.183.98.176 +2.183.100.95 +2.183.100.145 +2.183.102.201 +2.183.105.124 +2.183.105.142 +2.183.106.13 +2.183.107.214 +2.183.110.163 +2.183.112.69 +2.183.112.159 +2.183.112.219 +2.183.112.252 +2.183.113.112 +2.183.114.9 +2.183.114.107 +2.183.114.243 +2.183.115.91 +2.183.117.133 +2.183.117.167 +2.183.119.25 +2.183.120.146 +2.183.121.29 +2.183.123.172 +2.183.135.24 +2.183.135.52 +2.183.203.74 +2.183.203.131 +2.183.203.135 +2.183.203.142 +2.183.203.146 +2.183.231.18 +2.183.237.8 +2.184.38.118 +2.184.41.171 +2.184.48.4 +2.184.48.42 +2.184.48.46 +2.184.49.21 +2.184.49.22 +2.184.49.145 +2.184.50.27 +2.184.50.34 +2.184.50.144 +2.184.50.145 +2.184.50.165 +2.184.50.172 +2.184.51.69 +2.184.51.140 +2.184.51.143 +2.184.52.22 +2.184.52.194 +2.184.52.249 +2.184.52.250 +2.184.53.168 +2.184.53.237 +2.184.54.17 +2.184.54.38 +2.184.54.105 +2.184.54.113 +2.184.54.128 +2.184.54.157 +2.184.54.208 +2.184.55.14 +2.184.55.22 +2.184.55.37 +2.184.55.83 +2.184.55.105 +2.184.55.128 +2.184.55.156 +2.184.55.183 +2.184.57.117 +2.184.57.166 +2.184.58.19 +2.184.58.25 +2.184.58.188 +2.184.59.29 +2.184.59.52 +2.184.59.82 +2.184.59.137 +2.184.60.6 +2.184.61.36 +2.184.61.61 +2.184.61.204 +2.184.70.91 +2.184.70.104 +2.184.71.2 +2.184.71.3 +2.184.71.4 +2.184.71.6 +2.184.74.2 +2.184.74.62 +2.184.89.24 +2.184.90.99 +2.184.90.104 +2.184.90.129 +2.184.90.198 +2.184.92.31 +2.184.118.38 +2.184.155.46 +2.184.155.72 +2.184.155.83 +2.184.155.92 +2.184.155.117 +2.184.156.9 +2.184.156.38 +2.184.156.79 +2.184.156.155 +2.184.156.188 +2.184.156.189 +2.184.156.233 +2.184.157.5 +2.184.157.15 +2.184.157.18 +2.184.157.129 +2.184.157.134 +2.184.157.146 +2.184.157.150 +2.184.157.226 +2.184.157.237 +2.184.158.14 +2.184.158.38 +2.184.158.117 +2.184.158.221 +2.184.158.253 +2.184.159.66 +2.184.159.68 +2.184.159.111 +2.184.159.119 +2.184.159.126 +2.184.159.167 +2.184.159.227 +2.184.169.181 +2.184.171.87 +2.184.182.68 +2.184.182.76 +2.184.182.87 +2.184.182.136 +2.184.182.152 +2.184.217.25 +2.184.217.127 +2.184.217.167 +2.184.217.176 +2.184.217.193 +2.184.217.197 +2.184.217.224 +2.184.236.9 +2.184.236.34 +2.184.236.119 +2.184.236.159 +2.184.236.164 +2.184.236.166 +2.184.236.197 +2.184.236.217 +2.184.236.233 +2.184.236.240 +2.184.237.70 +2.184.237.208 +2.184.238.17 +2.184.238.19 +2.184.238.113 +2.184.238.123 +2.184.238.214 +2.184.239.89 +2.184.239.236 +2.185.17.15 +2.185.17.144 +2.185.17.206 +2.185.17.242 +2.185.18.223 +2.185.96.234 +2.185.97.7 +2.185.97.61 +2.185.97.73 +2.185.98.143 +2.185.98.165 +2.185.100.127 +2.185.100.154 +2.185.100.238 +2.185.105.18 +2.185.109.245 +2.185.112.36 +2.185.112.226 +2.185.114.134 +2.185.116.58 +2.185.116.139 +2.185.122.55 +2.185.123.194 +2.185.126.25 +2.185.126.221 +2.185.126.230 +2.185.127.96 +2.185.127.128 +2.185.128.41 +2.185.128.69 +2.185.128.113 +2.185.128.121 +2.185.128.161 +2.185.128.211 +2.185.128.218 +2.185.128.221 +2.185.128.225 +2.185.128.230 +2.185.141.25 +2.185.141.55 +2.185.143.33 +2.185.143.103 +2.185.144.22 +2.185.147.55 +2.185.147.166 +2.185.147.174 +2.185.147.198 +2.185.147.202 +2.185.148.21 +2.185.148.118 +2.185.148.128 +2.185.148.154 +2.185.148.217 +2.185.149.163 +2.185.150.166 +2.185.151.209 +2.185.152.1 +2.185.152.21 +2.185.153.51 +2.185.153.188 +2.185.175.55 +2.185.191.203 +2.185.192.9 +2.185.203.219 +2.185.214.7 +2.185.214.202 +2.185.224.33 +2.185.224.152 +2.185.224.167 +2.185.224.187 +2.185.225.195 +2.185.226.211 +2.185.227.51 +2.185.227.56 +2.185.227.124 +2.185.227.138 +2.185.229.38 +2.185.229.115 +2.185.231.20 +2.185.232.50 +2.185.232.170 +2.185.232.210 +2.185.232.242 +2.185.233.83 +2.185.233.166 +2.185.234.55 +2.185.235.73 +2.185.237.4 +2.185.238.45 +2.185.238.145 +2.185.239.130 +2.185.239.131 +2.185.239.132 +2.185.239.133 +2.185.239.134 +2.185.239.136 +2.185.239.137 +2.185.239.138 +2.185.239.139 +2.185.239.140 +2.185.239.230 +2.185.241.0 +2.185.241.150 +2.185.242.112 +2.185.242.141 +2.185.242.180 +2.185.244.68 +2.185.244.78 +2.185.247.168 +2.185.247.176 +2.185.247.197 +2.185.248.2 +2.185.248.141 +2.185.249.54 +2.185.250.155 +2.185.250.165 +2.185.250.215 +2.185.251.95 +2.185.252.124 +2.185.252.253 +2.185.253.215 +2.186.12.75 +2.186.12.113 +2.186.12.137 +2.186.12.138 +2.186.12.156 +2.186.12.180 +2.186.13.21 +2.186.13.26 +2.186.13.94 +2.186.14.27 +2.186.14.60 +2.186.14.92 +2.186.14.94 +2.186.14.102 +2.186.14.167 +2.186.14.234 +2.186.14.239 +2.186.15.17 +2.186.15.39 +2.186.15.74 +2.186.15.79 +2.186.15.170 +2.186.15.194 +2.186.15.196 +2.186.15.199 +2.186.112.22 +2.186.112.24 +2.186.112.85 +2.186.112.132 +2.186.112.178 +2.186.112.186 +2.186.114.125 +2.186.114.179 +2.186.114.196 +2.186.114.236 +2.186.115.35 +2.186.115.86 +2.186.115.162 +2.186.115.184 +2.186.115.252 +2.186.116.38 +2.186.116.121 +2.186.116.210 +2.186.117.2 +2.186.117.79 +2.186.117.93 +2.186.117.107 +2.186.117.140 +2.186.117.164 +2.186.118.17 +2.186.118.44 +2.186.118.49 +2.186.118.81 +2.186.118.165 +2.186.118.211 +2.186.118.255 +2.186.119.27 +2.186.119.32 +2.186.119.48 +2.186.119.65 +2.186.119.169 +2.186.119.217 +2.186.119.253 +2.186.120.41 +2.186.120.64 +2.186.120.70 +2.186.120.133 +2.186.120.175 +2.186.120.181 +2.186.120.202 +2.186.120.215 +2.186.121.39 +2.186.121.100 +2.186.121.170 +2.186.122.15 +2.186.122.50 +2.186.122.51 +2.186.122.60 +2.186.122.125 +2.186.122.184 +2.186.122.199 +2.186.122.214 +2.186.123.35 +2.186.123.64 +2.186.123.108 +2.186.123.152 +2.186.123.183 +2.186.123.213 +2.186.127.1 +2.186.127.68 +2.186.127.90 +2.186.127.158 +2.186.127.164 +2.186.127.212 +2.186.127.236 +2.186.127.247 +2.186.229.46 +2.186.229.60 +2.186.229.110 +2.186.229.192 +2.186.229.230 +2.186.231.68 +2.186.231.129 +2.186.231.130 +2.186.231.155 +2.186.231.241 +2.186.231.242 +2.186.231.243 +2.186.231.244 +2.186.231.245 +2.186.231.246 +2.187.1.167 +2.187.2.23 +2.187.2.30 +2.187.2.96 +2.187.2.101 +2.187.2.123 +2.187.2.184 +2.187.2.185 +2.187.4.172 +2.187.5.66 +2.187.6.17 +2.187.6.112 +2.187.6.182 +2.187.6.243 +2.187.7.232 +2.187.8.176 +2.187.9.2 +2.187.10.227 +2.187.11.1 +2.187.16.10 +2.187.16.17 +2.187.16.25 +2.187.16.63 +2.187.16.90 +2.187.16.147 +2.187.16.179 +2.187.16.223 +2.187.16.243 +2.187.16.252 +2.187.18.17 +2.187.18.248 +2.187.19.18 +2.187.19.151 +2.187.19.241 +2.187.19.242 +2.187.24.39 +2.187.24.235 +2.187.26.17 +2.187.26.75 +2.187.26.145 +2.187.27.146 +2.187.28.228 +2.187.30.84 +2.187.32.13 +2.187.32.72 +2.187.32.210 +2.187.32.228 +2.187.33.42 +2.187.33.50 +2.187.33.107 +2.187.33.174 +2.187.33.187 +2.187.34.87 +2.187.34.126 +2.187.34.161 +2.187.35.15 +2.187.35.19 +2.187.35.21 +2.187.35.61 +2.187.35.84 +2.187.35.140 +2.187.36.11 +2.187.37.180 +2.187.37.242 +2.187.38.26 +2.187.38.201 +2.187.39.91 +2.187.39.157 +2.187.39.172 +2.187.39.190 +2.187.39.234 +2.187.56.225 +2.187.57.8 +2.187.57.17 +2.187.57.23 +2.187.62.102 +2.187.96.30 +2.187.96.85 +2.187.96.116 +2.187.96.252 +2.187.97.4 +2.187.97.111 +2.187.97.121 +2.187.97.141 +2.187.98.41 +2.187.98.118 +2.187.98.181 +2.187.99.27 +2.187.99.71 +2.187.99.150 +2.187.100.13 +2.187.100.51 +2.187.100.137 +2.187.101.33 +2.187.101.46 +2.187.117.47 +2.187.117.140 +2.187.118.1 +2.187.119.3 +2.187.119.6 +2.187.119.113 +2.187.128.43 +2.187.128.159 +2.187.128.166 +2.187.128.170 +2.187.140.131 +2.187.142.95 +2.187.143.145 +2.187.150.9 +2.187.150.48 +2.187.150.108 +2.187.186.17 +2.187.186.65 +2.187.186.66 +2.187.186.67 +2.187.186.68 +2.187.186.69 +2.187.188.98 +2.187.188.123 +2.187.188.130 +2.187.188.137 +2.187.188.143 +2.187.188.174 +2.187.188.190 +2.187.188.239 +2.187.188.253 +2.187.189.60 +2.187.189.121 +2.187.189.193 +2.187.189.208 +2.187.189.226 +2.187.189.227 +2.187.212.11 +2.187.212.125 +2.187.213.78 +2.187.213.158 +2.187.221.33 +2.187.249.121 +2.187.249.148 +2.187.249.177 +2.187.249.178 +2.187.250.11 +2.187.250.50 +2.187.250.80 +2.187.250.110 +2.187.250.169 +2.187.250.218 +2.187.250.223 +2.187.251.88 +2.187.251.245 +2.188.43.76 +2.188.72.11 +2.188.72.48 +2.188.72.54 +2.188.72.56 +2.188.72.150 +2.188.72.174 +2.188.72.209 +2.188.72.220 +2.188.72.230 +2.188.72.238 +2.188.73.129 +2.188.73.137 +2.188.73.145 +2.188.73.153 +2.188.73.157 +2.188.73.161 +2.188.73.165 +2.188.75.14 +2.188.75.178 +2.188.160.236 +2.188.161.170 +2.188.162.74 +2.188.162.75 +2.188.162.78 +2.188.164.54 +2.188.164.91 +2.188.164.117 +2.188.164.140 +2.188.165.114 +2.188.165.170 +2.188.166.50 +2.188.166.74 +2.188.166.75 +2.188.166.78 +2.188.167.14 +2.188.167.34 +2.188.167.54 +2.188.167.114 +2.188.167.156 +2.188.168.41 +2.188.168.87 +2.188.168.88 +2.188.168.135 +2.188.168.222 +2.188.174.151 +2.188.174.173 +2.188.174.194 +2.188.174.230 +2.188.175.4 +2.188.175.21 +2.188.175.94 +2.188.175.105 +2.188.175.215 +2.188.175.228 +2.188.208.110 +2.188.208.142 +2.188.208.242 +2.188.208.254 +2.188.210.2 +2.188.210.3 +2.188.210.5 +2.188.210.6 +2.188.210.8 +2.188.210.10 +2.188.210.12 +2.188.210.13 +2.188.210.14 +2.188.210.16 +2.188.210.20 +2.188.210.21 +2.188.210.50 +2.188.210.111 +2.188.210.112 +2.188.210.125 +2.188.211.7 +2.188.211.234 +2.188.214.250 +2.188.214.252 +2.188.214.253 +2.188.214.254 +2.188.215.34 +2.188.215.67 +2.188.215.103 +2.188.217.54 +2.188.220.132 +2.188.221.14 +2.188.222.1 +2.188.222.2 +2.188.222.11 +2.188.222.14 +2.188.222.65 +2.188.222.81 +2.188.225.66 +2.188.225.67 +2.188.225.136 +2.188.225.147 +2.188.226.187 +2.188.226.188 +2.188.226.189 +2.188.226.190 +2.188.226.243 +2.188.226.245 +2.188.226.246 +2.188.226.247 +2.188.226.248 +2.188.226.250 +2.188.226.251 +2.188.226.252 +2.188.228.36 +2.188.228.37 +2.188.228.38 +2.188.228.98 +2.188.228.130 +2.188.230.210 +2.188.230.212 +2.188.231.2 +2.188.231.106 +2.188.231.110 +2.188.231.114 +2.188.231.115 +2.188.231.116 +2.188.231.117 +2.188.231.118 +2.188.231.120 +2.188.231.122 +2.188.231.123 +2.188.231.125 +2.188.231.194 +2.188.231.195 +2.188.231.196 +2.188.231.197 +2.188.231.198 +2.188.231.199 +2.188.235.128 +2.188.236.50 +2.188.236.70 +2.188.237.22 +2.188.237.146 +2.189.1.1 +2.189.1.2 +2.189.1.11 +2.189.1.12 +2.189.1.14 +2.189.1.40 +2.189.1.104 +2.189.1.251 +2.189.8.168 +2.189.9.77 +2.189.9.163 +2.189.10.247 +2.189.11.76 +2.189.11.239 +2.189.16.113 +2.189.20.156 +2.189.20.158 +2.189.21.139 +2.189.23.66 +2.189.28.38 +2.189.28.134 +2.189.28.166 +2.189.29.111 +2.189.29.229 +2.189.29.231 +2.189.30.37 +2.189.31.217 +2.189.32.52 +2.189.32.199 +2.189.33.61 +2.189.33.90 +2.189.33.114 +2.189.34.164 +2.189.34.221 +2.189.34.244 +2.189.59.81 +2.189.67.42 +2.189.86.10 +2.189.86.11 +2.189.86.12 +2.189.86.13 +2.189.86.14 +2.189.86.15 +2.189.86.16 +2.189.86.19 +2.189.86.30 +2.189.86.31 +2.189.86.32 +2.189.86.34 +2.189.86.35 +2.189.86.36 +2.189.86.41 +2.189.86.42 +2.189.86.43 +2.189.86.44 +2.189.86.45 +2.189.86.46 +2.189.86.84 +2.189.86.85 +2.189.86.86 +2.189.86.87 +2.189.86.88 +2.189.86.89 +2.189.86.90 +2.189.86.91 +2.189.86.92 +2.189.86.93 +2.189.86.94 +2.189.86.98 +2.189.86.103 +2.189.86.104 +2.189.86.105 +2.189.86.107 +2.189.86.108 +2.189.86.109 +2.189.86.110 +2.189.86.111 +2.189.86.112 +2.189.86.113 +2.189.87.172 +2.189.87.228 +2.189.89.18 +2.189.89.110 +2.189.89.166 +2.189.89.214 +2.189.90.84 +2.189.90.147 +2.189.90.206 +2.189.91.37 +2.189.91.141 +2.189.91.226 +2.189.91.229 +2.189.91.230 +2.189.91.231 +2.189.91.232 +2.189.92.31 +2.189.92.217 +2.189.93.162 +2.189.93.224 +2.189.96.121 +2.189.96.136 +2.189.98.67 +2.189.98.113 +2.189.98.135 +2.189.98.137 +2.189.101.0 +2.189.101.6 +2.189.101.12 +2.189.101.13 +2.189.101.14 +2.189.101.15 +2.189.101.85 +2.189.101.100 +2.189.101.101 +2.189.101.139 +2.189.101.146 +2.189.101.165 +2.189.101.224 +2.189.101.231 +2.189.102.15 +2.189.104.1 +2.189.104.3 +2.189.114.61 +2.189.115.49 +2.189.115.76 +2.189.115.150 +2.189.115.162 +2.189.115.174 +2.189.115.183 +2.189.120.2 +2.189.120.4 +2.189.120.14 +2.189.120.16 +2.189.120.23 +2.189.120.35 +2.189.120.39 +2.189.120.40 +2.189.120.43 +2.189.120.69 +2.189.120.74 +2.189.120.77 +2.189.120.78 +2.189.120.99 +2.189.120.109 +2.189.120.114 +2.189.120.116 +2.189.120.117 +2.189.120.144 +2.189.120.149 +2.189.120.164 +2.189.120.180 +2.189.120.183 +2.189.120.201 +2.189.120.213 +2.189.120.214 +2.189.120.220 +2.189.120.226 +2.189.120.231 +2.189.120.232 +2.189.120.233 +2.189.121.4 +2.189.121.10 +2.189.121.15 +2.189.121.16 +2.189.121.21 +2.189.121.47 +2.189.121.49 +2.189.121.53 +2.189.121.54 +2.189.121.56 +2.189.121.58 +2.189.121.62 +2.189.121.66 +2.189.121.78 +2.189.121.79 +2.189.121.88 +2.189.121.90 +2.189.121.91 +2.189.121.99 +2.189.121.104 +2.189.121.121 +2.189.121.131 +2.189.121.136 +2.189.121.146 +2.189.121.152 +2.189.121.155 +2.189.121.163 +2.189.121.176 +2.189.121.178 +2.189.121.191 +2.189.121.202 +2.189.121.210 +2.189.121.218 +2.189.121.231 +2.189.121.232 +2.189.121.240 +2.189.121.244 +2.189.122.20 +2.189.122.22 +2.189.122.103 +2.189.122.119 +2.189.122.240 +2.189.128.11 +2.189.128.12 +2.189.128.13 +2.189.128.180 +2.189.128.181 +2.189.128.182 +2.189.130.3 +2.189.130.4 +2.189.130.8 +2.189.130.10 +2.189.130.18 +2.189.130.33 +2.189.136.3 +2.189.136.6 +2.189.136.25 +2.189.136.67 +2.189.136.69 +2.189.136.78 +2.189.136.95 +2.189.136.112 +2.189.136.132 +2.189.136.153 +2.189.136.178 +2.189.137.0 +2.189.137.20 +2.189.137.27 +2.189.137.29 +2.189.137.43 +2.189.137.61 +2.189.137.98 +2.189.137.99 +2.189.137.221 +2.189.137.222 +2.189.137.223 +2.189.137.224 +2.189.137.240 +2.189.139.0 +2.189.139.5 +2.189.139.7 +2.189.139.10 +2.189.139.14 +2.189.139.16 +2.189.139.18 +2.189.139.21 +2.189.139.25 +2.189.139.35 +2.189.139.41 +2.189.139.55 +2.189.139.56 +2.189.139.63 +2.189.139.65 +2.189.139.76 +2.189.139.85 +2.189.139.89 +2.189.139.95 +2.189.139.101 +2.189.139.108 +2.189.139.109 +2.189.139.114 +2.189.139.120 +2.189.139.125 +2.189.139.128 +2.189.139.129 +2.189.139.130 +2.189.139.131 +2.189.139.135 +2.189.139.145 +2.189.139.150 +2.189.139.157 +2.189.139.161 +2.189.139.171 +2.189.139.180 +2.189.139.185 +2.189.139.193 +2.189.139.206 +2.189.139.214 +2.189.139.218 +2.189.139.221 +2.189.139.222 +2.189.139.230 +2.189.139.233 +2.189.139.240 +2.189.139.241 +2.189.139.242 +2.189.139.246 +2.189.139.247 +2.189.139.250 +2.189.140.5 +2.189.140.6 +2.189.140.7 +2.189.141.32 +2.189.142.20 +2.189.142.33 +2.189.142.38 +2.189.142.48 +2.189.142.55 +2.189.142.57 +2.189.142.63 +2.189.142.98 +2.189.142.112 +2.189.143.4 +2.189.143.5 +2.189.143.8 +2.189.143.11 +2.189.143.24 +2.189.143.49 +2.189.143.58 +2.189.143.61 +2.189.143.78 +2.189.143.145 +2.189.143.181 +2.189.143.191 +2.189.143.222 +2.189.143.253 +2.189.144.9 +2.189.144.11 +2.189.144.19 +2.189.144.28 +2.189.144.40 +2.189.144.55 +2.189.144.65 +2.189.144.73 +2.189.144.83 +2.189.144.86 +2.189.144.104 +2.189.144.109 +2.189.144.121 +2.189.144.126 +2.189.144.129 +2.189.144.140 +2.189.144.143 +2.189.144.147 +2.189.144.151 +2.189.144.159 +2.189.144.163 +2.189.144.173 +2.189.144.181 +2.189.144.184 +2.189.144.186 +2.189.144.207 +2.189.144.215 +2.189.144.218 +2.189.144.221 +2.189.144.222 +2.189.144.224 +2.189.144.227 +2.189.144.229 +2.189.144.233 +2.189.144.235 +2.189.144.247 +2.189.145.4 +2.189.145.11 +2.189.145.34 +2.189.145.46 +2.189.145.48 +2.189.145.70 +2.189.145.71 +2.189.145.75 +2.189.145.104 +2.189.145.108 +2.189.145.109 +2.189.145.122 +2.189.145.149 +2.189.145.154 +2.189.145.160 +2.189.145.164 +2.189.145.167 +2.189.145.168 +2.189.145.174 +2.189.145.177 +2.189.145.183 +2.189.145.193 +2.189.145.195 +2.189.145.197 +2.189.145.211 +2.189.145.213 +2.189.145.224 +2.189.145.244 +2.189.145.246 +2.189.145.249 +2.189.145.252 +2.189.146.113 +2.189.147.44 +2.189.147.188 +2.189.147.241 +2.189.148.2 +2.189.148.8 +2.189.148.12 +2.189.148.16 +2.189.148.17 +2.189.148.23 +2.189.148.28 +2.189.148.29 +2.189.148.32 +2.189.148.33 +2.189.148.34 +2.189.148.36 +2.189.148.38 +2.189.148.39 +2.189.148.43 +2.189.148.44 +2.189.148.49 +2.189.148.54 +2.189.148.63 +2.189.148.64 +2.189.148.67 +2.189.148.69 +2.189.148.71 +2.189.148.82 +2.189.148.87 +2.189.148.88 +2.189.148.94 +2.189.148.98 +2.189.148.104 +2.189.148.109 +2.189.148.126 +2.189.148.131 +2.189.148.133 +2.189.148.136 +2.189.148.137 +2.189.148.139 +2.189.148.146 +2.189.148.156 +2.189.148.166 +2.189.148.167 +2.189.148.169 +2.189.148.171 +2.189.148.176 +2.189.148.181 +2.189.148.182 +2.189.148.183 +2.189.148.192 +2.189.148.204 +2.189.148.205 +2.189.148.214 +2.189.148.215 +2.189.148.231 +2.189.148.235 +2.189.148.248 +2.189.148.251 +2.189.148.253 +2.189.151.5 +2.189.151.68 +2.189.151.109 +2.189.151.116 +2.189.151.126 +2.189.151.130 +2.189.151.146 +2.189.151.165 +2.189.151.171 +2.189.151.174 +2.189.151.180 +2.189.153.96 +2.189.153.162 +2.189.156.30 +2.189.156.42 +2.189.158.176 +2.189.162.18 +2.189.162.250 +2.189.163.4 +2.189.163.63 +2.189.163.112 +2.189.164.26 +2.189.164.27 +2.189.164.28 +2.189.164.29 +2.189.167.131 +2.189.167.203 +2.189.167.210 +2.189.167.211 +2.189.167.212 +2.189.167.254 +2.189.172.30 +2.189.172.49 +2.189.218.246 +2.189.220.93 +2.189.236.154 +2.189.236.246 +2.189.237.61 +2.189.237.180 +2.189.237.190 +2.189.237.236 +2.189.237.254 +2.189.238.236 +2.189.239.123 +2.189.243.1 +2.189.243.22 +2.189.254.109 +2.190.1.39 +2.190.2.98 +2.190.6.96 +2.190.6.165 +2.190.8.47 +2.190.11.209 +2.190.17.151 +2.190.19.236 +2.190.20.16 +2.190.20.231 +2.190.23.71 +2.190.25.71 +2.190.30.229 +2.190.30.232 +2.190.31.210 +2.190.32.109 +2.190.34.61 +2.190.35.228 +2.190.36.45 +2.190.36.76 +2.190.39.192 +2.190.41.13 +2.190.41.22 +2.190.43.66 +2.190.44.173 +2.190.48.7 +2.190.48.125 +2.190.48.237 +2.190.49.82 +2.190.50.185 +2.190.50.196 +2.190.52.208 +2.190.52.213 +2.190.55.11 +2.190.55.244 +2.190.61.97 +2.190.62.119 +2.190.62.191 +2.190.65.167 +2.190.67.113 +2.190.69.243 +2.190.70.196 +2.190.71.71 +2.190.73.192 +2.190.74.207 +2.190.76.5 +2.190.76.85 +2.190.76.193 +2.190.77.23 +2.190.78.61 +2.190.79.242 +2.190.83.111 +2.190.83.208 +2.190.84.71 +2.190.86.236 +2.190.89.139 +2.190.92.35 +2.190.94.144 +2.190.99.230 +2.190.107.69 +2.190.109.210 +2.190.118.22 +2.190.120.59 +2.190.122.198 +2.190.123.73 +2.190.124.254 +2.190.125.199 +2.190.126.81 +2.190.127.182 +2.190.128.34 +2.190.128.144 +2.190.129.63 +2.190.131.45 +2.190.131.59 +2.190.131.77 +2.190.132.76 +2.190.132.127 +2.190.134.82 +2.190.134.83 +2.190.135.61 +2.190.136.197 +2.190.138.238 +2.190.139.113 +2.190.139.227 +2.190.140.0 +2.190.141.236 +2.190.144.221 +2.190.145.33 +2.190.145.162 +2.190.146.22 +2.190.149.231 +2.190.150.187 +2.190.152.194 +2.190.154.18 +2.190.154.112 +2.190.155.29 +2.190.156.203 +2.190.159.247 +2.190.159.252 +2.190.161.41 +2.190.161.135 +2.190.161.184 +2.190.163.101 +2.190.165.30 +2.190.165.60 +2.190.165.211 +2.190.166.161 +2.190.167.23 +2.190.167.212 +2.190.168.142 +2.190.168.170 +2.190.168.222 +2.190.169.17 +2.190.169.52 +2.190.169.66 +2.190.170.46 +2.190.170.99 +2.190.170.190 +2.190.170.226 +2.190.171.67 +2.190.171.109 +2.190.171.169 +2.190.172.25 +2.190.174.227 +2.190.175.249 +2.190.175.253 +2.190.178.124 +2.190.179.0 +2.190.179.147 +2.190.180.43 +2.190.180.189 +2.190.184.132 +2.190.185.87 +2.190.186.3 +2.190.186.158 +2.190.186.203 +2.190.187.159 +2.190.187.241 +2.190.188.66 +2.190.188.249 +2.190.191.189 +2.190.191.200 +2.190.192.226 +2.190.192.228 +2.190.193.203 +2.190.194.46 +2.190.195.166 +2.190.195.204 +2.190.195.218 +2.190.199.157 +2.190.200.150 +2.190.200.250 +2.190.201.12 +2.190.201.171 +2.190.202.65 +2.190.202.158 +2.190.202.198 +2.190.202.216 +2.190.203.54 +2.190.203.131 +2.190.203.251 +2.190.204.64 +2.190.205.188 +2.190.206.99 +2.190.206.170 +2.190.206.210 +2.190.206.216 +2.190.207.248 +2.190.209.183 +2.190.209.211 +2.190.209.250 +2.190.210.10 +2.190.210.75 +2.190.212.167 +2.190.212.231 +2.190.213.22 +2.190.216.62 +2.190.216.174 +2.190.217.24 +2.190.217.92 +2.190.217.165 +2.190.219.214 +2.190.220.78 +2.190.220.211 +2.190.220.241 +2.190.221.12 +2.190.221.194 +2.190.222.89 +2.190.223.87 +2.190.224.7 +2.190.224.132 +2.190.224.239 +2.190.230.119 +2.190.231.82 +2.190.231.157 +2.190.232.123 +2.190.232.132 +2.190.234.135 +2.190.235.146 +2.190.238.243 +2.190.239.223 +2.190.240.72 +2.190.240.176 +2.190.243.87 +2.190.246.193 +2.190.247.151 +2.190.249.82 +2.190.249.112 +2.190.250.112 +2.190.250.122 +2.190.251.161 +2.190.252.84 +2.190.252.208 +2.190.252.246 +2.190.253.55 +2.190.254.174 +2.190.254.245 +2.190.255.24 +2.190.255.185 +2.191.0.118 +2.191.1.47 +2.191.2.50 +2.191.2.235 +2.191.6.10 +2.191.6.87 +2.191.6.132 +2.191.7.0 +2.191.8.125 +2.191.8.147 +2.191.9.10 +2.191.9.194 +2.191.12.12 +2.191.12.134 +2.191.13.14 +2.191.14.38 +2.191.14.110 +2.191.14.148 +2.191.15.52 +2.191.15.163 +2.191.17.129 +2.191.18.196 +2.191.19.66 +2.191.19.120 +2.191.19.124 +2.191.19.208 +2.191.22.243 +2.191.23.201 +2.191.24.52 +2.191.24.62 +2.191.28.157 +2.191.28.241 +2.191.29.45 +2.191.29.48 +2.191.31.85 +2.191.32.24 +2.191.34.80 +2.191.35.207 +2.191.36.246 +2.191.37.33 +2.191.37.183 +2.191.38.68 +2.191.38.70 +2.191.38.186 +2.191.38.227 +2.191.40.77 +2.191.40.149 +2.191.40.224 +2.191.41.211 +2.191.42.150 +2.191.43.136 +2.191.43.178 +2.191.44.34 +2.191.44.39 +2.191.44.211 +2.191.45.40 +2.191.45.171 +2.191.45.181 +2.191.46.25 +2.191.46.73 +2.191.46.113 +2.191.46.114 +2.191.47.158 +2.191.47.201 +2.191.48.18 +2.191.48.179 +2.191.50.99 +2.191.51.173 +2.191.52.46 +2.191.53.61 +2.191.53.242 +2.191.55.165 +2.191.56.28 +2.191.57.66 +2.191.57.179 +2.191.58.176 +2.191.58.178 +2.191.59.170 +2.191.60.102 +2.191.60.186 +2.191.60.205 +2.191.61.246 +2.191.62.3 +2.191.63.31 +2.191.63.189 +2.191.64.56 +2.191.64.61 +2.191.64.186 +2.191.64.242 +2.191.65.4 +2.191.67.29 +2.191.68.135 +2.191.69.94 +2.191.69.185 +2.191.70.110 +2.191.70.132 +2.191.71.57 +2.191.71.77 +2.191.71.219 +2.191.73.219 +2.191.74.137 +2.191.75.50 +2.191.75.144 +2.191.75.157 +2.191.76.121 +2.191.80.4 +2.191.80.253 +2.191.81.64 +2.191.82.127 +2.191.83.137 +2.191.84.65 +2.191.85.54 +2.191.85.82 +2.191.86.107 +2.191.86.180 +2.191.86.219 +2.191.86.255 +2.191.87.132 +2.191.87.206 +2.191.89.32 +2.191.90.27 +2.191.90.185 +2.191.90.186 +2.191.91.32 +2.191.91.101 +2.191.92.107 +2.191.92.185 +2.191.93.14 +2.191.94.26 +2.191.94.236 +2.191.95.249 +2.191.96.234 +2.191.97.65 +2.191.97.146 +2.191.98.243 +2.191.99.83 +2.191.99.158 +2.191.101.155 +2.191.103.93 +2.191.103.121 +2.191.103.168 +2.191.104.41 +2.191.104.51 +2.191.105.150 +2.191.108.244 +2.191.109.116 +2.191.110.5 +2.191.110.185 +2.191.111.42 +2.191.111.204 +2.191.112.254 +2.191.113.74 +2.191.113.106 +2.191.113.197 +2.191.113.215 +2.191.115.150 +2.191.115.245 +2.191.116.96 +2.191.116.114 +2.191.117.57 +2.191.117.147 +2.191.118.61 +2.191.120.25 +2.191.120.243 +2.191.124.210 +2.191.126.20 +2.191.127.29 +2.191.127.41 +2.191.131.119 +2.191.142.150 +2.191.197.194 +2.191.202.171 +2.191.208.248 +5.10.248.43 +5.10.248.53 +5.10.248.150 +5.10.248.181 +5.10.249.153 +5.10.249.156 +5.10.249.164 +5.10.249.179 +5.10.249.217 +5.10.249.245 +5.22.193.5 +5.22.193.6 +5.22.196.29 +5.22.196.102 +5.22.196.154 +5.22.197.172 +5.22.198.138 +5.22.198.190 +5.22.199.193 +5.22.200.232 +5.22.200.233 +5.22.200.234 +5.22.200.235 +5.22.201.17 +5.22.201.162 +5.22.203.98 +5.22.203.100 +5.22.203.101 +5.22.203.102 +5.42.217.140 +5.42.217.182 +5.42.223.221 +5.56.132.51 +5.56.132.142 +5.56.135.7 +5.57.32.131 +5.57.32.166 +5.57.32.180 +5.57.32.235 +5.57.33.97 +5.57.33.251 +5.57.34.199 +5.57.35.225 +5.57.37.76 +5.57.37.228 +5.57.39.58 +5.61.24.42 +5.61.28.101 +5.61.28.235 +5.61.30.35 +5.61.30.226 +5.61.31.99 +5.62.164.171 +5.63.8.227 +5.63.9.37 +5.63.9.218 +5.63.13.51 +5.63.13.52 +5.63.13.54 +5.63.13.130 +5.74.1.217 +5.74.5.10 +5.74.10.236 +5.74.13.6 +5.74.22.215 +5.74.25.251 +5.74.28.146 +5.74.35.80 +5.74.38.17 +5.74.38.158 +5.74.40.120 +5.74.40.141 +5.74.42.203 +5.74.44.199 +5.74.44.204 +5.74.56.99 +5.74.56.162 +5.74.58.120 +5.74.66.14 +5.74.66.233 +5.74.68.20 +5.74.71.139 +5.74.75.152 +5.74.80.36 +5.74.80.101 +5.74.83.157 +5.74.87.197 +5.74.89.177 +5.74.94.17 +5.74.97.165 +5.74.98.28 +5.74.100.96 +5.74.101.87 +5.74.105.229 +5.74.106.250 +5.74.111.74 +5.74.112.154 +5.74.113.149 +5.74.121.103 +5.74.126.100 +5.74.128.161 +5.74.128.210 +5.74.128.236 +5.74.129.74 +5.74.129.92 +5.74.130.60 +5.74.130.226 +5.74.131.34 +5.74.131.194 +5.74.131.198 +5.74.132.140 +5.74.132.164 +5.74.133.175 +5.74.134.201 +5.74.137.11 +5.74.139.47 +5.74.139.130 +5.74.145.22 +5.74.145.67 +5.74.146.141 +5.74.146.179 +5.74.147.228 +5.74.151.195 +5.74.154.129 +5.74.155.111 +5.74.156.49 +5.74.156.251 +5.74.157.139 +5.74.157.195 +5.74.157.228 +5.74.159.121 +5.74.159.123 +5.74.159.129 +5.74.160.71 +5.74.160.147 +5.74.160.212 +5.74.161.128 +5.74.163.73 +5.74.163.130 +5.74.164.17 +5.74.165.51 +5.74.165.234 +5.74.168.18 +5.74.168.54 +5.74.168.95 +5.74.169.91 +5.74.170.174 +5.74.170.233 +5.74.173.188 +5.74.174.30 +5.74.175.141 +5.74.176.18 +5.74.177.22 +5.74.178.191 +5.74.179.16 +5.74.180.250 +5.74.181.153 +5.74.183.117 +5.74.183.146 +5.74.184.240 +5.74.188.93 +5.74.188.151 +5.74.189.1 +5.74.193.164 +5.74.195.235 +5.74.197.49 +5.74.199.35 +5.74.199.81 +5.74.199.252 +5.74.205.78 +5.74.205.212 +5.74.206.36 +5.74.207.79 +5.74.207.114 +5.74.208.64 +5.74.211.96 +5.74.211.172 +5.74.212.1 +5.74.212.5 +5.74.212.11 +5.74.212.234 +5.74.215.107 +5.74.218.2 +5.74.219.171 +5.74.220.57 +5.74.221.24 +5.74.221.80 +5.74.221.102 +5.74.221.140 +5.74.222.115 +5.74.224.18 +5.74.225.117 +5.74.225.239 +5.74.227.193 +5.74.229.131 +5.74.231.56 +5.74.233.92 +5.74.233.254 +5.74.234.101 +5.74.234.181 +5.74.234.223 +5.74.236.142 +5.74.236.167 +5.74.237.240 +5.74.237.247 +5.74.239.195 +5.74.240.11 +5.74.242.17 +5.74.242.107 +5.74.245.153 +5.74.246.116 +5.74.246.192 +5.74.249.45 +5.74.249.231 +5.74.252.166 +5.74.255.153 +5.75.41.226 +5.106.4.130 +5.106.5.129 +5.106.5.130 +5.106.16.59 +5.106.16.163 +5.106.16.248 +5.106.17.59 +5.106.17.60 +5.106.17.61 +5.106.17.62 +5.106.17.64 +5.106.17.65 +5.106.17.66 +5.106.17.67 +5.106.17.77 +5.106.17.128 +5.106.17.129 +5.106.17.134 +5.106.17.135 +5.106.17.147 +5.106.17.161 +5.106.17.163 +5.106.17.172 +5.106.18.59 +5.106.18.62 +5.106.18.63 +5.106.18.90 +5.106.18.170 +5.106.18.176 +5.106.18.192 +5.106.18.218 +5.106.18.221 +5.106.18.235 +5.106.18.241 +5.106.18.252 +5.106.19.13 +5.106.19.45 +5.106.19.47 +5.106.19.48 +5.106.19.49 +5.106.19.50 +5.106.19.51 +5.106.19.53 +5.106.19.54 +5.106.19.56 +5.106.19.58 +5.106.19.60 +5.106.19.79 +5.106.19.89 +5.106.19.93 +5.106.19.95 +5.106.19.116 +5.106.19.126 +5.106.19.179 +5.106.19.188 +5.106.19.193 +5.106.19.195 +5.106.19.205 +5.106.21.62 +5.106.21.84 +5.106.21.103 +5.106.21.126 +5.106.21.154 +5.106.28.32 +5.106.28.115 +5.106.28.172 +5.106.28.198 +5.106.28.203 +5.106.29.212 +5.134.195.80 +5.134.195.162 +5.134.195.197 +5.134.196.195 +5.144.129.83 +5.144.129.177 +5.144.129.236 +5.144.130.94 +5.144.132.7 +5.144.132.98 +5.145.112.53 +5.145.112.57 +5.145.112.225 +5.145.113.1 +5.145.113.58 +5.145.113.60 +5.145.114.73 +5.145.114.83 +5.145.114.85 +5.145.114.98 +5.145.114.99 +5.145.114.112 +5.145.114.113 +5.145.114.115 +5.145.114.211 +5.145.114.222 +5.145.114.225 +5.145.114.234 +5.145.114.236 +5.145.114.239 +5.145.114.243 +5.145.114.249 +5.145.115.17 +5.145.115.170 +5.145.115.213 +5.145.115.217 +5.145.115.225 +5.145.115.232 +5.159.49.107 +5.159.49.207 +5.159.50.49 +5.159.50.50 +5.159.50.53 +5.159.50.55 +5.159.50.57 +5.159.50.58 +5.159.50.61 +5.159.50.194 +5.159.50.197 +5.159.50.198 +5.159.50.200 +5.159.50.201 +5.159.51.5 +5.159.52.25 +5.159.52.26 +5.159.52.27 +5.159.52.28 +5.159.52.29 +5.159.52.73 +5.159.52.76 +5.159.53.56 +5.159.53.81 +5.159.55.50 +5.159.55.53 +5.159.55.62 +5.159.55.105 +5.159.55.113 +5.159.55.121 +5.159.55.129 +5.159.55.150 +5.159.55.153 +5.159.55.181 +5.159.55.182 +5.159.55.249 +5.160.0.125 +5.160.0.127 +5.160.0.195 +5.160.0.222 +5.160.1.26 +5.160.1.42 +5.160.1.43 +5.160.1.44 +5.160.1.90 +5.160.2.38 +5.160.2.226 +5.160.2.227 +5.160.2.228 +5.160.2.229 +5.160.3.34 +5.160.3.104 +5.160.3.116 +5.160.3.174 +5.160.3.226 +5.160.4.147 +5.160.5.99 +5.160.5.100 +5.160.5.101 +5.160.5.102 +5.160.5.162 +5.160.7.214 +5.160.12.244 +5.160.12.247 +5.160.12.251 +5.160.14.203 +5.160.14.204 +5.160.14.205 +5.160.14.206 +5.160.15.10 +5.160.20.130 +5.160.23.98 +5.160.23.102 +5.160.25.180 +5.160.25.206 +5.160.26.30 +5.160.26.74 +5.160.26.75 +5.160.26.77 +5.160.27.150 +5.160.28.236 +5.160.28.238 +5.160.29.4 +5.160.29.44 +5.160.32.190 +5.160.33.2 +5.160.33.254 +5.160.36.48 +5.160.36.60 +5.160.37.8 +5.160.37.46 +5.160.37.147 +5.160.37.192 +5.160.37.201 +5.160.38.46 +5.160.38.78 +5.160.38.189 +5.160.39.98 +5.160.39.250 +5.160.40.4 +5.160.40.39 +5.160.40.44 +5.160.40.56 +5.160.40.62 +5.160.40.71 +5.160.40.154 +5.160.40.173 +5.160.40.250 +5.160.42.34 +5.160.42.46 +5.160.42.202 +5.160.43.18 +5.160.43.62 +5.160.43.82 +5.160.43.110 +5.160.45.138 +5.160.45.220 +5.160.47.67 +5.160.47.82 +5.160.47.119 +5.160.48.53 +5.160.49.14 +5.160.49.26 +5.160.49.74 +5.160.49.75 +5.160.49.76 +5.160.49.77 +5.160.49.78 +5.160.49.98 +5.160.49.100 +5.160.49.101 +5.160.49.104 +5.160.49.108 +5.160.49.109 +5.160.49.226 +5.160.50.6 +5.160.50.130 +5.160.51.241 +5.160.52.90 +5.160.52.91 +5.160.52.92 +5.160.52.93 +5.160.52.94 +5.160.52.234 +5.160.53.94 +5.160.54.26 +5.160.54.27 +5.160.54.28 +5.160.54.29 +5.160.54.30 +5.160.54.118 +5.160.56.2 +5.160.56.30 +5.160.56.50 +5.160.56.118 +5.160.56.174 +5.160.56.228 +5.160.56.249 +5.160.57.34 +5.160.57.35 +5.160.58.129 +5.160.59.18 +5.160.59.19 +5.160.59.20 +5.160.59.29 +5.160.60.6 +5.160.60.43 +5.160.60.166 +5.160.61.2 +5.160.61.161 +5.160.61.162 +5.160.61.163 +5.160.61.164 +5.160.61.166 +5.160.61.169 +5.160.61.170 +5.160.61.171 +5.160.61.172 +5.160.61.173 +5.160.61.174 +5.160.61.251 +5.160.61.254 +5.160.62.88 +5.160.62.89 +5.160.62.91 +5.160.62.160 +5.160.62.164 +5.160.63.34 +5.160.63.38 +5.160.63.126 +5.160.65.18 +5.160.65.19 +5.160.65.20 +5.160.65.21 +5.160.65.22 +5.160.65.25 +5.160.65.26 +5.160.65.27 +5.160.65.28 +5.160.65.29 +5.160.65.30 +5.160.65.190 +5.160.67.98 +5.160.67.102 +5.160.68.1 +5.160.68.50 +5.160.68.226 +5.160.68.229 +5.160.68.230 +5.160.68.231 +5.160.68.232 +5.160.68.233 +5.160.68.234 +5.160.68.235 +5.160.68.236 +5.160.68.238 +5.160.72.22 +5.160.72.53 +5.160.72.134 +5.160.72.222 +5.160.73.88 +5.160.74.147 +5.160.74.226 +5.160.74.227 +5.160.74.228 +5.160.74.229 +5.160.74.230 +5.160.74.232 +5.160.74.233 +5.160.74.254 +5.160.75.10 +5.160.75.90 +5.160.75.207 +5.160.76.106 +5.160.76.170 +5.160.76.171 +5.160.76.198 +5.160.76.226 +5.160.78.18 +5.160.78.86 +5.160.78.104 +5.160.78.134 +5.160.78.154 +5.160.78.238 +5.160.80.33 +5.160.80.98 +5.160.80.193 +5.160.80.194 +5.160.80.210 +5.160.80.211 +5.160.80.214 +5.160.81.177 +5.160.81.187 +5.160.81.251 +5.160.82.110 +5.160.83.18 +5.160.83.66 +5.160.83.117 +5.160.83.122 +5.160.84.34 +5.160.85.195 +5.160.85.234 +5.160.86.34 +5.160.87.51 +5.160.87.106 +5.160.88.34 +5.160.88.35 +5.160.88.36 +5.160.88.37 +5.160.88.38 +5.160.88.39 +5.160.88.40 +5.160.88.41 +5.160.88.42 +5.160.88.43 +5.160.88.44 +5.160.88.45 +5.160.88.46 +5.160.89.99 +5.160.89.194 +5.160.90.61 +5.160.90.86 +5.160.90.218 +5.160.91.180 +5.160.91.190 +5.160.93.210 +5.160.93.246 +5.160.95.194 +5.160.98.131 +5.160.99.4 +5.160.99.5 +5.160.99.6 +5.160.99.8 +5.160.99.9 +5.160.99.10 +5.160.99.11 +5.160.99.12 +5.160.99.13 +5.160.99.14 +5.160.99.15 +5.160.99.19 +5.160.99.20 +5.160.99.227 +5.160.99.229 +5.160.99.230 +5.160.99.231 +5.160.99.232 +5.160.99.235 +5.160.99.237 +5.160.100.8 +5.160.100.114 +5.160.100.118 +5.160.101.2 +5.160.101.244 +5.160.101.245 +5.160.101.246 +5.160.101.247 +5.160.101.249 +5.160.101.250 +5.160.101.251 +5.160.101.252 +5.160.101.253 +5.160.102.201 +5.160.103.42 +5.160.103.43 +5.160.103.46 +5.160.103.67 +5.160.103.114 +5.160.103.162 +5.160.103.185 +5.160.108.74 +5.160.108.210 +5.160.108.222 +5.160.109.186 +5.160.112.46 +5.160.112.145 +5.160.114.126 +5.160.114.132 +5.160.114.134 +5.160.115.42 +5.160.115.50 +5.160.115.51 +5.160.115.52 +5.160.115.53 +5.160.115.54 +5.160.115.130 +5.160.115.134 +5.160.115.138 +5.160.115.190 +5.160.115.210 +5.160.115.238 +5.160.118.86 +5.160.118.93 +5.160.118.110 +5.160.118.178 +5.160.118.186 +5.160.119.130 +5.160.119.210 +5.160.119.225 +5.160.119.228 +5.160.119.242 +5.160.119.250 +5.160.120.14 +5.160.121.6 +5.160.122.150 +5.160.122.226 +5.160.123.138 +5.160.124.155 +5.160.125.38 +5.160.125.46 +5.160.125.138 +5.160.125.186 +5.160.125.234 +5.160.126.139 +5.160.127.67 +5.160.127.68 +5.160.127.69 +5.160.127.70 +5.160.127.90 +5.160.129.118 +5.160.134.82 +5.160.134.83 +5.160.134.84 +5.160.134.85 +5.160.134.86 +5.160.135.46 +5.160.136.122 +5.160.136.162 +5.160.137.54 +5.160.138.227 +5.160.143.225 +5.160.144.9 +5.160.144.15 +5.160.144.19 +5.160.146.199 +5.160.146.233 +5.160.147.18 +5.160.147.226 +5.160.148.49 +5.160.150.138 +5.160.150.139 +5.160.150.140 +5.160.151.204 +5.160.151.206 +5.160.152.166 +5.160.157.75 +5.160.157.100 +5.160.159.84 +5.160.159.178 +5.160.159.181 +5.160.159.189 +5.160.160.42 +5.160.161.5 +5.160.161.18 +5.160.161.19 +5.160.161.33 +5.160.161.41 +5.160.161.45 +5.160.161.62 +5.160.161.66 +5.160.161.73 +5.160.161.84 +5.160.161.98 +5.160.161.99 +5.160.161.101 +5.160.161.117 +5.160.161.121 +5.160.161.122 +5.160.161.123 +5.160.161.127 +5.160.161.129 +5.160.161.142 +5.160.161.148 +5.160.161.152 +5.160.161.168 +5.160.161.176 +5.160.161.179 +5.160.161.180 +5.160.161.187 +5.160.161.197 +5.160.161.200 +5.160.161.219 +5.160.162.43 +5.160.162.44 +5.160.164.86 +5.160.164.93 +5.160.164.211 +5.160.166.20 +5.160.167.14 +5.160.167.60 +5.160.168.10 +5.160.168.85 +5.160.168.130 +5.160.170.2 +5.160.171.36 +5.160.171.47 +5.160.173.151 +5.160.173.190 +5.160.175.250 +5.160.176.27 +5.160.176.165 +5.160.176.208 +5.160.176.222 +5.160.176.242 +5.160.176.243 +5.160.176.245 +5.160.177.6 +5.160.178.50 +5.160.178.155 +5.160.178.157 +5.160.182.45 +5.160.182.65 +5.160.182.73 +5.160.182.77 +5.160.182.85 +5.160.182.88 +5.160.182.90 +5.160.182.101 +5.160.182.111 +5.160.182.119 +5.160.182.127 +5.160.182.130 +5.160.182.146 +5.160.182.155 +5.160.182.168 +5.160.182.180 +5.160.182.184 +5.160.182.197 +5.160.182.201 +5.160.182.208 +5.160.182.211 +5.160.182.219 +5.160.182.228 +5.160.182.229 +5.160.182.236 +5.160.182.245 +5.160.183.150 +5.160.184.90 +5.160.184.202 +5.160.185.86 +5.160.185.122 +5.160.185.222 +5.160.186.14 +5.160.186.42 +5.160.186.43 +5.160.186.44 +5.160.186.45 +5.160.187.44 +5.160.187.78 +5.160.187.98 +5.160.187.122 +5.160.187.126 +5.160.187.247 +5.160.187.254 +5.160.190.34 +5.160.190.38 +5.160.191.197 +5.160.191.242 +5.160.191.243 +5.160.196.126 +5.160.196.210 +5.160.197.207 +5.160.200.182 +5.160.211.170 +5.160.211.218 +5.160.211.219 +5.160.211.220 +5.160.214.110 +5.160.215.230 +5.160.217.254 +5.160.218.167 +5.160.219.72 +5.160.223.254 +5.160.224.225 +5.160.224.226 +5.160.224.227 +5.160.224.228 +5.160.224.229 +5.160.225.66 +5.160.225.116 +5.160.225.163 +5.160.225.174 +5.160.227.238 +5.160.228.190 +5.160.229.187 +5.160.229.234 +5.160.230.130 +5.160.230.250 +5.160.231.10 +5.160.231.130 +5.160.231.214 +5.160.233.16 +5.160.233.150 +5.160.233.226 +5.160.234.189 +5.160.235.130 +5.160.236.155 +5.160.236.158 +5.160.236.216 +5.160.237.118 +5.160.237.208 +5.160.239.98 +5.160.239.226 +5.160.239.250 +5.160.239.251 +5.160.242.48 +5.160.243.107 +5.160.243.238 +5.160.243.254 +5.160.246.29 +5.160.247.50 +5.160.247.230 +5.182.44.18 +5.182.44.124 +5.182.44.132 +5.182.44.134 +5.182.44.161 +5.182.44.184 +5.182.44.190 +5.182.46.185 +5.190.0.68 +5.190.0.178 +5.190.1.71 +5.190.1.79 +5.190.1.145 +5.190.1.161 +5.190.3.109 +5.190.3.110 +5.190.3.113 +5.190.4.17 +5.190.4.249 +5.190.6.41 +5.190.6.73 +5.190.6.241 +5.190.7.113 +5.190.20.1 +5.190.29.161 +5.190.45.70 +5.190.52.225 +5.190.53.6 +5.190.60.161 +5.190.69.68 +5.190.69.70 +5.190.69.73 +5.190.69.74 +5.190.69.75 +5.190.69.80 +5.190.69.89 +5.190.72.113 +5.190.83.70 +5.190.83.77 +5.190.84.112 +5.190.89.101 +5.190.93.133 +5.190.103.130 +5.190.106.210 +5.190.131.1 +5.190.131.230 +5.190.135.173 +5.190.138.125 +5.190.143.81 +5.190.147.1 +5.190.147.3 +5.190.154.164 +5.190.156.3 +5.190.156.4 +5.190.212.241 +5.190.212.242 +5.190.212.243 +5.190.212.245 +5.190.212.246 +5.190.212.247 +5.190.212.251 +5.190.212.252 +5.190.224.15 +5.190.224.23 +5.190.224.27 +5.190.224.37 +5.190.224.39 +5.190.235.10 +5.190.235.35 +5.190.235.74 +5.190.235.81 +5.190.235.117 +5.190.235.131 +5.190.235.160 +5.190.235.163 +5.190.235.168 +5.190.235.171 +5.190.235.183 +5.190.235.196 +5.190.235.209 +5.190.235.214 +5.190.235.247 +5.190.235.249 +5.190.235.254 +5.198.161.12 +5.198.178.64 +5.198.178.80 +5.198.178.160 +5.198.178.180 +5.198.178.237 +5.198.179.20 +5.198.179.50 +5.198.179.130 +5.198.179.190 +5.200.64.193 +5.200.67.24 +5.200.69.30 +5.200.73.82 +5.200.73.83 +5.200.73.84 +5.200.73.162 +5.200.74.66 +5.200.74.90 +5.200.74.91 +5.200.74.92 +5.200.79.14 +5.200.84.129 +5.200.94.45 +5.200.151.10 +5.200.160.21 +5.200.161.223 +5.200.165.21 +5.200.177.233 +5.200.194.25 +5.200.200.26 +5.200.217.213 +5.200.240.10 +5.200.246.248 +5.201.128.76 +5.201.129.44 +5.201.130.70 +5.201.130.106 +5.201.131.60 +5.201.132.27 +5.201.132.139 +5.201.132.143 +5.201.132.201 +5.201.132.246 +5.201.133.78 +5.201.133.162 +5.201.134.23 +5.201.134.222 +5.201.135.104 +5.201.135.127 +5.201.135.162 +5.201.136.54 +5.201.137.187 +5.201.138.9 +5.201.139.194 +5.201.141.202 +5.201.141.246 +5.201.142.74 +5.201.142.205 +5.201.142.220 +5.201.143.155 +5.201.144.80 +5.201.148.92 +5.201.150.103 +5.201.151.139 +5.201.152.225 +5.201.154.29 +5.201.154.149 +5.201.156.81 +5.201.159.173 +5.201.161.182 +5.201.161.210 +5.201.161.211 +5.201.161.212 +5.201.161.213 +5.201.162.150 +5.201.164.167 +5.201.166.105 +5.201.166.132 +5.201.166.143 +5.201.166.182 +5.201.167.42 +5.201.167.223 +5.201.168.123 +5.201.169.48 +5.201.169.124 +5.201.169.191 +5.201.170.24 +5.201.171.57 +5.201.175.251 +5.201.176.244 +5.201.177.48 +5.201.177.68 +5.201.177.93 +5.201.178.6 +5.201.178.10 +5.201.180.250 +5.201.181.247 +5.201.183.202 +5.201.184.237 +5.201.185.40 +5.201.185.163 +5.201.187.150 +5.201.187.226 +5.201.190.57 +5.201.190.239 +5.201.191.11 +5.201.191.114 +5.201.191.255 +5.202.5.114 +5.202.6.77 +5.202.6.129 +5.202.6.194 +5.202.6.196 +5.202.6.197 +5.202.6.204 +5.202.6.206 +5.202.7.19 +5.202.7.226 +5.202.7.230 +5.202.7.232 +5.202.7.234 +5.202.7.238 +5.202.8.43 +5.202.8.186 +5.202.8.187 +5.202.10.40 +5.202.15.213 +5.202.15.218 +5.202.15.219 +5.202.15.222 +5.202.18.113 +5.202.18.134 +5.202.18.169 +5.202.18.208 +5.202.18.210 +5.202.18.211 +5.202.18.212 +5.202.18.213 +5.202.18.216 +5.202.18.217 +5.202.18.218 +5.202.18.219 +5.202.18.220 +5.202.18.221 +5.202.18.222 +5.202.18.223 +5.202.20.11 +5.202.20.17 +5.202.20.44 +5.202.20.53 +5.202.20.54 +5.202.20.60 +5.202.20.79 +5.202.20.81 +5.202.20.149 +5.202.20.157 +5.202.20.161 +5.202.20.173 +5.202.20.178 +5.202.20.184 +5.202.20.196 +5.202.20.197 +5.202.20.207 +5.202.20.242 +5.202.21.11 +5.202.21.45 +5.202.21.57 +5.202.21.91 +5.202.21.136 +5.202.21.137 +5.202.21.245 +5.202.27.14 +5.202.27.142 +5.202.29.35 +5.202.29.196 +5.202.31.64 +5.202.31.65 +5.202.33.2 +5.202.33.9 +5.202.33.11 +5.202.33.32 +5.202.33.50 +5.202.33.68 +5.202.33.86 +5.202.33.123 +5.202.33.126 +5.202.33.151 +5.202.33.165 +5.202.33.179 +5.202.33.216 +5.202.33.237 +5.202.39.129 +5.202.44.20 +5.202.44.103 +5.202.44.106 +5.202.44.112 +5.202.44.162 +5.202.44.181 +5.202.44.189 +5.202.44.192 +5.202.44.196 +5.202.44.208 +5.202.44.222 +5.202.44.229 +5.202.45.20 +5.202.45.41 +5.202.45.53 +5.202.45.67 +5.202.45.92 +5.202.45.110 +5.202.45.130 +5.202.45.185 +5.202.45.201 +5.202.45.233 +5.202.46.0 +5.202.46.13 +5.202.46.38 +5.202.46.69 +5.202.46.74 +5.202.46.98 +5.202.46.109 +5.202.46.124 +5.202.46.162 +5.202.46.182 +5.202.46.191 +5.202.46.210 +5.202.46.221 +5.202.47.0 +5.202.47.5 +5.202.47.29 +5.202.47.64 +5.202.47.71 +5.202.47.107 +5.202.47.108 +5.202.47.141 +5.202.47.215 +5.202.47.253 +5.202.47.255 +5.202.50.73 +5.202.50.113 +5.202.50.114 +5.202.50.115 +5.202.50.116 +5.202.50.118 +5.202.51.81 +5.202.51.82 +5.202.51.83 +5.202.51.84 +5.202.52.6 +5.202.52.81 +5.202.52.138 +5.202.52.155 +5.202.53.100 +5.202.53.158 +5.202.53.161 +5.202.53.174 +5.202.53.186 +5.202.60.14 +5.202.60.202 +5.202.60.242 +5.202.61.160 +5.202.61.192 +5.202.63.40 +5.202.63.58 +5.202.63.60 +5.202.63.62 +5.202.63.125 +5.202.63.210 +5.202.66.2 +5.202.66.9 +5.202.67.1 +5.202.67.134 +5.202.68.10 +5.202.68.13 +5.202.68.14 +5.202.68.18 +5.202.68.21 +5.202.68.23 +5.202.68.34 +5.202.68.38 +5.202.68.65 +5.202.68.90 +5.202.68.116 +5.202.68.150 +5.202.73.50 +5.202.73.134 +5.202.77.46 +5.202.77.53 +5.202.78.8 +5.202.78.18 +5.202.78.23 +5.202.78.35 +5.202.79.163 +5.202.79.164 +5.202.79.194 +5.202.79.203 +5.202.79.210 +5.202.79.213 +5.202.79.214 +5.202.79.215 +5.202.79.217 +5.202.79.220 +5.202.79.222 +5.202.79.225 +5.202.79.227 +5.202.79.228 +5.202.79.229 +5.202.82.102 +5.202.82.118 +5.202.82.146 +5.202.82.190 +5.202.83.5 +5.202.83.59 +5.202.83.102 +5.202.83.118 +5.202.84.9 +5.202.84.21 +5.202.84.47 +5.202.84.63 +5.202.84.68 +5.202.84.85 +5.202.84.101 +5.202.84.150 +5.202.84.152 +5.202.84.161 +5.202.84.178 +5.202.84.207 +5.202.84.219 +5.202.84.247 +5.202.85.43 +5.202.85.66 +5.202.85.154 +5.202.85.186 +5.202.85.255 +5.202.86.26 +5.202.89.41 +5.202.89.89 +5.202.89.90 +5.202.90.40 +5.202.90.51 +5.202.90.56 +5.202.90.79 +5.202.90.93 +5.202.90.99 +5.202.90.103 +5.202.90.157 +5.202.90.170 +5.202.90.195 +5.202.90.196 +5.202.90.204 +5.202.90.214 +5.202.90.221 +5.202.93.144 +5.202.93.207 +5.202.96.3 +5.202.96.9 +5.202.96.10 +5.202.96.11 +5.202.96.31 +5.202.96.32 +5.202.96.37 +5.202.96.38 +5.202.96.49 +5.202.96.50 +5.202.96.51 +5.202.96.57 +5.202.96.66 +5.202.96.78 +5.202.96.83 +5.202.96.87 +5.202.96.92 +5.202.96.101 +5.202.96.109 +5.202.96.111 +5.202.96.118 +5.202.96.121 +5.202.96.126 +5.202.96.131 +5.202.96.134 +5.202.96.140 +5.202.96.146 +5.202.96.147 +5.202.96.160 +5.202.96.164 +5.202.96.171 +5.202.96.175 +5.202.96.177 +5.202.97.19 +5.202.97.20 +5.202.97.23 +5.202.97.24 +5.202.97.46 +5.202.97.49 +5.202.97.51 +5.202.97.59 +5.202.97.74 +5.202.97.79 +5.202.97.81 +5.202.97.93 +5.202.97.94 +5.202.97.100 +5.202.97.103 +5.202.97.107 +5.202.97.124 +5.202.97.126 +5.202.97.128 +5.202.101.89 +5.202.104.12 +5.202.104.14 +5.202.104.34 +5.202.104.62 +5.202.104.158 +5.202.104.166 +5.202.104.220 +5.202.105.9 +5.202.105.24 +5.202.105.31 +5.202.105.104 +5.202.105.111 +5.202.105.119 +5.202.105.120 +5.202.105.154 +5.202.105.208 +5.202.105.229 +5.202.105.240 +5.202.105.253 +5.202.112.10 +5.202.112.118 +5.202.112.154 +5.202.112.191 +5.202.112.197 +5.202.114.92 +5.202.120.35 +5.202.120.43 +5.202.120.82 +5.202.120.96 +5.202.120.97 +5.202.120.114 +5.202.120.123 +5.202.120.126 +5.202.120.163 +5.202.120.164 +5.202.120.169 +5.202.121.5 +5.202.121.6 +5.202.121.26 +5.202.121.154 +5.202.121.164 +5.202.121.222 +5.202.128.225 +5.202.130.2 +5.202.130.7 +5.202.130.41 +5.202.130.89 +5.202.130.110 +5.202.130.112 +5.202.130.179 +5.202.130.188 +5.202.130.195 +5.202.130.208 +5.202.130.213 +5.202.130.223 +5.202.130.224 +5.202.130.237 +5.202.130.239 +5.202.130.244 +5.202.130.249 +5.202.131.12 +5.202.131.21 +5.202.131.37 +5.202.131.38 +5.202.131.59 +5.202.131.68 +5.202.131.95 +5.202.131.151 +5.202.131.152 +5.202.131.160 +5.202.131.161 +5.202.131.162 +5.202.131.170 +5.202.131.190 +5.202.131.199 +5.202.131.203 +5.202.131.209 +5.202.131.212 +5.202.131.224 +5.202.132.5 +5.202.132.9 +5.202.132.36 +5.202.132.43 +5.202.132.55 +5.202.132.77 +5.202.132.83 +5.202.132.97 +5.202.132.119 +5.202.132.123 +5.202.132.124 +5.202.132.128 +5.202.132.133 +5.202.132.150 +5.202.132.189 +5.202.132.193 +5.202.132.197 +5.202.132.200 +5.202.132.204 +5.202.132.217 +5.202.132.228 +5.202.132.231 +5.202.132.232 +5.202.132.233 +5.202.132.252 +5.202.132.254 +5.202.133.0 +5.202.133.3 +5.202.133.40 +5.202.133.50 +5.202.133.56 +5.202.133.60 +5.202.133.69 +5.202.133.84 +5.202.133.110 +5.202.133.135 +5.202.133.139 +5.202.133.144 +5.202.133.146 +5.202.133.160 +5.202.133.163 +5.202.133.166 +5.202.133.168 +5.202.133.231 +5.202.133.244 +5.202.134.9 +5.202.134.38 +5.202.134.74 +5.202.134.99 +5.202.134.117 +5.202.134.131 +5.202.134.133 +5.202.134.166 +5.202.134.180 +5.202.134.196 +5.202.134.252 +5.202.135.8 +5.202.135.14 +5.202.135.17 +5.202.135.23 +5.202.135.65 +5.202.135.93 +5.202.135.96 +5.202.135.119 +5.202.135.132 +5.202.135.134 +5.202.135.140 +5.202.135.155 +5.202.135.162 +5.202.135.165 +5.202.135.201 +5.202.135.221 +5.202.135.226 +5.202.135.236 +5.202.135.243 +5.202.135.254 +5.202.136.0 +5.202.136.3 +5.202.140.48 +5.202.140.49 +5.202.140.50 +5.202.140.51 +5.202.140.52 +5.202.140.53 +5.202.140.54 +5.202.140.55 +5.202.140.65 +5.202.140.66 +5.202.140.67 +5.202.140.68 +5.202.140.69 +5.202.140.70 +5.202.140.80 +5.202.140.82 +5.202.140.83 +5.202.140.84 +5.202.140.85 +5.202.140.86 +5.202.140.87 +5.202.140.88 +5.202.140.89 +5.202.140.90 +5.202.140.91 +5.202.140.93 +5.202.140.94 +5.202.162.67 +5.202.162.75 +5.202.162.76 +5.202.163.9 +5.202.163.13 +5.202.163.80 +5.202.163.158 +5.202.163.183 +5.202.164.118 +5.202.165.112 +5.202.166.250 +5.202.166.254 +5.202.167.1 +5.202.167.30 +5.202.167.108 +5.202.167.222 +5.202.167.252 +5.202.168.0 +5.202.168.18 +5.202.168.53 +5.202.168.71 +5.202.168.93 +5.202.168.117 +5.202.168.133 +5.202.168.140 +5.202.168.149 +5.202.168.166 +5.202.168.173 +5.202.168.178 +5.202.168.180 +5.202.168.196 +5.202.168.207 +5.202.168.211 +5.202.169.2 +5.202.169.29 +5.202.169.33 +5.202.169.38 +5.202.169.80 +5.202.169.119 +5.202.169.142 +5.202.169.145 +5.202.169.155 +5.202.169.161 +5.202.169.162 +5.202.169.191 +5.202.169.192 +5.202.169.198 +5.202.170.33 +5.202.170.63 +5.202.170.100 +5.202.170.110 +5.202.170.111 +5.202.170.145 +5.202.170.153 +5.202.170.163 +5.202.170.178 +5.202.170.181 +5.202.170.187 +5.202.170.214 +5.202.170.219 +5.202.170.242 +5.202.171.35 +5.202.171.45 +5.202.171.62 +5.202.171.68 +5.202.171.82 +5.202.171.102 +5.202.171.105 +5.202.171.108 +5.202.171.120 +5.202.171.124 +5.202.171.128 +5.202.171.229 +5.202.172.3 +5.202.172.14 +5.202.172.27 +5.202.172.36 +5.202.172.56 +5.202.172.65 +5.202.172.67 +5.202.172.70 +5.202.172.134 +5.202.172.157 +5.202.172.176 +5.202.172.189 +5.202.172.200 +5.202.172.225 +5.202.172.233 +5.202.172.237 +5.202.172.239 +5.202.172.254 +5.202.174.27 +5.202.174.28 +5.202.174.33 +5.202.174.56 +5.202.174.73 +5.202.174.90 +5.202.174.170 +5.202.174.183 +5.202.174.225 +5.202.174.229 +5.202.174.233 +5.202.174.245 +5.202.175.1 +5.202.175.46 +5.202.175.52 +5.202.175.121 +5.202.175.143 +5.202.175.148 +5.202.175.165 +5.202.175.169 +5.202.175.179 +5.202.175.193 +5.202.175.207 +5.202.175.227 +5.202.175.242 +5.202.176.154 +5.202.176.166 +5.202.176.170 +5.202.177.45 +5.202.177.50 +5.202.177.51 +5.202.177.52 +5.202.177.53 +5.202.177.54 +5.202.177.114 +5.202.177.115 +5.202.177.116 +5.202.177.118 +5.202.177.122 +5.202.177.166 +5.202.177.194 +5.202.177.195 +5.202.177.196 +5.202.177.197 +5.202.177.198 +5.202.177.254 +5.202.178.67 +5.202.178.86 +5.202.179.2 +5.202.179.3 +5.202.179.4 +5.202.179.5 +5.202.179.6 +5.202.179.7 +5.202.179.8 +5.202.179.9 +5.202.179.10 +5.202.179.12 +5.202.179.13 +5.202.179.14 +5.202.179.50 +5.202.179.138 +5.202.179.170 +5.202.179.171 +5.202.179.172 +5.202.179.173 +5.202.179.174 +5.202.180.46 +5.202.182.2 +5.202.182.102 +5.202.182.210 +5.202.182.222 +5.202.184.90 +5.202.184.242 +5.202.185.38 +5.202.185.206 +5.202.186.70 +5.202.186.229 +5.202.188.2 +5.202.188.211 +5.202.188.212 +5.202.188.213 +5.202.189.18 +5.202.189.242 +5.202.190.14 +5.202.190.18 +5.202.190.19 +5.202.190.20 +5.202.190.217 +5.202.191.42 +5.202.191.78 +5.202.191.126 +5.202.191.186 +5.202.191.225 +5.202.191.245 +5.202.193.14 +5.202.193.29 +5.202.193.61 +5.202.193.62 +5.202.193.77 +5.202.193.85 +5.202.193.102 +5.202.193.115 +5.202.193.134 +5.202.193.147 +5.202.193.152 +5.202.193.210 +5.202.193.225 +5.202.193.229 +5.202.194.249 +5.202.195.4 +5.202.195.14 +5.202.195.16 +5.202.195.83 +5.202.195.97 +5.202.195.133 +5.202.195.143 +5.202.195.147 +5.202.195.162 +5.202.195.165 +5.202.195.205 +5.202.195.217 +5.202.196.7 +5.202.196.14 +5.202.196.16 +5.202.196.35 +5.202.196.54 +5.202.196.62 +5.202.196.75 +5.202.196.76 +5.202.196.81 +5.202.196.103 +5.202.196.105 +5.202.196.184 +5.202.196.198 +5.202.196.206 +5.202.196.246 +5.202.196.253 +5.202.197.2 +5.202.197.3 +5.202.197.9 +5.202.197.27 +5.202.197.60 +5.202.197.61 +5.202.197.102 +5.202.197.103 +5.202.197.111 +5.202.197.201 +5.202.197.217 +5.202.197.224 +5.202.197.227 +5.202.197.241 +5.202.198.11 +5.202.198.23 +5.202.198.27 +5.202.198.69 +5.202.198.79 +5.202.198.85 +5.202.198.102 +5.202.198.107 +5.202.198.116 +5.202.198.124 +5.202.198.136 +5.202.206.115 +5.202.236.68 +5.202.239.59 +5.202.240.32 +5.202.240.34 +5.202.240.35 +5.202.240.38 +5.202.240.39 +5.202.240.42 +5.202.240.45 +5.202.241.54 +5.202.243.0 +5.202.243.4 +5.202.243.5 +5.202.243.106 +5.202.243.134 +5.202.243.142 +5.202.243.147 +5.202.243.157 +5.202.243.165 +5.202.243.191 +5.202.243.226 +5.202.244.4 +5.202.244.38 +5.202.244.44 +5.202.244.98 +5.202.244.127 +5.202.244.129 +5.202.244.134 +5.202.244.181 +5.202.244.184 +5.202.244.211 +5.202.245.11 +5.202.245.38 +5.202.245.42 +5.202.248.20 +5.202.248.36 +5.202.248.47 +5.202.248.48 +5.202.248.58 +5.202.248.68 +5.202.248.70 +5.202.248.74 +5.202.248.78 +5.202.248.93 +5.202.248.106 +5.202.248.107 +5.202.248.111 +5.202.248.148 +5.202.248.190 +5.202.248.194 +5.202.248.196 +5.202.248.200 +5.202.248.219 +5.202.249.21 +5.202.249.169 +5.202.249.170 +5.202.249.178 +5.202.250.10 +5.202.250.81 +5.202.250.153 +5.202.250.155 +5.202.250.156 +5.202.250.157 +5.202.251.32 +5.202.251.46 +5.202.251.57 +5.202.251.80 +5.202.251.94 +5.202.251.116 +5.202.251.152 +5.202.251.162 +5.202.251.212 +5.202.251.235 +5.202.251.237 +5.202.251.243 +5.202.251.254 +5.202.252.27 +5.202.252.28 +5.202.252.29 +5.202.252.126 +5.202.252.250 +5.202.252.252 +5.202.252.253 +5.202.253.3 +5.202.253.6 +5.202.253.10 +5.202.253.11 +5.202.253.13 +5.202.253.14 +5.202.253.46 +5.202.253.52 +5.202.253.53 +5.202.253.54 +5.202.253.122 +5.202.253.146 +5.202.254.10 +5.202.254.18 +5.202.254.29 +5.202.254.154 +5.202.254.174 +5.202.254.242 +5.202.255.107 +5.202.255.108 +5.219.64.1 +5.219.128.1 +5.232.97.110 +5.232.103.33 +5.232.104.21 +5.232.116.189 +5.232.119.40 +5.232.119.66 +5.232.124.227 +5.232.126.173 +5.232.128.72 +5.232.128.198 +5.232.132.201 +5.232.140.84 +5.232.140.119 +5.232.141.89 +5.232.144.160 +5.232.149.174 +5.232.155.22 +5.232.157.90 +5.232.176.234 +5.232.177.130 +5.232.177.148 +5.232.178.50 +5.232.178.196 +5.232.179.44 +5.232.179.185 +5.232.181.38 +5.232.181.208 +5.232.182.146 +5.232.183.4 +5.232.184.206 +5.232.185.3 +5.232.185.61 +5.232.185.215 +5.232.186.151 +5.232.186.218 +5.232.186.219 +5.232.187.186 +5.232.188.152 +5.232.188.188 +5.232.189.68 +5.232.189.145 +5.232.190.238 +5.232.191.43 +5.232.191.97 +5.232.192.52 +5.232.192.99 +5.232.192.180 +5.232.193.62 +5.232.193.180 +5.232.193.202 +5.232.193.222 +5.232.194.139 +5.232.194.165 +5.232.195.238 +5.232.196.102 +5.232.197.1 +5.232.197.13 +5.232.197.158 +5.232.197.168 +5.232.197.200 +5.232.198.17 +5.232.199.18 +5.232.199.107 +5.232.199.148 +5.232.200.85 +5.232.201.113 +5.232.201.169 +5.232.201.232 +5.232.201.250 +5.232.202.205 +5.232.202.216 +5.232.204.169 +5.232.205.170 +5.232.207.56 +5.232.208.249 +5.232.209.119 +5.232.211.28 +5.232.213.42 +5.232.213.59 +5.232.213.136 +5.232.214.164 +5.232.215.34 +5.232.215.220 +5.232.215.226 +5.232.238.245 +5.233.44.59 +5.233.44.61 +5.233.94.81 +5.233.94.140 +5.233.94.174 +5.233.205.157 +5.233.205.182 +5.233.248.224 +5.233.249.52 +5.233.254.88 +5.233.254.232 +5.233.255.221 +5.234.0.57 +5.234.0.96 +5.234.0.203 +5.234.2.29 +5.234.2.132 +5.234.3.51 +5.234.3.194 +5.234.5.251 +5.234.10.9 +5.234.11.43 +5.234.12.47 +5.234.13.112 +5.234.14.122 +5.234.14.219 +5.234.15.194 +5.234.19.84 +5.234.20.156 +5.234.20.211 +5.234.21.169 +5.234.21.250 +5.234.22.63 +5.234.24.218 +5.234.25.103 +5.234.26.54 +5.234.26.138 +5.234.26.148 +5.234.28.73 +5.234.28.126 +5.234.28.147 +5.234.29.197 +5.234.32.10 +5.234.32.62 +5.234.32.75 +5.234.32.129 +5.234.33.230 +5.234.34.89 +5.234.35.195 +5.234.44.68 +5.234.44.226 +5.234.46.178 +5.234.68.222 +5.234.91.74 +5.234.166.182 +5.234.179.43 +5.234.215.102 +5.235.152.156 +5.235.152.182 +5.235.153.120 +5.235.155.61 +5.235.155.221 +5.235.156.42 +5.235.156.147 +5.235.156.149 +5.235.157.238 +5.235.158.235 +5.235.159.31 +5.235.160.173 +5.235.161.106 +5.235.162.106 +5.235.163.30 +5.235.163.180 +5.235.164.199 +5.235.164.236 +5.235.166.121 +5.235.170.173 +5.235.171.180 +5.235.171.231 +5.235.173.118 +5.235.174.61 +5.235.175.27 +5.235.176.46 +5.235.176.192 +5.235.176.234 +5.235.177.148 +5.235.179.135 +5.235.180.142 +5.235.181.200 +5.235.182.66 +5.235.182.193 +5.235.182.229 +5.235.183.189 +5.235.185.142 +5.235.185.176 +5.235.186.75 +5.235.186.161 +5.235.186.211 +5.235.186.226 +5.235.186.227 +5.235.187.111 +5.235.187.127 +5.235.189.58 +5.235.195.238 +5.235.196.83 +5.235.196.136 +5.235.197.225 +5.235.198.83 +5.235.199.92 +5.235.201.252 +5.235.202.22 +5.235.204.30 +5.235.204.191 +5.235.205.157 +5.235.219.169 +5.235.220.184 +5.235.220.187 +5.235.222.138 +5.235.223.201 +5.235.224.154 +5.235.225.248 +5.235.226.101 +5.235.226.173 +5.235.226.211 +5.235.227.158 +5.235.227.195 +5.235.228.165 +5.235.229.169 +5.235.230.173 +5.235.230.195 +5.235.231.205 +5.235.232.161 +5.235.233.39 +5.235.234.58 +5.235.234.154 +5.235.234.176 +5.235.235.10 +5.235.238.60 +5.235.239.32 +5.235.241.41 +5.235.241.121 +5.235.243.12 +5.235.245.242 +5.235.247.164 +5.235.250.43 +5.235.252.190 +5.235.253.14 +5.235.253.137 +5.235.254.8 +5.235.255.101 +5.236.25.31 +5.236.26.247 +5.236.28.53 +5.236.29.178 +5.236.30.168 +5.236.31.208 +5.236.36.45 +5.236.36.47 +5.236.36.66 +5.236.36.85 +5.236.36.100 +5.236.36.113 +5.236.36.145 +5.236.67.229 +5.236.68.165 +5.236.69.213 +5.236.71.98 +5.236.87.208 +5.236.93.14 +5.236.93.48 +5.236.93.174 +5.236.93.234 +5.236.96.19 +5.236.96.23 +5.236.96.40 +5.236.96.60 +5.236.108.235 +5.236.109.224 +5.236.113.178 +5.236.114.159 +5.236.114.235 +5.236.115.128 +5.236.115.251 +5.236.117.60 +5.236.119.154 +5.236.124.194 +5.236.192.40 +5.236.193.100 +5.236.194.126 +5.236.195.79 +5.236.195.96 +5.236.195.201 +5.236.209.244 +5.236.212.244 +5.236.213.53 +5.236.213.78 +5.236.213.179 +5.236.219.109 +5.236.220.70 +5.236.225.244 +5.236.226.78 +5.236.226.188 +5.236.229.139 +5.236.235.2 +5.236.242.203 +5.236.245.102 +5.236.246.11 +5.236.246.159 +5.236.251.75 +5.237.55.14 +5.237.142.130 +5.237.176.90 +5.237.192.208 +5.237.193.127 +5.237.194.226 +5.237.195.188 +5.237.196.7 +5.237.197.52 +5.237.200.253 +5.237.201.159 +5.237.202.87 +5.237.204.168 +5.237.208.237 +5.237.209.121 +5.237.211.73 +5.237.211.98 +5.237.211.231 +5.237.217.54 +5.237.217.64 +5.237.223.84 +5.237.223.146 +5.237.226.189 +5.237.229.54 +5.237.229.226 +5.237.230.76 +5.237.231.170 +5.237.232.125 +5.237.232.243 +5.237.233.144 +5.237.233.191 +5.237.234.34 +5.237.236.43 +5.237.238.207 +5.237.240.53 +5.237.240.103 +5.237.241.179 +5.237.241.183 +5.237.242.60 +5.237.243.14 +5.237.243.198 +5.237.247.15 +5.237.247.195 +5.237.248.197 +5.237.249.142 +5.237.249.242 +5.237.253.187 +5.237.255.14 +5.237.255.166 +5.238.3.90 +5.238.17.31 +5.238.52.159 +5.238.52.186 +5.238.52.195 +5.238.52.234 +5.238.52.236 +5.238.52.241 +5.238.74.210 +5.238.94.243 +5.238.110.7 +5.238.110.8 +5.238.110.29 +5.238.110.64 +5.238.110.210 +5.238.111.39 +5.238.111.134 +5.238.128.92 +5.238.131.6 +5.238.131.21 +5.238.131.179 +5.238.131.223 +5.238.132.182 +5.238.132.208 +5.238.132.228 +5.238.134.53 +5.238.134.155 +5.238.134.212 +5.238.135.45 +5.238.135.73 +5.238.136.160 +5.238.137.1 +5.238.137.175 +5.238.139.45 +5.238.139.62 +5.238.139.231 +5.238.140.227 +5.238.140.248 +5.238.143.149 +5.238.143.243 +5.238.144.25 +5.238.146.116 +5.238.147.99 +5.238.153.65 +5.238.153.70 +5.238.153.102 +5.238.153.107 +5.238.153.138 +5.238.156.6 +5.238.157.206 +5.238.158.183 +5.238.159.102 +5.238.159.184 +5.238.160.134 +5.238.161.24 +5.238.162.17 +5.238.162.31 +5.238.162.90 +5.238.163.60 +5.238.163.142 +5.238.164.40 +5.238.164.44 +5.238.165.168 +5.238.166.7 +5.238.166.55 +5.238.167.171 +5.238.168.83 +5.238.169.99 +5.238.169.101 +5.238.169.107 +5.238.171.30 +5.238.171.107 +5.238.171.143 +5.238.172.214 +5.238.174.56 +5.238.174.129 +5.238.174.245 +5.238.176.1 +5.238.176.59 +5.238.176.95 +5.238.177.5 +5.238.177.236 +5.238.177.239 +5.238.181.65 +5.238.184.197 +5.238.185.39 +5.238.185.123 +5.238.186.41 +5.238.187.168 +5.238.189.167 +5.238.190.228 +5.238.191.5 +5.238.195.78 +5.238.195.138 +5.238.196.51 +5.238.196.107 +5.238.196.129 +5.238.196.169 +5.238.197.118 +5.238.199.24 +5.238.202.108 +5.238.203.16 +5.238.203.28 +5.238.203.154 +5.238.204.82 +5.238.204.224 +5.238.205.112 +5.238.206.175 +5.238.207.60 +5.238.208.233 +5.238.210.11 +5.238.210.66 +5.238.210.236 +5.238.211.85 +5.238.211.121 +5.238.211.148 +5.238.212.191 +5.238.213.86 +5.238.214.195 +5.238.217.57 +5.238.217.219 +5.238.218.144 +5.238.218.227 +5.238.223.38 +5.238.227.11 +5.238.227.85 +5.238.227.104 +5.238.227.171 +5.238.227.173 +5.238.228.116 +5.238.229.131 +5.238.229.246 +5.238.230.79 +5.238.232.178 +5.238.232.190 +5.238.234.48 +5.238.235.95 +5.238.237.166 +5.238.239.165 +5.238.240.235 +5.238.241.45 +5.238.241.116 +5.238.242.137 +5.238.242.204 +5.238.245.225 +5.238.246.114 +5.238.246.145 +5.238.247.72 +5.238.247.89 +5.238.248.90 +5.238.249.80 +5.238.250.153 +5.238.251.77 +5.238.251.170 +5.238.252.216 +5.238.254.51 +5.238.255.98 +5.239.0.243 +5.239.1.191 +5.239.2.25 +5.239.2.223 +5.239.3.128 +5.239.3.164 +5.239.7.14 +5.239.7.49 +5.239.7.104 +5.239.8.209 +5.239.9.11 +5.239.9.56 +5.239.9.183 +5.239.9.228 +5.239.10.48 +5.239.11.58 +5.239.11.197 +5.239.12.67 +5.239.13.160 +5.239.14.202 +5.239.15.7 +5.239.15.157 +5.239.17.149 +5.239.17.177 +5.239.18.248 +5.239.21.24 +5.239.21.85 +5.239.22.32 +5.239.22.107 +5.239.22.173 +5.239.23.71 +5.239.23.138 +5.239.24.181 +5.239.25.176 +5.239.26.131 +5.239.26.170 +5.239.26.174 +5.239.28.44 +5.239.30.38 +5.239.30.171 +5.239.31.129 +5.239.31.205 +5.239.32.77 +5.239.33.6 +5.239.34.56 +5.239.35.31 +5.239.35.252 +5.239.36.41 +5.239.37.171 +5.239.38.117 +5.239.42.232 +5.239.43.35 +5.239.44.129 +5.239.44.132 +5.239.45.109 +5.239.45.164 +5.239.45.178 +5.239.45.211 +5.239.46.185 +5.239.46.239 +5.239.49.138 +5.239.50.156 +5.239.51.83 +5.239.53.5 +5.239.53.127 +5.239.55.249 +5.239.56.81 +5.239.56.218 +5.239.58.175 +5.239.58.192 +5.239.59.106 +5.239.59.250 +5.239.60.38 +5.239.60.45 +5.239.62.32 +5.239.63.47 +5.239.63.100 +5.239.63.159 +5.239.63.216 +5.239.64.246 +5.239.66.72 +5.239.66.141 +5.239.66.143 +5.239.66.212 +5.239.69.80 +5.239.70.40 +5.239.70.212 +5.239.71.72 +5.239.71.251 +5.239.73.218 +5.239.73.230 +5.239.75.6 +5.239.75.79 +5.239.75.145 +5.239.76.59 +5.239.76.241 +5.239.78.125 +5.239.79.2 +5.239.80.135 +5.239.81.222 +5.239.82.151 +5.239.83.12 +5.239.84.66 +5.239.84.170 +5.239.85.99 +5.239.86.243 +5.239.86.253 +5.239.88.48 +5.239.88.163 +5.239.89.252 +5.239.89.254 +5.239.91.70 +5.239.91.80 +5.239.91.161 +5.239.92.104 +5.239.96.85 +5.239.99.99 +5.239.102.203 +5.239.103.229 +5.239.104.116 +5.239.104.243 +5.239.107.132 +5.239.108.58 +5.239.108.81 +5.239.108.221 +5.239.109.52 +5.239.110.66 +5.239.110.108 +5.239.112.30 +5.239.112.157 +5.239.114.208 +5.239.115.216 +5.239.117.1 +5.239.117.174 +5.239.117.179 +5.239.118.56 +5.239.118.198 +5.239.118.217 +5.239.119.100 +5.239.119.178 +5.239.119.183 +5.239.119.207 +5.239.121.137 +5.239.121.216 +5.239.124.95 +5.239.125.35 +5.239.127.128 +5.239.127.172 +5.239.149.170 +5.239.157.217 +5.239.158.188 +5.239.160.15 +5.239.160.75 +5.239.160.105 +5.239.160.125 +5.239.160.143 +5.239.161.33 +5.239.161.49 +5.239.161.87 +5.239.161.109 +5.239.161.147 +5.239.161.178 +5.239.161.194 +5.239.176.2 +5.239.177.48 +5.239.177.69 +5.239.177.163 +5.239.177.237 +5.239.197.37 +5.239.199.8 +5.239.202.231 +5.239.230.12 +5.239.231.88 +5.239.237.119 +5.239.239.25 +5.239.240.88 +5.239.240.91 +5.239.240.193 +5.239.240.197 +5.239.240.200 +5.239.240.238 +5.239.241.12 +5.239.241.28 +5.239.241.71 +5.239.241.195 +5.239.245.137 +5.239.245.192 +5.239.245.240 +5.239.245.245 +5.239.245.249 +5.239.245.250 +5.252.216.3 +5.252.216.4 +31.7.64.4 +31.7.64.6 +31.7.64.81 +31.7.65.18 +31.7.65.22 +31.7.65.55 +31.7.65.57 +31.7.65.82 +31.7.65.83 +31.7.66.75 +31.7.66.146 +31.7.66.147 +31.7.68.249 +31.7.69.220 +31.7.71.9 +31.7.72.60 +31.7.72.146 +31.7.73.38 +31.7.73.67 +31.7.73.77 +31.7.73.85 +31.7.73.132 +31.7.74.49 +31.7.78.16 +31.7.78.26 +31.7.78.46 +31.7.78.47 +31.7.78.109 +31.7.91.159 +31.7.91.166 +31.7.110.218 +31.14.112.210 +31.14.113.237 +31.14.114.83 +31.14.114.84 +31.14.114.85 +31.14.114.86 +31.14.114.87 +31.14.114.88 +31.14.114.89 +31.14.114.90 +31.14.114.91 +31.14.114.92 +31.14.114.94 +31.14.114.165 +31.14.117.116 +31.14.117.178 +31.14.117.180 +31.14.117.226 +31.14.117.227 +31.14.118.123 +31.14.118.125 +31.14.118.126 +31.14.119.162 +31.14.119.166 +31.14.120.54 +31.14.124.45 +31.14.124.195 +31.14.126.31 +31.14.126.40 +31.14.126.85 +31.14.126.88 +31.14.126.120 +31.14.127.139 +31.14.127.183 +31.24.201.108 +31.25.90.14 +31.25.90.43 +31.25.91.7 +31.25.92.13 +31.25.92.15 +31.25.92.24 +31.25.92.31 +31.25.92.44 +31.25.92.63 +31.25.92.64 +31.25.92.71 +31.25.92.73 +31.25.92.74 +31.25.92.97 +31.25.92.138 +31.25.92.153 +31.25.92.173 +31.25.92.194 +31.25.92.198 +31.25.92.205 +31.25.92.214 +31.25.92.243 +31.25.93.8 +31.25.93.18 +31.25.93.45 +31.25.93.163 +31.25.94.1 +31.25.94.106 +31.25.95.2 +31.25.95.32 +31.25.95.184 +31.25.95.216 +31.25.128.105 +31.25.128.132 +31.25.129.39 +31.25.129.163 +31.25.129.247 +31.25.130.8 +31.25.130.228 +31.25.130.239 +31.25.131.164 +31.25.132.30 +31.25.132.49 +31.25.132.85 +31.25.132.253 +31.25.134.99 +31.25.134.148 +31.25.134.150 +31.25.134.233 +31.25.134.240 +31.25.135.50 +31.25.135.80 +31.25.135.81 +31.25.135.93 +31.25.135.173 +31.25.135.236 +31.47.32.34 +31.47.33.10 +31.47.33.11 +31.47.33.12 +31.47.33.194 +31.47.34.42 +31.47.34.74 +31.47.35.34 +31.47.39.194 +31.47.45.227 +31.47.48.3 +31.47.48.4 +31.47.48.5 +31.47.48.6 +31.47.48.7 +31.47.48.8 +31.47.48.10 +31.47.48.11 +31.47.48.13 +31.47.48.15 +31.47.48.19 +31.47.48.22 +31.47.51.55 +31.47.52.27 +31.47.52.110 +31.47.54.90 +31.47.58.98 +31.47.61.190 +31.47.61.234 +31.130.181.241 +31.130.181.254 +31.130.182.12 +31.130.182.13 +31.170.49.245 +31.170.50.59 +31.170.52.61 +31.170.52.106 +31.170.52.132 +31.170.52.191 +31.170.52.197 +31.170.52.248 +31.170.53.0 +31.170.53.86 +31.170.57.21 +31.170.57.71 +31.170.58.30 +31.170.59.26 +31.170.59.98 +31.170.59.133 +31.170.59.225 +31.170.60.16 +31.170.60.20 +31.170.60.90 +31.170.60.99 +31.170.60.111 +31.170.60.146 +31.170.60.182 +31.170.60.210 +31.170.60.230 +31.170.61.161 +31.170.61.165 +31.170.61.173 +31.170.61.237 +31.184.129.77 +31.184.129.106 +31.184.144.34 +31.184.147.78 +31.184.147.88 +31.184.147.149 +31.184.147.161 +31.184.147.170 +31.184.166.2 +31.184.167.2 +31.184.170.80 +31.184.170.98 +31.184.170.100 +31.184.170.150 +31.184.170.169 +31.184.170.178 +31.184.170.234 +31.184.171.110 +31.184.171.216 +31.184.175.2 +31.184.176.176 +31.184.177.136 +31.184.178.11 +31.184.178.224 +31.184.178.228 +31.184.179.218 +31.184.179.219 +31.184.188.1 +31.184.188.8 +31.184.188.10 +31.184.188.14 +31.184.188.18 +31.184.188.26 +31.184.188.27 +31.184.188.28 +31.184.188.42 +31.184.188.45 +31.184.188.62 +31.184.188.64 +31.184.188.70 +31.184.188.74 +31.184.188.81 +31.184.188.91 +31.184.188.99 +31.184.188.105 +31.184.188.119 +31.184.188.120 +31.184.188.131 +31.184.188.135 +31.184.188.136 +31.184.188.137 +31.184.188.143 +31.184.188.161 +31.184.188.163 +31.184.188.166 +31.184.188.170 +31.184.188.174 +31.184.188.179 +31.184.188.184 +31.184.188.191 +31.184.188.201 +31.184.188.205 +31.184.188.216 +31.184.188.221 +31.184.188.224 +31.184.188.226 +31.184.188.227 +31.184.188.230 +31.184.188.231 +31.184.188.232 +31.184.188.239 +31.184.189.10 +31.184.189.22 +31.184.189.27 +31.184.189.29 +31.184.189.35 +31.184.189.41 +31.184.189.46 +31.184.189.48 +31.184.189.50 +31.184.189.57 +31.184.189.67 +31.184.189.68 +31.184.189.88 +31.184.189.97 +31.184.189.98 +31.184.189.109 +31.184.189.120 +31.184.189.127 +31.184.189.146 +31.184.189.151 +31.184.189.152 +31.184.189.153 +31.184.189.157 +31.184.189.179 +31.184.189.184 +31.184.189.187 +31.184.189.199 +31.184.189.208 +31.184.189.213 +31.184.189.233 +31.184.189.234 +31.184.189.236 +31.184.189.237 +31.184.189.246 +31.184.190.2 +31.184.190.10 +31.184.190.51 +31.184.190.56 +31.184.190.64 +31.184.190.77 +31.184.190.84 +31.184.190.100 +31.184.190.101 +31.184.190.104 +31.184.190.111 +31.184.190.114 +31.184.190.136 +31.184.190.145 +31.184.190.158 +31.184.190.180 +31.184.190.182 +31.184.190.185 +31.184.190.194 +31.184.190.202 +31.184.190.208 +31.184.190.219 +31.184.190.228 +31.184.190.231 +31.184.190.241 +31.184.190.245 +31.184.190.254 +31.184.191.2 +31.184.191.6 +31.184.191.7 +31.184.191.12 +31.184.191.20 +31.184.191.22 +31.184.191.23 +31.184.191.28 +31.184.191.34 +31.184.191.47 +31.184.191.48 +31.184.191.54 +31.184.191.62 +31.184.191.74 +31.184.191.94 +31.184.191.100 +31.184.191.112 +31.184.191.116 +31.184.191.125 +31.184.191.126 +31.184.191.127 +31.184.191.132 +31.184.191.138 +31.184.191.146 +31.184.191.159 +31.184.191.160 +31.184.191.166 +31.184.191.184 +31.184.191.199 +31.184.191.201 +31.184.191.213 +31.184.191.215 +31.184.191.220 +31.184.191.226 +31.184.191.228 +31.184.191.235 +31.184.191.242 +31.184.191.253 +31.214.147.255 +31.214.168.66 +31.214.168.104 +31.214.168.178 +31.214.171.136 +31.214.171.226 +31.214.172.4 +31.214.172.58 +31.214.173.75 +31.214.173.77 +31.214.174.130 +31.214.175.241 +31.214.231.120 +31.214.248.242 +31.214.250.18 +31.214.250.115 +31.214.251.237 +31.214.255.52 +31.214.255.164 +31.214.255.173 +31.216.62.242 +37.10.67.11 +37.10.109.148 +37.32.4.222 +37.32.5.5 +37.32.5.25 +37.32.5.59 +37.32.5.119 +37.32.5.215 +37.32.6.67 +37.32.6.194 +37.32.9.83 +37.32.10.126 +37.32.11.191 +37.32.11.204 +37.32.12.30 +37.32.12.84 +37.32.12.231 +37.32.13.25 +37.32.13.30 +37.32.14.62 +37.32.14.81 +37.32.14.200 +37.32.15.39 +37.32.21.0 +37.32.21.21 +37.32.21.148 +37.32.24.21 +37.32.24.49 +37.32.25.86 +37.32.25.134 +37.32.25.227 +37.32.26.37 +37.32.27.56 +37.32.27.68 +37.32.40.38 +37.32.43.74 +37.32.43.75 +37.32.43.77 +37.32.43.78 +37.32.43.80 +37.32.43.81 +37.32.43.82 +37.32.43.83 +37.32.43.84 +37.32.43.85 +37.32.43.86 +37.32.43.87 +37.32.43.88 +37.32.43.89 +37.32.43.90 +37.32.43.91 +37.32.43.92 +37.32.43.93 +37.32.43.197 +37.32.45.96 +37.32.46.140 +37.32.47.181 +37.32.47.199 +37.32.112.2 +37.32.117.5 +37.32.120.100 +37.32.120.137 +37.32.121.48 +37.32.121.53 +37.32.121.76 +37.32.124.16 +37.32.125.131 +37.32.125.192 +37.32.125.193 +37.32.125.195 +37.32.127.132 +37.32.127.163 +37.75.240.1 +37.75.240.2 +37.75.240.20 +37.75.240.21 +37.75.240.30 +37.75.240.65 +37.75.242.89 +37.75.242.179 +37.75.242.188 +37.75.243.61 +37.75.243.62 +37.75.243.82 +37.75.243.146 +37.75.243.154 +37.75.243.195 +37.75.243.254 +37.75.244.22 +37.75.244.26 +37.75.244.50 +37.75.244.52 +37.75.244.53 +37.75.244.122 +37.75.245.77 +37.75.245.80 +37.75.245.131 +37.75.246.54 +37.75.246.138 +37.75.246.241 +37.75.246.242 +37.75.246.243 +37.75.246.244 +37.75.246.246 +37.75.247.176 +37.75.247.177 +37.75.247.178 +37.75.247.182 +37.98.112.30 +37.98.112.210 +37.98.118.100 +37.114.204.42 +37.114.204.114 +37.114.204.230 +37.114.205.66 +37.114.205.67 +37.114.205.68 +37.114.205.69 +37.114.205.70 +37.114.205.74 +37.114.225.82 +37.114.225.84 +37.114.225.85 +37.114.225.138 +37.114.246.46 +37.114.246.196 +37.114.247.50 +37.114.247.202 +37.130.200.3 +37.130.200.29 +37.130.202.36 +37.130.205.2 +37.130.207.2 +37.143.144.8 +37.143.146.240 +37.143.147.34 +37.143.147.58 +37.143.147.78 +37.143.147.123 +37.143.147.133 +37.143.147.147 +37.143.147.150 +37.143.148.3 +37.143.148.9 +37.143.148.20 +37.143.148.31 +37.143.148.36 +37.143.148.72 +37.143.148.75 +37.143.148.83 +37.143.148.99 +37.143.148.100 +37.143.148.114 +37.143.148.120 +37.143.148.153 +37.143.148.164 +37.143.148.184 +37.143.148.188 +37.143.148.192 +37.143.148.208 +37.143.148.252 +37.143.148.253 +37.143.149.0 +37.143.149.1 +37.143.149.2 +37.143.149.3 +37.143.149.4 +37.143.149.5 +37.143.149.7 +37.143.149.8 +37.143.149.9 +37.143.149.10 +37.143.149.11 +37.143.149.12 +37.143.149.13 +37.143.149.14 +37.143.149.15 +37.143.149.16 +37.143.149.18 +37.143.149.20 +37.143.149.21 +37.143.149.22 +37.143.149.23 +37.143.149.24 +37.143.149.25 +37.143.149.26 +37.143.149.27 +37.143.149.28 +37.143.149.29 +37.143.149.30 +37.143.149.31 +37.143.149.32 +37.143.149.33 +37.143.149.34 +37.143.149.35 +37.143.149.36 +37.143.149.37 +37.143.149.38 +37.143.149.39 +37.143.149.40 +37.143.149.41 +37.143.149.42 +37.143.149.43 +37.143.149.44 +37.143.149.45 +37.143.149.46 +37.143.149.47 +37.143.149.48 +37.143.149.49 +37.143.149.50 +37.143.149.51 +37.143.149.52 +37.143.149.53 +37.143.149.54 +37.143.149.55 +37.143.149.57 +37.143.149.58 +37.143.149.59 +37.143.149.64 +37.143.149.65 +37.143.149.67 +37.143.149.68 +37.143.149.69 +37.143.149.70 +37.143.149.71 +37.143.149.72 +37.143.149.73 +37.143.149.74 +37.143.149.75 +37.143.149.76 +37.143.149.77 +37.143.149.78 +37.143.149.79 +37.143.149.80 +37.143.149.82 +37.143.149.83 +37.143.149.84 +37.143.149.85 +37.143.149.86 +37.143.149.87 +37.143.149.88 +37.143.149.89 +37.143.149.90 +37.143.149.92 +37.143.149.93 +37.143.149.94 +37.143.149.95 +37.143.149.96 +37.143.149.97 +37.143.149.98 +37.143.149.99 +37.143.149.100 +37.143.149.101 +37.143.149.102 +37.143.149.103 +37.143.149.104 +37.143.149.105 +37.143.149.106 +37.143.149.107 +37.143.149.108 +37.143.149.109 +37.143.149.110 +37.143.149.111 +37.143.149.112 +37.143.149.113 +37.143.149.114 +37.143.149.116 +37.143.149.117 +37.143.149.118 +37.143.149.119 +37.143.149.120 +37.143.149.121 +37.143.149.122 +37.143.149.123 +37.143.149.124 +37.143.149.125 +37.143.149.126 +37.143.149.127 +37.143.151.40 +37.143.151.78 +37.143.151.82 +37.143.151.89 +37.143.151.108 +37.143.151.157 +37.143.151.162 +37.143.151.192 +37.143.151.210 +37.143.151.219 +37.148.0.4 +37.148.0.8 +37.148.0.22 +37.148.0.32 +37.148.0.55 +37.148.0.60 +37.148.0.73 +37.148.0.77 +37.148.0.80 +37.148.0.82 +37.148.0.88 +37.148.0.99 +37.148.0.103 +37.148.0.200 +37.148.0.215 +37.148.0.220 +37.148.0.225 +37.148.0.238 +37.148.0.246 +37.148.1.27 +37.148.1.28 +37.148.1.30 +37.148.1.33 +37.148.1.56 +37.148.1.60 +37.148.1.66 +37.148.1.87 +37.148.1.115 +37.148.1.116 +37.148.1.128 +37.148.1.134 +37.148.1.150 +37.148.1.154 +37.148.1.161 +37.148.1.173 +37.148.1.186 +37.148.1.223 +37.148.1.226 +37.148.1.228 +37.148.1.239 +37.148.2.13 +37.148.2.14 +37.148.2.15 +37.148.2.52 +37.148.2.54 +37.148.2.63 +37.148.2.96 +37.148.2.98 +37.148.2.107 +37.148.2.108 +37.148.2.114 +37.148.2.116 +37.148.2.125 +37.148.2.127 +37.148.2.147 +37.148.2.185 +37.148.2.189 +37.148.2.198 +37.148.2.200 +37.148.2.205 +37.148.2.218 +37.148.2.228 +37.148.2.232 +37.148.2.233 +37.148.2.240 +37.148.2.243 +37.148.3.3 +37.148.3.7 +37.148.3.16 +37.148.3.36 +37.148.3.42 +37.148.3.60 +37.148.3.64 +37.148.3.83 +37.148.3.88 +37.148.3.91 +37.148.3.94 +37.148.3.100 +37.148.3.103 +37.148.3.108 +37.148.3.120 +37.148.3.125 +37.148.3.127 +37.148.3.133 +37.148.3.134 +37.148.3.136 +37.148.3.141 +37.148.3.148 +37.148.3.150 +37.148.3.157 +37.148.3.160 +37.148.3.179 +37.148.3.237 +37.148.3.246 +37.148.4.53 +37.148.4.74 +37.148.4.75 +37.148.4.96 +37.148.4.106 +37.148.4.132 +37.148.4.168 +37.148.4.179 +37.148.4.192 +37.148.5.43 +37.148.5.79 +37.148.5.88 +37.148.5.94 +37.148.5.97 +37.148.5.109 +37.148.5.115 +37.148.5.132 +37.148.5.136 +37.148.5.143 +37.148.5.147 +37.148.5.165 +37.148.5.199 +37.148.5.222 +37.148.5.223 +37.148.5.228 +37.148.5.248 +37.148.6.1 +37.148.6.17 +37.148.6.32 +37.148.6.48 +37.148.6.65 +37.148.6.76 +37.148.6.82 +37.148.6.84 +37.148.6.89 +37.148.6.92 +37.148.6.97 +37.148.6.101 +37.148.6.109 +37.148.6.111 +37.148.6.119 +37.148.6.134 +37.148.6.136 +37.148.6.138 +37.148.6.145 +37.148.6.156 +37.148.6.161 +37.148.6.162 +37.148.6.166 +37.148.6.179 +37.148.6.188 +37.148.6.198 +37.148.6.216 +37.148.6.225 +37.148.6.228 +37.148.6.231 +37.148.6.255 +37.148.7.0 +37.148.7.4 +37.148.7.22 +37.148.7.23 +37.148.7.50 +37.148.7.65 +37.148.7.74 +37.148.7.78 +37.148.7.85 +37.148.7.105 +37.148.7.127 +37.148.7.130 +37.148.7.132 +37.148.7.181 +37.148.7.185 +37.148.7.196 +37.148.7.234 +37.148.8.4 +37.148.8.8 +37.148.8.10 +37.148.8.12 +37.148.8.16 +37.148.8.40 +37.148.8.51 +37.148.8.53 +37.148.8.63 +37.148.8.68 +37.148.8.73 +37.148.8.87 +37.148.8.95 +37.148.8.99 +37.148.8.111 +37.148.8.118 +37.148.8.158 +37.148.8.188 +37.148.8.196 +37.148.8.201 +37.148.8.210 +37.148.8.217 +37.148.8.218 +37.148.8.231 +37.148.8.240 +37.148.8.253 +37.148.9.32 +37.148.9.36 +37.148.9.56 +37.148.9.96 +37.148.9.102 +37.148.9.146 +37.148.9.149 +37.148.9.155 +37.148.9.174 +37.148.9.188 +37.148.9.228 +37.148.9.246 +37.148.10.65 +37.148.10.79 +37.148.10.83 +37.148.10.113 +37.148.10.121 +37.148.10.141 +37.148.10.155 +37.148.10.166 +37.148.10.192 +37.148.10.195 +37.148.10.217 +37.148.10.230 +37.148.11.9 +37.148.11.23 +37.148.11.39 +37.148.11.50 +37.148.11.64 +37.148.11.66 +37.148.11.75 +37.148.11.85 +37.148.11.108 +37.148.11.141 +37.148.11.168 +37.148.11.171 +37.148.11.191 +37.148.11.212 +37.148.11.229 +37.148.11.231 +37.148.11.252 +37.148.12.25 +37.148.12.27 +37.148.12.28 +37.148.12.35 +37.148.12.46 +37.148.12.68 +37.148.12.76 +37.148.12.89 +37.148.12.129 +37.148.12.145 +37.148.12.152 +37.148.12.158 +37.148.12.160 +37.148.12.162 +37.148.12.169 +37.148.12.200 +37.148.12.203 +37.148.12.212 +37.148.12.218 +37.148.12.239 +37.148.12.240 +37.148.12.241 +37.148.13.0 +37.148.13.3 +37.148.13.55 +37.148.13.78 +37.148.13.80 +37.148.13.93 +37.148.13.111 +37.148.13.119 +37.148.13.123 +37.148.13.128 +37.148.13.130 +37.148.13.131 +37.148.13.142 +37.148.13.156 +37.148.13.160 +37.148.13.166 +37.148.13.174 +37.148.13.202 +37.148.13.206 +37.148.13.213 +37.148.13.224 +37.148.13.228 +37.148.13.232 +37.148.13.239 +37.148.13.246 +37.148.14.19 +37.148.14.38 +37.148.14.41 +37.148.14.57 +37.148.14.60 +37.148.14.69 +37.148.14.94 +37.148.14.116 +37.148.14.151 +37.148.14.173 +37.148.14.179 +37.148.14.193 +37.148.14.197 +37.148.14.204 +37.148.14.246 +37.148.15.3 +37.148.15.7 +37.148.15.11 +37.148.15.26 +37.148.15.65 +37.148.15.115 +37.148.15.117 +37.148.15.142 +37.148.15.149 +37.148.15.153 +37.148.15.155 +37.148.15.177 +37.148.15.195 +37.148.15.198 +37.148.15.222 +37.148.15.234 +37.148.15.237 +37.148.16.6 +37.148.16.15 +37.148.16.17 +37.148.16.26 +37.148.16.31 +37.148.16.32 +37.148.16.45 +37.148.16.47 +37.148.16.54 +37.148.16.61 +37.148.16.66 +37.148.16.67 +37.148.16.76 +37.148.16.79 +37.148.16.100 +37.148.16.130 +37.148.16.176 +37.148.16.196 +37.148.16.214 +37.148.16.215 +37.148.16.231 +37.148.16.254 +37.148.16.255 +37.148.17.22 +37.148.17.29 +37.148.17.38 +37.148.17.43 +37.148.17.61 +37.148.17.63 +37.148.17.67 +37.148.17.83 +37.148.17.87 +37.148.17.100 +37.148.17.105 +37.148.17.111 +37.148.17.135 +37.148.17.144 +37.148.17.161 +37.148.17.170 +37.148.17.188 +37.148.17.215 +37.148.17.229 +37.148.17.255 +37.148.18.40 +37.148.18.45 +37.148.18.46 +37.148.18.71 +37.148.18.79 +37.148.18.84 +37.148.18.92 +37.148.18.124 +37.148.18.128 +37.148.18.132 +37.148.18.135 +37.148.18.137 +37.148.18.143 +37.148.18.144 +37.148.18.146 +37.148.18.155 +37.148.18.166 +37.148.18.173 +37.148.18.185 +37.148.18.199 +37.148.18.215 +37.148.18.218 +37.148.18.226 +37.148.18.237 +37.148.18.246 +37.148.18.247 +37.148.19.13 +37.148.19.15 +37.148.19.28 +37.148.19.43 +37.148.19.61 +37.148.19.72 +37.148.19.77 +37.148.19.78 +37.148.19.92 +37.148.19.93 +37.148.19.107 +37.148.19.145 +37.148.19.147 +37.148.19.195 +37.148.19.200 +37.148.19.210 +37.148.19.218 +37.148.20.12 +37.148.20.62 +37.148.20.69 +37.148.20.72 +37.148.20.94 +37.148.20.119 +37.148.20.120 +37.148.20.148 +37.148.20.159 +37.148.20.172 +37.148.20.175 +37.148.20.188 +37.148.20.190 +37.148.20.198 +37.148.20.200 +37.148.20.232 +37.148.20.235 +37.148.20.236 +37.148.20.252 +37.148.21.55 +37.148.21.70 +37.148.21.130 +37.148.21.133 +37.148.21.150 +37.148.21.157 +37.148.21.165 +37.148.21.167 +37.148.21.199 +37.148.21.210 +37.148.21.213 +37.148.21.253 +37.148.22.12 +37.148.22.32 +37.148.22.35 +37.148.22.56 +37.148.22.83 +37.148.22.114 +37.148.22.138 +37.148.22.170 +37.148.22.173 +37.148.22.176 +37.148.22.185 +37.148.22.196 +37.148.22.204 +37.148.22.218 +37.148.22.241 +37.148.22.244 +37.148.23.1 +37.148.23.4 +37.148.23.6 +37.148.23.18 +37.148.23.26 +37.148.23.32 +37.148.23.33 +37.148.23.47 +37.148.23.53 +37.148.23.76 +37.148.23.93 +37.148.23.104 +37.148.23.109 +37.148.23.115 +37.148.23.128 +37.148.23.138 +37.148.23.141 +37.148.23.142 +37.148.23.144 +37.148.23.147 +37.148.23.169 +37.148.23.178 +37.148.23.182 +37.148.23.184 +37.148.23.225 +37.148.24.7 +37.148.24.8 +37.148.24.17 +37.148.24.54 +37.148.24.61 +37.148.24.64 +37.148.24.68 +37.148.24.70 +37.148.24.80 +37.148.24.95 +37.148.24.108 +37.148.24.111 +37.148.24.116 +37.148.24.138 +37.148.24.139 +37.148.24.142 +37.148.24.147 +37.148.24.175 +37.148.24.179 +37.148.24.188 +37.148.24.205 +37.148.24.210 +37.148.24.211 +37.148.24.216 +37.148.24.240 +37.148.24.243 +37.148.24.245 +37.148.25.3 +37.148.25.13 +37.148.25.14 +37.148.25.21 +37.148.25.23 +37.148.25.30 +37.148.25.32 +37.148.25.56 +37.148.25.105 +37.148.25.134 +37.148.25.138 +37.148.25.146 +37.148.25.151 +37.148.25.165 +37.148.25.168 +37.148.25.185 +37.148.25.210 +37.148.25.214 +37.148.25.253 +37.148.26.23 +37.148.26.36 +37.148.26.67 +37.148.26.79 +37.148.26.81 +37.148.26.103 +37.148.26.107 +37.148.26.161 +37.148.26.171 +37.148.26.186 +37.148.26.195 +37.148.26.210 +37.148.26.213 +37.148.26.214 +37.148.26.233 +37.148.26.235 +37.148.26.250 +37.148.27.13 +37.148.27.24 +37.148.27.30 +37.148.27.46 +37.148.27.56 +37.148.27.57 +37.148.27.58 +37.148.27.74 +37.148.27.100 +37.148.27.107 +37.148.27.110 +37.148.27.123 +37.148.27.124 +37.148.27.138 +37.148.27.147 +37.148.27.156 +37.148.27.157 +37.148.27.172 +37.148.27.176 +37.148.27.182 +37.148.27.189 +37.148.27.194 +37.148.27.208 +37.148.27.244 +37.148.27.250 +37.148.27.254 +37.148.28.39 +37.148.28.44 +37.148.28.46 +37.148.28.66 +37.148.28.81 +37.148.28.91 +37.148.28.119 +37.148.28.131 +37.148.28.142 +37.148.28.166 +37.148.28.170 +37.148.28.202 +37.148.28.204 +37.148.28.214 +37.148.28.215 +37.148.28.247 +37.148.28.248 +37.148.29.3 +37.148.29.17 +37.148.29.25 +37.148.29.27 +37.148.29.34 +37.148.29.39 +37.148.29.46 +37.148.29.71 +37.148.29.77 +37.148.29.81 +37.148.29.82 +37.148.29.83 +37.148.29.92 +37.148.29.97 +37.148.29.98 +37.148.29.99 +37.148.29.107 +37.148.29.123 +37.148.29.135 +37.148.29.166 +37.148.29.183 +37.148.29.184 +37.148.29.189 +37.148.29.193 +37.148.29.196 +37.148.29.224 +37.148.29.235 +37.148.29.248 +37.148.30.1 +37.148.30.4 +37.148.30.8 +37.148.30.13 +37.148.30.20 +37.148.30.47 +37.148.30.52 +37.148.30.54 +37.148.30.66 +37.148.30.69 +37.148.30.71 +37.148.30.79 +37.148.30.89 +37.148.30.92 +37.148.30.99 +37.148.30.101 +37.148.30.104 +37.148.30.111 +37.148.30.119 +37.148.30.130 +37.148.30.139 +37.148.30.143 +37.148.30.146 +37.148.30.147 +37.148.30.176 +37.148.30.182 +37.148.30.185 +37.148.30.195 +37.148.30.202 +37.148.30.204 +37.148.30.209 +37.148.30.219 +37.148.30.235 +37.148.30.248 +37.148.30.252 +37.148.31.1 +37.148.31.9 +37.148.31.15 +37.148.31.16 +37.148.31.21 +37.148.31.49 +37.148.31.50 +37.148.31.53 +37.148.31.56 +37.148.31.71 +37.148.31.87 +37.148.31.97 +37.148.31.103 +37.148.31.104 +37.148.31.117 +37.148.31.131 +37.148.31.141 +37.148.31.157 +37.148.31.159 +37.148.31.163 +37.148.31.173 +37.148.31.180 +37.148.31.181 +37.148.31.185 +37.148.31.192 +37.148.31.201 +37.148.31.225 +37.148.31.240 +37.148.31.241 +37.148.31.242 +37.148.31.243 +37.148.31.249 +37.148.31.250 +37.148.40.3 +37.148.40.9 +37.148.40.11 +37.148.40.20 +37.148.40.44 +37.148.40.60 +37.148.40.69 +37.148.40.70 +37.148.40.73 +37.148.40.80 +37.148.40.83 +37.148.40.85 +37.148.40.95 +37.148.40.103 +37.148.40.120 +37.148.40.136 +37.148.40.153 +37.148.40.179 +37.148.40.180 +37.148.40.184 +37.148.40.189 +37.148.40.192 +37.148.40.197 +37.148.40.205 +37.148.40.209 +37.148.40.211 +37.148.40.213 +37.148.40.219 +37.148.40.236 +37.148.40.237 +37.148.40.240 +37.148.40.252 +37.148.41.31 +37.148.41.36 +37.148.41.40 +37.148.41.56 +37.148.41.57 +37.148.41.66 +37.148.41.71 +37.148.41.78 +37.148.41.89 +37.148.41.90 +37.148.41.111 +37.148.41.127 +37.148.41.141 +37.148.41.151 +37.148.41.164 +37.148.41.181 +37.148.41.182 +37.148.41.183 +37.148.41.186 +37.148.41.188 +37.148.41.195 +37.148.41.205 +37.148.41.207 +37.148.41.208 +37.148.41.213 +37.148.41.215 +37.148.41.219 +37.148.41.224 +37.148.42.15 +37.148.42.19 +37.148.42.22 +37.148.42.26 +37.148.42.30 +37.148.42.31 +37.148.42.40 +37.148.42.53 +37.148.42.62 +37.148.42.68 +37.148.42.74 +37.148.42.76 +37.148.42.86 +37.148.42.92 +37.148.42.101 +37.148.42.108 +37.148.42.133 +37.148.42.150 +37.148.42.151 +37.148.42.156 +37.148.42.162 +37.148.42.164 +37.148.42.172 +37.148.42.174 +37.148.42.180 +37.148.42.182 +37.148.42.185 +37.148.42.189 +37.148.42.194 +37.148.42.196 +37.148.42.198 +37.148.42.205 +37.148.42.211 +37.148.42.212 +37.148.42.235 +37.148.42.251 +37.148.43.2 +37.148.43.4 +37.148.43.6 +37.148.43.7 +37.148.43.8 +37.148.43.11 +37.148.43.35 +37.148.43.42 +37.148.43.62 +37.148.43.65 +37.148.43.67 +37.148.43.80 +37.148.43.85 +37.148.43.98 +37.148.43.107 +37.148.43.119 +37.148.43.122 +37.148.43.125 +37.148.43.128 +37.148.43.130 +37.148.43.164 +37.148.43.166 +37.148.43.169 +37.148.43.171 +37.148.43.189 +37.148.43.190 +37.148.43.195 +37.148.43.196 +37.148.43.202 +37.148.43.205 +37.148.43.207 +37.148.43.219 +37.148.43.222 +37.148.43.229 +37.148.43.233 +37.148.43.250 +37.148.44.0 +37.148.44.14 +37.148.44.24 +37.148.44.30 +37.148.44.39 +37.148.44.43 +37.148.44.60 +37.148.44.65 +37.148.44.70 +37.148.44.72 +37.148.44.73 +37.148.44.80 +37.148.44.84 +37.148.44.85 +37.148.44.92 +37.148.44.95 +37.148.44.98 +37.148.44.106 +37.148.44.113 +37.148.44.118 +37.148.44.129 +37.148.44.133 +37.148.44.134 +37.148.44.135 +37.148.44.142 +37.148.44.143 +37.148.44.152 +37.148.44.159 +37.148.44.161 +37.148.44.169 +37.148.44.181 +37.148.44.185 +37.148.44.188 +37.148.44.189 +37.148.44.191 +37.148.44.198 +37.148.44.201 +37.148.44.213 +37.148.44.215 +37.148.44.216 +37.148.44.238 +37.148.44.244 +37.148.44.254 +37.148.45.21 +37.148.45.30 +37.148.45.31 +37.148.45.35 +37.148.45.37 +37.148.45.43 +37.148.45.52 +37.148.45.53 +37.148.45.54 +37.148.45.64 +37.148.45.65 +37.148.45.72 +37.148.45.77 +37.148.45.89 +37.148.45.96 +37.148.45.97 +37.148.45.100 +37.148.45.108 +37.148.45.116 +37.148.45.120 +37.148.45.125 +37.148.45.129 +37.148.45.134 +37.148.45.142 +37.148.45.151 +37.148.45.162 +37.148.45.164 +37.148.45.183 +37.148.45.194 +37.148.45.202 +37.148.45.206 +37.148.45.217 +37.148.46.31 +37.148.46.35 +37.148.46.47 +37.148.46.53 +37.148.46.59 +37.148.46.67 +37.148.46.113 +37.148.46.142 +37.148.46.174 +37.148.46.191 +37.148.46.237 +37.148.47.7 +37.148.47.19 +37.148.47.25 +37.148.47.33 +37.148.47.45 +37.148.47.70 +37.148.47.75 +37.148.47.92 +37.148.47.99 +37.148.47.100 +37.148.47.131 +37.148.47.132 +37.148.47.139 +37.148.47.146 +37.148.47.165 +37.148.48.15 +37.148.48.17 +37.148.48.19 +37.148.48.25 +37.148.48.31 +37.148.48.43 +37.148.48.52 +37.148.48.53 +37.148.48.56 +37.148.48.68 +37.148.48.70 +37.148.48.82 +37.148.48.86 +37.148.48.87 +37.148.48.99 +37.148.48.102 +37.148.48.103 +37.148.48.104 +37.148.48.116 +37.148.48.123 +37.148.48.124 +37.148.48.180 +37.148.48.202 +37.148.48.203 +37.148.48.207 +37.148.48.210 +37.148.48.217 +37.148.48.234 +37.148.48.239 +37.148.48.241 +37.148.48.242 +37.148.48.244 +37.148.48.252 +37.148.49.17 +37.148.49.19 +37.148.49.26 +37.148.49.40 +37.148.49.41 +37.148.49.43 +37.148.49.44 +37.148.49.45 +37.148.49.70 +37.148.49.71 +37.148.49.74 +37.148.49.80 +37.148.49.93 +37.148.49.120 +37.148.49.130 +37.148.49.133 +37.148.49.134 +37.148.49.137 +37.148.49.141 +37.148.49.142 +37.148.49.143 +37.148.49.153 +37.148.49.155 +37.148.49.162 +37.148.49.163 +37.148.49.165 +37.148.49.169 +37.148.49.171 +37.148.49.173 +37.148.49.175 +37.148.49.177 +37.148.49.178 +37.148.49.182 +37.148.49.183 +37.148.49.184 +37.148.49.193 +37.148.49.236 +37.148.50.9 +37.148.50.11 +37.148.50.18 +37.148.50.29 +37.148.50.30 +37.148.50.39 +37.148.50.41 +37.148.50.58 +37.148.50.67 +37.148.50.71 +37.148.50.82 +37.148.50.91 +37.148.50.108 +37.148.50.116 +37.148.50.124 +37.148.50.128 +37.148.50.134 +37.148.50.152 +37.148.50.157 +37.148.50.158 +37.148.50.160 +37.148.50.163 +37.148.50.170 +37.148.50.174 +37.148.50.175 +37.148.50.220 +37.148.50.223 +37.148.50.230 +37.148.50.241 +37.148.50.248 +37.148.51.6 +37.148.51.15 +37.148.51.28 +37.148.51.41 +37.148.51.51 +37.148.51.58 +37.148.51.59 +37.148.51.61 +37.148.51.79 +37.148.51.114 +37.148.51.118 +37.148.51.131 +37.148.51.134 +37.148.51.135 +37.148.51.137 +37.148.51.142 +37.148.51.144 +37.148.51.147 +37.148.51.167 +37.148.51.168 +37.148.51.200 +37.148.51.201 +37.148.51.222 +37.148.51.226 +37.148.56.4 +37.148.56.10 +37.148.56.26 +37.148.56.29 +37.148.56.48 +37.148.56.81 +37.148.56.84 +37.148.56.110 +37.148.56.125 +37.148.56.155 +37.148.56.157 +37.148.56.160 +37.148.56.163 +37.148.56.165 +37.148.56.172 +37.148.56.173 +37.148.56.179 +37.148.56.191 +37.148.56.206 +37.148.56.211 +37.148.56.232 +37.148.57.4 +37.148.57.5 +37.148.57.8 +37.148.57.15 +37.148.57.18 +37.148.57.21 +37.148.57.27 +37.148.57.35 +37.148.57.36 +37.148.57.37 +37.148.57.39 +37.148.57.67 +37.148.57.71 +37.148.57.74 +37.148.57.82 +37.148.57.88 +37.148.57.98 +37.148.57.102 +37.148.57.108 +37.148.57.117 +37.148.57.119 +37.148.57.122 +37.148.57.137 +37.148.57.144 +37.148.57.171 +37.148.57.186 +37.148.57.187 +37.148.57.194 +37.148.57.198 +37.148.57.208 +37.148.57.216 +37.148.57.222 +37.148.57.236 +37.148.57.250 +37.148.58.53 +37.148.58.63 +37.148.58.89 +37.148.58.102 +37.148.58.107 +37.148.58.121 +37.148.58.138 +37.148.58.157 +37.148.58.159 +37.148.58.177 +37.148.58.190 +37.148.58.191 +37.148.58.231 +37.148.58.233 +37.148.58.235 +37.148.58.237 +37.148.59.1 +37.148.59.5 +37.148.59.10 +37.148.59.29 +37.148.59.31 +37.148.59.32 +37.148.59.53 +37.148.59.57 +37.148.59.101 +37.148.59.109 +37.148.59.169 +37.148.59.174 +37.148.59.180 +37.148.59.188 +37.148.59.189 +37.148.59.195 +37.148.59.229 +37.148.59.231 +37.148.59.235 +37.148.59.252 +37.148.60.12 +37.148.60.22 +37.148.60.55 +37.148.60.64 +37.148.60.69 +37.148.60.78 +37.148.60.81 +37.148.60.89 +37.148.60.98 +37.148.60.100 +37.148.60.101 +37.148.60.109 +37.148.60.114 +37.148.60.126 +37.148.60.132 +37.148.60.140 +37.148.60.143 +37.148.60.155 +37.148.60.178 +37.148.60.190 +37.148.60.193 +37.148.60.197 +37.148.60.198 +37.148.60.249 +37.148.61.29 +37.148.61.30 +37.148.61.37 +37.148.61.60 +37.148.61.85 +37.148.61.89 +37.148.61.97 +37.148.61.109 +37.148.61.149 +37.148.61.151 +37.148.61.168 +37.148.61.173 +37.148.61.201 +37.148.61.204 +37.148.61.213 +37.148.61.215 +37.148.61.217 +37.148.61.218 +37.148.61.234 +37.148.61.251 +37.148.62.23 +37.148.62.37 +37.148.62.100 +37.148.62.105 +37.148.62.118 +37.148.62.119 +37.148.62.141 +37.148.62.144 +37.148.62.153 +37.148.62.188 +37.148.62.195 +37.148.62.216 +37.148.62.240 +37.148.62.252 +37.148.63.45 +37.148.63.55 +37.148.63.60 +37.148.63.66 +37.148.63.79 +37.148.63.93 +37.148.63.95 +37.148.63.96 +37.148.63.97 +37.148.63.109 +37.148.63.117 +37.148.63.129 +37.148.63.137 +37.148.63.140 +37.148.63.161 +37.148.63.171 +37.148.63.178 +37.148.63.186 +37.148.63.193 +37.148.63.195 +37.148.63.197 +37.148.63.199 +37.148.63.217 +37.148.63.218 +37.148.63.250 +37.148.80.18 +37.148.80.35 +37.148.80.56 +37.148.80.78 +37.148.80.82 +37.148.80.88 +37.148.80.90 +37.148.80.95 +37.148.80.109 +37.148.80.116 +37.148.80.123 +37.148.80.146 +37.148.80.152 +37.148.80.157 +37.148.80.160 +37.148.80.161 +37.148.80.166 +37.148.80.172 +37.148.80.183 +37.148.80.188 +37.148.80.213 +37.148.80.221 +37.148.80.244 +37.148.81.0 +37.148.81.15 +37.148.81.47 +37.148.81.70 +37.148.81.80 +37.148.81.90 +37.148.81.92 +37.148.81.102 +37.148.81.118 +37.148.81.151 +37.148.81.153 +37.148.81.170 +37.148.81.173 +37.148.81.180 +37.148.81.194 +37.148.81.220 +37.148.81.224 +37.148.81.233 +37.148.81.234 +37.148.81.245 +37.148.82.9 +37.148.82.13 +37.148.82.28 +37.148.82.36 +37.148.82.48 +37.148.82.49 +37.148.82.56 +37.148.82.63 +37.148.82.69 +37.148.82.75 +37.148.82.88 +37.148.82.112 +37.148.82.113 +37.148.82.124 +37.148.82.134 +37.148.82.139 +37.148.82.152 +37.148.82.159 +37.148.82.161 +37.148.82.166 +37.148.82.170 +37.148.82.174 +37.148.82.184 +37.148.82.193 +37.148.82.212 +37.148.82.213 +37.148.82.223 +37.148.82.226 +37.148.82.245 +37.148.83.2 +37.148.83.5 +37.148.83.9 +37.148.83.25 +37.148.83.28 +37.148.83.36 +37.148.83.37 +37.148.83.46 +37.148.83.62 +37.148.83.68 +37.148.83.76 +37.148.83.141 +37.148.83.169 +37.148.83.179 +37.148.83.194 +37.148.83.221 +37.148.83.223 +37.148.83.230 +37.148.83.252 +37.148.83.255 +37.148.84.4 +37.148.84.10 +37.148.84.19 +37.148.84.44 +37.148.84.46 +37.148.84.48 +37.148.84.60 +37.148.84.62 +37.148.84.64 +37.148.84.66 +37.148.84.80 +37.148.84.115 +37.148.84.155 +37.148.84.161 +37.148.84.162 +37.148.84.163 +37.148.84.165 +37.148.84.188 +37.148.84.195 +37.148.84.227 +37.148.84.238 +37.148.84.240 +37.148.84.242 +37.148.84.243 +37.148.84.247 +37.148.85.19 +37.148.85.21 +37.148.85.24 +37.148.85.57 +37.148.85.62 +37.148.85.78 +37.148.85.83 +37.148.85.90 +37.148.85.94 +37.148.85.95 +37.148.85.102 +37.148.85.105 +37.148.85.115 +37.148.85.125 +37.148.85.131 +37.148.85.132 +37.148.85.141 +37.148.85.159 +37.148.85.175 +37.148.85.209 +37.148.85.211 +37.148.85.212 +37.148.85.218 +37.148.85.223 +37.148.85.243 +37.148.86.26 +37.148.86.42 +37.148.86.48 +37.148.86.55 +37.148.86.57 +37.148.86.85 +37.148.86.88 +37.148.86.94 +37.148.86.102 +37.148.86.110 +37.148.86.129 +37.148.86.139 +37.148.86.144 +37.148.86.149 +37.148.86.170 +37.148.86.209 +37.148.86.213 +37.148.86.243 +37.148.86.249 +37.148.87.2 +37.148.87.19 +37.148.87.25 +37.148.87.26 +37.148.87.29 +37.148.87.42 +37.148.87.44 +37.148.87.49 +37.148.87.65 +37.148.87.74 +37.148.87.76 +37.148.87.78 +37.148.87.83 +37.148.87.88 +37.148.87.90 +37.148.87.91 +37.148.87.92 +37.148.87.100 +37.148.87.110 +37.148.87.117 +37.148.87.118 +37.148.87.140 +37.148.87.144 +37.148.87.150 +37.148.87.161 +37.148.87.169 +37.148.87.173 +37.148.87.181 +37.148.87.182 +37.148.87.184 +37.148.87.192 +37.148.87.198 +37.148.87.214 +37.148.87.219 +37.148.87.223 +37.148.87.229 +37.148.87.230 +37.148.87.236 +37.148.87.248 +37.148.87.250 +37.148.87.251 +37.148.87.253 +37.148.90.12 +37.148.90.13 +37.148.90.18 +37.148.90.22 +37.148.90.34 +37.148.90.47 +37.148.90.51 +37.148.90.56 +37.148.90.66 +37.148.90.84 +37.148.90.93 +37.148.90.102 +37.148.90.118 +37.148.90.119 +37.148.90.124 +37.148.90.132 +37.148.90.176 +37.148.90.223 +37.148.90.240 +37.152.160.42 +37.152.161.45 +37.152.163.14 +37.152.163.69 +37.152.163.132 +37.152.163.145 +37.152.163.148 +37.152.163.160 +37.152.163.211 +37.152.163.216 +37.152.163.226 +37.152.163.227 +37.152.163.253 +37.152.164.17 +37.152.165.37 +37.152.165.136 +37.152.165.139 +37.152.165.151 +37.152.165.156 +37.152.165.197 +37.152.165.200 +37.152.166.10 +37.152.166.18 +37.152.166.36 +37.152.166.58 +37.152.166.68 +37.152.166.84 +37.152.166.88 +37.152.166.98 +37.152.166.102 +37.152.166.121 +37.152.166.139 +37.152.166.142 +37.152.166.144 +37.152.166.148 +37.152.166.151 +37.152.166.156 +37.152.166.158 +37.152.167.92 +37.152.167.142 +37.152.171.115 +37.152.172.104 +37.152.173.126 +37.152.177.179 +37.152.177.200 +37.152.178.112 +37.152.178.130 +37.152.179.40 +37.152.179.135 +37.152.181.83 +37.152.182.77 +37.152.188.155 +37.152.189.124 +37.152.190.29 +37.152.190.69 +37.152.190.238 +37.156.8.43 +37.156.8.147 +37.156.8.165 +37.156.8.197 +37.156.8.240 +37.156.9.26 +37.156.9.42 +37.156.9.67 +37.156.9.99 +37.156.9.152 +37.156.9.184 +37.156.9.192 +37.156.10.32 +37.156.10.49 +37.156.10.60 +37.156.10.71 +37.156.11.6 +37.156.11.10 +37.156.11.24 +37.156.11.31 +37.156.11.74 +37.156.11.80 +37.156.11.138 +37.156.11.142 +37.156.11.167 +37.156.11.175 +37.156.11.179 +37.156.11.196 +37.156.11.229 +37.156.11.248 +37.156.12.3 +37.156.12.4 +37.156.12.65 +37.156.12.94 +37.156.12.147 +37.156.12.166 +37.156.12.169 +37.156.12.209 +37.156.12.231 +37.156.12.233 +37.156.12.241 +37.156.12.251 +37.156.13.11 +37.156.13.23 +37.156.13.151 +37.156.13.152 +37.156.13.163 +37.156.13.237 +37.156.14.64 +37.156.14.97 +37.156.14.206 +37.156.14.209 +37.156.14.215 +37.156.14.226 +37.156.14.248 +37.156.15.20 +37.156.15.131 +37.156.15.149 +37.156.15.174 +37.156.15.198 +37.156.15.254 +37.156.16.202 +37.156.16.241 +37.156.17.102 +37.156.17.179 +37.156.17.246 +37.156.18.149 +37.156.19.32 +37.156.19.54 +37.156.19.84 +37.156.20.60 +37.156.20.92 +37.156.20.185 +37.156.20.235 +37.156.20.241 +37.156.21.15 +37.156.21.216 +37.156.21.225 +37.156.22.51 +37.156.23.23 +37.156.23.35 +37.156.23.39 +37.156.23.80 +37.156.23.105 +37.156.24.204 +37.156.25.28 +37.156.26.223 +37.156.27.77 +37.156.115.84 +37.156.144.66 +37.156.144.172 +37.156.144.174 +37.156.145.85 +37.156.145.195 +37.156.145.234 +37.156.146.2 +37.156.146.3 +37.156.146.4 +37.156.146.5 +37.156.146.46 +37.156.146.115 +37.156.146.180 +37.156.147.34 +37.156.147.36 +37.156.147.38 +37.156.147.213 +37.156.165.69 +37.156.165.198 +37.156.248.43 +37.156.248.193 +37.156.249.213 +37.156.249.237 +37.191.76.48 +37.191.76.49 +37.191.76.50 +37.191.76.51 +37.191.76.52 +37.191.76.53 +37.191.76.54 +37.191.76.64 +37.191.76.177 +37.191.77.102 +37.191.77.106 +37.191.77.202 +37.191.78.210 +37.191.78.211 +37.191.78.212 +37.191.78.215 +37.191.79.18 +37.191.79.55 +37.191.79.252 +37.191.84.46 +37.191.84.170 +37.191.84.222 +37.191.85.58 +37.191.92.225 +37.191.93.67 +37.191.93.126 +37.191.93.151 +37.191.94.66 +37.191.95.88 +37.191.95.181 +37.202.148.10 +37.202.148.28 +37.202.148.41 +37.202.148.69 +37.202.148.78 +37.202.148.89 +37.202.148.96 +37.202.148.101 +37.202.148.124 +37.202.148.126 +37.202.148.141 +37.202.148.165 +37.202.148.185 +37.202.148.190 +37.202.148.219 +37.202.148.231 +37.202.148.234 +37.202.148.235 +37.202.149.215 +37.202.149.224 +37.202.149.227 +37.202.149.234 +37.202.149.244 +37.202.149.252 +37.202.152.4 +37.202.152.33 +37.202.152.43 +37.202.152.46 +37.202.152.50 +37.202.152.84 +37.202.152.109 +37.202.152.112 +37.202.152.126 +37.202.152.133 +37.202.152.139 +37.202.152.149 +37.202.152.160 +37.202.152.165 +37.202.152.178 +37.202.152.182 +37.202.152.187 +37.202.152.189 +37.202.152.194 +37.202.152.203 +37.202.152.207 +37.202.152.213 +37.202.152.224 +37.202.152.226 +37.202.152.231 +37.202.152.250 +37.202.153.21 +37.202.153.22 +37.202.153.30 +37.202.153.61 +37.202.153.80 +37.202.153.83 +37.202.153.89 +37.202.153.94 +37.202.153.97 +37.202.153.103 +37.202.153.105 +37.202.153.123 +37.202.153.127 +37.202.153.138 +37.202.153.140 +37.202.153.145 +37.202.153.148 +37.202.153.197 +37.202.153.202 +37.202.153.204 +37.202.153.212 +37.202.153.219 +37.202.153.221 +37.202.153.232 +37.202.153.235 +37.202.153.242 +37.202.153.255 +37.202.154.5 +37.202.154.8 +37.202.154.26 +37.202.154.34 +37.202.154.51 +37.202.154.76 +37.202.154.83 +37.202.154.90 +37.202.154.132 +37.202.154.134 +37.202.154.158 +37.202.154.163 +37.202.154.181 +37.202.154.184 +37.202.154.195 +37.202.154.218 +37.202.154.219 +37.202.154.228 +37.202.154.230 +37.202.154.234 +37.202.154.246 +37.202.154.252 +37.202.154.253 +37.202.155.6 +37.202.155.19 +37.202.155.36 +37.202.155.40 +37.202.155.45 +37.202.155.50 +37.202.155.51 +37.202.155.69 +37.202.155.75 +37.202.155.82 +37.202.155.86 +37.202.155.88 +37.202.155.97 +37.202.155.108 +37.202.155.131 +37.202.155.132 +37.202.155.174 +37.202.155.188 +37.202.155.192 +37.202.155.218 +37.202.155.225 +37.202.155.231 +37.202.155.232 +37.202.155.242 +37.202.155.246 +37.202.156.0 +37.202.156.5 +37.202.156.10 +37.202.156.20 +37.202.156.26 +37.202.156.34 +37.202.156.53 +37.202.156.61 +37.202.156.72 +37.202.156.79 +37.202.156.82 +37.202.156.87 +37.202.156.88 +37.202.156.115 +37.202.156.132 +37.202.156.134 +37.202.156.135 +37.202.156.143 +37.202.156.204 +37.202.156.218 +37.202.156.240 +37.202.156.243 +37.202.156.246 +37.202.156.251 +37.202.157.3 +37.202.157.12 +37.202.157.40 +37.202.157.42 +37.202.157.86 +37.202.157.91 +37.202.157.96 +37.202.157.110 +37.202.157.119 +37.202.157.121 +37.202.157.126 +37.202.157.145 +37.202.157.175 +37.202.157.189 +37.202.157.197 +37.202.157.207 +37.202.157.209 +37.202.157.234 +37.202.157.237 +37.202.158.15 +37.202.158.31 +37.202.158.42 +37.202.158.59 +37.202.158.71 +37.202.158.79 +37.202.158.110 +37.202.158.154 +37.202.158.186 +37.202.158.198 +37.202.158.205 +37.202.158.238 +37.202.158.240 +37.202.158.249 +37.202.158.254 +37.202.159.1 +37.202.159.20 +37.202.159.58 +37.202.159.72 +37.202.159.78 +37.202.159.90 +37.202.159.101 +37.202.159.126 +37.202.159.135 +37.202.159.136 +37.202.159.147 +37.202.159.171 +37.202.159.184 +37.202.159.206 +37.202.159.210 +37.202.159.224 +37.202.159.243 +37.202.159.246 +37.202.159.253 +37.202.168.10 +37.202.168.28 +37.202.168.55 +37.202.168.61 +37.202.168.64 +37.202.168.80 +37.202.168.96 +37.202.168.101 +37.202.168.112 +37.202.168.119 +37.202.168.120 +37.202.168.121 +37.202.168.122 +37.202.168.135 +37.202.168.137 +37.202.168.176 +37.202.168.177 +37.202.168.228 +37.202.169.3 +37.202.169.8 +37.202.169.59 +37.202.169.65 +37.202.169.67 +37.202.169.89 +37.202.169.104 +37.202.169.145 +37.202.169.146 +37.202.169.168 +37.202.169.179 +37.202.169.189 +37.202.169.197 +37.202.169.222 +37.202.169.224 +37.202.169.242 +37.202.170.3 +37.202.170.7 +37.202.170.49 +37.202.170.60 +37.202.170.80 +37.202.170.88 +37.202.170.96 +37.202.170.115 +37.202.170.145 +37.202.170.209 +37.202.170.252 +37.202.171.4 +37.202.171.21 +37.202.171.22 +37.202.171.37 +37.202.171.49 +37.202.171.82 +37.202.171.100 +37.202.171.106 +37.202.171.131 +37.202.171.145 +37.202.171.146 +37.202.171.152 +37.202.171.157 +37.202.171.158 +37.202.171.171 +37.202.171.180 +37.202.171.188 +37.202.171.198 +37.202.171.203 +37.202.171.208 +37.202.171.230 +37.202.171.251 +37.202.171.253 +37.202.171.254 +37.202.171.255 +37.202.172.2 +37.202.172.6 +37.202.172.10 +37.202.172.11 +37.202.172.15 +37.202.172.16 +37.202.172.18 +37.202.172.36 +37.202.172.55 +37.202.172.70 +37.202.172.76 +37.202.172.81 +37.202.172.103 +37.202.172.115 +37.202.172.132 +37.202.172.138 +37.202.172.171 +37.202.172.187 +37.202.172.202 +37.202.172.222 +37.202.172.246 +37.202.173.4 +37.202.173.14 +37.202.173.23 +37.202.173.44 +37.202.173.57 +37.202.173.64 +37.202.173.72 +37.202.173.88 +37.202.173.91 +37.202.173.93 +37.202.173.112 +37.202.173.148 +37.202.173.155 +37.202.173.179 +37.202.173.183 +37.202.173.188 +37.202.173.205 +37.202.173.228 +37.202.173.240 +37.202.173.243 +37.202.173.244 +37.202.174.3 +37.202.174.10 +37.202.174.17 +37.202.174.23 +37.202.174.33 +37.202.174.50 +37.202.174.53 +37.202.174.62 +37.202.174.71 +37.202.174.91 +37.202.174.101 +37.202.174.104 +37.202.174.105 +37.202.174.115 +37.202.174.121 +37.202.174.128 +37.202.174.161 +37.202.174.181 +37.202.174.184 +37.202.174.212 +37.202.174.231 +37.202.174.232 +37.202.175.34 +37.202.175.35 +37.202.175.37 +37.202.175.39 +37.202.175.52 +37.202.175.59 +37.202.175.68 +37.202.175.69 +37.202.175.87 +37.202.175.88 +37.202.175.93 +37.202.175.94 +37.202.175.98 +37.202.175.115 +37.202.175.116 +37.202.175.120 +37.202.175.129 +37.202.175.173 +37.202.175.175 +37.202.175.185 +37.202.175.205 +37.202.175.207 +37.202.175.208 +37.202.175.224 +37.202.175.232 +37.202.175.242 +37.202.176.4 +37.202.176.10 +37.202.176.13 +37.202.176.26 +37.202.176.34 +37.202.176.49 +37.202.176.51 +37.202.176.59 +37.202.176.64 +37.202.176.66 +37.202.176.70 +37.202.176.76 +37.202.176.87 +37.202.176.95 +37.202.176.97 +37.202.176.104 +37.202.176.111 +37.202.176.119 +37.202.176.132 +37.202.176.143 +37.202.176.154 +37.202.176.168 +37.202.176.171 +37.202.176.179 +37.202.176.186 +37.202.176.189 +37.202.176.199 +37.202.176.200 +37.202.176.201 +37.202.176.205 +37.202.176.226 +37.202.176.230 +37.202.177.6 +37.202.177.10 +37.202.177.11 +37.202.177.26 +37.202.177.29 +37.202.177.30 +37.202.177.44 +37.202.177.46 +37.202.177.51 +37.202.177.58 +37.202.177.96 +37.202.177.107 +37.202.177.112 +37.202.177.117 +37.202.177.119 +37.202.177.137 +37.202.177.152 +37.202.177.174 +37.202.177.192 +37.202.177.193 +37.202.177.211 +37.202.177.217 +37.202.177.219 +37.202.177.224 +37.202.177.242 +37.202.178.4 +37.202.178.8 +37.202.178.12 +37.202.178.13 +37.202.178.24 +37.202.178.60 +37.202.178.76 +37.202.178.82 +37.202.178.106 +37.202.178.109 +37.202.178.123 +37.202.178.128 +37.202.178.136 +37.202.178.143 +37.202.178.178 +37.202.178.182 +37.202.178.198 +37.202.178.240 +37.202.178.242 +37.202.178.244 +37.202.178.245 +37.202.179.2 +37.202.179.19 +37.202.179.20 +37.202.179.21 +37.202.179.28 +37.202.179.32 +37.202.179.47 +37.202.179.53 +37.202.179.64 +37.202.179.67 +37.202.179.71 +37.202.179.80 +37.202.179.92 +37.202.179.96 +37.202.179.110 +37.202.179.188 +37.202.179.191 +37.202.179.193 +37.202.179.197 +37.202.179.203 +37.202.179.206 +37.202.179.224 +37.202.179.229 +37.202.179.230 +37.202.179.238 +37.202.179.239 +37.202.180.2 +37.202.180.43 +37.202.180.51 +37.202.180.72 +37.202.180.79 +37.202.180.83 +37.202.180.94 +37.202.180.112 +37.202.180.121 +37.202.180.133 +37.202.180.135 +37.202.180.136 +37.202.180.149 +37.202.180.176 +37.202.180.189 +37.202.180.196 +37.202.180.210 +37.202.180.219 +37.202.180.225 +37.202.180.228 +37.202.180.238 +37.202.181.5 +37.202.181.7 +37.202.181.20 +37.202.181.22 +37.202.181.26 +37.202.181.37 +37.202.181.42 +37.202.181.54 +37.202.181.62 +37.202.181.67 +37.202.181.77 +37.202.181.111 +37.202.181.112 +37.202.181.120 +37.202.181.125 +37.202.181.165 +37.202.181.192 +37.202.181.212 +37.202.181.219 +37.202.181.223 +37.202.181.231 +37.202.181.233 +37.202.181.243 +37.202.181.253 +37.202.182.41 +37.202.182.46 +37.202.182.48 +37.202.182.59 +37.202.182.66 +37.202.182.72 +37.202.182.83 +37.202.182.87 +37.202.182.90 +37.202.182.130 +37.202.182.153 +37.202.182.167 +37.202.182.189 +37.202.182.203 +37.202.184.36 +37.202.184.39 +37.202.184.42 +37.202.184.50 +37.202.184.82 +37.202.184.98 +37.202.184.130 +37.202.184.131 +37.202.184.146 +37.202.184.147 +37.202.184.154 +37.202.184.162 +37.202.184.189 +37.202.184.218 +37.202.184.251 +37.202.184.252 +37.202.184.253 +37.202.184.254 +37.202.185.1 +37.202.185.22 +37.202.185.26 +37.202.185.31 +37.202.185.35 +37.202.185.36 +37.202.185.51 +37.202.185.80 +37.202.185.91 +37.202.185.125 +37.202.185.142 +37.202.185.156 +37.202.185.170 +37.202.185.196 +37.202.185.241 +37.202.185.253 +37.202.186.0 +37.202.186.2 +37.202.186.5 +37.202.186.17 +37.202.186.29 +37.202.186.33 +37.202.186.40 +37.202.186.51 +37.202.186.52 +37.202.186.53 +37.202.186.58 +37.202.186.64 +37.202.186.67 +37.202.186.78 +37.202.186.114 +37.202.186.117 +37.202.186.120 +37.202.186.140 +37.202.186.151 +37.202.186.155 +37.202.186.181 +37.202.186.190 +37.202.186.201 +37.202.186.209 +37.202.186.211 +37.202.186.223 +37.202.186.247 +37.202.186.252 +37.202.187.43 +37.202.187.48 +37.202.187.67 +37.202.187.69 +37.202.187.72 +37.202.187.79 +37.202.187.80 +37.202.187.82 +37.202.187.120 +37.202.187.132 +37.202.187.135 +37.202.187.137 +37.202.187.140 +37.202.187.160 +37.202.187.181 +37.202.187.182 +37.202.187.185 +37.202.187.204 +37.202.187.210 +37.202.187.211 +37.202.187.220 +37.202.187.227 +37.202.188.10 +37.202.188.13 +37.202.188.15 +37.202.188.27 +37.202.188.40 +37.202.188.43 +37.202.188.50 +37.202.188.54 +37.202.188.59 +37.202.188.90 +37.202.188.91 +37.202.188.110 +37.202.188.144 +37.202.188.149 +37.202.188.158 +37.202.188.165 +37.202.188.177 +37.202.188.179 +37.202.188.185 +37.202.188.195 +37.202.188.198 +37.202.188.235 +37.202.188.238 +37.202.188.240 +37.202.188.255 +37.202.189.9 +37.202.189.25 +37.202.189.39 +37.202.189.44 +37.202.189.47 +37.202.189.76 +37.202.189.94 +37.202.189.99 +37.202.189.100 +37.202.189.103 +37.202.189.112 +37.202.189.134 +37.202.189.138 +37.202.189.147 +37.202.189.149 +37.202.189.158 +37.202.189.159 +37.202.189.189 +37.202.189.200 +37.202.189.214 +37.202.189.235 +37.202.189.239 +37.202.190.6 +37.202.190.8 +37.202.190.16 +37.202.190.61 +37.202.190.80 +37.202.190.82 +37.202.190.85 +37.202.190.88 +37.202.190.92 +37.202.190.103 +37.202.190.118 +37.202.190.137 +37.202.190.147 +37.202.190.153 +37.202.190.162 +37.202.190.166 +37.202.190.172 +37.202.190.182 +37.202.190.190 +37.202.190.194 +37.202.190.200 +37.202.190.217 +37.202.190.219 +37.202.190.222 +37.202.190.226 +37.202.190.240 +37.202.190.241 +37.202.190.242 +37.202.190.244 +37.202.191.2 +37.202.191.4 +37.202.191.12 +37.202.191.17 +37.202.191.21 +37.202.191.58 +37.202.191.63 +37.202.191.78 +37.202.191.90 +37.202.191.106 +37.202.191.128 +37.202.191.155 +37.202.191.175 +37.202.191.185 +37.202.191.212 +37.202.191.228 +37.202.191.230 +37.202.191.232 +37.202.191.238 +37.202.191.240 +37.202.225.185 +37.202.228.171 +37.202.228.254 +37.202.229.103 +37.202.231.16 +37.202.231.59 +37.202.231.242 +37.202.237.194 +37.202.239.132 +37.228.136.214 +37.228.137.85 +37.228.137.88 +37.228.137.107 +37.228.137.108 +37.228.137.156 +37.228.137.157 +37.228.137.158 +37.228.137.254 +37.228.138.66 +37.228.139.27 +37.228.139.51 +37.228.139.61 +37.228.139.226 +37.254.0.1 +37.254.96.1 +37.255.64.1 +37.255.128.41 +37.255.133.26 +37.255.133.27 +37.255.133.28 +37.255.133.29 +37.255.134.66 +37.255.134.160 +37.255.134.194 +37.255.135.34 +37.255.135.35 +37.255.135.50 +37.255.135.66 +37.255.135.203 +37.255.149.136 +37.255.149.222 +37.255.173.83 +37.255.173.134 +37.255.177.65 +37.255.177.243 +37.255.186.150 +37.255.187.147 +37.255.192.12 +37.255.192.24 +37.255.192.96 +37.255.192.191 +37.255.192.227 +37.255.193.110 +37.255.193.115 +37.255.193.151 +37.255.193.159 +37.255.193.229 +37.255.193.241 +37.255.194.14 +37.255.194.19 +37.255.194.51 +37.255.194.85 +37.255.194.100 +37.255.194.108 +37.255.194.121 +37.255.194.139 +37.255.194.171 +37.255.194.198 +37.255.194.199 +37.255.194.213 +37.255.194.231 +37.255.195.7 +37.255.195.10 +37.255.195.96 +37.255.195.170 +37.255.195.194 +37.255.196.26 +37.255.196.90 +37.255.196.95 +37.255.196.125 +37.255.196.150 +37.255.196.199 +37.255.197.63 +37.255.197.190 +37.255.198.41 +37.255.198.110 +37.255.198.220 +37.255.198.231 +37.255.199.204 +37.255.199.248 +37.255.200.103 +37.255.200.127 +37.255.200.161 +37.255.200.204 +37.255.200.254 +37.255.201.105 +37.255.201.171 +37.255.201.250 +37.255.202.94 +37.255.202.123 +37.255.202.124 +37.255.202.160 +37.255.202.219 +37.255.202.244 +37.255.202.251 +37.255.203.94 +37.255.203.130 +37.255.203.155 +37.255.203.235 +37.255.203.250 +37.255.204.1 +37.255.204.238 +37.255.204.239 +37.255.205.22 +37.255.205.232 +37.255.206.150 +37.255.206.159 +37.255.206.180 +37.255.207.114 +37.255.207.164 +37.255.207.236 +37.255.208.10 +37.255.208.12 +37.255.208.43 +37.255.208.53 +37.255.209.0 +37.255.209.44 +37.255.209.123 +37.255.209.136 +37.255.209.149 +37.255.209.161 +37.255.209.212 +37.255.210.51 +37.255.210.55 +37.255.210.70 +37.255.210.92 +37.255.210.168 +37.255.211.26 +37.255.211.56 +37.255.211.106 +37.255.211.221 +37.255.211.245 +37.255.211.252 +37.255.212.30 +37.255.212.46 +37.255.213.36 +37.255.213.38 +37.255.213.51 +37.255.213.88 +37.255.213.150 +37.255.213.235 +37.255.214.119 +37.255.215.21 +37.255.215.185 +37.255.216.19 +37.255.216.22 +37.255.216.113 +37.255.216.160 +37.255.217.22 +37.255.217.41 +37.255.217.115 +37.255.217.164 +37.255.217.196 +37.255.218.116 +37.255.218.143 +37.255.218.170 +37.255.218.191 +37.255.219.17 +37.255.219.190 +37.255.220.48 +37.255.220.51 +37.255.220.59 +37.255.220.121 +37.255.220.162 +37.255.220.172 +37.255.220.208 +37.255.221.6 +37.255.221.16 +37.255.221.40 +37.255.221.62 +37.255.221.147 +37.255.221.163 +37.255.222.36 +37.255.222.39 +37.255.222.42 +37.255.222.124 +37.255.222.223 +37.255.222.232 +37.255.223.34 +37.255.223.38 +37.255.223.40 +37.255.223.51 +37.255.223.201 +37.255.223.235 +37.255.223.239 +37.255.224.3 +37.255.224.143 +37.255.225.38 +37.255.225.108 +37.255.228.106 +37.255.228.122 +37.255.228.165 +37.255.228.219 +37.255.229.35 +37.255.229.88 +37.255.229.99 +37.255.229.163 +37.255.229.168 +37.255.229.238 +37.255.230.64 +37.255.230.169 +37.255.230.246 +37.255.231.32 +37.255.231.128 +37.255.231.132 +37.255.231.172 +37.255.231.184 +37.255.231.207 +37.255.231.221 +37.255.232.10 +37.255.232.130 +37.255.232.194 +37.255.232.233 +37.255.232.255 +37.255.233.12 +37.255.233.128 +37.255.233.143 +37.255.233.226 +37.255.233.253 +37.255.234.15 +37.255.234.75 +37.255.234.132 +37.255.234.134 +37.255.234.180 +37.255.234.188 +37.255.235.111 +37.255.235.138 +37.255.236.5 +37.255.236.64 +37.255.236.97 +37.255.236.134 +37.255.236.149 +37.255.236.193 +37.255.236.209 +37.255.237.68 +37.255.237.69 +37.255.237.70 +37.255.237.116 +37.255.237.117 +37.255.237.118 +37.255.237.119 +37.255.237.132 +37.255.238.4 +37.255.238.12 +37.255.238.23 +37.255.238.142 +37.255.238.188 +37.255.238.253 +37.255.239.81 +37.255.239.99 +37.255.239.104 +37.255.239.171 +37.255.240.0 +37.255.240.23 +37.255.240.124 +37.255.240.217 +37.255.241.27 +37.255.241.102 +37.255.241.122 +37.255.241.129 +37.255.241.137 +37.255.241.140 +37.255.241.213 +37.255.242.91 +37.255.242.182 +37.255.242.186 +37.255.242.212 +37.255.243.44 +37.255.243.69 +37.255.243.169 +37.255.243.205 +37.255.243.218 +37.255.244.53 +37.255.244.254 +37.255.245.67 +37.255.246.156 +37.255.248.156 +37.255.248.202 +37.255.248.241 +37.255.254.18 +37.255.254.20 +37.255.254.254 +37.255.255.18 +45.8.160.13 +45.8.160.79 +45.8.160.82 +45.8.160.84 +45.8.160.94 +45.8.160.211 +45.8.160.232 +45.8.160.237 +45.8.160.253 +45.9.252.86 +45.9.253.10 +45.9.253.46 +45.9.253.53 +45.9.253.122 +45.9.253.169 +45.9.254.90 +45.11.185.253 +45.11.187.165 +45.11.187.173 +45.15.200.30 +45.65.59.201 +45.81.16.103 +45.81.17.18 +45.81.17.19 +45.81.17.20 +45.81.17.23 +45.81.17.24 +45.81.17.27 +45.81.17.28 +45.81.17.29 +45.81.17.30 +45.81.17.133 +45.81.17.135 +45.81.17.190 +45.81.18.107 +45.81.18.123 +45.81.19.10 +45.81.19.11 +45.81.19.13 +45.81.19.14 +45.81.19.15 +45.81.19.16 +45.81.19.19 +45.81.19.59 +45.81.19.132 +45.81.19.150 +45.81.19.235 +45.82.137.234 +45.82.138.48 +45.84.157.43 +45.84.157.200 +45.84.157.202 +45.84.157.203 +45.84.157.204 +45.84.157.209 +45.84.157.210 +45.84.157.211 +45.84.157.212 +45.84.157.213 +45.84.157.217 +45.84.157.218 +45.84.157.220 +45.86.4.11 +45.86.4.66 +45.86.4.67 +45.86.4.82 +45.86.4.100 +45.86.5.211 +45.86.6.25 +45.86.6.113 +45.86.87.148 +45.86.87.186 +45.86.87.227 +45.87.4.116 +45.87.4.151 +45.87.5.66 +45.87.5.148 +45.87.5.178 +45.87.6.9 +45.87.6.38 +45.87.6.45 +45.89.236.37 +45.89.236.181 +45.89.237.66 +45.89.239.214 +45.89.239.227 +45.90.72.166 +45.90.73.207 +45.90.74.109 +45.90.74.225 +45.92.93.236 +45.92.94.4 +45.92.94.20 +45.92.94.122 +45.92.95.59 +45.94.214.3 +45.94.214.163 +45.94.214.219 +45.94.214.240 +45.94.215.32 +45.94.255.186 +45.129.36.45 +45.129.36.181 +45.129.36.220 +45.129.36.221 +45.129.36.222 +45.129.37.123 +45.129.37.124 +45.129.37.145 +45.129.37.148 +45.129.37.153 +45.129.37.156 +45.129.37.157 +45.129.37.158 +45.129.37.185 +45.129.37.198 +45.129.37.199 +45.129.38.35 +45.129.38.51 +45.129.38.53 +45.129.38.56 +45.129.38.60 +45.129.39.5 +45.129.39.6 +45.129.39.9 +45.129.39.12 +45.129.39.13 +45.129.39.168 +45.132.172.9 +45.132.172.56 +45.132.172.84 +45.132.172.93 +45.132.172.100 +45.132.172.144 +45.132.172.161 +45.132.173.200 +45.132.174.138 +45.132.174.191 +45.132.175.4 +45.134.99.4 +45.135.241.79 +45.135.243.61 +45.135.243.70 +45.138.132.27 +45.138.133.205 +45.138.135.32 +45.138.135.39 +45.138.135.57 +45.138.135.224 +45.139.10.61 +45.139.10.81 +45.139.10.200 +45.139.11.250 +45.140.225.4 +45.140.225.6 +45.140.225.60 +45.140.225.62 +45.140.225.218 +45.140.226.9 +45.140.226.10 +45.140.226.11 +45.140.226.12 +45.140.226.25 +45.140.226.73 +45.140.226.226 +45.142.190.13 +45.142.190.24 +45.142.190.46 +45.142.190.139 +45.142.190.142 +45.142.191.3 +45.142.191.9 +45.142.191.11 +45.142.191.17 +45.142.191.19 +45.142.191.24 +45.142.191.33 +45.142.191.35 +45.142.191.38 +45.142.191.39 +45.144.18.7 +45.144.18.52 +45.144.19.70 +45.144.19.75 +45.144.19.76 +45.144.19.77 +45.144.19.78 +45.144.19.85 +45.147.76.252 +45.147.77.59 +45.147.77.88 +45.147.77.169 +45.147.77.170 +45.147.77.171 +45.147.77.195 +45.147.79.36 +45.147.79.37 +45.147.79.38 +45.147.79.39 +45.148.248.8 +45.148.249.62 +45.148.250.68 +45.149.76.12 +45.149.76.138 +45.149.76.183 +45.149.76.195 +45.149.77.114 +45.149.77.135 +45.149.77.186 +45.149.78.156 +45.149.78.226 +45.149.79.53 +45.156.184.16 +45.156.184.26 +45.156.184.32 +45.156.184.36 +45.156.184.125 +45.156.185.30 +45.156.185.59 +45.156.185.80 +45.156.185.121 +45.156.185.129 +45.156.185.130 +45.156.185.164 +45.156.185.187 +45.156.185.192 +45.156.185.201 +45.156.185.240 +45.156.185.254 +45.156.186.108 +45.156.186.230 +45.156.186.240 +45.156.187.106 +45.158.120.7 +45.159.112.10 +45.159.112.43 +45.159.112.87 +45.159.112.107 +45.159.112.143 +45.159.112.144 +45.159.112.196 +45.159.113.144 +45.159.113.161 +45.159.113.165 +45.159.113.172 +45.159.113.242 +45.159.113.244 +45.159.114.200 +45.159.114.201 +45.159.114.202 +45.159.114.236 +45.159.115.117 +45.159.115.119 +45.159.115.127 +45.159.115.129 +45.159.115.151 +45.159.149.40 +45.159.149.108 +45.159.149.158 +45.159.150.94 +45.159.150.117 +45.159.150.157 +45.159.150.165 +45.159.151.16 +45.159.151.32 +45.159.151.39 +45.159.151.128 +45.159.151.169 +45.159.197.86 +45.159.197.114 +45.159.198.246 +46.28.72.18 +46.28.72.19 +46.28.72.20 +46.28.72.21 +46.28.72.133 +46.28.74.130 +46.28.75.66 +46.28.75.67 +46.28.75.106 +46.28.75.107 +46.28.75.108 +46.28.75.109 +46.28.75.130 +46.28.76.101 +46.28.77.26 +46.28.77.38 +46.28.77.54 +46.28.77.194 +46.32.1.13 +46.32.1.75 +46.32.1.118 +46.32.1.145 +46.32.1.165 +46.32.1.171 +46.32.1.202 +46.32.1.250 +46.32.2.13 +46.32.4.82 +46.32.4.83 +46.32.4.85 +46.32.4.86 +46.32.4.90 +46.32.4.164 +46.32.21.14 +46.32.25.75 +46.32.25.76 +46.32.25.77 +46.32.25.78 +46.32.25.178 +46.32.28.17 +46.32.28.29 +46.32.28.40 +46.32.28.52 +46.32.28.68 +46.32.28.128 +46.32.28.141 +46.32.28.144 +46.32.28.209 +46.32.28.220 +46.32.28.238 +46.32.31.50 +46.34.96.20 +46.34.161.9 +46.34.161.41 +46.34.161.67 +46.34.161.68 +46.34.161.104 +46.34.161.110 +46.34.161.183 +46.34.161.186 +46.34.161.226 +46.34.161.227 +46.34.162.18 +46.34.162.19 +46.34.162.20 +46.34.162.22 +46.34.163.161 +46.34.164.10 +46.34.164.18 +46.34.164.94 +46.34.164.105 +46.34.164.110 +46.34.164.218 +46.34.164.238 +46.34.165.50 +46.34.165.134 +46.34.165.138 +46.34.165.186 +46.34.166.18 +46.34.166.91 +46.34.166.106 +46.34.166.114 +46.34.166.115 +46.34.166.186 +46.34.166.187 +46.34.166.235 +46.34.166.237 +46.34.167.14 +46.34.167.52 +46.34.167.162 +46.34.178.130 +46.36.96.30 +46.36.103.93 +46.36.107.56 +46.38.138.50 +46.38.138.72 +46.38.138.139 +46.38.138.151 +46.38.138.165 +46.38.143.163 +46.38.143.189 +46.38.147.88 +46.38.147.219 +46.38.147.220 +46.38.147.222 +46.38.149.4 +46.38.149.200 +46.38.149.201 +46.38.151.110 +46.51.37.37 +46.51.37.38 +46.51.37.70 +46.51.37.159 +46.51.37.181 +46.51.37.233 +46.51.37.246 +46.100.5.15 +46.100.5.59 +46.100.5.122 +46.100.5.134 +46.100.5.139 +46.100.5.143 +46.100.5.186 +46.100.5.221 +46.100.5.225 +46.100.6.13 +46.100.6.82 +46.100.6.132 +46.100.6.184 +46.100.6.198 +46.100.7.0 +46.100.7.18 +46.100.7.32 +46.100.7.60 +46.100.7.143 +46.100.7.190 +46.100.7.198 +46.100.7.234 +46.100.8.3 +46.100.8.84 +46.100.8.134 +46.100.8.180 +46.100.9.8 +46.100.9.100 +46.100.9.191 +46.100.9.199 +46.100.10.34 +46.100.10.77 +46.100.11.42 +46.100.11.70 +46.100.11.131 +46.100.11.138 +46.100.11.139 +46.100.11.171 +46.100.11.212 +46.100.11.242 +46.100.11.250 +46.100.12.23 +46.100.12.81 +46.100.12.99 +46.100.12.102 +46.100.12.112 +46.100.12.130 +46.100.12.141 +46.100.12.145 +46.100.12.175 +46.100.12.176 +46.100.12.208 +46.100.12.214 +46.100.13.12 +46.100.13.14 +46.100.13.16 +46.100.13.93 +46.100.13.145 +46.100.13.147 +46.100.13.148 +46.100.14.1 +46.100.14.69 +46.100.34.124 +46.100.40.59 +46.100.40.72 +46.100.40.93 +46.100.40.132 +46.100.40.175 +46.100.40.185 +46.100.40.187 +46.100.40.210 +46.100.40.230 +46.100.41.100 +46.100.41.120 +46.100.41.217 +46.100.41.254 +46.100.42.40 +46.100.42.49 +46.100.42.51 +46.100.42.103 +46.100.42.132 +46.100.42.160 +46.100.42.163 +46.100.42.188 +46.100.42.245 +46.100.43.11 +46.100.43.41 +46.100.43.154 +46.100.45.39 +46.100.45.52 +46.100.45.72 +46.100.45.195 +46.100.45.252 +46.100.46.8 +46.100.46.56 +46.100.46.107 +46.100.46.111 +46.100.46.113 +46.100.46.122 +46.100.46.126 +46.100.46.135 +46.100.46.167 +46.100.47.51 +46.100.47.95 +46.100.47.112 +46.100.47.122 +46.100.47.137 +46.100.47.202 +46.100.47.249 +46.100.47.250 +46.100.48.37 +46.100.48.63 +46.100.48.66 +46.100.48.120 +46.100.48.178 +46.100.48.181 +46.100.48.199 +46.100.48.235 +46.100.50.8 +46.100.50.33 +46.100.50.63 +46.100.50.106 +46.100.50.115 +46.100.50.118 +46.100.50.122 +46.100.50.155 +46.100.52.16 +46.100.52.40 +46.100.52.52 +46.100.52.112 +46.100.52.170 +46.100.52.192 +46.100.52.239 +46.100.52.247 +46.100.52.251 +46.100.53.35 +46.100.53.36 +46.100.53.85 +46.100.53.106 +46.100.53.115 +46.100.53.176 +46.100.53.185 +46.100.53.187 +46.100.53.212 +46.100.53.232 +46.100.54.1 +46.100.54.4 +46.100.54.23 +46.100.54.48 +46.100.54.53 +46.100.54.167 +46.100.54.191 +46.100.55.16 +46.100.55.18 +46.100.55.37 +46.100.55.54 +46.100.55.67 +46.100.55.72 +46.100.55.158 +46.100.55.172 +46.100.55.190 +46.100.55.193 +46.100.55.207 +46.100.55.232 +46.100.56.237 +46.100.57.62 +46.100.57.115 +46.100.57.124 +46.100.57.237 +46.100.58.16 +46.100.58.87 +46.100.58.141 +46.100.58.142 +46.100.59.119 +46.100.59.146 +46.100.59.148 +46.100.59.168 +46.100.60.24 +46.100.60.26 +46.100.60.28 +46.100.60.67 +46.100.60.73 +46.100.60.80 +46.100.60.122 +46.100.60.144 +46.100.60.177 +46.100.61.16 +46.100.61.17 +46.100.61.32 +46.100.61.33 +46.100.61.196 +46.100.62.21 +46.100.62.44 +46.100.62.56 +46.100.62.80 +46.100.62.110 +46.100.62.129 +46.100.62.169 +46.100.62.235 +46.100.63.28 +46.100.63.43 +46.100.63.112 +46.100.63.124 +46.100.63.220 +46.100.63.221 +46.100.63.244 +46.100.74.110 +46.100.84.9 +46.100.84.20 +46.100.84.47 +46.100.84.171 +46.100.84.197 +46.100.84.205 +46.100.84.227 +46.100.85.171 +46.100.85.218 +46.100.85.234 +46.100.90.82 +46.100.90.105 +46.100.90.116 +46.100.90.135 +46.100.90.138 +46.100.90.168 +46.100.91.17 +46.100.91.22 +46.100.91.27 +46.100.91.109 +46.100.91.134 +46.100.91.218 +46.100.92.15 +46.100.92.50 +46.100.92.84 +46.100.92.92 +46.100.92.113 +46.100.92.128 +46.100.92.130 +46.100.92.227 +46.100.93.214 +46.100.94.2 +46.100.94.7 +46.100.94.132 +46.100.94.147 +46.100.94.190 +46.100.95.29 +46.100.95.119 +46.100.95.162 +46.100.95.193 +46.100.97.51 +46.100.97.188 +46.100.99.31 +46.100.101.194 +46.100.101.226 +46.100.101.227 +46.100.104.22 +46.100.104.48 +46.100.104.162 +46.100.104.182 +46.100.104.183 +46.100.107.90 +46.100.107.213 +46.100.116.145 +46.100.132.34 +46.100.134.39 +46.100.164.15 +46.100.164.22 +46.100.164.27 +46.100.164.39 +46.100.164.48 +46.100.164.88 +46.100.164.112 +46.100.164.157 +46.100.164.165 +46.100.164.195 +46.100.164.205 +46.100.164.212 +46.100.164.216 +46.100.164.242 +46.100.165.4 +46.100.165.56 +46.100.165.140 +46.100.165.150 +46.100.165.151 +46.100.165.196 +46.100.165.249 +46.100.166.10 +46.100.166.18 +46.100.166.32 +46.100.166.43 +46.100.166.55 +46.100.166.153 +46.100.166.194 +46.100.166.204 +46.100.167.86 +46.100.167.99 +46.100.167.117 +46.100.167.185 +46.100.167.189 +46.100.168.101 +46.100.171.4 +46.100.177.18 +46.100.177.41 +46.100.177.184 +46.100.178.45 +46.100.179.39 +46.100.180.71 +46.100.181.18 +46.100.182.46 +46.100.182.95 +46.100.183.82 +46.100.183.91 +46.100.248.135 +46.100.248.199 +46.100.249.82 +46.100.249.89 +46.100.249.117 +46.100.249.190 +46.100.249.207 +46.100.250.5 +46.100.251.5 +46.100.251.156 +46.102.132.2 +46.102.132.3 +46.102.132.4 +46.102.132.31 +46.102.137.20 +46.102.137.22 +46.102.137.32 +46.102.139.18 +46.102.139.21 +46.102.139.85 +46.102.140.35 +46.102.143.141 +46.102.143.143 +46.102.184.135 +46.102.184.175 +46.102.187.30 +46.102.187.139 +46.148.32.5 +46.148.32.16 +46.148.32.17 +46.148.32.26 +46.148.32.70 +46.148.32.98 +46.148.32.112 +46.148.32.121 +46.148.32.125 +46.148.32.135 +46.148.32.158 +46.148.32.167 +46.148.32.176 +46.148.32.209 +46.148.32.235 +46.148.32.238 +46.148.32.239 +46.148.34.74 +46.148.34.226 +46.148.35.43 +46.148.35.57 +46.148.35.59 +46.148.35.210 +46.148.35.217 +46.148.36.10 +46.148.36.40 +46.148.36.55 +46.148.36.93 +46.148.36.100 +46.148.36.105 +46.148.36.106 +46.148.36.147 +46.148.36.150 +46.148.36.219 +46.148.36.241 +46.148.36.244 +46.148.38.154 +46.148.38.167 +46.148.39.25 +46.148.39.75 +46.148.39.104 +46.148.40.0 +46.148.40.2 +46.148.40.32 +46.148.40.40 +46.148.40.47 +46.148.41.5 +46.148.41.9 +46.148.42.210 +46.148.43.33 +46.148.43.206 +46.148.43.212 +46.148.44.48 +46.148.44.54 +46.148.44.106 +46.148.44.167 +46.148.44.221 +46.148.44.227 +46.148.45.5 +46.148.45.26 +46.148.45.28 +46.148.45.33 +46.148.45.42 +46.148.45.45 +46.148.45.80 +46.148.45.89 +46.148.45.114 +46.148.45.133 +46.148.45.145 +46.148.45.154 +46.148.45.155 +46.148.45.202 +46.148.45.208 +46.148.45.223 +46.148.45.233 +46.148.45.235 +46.148.45.242 +46.167.128.168 +46.167.131.114 +46.167.132.199 +46.167.133.40 +46.167.133.248 +46.167.134.160 +46.167.135.178 +46.167.136.14 +46.167.136.26 +46.167.136.112 +46.167.138.65 +46.167.138.205 +46.167.139.228 +46.167.140.98 +46.167.141.0 +46.167.141.21 +46.167.141.22 +46.167.141.138 +46.167.143.104 +46.167.146.138 +46.167.148.146 +46.167.149.72 +46.167.149.76 +46.167.150.29 +46.167.150.227 +46.167.152.192 +46.167.152.245 +46.167.154.99 +46.167.154.209 +46.167.155.0 +46.167.155.69 +46.167.156.137 +46.167.156.199 +46.167.157.162 +46.167.157.169 +46.167.157.205 +46.167.158.135 +46.167.159.0 +46.182.32.129 +46.209.5.114 +46.209.12.25 +46.209.15.187 +46.209.20.85 +46.209.20.207 +46.209.22.226 +46.209.22.227 +46.209.22.228 +46.209.22.229 +46.209.27.3 +46.209.28.27 +46.209.30.11 +46.209.30.12 +46.209.30.107 +46.209.30.108 +46.209.45.246 +46.209.48.5 +46.209.48.82 +46.209.48.114 +46.209.48.115 +46.209.48.116 +46.209.48.118 +46.209.48.194 +46.209.48.213 +46.209.49.254 +46.209.58.194 +46.209.58.195 +46.209.61.98 +46.209.61.99 +46.209.63.218 +46.209.70.226 +46.209.73.54 +46.209.74.198 +46.209.79.58 +46.209.79.59 +46.209.79.60 +46.209.79.61 +46.209.79.62 +46.209.92.162 +46.209.92.163 +46.209.92.164 +46.209.93.85 +46.209.94.14 +46.209.95.102 +46.209.100.114 +46.209.103.118 +46.209.115.30 +46.209.115.74 +46.209.118.178 +46.209.118.180 +46.209.118.181 +46.209.118.182 +46.209.122.194 +46.209.122.196 +46.209.122.226 +46.209.140.170 +46.209.140.172 +46.209.142.2 +46.209.142.4 +46.209.142.51 +46.209.142.53 +46.209.142.82 +46.209.142.83 +46.209.142.84 +46.209.142.85 +46.209.148.202 +46.209.148.203 +46.209.148.204 +46.209.148.205 +46.209.153.225 +46.209.153.226 +46.209.153.228 +46.209.153.229 +46.209.153.230 +46.209.153.233 +46.209.153.234 +46.209.153.236 +46.209.153.238 +46.209.153.240 +46.209.153.243 +46.209.153.246 +46.209.153.247 +46.209.153.248 +46.209.153.249 +46.209.153.250 +46.209.156.131 +46.209.157.18 +46.209.157.22 +46.209.181.202 +46.209.195.18 +46.209.195.19 +46.209.195.20 +46.209.195.21 +46.209.199.86 +46.209.201.58 +46.209.203.141 +46.209.203.143 +46.209.204.142 +46.209.206.62 +46.209.206.178 +46.209.207.97 +46.209.207.98 +46.209.207.146 +46.209.207.147 +46.209.207.148 +46.209.207.149 +46.209.207.150 +46.209.207.151 +46.209.207.152 +46.209.207.155 +46.209.207.156 +46.209.207.158 +46.209.211.50 +46.209.221.66 +46.209.221.68 +46.209.221.69 +46.209.221.70 +46.209.221.71 +46.209.221.72 +46.209.221.73 +46.209.221.75 +46.209.221.76 +46.209.221.77 +46.209.221.78 +46.209.221.82 +46.209.221.84 +46.209.221.85 +46.209.221.86 +46.209.221.87 +46.209.221.88 +46.209.221.89 +46.209.221.90 +46.209.221.91 +46.209.221.93 +46.209.222.123 +46.209.232.146 +46.209.239.30 +46.209.240.90 +46.209.240.226 +46.209.240.227 +46.209.240.228 +46.209.241.67 +46.209.241.81 +46.209.241.82 +46.209.241.155 +46.209.241.226 +46.209.241.227 +46.209.241.229 +46.209.241.250 +46.209.247.155 +46.209.251.94 +46.235.76.81 +46.235.76.225 +46.235.77.15 +46.235.77.80 +46.235.77.110 +46.235.77.118 +46.235.77.131 +46.235.77.153 +46.235.77.185 +46.235.77.251 +46.245.0.109 +46.245.0.128 +46.245.0.138 +46.245.0.167 +46.245.1.65 +46.245.1.104 +46.245.2.47 +46.245.2.161 +46.245.2.253 +46.245.3.30 +46.245.4.85 +46.245.5.63 +46.245.5.75 +46.245.5.202 +46.245.5.236 +46.245.6.215 +46.245.7.102 +46.245.7.223 +46.245.8.66 +46.245.10.13 +46.245.12.183 +46.245.12.187 +46.245.12.222 +46.245.12.241 +46.245.17.101 +46.245.19.79 +46.245.25.72 +46.245.30.51 +46.245.32.26 +46.245.32.29 +46.245.32.40 +46.245.37.66 +46.245.38.42 +46.245.38.99 +46.245.38.107 +46.245.38.119 +46.245.38.206 +46.245.38.234 +46.245.39.63 +46.245.39.101 +46.245.39.102 +46.245.39.184 +46.245.39.185 +46.245.39.186 +46.245.39.210 +46.245.41.104 +46.245.42.44 +46.245.43.249 +46.245.44.1 +46.245.44.9 +46.245.46.128 +46.245.47.207 +46.245.65.2 +46.245.65.3 +46.245.66.4 +46.245.66.201 +46.245.66.202 +46.245.66.203 +46.245.66.204 +46.245.66.205 +46.245.66.251 +46.245.66.253 +46.245.67.142 +46.245.67.143 +46.245.67.144 +46.245.67.146 +46.245.67.147 +46.245.67.148 +46.245.67.151 +46.245.67.161 +46.245.69.214 +46.245.72.218 +46.245.72.219 +46.245.72.220 +46.245.76.203 +46.245.77.98 +46.245.77.99 +46.245.77.100 +46.245.77.101 +46.245.77.102 +46.245.77.103 +46.245.77.106 +46.245.77.107 +46.245.77.108 +46.245.77.109 +46.245.77.110 +46.245.78.154 +46.245.80.172 +46.245.81.34 +46.245.81.115 +46.245.81.116 +46.245.81.117 +46.245.81.118 +46.245.83.142 +46.245.84.81 +46.245.84.82 +46.245.84.83 +46.245.84.84 +46.245.84.86 +46.245.86.185 +46.245.87.218 +46.245.87.219 +46.245.87.221 +46.245.89.99 +46.245.89.101 +46.245.90.73 +46.245.90.103 +46.245.91.68 +46.245.91.71 +46.245.91.76 +46.245.91.82 +46.245.91.83 +46.245.91.84 +46.245.91.85 +46.245.91.86 +46.245.91.87 +46.245.91.88 +46.245.91.89 +46.245.91.90 +46.245.91.91 +46.245.91.92 +46.245.91.93 +46.245.91.94 +46.245.92.159 +46.245.94.106 +46.245.94.149 +46.245.94.150 +46.245.94.151 +46.245.94.177 +46.245.94.178 +46.245.95.30 +46.245.95.65 +46.245.95.188 +46.245.98.77 +46.245.98.133 +46.245.107.197 +46.245.112.16 +46.245.112.17 +46.245.112.18 +46.245.114.226 +46.245.114.227 +46.245.115.36 +46.245.115.39 +46.245.115.40 +46.245.115.43 +46.245.115.202 +46.245.117.51 +46.245.118.147 +46.245.122.19 +46.245.122.29 +46.245.123.9 +46.245.123.10 +46.245.123.82 +46.245.123.84 +46.245.123.86 +46.245.124.224 +46.249.120.25 +46.249.120.43 +46.249.120.104 +46.249.121.88 +46.249.121.89 +46.249.121.91 +46.249.121.97 +46.249.121.190 +46.249.122.3 +46.249.122.10 +46.249.122.12 +46.249.122.13 +46.249.122.15 +46.249.122.18 +46.249.122.21 +46.249.122.26 +46.249.122.29 +46.249.122.32 +46.249.122.34 +46.249.122.35 +46.249.122.39 +46.249.122.41 +46.249.122.48 +46.249.122.57 +46.249.123.7 +46.249.123.23 +46.249.124.14 +46.249.124.34 +46.249.124.244 +46.249.124.245 +46.249.125.9 +46.249.125.129 +46.249.125.200 +46.249.125.205 +46.249.125.215 +46.249.125.225 +46.249.125.244 +46.249.126.93 +46.249.127.121 +46.249.127.199 +58.147.146.146 +62.3.14.8 +62.3.14.136 +62.3.41.30 +62.3.41.88 +62.3.41.112 +62.3.41.131 +62.3.41.165 +62.3.42.2 +62.3.42.3 +62.3.42.50 +62.3.42.133 +62.3.42.136 +62.3.42.221 +62.3.42.241 +62.60.128.143 +62.60.128.166 +62.60.141.98 +62.60.141.99 +62.60.141.100 +62.60.141.102 +62.60.141.106 +62.60.141.107 +62.60.141.108 +62.60.141.109 +62.60.141.254 +62.60.143.146 +62.60.143.151 +62.60.143.163 +62.60.143.240 +62.60.143.243 +62.60.144.150 +62.60.144.151 +62.60.144.154 +62.60.144.167 +62.60.144.177 +62.60.144.184 +62.60.144.187 +62.60.144.195 +62.60.144.196 +62.60.144.197 +62.60.144.198 +62.60.144.199 +62.60.144.200 +62.60.160.29 +62.60.160.81 +62.60.160.123 +62.60.161.203 +62.60.161.217 +62.60.162.97 +62.60.162.148 +62.60.162.213 +62.60.163.202 +62.60.163.227 +62.60.163.233 +62.60.163.251 +62.60.164.128 +62.60.164.146 +62.60.167.26 +62.60.167.29 +62.60.190.190 +62.60.190.200 +62.60.200.18 +62.60.200.19 +62.60.200.20 +62.60.200.170 +62.60.200.243 +62.60.200.244 +62.60.200.245 +62.60.200.246 +62.60.204.141 +62.60.205.24 +62.60.206.6 +62.60.206.17 +62.60.207.160 +62.60.207.177 +62.60.210.60 +62.60.210.179 +62.60.212.164 +62.60.214.47 +62.60.214.71 +62.60.214.128 +62.60.214.134 +62.60.214.189 +62.60.218.224 +62.106.95.53 +62.106.95.132 +62.204.61.3 +62.204.61.16 +62.204.61.20 +62.204.61.40 +62.204.61.165 +62.204.61.188 +62.220.100.170 +62.220.100.172 +62.220.100.183 +62.220.112.63 +62.220.113.122 +62.220.113.160 +62.220.114.193 +62.220.114.197 +62.220.114.241 +62.220.115.99 +62.220.117.202 +62.220.124.52 +62.220.124.54 +62.220.126.3 +62.220.126.4 +62.220.126.5 +62.220.126.6 +62.220.126.8 +62.220.126.9 +62.220.126.15 +62.220.126.16 +62.220.126.23 +62.220.126.26 +62.220.126.28 +62.220.126.29 +62.220.126.30 +62.220.126.31 +62.220.126.33 +62.220.126.34 +62.220.126.35 +62.220.126.36 +62.220.126.37 +62.220.126.42 +62.220.126.48 +62.220.126.50 +62.220.126.52 +62.220.126.57 +62.220.126.73 +62.220.126.74 +62.220.126.75 +62.220.126.78 +62.220.126.79 +62.220.126.109 +62.220.126.111 +62.220.126.119 +62.220.126.153 +62.220.126.154 +62.220.126.155 +62.220.126.157 +62.220.126.159 +62.220.126.171 +62.220.126.172 +62.220.126.173 +62.220.126.180 +62.220.126.209 +62.220.126.210 +62.220.126.211 +62.220.126.213 +62.220.126.214 +62.220.126.215 +62.220.126.221 +62.220.126.222 +62.220.126.226 +62.220.126.236 +62.220.126.237 +62.220.126.238 +62.220.126.239 +62.220.126.244 +62.220.126.245 +62.220.126.246 +62.220.127.82 +62.220.127.116 +62.220.127.117 +62.220.127.118 +62.220.127.186 +66.79.96.123 +66.79.97.38 +66.79.98.201 +66.79.98.210 +66.79.99.182 +66.79.100.21 +66.79.100.86 +66.79.100.90 +66.79.101.14 +66.79.101.45 +66.79.102.175 +66.79.103.222 +66.79.105.22 +66.79.105.60 +77.36.237.37 +77.36.243.129 +77.81.34.250 +77.81.76.9 +77.81.76.16 +77.81.76.73 +77.81.78.16 +77.81.78.31 +77.81.78.39 +77.81.78.61 +77.81.78.63 +77.81.78.82 +77.81.78.93 +77.81.78.98 +77.81.78.108 +77.81.78.111 +77.81.78.117 +77.81.78.123 +77.81.78.125 +77.81.78.128 +77.81.78.141 +77.81.78.157 +77.81.78.186 +77.81.82.1 +77.81.82.21 +77.81.82.194 +77.81.83.3 +77.81.83.10 +77.81.144.14 +77.81.146.44 +77.81.147.224 +77.81.147.254 +77.81.148.63 +77.81.148.122 +77.81.148.155 +77.81.148.182 +77.81.149.7 +77.81.149.100 +77.81.149.196 +77.81.149.231 +77.81.151.101 +77.81.151.131 +77.81.152.62 +77.81.152.97 +77.81.152.229 +77.81.154.30 +77.81.156.28 +77.81.157.124 +77.81.158.53 +77.81.159.164 +77.95.219.74 +77.95.219.126 +77.95.219.155 +77.95.219.182 +77.104.66.74 +77.104.66.134 +77.104.67.146 +77.104.67.147 +77.104.67.148 +77.104.67.149 +77.104.67.150 +77.104.67.151 +77.104.67.152 +77.104.67.153 +77.104.67.154 +77.104.68.129 +77.104.68.153 +77.104.68.212 +77.104.72.22 +77.104.72.146 +77.104.73.66 +77.104.73.67 +77.104.73.68 +77.104.73.69 +77.104.73.70 +77.104.73.71 +77.104.73.72 +77.104.73.74 +77.104.73.75 +77.104.73.76 +77.104.73.77 +77.104.73.78 +77.104.73.130 +77.104.73.131 +77.104.73.132 +77.104.73.133 +77.104.73.135 +77.104.73.136 +77.104.73.137 +77.104.73.138 +77.104.73.139 +77.104.75.114 +77.104.75.227 +77.104.76.6 +77.104.76.44 +77.104.76.50 +77.104.76.230 +77.104.77.114 +77.104.77.197 +77.104.79.243 +77.104.79.244 +77.104.81.67 +77.104.82.66 +77.104.82.67 +77.104.82.68 +77.104.82.69 +77.104.82.70 +77.104.82.110 +77.104.82.196 +77.104.84.130 +77.104.84.136 +77.104.84.146 +77.104.84.150 +77.104.85.14 +77.104.85.50 +77.104.85.51 +77.104.85.74 +77.104.85.90 +77.104.92.3 +77.104.93.85 +77.104.99.150 +77.104.100.186 +77.104.102.162 +77.104.103.130 +77.104.103.250 +77.104.110.6 +77.104.110.10 +77.104.110.11 +77.104.110.13 +77.104.110.18 +77.104.110.137 +77.104.110.166 +77.104.114.229 +77.104.114.233 +77.104.114.237 +77.104.114.241 +77.104.114.245 +77.104.114.249 +77.104.115.183 +77.104.115.190 +77.104.115.217 +77.104.115.239 +77.104.126.28 +77.104.126.43 +77.104.126.152 +77.104.126.156 +77.104.126.181 +77.104.126.183 +77.104.126.189 +77.104.126.211 +77.104.127.6 +77.237.66.72 +77.237.67.19 +77.237.69.136 +77.237.69.192 +77.237.69.193 +77.237.70.222 +77.237.76.74 +77.237.76.223 +77.237.77.42 +77.237.77.106 +77.237.77.109 +77.237.78.1 +77.237.78.7 +77.237.78.8 +77.237.78.186 +77.237.82.49 +77.237.82.56 +77.237.82.112 +77.237.82.169 +77.237.82.221 +77.237.82.247 +77.237.83.186 +77.237.85.193 +77.237.85.197 +77.237.85.201 +77.237.85.205 +77.237.85.209 +77.237.85.213 +77.237.85.217 +77.237.85.229 +77.237.85.230 +77.237.85.233 +77.237.85.241 +77.237.85.245 +77.237.85.249 +77.237.87.190 +77.237.88.146 +77.237.88.178 +77.237.89.18 +77.237.89.194 +77.237.89.196 +77.237.89.197 +77.237.89.198 +77.237.91.146 +77.237.92.1 +77.237.92.82 +77.237.92.90 +77.237.92.106 +77.237.92.118 +77.237.92.220 +77.237.93.34 +77.237.93.35 +77.237.93.36 +77.237.93.37 +77.237.94.10 +77.237.94.202 +77.237.94.203 +77.237.95.186 +77.238.104.0 +77.238.104.47 +77.238.104.102 +77.238.104.107 +77.238.104.135 +77.238.104.150 +77.238.104.208 +77.238.104.238 +77.238.105.125 +77.238.105.147 +77.238.105.242 +77.238.106.19 +77.238.106.41 +77.238.106.58 +77.238.106.96 +77.238.106.103 +77.238.106.119 +77.238.106.138 +77.238.106.198 +77.238.106.225 +77.238.107.24 +77.238.107.32 +77.238.107.174 +77.238.107.198 +77.238.108.92 +77.238.109.91 +77.238.110.40 +77.238.110.44 +77.238.110.142 +77.238.110.163 +77.238.110.177 +77.238.110.231 +77.238.111.235 +77.238.114.64 +77.238.115.44 +77.238.117.255 +77.238.118.166 +77.238.119.32 +77.238.119.96 +77.238.120.23 +77.238.121.92 +77.238.121.128 +77.238.121.138 +77.238.121.200 +77.238.121.206 +77.238.122.146 +77.238.122.163 +77.238.122.242 +77.238.123.238 +78.31.235.1 +78.31.235.2 +78.38.0.73 +78.38.0.82 +78.38.0.92 +78.38.0.93 +78.38.16.210 +78.38.17.48 +78.38.17.67 +78.38.17.118 +78.38.17.155 +78.38.17.159 +78.38.18.137 +78.38.19.151 +78.38.20.2 +78.38.20.33 +78.38.20.98 +78.38.20.99 +78.38.20.105 +78.38.24.122 +78.38.24.199 +78.38.26.79 +78.38.26.103 +78.38.26.107 +78.38.26.124 +78.38.26.129 +78.38.26.130 +78.38.26.131 +78.38.26.133 +78.38.26.136 +78.38.26.137 +78.38.26.138 +78.38.26.140 +78.38.26.141 +78.38.26.142 +78.38.26.145 +78.38.26.146 +78.38.26.147 +78.38.26.148 +78.38.26.156 +78.38.26.157 +78.38.26.158 +78.38.26.160 +78.38.26.161 +78.38.26.162 +78.38.26.163 +78.38.26.165 +78.38.26.167 +78.38.26.168 +78.38.26.169 +78.38.26.170 +78.38.26.171 +78.38.26.173 +78.38.26.174 +78.38.26.175 +78.38.26.176 +78.38.26.177 +78.38.26.178 +78.38.26.179 +78.38.26.180 +78.38.26.182 +78.38.26.184 +78.38.26.185 +78.38.26.186 +78.38.26.187 +78.38.26.227 +78.38.29.9 +78.38.29.147 +78.38.30.35 +78.38.30.69 +78.38.30.179 +78.38.30.200 +78.38.31.45 +78.38.31.107 +78.38.31.117 +78.38.31.161 +78.38.32.56 +78.38.32.159 +78.38.36.61 +78.38.38.48 +78.38.41.17 +78.38.41.37 +78.38.46.154 +78.38.46.200 +78.38.46.209 +78.38.46.220 +78.38.48.107 +78.38.48.178 +78.38.48.210 +78.38.49.109 +78.38.49.110 +78.38.49.240 +78.38.49.255 +78.38.50.39 +78.38.50.44 +78.38.50.61 +78.38.50.125 +78.38.50.154 +78.38.50.207 +78.38.52.74 +78.38.52.82 +78.38.60.218 +78.38.64.65 +78.38.64.73 +78.38.64.129 +78.38.65.65 +78.38.65.108 +78.38.65.109 +78.38.67.24 +78.38.67.26 +78.38.67.93 +78.38.67.95 +78.38.69.87 +78.38.69.89 +78.38.71.13 +78.38.71.164 +78.38.77.47 +78.38.78.49 +78.38.80.241 +78.38.80.242 +78.38.84.12 +78.38.90.251 +78.38.92.106 +78.38.92.218 +78.38.93.34 +78.38.93.58 +78.38.93.86 +78.38.93.90 +78.38.94.74 +78.38.94.122 +78.38.94.123 +78.38.94.130 +78.38.94.131 +78.38.94.132 +78.38.94.133 +78.38.94.134 +78.38.95.57 +78.38.99.46 +78.38.99.50 +78.38.99.82 +78.38.99.118 +78.38.99.186 +78.38.99.194 +78.38.99.195 +78.38.99.196 +78.38.99.238 +78.38.100.54 +78.38.100.71 +78.38.100.121 +78.38.101.66 +78.38.105.9 +78.38.105.74 +78.38.106.232 +78.38.106.243 +78.38.107.53 +78.38.107.72 +78.38.107.88 +78.38.107.172 +78.38.108.194 +78.38.108.196 +78.38.108.198 +78.38.108.199 +78.38.108.202 +78.38.108.203 +78.38.108.204 +78.38.108.205 +78.38.109.100 +78.38.110.110 +78.38.110.138 +78.38.110.171 +78.38.110.174 +78.38.112.130 +78.38.114.86 +78.38.114.210 +78.38.114.212 +78.38.114.213 +78.38.114.221 +78.38.120.80 +78.38.120.120 +78.38.121.218 +78.38.123.210 +78.38.124.28 +78.38.124.35 +78.38.124.163 +78.38.126.2 +78.38.126.101 +78.38.126.141 +78.38.126.247 +78.38.127.149 +78.38.127.238 +78.38.140.50 +78.38.144.243 +78.38.145.228 +78.38.149.32 +78.38.152.102 +78.38.153.37 +78.38.153.81 +78.38.153.177 +78.38.153.181 +78.38.156.22 +78.38.156.129 +78.38.156.205 +78.38.156.229 +78.38.156.246 +78.38.157.65 +78.38.157.66 +78.38.157.112 +78.38.157.124 +78.38.157.167 +78.38.158.22 +78.38.161.80 +78.38.166.121 +78.38.166.122 +78.38.166.123 +78.38.166.145 +78.38.166.249 +78.38.174.78 +78.38.179.236 +78.38.182.61 +78.38.189.221 +78.38.209.57 +78.38.221.170 +78.38.221.172 +78.38.221.173 +78.38.221.174 +78.38.224.98 +78.38.239.220 +78.38.239.221 +78.38.239.222 +78.38.239.223 +78.38.239.224 +78.38.243.6 +78.38.243.234 +78.38.243.237 +78.38.246.174 +78.38.246.183 +78.38.248.46 +78.38.248.117 +78.38.248.154 +78.38.248.242 +78.38.248.250 +78.38.250.6 +78.38.250.82 +78.38.250.84 +78.38.250.85 +78.38.250.86 +78.38.250.150 +78.38.251.250 +78.38.251.251 +78.38.251.252 +78.38.251.253 +78.38.254.14 +78.38.254.66 +78.39.10.54 +78.39.10.234 +78.39.11.2 +78.39.11.14 +78.39.11.62 +78.39.11.122 +78.39.37.170 +78.39.40.114 +78.39.40.115 +78.39.40.142 +78.39.40.147 +78.39.40.155 +78.39.43.242 +78.39.46.121 +78.39.46.122 +78.39.46.123 +78.39.46.124 +78.39.46.125 +78.39.46.126 +78.39.46.127 +78.39.48.28 +78.39.48.140 +78.39.48.150 +78.39.48.202 +78.39.49.90 +78.39.50.6 +78.39.50.42 +78.39.50.76 +78.39.50.134 +78.39.50.170 +78.39.50.234 +78.39.53.120 +78.39.53.162 +78.39.53.194 +78.39.53.254 +78.39.56.183 +78.39.57.178 +78.39.57.179 +78.39.59.14 +78.39.59.38 +78.39.59.142 +78.39.62.37 +78.39.62.147 +78.39.62.148 +78.39.67.138 +78.39.80.59 +78.39.80.169 +78.39.81.17 +78.39.81.26 +78.39.81.57 +78.39.81.166 +78.39.82.26 +78.39.83.13 +78.39.83.20 +78.39.83.27 +78.39.83.30 +78.39.83.55 +78.39.83.103 +78.39.83.111 +78.39.83.118 +78.39.83.128 +78.39.88.175 +78.39.99.17 +78.39.99.25 +78.39.99.98 +78.39.102.99 +78.39.102.103 +78.39.102.108 +78.39.103.3 +78.39.103.4 +78.39.103.9 +78.39.103.10 +78.39.103.15 +78.39.103.16 +78.39.103.24 +78.39.108.18 +78.39.108.24 +78.39.108.121 +78.39.108.196 +78.39.109.10 +78.39.109.54 +78.39.117.48 +78.39.124.225 +78.39.125.69 +78.39.125.70 +78.39.125.71 +78.39.125.72 +78.39.125.73 +78.39.125.74 +78.39.125.75 +78.39.125.77 +78.39.125.78 +78.39.125.80 +78.39.125.81 +78.39.136.50 +78.39.138.94 +78.39.138.115 +78.39.140.23 +78.39.141.84 +78.39.141.204 +78.39.144.157 +78.39.145.101 +78.39.145.216 +78.39.147.202 +78.39.150.26 +78.39.159.153 +78.39.175.161 +78.39.176.89 +78.39.180.157 +78.39.180.174 +78.39.181.87 +78.39.181.97 +78.39.192.15 +78.39.193.34 +78.39.193.74 +78.39.193.75 +78.39.193.78 +78.39.194.9 +78.39.195.234 +78.39.195.238 +78.39.198.4 +78.39.204.9 +78.39.204.17 +78.39.204.33 +78.39.209.115 +78.39.209.202 +78.39.217.45 +78.39.217.53 +78.39.217.161 +78.39.217.236 +78.39.218.2 +78.39.218.3 +78.39.218.4 +78.39.218.6 +78.39.218.7 +78.39.218.8 +78.39.218.10 +78.39.218.12 +78.39.220.114 +78.39.220.122 +78.39.220.123 +78.39.220.124 +78.39.220.125 +78.39.220.126 +78.39.220.163 +78.39.225.143 +78.39.226.16 +78.39.226.113 +78.39.227.26 +78.39.228.234 +78.39.229.24 +78.39.230.239 +78.39.233.108 +78.39.233.143 +78.39.233.156 +78.39.233.159 +78.39.234.54 +78.39.234.221 +78.39.234.231 +78.39.235.108 +78.39.235.148 +78.39.244.140 +78.39.245.159 +78.39.249.51 +78.39.249.136 +78.39.250.143 +78.39.251.53 +78.39.251.221 +78.39.252.62 +78.39.253.225 +78.39.255.246 +78.109.194.122 +78.109.195.58 +78.109.196.139 +78.109.198.2 +78.109.199.57 +78.109.201.52 +78.109.201.55 +78.110.120.195 +78.110.120.200 +78.110.120.227 +78.110.122.96 +78.110.123.67 +78.110.124.19 +78.110.124.90 +78.110.124.162 +78.110.124.203 +78.110.124.206 +78.110.125.58 +78.110.125.83 +78.110.125.85 +78.110.125.250 +78.110.126.170 +78.110.127.91 +78.111.0.30 +78.111.0.68 +78.111.1.22 +78.111.3.17 +78.111.3.94 +78.111.3.177 +78.111.5.2 +78.111.5.246 +78.111.7.1 +78.111.7.2 +78.111.7.3 +78.111.7.4 +78.111.7.5 +78.111.7.6 +78.111.7.7 +78.111.7.8 +78.111.7.9 +78.111.7.10 +78.111.7.12 +78.111.7.14 +78.111.7.15 +78.111.7.16 +78.111.7.18 +78.111.7.19 +78.111.7.20 +78.111.7.22 +78.111.7.23 +78.111.7.24 +78.111.7.25 +78.111.7.26 +78.111.7.27 +78.111.7.28 +78.111.7.29 +78.111.7.30 +78.111.7.31 +78.111.7.32 +78.111.7.33 +78.111.7.34 +78.111.7.35 +78.111.7.36 +78.111.7.37 +78.111.7.38 +78.111.7.39 +78.111.7.40 +78.111.7.42 +78.111.7.43 +78.111.7.44 +78.111.7.45 +78.111.7.46 +78.111.7.47 +78.111.7.48 +78.111.7.49 +78.111.7.50 +78.111.7.52 +78.111.7.53 +78.111.7.54 +78.111.7.55 +78.111.7.56 +78.111.7.57 +78.111.7.58 +78.111.7.59 +78.111.7.60 +78.111.7.61 +78.111.7.62 +78.111.7.63 +78.111.7.64 +78.111.7.66 +78.111.7.67 +78.111.7.68 +78.111.7.69 +78.111.7.70 +78.111.7.71 +78.111.7.72 +78.111.7.74 +78.111.7.75 +78.111.7.76 +78.111.7.77 +78.111.7.78 +78.111.7.79 +78.111.7.80 +78.111.7.81 +78.111.7.82 +78.111.7.84 +78.111.7.85 +78.111.7.86 +78.111.7.87 +78.111.7.88 +78.111.7.89 +78.111.7.90 +78.111.7.91 +78.111.7.92 +78.111.7.93 +78.111.7.94 +78.111.7.95 +78.111.7.96 +78.111.7.97 +78.111.7.98 +78.111.7.99 +78.111.7.100 +78.111.7.101 +78.111.7.102 +78.111.7.103 +78.111.7.104 +78.111.7.105 +78.111.7.106 +78.111.7.108 +78.111.7.109 +78.111.7.110 +78.111.7.112 +78.111.7.113 +78.111.7.114 +78.111.7.115 +78.111.7.116 +78.111.7.117 +78.111.7.118 +78.111.7.119 +78.111.7.121 +78.111.7.122 +78.111.7.123 +78.111.7.124 +78.111.7.125 +78.111.7.126 +78.111.7.127 +78.111.7.128 +78.111.7.129 +78.111.7.130 +78.111.7.131 +78.111.7.132 +78.111.7.133 +78.111.7.134 +78.111.7.135 +78.111.7.136 +78.111.7.138 +78.111.7.139 +78.111.7.140 +78.111.7.141 +78.111.7.142 +78.111.7.143 +78.111.7.144 +78.111.7.145 +78.111.7.146 +78.111.7.147 +78.111.7.149 +78.111.7.150 +78.111.7.151 +78.111.7.152 +78.111.7.153 +78.111.7.154 +78.111.7.156 +78.111.7.157 +78.111.7.158 +78.111.7.159 +78.111.7.161 +78.111.7.162 +78.111.7.163 +78.111.7.164 +78.111.7.165 +78.111.7.166 +78.111.7.167 +78.111.7.168 +78.111.7.169 +78.111.7.170 +78.111.7.171 +78.111.7.173 +78.111.7.174 +78.111.7.175 +78.111.7.176 +78.111.7.177 +78.111.7.178 +78.111.7.179 +78.111.7.180 +78.111.7.182 +78.111.7.183 +78.111.7.185 +78.111.7.186 +78.111.7.187 +78.111.7.188 +78.111.7.189 +78.111.7.190 +78.111.7.191 +78.111.7.192 +78.111.7.194 +78.111.7.195 +78.111.7.196 +78.111.7.197 +78.111.7.198 +78.111.7.200 +78.111.7.201 +78.111.7.202 +78.111.7.203 +78.111.7.204 +78.111.7.205 +78.111.7.206 +78.111.7.207 +78.111.7.208 +78.111.7.209 +78.111.7.210 +78.111.7.211 +78.111.7.212 +78.111.7.213 +78.111.7.214 +78.111.7.215 +78.111.7.216 +78.111.7.217 +78.111.7.218 +78.111.7.219 +78.111.7.220 +78.111.7.221 +78.111.7.222 +78.111.7.223 +78.111.7.224 +78.111.7.225 +78.111.7.228 +78.111.7.229 +78.111.7.230 +78.111.7.231 +78.111.7.232 +78.111.7.234 +78.111.7.235 +78.111.7.236 +78.111.7.237 +78.111.7.238 +78.111.7.239 +78.111.7.240 +78.111.7.241 +78.111.7.243 +78.111.7.244 +78.111.7.245 +78.111.7.246 +78.111.7.247 +78.111.7.248 +78.111.7.249 +78.111.7.251 +78.111.7.252 +78.111.7.253 +78.111.7.254 +78.157.34.172 +78.157.34.173 +78.157.35.53 +78.157.35.71 +78.157.35.225 +78.157.36.94 +78.157.36.116 +78.157.36.117 +78.157.36.118 +78.157.36.119 +78.157.36.120 +78.157.36.121 +78.157.36.122 +78.157.36.123 +78.157.36.124 +78.157.36.125 +78.157.36.126 +78.157.38.110 +78.157.38.115 +78.157.38.210 +78.157.39.71 +78.157.39.167 +78.157.39.180 +78.157.40.67 +78.157.40.75 +78.157.40.144 +78.157.41.50 +78.157.41.242 +78.157.41.243 +78.157.42.199 +78.157.42.207 +78.157.44.181 +78.157.44.221 +78.157.45.197 +78.157.48.187 +78.157.48.189 +78.157.48.190 +78.157.48.230 +78.157.49.38 +78.157.49.44 +78.157.50.35 +78.157.50.36 +78.157.50.206 +78.157.50.208 +78.157.50.218 +78.157.50.226 +78.157.51.212 +78.157.51.249 +78.157.51.253 +78.157.53.183 +78.157.56.22 +78.157.56.98 +78.157.56.99 +78.157.56.101 +78.157.57.62 +78.157.57.69 +78.157.57.71 +78.157.57.85 +78.157.57.204 +78.157.57.222 +78.157.57.251 +78.157.60.1 +78.157.60.2 +78.157.60.3 +78.157.60.215 +78.157.61.24 +78.157.61.109 +78.157.62.33 +78.157.62.148 +78.157.63.106 +78.158.165.138 +78.158.165.139 +78.158.166.131 +78.158.166.132 +78.158.168.143 +78.158.168.189 +78.158.168.190 +78.158.179.8 +78.158.179.10 +78.158.179.43 +78.158.179.87 +78.158.179.112 +78.158.179.120 +78.158.179.124 +78.158.179.146 +78.158.179.155 +78.158.179.203 +78.158.182.126 +78.158.191.77 +78.158.191.115 +78.158.191.130 +78.158.191.156 +78.158.191.158 +78.158.191.224 +78.158.191.225 +78.158.191.231 +78.158.191.236 +78.158.191.237 +78.158.191.241 +78.158.191.252 +79.127.0.18 +79.127.0.63 +79.127.0.85 +79.127.0.140 +79.127.0.184 +79.127.0.200 +79.127.0.224 +79.127.1.32 +79.127.1.77 +79.127.1.85 +79.127.1.104 +79.127.1.187 +79.127.1.205 +79.127.1.211 +79.127.1.248 +79.127.2.25 +79.127.2.120 +79.127.2.128 +79.127.2.141 +79.127.2.142 +79.127.2.149 +79.127.2.164 +79.127.2.175 +79.127.2.236 +79.127.3.10 +79.127.3.28 +79.127.3.42 +79.127.3.50 +79.127.3.53 +79.127.3.164 +79.127.3.173 +79.127.3.182 +79.127.3.196 +79.127.3.208 +79.127.3.210 +79.127.4.49 +79.127.4.59 +79.127.4.81 +79.127.4.134 +79.127.4.135 +79.127.4.197 +79.127.4.200 +79.127.4.245 +79.127.5.11 +79.127.5.13 +79.127.5.17 +79.127.5.53 +79.127.5.79 +79.127.5.93 +79.127.5.94 +79.127.5.109 +79.127.5.127 +79.127.5.161 +79.127.5.164 +79.127.6.2 +79.127.6.65 +79.127.6.91 +79.127.6.105 +79.127.6.141 +79.127.6.142 +79.127.6.191 +79.127.6.218 +79.127.6.227 +79.127.7.18 +79.127.7.34 +79.127.7.50 +79.127.7.59 +79.127.7.83 +79.127.7.114 +79.127.7.127 +79.127.7.226 +79.127.11.80 +79.127.11.106 +79.127.11.154 +79.127.11.226 +79.127.12.1 +79.127.12.112 +79.127.12.147 +79.127.12.148 +79.127.12.162 +79.127.13.0 +79.127.13.30 +79.127.13.38 +79.127.13.134 +79.127.13.167 +79.127.13.171 +79.127.13.200 +79.127.13.236 +79.127.14.49 +79.127.14.70 +79.127.14.81 +79.127.14.108 +79.127.14.116 +79.127.14.140 +79.127.14.145 +79.127.15.17 +79.127.15.28 +79.127.15.41 +79.127.15.91 +79.127.15.117 +79.127.15.135 +79.127.15.148 +79.127.15.156 +79.127.15.159 +79.127.15.165 +79.127.15.206 +79.127.15.223 +79.127.15.239 +79.127.16.37 +79.127.24.251 +79.127.29.99 +79.127.29.164 +79.127.30.178 +79.127.30.179 +79.127.30.180 +79.127.30.186 +79.127.31.209 +79.127.32.2 +79.127.32.18 +79.127.32.26 +79.127.32.43 +79.127.32.122 +79.127.32.123 +79.127.32.125 +79.127.32.126 +79.127.32.250 +79.127.33.10 +79.127.33.81 +79.127.33.106 +79.127.33.186 +79.127.33.188 +79.127.33.198 +79.127.34.58 +79.127.34.62 +79.127.34.130 +79.127.34.226 +79.127.34.229 +79.127.35.50 +79.127.35.242 +79.127.35.243 +79.127.36.98 +79.127.37.4 +79.127.37.5 +79.127.37.6 +79.127.37.90 +79.127.37.226 +79.127.37.234 +79.127.37.235 +79.127.40.66 +79.127.40.135 +79.127.40.202 +79.127.41.42 +79.127.42.226 +79.127.45.49 +79.127.47.18 +79.127.47.19 +79.127.47.26 +79.127.47.27 +79.127.47.28 +79.127.47.29 +79.127.47.30 +79.127.47.68 +79.127.47.82 +79.127.47.83 +79.127.47.146 +79.127.47.186 +79.127.47.251 +79.127.49.27 +79.127.49.130 +79.127.49.178 +79.127.49.179 +79.127.50.178 +79.127.50.180 +79.127.50.250 +79.127.51.90 +79.127.51.170 +79.127.51.226 +79.127.52.162 +79.127.52.163 +79.127.52.164 +79.127.52.165 +79.127.52.178 +79.127.52.194 +79.127.52.234 +79.127.53.26 +79.127.53.122 +79.127.53.131 +79.127.53.170 +79.127.54.163 +79.127.54.186 +79.127.54.205 +79.127.54.242 +79.127.54.250 +79.127.55.178 +79.127.55.234 +79.127.56.58 +79.127.56.61 +79.127.56.139 +79.127.56.140 +79.127.56.178 +79.127.59.101 +79.127.60.18 +79.127.63.90 +79.127.63.210 +79.127.65.34 +79.127.65.155 +79.127.66.90 +79.127.66.101 +79.127.66.194 +79.127.66.195 +79.127.66.250 +79.127.68.34 +79.127.68.35 +79.127.68.37 +79.127.68.38 +79.127.69.116 +79.127.69.117 +79.127.69.118 +79.127.69.122 +79.127.69.123 +79.127.69.124 +79.127.69.126 +79.127.69.163 +79.127.70.130 +79.127.70.131 +79.127.70.132 +79.127.70.133 +79.127.70.134 +79.127.70.135 +79.127.70.136 +79.127.70.137 +79.127.70.139 +79.127.70.141 +79.127.70.142 +79.127.70.146 +79.127.70.162 +79.127.71.10 +79.127.71.11 +79.127.71.12 +79.127.71.14 +79.127.71.34 +79.127.71.114 +79.127.71.219 +79.127.71.243 +79.127.72.26 +79.127.72.187 +79.127.72.189 +79.127.72.191 +79.127.72.218 +79.127.73.201 +79.127.74.34 +79.127.74.66 +79.127.74.114 +79.127.74.115 +79.127.74.116 +79.127.74.117 +79.127.74.234 +79.127.74.243 +79.127.75.10 +79.127.76.18 +79.127.76.122 +79.127.76.138 +79.127.76.147 +79.127.76.186 +79.127.76.210 +79.127.76.211 +79.127.76.214 +79.127.76.226 +79.127.77.106 +79.127.77.142 +79.127.77.161 +79.127.78.26 +79.127.78.66 +79.127.79.10 +79.127.79.162 +79.127.79.165 +79.127.80.114 +79.127.80.115 +79.127.81.2 +79.127.81.10 +79.127.82.26 +79.127.83.26 +79.127.83.94 +79.127.83.114 +79.127.83.202 +79.127.84.122 +79.127.84.124 +79.127.84.125 +79.127.84.126 +79.127.85.170 +79.127.85.171 +79.127.85.178 +79.127.86.11 +79.127.86.252 +79.127.87.179 +79.127.87.226 +79.127.92.1 +79.127.92.8 +79.127.92.9 +79.127.92.16 +79.127.92.17 +79.127.92.20 +79.127.92.79 +79.127.92.83 +79.127.92.106 +79.127.92.166 +79.127.92.180 +79.127.92.186 +79.127.92.215 +79.127.92.217 +79.127.92.221 +79.127.92.238 +79.127.94.21 +79.127.94.22 +79.127.94.30 +79.127.94.36 +79.127.94.55 +79.127.94.57 +79.127.94.59 +79.127.94.83 +79.127.94.91 +79.127.94.108 +79.127.94.116 +79.127.94.135 +79.127.94.141 +79.127.94.142 +79.127.94.157 +79.127.94.163 +79.127.94.170 +79.127.94.184 +79.127.94.187 +79.127.94.213 +79.127.94.228 +79.127.94.255 +79.127.95.6 +79.127.95.14 +79.127.95.63 +79.127.95.74 +79.127.95.96 +79.127.95.102 +79.127.95.107 +79.127.95.110 +79.127.95.117 +79.127.95.119 +79.127.95.130 +79.127.95.133 +79.127.95.139 +79.127.95.143 +79.127.95.164 +79.127.95.184 +79.127.95.193 +79.127.95.195 +79.127.95.205 +79.127.95.230 +79.127.95.234 +79.127.95.236 +79.127.95.241 +79.127.95.248 +79.127.96.6 +79.127.96.40 +79.127.101.21 +79.127.101.194 +79.127.102.143 +79.127.103.225 +79.127.107.233 +79.127.108.80 +79.127.110.91 +79.127.113.91 +79.127.116.1 +79.127.116.9 +79.127.116.17 +79.127.116.56 +79.127.116.186 +79.127.117.60 +79.127.117.66 +79.127.117.199 +79.127.119.146 +79.127.120.51 +79.127.120.82 +79.127.120.218 +79.127.120.222 +79.127.121.10 +79.127.121.184 +79.127.121.185 +79.127.121.187 +79.127.121.188 +79.127.121.189 +79.127.121.226 +79.127.122.10 +79.127.122.98 +79.127.123.34 +79.127.124.106 +79.127.125.74 +79.127.125.75 +79.127.125.76 +79.127.125.77 +79.127.125.78 +79.127.125.212 +79.127.126.67 +79.127.126.93 +79.127.126.114 +79.127.126.243 +79.127.127.71 +79.127.127.73 +79.127.127.122 +79.127.127.214 +79.127.127.216 +79.132.193.34 +79.132.193.58 +79.132.193.73 +79.132.193.196 +79.132.193.198 +79.132.193.212 +79.132.193.235 +79.132.205.29 +79.132.205.33 +79.132.206.198 +79.132.206.209 +79.132.206.217 +79.132.208.49 +79.132.208.50 +79.132.208.53 +79.132.214.254 +79.143.84.70 +79.143.86.3 +79.143.86.11 +79.143.86.12 +79.143.86.40 +79.143.86.50 +79.143.86.66 +79.143.86.129 +79.143.86.158 +79.143.86.161 +79.143.86.193 +79.143.86.201 +79.143.86.209 +79.174.160.210 +79.174.160.222 +79.175.131.190 +79.175.131.196 +79.175.131.253 +79.175.133.54 +79.175.134.37 +79.175.139.185 +79.175.149.66 +79.175.149.68 +79.175.149.69 +79.175.149.70 +79.175.149.217 +79.175.151.43 +79.175.151.142 +79.175.152.194 +79.175.152.195 +79.175.153.210 +79.175.153.211 +79.175.153.212 +79.175.153.213 +79.175.153.214 +79.175.153.215 +79.175.153.217 +79.175.153.218 +79.175.153.219 +79.175.153.221 +79.175.153.222 +79.175.162.226 +79.175.163.215 +79.175.165.171 +79.175.167.96 +79.175.168.78 +79.175.168.220 +79.175.168.222 +79.175.169.50 +79.175.169.61 +79.175.169.213 +79.175.169.221 +79.175.170.133 +79.175.173.154 +79.175.173.155 +79.175.173.156 +79.175.173.157 +79.175.173.158 +79.175.176.2 +79.175.176.90 +79.175.177.8 +79.175.177.10 +79.175.177.125 +79.175.187.189 +79.175.189.3 +79.175.189.4 +79.175.189.231 +80.66.177.23 +80.71.112.51 +80.71.112.98 +80.71.112.115 +80.71.114.16 +80.71.114.19 +80.71.114.23 +80.71.114.38 +80.71.114.148 +80.71.114.153 +80.71.114.164 +80.71.114.175 +80.71.114.197 +80.71.114.203 +80.71.118.2 +80.71.149.32 +80.71.149.62 +80.75.0.201 +80.75.1.66 +80.75.1.230 +80.75.4.24 +80.75.4.50 +80.75.4.66 +80.75.4.67 +80.75.4.68 +80.75.4.69 +80.75.4.76 +80.75.4.77 +80.75.7.22 +80.75.7.58 +80.75.7.66 +80.75.7.94 +80.75.7.170 +80.75.7.171 +80.75.7.172 +80.75.7.173 +80.75.7.174 +80.75.8.194 +80.75.9.182 +80.75.9.197 +80.75.9.222 +80.75.11.58 +80.75.12.100 +80.75.13.30 +80.75.13.34 +80.75.13.107 +80.75.13.178 +80.75.14.34 +80.75.14.110 +80.75.14.111 +80.75.14.130 +80.75.14.219 +80.75.14.250 +80.75.213.163 +80.75.213.247 +80.91.208.26 +80.91.208.27 +80.91.208.28 +80.91.208.29 +80.91.208.30 +80.91.208.75 +80.91.208.76 +80.191.2.7 +80.191.2.224 +80.191.11.45 +80.191.11.217 +80.191.16.15 +80.191.16.68 +80.191.16.126 +80.191.16.194 +80.191.17.100 +80.191.18.190 +80.191.18.211 +80.191.23.134 +80.191.27.33 +80.191.27.34 +80.191.40.130 +80.191.40.131 +80.191.40.132 +80.191.40.133 +80.191.40.134 +80.191.40.135 +80.191.40.138 +80.191.40.139 +80.191.40.140 +80.191.40.141 +80.191.40.142 +80.191.40.143 +80.191.40.144 +80.191.40.145 +80.191.40.146 +80.191.40.147 +80.191.40.148 +80.191.40.149 +80.191.40.150 +80.191.40.151 +80.191.40.153 +80.191.40.154 +80.191.40.155 +80.191.40.156 +80.191.40.157 +80.191.40.158 +80.191.42.10 +80.191.44.80 +80.191.44.91 +80.191.46.150 +80.191.50.28 +80.191.50.253 +80.191.51.53 +80.191.52.37 +80.191.52.216 +80.191.52.254 +80.191.60.35 +80.191.60.36 +80.191.75.159 +80.191.77.139 +80.191.84.18 +80.191.85.46 +80.191.89.66 +80.191.91.57 +80.191.91.84 +80.191.91.109 +80.191.92.74 +80.191.100.9 +80.191.100.67 +80.191.100.106 +80.191.100.124 +80.191.100.182 +80.191.100.199 +80.191.102.39 +80.191.102.46 +80.191.102.93 +80.191.102.154 +80.191.102.167 +80.191.102.225 +80.191.102.241 +80.191.106.141 +80.191.106.144 +80.191.106.237 +80.191.107.16 +80.191.107.90 +80.191.108.51 +80.191.108.70 +80.191.108.107 +80.191.108.164 +80.191.108.192 +80.191.108.228 +80.191.110.90 +80.191.110.161 +80.191.110.178 +80.191.115.120 +80.191.115.122 +80.191.116.30 +80.191.128.1 +80.191.131.64 +80.191.143.18 +80.191.144.1 +80.191.152.66 +80.191.153.1 +80.191.153.4 +80.191.153.107 +80.191.155.242 +80.191.160.149 +80.191.164.98 +80.191.164.105 +80.191.168.212 +80.191.169.162 +80.191.171.224 +80.191.172.9 +80.191.172.23 +80.191.172.33 +80.191.172.35 +80.191.172.50 +80.191.172.81 +80.191.185.180 +80.191.186.118 +80.191.189.125 +80.191.190.179 +80.191.191.11 +80.191.191.12 +80.191.191.14 +80.191.191.18 +80.191.192.209 +80.191.197.194 +80.191.198.122 +80.191.198.226 +80.191.200.3 +80.191.200.5 +80.191.206.50 +80.191.206.66 +80.191.206.98 +80.191.206.135 +80.191.207.2 +80.191.207.3 +80.191.207.4 +80.191.207.5 +80.191.207.7 +80.191.207.9 +80.191.207.10 +80.191.207.11 +80.191.207.21 +80.191.207.33 +80.191.207.34 +80.191.207.35 +80.191.207.37 +80.191.207.38 +80.191.207.40 +80.191.209.94 +80.191.209.105 +80.191.212.141 +80.191.221.26 +80.191.221.27 +80.191.221.33 +80.191.227.147 +80.191.227.148 +80.191.235.90 +80.191.235.91 +80.191.235.92 +80.191.235.93 +80.191.235.94 +80.191.235.145 +80.191.235.194 +80.191.236.19 +80.191.238.82 +80.191.238.83 +80.191.240.66 +80.191.240.128 +80.191.240.138 +80.191.240.185 +80.191.240.191 +80.191.241.194 +80.191.241.195 +80.191.241.196 +80.191.241.197 +80.191.241.198 +80.191.241.199 +80.191.241.200 +80.191.241.201 +80.191.241.202 +80.191.241.203 +80.191.241.205 +80.191.241.208 +80.191.241.209 +80.191.241.210 +80.191.241.212 +80.191.241.214 +80.191.241.217 +80.191.241.218 +80.191.241.220 +80.191.241.221 +80.191.242.2 +80.191.242.110 +80.191.243.226 +80.191.243.234 +80.191.245.235 +80.191.252.152 +80.191.253.205 +80.191.255.18 +80.191.255.22 +80.210.17.128 +80.210.17.137 +80.210.17.163 +80.210.17.197 +80.210.17.229 +80.210.17.240 +80.210.17.249 +80.210.18.45 +80.210.18.93 +80.210.18.121 +80.210.18.147 +80.210.18.219 +80.210.18.220 +80.210.18.235 +80.210.18.243 +80.210.19.31 +80.210.19.69 +80.210.19.124 +80.210.19.172 +80.210.19.179 +80.210.19.186 +80.210.19.215 +80.210.19.220 +80.210.20.31 +80.210.20.88 +80.210.21.16 +80.210.21.70 +80.210.21.84 +80.210.21.91 +80.210.21.179 +80.210.21.211 +80.210.21.224 +80.210.22.14 +80.210.22.47 +80.210.22.77 +80.210.22.79 +80.210.22.113 +80.210.22.140 +80.210.22.142 +80.210.22.153 +80.210.22.217 +80.210.22.229 +80.210.22.243 +80.210.24.7 +80.210.24.89 +80.210.24.110 +80.210.24.158 +80.210.24.185 +80.210.24.246 +80.210.25.33 +80.210.25.237 +80.210.26.72 +80.210.26.90 +80.210.26.101 +80.210.26.104 +80.210.26.110 +80.210.26.120 +80.210.26.126 +80.210.26.179 +80.210.26.219 +80.210.26.228 +80.210.26.240 +80.210.26.251 +80.210.27.111 +80.210.27.140 +80.210.27.148 +80.210.27.153 +80.210.27.187 +80.210.27.213 +80.210.27.221 +80.210.28.38 +80.210.28.52 +80.210.28.88 +80.210.28.161 +80.210.29.27 +80.210.29.50 +80.210.29.57 +80.210.29.105 +80.210.29.132 +80.210.29.155 +80.210.29.168 +80.210.29.175 +80.210.29.180 +80.210.29.184 +80.210.29.224 +80.210.30.2 +80.210.30.57 +80.210.30.129 +80.210.30.152 +80.210.30.155 +80.210.30.170 +80.210.30.202 +80.210.30.251 +80.210.31.23 +80.210.31.60 +80.210.31.74 +80.210.31.112 +80.210.31.133 +80.210.31.151 +80.210.31.184 +80.210.31.218 +80.210.31.223 +80.210.31.229 +80.210.32.40 +80.210.32.63 +80.210.32.103 +80.210.32.104 +80.210.32.116 +80.210.32.131 +80.210.32.143 +80.210.32.146 +80.210.32.176 +80.210.32.201 +80.210.32.235 +80.210.33.52 +80.210.33.166 +80.210.33.227 +80.210.33.238 +80.210.34.40 +80.210.34.71 +80.210.34.89 +80.210.34.137 +80.210.34.150 +80.210.34.172 +80.210.34.230 +80.210.35.144 +80.210.35.159 +80.210.35.171 +80.210.35.175 +80.210.36.17 +80.210.36.37 +80.210.36.68 +80.210.36.111 +80.210.36.121 +80.210.36.169 +80.210.36.196 +80.210.36.201 +80.210.36.203 +80.210.37.14 +80.210.37.100 +80.210.37.115 +80.210.37.209 +80.210.38.53 +80.210.38.67 +80.210.38.101 +80.210.38.127 +80.210.38.130 +80.210.38.178 +80.210.38.187 +80.210.38.205 +80.210.38.222 +80.210.38.251 +80.210.39.57 +80.210.39.81 +80.210.39.98 +80.210.39.153 +80.210.39.167 +80.210.39.205 +80.210.39.221 +80.210.39.230 +80.210.40.34 +80.210.40.72 +80.210.40.98 +80.210.40.109 +80.210.40.147 +80.210.40.192 +80.210.40.195 +80.210.40.213 +80.210.40.233 +80.210.41.13 +80.210.41.22 +80.210.41.34 +80.210.41.113 +80.210.41.116 +80.210.41.236 +80.210.42.3 +80.210.42.11 +80.210.42.18 +80.210.42.49 +80.210.42.69 +80.210.42.149 +80.210.42.161 +80.210.42.169 +80.210.43.101 +80.210.43.108 +80.210.43.131 +80.210.43.141 +80.210.43.169 +80.210.43.184 +80.210.43.217 +80.210.44.21 +80.210.44.59 +80.210.44.71 +80.210.44.105 +80.210.44.147 +80.210.44.184 +80.210.44.187 +80.210.44.245 +80.210.45.59 +80.210.45.107 +80.210.45.234 +80.210.46.105 +80.210.46.171 +80.210.46.202 +80.210.46.246 +80.210.47.68 +80.210.47.93 +80.210.47.94 +80.210.47.149 +80.210.48.24 +80.210.48.26 +80.210.48.48 +80.210.48.57 +80.210.48.84 +80.210.48.98 +80.210.48.147 +80.210.48.177 +80.210.49.41 +80.210.49.70 +80.210.49.79 +80.210.49.83 +80.210.49.173 +80.210.49.196 +80.210.49.232 +80.210.50.30 +80.210.50.35 +80.210.50.40 +80.210.50.173 +80.210.51.50 +80.210.51.82 +80.210.51.202 +80.210.52.240 +80.210.53.34 +80.210.53.35 +80.210.53.70 +80.210.53.97 +80.210.53.209 +80.210.53.213 +80.210.54.38 +80.210.54.40 +80.210.54.49 +80.210.54.52 +80.210.54.60 +80.210.54.68 +80.210.54.95 +80.210.54.118 +80.210.54.185 +80.210.54.199 +80.210.54.217 +80.210.54.221 +80.210.54.243 +80.210.55.19 +80.210.55.56 +80.210.55.87 +80.210.55.118 +80.210.55.120 +80.210.55.131 +80.210.55.146 +80.210.55.161 +80.210.56.0 +80.210.56.83 +80.210.56.102 +80.210.56.141 +80.210.56.143 +80.210.56.169 +80.210.56.186 +80.210.56.199 +80.210.56.205 +80.210.56.225 +80.210.56.233 +80.210.56.253 +80.210.57.117 +80.210.58.1 +80.210.58.7 +80.210.58.29 +80.210.58.59 +80.210.58.73 +80.210.58.86 +80.210.58.89 +80.210.58.111 +80.210.58.117 +80.210.58.133 +80.210.58.142 +80.210.58.146 +80.210.58.163 +80.210.58.229 +80.210.58.243 +80.210.59.100 +80.210.60.109 +80.210.60.150 +80.210.60.155 +80.210.60.211 +80.210.61.20 +80.210.61.51 +80.210.61.123 +80.210.61.124 +80.210.61.128 +80.210.61.141 +80.210.61.159 +80.210.61.174 +80.210.61.178 +80.210.62.57 +80.210.62.109 +80.210.62.128 +80.210.62.146 +80.210.62.155 +80.210.62.173 +80.210.62.195 +80.210.62.203 +80.210.62.206 +80.210.62.210 +80.210.62.237 +80.210.63.3 +80.210.63.13 +80.210.63.21 +80.210.63.26 +80.210.63.63 +80.210.63.107 +80.210.63.142 +80.210.63.153 +80.210.63.185 +80.210.63.194 +80.210.63.233 +80.210.63.243 +80.210.63.251 +80.210.128.198 +80.210.131.213 +80.210.132.9 +80.210.132.50 +80.210.132.100 +80.210.132.101 +80.210.132.191 +80.210.133.249 +80.210.134.163 +80.210.135.10 +80.210.135.125 +80.210.137.2 +80.210.139.10 +80.210.139.68 +80.210.141.214 +80.210.141.239 +80.210.142.6 +80.210.142.54 +80.210.143.142 +80.210.160.23 +80.210.164.73 +80.210.164.78 +80.210.165.60 +80.210.165.199 +80.210.168.207 +80.210.170.150 +80.210.171.56 +80.210.171.115 +80.210.172.44 +80.210.172.76 +80.210.172.118 +80.210.174.109 +80.210.176.194 +80.210.177.164 +80.250.200.1 +80.253.133.144 +80.253.133.145 +80.253.152.222 +81.12.1.27 +81.12.5.22 +81.12.5.152 +81.12.5.229 +81.12.5.247 +81.12.5.250 +81.12.6.34 +81.12.6.58 +81.12.7.57 +81.12.7.219 +81.12.7.220 +81.12.7.221 +81.12.12.233 +81.12.24.167 +81.12.24.170 +81.12.24.198 +81.12.24.203 +81.12.24.205 +81.12.24.224 +81.12.24.250 +81.12.25.25 +81.12.26.27 +81.12.26.28 +81.12.26.29 +81.12.26.31 +81.12.26.32 +81.12.26.112 +81.12.26.113 +81.12.26.114 +81.12.26.115 +81.12.26.116 +81.12.26.117 +81.12.26.118 +81.12.26.119 +81.12.26.127 +81.12.26.128 +81.12.26.129 +81.12.27.123 +81.12.27.252 +81.12.32.164 +81.12.33.99 +81.12.36.37 +81.12.36.58 +81.12.36.94 +81.12.36.155 +81.12.36.178 +81.12.36.190 +81.12.36.217 +81.12.39.30 +81.12.39.136 +81.12.39.142 +81.12.39.145 +81.12.39.192 +81.12.39.217 +81.12.39.232 +81.12.39.234 +81.12.39.245 +81.12.40.14 +81.12.40.86 +81.12.40.165 +81.12.41.54 +81.12.41.178 +81.12.41.251 +81.12.41.252 +81.12.41.253 +81.12.41.254 +81.12.43.66 +81.12.43.238 +81.12.43.242 +81.12.44.26 +81.12.47.94 +81.12.47.234 +81.12.47.235 +81.12.47.236 +81.12.47.237 +81.12.47.238 +81.12.52.94 +81.12.53.132 +81.12.53.163 +81.12.53.235 +81.12.53.236 +81.12.64.10 +81.12.64.11 +81.12.64.13 +81.12.64.14 +81.12.64.66 +81.12.64.67 +81.12.64.68 +81.12.64.69 +81.12.64.71 +81.12.64.72 +81.12.64.73 +81.12.64.77 +81.12.65.42 +81.12.65.135 +81.12.69.174 +81.12.70.98 +81.12.70.178 +81.12.71.10 +81.12.71.98 +81.12.72.108 +81.12.72.162 +81.12.72.238 +81.12.72.251 +81.12.73.150 +81.12.74.16 +81.12.75.30 +81.12.75.58 +81.12.75.243 +81.12.76.250 +81.12.78.130 +81.12.79.82 +81.12.85.237 +81.12.85.241 +81.12.85.245 +81.12.87.2 +81.12.87.136 +81.12.87.138 +81.12.87.139 +81.12.87.141 +81.12.87.142 +81.12.87.226 +81.12.88.51 +81.12.89.74 +81.12.89.86 +81.12.89.243 +81.12.90.74 +81.12.90.246 +81.12.93.106 +81.12.94.74 +81.12.94.106 +81.12.95.18 +81.12.98.2 +81.12.98.125 +81.12.99.22 +81.12.99.98 +81.12.99.99 +81.12.99.100 +81.12.99.101 +81.12.99.102 +81.12.99.103 +81.12.99.105 +81.12.99.138 +81.12.99.227 +81.12.100.174 +81.12.100.216 +81.12.100.222 +81.12.102.145 +81.12.102.149 +81.12.102.153 +81.12.104.34 +81.12.104.36 +81.12.104.37 +81.12.104.38 +81.12.104.39 +81.12.104.40 +81.12.104.42 +81.12.104.43 +81.12.104.44 +81.12.104.45 +81.12.104.46 +81.12.104.66 +81.12.104.67 +81.12.104.68 +81.12.104.69 +81.12.104.70 +81.12.104.71 +81.12.104.73 +81.12.104.74 +81.12.105.50 +81.12.105.56 +81.12.105.57 +81.12.105.58 +81.12.105.62 +81.12.106.27 +81.12.106.130 +81.12.107.26 +81.12.108.90 +81.12.108.92 +81.12.108.93 +81.12.108.94 +81.12.108.98 +81.12.108.115 +81.12.108.116 +81.12.108.118 +81.12.109.19 +81.12.109.22 +81.12.109.134 +81.12.109.170 +81.12.109.174 +81.12.110.130 +81.12.111.166 +81.12.112.10 +81.12.113.18 +81.12.113.101 +81.12.113.118 +81.12.113.196 +81.12.113.198 +81.12.113.212 +81.12.114.62 +81.12.116.10 +81.12.116.174 +81.12.117.114 +81.12.117.119 +81.12.117.120 +81.12.118.34 +81.12.118.35 +81.12.118.36 +81.12.118.37 +81.12.118.38 +81.12.118.39 +81.12.118.41 +81.12.118.42 +81.12.118.43 +81.12.118.44 +81.12.118.45 +81.12.118.47 +81.12.118.48 +81.12.118.49 +81.12.118.50 +81.12.118.51 +81.12.118.52 +81.12.118.53 +81.12.118.57 +81.12.118.58 +81.12.118.59 +81.12.118.60 +81.12.118.61 +81.12.118.62 +81.12.118.210 +81.12.118.211 +81.12.118.212 +81.12.118.213 +81.12.119.114 +81.12.119.138 +81.12.119.214 +81.12.120.26 +81.12.120.62 +81.12.120.132 +81.12.120.220 +81.12.120.222 +81.12.120.234 +81.12.120.235 +81.12.120.236 +81.12.121.19 +81.12.122.118 +81.12.123.66 +81.12.124.33 +81.12.124.105 +81.12.124.114 +81.12.125.130 +81.12.125.146 +81.12.125.148 +81.12.125.152 +81.12.125.153 +81.12.125.156 +81.12.127.54 +81.16.112.6 +81.16.112.154 +81.16.112.213 +81.16.112.215 +81.16.113.82 +81.16.113.85 +81.16.113.88 +81.16.113.110 +81.16.113.118 +81.16.113.122 +81.16.113.165 +81.16.114.84 +81.16.114.86 +81.16.114.127 +81.16.114.134 +81.16.114.155 +81.16.116.18 +81.16.116.89 +81.16.116.92 +81.16.116.97 +81.16.116.211 +81.16.116.220 +81.16.116.228 +81.16.116.233 +81.16.117.127 +81.16.117.129 +81.16.117.205 +81.16.118.109 +81.16.118.156 +81.16.119.27 +81.16.119.55 +81.16.119.78 +81.16.119.196 +81.16.119.229 +81.16.120.60 +81.16.120.93 +81.16.120.119 +81.16.120.122 +81.16.120.236 +81.16.121.93 +81.16.121.114 +81.16.121.115 +81.16.121.178 +81.16.121.221 +81.16.121.226 +81.16.122.194 +81.16.122.212 +81.16.123.3 +81.16.123.248 +81.16.124.33 +81.16.124.54 +81.16.124.67 +81.16.124.71 +81.16.124.72 +81.16.124.80 +81.16.124.147 +81.16.124.148 +81.16.124.184 +81.16.124.243 +81.16.125.8 +81.16.125.9 +81.16.125.21 +81.16.125.46 +81.16.125.181 +81.16.125.190 +81.16.125.194 +81.16.125.213 +81.16.125.221 +81.16.126.44 +81.16.126.67 +81.16.126.81 +81.16.126.92 +81.16.126.137 +81.16.126.144 +81.16.126.196 +81.16.127.47 +81.16.127.72 +81.16.127.94 +81.16.127.112 +81.16.127.155 +81.16.127.160 +81.16.127.186 +81.16.127.189 +81.16.127.192 +81.16.127.199 +81.16.127.204 +81.16.127.205 +81.16.127.254 +81.28.36.146 +81.28.43.30 +81.28.43.162 +81.28.47.2 +81.28.50.2 +81.28.57.2 +81.28.60.102 +81.28.252.58 +81.28.252.59 +81.28.252.136 +81.28.252.171 +81.28.252.243 +81.28.253.103 +81.28.253.144 +81.28.253.157 +81.28.253.175 +81.29.248.22 +81.29.248.38 +81.29.248.109 +81.29.248.202 +81.29.249.86 +81.29.249.190 +81.29.249.242 +81.29.249.246 +81.29.255.170 +81.29.255.218 +81.31.250.2 +81.31.250.5 +81.31.250.6 +81.31.250.11 +81.31.250.13 +81.31.250.15 +81.31.250.18 +81.31.250.21 +81.31.250.130 +81.31.250.146 +81.31.250.147 +81.31.250.232 +81.31.250.250 +81.90.145.19 +81.90.145.37 +81.90.145.76 +81.90.145.94 +81.90.145.116 +81.90.145.120 +81.90.146.66 +81.90.148.106 +81.90.149.178 +81.90.149.234 +81.90.149.254 +81.90.152.38 +81.90.152.86 +81.90.154.178 +81.90.154.179 +81.90.154.180 +81.90.154.181 +81.90.154.182 +81.90.154.183 +81.90.154.184 +81.90.154.186 +81.90.154.187 +81.90.154.188 +81.90.154.190 +81.90.157.82 +81.90.158.110 +81.90.159.158 +81.91.129.228 +81.91.132.20 +81.91.136.134 +81.91.137.34 +81.91.138.3 +81.91.138.67 +81.91.138.114 +81.91.138.182 +81.91.139.18 +81.91.144.116 +81.91.145.2 +81.91.145.7 +81.91.145.66 +81.91.145.78 +81.91.145.192 +81.91.145.193 +81.91.145.194 +81.91.145.195 +81.91.146.30 +81.91.146.138 +81.91.146.202 +81.91.146.203 +81.91.146.214 +81.91.147.30 +81.91.150.66 +81.91.150.190 +81.91.152.6 +81.91.152.70 +81.91.152.86 +81.91.153.2 +81.91.153.10 +81.91.153.26 +81.91.153.210 +81.91.153.211 +81.91.153.234 +81.91.154.69 +81.91.154.82 +81.91.154.230 +81.91.155.2 +81.91.155.22 +81.91.156.67 +81.91.156.134 +81.91.156.150 +81.91.157.6 +81.91.157.50 +81.91.157.51 +81.91.157.134 +81.91.158.126 +81.91.158.130 +81.91.158.132 +81.91.158.133 +81.91.158.218 +81.91.159.34 +81.91.159.82 +81.91.159.106 +81.91.159.170 +81.91.159.234 +81.163.0.33 +81.163.0.79 +81.163.0.96 +81.163.0.97 +81.163.0.110 +81.163.0.117 +81.163.0.138 +81.163.1.66 +81.163.1.105 +81.163.1.106 +81.163.1.107 +81.163.1.108 +81.163.1.109 +81.163.1.180 +81.163.3.1 +81.163.3.2 +81.163.4.85 +81.163.6.30 +81.163.6.107 +81.163.7.49 +81.163.7.57 +81.163.7.71 +82.99.194.34 +82.99.194.106 +82.99.194.197 +82.99.195.82 +82.99.195.110 +82.99.195.114 +82.99.201.162 +82.99.201.163 +82.99.201.164 +82.99.201.166 +82.99.201.180 +82.99.201.182 +82.99.202.157 +82.99.202.209 +82.99.202.210 +82.99.202.218 +82.99.202.225 +82.99.202.227 +82.99.204.1 +82.99.204.18 +82.99.204.21 +82.99.204.249 +82.99.206.98 +82.99.206.99 +82.99.206.191 +82.99.213.62 +82.99.213.206 +82.99.214.106 +82.99.214.107 +82.99.214.109 +82.99.214.110 +82.99.230.170 +82.99.247.45 +82.99.248.106 +82.99.248.114 +82.99.248.214 +82.99.249.230 +82.99.250.178 +82.99.250.228 +82.99.252.247 +82.99.253.81 +82.99.254.250 +82.102.14.39 +82.102.14.248 +83.97.72.1 +83.97.72.50 +83.97.72.254 +83.147.193.5 +83.150.193.79 +84.47.225.198 +84.47.226.106 +84.47.226.108 +84.47.226.111 +84.47.226.242 +84.47.227.194 +84.47.236.57 +84.47.236.59 +84.47.236.74 +84.47.236.112 +84.47.236.113 +84.47.236.200 +84.47.239.21 +84.241.0.3 +84.241.0.62 +84.241.0.119 +84.241.0.123 +84.241.0.156 +84.241.0.223 +84.241.0.226 +84.241.0.232 +84.241.1.36 +84.241.1.71 +84.241.1.76 +84.241.1.99 +84.241.1.119 +84.241.1.203 +84.241.1.248 +84.241.1.254 +84.241.2.85 +84.241.2.90 +84.241.2.198 +84.241.2.206 +84.241.2.235 +84.241.3.50 +84.241.3.98 +84.241.3.105 +84.241.3.137 +84.241.3.149 +84.241.3.199 +84.241.3.235 +84.241.4.8 +84.241.4.33 +84.241.4.68 +84.241.4.86 +84.241.4.140 +84.241.4.192 +84.241.5.18 +84.241.5.20 +84.241.5.45 +84.241.5.52 +84.241.5.79 +84.241.5.94 +84.241.5.140 +84.241.5.176 +84.241.5.253 +84.241.6.19 +84.241.6.72 +84.241.6.105 +84.241.6.150 +84.241.6.165 +84.241.6.197 +84.241.6.236 +84.241.6.248 +84.241.7.8 +84.241.7.46 +84.241.7.48 +84.241.7.61 +84.241.7.136 +84.241.7.233 +84.241.8.39 +84.241.8.50 +84.241.8.225 +84.241.8.238 +84.241.9.7 +84.241.9.51 +84.241.9.69 +84.241.9.79 +84.241.9.96 +84.241.9.105 +84.241.9.108 +84.241.9.115 +84.241.9.171 +84.241.9.191 +84.241.9.200 +84.241.9.223 +84.241.9.233 +84.241.9.234 +84.241.9.236 +84.241.10.57 +84.241.10.70 +84.241.10.99 +84.241.10.131 +84.241.10.164 +84.241.10.193 +84.241.10.212 +84.241.10.216 +84.241.11.10 +84.241.11.26 +84.241.11.61 +84.241.11.66 +84.241.11.73 +84.241.11.138 +84.241.11.144 +84.241.11.145 +84.241.11.172 +84.241.11.190 +84.241.11.211 +84.241.12.24 +84.241.12.49 +84.241.12.69 +84.241.12.104 +84.241.12.112 +84.241.12.138 +84.241.12.153 +84.241.12.165 +84.241.12.197 +84.241.13.93 +84.241.13.133 +84.241.13.136 +84.241.13.171 +84.241.13.210 +84.241.13.235 +84.241.13.241 +84.241.14.30 +84.241.14.50 +84.241.14.126 +84.241.14.157 +84.241.14.198 +84.241.14.239 +84.241.15.2 +84.241.15.6 +84.241.15.56 +84.241.15.75 +84.241.15.127 +84.241.15.147 +84.241.15.161 +84.241.15.189 +84.241.15.203 +84.241.15.208 +84.241.15.220 +84.241.15.230 +84.241.15.253 +84.241.15.254 +84.241.16.13 +84.241.16.17 +84.241.16.44 +84.241.16.54 +84.241.16.66 +84.241.16.126 +84.241.16.165 +84.241.16.195 +84.241.16.219 +84.241.16.222 +84.241.16.232 +84.241.18.90 +84.241.18.108 +84.241.18.161 +84.241.18.185 +84.241.18.195 +84.241.18.197 +84.241.18.246 +84.241.19.9 +84.241.19.22 +84.241.19.52 +84.241.19.111 +84.241.19.115 +84.241.19.134 +84.241.19.172 +84.241.19.186 +84.241.19.229 +84.241.20.91 +84.241.21.48 +84.241.21.127 +84.241.21.213 +84.241.21.216 +84.241.21.245 +84.241.22.38 +84.241.22.52 +84.241.22.61 +84.241.22.94 +84.241.22.157 +84.241.22.245 +84.241.23.238 +84.241.24.10 +84.241.24.55 +84.241.24.58 +84.241.24.59 +84.241.24.132 +84.241.24.204 +84.241.24.207 +84.241.24.233 +84.241.24.249 +84.241.24.254 +84.241.25.4 +84.241.25.30 +84.241.25.79 +84.241.25.124 +84.241.25.131 +84.241.25.178 +84.241.25.187 +84.241.25.200 +84.241.25.205 +84.241.25.239 +84.241.25.244 +84.241.26.9 +84.241.26.29 +84.241.26.34 +84.241.26.40 +84.241.26.58 +84.241.26.65 +84.241.26.98 +84.241.26.100 +84.241.26.149 +84.241.26.240 +84.241.27.13 +84.241.27.86 +84.241.27.124 +84.241.27.145 +84.241.27.180 +84.241.27.189 +84.241.28.10 +84.241.28.27 +84.241.28.36 +84.241.28.49 +84.241.28.80 +84.241.28.85 +84.241.28.101 +84.241.29.26 +84.241.29.70 +84.241.29.119 +84.241.29.129 +84.241.29.194 +84.241.29.206 +84.241.29.228 +84.241.30.13 +84.241.30.33 +84.241.30.57 +84.241.30.96 +84.241.30.103 +84.241.30.111 +84.241.30.190 +84.241.30.224 +84.241.31.0 +84.241.31.7 +84.241.31.53 +84.241.31.90 +84.241.31.100 +84.241.32.19 +84.241.32.78 +84.241.32.89 +84.241.32.203 +84.241.32.254 +84.241.33.19 +84.241.33.183 +84.241.34.132 +84.241.35.0 +84.241.35.155 +84.241.36.15 +84.241.36.86 +84.241.36.104 +84.241.36.116 +84.241.36.138 +84.241.36.150 +84.241.37.79 +84.241.37.202 +84.241.37.211 +84.241.37.234 +84.241.38.130 +84.241.38.131 +84.241.38.177 +84.241.40.188 +84.241.41.21 +84.241.41.131 +84.241.41.197 +84.241.41.225 +84.241.42.178 +84.241.43.18 +84.241.43.118 +84.241.43.119 +84.241.44.96 +84.241.44.106 +84.241.44.211 +84.241.44.245 +84.241.45.14 +84.241.45.183 +84.241.45.194 +84.241.46.127 +84.241.46.177 +84.241.47.6 +84.241.47.109 +84.241.47.190 +84.241.47.244 +84.241.51.38 +84.241.51.93 +84.241.53.153 +84.241.53.175 +84.241.53.190 +84.241.53.243 +84.241.54.101 +84.241.55.13 +84.241.55.14 +84.241.55.30 +84.241.55.57 +84.241.55.234 +84.241.55.250 +84.241.56.80 +84.241.59.241 +84.241.59.249 +84.241.59.251 +84.241.59.252 +84.241.59.253 +84.241.59.254 +84.241.60.21 +84.241.60.253 +84.241.61.1 +84.241.61.193 +84.241.63.25 +85.9.65.241 +85.9.65.242 +85.9.71.126 +85.9.71.233 +85.9.79.217 +85.9.85.111 +85.9.85.113 +85.9.86.54 +85.9.86.239 +85.9.86.254 +85.9.86.255 +85.9.87.53 +85.9.87.54 +85.9.87.62 +85.9.87.102 +85.9.87.121 +85.9.91.25 +85.9.91.211 +85.9.92.36 +85.9.94.166 +85.9.94.182 +85.9.94.208 +85.9.94.238 +85.9.95.234 +85.9.97.10 +85.9.97.179 +85.9.97.209 +85.9.105.36 +85.9.105.39 +85.9.105.40 +85.9.105.42 +85.9.105.43 +85.9.105.44 +85.9.105.45 +85.9.105.202 +85.9.105.204 +85.9.105.205 +85.9.107.178 +85.9.107.179 +85.9.107.180 +85.9.107.187 +85.9.107.226 +85.9.108.169 +85.9.109.55 +85.9.111.9 +85.9.111.11 +85.9.111.12 +85.9.111.15 +85.9.111.17 +85.9.111.18 +85.9.111.22 +85.9.111.124 +85.9.113.235 +85.9.113.237 +85.9.113.238 +85.9.113.239 +85.9.113.240 +85.9.113.242 +85.9.113.244 +85.9.114.84 +85.9.119.237 +85.9.119.255 +85.9.121.33 +85.9.123.72 +85.9.123.252 +85.9.124.89 +85.9.124.111 +85.9.124.233 +85.9.124.245 +85.9.125.68 +85.15.1.115 +85.15.1.120 +85.15.1.188 +85.15.3.5 +85.15.45.187 +85.15.47.176 +85.15.47.236 +85.133.130.99 +85.133.130.114 +85.133.130.132 +85.133.133.102 +85.133.133.104 +85.133.133.109 +85.133.133.111 +85.133.133.138 +85.133.133.148 +85.133.133.161 +85.133.133.162 +85.133.133.165 +85.133.133.173 +85.133.133.192 +85.133.133.199 +85.133.133.201 +85.133.133.214 +85.133.133.225 +85.133.133.235 +85.133.133.239 +85.133.134.5 +85.133.134.28 +85.133.134.30 +85.133.134.40 +85.133.134.53 +85.133.134.63 +85.133.134.71 +85.133.134.72 +85.133.134.78 +85.133.134.87 +85.133.134.112 +85.133.134.118 +85.133.134.122 +85.133.134.129 +85.133.134.138 +85.133.134.142 +85.133.134.152 +85.133.134.173 +85.133.134.175 +85.133.134.184 +85.133.134.186 +85.133.134.189 +85.133.134.195 +85.133.134.204 +85.133.134.211 +85.133.134.242 +85.133.135.146 +85.133.135.190 +85.133.136.242 +85.133.137.190 +85.133.138.10 +85.133.138.53 +85.133.138.99 +85.133.138.101 +85.133.138.130 +85.133.138.154 +85.133.138.168 +85.133.138.178 +85.133.138.187 +85.133.138.188 +85.133.138.209 +85.133.139.34 +85.133.139.45 +85.133.139.79 +85.133.139.81 +85.133.139.83 +85.133.139.88 +85.133.139.104 +85.133.139.114 +85.133.139.120 +85.133.139.121 +85.133.139.130 +85.133.139.132 +85.133.139.183 +85.133.139.187 +85.133.139.198 +85.133.139.199 +85.133.139.207 +85.133.139.210 +85.133.139.215 +85.133.139.219 +85.133.139.237 +85.133.139.240 +85.133.145.37 +85.133.145.159 +85.133.149.42 +85.133.152.3 +85.133.152.25 +85.133.152.27 +85.133.152.34 +85.133.152.49 +85.133.152.57 +85.133.152.80 +85.133.152.87 +85.133.152.90 +85.133.152.103 +85.133.152.123 +85.133.152.136 +85.133.152.137 +85.133.152.146 +85.133.152.152 +85.133.152.155 +85.133.152.172 +85.133.152.184 +85.133.152.185 +85.133.152.198 +85.133.152.217 +85.133.152.221 +85.133.152.222 +85.133.152.227 +85.133.152.232 +85.133.152.236 +85.133.152.238 +85.133.152.244 +85.133.152.252 +85.133.153.171 +85.133.154.130 +85.133.154.154 +85.133.154.155 +85.133.154.190 +85.133.155.130 +85.133.155.131 +85.133.155.132 +85.133.155.133 +85.133.155.134 +85.133.155.162 +85.133.156.179 +85.133.157.4 +85.133.157.26 +85.133.157.47 +85.133.157.72 +85.133.157.79 +85.133.157.86 +85.133.157.90 +85.133.157.93 +85.133.157.103 +85.133.157.106 +85.133.157.112 +85.133.157.113 +85.133.157.128 +85.133.157.129 +85.133.157.152 +85.133.157.153 +85.133.157.157 +85.133.157.174 +85.133.157.175 +85.133.157.180 +85.133.157.197 +85.133.157.217 +85.133.157.240 +85.133.158.122 +85.133.158.195 +85.133.158.196 +85.133.158.198 +85.133.158.201 +85.133.160.180 +85.133.160.226 +85.133.161.24 +85.133.161.56 +85.133.161.87 +85.133.161.202 +85.133.162.16 +85.133.162.17 +85.133.162.19 +85.133.162.44 +85.133.162.95 +85.133.162.140 +85.133.162.151 +85.133.162.172 +85.133.162.182 +85.133.163.2 +85.133.163.31 +85.133.163.85 +85.133.163.94 +85.133.163.114 +85.133.163.158 +85.133.167.62 +85.133.167.107 +85.133.167.108 +85.133.167.246 +85.133.168.130 +85.133.168.202 +85.133.171.34 +85.133.171.35 +85.133.171.36 +85.133.171.37 +85.133.171.38 +85.133.171.178 +85.133.172.3 +85.133.172.68 +85.133.172.84 +85.133.172.102 +85.133.172.118 +85.133.172.129 +85.133.172.144 +85.133.172.158 +85.133.172.162 +85.133.172.164 +85.133.172.172 +85.133.172.180 +85.133.172.206 +85.133.172.211 +85.133.172.213 +85.133.172.217 +85.133.172.218 +85.133.172.228 +85.133.172.244 +85.133.173.66 +85.133.173.90 +85.133.173.98 +85.133.175.18 +85.133.175.110 +85.133.175.199 +85.133.176.13 +85.133.176.127 +85.133.178.30 +85.133.179.46 +85.133.179.189 +85.133.180.170 +85.133.181.26 +85.133.181.102 +85.133.181.118 +85.133.181.190 +85.133.182.12 +85.133.182.17 +85.133.182.23 +85.133.182.27 +85.133.182.36 +85.133.182.40 +85.133.182.42 +85.133.182.52 +85.133.182.75 +85.133.182.86 +85.133.182.88 +85.133.182.98 +85.133.182.106 +85.133.182.110 +85.133.182.116 +85.133.182.121 +85.133.182.125 +85.133.182.134 +85.133.182.141 +85.133.182.158 +85.133.182.193 +85.133.182.205 +85.133.182.217 +85.133.182.222 +85.133.182.236 +85.133.182.240 +85.133.182.241 +85.133.182.246 +85.133.183.62 +85.133.183.106 +85.133.183.110 +85.133.183.182 +85.133.183.186 +85.133.184.51 +85.133.184.158 +85.133.185.26 +85.133.185.102 +85.133.185.106 +85.133.185.110 +85.133.185.130 +85.133.185.150 +85.133.186.26 +85.133.186.149 +85.133.186.178 +85.133.186.179 +85.133.186.180 +85.133.186.181 +85.133.186.182 +85.133.186.190 +85.133.187.59 +85.133.187.178 +85.133.188.218 +85.133.189.147 +85.133.189.222 +85.133.190.40 +85.133.190.171 +85.133.190.172 +85.133.190.173 +85.133.190.174 +85.133.191.198 +85.133.193.36 +85.133.193.41 +85.133.193.54 +85.133.193.72 +85.133.193.74 +85.133.193.114 +85.133.193.136 +85.133.197.203 +85.133.202.86 +85.133.205.212 +85.133.212.234 +85.133.212.251 +85.133.216.14 +85.133.216.41 +85.133.217.68 +85.133.225.160 +85.133.225.162 +85.133.225.172 +85.133.225.187 +85.133.229.115 +85.133.229.120 +85.133.229.129 +85.133.229.210 +85.133.237.26 +85.133.237.93 +85.133.240.33 +85.133.240.35 +85.133.240.38 +85.133.240.41 +85.133.240.44 +85.133.240.48 +85.133.240.59 +85.133.240.60 +85.133.240.61 +85.133.240.62 +85.133.240.151 +85.133.240.155 +85.133.240.156 +85.133.240.157 +85.133.240.159 +85.133.240.161 +85.133.240.165 +85.133.240.166 +85.133.240.167 +85.133.240.171 +85.133.240.173 +85.133.240.174 +85.133.240.175 +85.133.240.176 +85.133.240.178 +85.133.240.179 +85.133.240.180 +85.133.240.181 +85.133.240.183 +85.133.240.184 +85.133.240.185 +85.133.240.186 +85.133.240.188 +85.133.240.189 +85.133.240.197 +85.133.240.199 +85.133.240.203 +85.133.240.204 +85.133.240.205 +85.133.240.206 +85.133.240.207 +85.133.240.208 +85.133.240.210 +85.133.240.212 +85.133.241.116 +85.133.241.138 +85.133.241.188 +85.133.253.101 +85.133.253.118 +85.133.253.119 +85.133.254.71 +85.185.4.19 +85.185.4.20 +85.185.4.62 +85.185.4.146 +85.185.4.218 +85.185.4.219 +85.185.6.1 +85.185.6.2 +85.185.6.3 +85.185.6.4 +85.185.6.5 +85.185.6.6 +85.185.6.8 +85.185.6.9 +85.185.6.10 +85.185.6.11 +85.185.6.12 +85.185.6.13 +85.185.6.209 +85.185.8.75 +85.185.13.35 +85.185.13.41 +85.185.13.161 +85.185.14.41 +85.185.17.124 +85.185.20.185 +85.185.20.226 +85.185.21.78 +85.185.22.114 +85.185.28.141 +85.185.29.6 +85.185.29.30 +85.185.29.90 +85.185.29.178 +85.185.41.85 +85.185.41.87 +85.185.41.104 +85.185.41.123 +85.185.42.106 +85.185.52.176 +85.185.54.229 +85.185.55.245 +85.185.55.252 +85.185.56.80 +85.185.57.27 +85.185.58.20 +85.185.60.200 +85.185.62.57 +85.185.62.72 +85.185.62.196 +85.185.64.8 +85.185.64.61 +85.185.64.89 +85.185.64.90 +85.185.64.91 +85.185.64.92 +85.185.64.93 +85.185.64.205 +85.185.64.219 +85.185.66.81 +85.185.66.179 +85.185.66.193 +85.185.66.216 +85.185.66.225 +85.185.67.38 +85.185.67.43 +85.185.67.165 +85.185.67.177 +85.185.67.178 +85.185.75.11 +85.185.75.38 +85.185.75.187 +85.185.75.210 +85.185.75.211 +85.185.75.212 +85.185.76.130 +85.185.76.131 +85.185.76.132 +85.185.76.133 +85.185.76.134 +85.185.76.138 +85.185.76.142 +85.185.76.194 +85.185.81.82 +85.185.81.99 +85.185.84.194 +85.185.84.226 +85.185.86.211 +85.185.88.133 +85.185.88.137 +85.185.88.139 +85.185.93.114 +85.185.95.185 +85.185.104.17 +85.185.105.101 +85.185.105.104 +85.185.105.217 +85.185.106.20 +85.185.110.226 +85.185.110.252 +85.185.123.6 +85.185.128.1 +85.185.130.136 +85.185.138.2 +85.185.138.5 +85.185.138.6 +85.185.138.18 +85.185.138.28 +85.185.138.29 +85.185.138.30 +85.185.138.58 +85.185.138.122 +85.185.138.154 +85.185.138.158 +85.185.138.178 +85.185.138.182 +85.185.138.186 +85.185.138.218 +85.185.138.226 +85.185.139.46 +85.185.140.67 +85.185.149.18 +85.185.149.24 +85.185.149.30 +85.185.158.146 +85.185.159.74 +85.185.159.76 +85.185.159.77 +85.185.161.214 +85.185.161.227 +85.185.161.240 +85.185.164.111 +85.185.165.124 +85.185.165.125 +85.185.165.126 +85.185.165.127 +85.185.166.138 +85.185.169.42 +85.185.170.2 +85.185.185.10 +85.185.190.141 +85.185.198.135 +85.185.201.16 +85.185.201.19 +85.185.202.98 +85.185.202.178 +85.185.202.242 +85.185.206.193 +85.185.206.222 +85.185.207.178 +85.185.216.106 +85.185.218.11 +85.185.218.15 +85.185.218.102 +85.185.218.127 +85.185.222.240 +85.185.229.9 +85.185.229.84 +85.185.229.90 +85.185.229.163 +85.185.232.42 +85.185.235.43 +85.185.235.59 +85.185.235.188 +85.185.235.190 +85.185.235.235 +85.185.236.131 +85.185.236.154 +85.185.237.58 +85.185.237.82 +85.185.237.83 +85.185.237.84 +85.185.237.85 +85.185.239.26 +85.185.239.178 +85.185.239.179 +85.185.239.180 +85.185.239.181 +85.185.239.182 +85.185.239.202 +85.185.241.106 +85.185.241.107 +85.185.241.170 +85.185.241.171 +85.185.241.172 +85.185.241.173 +85.185.241.236 +85.185.244.115 +85.185.244.178 +85.185.244.194 +85.185.244.242 +85.185.246.18 +85.185.246.218 +85.185.247.68 +85.185.247.138 +85.185.249.66 +85.185.249.67 +85.185.251.1 +85.185.251.57 +85.185.251.89 +85.185.251.177 +85.185.251.178 +85.185.251.193 +85.185.251.218 +85.185.252.10 +85.185.253.160 +85.185.254.15 +85.185.255.8 +85.185.255.20 +85.185.255.36 +85.185.255.133 +85.198.0.113 +85.198.2.5 +85.198.24.54 +85.198.24.166 +85.198.25.50 +85.198.25.66 +85.198.25.93 +85.198.25.98 +85.198.25.99 +85.198.25.100 +85.198.25.101 +85.198.25.102 +85.198.25.146 +85.198.25.147 +85.198.25.234 +85.198.26.82 +85.198.26.177 +85.198.26.250 +85.198.27.86 +85.198.28.10 +85.198.28.27 +85.198.28.38 +85.198.28.112 +85.198.28.123 +85.198.28.125 +85.198.28.197 +85.198.28.198 +85.198.29.33 +85.198.29.55 +85.198.29.56 +85.198.29.127 +85.198.29.161 +85.198.30.3 +85.198.30.4 +85.198.30.15 +85.198.30.31 +85.198.30.52 +85.198.30.60 +85.198.30.70 +85.198.30.84 +85.198.30.123 +85.198.30.213 +85.198.30.224 +85.198.30.244 +85.198.30.254 +85.198.31.18 +85.198.31.35 +85.198.31.40 +85.198.31.146 +85.198.31.169 +85.198.31.183 +85.198.31.192 +85.198.31.232 +85.204.30.148 +85.204.30.192 +85.204.76.71 +85.204.76.222 +85.204.77.105 +85.204.77.153 +85.204.77.217 +85.204.80.224 +85.204.83.87 +85.204.83.145 +85.204.84.122 +85.204.85.173 +85.204.87.34 +85.204.87.94 +85.204.88.117 +85.204.88.234 +85.204.90.40 +85.204.91.128 +85.204.91.149 +85.204.92.246 +85.204.93.198 +85.204.104.181 +85.204.104.218 +85.204.104.238 +85.204.105.43 +85.204.105.122 +85.204.105.169 +85.204.105.215 +85.204.105.253 +85.204.128.134 +85.204.128.252 +85.204.129.59 +85.204.130.34 +85.204.211.136 +85.204.211.220 +85.204.213.50 +85.204.213.86 +85.204.213.169 +85.204.215.106 +85.204.215.153 +85.204.217.165 +85.204.218.216 +85.204.219.79 +85.204.219.135 +85.204.220.90 +85.204.223.114 +85.208.253.12 +85.208.253.25 +85.208.253.43 +85.208.253.62 +85.208.253.119 +85.208.253.144 +85.208.253.152 +85.208.253.232 +85.208.253.243 +85.208.254.61 +85.208.254.63 +85.208.254.152 +85.208.254.209 +85.208.254.213 +86.104.32.106 +86.104.81.59 +86.104.96.213 +86.104.96.253 +86.104.104.116 +86.104.104.176 +86.104.107.7 +86.104.110.81 +86.104.243.225 +86.104.244.130 +86.104.244.136 +86.104.244.176 +86.104.244.225 +86.104.244.241 +86.105.44.36 +86.105.47.48 +86.105.47.82 +86.105.47.235 +86.106.142.1 +86.106.142.44 +86.106.142.117 +86.107.1.133 +86.107.8.2 +86.107.8.18 +86.107.8.75 +86.107.14.14 +86.107.14.21 +86.107.14.45 +86.107.14.48 +86.107.14.54 +86.107.14.62 +86.107.14.69 +86.107.14.77 +86.107.14.87 +86.107.14.102 +86.107.14.103 +86.107.14.122 +86.107.14.152 +86.107.14.156 +86.107.14.160 +86.107.14.165 +86.107.14.170 +86.107.14.181 +86.107.14.189 +86.107.14.191 +86.107.14.204 +86.107.14.219 +86.107.14.234 +86.107.14.236 +86.107.14.243 +86.107.14.244 +86.107.14.251 +86.107.15.4 +86.107.15.14 +86.107.15.16 +86.107.15.22 +86.107.15.32 +86.107.15.35 +86.107.15.56 +86.107.15.62 +86.107.15.90 +86.107.15.91 +86.107.15.123 +86.107.15.127 +86.107.15.198 +86.107.15.201 +86.107.15.212 +86.107.15.215 +86.107.15.221 +86.107.15.228 +86.107.15.236 +86.107.15.249 +86.107.15.254 +86.107.144.227 +86.107.146.168 +86.107.147.238 +86.107.150.6 +86.107.150.35 +86.107.151.171 +86.107.151.172 +86.107.152.190 +86.107.152.194 +86.107.152.248 +86.107.153.163 +86.107.153.179 +86.107.157.155 +86.107.157.232 +86.107.158.161 +86.107.158.181 +86.107.159.246 +86.109.45.154 +86.109.45.186 +86.109.45.187 +86.109.45.188 +86.109.54.134 +87.107.6.17 +87.107.6.23 +87.107.6.36 +87.107.6.59 +87.107.6.60 +87.107.6.84 +87.107.6.97 +87.107.6.202 +87.107.8.85 +87.107.8.87 +87.107.8.88 +87.107.8.89 +87.107.8.166 +87.107.9.170 +87.107.9.171 +87.107.9.173 +87.107.10.74 +87.107.10.241 +87.107.11.91 +87.107.11.93 +87.107.12.221 +87.107.16.30 +87.107.16.214 +87.107.16.222 +87.107.17.222 +87.107.18.2 +87.107.18.14 +87.107.18.22 +87.107.18.66 +87.107.18.82 +87.107.19.14 +87.107.19.68 +87.107.19.104 +87.107.19.196 +87.107.19.198 +87.107.28.90 +87.107.29.158 +87.107.30.6 +87.107.30.86 +87.107.30.166 +87.107.38.230 +87.107.44.2 +87.107.48.7 +87.107.48.26 +87.107.48.63 +87.107.48.66 +87.107.48.81 +87.107.48.124 +87.107.48.144 +87.107.48.159 +87.107.48.173 +87.107.48.184 +87.107.48.229 +87.107.48.233 +87.107.49.19 +87.107.49.88 +87.107.49.106 +87.107.49.163 +87.107.49.173 +87.107.49.195 +87.107.49.220 +87.107.52.99 +87.107.52.235 +87.107.54.69 +87.107.54.228 +87.107.54.233 +87.107.55.56 +87.107.55.134 +87.107.55.203 +87.107.68.38 +87.107.68.43 +87.107.68.49 +87.107.68.87 +87.107.73.116 +87.107.74.34 +87.107.74.36 +87.107.74.37 +87.107.75.32 +87.107.77.18 +87.107.77.94 +87.107.78.18 +87.107.78.130 +87.107.78.178 +87.107.78.242 +87.107.78.246 +87.107.79.62 +87.107.79.152 +87.107.82.201 +87.107.82.213 +87.107.83.18 +87.107.83.64 +87.107.83.77 +87.107.83.120 +87.107.83.238 +87.107.83.240 +87.107.87.5 +87.107.87.118 +87.107.94.3 +87.107.95.138 +87.107.100.160 +87.107.101.13 +87.107.101.26 +87.107.101.61 +87.107.101.63 +87.107.101.70 +87.107.101.72 +87.107.101.88 +87.107.101.97 +87.107.101.99 +87.107.101.100 +87.107.101.101 +87.107.101.251 +87.107.102.195 +87.107.102.227 +87.107.103.226 +87.107.103.250 +87.107.104.8 +87.107.104.177 +87.107.104.214 +87.107.105.27 +87.107.105.31 +87.107.108.185 +87.107.108.208 +87.107.108.221 +87.107.109.107 +87.107.110.10 +87.107.110.22 +87.107.110.138 +87.107.111.178 +87.107.111.180 +87.107.111.181 +87.107.111.182 +87.107.111.202 +87.107.111.203 +87.107.111.204 +87.107.111.206 +87.107.121.85 +87.107.124.117 +87.107.124.124 +87.107.132.35 +87.107.135.242 +87.107.136.42 +87.107.137.50 +87.107.137.82 +87.107.137.250 +87.107.137.251 +87.107.138.50 +87.107.138.52 +87.107.138.53 +87.107.138.58 +87.107.138.70 +87.107.138.151 +87.107.138.226 +87.107.139.218 +87.107.139.220 +87.107.139.222 +87.107.139.254 +87.107.141.1 +87.107.141.37 +87.107.141.79 +87.107.141.95 +87.107.141.105 +87.107.141.113 +87.107.142.18 +87.107.143.4 +87.107.143.12 +87.107.143.15 +87.107.143.19 +87.107.143.21 +87.107.143.29 +87.107.143.130 +87.107.143.131 +87.107.143.132 +87.107.143.133 +87.107.143.134 +87.107.143.135 +87.107.143.136 +87.107.143.137 +87.107.143.138 +87.107.143.139 +87.107.143.140 +87.107.143.141 +87.107.143.142 +87.107.144.54 +87.107.144.62 +87.107.144.217 +87.107.146.29 +87.107.146.39 +87.107.146.141 +87.107.146.243 +87.107.152.35 +87.107.152.211 +87.107.152.212 +87.107.152.214 +87.107.152.246 +87.107.153.116 +87.107.154.27 +87.107.154.69 +87.107.154.174 +87.107.155.82 +87.107.155.238 +87.107.164.15 +87.107.164.28 +87.107.164.29 +87.107.164.65 +87.107.164.179 +87.107.164.215 +87.107.165.65 +87.107.165.135 +87.107.166.6 +87.107.166.122 +87.107.166.150 +87.107.167.67 +87.107.167.92 +87.107.167.182 +87.107.167.183 +87.107.167.185 +87.107.167.186 +87.107.174.43 +87.107.174.114 +87.107.174.134 +87.107.174.139 +87.107.184.98 +87.107.185.204 +87.107.185.216 +87.236.38.233 +87.236.39.134 +87.236.166.3 +87.236.209.21 +87.236.209.173 +87.236.209.229 +87.236.210.54 +87.236.210.126 +87.236.211.5 +87.236.211.11 +87.236.211.70 +87.236.211.75 +87.236.211.119 +87.236.211.220 +87.236.211.222 +87.236.212.82 +87.236.212.132 +87.236.212.146 +87.236.212.178 +87.236.213.20 +87.236.213.33 +87.236.213.97 +87.236.213.129 +87.236.213.161 +87.236.213.249 +87.236.214.20 +87.236.214.47 +87.236.214.129 +87.236.214.225 +87.236.214.249 +87.236.215.169 +87.236.215.170 +87.236.215.227 +87.247.168.101 +87.247.168.190 +87.247.170.156 +87.247.171.4 +87.247.171.74 +87.247.174.146 +87.247.174.214 +87.247.176.68 +87.247.176.242 +87.247.176.252 +87.247.176.254 +87.248.130.28 +87.248.130.42 +87.248.130.91 +87.248.130.133 +87.248.130.186 +87.248.131.23 +87.248.131.45 +87.248.131.67 +87.248.131.125 +87.248.131.164 +87.248.131.207 +87.248.137.243 +87.248.138.33 +87.248.138.162 +87.248.138.163 +87.248.138.164 +87.248.138.165 +87.248.138.166 +87.248.139.186 +87.248.139.236 +87.248.139.237 +87.248.140.6 +87.248.140.65 +87.248.140.123 +87.248.140.171 +87.248.140.195 +87.248.140.197 +87.248.141.133 +87.248.141.142 +87.248.141.144 +87.248.141.152 +87.248.141.196 +87.248.141.211 +87.248.145.145 +87.248.147.154 +87.248.147.155 +87.248.150.118 +87.248.151.5 +87.248.151.38 +87.248.151.59 +87.248.151.117 +87.248.151.124 +87.248.152.44 +87.248.152.189 +87.248.153.230 +87.248.154.31 +87.248.154.89 +87.248.154.103 +87.248.154.113 +87.248.154.129 +87.248.154.194 +87.248.154.207 +87.248.155.14 +87.248.155.79 +87.248.155.81 +87.248.155.245 +87.248.156.62 +87.248.156.134 +87.248.156.195 +88.135.38.143 +88.135.38.146 +88.135.38.147 +88.135.38.148 +88.135.38.149 +88.135.38.150 +88.135.38.151 +88.135.39.29 +88.135.68.2 +88.135.68.15 +88.135.68.96 +88.135.75.148 +88.135.75.201 +88.218.16.58 +88.218.16.123 +88.218.16.124 +89.32.197.12 +89.32.197.30 +89.32.197.51 +89.32.197.52 +89.32.197.87 +89.32.197.111 +89.32.197.182 +89.32.197.214 +89.32.248.1 +89.32.248.36 +89.32.248.65 +89.32.248.73 +89.32.248.81 +89.32.248.129 +89.32.248.130 +89.32.248.134 +89.32.248.137 +89.32.248.145 +89.32.248.161 +89.32.248.162 +89.32.248.163 +89.32.248.194 +89.32.248.201 +89.32.248.203 +89.32.248.209 +89.32.248.225 +89.32.249.1 +89.32.249.65 +89.32.249.97 +89.32.249.129 +89.32.249.145 +89.32.249.153 +89.32.249.161 +89.32.249.193 +89.32.250.1 +89.32.250.6 +89.32.250.33 +89.32.250.39 +89.32.250.65 +89.32.250.77 +89.32.250.129 +89.32.250.161 +89.32.250.177 +89.32.250.193 +89.32.250.223 +89.32.251.1 +89.32.251.17 +89.32.251.33 +89.32.251.41 +89.32.251.65 +89.32.251.95 +89.32.251.107 +89.32.251.129 +89.32.251.161 +89.33.18.10 +89.33.18.18 +89.33.18.20 +89.33.18.21 +89.33.18.29 +89.33.18.42 +89.33.18.50 +89.33.18.52 +89.33.18.54 +89.33.18.55 +89.33.18.61 +89.33.18.68 +89.33.18.70 +89.33.18.84 +89.33.18.93 +89.33.18.99 +89.33.18.101 +89.33.18.104 +89.33.18.106 +89.33.18.111 +89.33.18.114 +89.33.18.142 +89.33.18.145 +89.33.18.150 +89.33.18.153 +89.33.18.157 +89.33.18.160 +89.33.18.168 +89.33.18.174 +89.33.18.186 +89.33.18.189 +89.33.18.190 +89.33.18.191 +89.33.18.192 +89.33.18.193 +89.33.18.206 +89.33.18.212 +89.33.18.225 +89.33.18.231 +89.33.18.235 +89.33.18.242 +89.33.18.246 +89.33.18.249 +89.33.18.251 +89.33.19.13 +89.33.19.14 +89.33.19.30 +89.33.19.31 +89.33.19.45 +89.33.19.49 +89.33.19.50 +89.33.19.51 +89.33.19.62 +89.33.19.65 +89.33.19.67 +89.33.19.68 +89.33.19.73 +89.33.19.94 +89.33.19.99 +89.33.19.107 +89.33.19.112 +89.33.19.117 +89.33.19.118 +89.33.19.119 +89.33.19.125 +89.33.19.126 +89.33.19.130 +89.33.19.141 +89.33.19.152 +89.33.19.156 +89.33.19.165 +89.33.19.169 +89.33.19.170 +89.33.19.173 +89.33.19.176 +89.33.19.179 +89.33.19.182 +89.33.19.190 +89.33.19.197 +89.33.19.200 +89.33.19.207 +89.33.19.209 +89.33.19.216 +89.33.19.218 +89.33.19.220 +89.33.19.227 +89.33.19.229 +89.33.19.231 +89.33.19.235 +89.33.19.236 +89.33.19.239 +89.33.19.240 +89.33.19.244 +89.33.19.246 +89.33.19.251 +89.33.129.167 +89.33.129.170 +89.33.205.167 +89.33.234.12 +89.33.240.41 +89.34.21.101 +89.34.96.30 +89.34.96.77 +89.34.96.86 +89.34.96.87 +89.34.96.88 +89.34.96.89 +89.34.96.90 +89.34.96.91 +89.34.96.92 +89.34.96.93 +89.34.96.94 +89.34.96.95 +89.34.96.100 +89.34.96.101 +89.34.96.102 +89.34.96.103 +89.34.96.104 +89.34.96.105 +89.34.96.106 +89.34.96.117 +89.34.96.146 +89.34.97.70 +89.34.169.18 +89.34.169.108 +89.34.169.116 +89.34.169.222 +89.34.176.58 +89.34.176.66 +89.34.176.105 +89.34.176.185 +89.34.177.113 +89.34.200.91 +89.34.200.94 +89.34.200.117 +89.34.201.120 +89.34.254.147 +89.35.58.51 +89.35.132.69 +89.35.132.78 +89.35.132.122 +89.35.133.153 +89.35.133.210 +89.35.133.223 +89.35.133.237 +89.36.226.24 +89.36.226.29 +89.37.1.10 +89.37.1.58 +89.37.1.59 +89.37.1.60 +89.37.1.61 +89.37.1.62 +89.37.1.115 +89.37.3.182 +89.37.4.54 +89.37.9.118 +89.37.11.210 +89.37.12.84 +89.37.13.250 +89.37.15.11 +89.37.15.12 +89.37.15.34 +89.37.15.90 +89.37.15.91 +89.37.15.92 +89.37.103.14 +89.37.103.30 +89.37.103.65 +89.37.103.81 +89.37.103.103 +89.37.146.97 +89.37.147.29 +89.37.148.152 +89.37.148.159 +89.37.148.182 +89.37.153.246 +89.37.155.233 +89.37.198.147 +89.37.199.125 +89.37.199.209 +89.37.219.29 +89.37.219.30 +89.37.219.51 +89.37.219.220 +89.37.241.17 +89.37.242.36 +89.37.242.162 +89.37.245.11 +89.37.245.55 +89.37.246.36 +89.37.247.74 +89.37.248.10 +89.37.248.120 +89.37.249.127 +89.37.250.29 +89.37.250.157 +89.37.251.210 +89.37.252.31 +89.37.253.74 +89.37.253.85 +89.37.253.125 +89.37.253.130 +89.37.253.240 +89.37.254.33 +89.37.254.83 +89.37.255.217 +89.38.81.43 +89.38.81.101 +89.38.82.199 +89.38.83.102 +89.38.84.75 +89.38.84.110 +89.38.85.197 +89.38.86.67 +89.38.87.69 +89.38.87.96 +89.38.87.192 +89.38.88.198 +89.38.89.20 +89.38.89.32 +89.38.89.171 +89.38.90.62 +89.38.90.169 +89.38.91.21 +89.38.92.26 +89.38.92.163 +89.38.93.89 +89.38.93.111 +89.38.94.189 +89.38.94.214 +89.38.95.197 +89.38.102.107 +89.38.102.227 +89.38.102.251 +89.38.103.22 +89.38.103.58 +89.38.103.81 +89.38.103.139 +89.38.103.166 +89.38.103.219 +89.38.246.58 +89.39.208.9 +89.39.208.17 +89.39.208.21 +89.39.208.25 +89.39.208.33 +89.39.208.39 +89.39.208.73 +89.39.208.77 +89.39.208.81 +89.39.208.97 +89.39.208.105 +89.39.208.113 +89.39.208.126 +89.39.208.129 +89.39.208.133 +89.39.208.145 +89.39.208.171 +89.39.208.177 +89.39.208.182 +89.40.78.162 +89.40.78.189 +89.40.78.208 +89.40.78.219 +89.40.79.27 +89.40.79.53 +89.40.79.71 +89.40.79.117 +89.40.79.123 +89.40.79.185 +89.40.79.211 +89.40.79.226 +89.40.106.17 +89.40.106.139 +89.40.107.24 +89.40.107.223 +89.40.107.244 +89.40.159.148 +89.40.246.2 +89.40.246.135 +89.40.246.212 +89.40.247.10 +89.40.247.138 +89.40.247.154 +89.41.40.56 +89.41.40.106 +89.41.40.171 +89.41.41.83 +89.41.42.52 +89.41.42.193 +89.41.42.230 +89.41.42.234 +89.41.43.48 +89.41.43.64 +89.41.43.135 +89.41.43.163 +89.42.44.211 +89.42.96.21 +89.42.96.80 +89.42.96.167 +89.42.97.57 +89.42.100.11 +89.42.100.28 +89.42.100.50 +89.42.100.56 +89.42.101.45 +89.42.101.182 +89.42.102.130 +89.42.102.184 +89.42.103.21 +89.42.103.160 +89.42.103.237 +89.42.136.30 +89.42.136.114 +89.42.136.115 +89.42.136.116 +89.42.136.218 +89.42.138.122 +89.42.139.89 +89.42.150.51 +89.42.151.230 +89.42.197.6 +89.42.197.17 +89.42.197.18 +89.42.197.21 +89.42.197.25 +89.42.197.78 +89.42.198.4 +89.42.198.12 +89.42.198.21 +89.42.198.30 +89.42.198.31 +89.42.198.43 +89.42.198.57 +89.42.198.73 +89.42.198.86 +89.42.198.110 +89.42.198.116 +89.42.198.131 +89.42.198.180 +89.42.198.216 +89.42.199.59 +89.42.199.212 +89.42.208.1 +89.42.208.65 +89.42.208.97 +89.42.208.112 +89.42.208.129 +89.42.208.145 +89.42.208.153 +89.42.208.161 +89.42.208.177 +89.42.208.193 +89.42.208.209 +89.42.208.211 +89.42.208.217 +89.42.208.225 +89.42.208.233 +89.42.208.241 +89.42.209.18 +89.42.209.32 +89.42.209.55 +89.42.209.65 +89.42.209.85 +89.42.209.90 +89.42.209.97 +89.42.209.129 +89.42.209.145 +89.42.209.149 +89.42.209.161 +89.42.209.177 +89.42.209.185 +89.42.209.193 +89.42.209.201 +89.42.209.225 +89.42.209.233 +89.42.209.249 +89.42.209.253 +89.42.210.1 +89.42.210.18 +89.42.210.56 +89.42.210.59 +89.42.210.129 +89.42.210.161 +89.42.210.193 +89.42.210.209 +89.42.210.233 +89.42.210.241 +89.42.210.242 +89.42.210.249 +89.42.211.1 +89.42.211.17 +89.42.211.33 +89.42.211.97 +89.42.211.113 +89.42.211.129 +89.42.211.161 +89.42.211.193 +89.42.211.225 +89.42.211.233 +89.43.2.90 +89.43.3.78 +89.43.3.224 +89.43.7.205 +89.43.11.226 +89.43.13.7 +89.43.36.175 +89.43.144.185 +89.43.216.15 +89.43.217.102 +89.43.222.45 +89.43.222.109 +89.43.223.216 +89.43.226.5 +89.43.227.38 +89.43.227.44 +89.43.229.90 +89.43.230.171 +89.43.230.207 +89.43.231.200 +89.44.112.4 +89.44.112.6 +89.44.112.7 +89.44.112.8 +89.44.112.9 +89.44.118.4 +89.44.118.20 +89.44.118.21 +89.44.129.129 +89.44.129.255 +89.44.130.118 +89.44.132.3 +89.44.134.62 +89.44.176.34 +89.44.176.246 +89.44.177.25 +89.44.177.66 +89.44.179.14 +89.44.179.172 +89.44.179.254 +89.44.180.39 +89.44.183.233 +89.44.240.59 +89.44.240.200 +89.44.240.220 +89.45.56.19 +89.45.56.25 +89.45.56.35 +89.45.56.52 +89.45.56.74 +89.45.56.78 +89.45.56.82 +89.45.56.86 +89.45.56.111 +89.45.56.113 +89.45.56.122 +89.45.56.125 +89.45.56.140 +89.45.56.179 +89.45.56.186 +89.45.89.1 +89.45.89.90 +89.45.89.248 +89.45.89.249 +89.45.89.251 +89.45.89.252 +89.45.89.253 +89.45.89.254 +89.45.115.201 +89.46.60.49 +89.46.61.219 +89.46.94.24 +89.46.94.71 +89.46.94.197 +89.46.94.202 +89.46.94.231 +89.46.95.220 +89.46.95.222 +89.46.216.5 +89.46.216.13 +89.46.216.14 +89.46.216.41 +89.46.216.208 +89.46.218.17 +89.46.218.41 +89.46.218.145 +89.46.219.1 +89.46.219.4 +89.46.219.33 +89.46.219.34 +89.46.219.36 +89.46.219.37 +89.46.219.38 +89.46.219.40 +89.46.219.41 +89.46.219.42 +89.46.219.44 +89.46.219.45 +89.46.219.46 +89.47.196.131 +89.47.196.137 +89.47.196.143 +89.47.199.106 +89.47.199.158 +89.47.199.230 +89.47.201.38 +89.47.202.101 +89.144.132.178 +89.144.133.130 +89.144.133.136 +89.144.133.193 +89.144.135.118 +89.144.137.200 +89.144.139.180 +89.144.139.181 +89.144.140.81 +89.144.141.129 +89.144.143.162 +89.144.143.163 +89.144.143.165 +89.144.149.93 +89.144.151.82 +89.144.151.88 +89.144.152.1 +89.144.152.2 +89.144.153.48 +89.144.155.82 +89.144.157.164 +89.144.160.25 +89.144.169.244 +89.144.172.248 +89.144.173.180 +89.144.174.181 +89.144.174.248 +89.144.175.220 +89.144.180.65 +89.144.183.65 +89.144.183.241 +89.144.184.9 +89.144.185.201 +89.144.186.10 +89.144.187.121 +89.144.188.168 +89.144.189.72 +89.165.0.138 +89.165.3.242 +89.165.7.118 +89.165.40.10 +89.165.44.194 +89.165.45.194 +89.165.56.12 +89.165.64.8 +89.219.64.56 +89.219.64.250 +89.219.66.253 +89.219.67.35 +89.219.67.204 +89.219.68.206 +89.219.68.251 +89.219.69.245 +89.219.70.146 +89.219.70.157 +89.219.71.200 +89.219.72.72 +89.219.72.195 +89.219.74.112 +89.219.75.26 +89.219.76.119 +89.219.78.58 +89.219.81.228 +89.219.84.28 +89.219.86.59 +89.219.86.83 +89.219.86.161 +89.219.88.197 +89.219.89.194 +89.219.90.152 +89.219.91.179 +89.219.91.185 +89.219.91.252 +89.219.93.59 +89.219.93.170 +89.219.94.38 +89.219.94.79 +89.219.95.230 +89.219.100.4 +89.219.100.20 +89.219.100.47 +89.219.105.68 +89.219.107.184 +89.219.108.107 +89.219.108.139 +89.219.108.250 +89.219.111.145 +89.219.112.154 +89.219.114.134 +89.219.115.186 +89.219.117.58 +89.219.118.20 +89.219.118.67 +89.219.119.157 +89.219.120.250 +89.219.122.59 +89.219.124.202 +89.219.125.162 +89.219.194.91 +89.219.194.254 +89.219.195.206 +89.219.200.100 +89.219.202.212 +89.219.203.130 +89.219.207.174 +89.219.208.238 +89.219.209.174 +89.219.218.83 +89.219.221.97 +89.219.221.160 +89.219.225.235 +89.219.226.15 +89.219.229.89 +89.219.236.121 +89.219.243.103 +89.219.245.17 +89.219.246.98 +89.219.246.103 +89.219.247.62 +89.219.252.29 +89.219.252.79 +89.219.255.238 +89.221.84.84 +89.221.84.88 +89.221.85.56 +89.221.85.110 +89.221.85.122 +89.221.85.137 +89.221.85.140 +89.221.85.142 +89.221.85.217 +89.221.86.76 +89.221.86.171 +89.221.86.173 +89.221.86.202 +89.221.86.213 +89.235.79.11 +89.235.79.12 +89.235.79.14 +89.235.79.17 +89.235.79.179 +89.235.93.201 +89.235.95.206 +89.235.96.26 +89.235.99.208 +89.235.104.1 +89.235.112.1 +89.235.116.213 +91.92.114.105 +91.92.114.198 +91.92.114.239 +91.92.121.13 +91.92.121.59 +91.92.121.77 +91.92.121.115 +91.92.121.122 +91.92.121.157 +91.92.122.4 +91.92.122.22 +91.92.122.90 +91.92.122.95 +91.92.122.138 +91.92.122.144 +91.92.122.219 +91.92.123.159 +91.92.123.214 +91.92.124.1 +91.92.124.40 +91.92.124.81 +91.92.124.90 +91.92.124.147 +91.92.124.172 +91.92.124.220 +91.92.124.248 +91.92.125.160 +91.92.125.219 +91.92.125.243 +91.92.126.4 +91.92.126.48 +91.92.126.49 +91.92.126.71 +91.92.126.85 +91.92.126.97 +91.92.126.117 +91.92.126.123 +91.92.126.238 +91.92.127.14 +91.92.127.37 +91.92.127.53 +91.92.127.75 +91.92.127.105 +91.92.127.145 +91.92.127.167 +91.92.127.191 +91.92.127.228 +91.92.129.59 +91.92.129.87 +91.92.129.162 +91.92.129.188 +91.92.129.216 +91.92.130.4 +91.92.130.49 +91.92.130.111 +91.92.130.112 +91.92.130.148 +91.92.130.157 +91.92.130.168 +91.92.130.197 +91.92.130.202 +91.92.130.231 +91.92.131.57 +91.92.131.107 +91.92.131.115 +91.92.131.135 +91.92.131.151 +91.92.131.155 +91.92.131.166 +91.92.131.168 +91.92.131.196 +91.92.131.199 +91.92.131.201 +91.92.131.204 +91.92.131.208 +91.92.131.218 +91.92.131.248 +91.92.132.17 +91.92.133.8 +91.92.133.75 +91.92.133.80 +91.92.133.83 +91.92.133.87 +91.92.133.89 +91.92.133.97 +91.92.133.121 +91.92.133.135 +91.92.133.145 +91.92.133.158 +91.92.133.195 +91.92.149.37 +91.92.151.37 +91.92.156.9 +91.92.157.82 +91.92.158.66 +91.92.158.72 +91.92.159.114 +91.92.159.131 +91.92.159.241 +91.92.164.17 +91.92.164.200 +91.92.165.41 +91.92.165.51 +91.92.166.52 +91.92.166.201 +91.92.167.216 +91.92.180.19 +91.92.180.30 +91.92.180.36 +91.92.180.72 +91.92.180.80 +91.92.180.87 +91.92.180.106 +91.92.180.109 +91.92.180.125 +91.92.180.127 +91.92.180.131 +91.92.180.139 +91.92.180.182 +91.92.180.216 +91.92.180.230 +91.92.180.253 +91.92.181.44 +91.92.181.61 +91.92.181.113 +91.92.181.138 +91.92.181.142 +91.92.181.205 +91.92.182.57 +91.92.182.77 +91.92.182.120 +91.92.182.150 +91.92.182.181 +91.92.182.200 +91.92.182.251 +91.92.183.23 +91.92.183.70 +91.92.183.106 +91.92.183.172 +91.92.185.29 +91.92.185.70 +91.92.185.92 +91.92.186.17 +91.92.186.108 +91.92.186.132 +91.92.186.154 +91.92.186.167 +91.92.186.214 +91.92.186.225 +91.92.187.10 +91.92.187.30 +91.92.188.38 +91.92.188.142 +91.92.188.218 +91.92.188.223 +91.92.189.39 +91.92.189.183 +91.92.190.18 +91.92.190.87 +91.92.190.126 +91.92.190.168 +91.92.190.190 +91.92.190.193 +91.92.190.215 +91.92.191.30 +91.92.191.36 +91.92.191.81 +91.92.191.167 +91.92.191.168 +91.92.191.177 +91.92.191.178 +91.92.191.221 +91.92.192.17 +91.92.204.27 +91.92.204.130 +91.92.204.165 +91.92.204.178 +91.92.204.194 +91.92.204.211 +91.92.205.179 +91.92.205.208 +91.92.206.34 +91.92.206.90 +91.92.207.152 +91.92.207.195 +91.92.207.221 +91.92.207.228 +91.92.207.238 +91.92.208.3 +91.92.208.10 +91.92.208.21 +91.92.208.38 +91.92.208.43 +91.92.208.51 +91.92.208.63 +91.92.208.108 +91.92.208.149 +91.92.208.173 +91.92.208.192 +91.92.208.205 +91.92.208.229 +91.92.208.245 +91.92.209.2 +91.92.209.5 +91.92.209.9 +91.92.209.27 +91.92.209.32 +91.92.209.38 +91.92.209.43 +91.92.209.45 +91.92.209.75 +91.92.209.115 +91.92.209.165 +91.92.209.167 +91.92.209.173 +91.92.209.192 +91.92.209.194 +91.92.209.247 +91.92.210.135 +91.92.211.24 +91.92.211.55 +91.92.211.66 +91.92.211.77 +91.92.211.78 +91.92.211.81 +91.92.211.108 +91.92.211.135 +91.92.211.151 +91.92.211.160 +91.92.211.174 +91.92.211.218 +91.92.211.232 +91.92.211.245 +91.92.211.250 +91.92.211.253 +91.92.212.33 +91.92.212.69 +91.92.212.157 +91.92.212.167 +91.92.212.171 +91.92.212.194 +91.92.212.240 +91.92.212.246 +91.92.213.37 +91.92.213.82 +91.92.213.148 +91.92.214.41 +91.92.214.46 +91.92.214.65 +91.92.214.85 +91.92.214.93 +91.92.214.104 +91.92.214.143 +91.92.214.188 +91.92.214.190 +91.92.214.202 +91.92.214.211 +91.92.214.218 +91.92.214.236 +91.92.214.242 +91.92.215.0 +91.92.215.10 +91.92.215.13 +91.92.215.25 +91.92.215.48 +91.92.215.74 +91.92.215.81 +91.92.215.98 +91.92.215.106 +91.92.215.129 +91.92.215.132 +91.92.215.231 +91.92.231.55 +91.92.231.92 +91.92.231.112 +91.92.231.203 +91.106.64.25 +91.106.64.49 +91.106.64.94 +91.106.66.32 +91.106.66.173 +91.106.67.85 +91.106.67.252 +91.106.68.164 +91.106.70.14 +91.106.70.206 +91.106.70.213 +91.106.72.95 +91.106.74.77 +91.106.76.129 +91.106.83.25 +91.106.83.26 +91.106.83.193 +91.106.84.20 +91.106.84.25 +91.106.85.2 +91.106.85.5 +91.106.85.6 +91.106.85.19 +91.106.85.22 +91.106.85.33 +91.106.85.34 +91.106.85.35 +91.106.85.36 +91.106.85.37 +91.106.85.38 +91.106.85.39 +91.106.85.41 +91.106.85.42 +91.106.85.44 +91.106.85.45 +91.106.85.46 +91.106.85.47 +91.106.91.55 +91.106.91.69 +91.106.91.85 +91.106.91.192 +91.106.91.232 +91.106.92.139 +91.106.94.145 +91.106.95.130 +91.108.128.114 +91.108.128.174 +91.108.129.98 +91.108.129.99 +91.108.129.100 +91.108.129.101 +91.108.129.122 +91.108.130.112 +91.108.131.9 +91.108.131.10 +91.108.138.41 +91.108.140.3 +91.108.140.4 +91.108.140.5 +91.108.145.15 +91.108.145.68 +91.108.150.2 +91.108.150.24 +91.108.150.41 +91.108.152.6 +91.108.155.44 +91.108.155.194 +91.108.155.195 +91.108.155.198 +91.108.155.210 +91.108.155.244 +91.185.148.110 +91.186.193.222 +91.199.9.18 +91.199.9.117 +91.199.9.118 +91.199.9.232 +91.199.9.234 +91.199.18.5 +91.199.18.20 +91.199.18.37 +91.199.18.39 +91.199.18.68 +91.199.18.69 +91.199.18.70 +91.199.18.71 +91.199.18.242 +91.199.18.243 +91.199.18.255 +91.199.27.177 +91.199.27.178 +91.199.27.211 +91.199.27.214 +91.199.27.240 +91.199.27.241 +91.199.30.254 +91.199.215.34 +91.199.215.238 +91.206.177.2 +91.206.177.3 +91.207.205.3 +91.207.205.7 +91.208.163.195 +91.212.174.20 +91.212.174.32 +91.212.174.78 +91.212.174.246 +91.212.252.48 +91.220.113.36 +91.220.113.121 +91.220.113.247 +91.221.241.243 +91.222.196.8 +91.222.196.9 +91.222.196.53 +91.222.197.22 +91.222.197.25 +91.222.198.42 +91.222.198.43 +91.223.116.150 +91.223.116.173 +91.223.116.176 +91.223.116.179 +91.223.116.238 +91.223.146.17 +91.224.20.253 +91.224.176.0 +91.224.176.9 +91.227.86.244 +91.227.246.49 +91.227.246.132 +91.227.246.150 +91.227.246.167 +91.227.246.168 +91.227.246.197 +91.227.246.224 +91.227.246.225 +91.228.132.5 +91.232.72.30 +91.232.72.70 +91.234.39.76 +91.234.39.98 +91.234.39.193 +91.234.39.231 +91.236.168.144 +91.236.168.168 +91.236.169.161 +91.236.169.162 +91.236.169.163 +91.236.169.164 +91.236.169.165 +91.236.169.166 +91.236.169.167 +91.236.169.168 +91.236.169.169 +91.236.169.170 +91.237.255.130 +91.237.255.224 +91.240.60.65 +91.240.60.66 +91.240.60.230 +91.240.61.17 +91.240.61.45 +91.240.61.164 +91.240.61.165 +91.240.61.166 +91.240.61.217 +91.240.61.230 +91.240.61.238 +91.240.61.240 +91.240.61.248 +91.240.61.254 +91.240.63.15 +91.240.63.25 +91.240.63.59 +91.240.63.129 +91.240.63.214 +91.241.20.98 +91.241.20.181 +91.241.20.182 +91.241.20.183 +91.241.20.193 +91.241.21.17 +91.241.21.66 +91.241.21.82 +91.241.21.116 +91.241.21.117 +91.241.21.118 +91.241.21.119 +91.241.21.137 +91.241.21.139 +91.241.21.140 +91.241.21.142 +91.241.21.237 +91.242.44.2 +91.242.44.96 +91.242.44.253 +91.242.45.14 +91.242.45.37 +91.242.45.249 +91.243.127.222 +91.243.160.13 +91.243.160.62 +91.243.160.90 +91.243.160.104 +91.243.160.123 +91.243.160.144 +91.243.160.180 +91.243.160.211 +91.243.161.6 +91.243.161.35 +91.243.161.44 +91.243.161.77 +91.243.161.124 +91.243.161.164 +91.243.161.180 +91.243.161.188 +91.243.162.90 +91.243.164.4 +91.243.164.87 +91.243.164.132 +91.243.164.183 +91.243.164.188 +91.243.164.203 +91.243.164.227 +91.243.164.241 +91.243.165.43 +91.243.165.48 +91.243.165.59 +91.243.165.143 +91.243.165.192 +91.243.165.196 +91.243.165.245 +91.243.168.44 +91.243.168.106 +91.243.168.140 +91.243.168.251 +91.243.170.15 +91.243.170.211 +91.243.170.212 +91.243.170.213 +91.243.170.214 +91.243.171.169 +91.243.171.173 +91.243.171.193 +91.243.171.198 +91.243.172.27 +91.243.172.67 +91.243.172.123 +91.243.172.165 +91.243.172.218 +91.243.172.247 +91.243.175.129 +91.243.175.145 +91.245.228.241 +91.245.229.2 +91.245.230.16 +91.245.230.17 +91.245.230.25 +91.245.231.3 +91.246.31.108 +91.247.66.10 +91.247.66.46 +91.247.66.53 +91.247.66.66 +91.247.66.68 +91.247.66.90 +91.247.66.95 +91.247.66.97 +91.247.66.113 +91.247.66.138 +91.247.66.152 +91.247.66.156 +91.247.66.157 +91.247.66.162 +91.247.66.163 +91.247.66.164 +91.247.66.178 +91.247.66.193 +91.247.66.201 +91.247.66.206 +91.247.66.218 +91.247.66.235 +91.247.66.249 +91.247.67.222 +91.247.67.237 +91.247.67.244 +91.247.67.247 +91.247.171.167 +91.247.171.183 +92.42.207.4 +92.43.162.37 +92.43.162.95 +92.43.163.134 +92.43.163.179 +92.114.19.11 +92.114.26.73 +92.114.26.125 +92.114.29.91 +92.114.29.246 +92.114.29.247 +92.114.30.127 +92.114.48.60 +92.114.48.97 +92.114.48.140 +92.114.48.157 +92.114.48.171 +92.114.48.207 +92.114.48.210 +92.114.49.78 +92.114.49.154 +92.114.49.196 +92.114.49.243 +92.114.49.246 +92.119.57.85 +92.242.192.176 +92.242.192.178 +92.242.198.74 +92.242.198.202 +92.242.198.203 +92.242.198.204 +92.242.198.206 +92.242.198.254 +92.242.202.130 +92.242.202.186 +92.242.202.187 +92.242.202.188 +92.242.202.189 +92.242.202.190 +92.242.204.66 +92.242.204.67 +92.242.204.68 +92.242.204.69 +92.242.204.70 +92.242.204.71 +92.242.204.75 +92.242.204.150 +92.242.206.224 +92.242.207.43 +92.242.207.68 +92.242.212.50 +92.242.212.51 +92.242.212.99 +92.242.217.169 +92.242.217.174 +92.242.219.202 +92.242.219.203 +92.242.219.204 +92.242.219.205 +92.242.219.213 +92.242.220.181 +92.242.220.188 +92.242.223.10 +92.242.223.178 +92.246.144.10 +92.246.144.58 +92.246.144.59 +92.246.144.130 +92.246.144.225 +92.246.145.96 +92.246.145.164 +92.246.145.165 +92.246.145.166 +92.246.146.12 +92.246.146.22 +92.246.146.46 +92.246.146.74 +92.246.146.81 +92.246.146.89 +92.246.146.110 +92.246.146.141 +92.246.146.151 +92.246.146.202 +92.246.147.96 +92.246.147.212 +92.246.147.225 +92.249.56.18 +92.249.56.28 +93.113.224.82 +93.113.227.2 +93.113.233.3 +93.113.233.4 +93.113.234.70 +93.113.234.74 +93.113.236.13 +93.113.236.14 +93.113.236.15 +93.113.236.17 +93.114.104.1 +93.114.104.45 +93.114.104.91 +93.114.104.133 +93.114.104.139 +93.114.104.154 +93.114.104.185 +93.114.104.200 +93.114.105.11 +93.114.105.57 +93.114.105.69 +93.114.105.88 +93.114.105.93 +93.114.105.145 +93.114.105.153 +93.114.105.200 +93.114.105.233 +93.114.106.51 +93.114.106.94 +93.114.106.187 +93.114.106.188 +93.114.106.203 +93.114.108.15 +93.114.108.58 +93.114.108.62 +93.114.108.138 +93.114.108.140 +93.114.108.234 +93.114.108.241 +93.114.109.56 +93.114.109.77 +93.114.109.82 +93.114.109.137 +93.114.109.140 +93.114.109.158 +93.114.109.204 +93.114.110.11 +93.114.110.92 +93.114.110.123 +93.114.110.166 +93.114.110.193 +93.114.110.196 +93.114.110.218 +93.114.110.224 +93.114.111.49 +93.114.111.125 +93.114.111.147 +93.114.111.216 +93.114.111.244 +93.115.120.4 +93.115.120.98 +93.115.120.168 +93.115.120.181 +93.115.120.231 +93.115.121.0 +93.115.121.62 +93.115.121.68 +93.115.121.79 +93.115.121.106 +93.115.121.161 +93.115.121.223 +93.115.121.229 +93.115.122.8 +93.115.122.104 +93.115.122.144 +93.115.122.171 +93.115.123.15 +93.115.123.240 +93.115.124.114 +93.115.124.131 +93.115.124.189 +93.115.124.191 +93.115.124.200 +93.115.124.230 +93.115.124.236 +93.115.125.21 +93.115.125.53 +93.115.125.106 +93.115.125.123 +93.115.125.125 +93.115.125.157 +93.115.125.158 +93.115.125.171 +93.115.125.179 +93.115.125.221 +93.115.125.252 +93.115.126.1 +93.115.126.157 +93.115.126.183 +93.115.126.223 +93.115.126.224 +93.115.126.244 +93.115.127.71 +93.115.127.75 +93.115.127.119 +93.115.127.220 +93.115.144.1 +93.115.144.4 +93.115.144.13 +93.115.144.40 +93.115.144.45 +93.115.144.50 +93.115.144.62 +93.115.144.81 +93.115.144.88 +93.115.144.121 +93.115.144.130 +93.115.144.134 +93.115.144.137 +93.115.144.159 +93.115.144.167 +93.115.144.193 +93.115.144.195 +93.115.144.210 +93.115.144.214 +93.115.144.220 +93.115.144.234 +93.115.144.246 +93.115.145.22 +93.115.145.25 +93.115.145.30 +93.115.145.71 +93.115.145.83 +93.115.145.94 +93.115.145.105 +93.115.145.125 +93.115.145.141 +93.115.145.185 +93.115.145.198 +93.115.145.229 +93.115.145.239 +93.115.146.4 +93.115.146.17 +93.115.146.52 +93.115.146.53 +93.115.146.74 +93.115.146.81 +93.115.146.178 +93.115.146.215 +93.115.146.219 +93.115.146.234 +93.115.146.237 +93.115.147.71 +93.115.147.113 +93.115.147.115 +93.115.147.122 +93.115.147.126 +93.115.147.189 +93.115.147.215 +93.115.147.218 +93.115.147.225 +93.115.147.230 +93.115.148.16 +93.115.148.27 +93.115.148.29 +93.115.148.47 +93.115.148.64 +93.115.148.66 +93.115.148.82 +93.115.148.102 +93.115.148.129 +93.115.148.136 +93.115.148.156 +93.115.148.165 +93.115.148.186 +93.115.148.189 +93.115.148.207 +93.115.148.211 +93.115.148.237 +93.115.148.248 +93.115.149.8 +93.115.149.10 +93.115.149.29 +93.115.149.49 +93.115.149.54 +93.115.149.57 +93.115.149.58 +93.115.149.75 +93.115.149.86 +93.115.149.92 +93.115.149.136 +93.115.149.166 +93.115.149.173 +93.115.149.178 +93.115.149.198 +93.115.149.207 +93.115.149.230 +93.115.150.30 +93.115.151.130 +93.115.151.131 +93.115.151.132 +93.115.151.133 +93.115.151.136 +93.115.151.137 +93.115.151.139 +93.115.151.140 +93.115.151.141 +93.115.151.143 +93.115.151.144 +93.115.151.148 +93.115.151.153 +93.115.151.155 +93.115.151.157 +93.115.151.158 +93.115.151.160 +93.115.151.161 +93.115.151.162 +93.115.151.163 +93.115.151.164 +93.115.151.166 +93.115.151.170 +93.115.151.180 +93.115.151.181 +93.115.151.182 +93.115.151.183 +93.115.151.184 +93.115.151.185 +93.115.151.186 +93.115.151.188 +93.115.151.189 +93.115.151.190 +93.117.0.57 +93.117.0.248 +93.117.4.197 +93.117.7.237 +93.117.10.108 +93.117.11.67 +93.117.13.70 +93.117.14.55 +93.117.16.212 +93.117.19.20 +93.117.20.234 +93.117.23.104 +93.117.23.251 +93.117.24.200 +93.117.28.7 +93.117.28.137 +93.117.31.137 +93.117.33.30 +93.117.33.209 +93.117.33.239 +93.117.34.68 +93.117.35.213 +93.117.36.21 +93.117.36.179 +93.117.37.169 +93.117.38.3 +93.117.39.183 +93.117.40.249 +93.117.42.5 +93.117.42.113 +93.117.42.253 +93.117.43.51 +93.117.43.72 +93.117.44.18 +93.117.44.23 +93.117.44.93 +93.117.44.135 +93.117.46.170 +93.117.46.171 +93.117.46.214 +93.117.97.109 +93.117.127.70 +93.117.127.73 +93.117.127.91 +93.117.127.107 +93.117.127.164 +93.117.127.218 +93.118.96.24 +93.118.96.35 +93.118.96.68 +93.118.96.188 +93.118.96.210 +93.118.97.33 +93.118.97.63 +93.118.97.88 +93.118.97.92 +93.118.97.121 +93.118.97.135 +93.118.97.147 +93.118.98.13 +93.118.98.90 +93.118.98.119 +93.118.98.244 +93.118.99.20 +93.118.99.31 +93.118.99.74 +93.118.99.150 +93.118.100.139 +93.118.100.158 +93.118.101.10 +93.118.101.25 +93.118.101.48 +93.118.101.94 +93.118.101.129 +93.118.101.139 +93.118.101.153 +93.118.101.154 +93.118.101.155 +93.118.101.179 +93.118.101.232 +93.118.101.243 +93.118.102.6 +93.118.103.45 +93.118.103.208 +93.118.105.62 +93.118.105.139 +93.118.105.144 +93.118.105.170 +93.118.105.178 +93.118.106.95 +93.118.106.99 +93.118.106.182 +93.118.107.84 +93.118.108.18 +93.118.108.32 +93.118.108.90 +93.118.108.146 +93.118.108.177 +93.118.108.216 +93.118.108.225 +93.118.108.232 +93.118.109.52 +93.118.109.58 +93.118.109.61 +93.118.109.82 +93.118.109.117 +93.118.109.127 +93.118.109.140 +93.118.109.167 +93.118.109.200 +93.118.110.10 +93.118.110.24 +93.118.110.43 +93.118.110.73 +93.118.110.100 +93.118.110.156 +93.118.110.190 +93.118.110.215 +93.118.110.216 +93.118.110.231 +93.118.110.235 +93.118.111.49 +93.118.111.64 +93.118.111.109 +93.118.112.17 +93.118.112.19 +93.118.112.31 +93.118.112.46 +93.118.112.62 +93.118.112.102 +93.118.112.145 +93.118.112.161 +93.118.112.191 +93.118.112.236 +93.118.113.23 +93.118.113.133 +93.118.113.152 +93.118.113.224 +93.118.114.59 +93.118.114.127 +93.118.114.138 +93.118.114.165 +93.118.114.196 +93.118.114.240 +93.118.115.13 +93.118.115.43 +93.118.115.45 +93.118.115.108 +93.118.115.112 +93.118.115.131 +93.118.115.138 +93.118.115.143 +93.118.115.171 +93.118.115.207 +93.118.115.216 +93.118.115.234 +93.118.115.236 +93.118.115.240 +93.118.116.33 +93.118.116.37 +93.118.116.66 +93.118.116.127 +93.118.116.138 +93.118.116.174 +93.118.116.239 +93.118.116.247 +93.118.117.31 +93.118.117.50 +93.118.117.97 +93.118.117.122 +93.118.117.129 +93.118.117.177 +93.118.119.87 +93.118.119.110 +93.118.119.117 +93.118.119.169 +93.118.119.223 +93.118.120.5 +93.118.120.93 +93.118.120.119 +93.118.120.133 +93.118.120.139 +93.118.120.148 +93.118.120.207 +93.118.120.214 +93.118.121.32 +93.118.122.17 +93.118.122.34 +93.118.122.126 +93.118.122.154 +93.118.123.9 +93.118.123.11 +93.118.123.70 +93.118.123.83 +93.118.123.101 +93.118.123.121 +93.118.123.155 +93.118.123.176 +93.118.123.177 +93.118.123.196 +93.118.123.211 +93.118.123.218 +93.118.123.229 +93.118.124.38 +93.118.124.182 +93.118.124.254 +93.118.125.5 +93.118.125.28 +93.118.125.34 +93.118.125.143 +93.118.125.201 +93.118.125.205 +93.118.126.23 +93.118.126.41 +93.118.126.100 +93.118.126.107 +93.118.126.170 +93.118.126.205 +93.118.126.229 +93.118.126.251 +93.118.127.10 +93.118.127.49 +93.118.127.54 +93.118.127.60 +93.118.127.182 +93.118.127.206 +93.118.127.230 +93.118.127.249 +93.118.128.27 +93.118.128.37 +93.118.128.38 +93.118.128.56 +93.118.128.66 +93.118.128.101 +93.118.128.104 +93.118.128.124 +93.118.129.28 +93.118.129.96 +93.118.129.191 +93.118.130.149 +93.118.130.183 +93.118.130.185 +93.118.130.243 +93.118.131.12 +93.118.131.106 +93.118.132.12 +93.118.132.41 +93.118.132.213 +93.118.133.12 +93.118.133.69 +93.118.133.170 +93.118.133.198 +93.118.133.212 +93.118.134.13 +93.118.135.61 +93.118.135.97 +93.118.135.103 +93.118.135.170 +93.118.135.225 +93.118.136.25 +93.118.136.36 +93.118.136.44 +93.118.136.83 +93.118.136.169 +93.118.136.203 +93.118.136.207 +93.118.136.232 +93.118.137.14 +93.118.137.38 +93.118.137.49 +93.118.137.65 +93.118.137.91 +93.118.137.98 +93.118.137.221 +93.118.138.36 +93.118.138.52 +93.118.138.73 +93.118.138.88 +93.118.138.114 +93.118.138.223 +93.118.138.246 +93.118.139.36 +93.118.139.84 +93.118.139.90 +93.118.139.97 +93.118.139.130 +93.118.139.144 +93.118.139.145 +93.118.139.151 +93.118.139.155 +93.118.139.210 +93.118.139.246 +93.118.140.18 +93.118.140.56 +93.118.140.70 +93.118.140.102 +93.118.140.148 +93.118.140.152 +93.118.140.196 +93.118.140.209 +93.118.140.211 +93.118.140.224 +93.118.140.225 +93.118.141.27 +93.118.141.188 +93.118.141.201 +93.118.141.232 +93.118.141.252 +93.118.142.209 +93.118.142.222 +93.118.143.2 +93.118.143.9 +93.118.143.64 +93.118.143.67 +93.118.143.86 +93.118.143.89 +93.118.143.95 +93.118.143.97 +93.118.143.111 +93.118.143.112 +93.118.143.143 +93.118.143.155 +93.118.143.195 +93.118.143.211 +93.118.143.226 +93.118.143.231 +93.118.144.0 +93.118.144.16 +93.118.144.41 +93.118.144.74 +93.118.144.114 +93.118.144.119 +93.118.144.124 +93.118.144.141 +93.118.144.168 +93.118.144.228 +93.118.144.242 +93.118.145.25 +93.118.145.213 +93.118.145.245 +93.118.145.248 +93.118.146.59 +93.118.146.88 +93.118.146.176 +93.118.146.253 +93.118.147.14 +93.118.147.158 +93.118.147.176 +93.118.147.180 +93.118.147.193 +93.118.148.1 +93.118.148.7 +93.118.148.26 +93.118.148.30 +93.118.148.34 +93.118.148.70 +93.118.148.118 +93.118.148.140 +93.118.148.189 +93.118.148.199 +93.118.149.228 +93.118.150.42 +93.118.151.217 +93.118.151.236 +93.118.151.249 +93.118.152.46 +93.118.152.81 +93.118.152.168 +93.118.152.177 +93.118.152.247 +93.118.152.253 +93.118.153.92 +93.118.153.104 +93.118.153.108 +93.118.153.109 +93.118.153.110 +93.118.153.119 +93.118.153.126 +93.118.153.131 +93.118.153.137 +93.118.153.157 +93.118.153.229 +93.118.153.252 +93.118.154.12 +93.118.154.56 +93.118.154.174 +93.118.154.188 +93.118.154.205 +93.118.154.225 +93.118.154.226 +93.118.155.66 +93.118.155.69 +93.118.155.100 +93.118.155.109 +93.118.156.42 +93.118.156.46 +93.118.156.78 +93.118.156.149 +93.118.156.176 +93.118.157.57 +93.118.157.146 +93.118.157.210 +93.118.157.223 +93.118.158.0 +93.118.158.20 +93.118.158.37 +93.118.158.78 +93.118.158.92 +93.118.158.166 +93.118.158.172 +93.118.158.204 +93.118.159.64 +93.118.159.114 +93.118.159.149 +93.118.159.200 +93.118.160.27 +93.118.160.109 +93.118.160.123 +93.118.160.175 +93.118.160.189 +93.118.160.213 +93.118.161.8 +93.118.161.30 +93.118.161.39 +93.118.161.69 +93.118.161.71 +93.118.161.168 +93.118.161.225 +93.118.161.228 +93.118.161.233 +93.118.162.13 +93.118.162.161 +93.118.163.9 +93.118.163.140 +93.118.163.146 +93.118.163.230 +93.118.163.252 +93.118.164.28 +93.118.164.41 +93.118.164.59 +93.118.164.183 +93.118.164.199 +93.118.165.55 +93.118.165.136 +93.118.165.182 +93.118.166.36 +93.118.166.62 +93.118.166.73 +93.118.166.131 +93.118.166.150 +93.118.166.193 +93.118.166.235 +93.118.166.245 +93.118.167.33 +93.118.167.58 +93.118.167.64 +93.118.167.147 +93.118.167.152 +93.118.167.176 +93.118.167.208 +93.118.167.219 +93.118.167.223 +93.118.167.236 +93.118.180.129 +93.118.180.245 +93.118.181.27 +93.118.181.112 +93.118.181.114 +93.118.181.149 +93.118.181.225 +93.118.181.238 +93.118.182.9 +93.118.182.21 +93.118.182.35 +93.118.182.63 +93.118.183.167 +93.118.183.188 +93.119.54.205 +93.126.2.252 +93.126.3.22 +93.126.3.98 +93.126.3.229 +93.126.4.73 +93.126.5.81 +93.126.5.94 +93.126.5.106 +93.126.5.127 +93.126.6.39 +93.126.6.61 +93.126.7.168 +93.126.8.121 +93.126.9.1 +93.126.9.2 +93.126.9.3 +93.126.9.10 +93.126.9.57 +93.126.9.86 +93.126.9.153 +93.126.9.166 +93.126.9.186 +93.126.9.233 +93.126.9.234 +93.126.9.235 +93.126.9.236 +93.126.9.237 +93.126.9.238 +93.126.10.17 +93.126.10.70 +93.126.11.234 +93.126.12.22 +93.126.12.254 +93.126.13.84 +93.126.13.132 +93.126.15.129 +93.126.17.130 +93.126.19.246 +93.126.20.107 +93.126.21.4 +93.126.21.57 +93.126.21.58 +93.126.21.117 +93.126.25.33 +93.126.29.14 +93.126.29.44 +93.126.29.53 +93.126.29.64 +93.126.29.96 +93.126.29.108 +93.126.29.109 +93.126.31.140 +93.126.31.161 +93.126.34.81 +93.126.40.7 +93.126.40.17 +93.126.40.18 +93.126.40.19 +93.126.40.22 +93.126.40.23 +93.126.40.24 +93.126.40.30 +93.126.40.67 +93.126.40.68 +93.126.40.69 +93.126.40.70 +93.126.40.71 +93.126.40.72 +93.126.40.73 +93.126.40.76 +93.126.41.0 +93.126.41.1 +93.126.41.2 +93.126.41.3 +93.126.41.4 +93.126.41.6 +93.126.41.7 +93.126.41.8 +93.126.41.9 +93.126.41.11 +93.126.41.12 +93.126.41.13 +93.126.41.14 +93.126.41.15 +93.126.41.16 +93.126.41.17 +93.126.41.18 +93.126.41.19 +93.126.41.20 +93.126.41.23 +93.126.41.24 +93.126.41.26 +93.126.41.27 +93.126.41.28 +93.126.41.29 +93.126.41.30 +93.126.41.31 +93.126.41.34 +93.126.41.35 +93.126.41.36 +93.126.41.37 +93.126.41.38 +93.126.41.39 +93.126.41.40 +93.126.41.41 +93.126.41.42 +93.126.41.44 +93.126.41.45 +93.126.41.46 +93.126.41.47 +93.126.41.48 +93.126.41.49 +93.126.41.50 +93.126.41.51 +93.126.41.52 +93.126.41.53 +93.126.41.55 +93.126.41.56 +93.126.41.57 +93.126.41.58 +93.126.41.60 +93.126.41.61 +93.126.41.62 +93.126.41.63 +93.126.41.64 +93.126.41.65 +93.126.41.66 +93.126.41.67 +93.126.41.68 +93.126.41.69 +93.126.41.70 +93.126.41.71 +93.126.41.72 +93.126.41.73 +93.126.41.74 +93.126.41.76 +93.126.41.77 +93.126.41.78 +93.126.41.80 +93.126.41.81 +93.126.41.82 +93.126.41.83 +93.126.41.84 +93.126.41.85 +93.126.41.86 +93.126.41.87 +93.126.41.88 +93.126.41.89 +93.126.41.90 +93.126.41.91 +93.126.41.92 +93.126.41.93 +93.126.41.95 +93.126.41.96 +93.126.41.97 +93.126.41.99 +93.126.41.100 +93.126.41.101 +93.126.41.102 +93.126.41.103 +93.126.41.104 +93.126.41.105 +93.126.41.106 +93.126.41.107 +93.126.41.108 +93.126.41.109 +93.126.41.110 +93.126.41.111 +93.126.41.112 +93.126.41.113 +93.126.41.114 +93.126.41.115 +93.126.41.116 +93.126.41.117 +93.126.41.118 +93.126.41.119 +93.126.41.120 +93.126.41.121 +93.126.41.122 +93.126.41.123 +93.126.41.124 +93.126.41.125 +93.126.41.126 +93.126.41.127 +93.126.41.128 +93.126.41.129 +93.126.41.130 +93.126.41.131 +93.126.41.132 +93.126.41.133 +93.126.41.134 +93.126.41.135 +93.126.41.136 +93.126.41.137 +93.126.41.138 +93.126.41.139 +93.126.41.140 +93.126.41.141 +93.126.41.142 +93.126.41.143 +93.126.41.144 +93.126.41.145 +93.126.41.147 +93.126.41.148 +93.126.41.149 +93.126.41.150 +93.126.41.151 +93.126.41.153 +93.126.41.154 +93.126.41.155 +93.126.41.156 +93.126.41.157 +93.126.41.158 +93.126.41.159 +93.126.41.160 +93.126.41.161 +93.126.41.163 +93.126.41.164 +93.126.41.165 +93.126.41.166 +93.126.41.167 +93.126.41.168 +93.126.41.169 +93.126.41.170 +93.126.41.171 +93.126.41.172 +93.126.41.173 +93.126.41.174 +93.126.41.175 +93.126.41.176 +93.126.41.177 +93.126.41.178 +93.126.41.179 +93.126.41.180 +93.126.41.181 +93.126.41.182 +93.126.41.183 +93.126.41.184 +93.126.41.185 +93.126.41.186 +93.126.41.187 +93.126.41.188 +93.126.41.189 +93.126.41.190 +93.126.41.192 +93.126.41.193 +93.126.41.194 +93.126.41.195 +93.126.41.196 +93.126.41.198 +93.126.41.200 +93.126.41.201 +93.126.41.202 +93.126.41.203 +93.126.41.204 +93.126.41.205 +93.126.41.207 +93.126.41.208 +93.126.41.210 +93.126.41.211 +93.126.41.212 +93.126.41.213 +93.126.41.214 +93.126.41.215 +93.126.41.216 +93.126.41.217 +93.126.41.218 +93.126.41.219 +93.126.41.220 +93.126.41.221 +93.126.41.222 +93.126.41.223 +93.126.41.225 +93.126.41.226 +93.126.41.227 +93.126.41.228 +93.126.41.229 +93.126.41.230 +93.126.41.231 +93.126.41.232 +93.126.41.233 +93.126.41.234 +93.126.41.235 +93.126.41.236 +93.126.41.237 +93.126.41.238 +93.126.41.239 +93.126.44.161 +93.126.44.162 +93.126.44.163 +93.126.44.164 +93.126.44.165 +93.126.44.166 +93.126.44.167 +93.126.44.169 +93.126.44.170 +93.126.49.151 +93.126.51.142 +93.126.51.144 +93.126.51.179 +93.126.57.4 +93.126.58.43 +93.126.58.69 +93.126.58.245 +94.74.128.169 +94.74.128.185 +94.74.128.251 +94.74.129.52 +94.74.130.172 +94.74.132.135 +94.74.133.174 +94.74.133.178 +94.74.135.180 +94.74.136.120 +94.74.136.145 +94.74.136.187 +94.74.136.240 +94.74.138.44 +94.74.138.54 +94.74.138.123 +94.74.138.124 +94.74.138.146 +94.74.138.174 +94.74.138.201 +94.74.138.210 +94.74.138.226 +94.74.139.44 +94.74.139.149 +94.74.139.168 +94.74.139.216 +94.74.139.252 +94.74.141.70 +94.74.141.176 +94.74.142.165 +94.74.144.14 +94.74.144.25 +94.74.144.31 +94.74.144.166 +94.74.145.2 +94.74.145.28 +94.74.145.110 +94.74.146.192 +94.74.146.200 +94.74.146.251 +94.74.149.5 +94.74.149.71 +94.74.150.75 +94.74.150.184 +94.74.151.34 +94.74.160.61 +94.74.160.86 +94.74.160.88 +94.74.161.41 +94.74.161.51 +94.74.161.101 +94.74.161.166 +94.74.161.169 +94.74.161.237 +94.74.161.251 +94.74.163.22 +94.74.163.185 +94.74.163.186 +94.74.165.190 +94.74.166.42 +94.74.166.99 +94.74.167.161 +94.74.168.200 +94.74.168.201 +94.74.168.202 +94.74.168.203 +94.74.168.206 +94.74.169.9 +94.74.169.10 +94.74.169.29 +94.74.169.30 +94.74.172.64 +94.74.172.104 +94.74.172.155 +94.74.172.173 +94.74.174.73 +94.74.174.122 +94.74.174.232 +94.74.174.235 +94.74.175.55 +94.74.177.157 +94.74.177.220 +94.74.178.235 +94.74.179.219 +94.74.180.242 +94.74.183.30 +94.74.183.32 +94.74.183.196 +94.74.186.1 +94.74.186.9 +94.74.186.53 +94.74.186.103 +94.74.186.187 +94.74.188.48 +94.74.188.53 +94.74.188.151 +94.74.188.199 +94.74.188.210 +94.74.190.92 +94.101.128.11 +94.101.128.124 +94.101.128.174 +94.101.129.174 +94.101.131.160 +94.101.131.176 +94.101.132.45 +94.101.133.28 +94.101.133.133 +94.101.133.192 +94.101.140.10 +94.101.177.11 +94.101.177.92 +94.101.178.227 +94.101.179.5 +94.101.179.22 +94.101.179.216 +94.101.180.83 +94.101.180.180 +94.101.184.113 +94.101.185.66 +94.101.185.103 +94.101.185.253 +94.101.186.9 +94.101.186.145 +94.101.187.203 +94.139.180.63 +94.139.180.84 +94.139.180.131 +94.139.182.140 +94.139.183.152 +94.139.183.153 +94.139.191.205 +94.139.191.216 +94.182.0.43 +94.182.0.131 +94.182.0.152 +94.182.0.217 +94.182.1.188 +94.182.1.195 +94.182.1.227 +94.182.2.14 +94.182.2.48 +94.182.2.99 +94.182.2.110 +94.182.2.126 +94.182.2.174 +94.182.2.193 +94.182.2.245 +94.182.10.166 +94.182.16.81 +94.182.16.82 +94.182.16.83 +94.182.16.84 +94.182.16.85 +94.182.16.86 +94.182.16.87 +94.182.16.88 +94.182.16.90 +94.182.17.163 +94.182.18.5 +94.182.18.26 +94.182.18.46 +94.182.18.118 +94.182.18.137 +94.182.18.150 +94.182.18.162 +94.182.18.191 +94.182.18.207 +94.182.18.208 +94.182.18.210 +94.182.18.225 +94.182.18.248 +94.182.18.251 +94.182.19.97 +94.182.20.8 +94.182.20.22 +94.182.22.18 +94.182.22.20 +94.182.22.24 +94.182.22.26 +94.182.22.31 +94.182.26.26 +94.182.26.28 +94.182.26.58 +94.182.26.82 +94.182.27.58 +94.182.27.101 +94.182.27.102 +94.182.27.103 +94.182.27.107 +94.182.27.142 +94.182.27.230 +94.182.31.3 +94.182.31.13 +94.182.31.25 +94.182.31.37 +94.182.31.57 +94.182.31.61 +94.182.31.68 +94.182.31.73 +94.182.31.91 +94.182.31.98 +94.182.31.106 +94.182.31.112 +94.182.31.119 +94.182.31.123 +94.182.31.126 +94.182.31.130 +94.182.31.143 +94.182.31.148 +94.182.31.165 +94.182.31.175 +94.182.31.180 +94.182.31.183 +94.182.31.187 +94.182.34.59 +94.182.35.22 +94.182.35.86 +94.182.35.150 +94.182.35.167 +94.182.35.187 +94.182.35.201 +94.182.35.210 +94.182.35.246 +94.182.35.247 +94.182.35.253 +94.182.36.26 +94.182.36.121 +94.182.36.134 +94.182.36.182 +94.182.36.192 +94.182.36.202 +94.182.36.214 +94.182.36.225 +94.182.36.231 +94.182.36.251 +94.182.37.9 +94.182.37.113 +94.182.37.163 +94.182.37.213 +94.182.37.234 +94.182.38.2 +94.182.38.74 +94.182.38.88 +94.182.38.89 +94.182.38.90 +94.182.38.91 +94.182.38.113 +94.182.39.195 +94.182.39.224 +94.182.39.225 +94.182.39.226 +94.182.39.227 +94.182.39.228 +94.182.39.229 +94.182.39.231 +94.182.39.232 +94.182.39.233 +94.182.39.234 +94.182.39.235 +94.182.39.236 +94.182.39.237 +94.182.39.238 +94.182.39.239 +94.182.42.7 +94.182.42.8 +94.182.43.2 +94.182.43.30 +94.182.43.86 +94.182.44.157 +94.182.44.174 +94.182.46.46 +94.182.46.197 +94.182.47.138 +94.182.48.4 +94.182.48.11 +94.182.48.14 +94.182.48.49 +94.182.48.50 +94.182.48.69 +94.182.48.70 +94.182.48.71 +94.182.49.6 +94.182.49.54 +94.182.49.106 +94.182.49.113 +94.182.49.227 +94.182.49.232 +94.182.49.240 +94.182.50.49 +94.182.50.50 +94.182.50.51 +94.182.50.52 +94.182.50.53 +94.182.50.54 +94.182.50.55 +94.182.50.60 +94.182.50.62 +94.182.50.66 +94.182.50.67 +94.182.50.69 +94.182.50.70 +94.182.50.73 +94.182.50.74 +94.182.50.78 +94.182.50.94 +94.182.50.129 +94.182.50.147 +94.182.52.49 +94.182.52.50 +94.182.52.68 +94.182.52.72 +94.182.52.91 +94.182.52.92 +94.182.53.66 +94.182.53.94 +94.182.53.161 +94.182.53.162 +94.182.53.178 +94.182.53.186 +94.182.53.189 +94.182.53.237 +94.182.53.246 +94.182.54.2 +94.182.54.7 +94.182.54.12 +94.182.54.38 +94.182.54.77 +94.182.54.200 +94.182.54.202 +94.182.54.204 +94.182.54.207 +94.182.54.218 +94.182.56.6 +94.182.56.20 +94.182.56.33 +94.182.56.58 +94.182.56.60 +94.182.56.254 +94.182.62.1 +94.182.83.203 +94.182.83.215 +94.182.85.86 +94.182.87.148 +94.182.87.213 +94.182.87.221 +94.182.91.212 +94.182.92.46 +94.182.92.87 +94.182.95.178 +94.182.104.157 +94.182.110.254 +94.182.129.67 +94.182.134.138 +94.182.136.21 +94.182.136.107 +94.182.136.163 +94.182.137.91 +94.182.146.6 +94.182.146.7 +94.182.146.92 +94.182.149.69 +94.182.149.80 +94.182.150.184 +94.182.155.132 +94.182.155.135 +94.182.155.200 +94.182.156.116 +94.182.156.188 +94.182.157.184 +94.182.168.168 +94.182.168.220 +94.182.169.56 +94.182.169.113 +94.182.170.150 +94.182.170.196 +94.182.170.242 +94.182.172.195 +94.182.172.201 +94.182.172.252 +94.182.175.3 +94.182.175.29 +94.182.175.30 +94.182.175.57 +94.182.176.141 +94.182.176.143 +94.182.176.144 +94.182.178.96 +94.182.178.106 +94.182.178.139 +94.182.180.141 +94.182.183.154 +94.182.186.227 +94.182.186.228 +94.182.192.4 +94.182.192.30 +94.182.192.31 +94.182.192.46 +94.182.192.50 +94.182.192.57 +94.182.192.79 +94.182.192.85 +94.182.192.107 +94.182.192.120 +94.182.192.139 +94.182.192.169 +94.182.192.170 +94.182.192.173 +94.182.192.192 +94.182.192.228 +94.182.192.231 +94.182.192.248 +94.182.192.253 +94.182.193.3 +94.182.193.12 +94.182.193.36 +94.182.193.40 +94.182.193.44 +94.182.193.52 +94.182.193.76 +94.182.193.78 +94.182.193.93 +94.182.193.98 +94.182.193.106 +94.182.193.112 +94.182.193.113 +94.182.193.140 +94.182.193.141 +94.182.193.155 +94.182.193.171 +94.182.193.175 +94.182.193.183 +94.182.193.184 +94.182.193.186 +94.182.193.189 +94.182.193.230 +94.182.193.235 +94.182.193.238 +94.182.193.243 +94.182.193.244 +94.182.194.73 +94.182.194.74 +94.182.194.83 +94.182.194.98 +94.182.194.120 +94.182.194.149 +94.182.194.151 +94.182.194.181 +94.182.194.184 +94.182.194.185 +94.182.194.195 +94.182.194.198 +94.182.194.206 +94.182.194.227 +94.182.194.231 +94.182.195.14 +94.182.195.58 +94.182.195.61 +94.182.195.160 +94.182.196.62 +94.182.196.82 +94.182.196.238 +94.182.197.62 +94.182.197.142 +94.182.197.153 +94.182.197.154 +94.182.197.174 +94.182.197.198 +94.182.198.6 +94.182.198.30 +94.182.198.34 +94.182.198.78 +94.182.198.194 +94.182.198.214 +94.182.198.238 +94.182.199.106 +94.182.199.226 +94.182.199.246 +94.182.199.250 +94.182.201.9 +94.182.201.25 +94.182.201.58 +94.182.201.59 +94.182.201.115 +94.182.202.91 +94.182.202.185 +94.182.202.216 +94.182.202.217 +94.182.203.114 +94.182.203.115 +94.182.203.116 +94.182.203.117 +94.182.203.178 +94.182.209.2 +94.182.211.62 +94.182.211.250 +94.182.211.251 +94.182.214.179 +94.182.214.180 +94.182.214.181 +94.182.214.183 +94.182.214.184 +94.182.214.186 +94.182.214.187 +94.182.214.188 +94.182.214.190 +94.182.216.1 +94.182.216.2 +94.182.219.26 +94.182.219.54 +94.182.219.129 +94.182.219.154 +94.182.219.162 +94.182.219.246 +94.182.219.247 +94.182.220.6 +94.182.225.139 +94.182.225.152 +94.182.225.164 +94.182.225.175 +94.182.225.183 +94.182.225.231 +94.182.225.233 +94.182.225.238 +94.182.225.254 +94.182.227.48 +94.182.227.93 +94.182.227.139 +94.182.227.177 +94.182.250.3 +94.182.250.4 +94.182.250.5 +94.182.250.50 +94.182.251.115 +94.182.251.125 +94.182.251.151 +94.182.253.71 +94.182.253.135 +94.182.254.5 +94.182.254.117 +94.182.254.121 +94.182.254.122 +94.182.255.139 +94.182.255.200 +94.183.0.37 +94.183.0.46 +94.183.0.54 +94.183.0.85 +94.183.0.97 +94.183.0.125 +94.183.0.167 +94.183.0.241 +94.183.1.21 +94.183.1.50 +94.183.1.98 +94.183.1.229 +94.183.1.246 +94.183.2.47 +94.183.2.151 +94.183.2.211 +94.183.3.130 +94.183.3.150 +94.183.3.196 +94.183.5.95 +94.183.6.186 +94.183.6.229 +94.183.6.245 +94.183.7.129 +94.183.7.199 +94.183.7.207 +94.183.11.9 +94.183.11.29 +94.183.11.36 +94.183.11.87 +94.183.11.100 +94.183.11.160 +94.183.13.233 +94.183.14.202 +94.183.15.43 +94.183.15.64 +94.183.15.245 +94.183.16.15 +94.183.16.45 +94.183.17.128 +94.183.17.141 +94.183.18.36 +94.183.20.50 +94.183.20.147 +94.183.21.119 +94.183.22.114 +94.183.23.1 +94.183.23.2 +94.183.23.163 +94.183.23.207 +94.183.24.57 +94.183.24.98 +94.183.26.10 +94.183.26.122 +94.183.26.130 +94.183.26.132 +94.183.27.72 +94.183.27.83 +94.183.27.96 +94.183.27.154 +94.183.27.159 +94.183.27.223 +94.183.27.235 +94.183.28.24 +94.183.28.60 +94.183.28.85 +94.183.28.92 +94.183.28.102 +94.183.28.111 +94.183.28.141 +94.183.28.159 +94.183.28.195 +94.183.28.235 +94.183.29.4 +94.183.29.100 +94.183.29.112 +94.183.29.113 +94.183.29.137 +94.183.29.167 +94.183.29.187 +94.183.30.36 +94.183.30.38 +94.183.30.42 +94.183.30.43 +94.183.30.46 +94.183.30.65 +94.183.30.78 +94.183.30.88 +94.183.30.107 +94.183.30.116 +94.183.30.117 +94.183.30.128 +94.183.30.161 +94.183.30.164 +94.183.30.167 +94.183.30.170 +94.183.30.171 +94.183.30.199 +94.183.30.202 +94.183.30.204 +94.183.30.212 +94.183.30.216 +94.183.31.23 +94.183.31.63 +94.183.31.73 +94.183.31.124 +94.183.31.156 +94.183.31.202 +94.183.31.205 +94.183.32.25 +94.183.32.86 +94.183.33.78 +94.183.33.240 +94.183.34.55 +94.183.34.98 +94.183.34.232 +94.183.35.74 +94.183.35.226 +94.183.37.2 +94.183.37.76 +94.183.38.141 +94.183.40.253 +94.183.42.107 +94.183.44.111 +94.183.46.108 +94.183.46.116 +94.183.48.10 +94.183.48.15 +94.183.49.6 +94.183.49.21 +94.183.49.41 +94.183.49.47 +94.183.49.130 +94.183.49.206 +94.183.50.97 +94.183.50.174 +94.183.50.187 +94.183.50.231 +94.183.51.95 +94.183.51.144 +94.183.51.165 +94.183.51.215 +94.183.52.51 +94.183.52.67 +94.183.52.94 +94.183.52.215 +94.183.52.227 +94.183.53.29 +94.183.53.99 +94.183.53.113 +94.183.53.133 +94.183.56.163 +94.183.56.191 +94.183.56.229 +94.183.56.251 +94.183.57.45 +94.183.57.66 +94.183.57.215 +94.183.58.29 +94.183.58.79 +94.183.58.188 +94.183.58.198 +94.183.59.32 +94.183.59.75 +94.183.59.104 +94.183.59.114 +94.183.59.197 +94.183.59.245 +94.183.60.19 +94.183.60.28 +94.183.61.126 +94.183.61.145 +94.183.61.218 +94.183.62.125 +94.183.62.141 +94.183.62.222 +94.183.63.68 +94.183.67.57 +94.183.67.84 +94.183.68.106 +94.183.70.115 +94.183.70.140 +94.183.72.99 +94.183.72.116 +94.183.72.164 +94.183.72.187 +94.183.72.243 +94.183.73.76 +94.183.73.77 +94.183.73.78 +94.183.73.111 +94.183.73.214 +94.183.73.218 +94.183.73.246 +94.183.74.15 +94.183.74.58 +94.183.74.78 +94.183.74.101 +94.183.74.184 +94.183.75.49 +94.183.78.74 +94.183.80.22 +94.183.82.7 +94.183.82.216 +94.183.83.1 +94.183.83.19 +94.183.83.115 +94.183.83.183 +94.183.83.211 +94.183.83.241 +94.183.84.144 +94.183.84.236 +94.183.85.31 +94.183.85.115 +94.183.85.139 +94.183.85.190 +94.183.85.232 +94.183.88.131 +94.183.88.134 +94.183.90.106 +94.183.90.166 +94.183.90.221 +94.183.90.227 +94.183.91.32 +94.183.91.82 +94.183.91.241 +94.183.92.64 +94.183.92.70 +94.183.92.77 +94.183.92.84 +94.183.92.92 +94.183.92.95 +94.183.92.112 +94.183.92.121 +94.183.92.227 +94.183.92.232 +94.183.93.28 +94.183.93.48 +94.183.93.51 +94.183.93.52 +94.183.93.74 +94.183.93.103 +94.183.93.106 +94.183.93.109 +94.183.93.117 +94.183.93.123 +94.183.93.200 +94.183.93.210 +94.183.93.228 +94.183.93.239 +94.183.94.29 +94.183.94.38 +94.183.94.39 +94.183.94.40 +94.183.94.54 +94.183.94.55 +94.183.95.114 +94.183.95.122 +94.183.95.146 +94.183.95.171 +94.183.95.206 +94.183.95.208 +94.183.97.22 +94.183.99.63 +94.183.99.81 +94.183.99.111 +94.183.99.176 +94.183.99.210 +94.183.99.218 +94.183.99.222 +94.183.99.226 +94.183.99.237 +94.183.99.238 +94.183.100.59 +94.183.100.202 +94.183.100.251 +94.183.102.151 +94.183.102.226 +94.183.103.139 +94.183.104.107 +94.183.106.110 +94.183.107.102 +94.183.108.65 +94.183.109.224 +94.183.110.113 +94.183.110.223 +94.183.111.6 +94.183.113.41 +94.183.113.86 +94.183.114.81 +94.183.114.92 +94.183.115.73 +94.183.115.167 +94.183.115.187 +94.183.115.188 +94.183.116.18 +94.183.116.56 +94.183.116.126 +94.183.116.220 +94.183.117.124 +94.183.117.200 +94.183.118.6 +94.183.118.14 +94.183.118.42 +94.183.118.107 +94.183.118.123 +94.183.118.127 +94.183.119.28 +94.183.119.88 +94.183.119.104 +94.183.119.202 +94.183.119.246 +94.183.120.17 +94.183.120.34 +94.183.120.86 +94.183.120.104 +94.183.120.224 +94.183.122.16 +94.183.122.17 +94.183.122.64 +94.183.122.126 +94.183.122.174 +94.183.123.128 +94.183.123.129 +94.183.123.132 +94.183.124.16 +94.183.124.21 +94.183.124.30 +94.183.124.45 +94.183.124.77 +94.183.124.130 +94.183.124.140 +94.183.124.187 +94.183.124.194 +94.183.125.56 +94.183.125.172 +94.183.125.187 +94.183.126.39 +94.183.126.41 +94.183.126.42 +94.183.126.111 +94.183.126.211 +94.183.127.13 +94.183.127.27 +94.183.127.40 +94.183.127.76 +94.183.127.100 +94.183.127.114 +94.183.127.123 +94.183.127.163 +94.183.129.209 +94.183.132.5 +94.183.132.100 +94.183.132.119 +94.183.132.126 +94.183.132.128 +94.183.132.143 +94.183.132.144 +94.183.132.174 +94.183.132.214 +94.183.133.82 +94.183.133.113 +94.183.133.132 +94.183.133.146 +94.183.133.159 +94.183.133.175 +94.183.133.223 +94.183.133.235 +94.183.133.247 +94.183.134.150 +94.183.134.153 +94.183.134.202 +94.183.134.255 +94.183.135.140 +94.183.138.109 +94.183.139.136 +94.183.140.110 +94.183.140.138 +94.183.141.68 +94.183.144.185 +94.183.145.89 +94.183.145.147 +94.183.145.155 +94.183.145.214 +94.183.146.186 +94.183.146.203 +94.183.146.221 +94.183.147.241 +94.183.148.65 +94.183.148.98 +94.183.148.129 +94.183.148.184 +94.183.148.238 +94.183.149.65 +94.183.149.85 +94.183.149.140 +94.183.149.146 +94.183.149.171 +94.183.149.204 +94.183.149.249 +94.183.149.252 +94.183.150.73 +94.183.150.75 +94.183.150.115 +94.183.150.229 +94.183.154.207 +94.183.156.128 +94.183.156.137 +94.183.162.195 +94.183.163.52 +94.183.163.60 +94.183.163.75 +94.183.164.37 +94.183.164.49 +94.183.172.174 +94.183.172.182 +94.183.173.6 +94.183.175.157 +94.183.176.45 +94.183.180.21 +94.184.3.203 +94.184.3.226 +94.184.3.227 +94.184.3.230 +94.184.10.98 +94.184.16.7 +94.184.16.178 +94.184.17.213 +94.184.18.24 +94.184.18.44 +94.184.18.189 +94.184.21.66 +94.184.22.6 +94.184.22.158 +94.184.23.65 +94.184.23.243 +94.184.29.33 +94.184.29.218 +94.184.90.225 +94.184.177.129 +94.184.208.77 +94.184.208.102 +94.199.0.1 +94.199.0.41 +94.199.0.60 +94.199.0.78 +94.199.0.212 +94.232.168.162 +94.232.171.82 +94.232.171.83 +94.232.171.84 +94.232.171.85 +94.232.171.91 +94.232.172.109 +94.232.173.8 +94.232.173.136 +94.232.173.192 +94.232.175.210 +94.232.175.211 +95.38.10.9 +95.38.10.65 +95.38.10.153 +95.38.10.195 +95.38.10.197 +95.38.10.219 +95.38.10.230 +95.38.10.253 +95.38.11.1 +95.38.11.26 +95.38.11.42 +95.38.11.46 +95.38.11.84 +95.38.11.89 +95.38.11.94 +95.38.11.95 +95.38.11.201 +95.38.11.223 +95.38.15.205 +95.38.15.247 +95.38.20.66 +95.38.20.67 +95.38.24.146 +95.38.25.0 +95.38.27.16 +95.38.27.32 +95.38.27.36 +95.38.34.30 +95.38.34.58 +95.38.34.59 +95.38.34.60 +95.38.34.242 +95.38.35.44 +95.38.38.75 +95.38.38.83 +95.38.39.173 +95.38.46.84 +95.38.46.130 +95.38.47.173 +95.38.48.253 +95.38.49.157 +95.38.50.102 +95.38.52.218 +95.38.53.118 +95.38.60.5 +95.38.60.11 +95.38.60.42 +95.38.60.252 +95.38.63.47 +95.38.63.59 +95.38.63.200 +95.38.63.234 +95.38.65.2 +95.38.65.3 +95.38.65.5 +95.38.65.234 +95.38.66.23 +95.38.72.23 +95.38.75.75 +95.38.75.79 +95.38.76.42 +95.38.76.160 +95.38.77.190 +95.38.78.69 +95.38.78.239 +95.38.86.50 +95.38.86.51 +95.38.86.52 +95.38.86.53 +95.38.86.54 +95.38.86.55 +95.38.86.56 +95.38.86.59 +95.38.86.60 +95.38.86.61 +95.38.86.62 +95.38.89.16 +95.38.89.17 +95.38.89.18 +95.38.89.19 +95.38.89.21 +95.38.89.77 +95.38.89.78 +95.38.89.80 +95.38.89.90 +95.38.89.95 +95.38.89.107 +95.38.89.112 +95.38.89.192 +95.38.89.199 +95.38.94.14 +95.38.94.21 +95.38.94.218 +95.38.96.230 +95.38.99.146 +95.38.102.89 +95.38.103.70 +95.38.103.218 +95.38.103.254 +95.38.105.66 +95.38.105.71 +95.38.105.122 +95.38.106.146 +95.38.123.254 +95.38.130.108 +95.38.131.28 +95.38.131.44 +95.38.131.59 +95.38.131.81 +95.38.131.93 +95.38.131.95 +95.38.131.110 +95.38.131.143 +95.38.131.144 +95.38.131.159 +95.38.131.163 +95.38.131.194 +95.38.131.199 +95.38.131.216 +95.38.131.243 +95.38.131.247 +95.38.131.248 +95.38.134.24 +95.38.134.26 +95.38.134.50 +95.38.134.85 +95.38.134.190 +95.38.134.202 +95.38.135.12 +95.38.135.140 +95.38.138.38 +95.38.138.154 +95.38.138.174 +95.38.138.202 +95.38.142.34 +95.38.142.62 +95.38.144.16 +95.38.144.17 +95.38.144.26 +95.38.144.41 +95.38.144.127 +95.38.144.158 +95.38.144.178 +95.38.144.203 +95.38.145.12 +95.38.145.68 +95.38.145.73 +95.38.145.74 +95.38.145.78 +95.38.145.95 +95.38.146.86 +95.38.146.202 +95.38.146.217 +95.38.147.86 +95.38.147.133 +95.38.147.134 +95.38.147.190 +95.38.148.15 +95.38.148.59 +95.38.148.163 +95.38.148.171 +95.38.148.198 +95.38.149.2 +95.38.149.176 +95.38.153.226 +95.38.155.50 +95.38.155.74 +95.38.156.45 +95.38.156.53 +95.38.156.56 +95.38.156.60 +95.38.157.90 +95.38.157.120 +95.38.159.194 +95.38.159.254 +95.38.169.118 +95.38.169.154 +95.38.169.202 +95.38.169.222 +95.38.170.16 +95.38.170.17 +95.38.174.38 +95.38.174.65 +95.38.174.81 +95.38.174.83 +95.38.174.85 +95.38.174.87 +95.38.174.106 +95.38.174.226 +95.38.192.99 +95.38.192.140 +95.38.192.167 +95.38.192.183 +95.38.196.129 +95.38.196.134 +95.38.196.190 +95.38.196.216 +95.38.197.134 +95.38.197.254 +95.38.199.0 +95.38.199.10 +95.38.199.18 +95.38.201.30 +95.38.201.114 +95.38.201.201 +95.38.201.211 +95.38.203.178 +95.38.209.226 +95.38.209.227 +95.38.209.229 +95.38.209.230 +95.38.209.231 +95.38.209.232 +95.38.209.233 +95.38.209.234 +95.38.209.235 +95.38.211.243 +95.38.211.250 +95.38.224.86 +95.38.224.151 +95.38.224.202 +95.38.224.218 +95.38.241.65 +95.38.243.46 +95.38.243.250 +95.38.244.50 +95.38.244.114 +95.38.244.115 +95.38.244.120 +95.38.244.121 +95.38.244.122 +95.38.244.124 +95.38.244.126 +95.38.245.14 +95.38.245.33 +95.38.245.58 +95.38.245.62 +95.38.245.146 +95.38.245.147 +95.38.245.148 +95.38.245.149 +95.38.245.150 +95.38.246.19 +95.38.248.184 +95.38.248.194 +95.38.248.246 +95.38.248.250 +95.38.250.184 +95.38.250.195 +95.38.250.230 +95.80.140.2 +95.80.140.26 +95.80.140.32 +95.80.140.53 +95.80.140.64 +95.80.140.80 +95.80.140.91 +95.80.140.92 +95.80.140.110 +95.80.140.126 +95.80.140.159 +95.80.140.162 +95.80.140.205 +95.80.140.210 +95.80.140.229 +95.80.140.233 +95.80.140.238 +95.80.141.2 +95.80.141.12 +95.80.141.15 +95.80.141.18 +95.80.141.37 +95.80.141.48 +95.80.141.71 +95.80.141.74 +95.80.141.81 +95.80.141.84 +95.80.141.103 +95.80.141.110 +95.80.141.112 +95.80.141.117 +95.80.141.124 +95.80.141.142 +95.80.141.150 +95.80.141.151 +95.80.141.162 +95.80.141.167 +95.80.141.176 +95.80.141.190 +95.80.141.214 +95.80.141.220 +95.80.141.238 +95.80.141.253 +95.80.142.4 +95.80.142.9 +95.80.142.29 +95.80.142.33 +95.80.142.42 +95.80.142.54 +95.80.142.57 +95.80.142.81 +95.80.142.96 +95.80.142.102 +95.80.142.106 +95.80.142.117 +95.80.142.124 +95.80.142.140 +95.80.142.150 +95.80.142.167 +95.80.142.177 +95.80.142.199 +95.80.142.217 +95.80.142.224 +95.80.142.228 +95.80.142.244 +95.80.142.248 +95.80.143.1 +95.80.143.11 +95.80.143.28 +95.80.143.31 +95.80.143.45 +95.80.143.82 +95.80.143.83 +95.80.143.119 +95.80.143.134 +95.80.143.137 +95.80.143.138 +95.80.143.151 +95.80.143.160 +95.80.143.163 +95.80.143.169 +95.80.143.183 +95.80.143.198 +95.80.143.224 +95.80.157.65 +95.80.159.242 +95.80.160.58 +95.80.160.66 +95.80.160.180 +95.80.161.130 +95.80.168.12 +95.80.168.22 +95.80.168.56 +95.80.168.63 +95.80.168.69 +95.80.168.90 +95.80.168.115 +95.80.168.151 +95.80.168.161 +95.80.168.194 +95.80.168.203 +95.80.168.221 +95.80.168.233 +95.80.169.1 +95.80.169.8 +95.80.169.14 +95.80.169.20 +95.80.169.48 +95.80.169.61 +95.80.169.62 +95.80.169.78 +95.80.169.101 +95.80.169.131 +95.80.169.138 +95.80.169.152 +95.80.169.177 +95.80.169.185 +95.80.169.190 +95.80.169.191 +95.80.169.198 +95.80.169.215 +95.80.170.4 +95.80.170.24 +95.80.170.25 +95.80.170.36 +95.80.170.42 +95.80.170.53 +95.80.170.81 +95.80.170.95 +95.80.170.104 +95.80.170.125 +95.80.170.133 +95.80.170.134 +95.80.170.142 +95.80.170.167 +95.80.170.176 +95.80.170.180 +95.80.170.225 +95.80.170.251 +95.80.171.12 +95.80.171.23 +95.80.171.32 +95.80.171.93 +95.80.171.94 +95.80.171.113 +95.80.171.122 +95.80.171.141 +95.80.171.168 +95.80.171.197 +95.80.171.216 +95.80.171.249 +95.80.172.18 +95.80.172.36 +95.80.172.55 +95.80.172.88 +95.80.172.123 +95.80.172.128 +95.80.172.138 +95.80.172.140 +95.80.172.157 +95.80.172.165 +95.80.172.191 +95.80.172.222 +95.80.172.242 +95.80.172.248 +95.80.172.250 +95.80.173.89 +95.80.173.99 +95.80.173.137 +95.80.173.141 +95.80.173.146 +95.80.173.156 +95.80.173.158 +95.80.173.168 +95.80.173.228 +95.80.173.236 +95.80.173.244 +95.80.173.253 +95.80.174.2 +95.80.174.3 +95.80.174.12 +95.80.174.19 +95.80.174.83 +95.80.174.100 +95.80.174.109 +95.80.174.152 +95.80.174.160 +95.80.174.180 +95.80.174.181 +95.80.174.187 +95.80.174.191 +95.80.174.196 +95.80.174.200 +95.80.174.214 +95.80.174.222 +95.80.174.229 +95.80.174.237 +95.80.175.21 +95.80.175.25 +95.80.175.33 +95.80.175.34 +95.80.175.57 +95.80.175.64 +95.80.175.93 +95.80.175.99 +95.80.175.127 +95.80.175.167 +95.80.175.184 +95.80.175.205 +95.80.175.228 +95.80.175.235 +95.80.190.10 +95.80.190.11 +95.130.56.214 +95.130.58.25 +95.130.58.40 +95.130.60.34 +95.130.62.46 +95.130.225.12 +95.130.225.65 +95.141.39.218 +95.142.226.13 +95.142.237.244 +95.156.236.25 +95.156.236.27 +95.156.236.58 +95.156.236.59 +95.156.236.60 +95.156.253.34 +95.156.253.51 +95.215.59.140 +95.215.160.1 +95.215.160.22 +95.215.160.62 +95.215.160.108 +95.215.160.126 +95.215.160.157 +95.215.160.195 +95.215.160.217 +95.215.160.223 +95.215.161.106 +95.215.161.125 +95.215.161.137 +95.215.161.187 +95.215.161.231 +95.215.161.233 +95.215.161.246 +102.133.234.155 +102.215.240.26 +102.215.240.45 +102.215.240.66 +102.215.240.72 +102.215.240.74 +102.215.240.81 +102.215.240.120 +102.215.240.121 +102.215.240.216 +102.215.242.125 +102.215.242.149 +102.215.242.182 +102.215.243.18 +102.215.243.98 +103.46.211.146 +103.53.18.115 +103.53.19.201 +103.53.25.216 +103.53.27.90 +103.53.27.208 +103.130.146.35 +103.130.146.36 +103.130.146.37 +103.130.146.43 +103.130.146.195 +103.130.146.198 +103.130.146.206 +103.130.146.235 +103.130.146.238 +103.213.107.241 +103.215.220.216 +103.215.223.64 +103.215.223.91 +103.216.61.171 +103.216.63.202 +103.216.63.203 +103.216.63.204 +103.231.136.0 +103.231.136.255 +103.231.137.0 +103.231.137.255 +103.231.138.0 +103.231.138.1 +103.231.139.0 +103.231.139.255 +109.70.237.25 +109.70.237.44 +109.70.237.50 +109.70.237.67 +109.70.237.94 +109.70.237.118 +109.70.237.123 +109.70.237.137 +109.70.237.177 +109.70.237.207 +109.70.237.233 +109.70.237.235 +109.70.237.245 +109.72.195.128 +109.72.195.139 +109.72.195.159 +109.72.195.161 +109.72.195.178 +109.72.195.184 +109.72.195.188 +109.72.195.192 +109.72.195.203 +109.72.195.211 +109.72.195.221 +109.72.195.223 +109.72.195.225 +109.72.195.226 +109.72.195.230 +109.72.195.234 +109.72.195.237 +109.72.195.239 +109.72.195.243 +109.72.195.251 +109.72.195.255 +109.72.201.9 +109.72.201.14 +109.72.201.17 +109.72.201.29 +109.72.201.36 +109.72.201.44 +109.72.201.47 +109.72.201.51 +109.72.201.54 +109.72.201.61 +109.72.201.65 +109.72.201.66 +109.72.201.73 +109.72.201.80 +109.72.201.81 +109.72.201.84 +109.72.201.87 +109.72.201.115 +109.72.201.125 +109.72.201.127 +109.72.201.128 +109.72.201.154 +109.72.201.155 +109.72.201.163 +109.72.201.167 +109.72.201.183 +109.72.201.187 +109.72.201.193 +109.72.201.216 +109.72.201.223 +109.72.201.231 +109.72.201.239 +109.72.201.242 +109.74.226.188 +109.74.228.195 +109.74.230.245 +109.74.238.178 +109.94.164.100 +109.94.164.170 +109.95.60.4 +109.95.60.33 +109.95.60.38 +109.95.60.56 +109.95.60.57 +109.95.60.59 +109.95.60.61 +109.95.60.77 +109.95.60.97 +109.95.60.139 +109.95.60.149 +109.95.60.242 +109.95.60.255 +109.95.61.201 +109.95.61.204 +109.95.61.245 +109.95.61.254 +109.95.62.134 +109.95.63.192 +109.95.67.100 +109.95.71.25 +109.109.42.61 +109.109.43.186 +109.109.43.199 +109.109.46.76 +109.109.52.167 +109.109.54.104 +109.109.54.150 +109.109.55.91 +109.109.63.58 +109.122.227.126 +109.122.228.6 +109.122.228.152 +109.122.230.26 +109.122.231.102 +109.122.232.193 +109.122.233.36 +109.122.236.62 +109.122.238.53 +109.122.238.121 +109.122.240.4 +109.122.240.6 +109.122.240.16 +109.122.240.17 +109.122.240.19 +109.122.240.20 +109.122.240.22 +109.122.245.53 +109.122.245.58 +109.125.128.28 +109.125.128.59 +109.125.128.166 +109.125.128.236 +109.125.129.72 +109.125.129.84 +109.125.129.170 +109.125.129.226 +109.125.130.3 +109.125.130.50 +109.125.130.111 +109.125.130.176 +109.125.131.80 +109.125.131.90 +109.125.131.101 +109.125.131.136 +109.125.131.162 +109.125.132.73 +109.125.132.147 +109.125.132.154 +109.125.133.22 +109.125.133.102 +109.125.133.206 +109.125.134.30 +109.125.134.83 +109.125.134.87 +109.125.134.254 +109.125.135.50 +109.125.135.62 +109.125.135.160 +109.125.135.213 +109.125.136.12 +109.125.136.149 +109.125.136.234 +109.125.136.239 +109.125.137.32 +109.125.137.170 +109.125.137.212 +109.125.138.89 +109.125.140.57 +109.125.140.127 +109.125.140.150 +109.125.140.211 +109.125.141.45 +109.125.141.57 +109.125.141.67 +109.125.141.78 +109.125.141.148 +109.125.141.166 +109.125.141.235 +109.125.141.246 +109.125.142.212 +109.125.143.33 +109.125.144.51 +109.125.144.96 +109.125.144.143 +109.125.145.79 +109.125.145.97 +109.125.146.166 +109.125.146.173 +109.125.148.226 +109.125.160.3 +109.125.160.7 +109.125.160.9 +109.125.160.10 +109.125.160.65 +109.125.160.144 +109.125.160.147 +109.125.160.160 +109.125.160.176 +109.125.160.191 +109.125.160.205 +109.125.160.209 +109.125.160.226 +109.125.160.252 +109.125.168.1 +109.125.168.35 +109.125.168.41 +109.125.168.53 +109.125.168.83 +109.125.168.119 +109.125.168.126 +109.125.168.141 +109.125.168.218 +109.125.168.226 +109.125.168.234 +109.125.178.101 +109.125.191.2 +109.125.191.54 +109.162.128.193 +109.162.158.62 +109.162.158.111 +109.162.196.66 +109.162.196.138 +109.162.197.28 +109.162.197.65 +109.162.198.243 +109.162.230.136 +109.162.251.14 +109.162.251.43 +109.162.251.46 +109.162.251.110 +109.162.251.158 +109.201.0.17 +109.201.0.58 +109.201.2.81 +109.201.8.84 +109.201.8.85 +109.201.8.86 +109.201.13.186 +109.201.13.187 +109.201.13.189 +109.201.13.190 +109.201.15.10 +109.201.15.38 +109.201.15.86 +109.201.15.161 +109.201.17.57 +109.201.19.177 +109.201.19.213 +109.201.19.241 +109.201.19.244 +109.201.19.246 +109.201.23.6 +109.201.23.25 +109.201.23.41 +109.201.23.43 +109.201.23.48 +109.201.23.63 +109.201.23.77 +109.201.23.96 +109.201.23.138 +109.201.23.147 +109.201.23.195 +109.201.24.138 +109.201.24.194 +109.201.24.196 +109.201.27.79 +109.201.27.146 +109.206.254.122 +109.206.254.123 +109.206.254.124 +109.206.254.125 +109.206.254.126 +109.206.254.130 +109.230.72.32 +109.230.72.53 +109.230.72.63 +109.230.72.97 +109.230.72.98 +109.230.72.99 +109.230.72.110 +109.230.72.137 +109.230.72.145 +109.230.72.162 +109.230.72.172 +109.230.72.222 +109.230.72.224 +109.230.72.226 +109.230.72.227 +109.230.72.228 +109.230.72.231 +109.230.72.236 +109.230.72.242 +109.230.72.243 +109.230.72.245 +109.230.73.109 +109.230.73.151 +109.230.74.86 +109.230.74.105 +109.230.75.51 +109.230.77.76 +109.230.77.77 +109.230.77.78 +109.230.77.79 +109.230.78.3 +109.230.78.13 +109.230.78.86 +109.230.78.113 +109.230.78.114 +109.230.78.115 +109.230.78.116 +109.230.78.117 +109.230.78.118 +109.230.78.119 +109.230.78.120 +109.230.78.121 +109.230.78.122 +109.230.78.123 +109.230.78.124 +109.230.78.125 +109.230.78.126 +109.230.78.245 +109.230.79.1 +109.230.79.2 +109.230.79.4 +109.230.79.10 +109.230.79.198 +109.230.79.208 +109.230.79.248 +109.230.79.250 +109.230.79.251 +109.230.80.126 +109.230.80.186 +109.230.80.218 +109.230.80.219 +109.230.80.220 +109.230.80.221 +109.230.80.222 +109.230.80.242 +109.230.81.50 +109.230.81.54 +109.230.81.98 +109.230.81.102 +109.230.81.201 +109.230.82.70 +109.230.83.155 +109.230.83.210 +109.230.83.212 +109.230.83.214 +109.230.83.245 +109.230.88.22 +109.230.88.42 +109.230.88.226 +109.230.88.234 +109.230.88.235 +109.230.88.236 +109.230.88.237 +109.230.89.114 +109.230.89.115 +109.230.89.116 +109.230.89.118 +109.230.89.126 +109.230.89.139 +109.230.89.140 +109.230.89.154 +109.230.89.155 +109.230.89.156 +109.230.89.158 +109.230.89.210 +109.230.90.146 +109.230.90.238 +109.230.91.226 +109.230.91.227 +109.230.91.228 +109.230.92.98 +109.230.92.158 +109.230.92.166 +109.230.92.174 +109.230.92.178 +109.230.92.182 +109.230.92.242 +109.230.93.51 +109.230.93.82 +109.230.93.138 +109.230.93.206 +109.230.94.50 +109.230.94.130 +109.230.94.182 +109.230.95.62 +109.230.95.82 +109.230.95.194 +109.230.95.196 +109.230.95.208 +109.230.95.226 +109.230.95.234 +109.230.200.83 +109.230.200.84 +109.230.200.85 +109.230.204.64 +109.230.205.174 +109.230.221.18 +109.230.221.19 +109.230.221.20 +109.230.221.66 +109.230.221.67 +109.230.221.221 +109.230.221.225 +109.230.242.53 +109.230.242.90 +109.230.242.227 +109.232.0.34 +109.232.0.36 +109.232.0.76 +109.232.0.85 +109.232.0.248 +109.232.0.250 +109.232.0.251 +109.232.1.46 +109.232.1.84 +109.232.1.105 +109.232.1.145 +109.232.1.158 +109.232.1.255 +109.232.2.7 +109.232.2.13 +109.232.2.31 +109.232.2.44 +109.232.2.73 +109.232.2.74 +109.232.2.107 +109.232.2.227 +109.232.2.253 +109.232.2.255 +109.232.3.3 +109.232.3.10 +109.232.3.11 +109.232.3.87 +109.232.3.108 +109.232.3.128 +109.232.3.205 +109.232.4.34 +109.232.4.53 +109.232.4.54 +109.232.4.83 +109.238.181.9 +109.238.187.93 +109.238.187.241 +109.238.187.246 +109.238.188.49 +109.238.188.148 +109.238.188.149 +109.238.189.70 +109.238.190.41 +109.238.190.49 +109.238.190.93 +109.238.191.34 +109.238.191.225 +116.204.242.61 +116.204.242.63 +128.65.176.41 +128.65.176.57 +128.65.176.64 +128.65.176.82 +128.65.176.139 +128.65.176.154 +128.65.177.117 +128.65.177.254 +128.65.178.130 +128.65.178.172 +128.65.178.213 +128.65.179.52 +128.65.179.62 +128.65.179.126 +128.65.179.151 +128.65.179.177 +128.65.179.229 +128.65.179.252 +128.65.181.35 +128.65.181.50 +128.65.181.122 +128.65.181.167 +128.65.181.224 +128.65.181.247 +128.65.182.14 +128.65.182.27 +128.65.182.34 +128.65.182.87 +128.65.183.18 +128.65.183.128 +128.65.183.141 +128.65.183.144 +128.65.183.194 +128.65.184.36 +128.65.184.66 +128.65.184.84 +128.65.184.195 +128.65.184.207 +128.65.185.45 +128.65.185.82 +128.65.185.167 +128.65.185.227 +128.65.185.247 +128.65.186.99 +128.65.187.46 +128.65.188.23 +128.65.188.67 +128.65.188.113 +128.65.188.209 +128.65.189.162 +128.65.190.48 +128.65.190.182 +128.65.191.19 +130.185.72.101 +130.185.72.147 +130.185.72.188 +130.185.72.220 +130.185.72.222 +130.185.72.239 +130.185.73.133 +130.185.73.173 +130.185.73.202 +130.185.73.213 +130.185.74.10 +130.185.74.55 +130.185.74.182 +130.185.74.196 +130.185.74.200 +130.185.75.48 +130.185.75.161 +130.185.75.199 +130.185.76.29 +130.185.76.93 +130.185.76.98 +130.185.76.159 +130.185.76.167 +130.185.76.186 +130.185.76.222 +130.185.76.242 +130.185.77.163 +130.185.77.211 +130.185.77.223 +130.185.77.235 +130.185.78.32 +130.185.78.97 +130.193.77.3 +130.193.77.65 +130.193.77.72 +130.193.77.135 +130.193.77.200 +134.255.200.202 +134.255.201.90 +134.255.201.198 +134.255.206.5 +146.19.212.24 +146.19.212.29 +146.19.212.90 +146.19.212.125 +146.19.212.169 +146.19.212.189 +146.70.86.142 +149.54.11.98 +149.54.64.118 +151.232.1.192 +151.232.1.193 +151.232.1.194 +151.232.1.197 +151.232.1.198 +151.232.1.199 +151.232.1.201 +151.232.2.118 +151.232.3.162 +151.232.8.14 +151.232.8.80 +151.232.8.82 +151.232.8.84 +151.232.8.85 +151.232.8.87 +151.232.8.88 +151.232.8.89 +151.232.8.94 +151.232.9.3 +151.232.9.48 +151.232.9.64 +151.232.9.222 +151.232.17.161 +151.232.18.66 +151.232.18.69 +151.232.23.53 +151.232.36.11 +151.232.36.129 +151.232.37.137 +151.232.76.21 +151.232.76.142 +151.232.80.214 +151.232.81.227 +151.232.83.174 +151.232.83.205 +151.232.84.74 +151.232.87.195 +151.232.124.57 +151.232.130.87 +151.232.143.14 +151.232.143.214 +151.232.193.44 +151.232.195.120 +151.232.201.125 +151.232.202.148 +151.232.203.139 +151.232.207.162 +151.233.48.38 +151.233.48.61 +151.233.48.113 +151.233.49.80 +151.233.49.238 +151.233.50.9 +151.233.50.51 +151.233.50.57 +151.233.50.58 +151.233.50.82 +151.233.50.91 +151.233.50.158 +151.233.50.203 +151.233.50.251 +151.233.51.43 +151.233.51.64 +151.233.51.90 +151.233.51.169 +151.233.51.187 +151.233.51.192 +151.233.51.208 +151.233.51.223 +151.233.51.239 +151.233.52.97 +151.233.52.245 +151.233.52.255 +151.233.53.29 +151.233.53.53 +151.233.53.100 +151.233.53.106 +151.233.53.170 +151.233.53.226 +151.233.54.20 +151.233.54.44 +151.233.54.60 +151.233.54.65 +151.233.54.80 +151.233.54.182 +151.233.54.240 +151.233.55.15 +151.233.55.108 +151.233.55.125 +151.233.55.133 +151.233.55.149 +151.233.56.140 +151.233.56.149 +151.233.56.164 +151.233.57.7 +151.233.57.13 +151.233.57.89 +151.233.57.129 +151.233.57.151 +151.233.57.182 +151.233.57.255 +151.233.58.3 +151.233.58.16 +151.233.58.74 +151.233.58.92 +151.233.58.93 +151.233.58.227 +151.233.59.39 +151.233.59.136 +151.233.75.0 +151.233.76.239 +151.233.81.99 +151.233.89.126 +151.233.103.231 +151.233.144.2 +151.233.170.0 +151.233.172.5 +151.234.24.238 +151.234.25.204 +151.234.26.244 +151.234.27.0 +151.234.27.197 +151.234.29.147 +151.234.30.219 +151.234.30.223 +151.234.31.188 +151.234.31.191 +151.234.31.221 +151.234.72.89 +151.234.73.63 +151.234.73.92 +151.234.73.155 +151.234.75.100 +151.234.75.105 +151.234.77.38 +151.234.77.219 +151.234.87.1 +151.234.87.40 +151.234.87.114 +151.234.87.165 +151.234.87.194 +151.234.87.210 +151.234.87.211 +151.234.87.242 +151.234.92.135 +151.234.113.106 +151.234.115.64 +151.234.150.188 +151.234.151.114 +151.234.156.241 +151.234.157.118 +151.234.158.28 +151.234.158.113 +151.234.158.197 +151.234.161.54 +151.234.161.74 +151.234.161.176 +151.234.161.204 +151.234.161.224 +151.234.161.241 +151.234.161.242 +151.234.161.244 +151.234.186.191 +151.234.189.135 +151.234.191.195 +151.234.191.217 +151.234.213.48 +151.234.219.129 +151.234.225.130 +151.234.226.164 +151.234.242.190 +151.234.245.221 +151.234.249.4 +151.234.249.182 +151.234.249.224 +151.234.250.47 +151.234.251.59 +151.234.251.135 +151.235.0.146 +151.235.0.237 +151.235.5.101 +151.235.10.118 +151.235.10.218 +151.235.11.43 +151.235.11.227 +151.235.13.114 +151.235.13.222 +151.235.13.252 +151.235.17.25 +151.235.18.33 +151.235.18.73 +151.235.28.168 +151.235.29.252 +151.235.34.200 +151.235.34.224 +151.235.36.180 +151.235.37.161 +151.235.128.57 +151.235.128.147 +151.235.128.153 +151.235.129.153 +151.235.130.71 +151.235.133.10 +151.235.134.174 +151.235.135.50 +151.235.135.181 +151.235.135.186 +151.235.136.191 +151.235.136.230 +151.235.137.37 +151.235.138.46 +151.235.138.247 +151.235.140.42 +151.235.140.80 +151.235.140.246 +151.235.141.196 +151.235.142.241 +151.235.164.118 +151.235.165.6 +151.235.167.214 +151.235.169.72 +151.235.175.211 +151.235.180.120 +151.235.183.250 +151.235.186.159 +151.235.187.218 +151.235.188.185 +151.235.190.187 +151.235.190.205 +151.235.192.69 +151.235.193.122 +151.235.196.22 +151.235.197.125 +151.235.198.102 +151.235.201.56 +151.235.201.162 +151.235.204.31 +151.235.206.184 +151.235.208.44 +151.235.210.61 +151.235.210.73 +151.235.212.131 +151.235.213.71 +151.235.214.4 +151.235.215.175 +151.235.215.224 +151.235.216.23 +151.235.216.27 +151.235.217.215 +151.235.218.134 +151.235.220.179 +151.235.220.218 +151.235.221.71 +151.235.223.17 +151.235.224.240 +151.235.225.172 +151.235.227.237 +151.235.229.66 +151.235.229.140 +151.235.229.210 +151.235.232.49 +151.235.233.108 +151.235.233.146 +151.235.234.18 +151.235.234.38 +151.235.236.56 +151.235.236.152 +151.235.237.71 +151.235.237.171 +151.235.239.155 +151.235.239.169 +151.235.239.212 +151.235.240.147 +151.235.242.23 +151.235.243.133 +151.235.249.16 +151.235.250.210 +151.235.253.108 +151.235.253.221 +151.235.254.196 +151.235.255.118 +151.235.255.131 +151.238.184.86 +151.238.184.246 +151.238.185.102 +151.238.186.13 +151.238.186.173 +151.238.187.46 +151.238.187.116 +151.238.190.155 +151.238.190.165 +151.238.193.134 +151.238.193.178 +151.238.193.182 +151.238.194.110 +151.238.195.0 +151.238.195.35 +151.238.196.29 +151.238.196.207 +151.238.196.251 +151.238.197.123 +151.238.198.106 +151.238.198.134 +151.238.204.159 +151.238.205.88 +151.238.205.150 +151.238.207.135 +151.238.207.197 +151.238.208.237 +151.238.208.239 +151.238.209.137 +151.238.210.27 +151.238.210.154 +151.238.211.61 +151.238.212.194 +151.238.212.219 +151.238.213.21 +151.238.213.62 +151.238.213.99 +151.238.216.107 +151.238.218.31 +151.238.218.114 +151.238.220.172 +151.238.220.253 +151.238.222.203 +151.238.222.237 +151.238.223.235 +151.238.224.2 +151.238.225.244 +151.238.226.19 +151.238.232.186 +151.238.233.90 +151.238.234.231 +151.238.240.186 +151.238.242.23 +151.238.243.33 +151.238.244.103 +151.238.248.152 +151.238.248.203 +151.238.250.71 +151.238.251.250 +151.238.252.32 +151.238.252.162 +151.238.253.25 +151.238.253.54 +151.238.254.63 +151.238.254.142 +151.238.254.254 +151.238.255.176 +151.239.1.37 +151.239.1.66 +151.239.11.99 +151.239.11.159 +151.239.14.27 +151.239.14.73 +151.239.15.154 +151.239.15.226 +151.239.16.160 +151.239.17.80 +151.239.18.84 +151.239.18.137 +151.239.18.224 +151.239.19.56 +151.239.19.131 +151.239.19.178 +151.239.22.1 +151.239.22.42 +151.239.22.65 +151.239.24.135 +151.239.25.75 +151.239.25.111 +151.239.38.241 +151.239.39.41 +151.239.40.194 +151.239.40.206 +151.239.40.239 +151.239.42.33 +151.239.42.90 +151.239.43.114 +151.239.44.93 +151.239.44.204 +151.239.45.80 +151.239.45.162 +151.239.49.100 +151.239.51.84 +151.239.52.50 +151.239.52.84 +151.239.55.108 +151.239.57.23 +151.239.57.138 +151.239.57.141 +151.239.57.144 +151.239.59.104 +151.239.60.8 +151.239.60.48 +151.239.62.85 +151.239.63.111 +151.239.64.86 +151.239.64.154 +151.239.64.179 +151.239.66.174 +151.239.69.194 +151.239.72.162 +151.239.76.67 +151.239.76.69 +151.239.76.126 +151.239.77.139 +151.239.79.39 +151.239.79.85 +151.239.79.146 +151.239.87.78 +151.239.88.240 +151.239.89.88 +151.239.89.147 +151.239.89.152 +151.239.89.215 +151.239.91.162 +151.239.92.206 +151.239.94.229 +151.239.95.199 +151.239.96.66 +151.239.97.53 +151.239.98.194 +151.239.100.227 +151.239.103.11 +151.239.103.119 +151.239.104.254 +151.239.105.172 +151.239.105.228 +151.239.111.149 +151.239.112.50 +151.239.113.8 +151.239.113.218 +151.239.113.242 +151.239.114.86 +151.239.115.141 +151.239.116.70 +151.239.116.152 +151.239.117.135 +157.119.188.208 +157.119.188.209 +157.119.188.218 +157.119.191.243 +157.119.191.244 +157.119.191.249 +158.58.184.98 +158.58.185.43 +158.58.185.106 +158.58.185.170 +158.58.187.5 +158.58.187.21 +158.58.187.43 +158.58.187.140 +158.58.187.188 +158.58.188.22 +158.58.190.44 +158.255.74.7 +158.255.74.18 +158.255.74.28 +158.255.74.37 +158.255.74.92 +158.255.74.139 +158.255.74.161 +158.255.74.222 +158.255.74.227 +159.20.96.100 +159.20.102.234 +159.20.109.48 +160.187.18.5 +164.138.16.46 +164.138.16.181 +164.138.16.191 +164.138.17.234 +164.138.18.73 +164.138.19.203 +164.138.19.217 +164.138.19.218 +164.138.19.219 +164.138.19.220 +164.138.19.221 +164.138.20.154 +164.138.21.3 +164.138.22.115 +164.138.22.116 +164.138.128.0 +164.138.128.1 +164.138.128.2 +164.138.128.3 +164.138.128.7 +164.138.128.8 +164.138.203.1 +164.215.59.116 +165.101.26.38 +167.234.255.2 +171.22.24.19 +171.22.24.71 +171.22.24.99 +171.22.24.173 +171.22.24.213 +171.22.25.17 +171.22.25.38 +171.22.25.67 +171.22.25.74 +171.22.25.85 +171.22.25.184 +171.22.26.61 +171.22.26.65 +171.22.26.71 +171.22.26.213 +171.22.27.22 +171.22.27.105 +171.22.27.186 +176.10.95.144 +176.10.95.176 +176.10.95.225 +176.10.95.253 +176.65.176.61 +176.65.177.21 +176.65.177.237 +176.65.178.227 +176.65.180.244 +176.65.183.179 +176.65.184.237 +176.65.187.129 +176.65.191.87 +176.65.191.237 +176.65.232.19 +176.65.233.3 +176.65.238.47 +176.65.240.82 +176.65.240.83 +176.65.240.84 +176.65.240.85 +176.65.240.86 +176.65.240.88 +176.65.240.90 +176.65.240.91 +176.65.240.92 +176.65.240.93 +176.65.240.94 +176.65.240.130 +176.65.241.110 +176.65.241.163 +176.65.241.166 +176.65.241.194 +176.65.241.236 +176.65.242.68 +176.65.242.100 +176.65.242.130 +176.65.242.132 +176.65.243.55 +176.65.243.237 +176.65.243.245 +176.65.252.2 +176.65.252.15 +176.65.252.26 +176.65.252.29 +176.65.252.53 +176.65.252.60 +176.65.252.71 +176.65.252.72 +176.65.252.96 +176.65.252.116 +176.65.252.150 +176.65.252.198 +176.65.252.214 +176.65.252.243 +176.65.253.21 +176.65.253.32 +176.65.253.44 +176.65.253.49 +176.65.253.69 +176.65.253.87 +176.65.253.132 +176.65.253.135 +176.65.253.149 +176.65.253.205 +176.65.253.240 +176.65.253.241 +176.65.253.242 +176.65.253.243 +176.65.253.244 +176.65.253.245 +176.65.253.246 +176.65.253.255 +176.65.254.50 +176.65.254.80 +176.65.254.85 +176.65.254.86 +176.65.254.120 +176.65.254.121 +176.65.254.130 +176.65.254.162 +176.65.254.202 +176.65.254.212 +176.65.254.239 +176.65.254.241 +176.97.218.20 +176.97.218.22 +176.97.218.39 +176.97.218.55 +176.97.218.184 +176.97.220.65 +176.97.220.149 +176.101.32.2 +176.101.32.62 +176.101.32.90 +176.101.32.98 +176.101.32.146 +176.101.32.200 +176.101.33.190 +176.101.33.214 +176.101.48.250 +176.102.224.50 +176.102.233.11 +176.102.233.253 +176.102.240.123 +176.102.240.200 +176.102.251.1 +176.102.253.1 +176.120.16.220 +176.120.16.242 +176.122.210.1 +176.122.210.5 +176.122.210.9 +176.122.210.13 +176.122.210.17 +176.122.210.21 +176.122.210.25 +176.122.210.29 +176.122.210.34 +176.122.210.37 +176.122.210.40 +176.122.210.41 +176.122.210.42 +176.122.210.43 +176.122.210.44 +176.122.210.45 +176.122.210.46 +176.122.210.57 +176.122.210.89 +176.122.210.97 +176.122.210.101 +176.122.210.190 +176.122.211.0 +176.122.211.48 +176.122.211.255 +176.221.66.2 +176.221.66.9 +178.21.161.14 +178.21.161.139 +178.21.163.54 +178.21.163.66 +178.21.163.83 +178.21.163.84 +178.21.163.85 +178.21.164.176 +178.21.164.177 +178.22.121.211 +178.22.121.213 +178.22.121.254 +178.22.122.102 +178.22.122.246 +178.22.122.250 +178.22.122.253 +178.22.123.6 +178.22.126.90 +178.131.9.23 +178.131.16.8 +178.131.17.162 +178.131.17.214 +178.131.18.13 +178.131.18.63 +178.131.19.140 +178.131.19.192 +178.131.20.57 +178.131.20.111 +178.131.20.156 +178.131.20.180 +178.131.23.33 +178.131.28.7 +178.131.30.3 +178.131.30.244 +178.131.40.9 +178.131.40.56 +178.131.40.83 +178.131.41.99 +178.131.41.212 +178.131.42.225 +178.131.42.254 +178.131.43.32 +178.131.43.78 +178.131.44.3 +178.131.44.142 +178.131.46.16 +178.131.47.218 +178.131.47.220 +178.131.48.19 +178.131.49.249 +178.131.50.56 +178.131.50.203 +178.131.51.39 +178.131.54.56 +178.131.55.72 +178.131.57.93 +178.131.59.45 +178.131.59.80 +178.131.61.47 +178.131.62.127 +178.131.63.36 +178.131.63.166 +178.131.63.182 +178.131.63.206 +178.131.64.50 +178.131.64.66 +178.131.65.18 +178.131.65.111 +178.131.65.232 +178.131.66.192 +178.131.67.252 +178.131.68.20 +178.131.68.123 +178.131.69.104 +178.131.69.213 +178.131.70.80 +178.131.71.200 +178.131.73.105 +178.131.73.211 +178.131.74.27 +178.131.74.97 +178.131.76.7 +178.131.77.156 +178.131.77.251 +178.131.78.185 +178.131.78.222 +178.131.78.227 +178.131.79.133 +178.131.79.169 +178.131.80.5 +178.131.80.23 +178.131.80.41 +178.131.82.47 +178.131.83.57 +178.131.84.118 +178.131.85.103 +178.131.86.77 +178.131.86.133 +178.131.86.141 +178.131.87.166 +178.131.87.170 +178.131.88.87 +178.131.88.156 +178.131.89.205 +178.131.89.249 +178.131.90.103 +178.131.93.109 +178.131.93.112 +178.131.93.188 +178.131.93.195 +178.131.93.205 +178.131.102.148 +178.131.105.110 +178.131.107.34 +178.131.108.6 +178.131.109.241 +178.131.110.99 +178.131.111.49 +178.131.114.21 +178.131.115.48 +178.131.116.190 +178.131.125.33 +178.131.126.12 +178.131.133.113 +178.131.140.116 +178.131.142.33 +178.131.142.190 +178.131.145.1 +178.131.157.34 +178.131.165.18 +178.131.172.252 +178.131.177.36 +178.131.180.73 +178.131.181.136 +178.131.181.227 +178.131.182.149 +178.131.182.155 +178.131.183.122 +178.131.183.130 +178.131.190.216 +178.131.190.237 +178.157.0.86 +178.157.0.113 +178.157.0.140 +178.157.0.144 +178.157.0.166 +178.173.128.88 +178.173.130.10 +178.173.130.36 +178.173.131.93 +178.173.131.128 +178.173.132.25 +178.173.132.37 +178.173.132.86 +178.173.132.104 +178.173.132.114 +178.173.132.117 +178.173.134.42 +178.173.134.57 +178.173.134.72 +178.173.134.183 +178.173.134.236 +178.173.134.239 +178.173.137.96 +178.173.137.140 +178.173.137.176 +178.173.137.192 +178.173.137.215 +178.173.137.231 +178.173.138.53 +178.173.138.169 +178.173.138.235 +178.173.138.252 +178.173.139.11 +178.173.139.49 +178.173.139.120 +178.173.139.149 +178.173.139.171 +178.173.139.173 +178.173.139.237 +178.173.140.148 +178.173.140.161 +178.173.140.250 +178.173.141.63 +178.173.141.83 +178.173.141.147 +178.173.141.158 +178.173.141.194 +178.173.141.244 +178.173.142.5 +178.173.142.37 +178.173.142.68 +178.173.142.134 +178.173.142.159 +178.173.142.173 +178.173.142.206 +178.173.142.210 +178.173.142.219 +178.173.143.29 +178.173.143.50 +178.173.143.112 +178.173.143.150 +178.173.143.209 +178.173.144.45 +178.173.144.59 +178.173.144.71 +178.173.144.149 +178.173.144.175 +178.173.144.240 +178.173.145.17 +178.173.145.83 +178.173.145.149 +178.173.145.217 +178.173.147.25 +178.173.147.43 +178.173.147.58 +178.173.147.132 +178.173.147.133 +178.173.147.149 +178.173.147.169 +178.173.147.238 +178.173.147.240 +178.173.147.242 +178.173.148.20 +178.173.148.128 +178.173.148.129 +178.173.148.130 +178.173.148.140 +178.173.148.142 +178.173.148.143 +178.173.148.145 +178.173.148.240 +178.173.149.21 +178.173.149.36 +178.173.149.210 +178.173.149.213 +178.173.149.247 +178.173.149.249 +178.173.151.94 +178.173.151.166 +178.173.151.211 +178.173.151.226 +178.173.160.70 +178.173.163.66 +178.173.163.67 +178.173.163.77 +178.173.163.85 +178.173.163.117 +178.173.163.166 +178.173.163.196 +178.173.163.216 +178.173.163.217 +178.173.163.233 +178.173.169.12 +178.173.169.100 +178.173.169.125 +178.173.169.150 +178.173.169.179 +178.173.169.203 +178.173.170.42 +178.173.170.68 +178.173.170.124 +178.173.170.222 +178.173.170.250 +178.173.171.58 +178.173.171.242 +178.173.171.243 +178.173.171.244 +178.173.171.245 +178.173.171.246 +178.173.171.247 +178.173.171.248 +178.173.171.250 +178.173.171.251 +178.173.172.10 +178.173.172.71 +178.173.172.74 +178.173.172.241 +178.173.172.243 +178.173.172.245 +178.173.172.247 +178.173.172.252 +178.173.174.145 +178.173.174.182 +178.173.174.240 +178.173.175.47 +178.173.175.84 +178.173.175.90 +178.173.175.91 +178.173.175.156 +178.173.175.199 +178.173.175.227 +178.173.182.27 +178.173.182.40 +178.173.182.48 +178.173.182.128 +178.173.182.186 +178.173.182.213 +178.173.182.249 +178.173.183.19 +178.173.183.81 +178.173.183.168 +178.173.183.233 +178.173.184.106 +178.173.184.232 +178.173.186.80 +178.173.186.121 +178.173.186.157 +178.173.186.186 +178.173.186.229 +178.173.186.253 +178.173.187.45 +178.173.187.47 +178.173.187.62 +178.173.187.92 +178.173.187.115 +178.173.187.122 +178.173.187.164 +178.173.187.179 +178.173.187.182 +178.173.187.223 +178.173.188.9 +178.173.188.128 +178.173.188.133 +178.173.188.191 +178.173.188.207 +178.173.188.240 +178.173.189.15 +178.173.189.35 +178.173.189.43 +178.173.189.78 +178.173.189.133 +178.173.189.141 +178.173.189.189 +178.173.189.207 +178.173.189.213 +178.173.189.247 +178.173.190.62 +178.173.190.222 +178.173.190.244 +178.173.191.3 +178.173.191.149 +178.173.192.32 +178.173.192.122 +178.173.192.139 +178.173.192.146 +178.173.192.169 +178.173.193.10 +178.173.193.45 +178.173.193.78 +178.173.193.84 +178.173.193.139 +178.173.193.144 +178.173.195.24 +178.173.195.52 +178.173.195.60 +178.173.195.70 +178.173.195.86 +178.173.195.123 +178.173.195.138 +178.173.195.163 +178.173.195.170 +178.173.195.188 +178.173.195.195 +178.173.195.243 +178.173.196.45 +178.173.196.164 +178.173.196.212 +178.173.197.82 +178.173.197.132 +178.173.198.3 +178.173.198.16 +178.173.198.25 +178.173.198.39 +178.173.198.237 +178.173.198.246 +178.173.199.48 +178.173.199.76 +178.173.199.162 +178.173.200.44 +178.173.200.72 +178.173.200.107 +178.173.200.212 +178.173.210.128 +178.173.210.212 +178.173.210.249 +178.173.211.22 +178.173.211.33 +178.173.211.80 +178.173.211.164 +178.173.211.177 +178.173.211.240 +178.173.212.39 +178.173.212.168 +178.173.212.169 +178.173.212.171 +178.173.212.203 +178.173.213.48 +178.173.213.51 +178.173.213.112 +178.173.213.217 +178.173.214.85 +178.173.214.152 +178.173.214.172 +178.173.214.250 +178.173.215.25 +178.173.215.57 +178.173.215.89 +178.173.215.92 +178.173.215.166 +178.173.215.168 +178.173.215.212 +178.173.215.236 +178.173.216.34 +178.173.216.40 +178.173.216.131 +178.173.216.159 +178.173.216.190 +178.173.217.29 +178.173.217.145 +178.173.217.253 +178.173.218.29 +178.173.218.35 +178.173.218.64 +178.173.218.107 +178.173.218.214 +178.173.218.220 +178.173.219.10 +178.173.219.128 +178.173.219.229 +178.173.219.242 +178.173.219.251 +178.173.220.55 +178.173.220.103 +178.173.220.146 +178.173.220.160 +178.173.220.161 +178.173.220.215 +178.173.221.2 +178.173.221.9 +178.173.221.46 +178.173.221.125 +178.173.221.195 +178.173.222.2 +178.173.222.3 +178.173.222.177 +178.173.222.200 +178.173.222.206 +178.216.248.235 +178.216.248.236 +178.216.248.237 +178.216.249.213 +178.216.250.190 +178.216.251.236 +178.216.251.237 +178.216.251.238 +178.236.33.129 +178.236.33.161 +178.236.100.59 +178.236.104.48 +178.236.104.118 +178.236.104.192 +178.236.104.240 +178.236.105.223 +178.236.106.91 +178.236.106.227 +178.236.107.71 +178.236.108.175 +178.236.109.34 +178.236.109.97 +178.238.192.60 +178.238.192.112 +178.238.192.166 +178.238.193.205 +178.238.194.22 +178.238.194.118 +178.238.194.139 +178.238.195.99 +178.238.195.240 +178.238.197.73 +178.238.199.3 +178.239.144.129 +178.239.147.133 +178.239.151.4 +178.239.151.49 +178.239.151.80 +178.239.151.94 +178.239.151.158 +178.239.151.229 +178.239.152.57 +178.239.152.145 +178.239.152.150 +178.239.152.160 +178.239.152.179 +178.239.152.184 +178.239.156.1 +178.239.156.5 +178.239.156.56 +178.239.156.61 +178.239.156.67 +178.239.156.126 +178.239.158.66 +178.239.158.129 +178.239.159.73 +178.239.159.160 +178.239.159.255 +178.252.129.30 +178.252.132.125 +178.252.132.217 +178.252.132.218 +178.252.132.222 +178.252.132.228 +178.252.133.85 +178.252.133.222 +178.252.134.178 +178.252.134.213 +178.252.134.229 +178.252.134.238 +178.252.135.2 +178.252.135.26 +178.252.135.53 +178.252.135.54 +178.252.135.117 +178.252.135.196 +178.252.135.202 +178.252.135.206 +178.252.136.2 +178.252.136.18 +178.252.136.130 +178.252.136.132 +178.252.136.133 +178.252.136.250 +178.252.140.98 +178.252.140.99 +178.252.140.104 +178.252.140.107 +178.252.141.50 +178.252.141.134 +178.252.141.226 +178.252.142.78 +178.252.143.22 +178.252.143.99 +178.252.143.102 +178.252.143.234 +178.252.144.18 +178.252.144.58 +178.252.144.102 +178.252.144.240 +178.252.147.35 +178.252.147.54 +178.252.147.65 +178.252.147.82 +178.252.147.83 +178.252.147.84 +178.252.147.85 +178.252.147.86 +178.252.147.122 +178.252.147.123 +178.252.147.124 +178.252.147.146 +178.252.147.148 +178.252.147.150 +178.252.147.154 +178.252.147.186 +178.252.147.187 +178.252.147.188 +178.252.147.189 +178.252.148.204 +178.252.149.202 +178.252.151.18 +178.252.151.22 +178.252.151.42 +178.252.151.43 +178.252.151.44 +178.252.151.46 +178.252.151.154 +178.252.151.162 +178.252.151.238 +178.252.151.250 +178.252.151.251 +178.252.152.56 +178.252.152.57 +178.252.152.58 +178.252.152.61 +178.252.152.62 +178.252.153.106 +178.252.153.122 +178.252.165.24 +178.252.165.82 +178.252.165.83 +178.252.165.84 +178.252.165.85 +178.252.165.102 +178.252.165.118 +178.252.165.193 +178.252.165.230 +178.252.166.10 +178.252.166.98 +178.252.166.153 +178.252.166.182 +178.252.167.51 +178.252.167.90 +178.252.170.106 +178.252.170.166 +178.252.170.222 +178.252.171.70 +178.252.171.74 +178.252.171.226 +178.252.171.228 +178.252.171.229 +178.252.171.234 +178.252.176.10 +178.252.176.68 +178.252.176.115 +178.252.176.130 +178.252.176.131 +178.252.176.146 +178.252.177.6 +178.252.177.8 +178.252.177.10 +178.252.177.12 +178.252.177.170 +178.252.178.205 +178.252.178.209 +178.252.178.210 +178.252.178.222 +178.252.183.28 +178.252.183.99 +178.252.183.250 +178.252.183.251 +178.252.183.252 +178.252.183.253 +178.252.183.254 +178.252.184.70 +178.252.184.138 +178.252.184.139 +178.252.184.141 +178.252.184.154 +178.252.189.82 +178.252.189.83 +178.252.189.84 +178.252.189.213 +178.252.189.214 +178.252.191.19 +178.252.191.163 +178.252.191.164 +178.252.191.169 +180.94.70.74 +180.94.91.170 +185.2.14.94 +185.2.14.253 +185.3.124.62 +185.3.124.83 +185.3.124.85 +185.3.124.86 +185.3.125.2 +185.3.125.3 +185.3.125.4 +185.3.125.87 +185.3.125.144 +185.3.201.197 +185.3.202.42 +185.3.202.57 +185.3.203.19 +185.3.203.77 +185.3.203.84 +185.3.203.85 +185.3.203.91 +185.3.212.76 +185.3.212.141 +185.3.213.61 +185.3.215.200 +185.3.215.226 +185.4.1.24 +185.4.1.25 +185.4.1.45 +185.4.1.96 +185.4.3.2 +185.4.3.73 +185.4.30.6 +185.4.30.46 +185.4.30.58 +185.4.30.72 +185.4.30.100 +185.7.172.1 +185.7.172.2 +185.7.172.7 +185.7.172.17 +185.7.172.33 +185.7.172.49 +185.7.172.65 +185.7.172.81 +185.7.172.97 +185.7.172.113 +185.7.172.129 +185.7.172.145 +185.7.212.35 +185.7.212.89 +185.7.212.133 +185.7.212.150 +185.8.172.49 +185.8.172.207 +185.8.173.27 +185.8.173.91 +185.8.174.13 +185.8.174.184 +185.8.174.207 +185.8.175.44 +185.8.175.153 +185.8.175.187 +185.8.175.229 +185.8.175.241 +185.8.175.249 +185.10.71.235 +185.10.72.124 +185.10.74.112 +185.10.74.234 +185.10.75.77 +185.10.75.108 +185.10.75.113 +185.10.75.119 +185.10.75.121 +185.10.75.196 +185.11.69.55 +185.11.69.56 +185.11.69.148 +185.11.69.150 +185.11.69.174 +185.11.70.18 +185.11.70.55 +185.11.70.211 +185.11.70.217 +185.11.70.230 +185.11.70.233 +185.11.71.169 +185.13.229.253 +185.13.229.254 +185.13.230.46 +185.13.230.116 +185.13.230.155 +185.13.230.158 +185.13.231.49 +185.14.81.252 +185.14.161.36 +185.14.161.67 +185.14.161.82 +185.14.161.84 +185.14.161.85 +185.14.162.5 +185.14.162.18 +185.14.163.162 +185.14.163.163 +185.14.163.164 +185.14.163.165 +185.14.163.166 +185.14.163.167 +185.14.163.168 +185.14.163.169 +185.14.163.170 +185.14.163.171 +185.14.163.172 +185.14.163.174 +185.14.163.210 +185.14.163.234 +185.14.163.235 +185.18.212.67 +185.18.213.26 +185.18.213.47 +185.18.213.67 +185.18.213.76 +185.18.213.98 +185.18.213.180 +185.18.214.4 +185.18.214.120 +185.18.214.138 +185.18.214.244 +185.18.215.26 +185.18.215.36 +185.18.215.38 +185.18.215.90 +185.18.215.208 +185.18.215.240 +185.19.201.102 +185.19.201.188 +185.19.201.193 +185.19.201.199 +185.21.68.53 +185.21.70.41 +185.21.70.233 +185.21.71.46 +185.21.71.199 +185.21.71.216 +185.21.71.246 +185.22.30.174 +185.22.31.226 +185.22.31.227 +185.23.128.147 +185.23.128.148 +185.24.252.43 +185.24.252.65 +185.24.252.76 +185.24.252.111 +185.24.252.115 +185.24.252.129 +185.24.252.242 +185.24.252.250 +185.24.253.1 +185.24.253.8 +185.24.253.48 +185.24.253.65 +185.24.253.96 +185.24.253.120 +185.24.253.129 +185.24.255.109 +185.24.255.115 +185.24.255.161 +185.24.255.169 +185.24.255.187 +185.26.33.213 +185.26.34.227 +185.26.35.47 +185.26.35.242 +185.26.235.53 +185.29.223.233 +185.33.25.86 +185.36.228.170 +185.36.231.37 +185.36.231.53 +185.36.231.140 +185.37.53.144 +185.37.53.146 +185.37.53.147 +185.37.53.148 +185.37.53.149 +185.37.53.150 +185.37.54.10 +185.37.54.186 +185.39.181.6 +185.39.181.17 +185.39.181.21 +185.39.181.23 +185.39.181.92 +185.39.181.110 +185.39.181.142 +185.39.181.179 +185.39.181.229 +185.39.181.243 +185.39.182.34 +185.39.183.157 +185.41.3.5 +185.42.224.170 +185.42.225.68 +185.42.225.74 +185.42.225.90 +185.42.225.196 +185.42.225.199 +185.42.226.26 +185.42.226.27 +185.42.226.28 +185.42.226.30 +185.42.227.93 +185.42.227.94 +185.42.227.238 +185.44.36.3 +185.44.36.30 +185.44.36.50 +185.44.36.193 +185.44.112.13 +185.44.112.170 +185.44.112.206 +185.44.114.153 +185.46.109.0 +185.46.109.2 +185.46.109.10 +185.46.109.123 +185.46.109.126 +185.46.109.127 +185.46.109.130 +185.46.109.131 +185.46.109.146 +185.46.109.149 +185.46.109.151 +185.46.109.161 +185.46.109.167 +185.46.109.172 +185.46.109.175 +185.46.109.179 +185.46.110.92 +185.46.110.122 +185.46.111.43 +185.46.111.45 +185.46.111.74 +185.46.111.106 +185.46.111.219 +185.46.111.228 +185.46.111.231 +185.46.217.105 +185.46.218.77 +185.46.218.127 +185.46.218.198 +185.46.219.18 +185.46.219.87 +185.46.219.126 +185.46.219.220 +185.47.48.6 +185.47.48.10 +185.47.48.13 +185.47.48.14 +185.47.48.131 +185.47.48.178 +185.47.48.201 +185.47.48.225 +185.47.48.253 +185.47.48.254 +185.47.49.46 +185.47.49.53 +185.47.49.89 +185.47.49.127 +185.47.49.138 +185.47.49.168 +185.47.49.172 +185.47.49.226 +185.47.49.229 +185.47.49.245 +185.47.49.252 +185.47.50.62 +185.47.50.95 +185.47.50.238 +185.47.51.2 +185.47.51.201 +185.49.84.122 +185.49.84.123 +185.49.84.124 +185.49.84.125 +185.49.84.126 +185.49.85.202 +185.49.86.35 +185.49.86.228 +185.49.86.229 +185.49.86.230 +185.49.86.241 +185.49.87.156 +185.49.96.168 +185.49.96.207 +185.49.96.209 +185.49.97.49 +185.49.97.69 +185.49.97.83 +185.49.97.108 +185.49.97.170 +185.49.97.186 +185.49.97.187 +185.49.97.228 +185.50.38.1 +185.50.38.2 +185.50.38.3 +185.51.40.10 +185.51.42.255 +185.51.43.96 +185.51.43.130 +185.51.201.68 +185.51.201.69 +185.51.201.70 +185.53.140.9 +185.53.140.14 +185.53.140.18 +185.53.140.20 +185.53.140.25 +185.53.140.37 +185.53.140.38 +185.53.140.69 +185.53.140.75 +185.53.140.85 +185.53.140.106 +185.53.140.107 +185.53.140.119 +185.53.140.122 +185.53.140.147 +185.53.140.150 +185.53.140.151 +185.53.140.155 +185.53.140.157 +185.53.140.181 +185.53.140.184 +185.53.140.186 +185.53.140.187 +185.53.140.189 +185.53.140.194 +185.53.140.195 +185.53.140.196 +185.53.140.198 +185.53.140.206 +185.53.140.238 +185.53.140.239 +185.53.140.240 +185.53.140.242 +185.53.140.243 +185.53.140.245 +185.53.140.246 +185.53.140.248 +185.53.140.249 +185.53.140.250 +185.53.140.251 +185.53.140.252 +185.53.141.101 +185.53.142.12 +185.53.142.44 +185.53.142.101 +185.53.142.248 +185.53.143.13 +185.53.143.50 +185.53.143.51 +185.53.143.52 +185.53.143.53 +185.53.143.55 +185.53.143.56 +185.53.143.57 +185.53.143.58 +185.53.143.59 +185.53.143.60 +185.53.143.78 +185.55.224.58 +185.55.224.79 +185.55.224.89 +185.55.224.92 +185.55.224.95 +185.55.224.103 +185.55.224.115 +185.55.224.131 +185.55.224.193 +185.55.225.101 +185.55.225.158 +185.55.225.176 +185.55.225.189 +185.55.226.19 +185.55.226.25 +185.55.226.40 +185.55.227.25 +185.55.227.38 +185.55.227.43 +185.55.227.52 +185.55.227.90 +185.55.227.101 +185.55.227.121 +185.55.227.143 +185.55.227.147 +185.55.227.188 +185.55.227.200 +185.56.93.29 +185.56.93.30 +185.56.93.70 +185.56.93.122 +185.56.94.1 +185.56.94.16 +185.56.94.17 +185.56.95.186 +185.57.132.27 +185.57.133.62 +185.57.133.109 +185.57.133.140 +185.57.133.235 +185.57.133.245 +185.57.135.9 +185.57.167.225 +185.58.240.145 +185.58.240.165 +185.58.241.128 +185.58.242.64 +185.58.242.113 +185.58.242.220 +185.58.243.169 +185.60.59.2 +185.60.59.3 +185.60.59.4 +185.60.59.73 +185.60.136.28 +185.60.136.65 +185.60.136.74 +185.60.136.193 +185.63.113.12 +185.63.113.164 +185.63.113.165 +185.63.113.206 +185.63.113.216 +185.63.237.10 +185.64.176.19 +185.64.176.103 +185.66.224.14 +185.66.224.110 +185.66.224.148 +185.66.226.43 +185.66.226.130 +185.66.226.235 +185.66.227.59 +185.66.227.102 +185.66.227.113 +185.66.227.115 +185.66.227.118 +185.66.227.123 +185.66.227.124 +185.66.227.224 +185.66.227.227 +185.66.228.1 +185.66.228.2 +185.66.230.18 +185.66.230.155 +185.66.230.255 +185.71.194.140 +185.71.194.141 +185.71.194.153 +185.71.194.163 +185.72.25.85 +185.72.25.86 +185.72.25.94 +185.72.25.95 +185.72.25.121 +185.72.25.122 +185.72.25.124 +185.72.25.130 +185.72.25.146 +185.72.26.62 +185.72.26.114 +185.72.26.146 +185.72.26.147 +185.72.26.148 +185.72.26.149 +185.72.26.150 +185.72.26.218 +185.72.27.50 +185.72.27.97 +185.72.27.162 +185.72.27.232 +185.73.112.114 +185.73.112.123 +185.73.112.253 +185.73.114.228 +185.73.226.235 +185.74.221.11 +185.74.221.12 +185.74.221.14 +185.75.207.100 +185.75.207.210 +185.75.207.223 +185.78.21.48 +185.79.96.35 +185.79.96.36 +185.79.96.37 +185.79.96.92 +185.79.96.93 +185.79.96.94 +185.79.96.123 +185.79.96.124 +185.79.97.2 +185.79.97.5 +185.79.97.19 +185.79.97.131 +185.79.97.132 +185.79.97.133 +185.79.97.195 +185.79.97.196 +185.79.97.197 +185.79.98.131 +185.79.98.132 +185.79.98.133 +185.79.156.39 +185.79.156.55 +185.79.156.77 +185.79.156.82 +185.79.156.109 +185.79.156.170 +185.79.156.191 +185.79.156.208 +185.79.156.209 +185.79.156.210 +185.79.156.211 +185.79.156.212 +185.79.156.213 +185.79.156.230 +185.79.156.238 +185.79.156.239 +185.79.156.251 +185.79.156.253 +185.79.157.130 +185.79.157.131 +185.79.157.133 +185.79.157.162 +185.79.157.190 +185.79.158.12 +185.79.158.14 +185.79.158.19 +185.79.158.133 +185.79.159.130 +185.81.99.52 +185.82.65.172 +185.82.65.205 +185.82.67.2 +185.82.67.35 +185.82.67.36 +185.82.67.37 +185.82.67.144 +185.82.67.146 +185.82.67.147 +185.82.67.169 +185.82.67.175 +185.82.67.178 +185.82.136.149 +185.82.136.163 +185.82.136.188 +185.82.136.207 +185.82.136.215 +185.82.136.250 +185.82.136.254 +185.82.137.14 +185.82.137.18 +185.82.137.24 +185.82.137.33 +185.82.137.47 +185.82.137.55 +185.82.137.84 +185.82.137.97 +185.82.137.100 +185.82.139.9 +185.82.139.114 +185.82.139.124 +185.82.139.140 +185.82.139.168 +185.82.139.206 +185.82.139.227 +185.82.139.237 +185.82.139.238 +185.82.165.10 +185.82.165.66 +185.82.165.110 +185.83.88.9 +185.83.88.11 +185.83.88.13 +185.83.88.15 +185.83.88.18 +185.83.88.22 +185.83.88.44 +185.83.88.66 +185.83.88.89 +185.83.88.112 +185.83.88.113 +185.83.88.118 +185.83.88.156 +185.83.88.162 +185.83.88.190 +185.83.88.193 +185.83.88.241 +185.83.89.114 +185.83.89.194 +185.83.91.236 +185.83.112.19 +185.83.113.29 +185.83.114.220 +185.83.115.121 +185.83.115.122 +185.83.115.193 +185.83.115.204 +185.83.180.41 +185.83.180.145 +185.83.180.151 +185.83.182.20 +185.83.182.90 +185.83.182.197 +185.83.182.218 +185.83.183.96 +185.83.183.126 +185.83.183.137 +185.83.183.162 +185.83.183.251 +185.83.196.13 +185.83.196.14 +185.83.196.17 +185.83.196.30 +185.83.196.90 +185.83.196.102 +185.83.196.134 +185.83.196.150 +185.83.196.249 +185.83.197.42 +185.83.197.43 +185.83.197.45 +185.83.198.154 +185.83.198.156 +185.83.198.162 +185.83.199.83 +185.83.199.96 +185.83.199.99 +185.83.199.100 +185.83.199.101 +185.83.199.102 +185.83.199.123 +185.83.199.178 +185.83.199.234 +185.83.208.134 +185.83.208.166 +185.83.208.181 +185.83.208.185 +185.84.158.17 +185.84.158.36 +185.84.158.55 +185.85.136.1 +185.85.136.3 +185.85.136.5 +185.85.136.12 +185.85.136.18 +185.85.136.21 +185.85.136.22 +185.85.136.24 +185.85.136.26 +185.85.136.39 +185.85.136.253 +185.85.138.130 +185.85.138.200 +185.85.138.201 +185.85.138.202 +185.85.138.203 +185.85.139.201 +185.85.139.203 +185.85.139.205 +185.85.139.206 +185.85.139.207 +185.85.139.208 +185.85.139.209 +185.85.139.210 +185.85.139.211 +185.85.139.212 +185.85.139.213 +185.85.139.214 +185.85.139.215 +185.85.139.217 +185.85.139.218 +185.85.139.219 +185.85.139.222 +185.85.139.223 +185.85.139.240 +185.85.139.242 +185.85.139.243 +185.85.139.245 +185.85.139.246 +185.85.139.247 +185.85.139.248 +185.85.139.249 +185.85.139.250 +185.85.139.251 +185.85.139.252 +185.85.139.253 +185.85.139.254 +185.86.36.113 +185.86.181.52 +185.86.181.55 +185.86.181.84 +185.86.181.167 +185.86.183.160 +185.88.50.38 +185.88.50.53 +185.88.50.88 +185.88.50.177 +185.88.50.186 +185.88.51.17 +185.88.51.41 +185.88.51.96 +185.88.51.97 +185.88.51.98 +185.88.51.99 +185.88.51.100 +185.88.51.101 +185.88.51.102 +185.88.51.103 +185.88.152.54 +185.88.152.90 +185.88.152.198 +185.88.152.251 +185.88.153.3 +185.88.153.12 +185.88.153.48 +185.88.153.136 +185.88.153.160 +185.88.153.202 +185.88.153.248 +185.88.154.8 +185.88.154.68 +185.88.154.244 +185.88.176.10 +185.88.176.44 +185.88.177.23 +185.88.177.39 +185.88.177.65 +185.88.177.93 +185.88.177.108 +185.88.177.137 +185.88.177.189 +185.88.177.208 +185.88.177.213 +185.88.178.8 +185.88.178.10 +185.88.178.12 +185.88.178.14 +185.88.178.19 +185.88.178.25 +185.88.178.34 +185.88.178.37 +185.88.178.54 +185.88.178.70 +185.88.178.74 +185.88.178.78 +185.88.178.120 +185.88.178.121 +185.88.178.184 +185.88.179.232 +185.88.252.71 +185.88.252.79 +185.88.252.87 +185.88.252.107 +185.88.252.117 +185.88.252.146 +185.88.253.11 +185.89.112.58 +185.89.112.68 +185.89.112.162 +185.89.112.212 +185.89.113.106 +185.89.113.126 +185.89.113.200 +185.89.115.185 +185.92.40.1 +185.92.40.40 +185.92.42.89 +185.94.96.1 +185.94.96.5 +185.94.96.9 +185.94.96.17 +185.94.96.21 +185.94.96.25 +185.94.96.37 +185.94.96.41 +185.94.96.45 +185.94.96.49 +185.94.96.65 +185.94.96.97 +185.94.96.105 +185.94.96.117 +185.94.96.121 +185.94.96.129 +185.94.96.133 +185.94.96.137 +185.94.96.141 +185.94.96.145 +185.94.96.157 +185.94.96.161 +185.94.96.165 +185.94.96.169 +185.94.96.173 +185.94.96.177 +185.94.96.181 +185.94.96.185 +185.94.96.189 +185.94.96.193 +185.94.96.225 +185.94.96.241 +185.94.96.245 +185.94.96.249 +185.94.97.1 +185.94.97.17 +185.94.97.21 +185.94.97.22 +185.94.97.25 +185.94.97.29 +185.94.97.33 +185.94.97.38 +185.94.97.41 +185.94.97.45 +185.94.97.49 +185.94.97.53 +185.94.97.57 +185.94.97.61 +185.94.97.65 +185.94.97.69 +185.94.97.73 +185.94.97.77 +185.94.97.81 +185.94.97.97 +185.94.97.105 +185.94.97.109 +185.94.97.117 +185.94.97.145 +185.94.97.149 +185.94.97.157 +185.94.97.197 +185.94.97.201 +185.94.97.205 +185.94.97.209 +185.94.97.213 +185.94.97.217 +185.94.97.225 +185.94.97.232 +185.94.97.241 +185.94.97.245 +185.94.97.249 +185.94.97.253 +185.94.98.1 +185.94.98.37 +185.94.98.41 +185.94.98.49 +185.94.98.53 +185.94.98.57 +185.94.98.61 +185.94.98.73 +185.94.98.81 +185.94.98.89 +185.94.98.97 +185.94.98.101 +185.94.98.105 +185.94.98.109 +185.94.98.113 +185.94.98.129 +185.94.98.145 +185.94.98.165 +185.94.98.193 +185.94.98.225 +185.94.98.233 +185.94.98.241 +185.94.98.249 +185.94.99.1 +185.94.99.5 +185.94.99.33 +185.94.99.65 +185.94.99.129 +185.94.99.137 +185.94.99.141 +185.94.99.145 +185.94.99.153 +185.94.99.157 +185.94.99.161 +185.94.99.165 +185.94.99.169 +185.94.99.177 +185.94.99.185 +185.94.99.193 +185.94.99.208 +185.94.99.212 +185.94.99.225 +185.95.152.33 +185.95.152.105 +185.95.152.130 +185.95.152.132 +185.95.152.148 +185.95.182.1 +185.97.117.1 +185.97.117.112 +185.97.118.189 +185.97.118.202 +185.97.119.194 +185.97.119.241 +185.99.212.43 +185.99.212.92 +185.99.213.56 +185.100.44.28 +185.100.44.106 +185.100.44.183 +185.100.44.222 +185.100.45.22 +185.100.45.102 +185.100.46.56 +185.100.46.167 +185.100.46.168 +185.100.47.161 +185.100.47.166 +185.100.47.249 +185.101.231.186 +185.101.231.188 +185.101.231.190 +185.101.231.242 +185.103.84.1 +185.103.84.16 +185.103.87.30 +185.103.129.69 +185.103.129.70 +185.103.129.110 +185.103.130.34 +185.103.130.146 +185.103.130.162 +185.103.130.170 +185.103.130.238 +185.103.131.10 +185.103.131.131 +185.103.248.13 +185.103.248.150 +185.103.250.253 +185.104.229.202 +185.104.229.203 +185.104.229.204 +185.104.229.205 +185.104.229.206 +185.104.243.80 +185.105.101.1 +185.105.184.125 +185.105.187.133 +185.105.187.148 +185.105.187.172 +185.105.237.98 +185.105.238.157 +185.106.136.153 +185.106.136.154 +185.106.137.93 +185.106.138.209 +185.106.144.29 +185.106.145.98 +185.106.146.221 +185.106.201.7 +185.107.31.90 +185.107.31.91 +185.107.32.62 +185.107.244.195 +185.107.245.100 +185.107.245.177 +185.107.245.240 +185.107.246.26 +185.107.246.129 +185.107.246.131 +185.107.246.140 +185.107.246.141 +185.107.246.142 +185.107.246.146 +185.107.246.154 +185.107.247.215 +185.109.60.43 +185.109.60.45 +185.109.61.7 +185.109.61.41 +185.109.61.228 +185.109.62.69 +185.109.62.105 +185.109.62.108 +185.109.63.106 +185.109.63.107 +185.109.63.108 +185.109.63.109 +185.109.63.158 +185.109.63.192 +185.109.74.1 +185.109.74.87 +185.109.74.119 +185.109.74.124 +185.109.74.175 +185.109.74.195 +185.109.74.199 +185.109.74.241 +185.109.75.41 +185.109.81.91 +185.109.82.73 +185.109.82.77 +185.109.83.55 +185.109.244.188 +185.109.245.114 +185.109.246.82 +185.109.246.114 +185.109.247.18 +185.109.247.19 +185.109.247.20 +185.109.247.21 +185.109.247.85 +185.109.247.99 +185.109.247.226 +185.109.248.126 +185.110.28.1 +185.110.28.8 +185.110.28.9 +185.110.28.20 +185.110.28.30 +185.110.28.54 +185.110.28.79 +185.110.28.97 +185.110.28.98 +185.110.28.99 +185.110.28.100 +185.110.28.101 +185.110.28.102 +185.110.28.103 +185.110.28.105 +185.110.28.193 +185.110.28.194 +185.110.28.245 +185.110.28.249 +185.110.28.253 +185.110.29.38 +185.110.29.113 +185.110.29.153 +185.110.29.193 +185.110.29.249 +185.110.30.14 +185.110.30.62 +185.110.30.70 +185.110.30.79 +185.110.30.145 +185.110.30.177 +185.110.30.241 +185.110.31.10 +185.110.31.17 +185.110.31.140 +185.110.31.170 +185.110.219.25 +185.110.219.57 +185.110.219.131 +185.110.236.0 +185.110.236.33 +185.110.236.36 +185.110.236.66 +185.110.237.0 +185.110.237.58 +185.110.237.197 +185.110.238.0 +185.110.239.1 +185.110.244.55 +185.110.247.23 +185.110.252.41 +185.110.252.49 +185.110.252.97 +185.110.252.145 +185.110.252.159 +185.110.252.167 +185.110.252.229 +185.110.253.208 +185.111.65.28 +185.111.81.117 +185.111.81.118 +185.111.82.139 +185.111.83.6 +185.111.83.73 +185.111.83.144 +185.111.83.163 +185.111.83.221 +185.112.32.26 +185.112.32.42 +185.112.32.186 +185.112.33.80 +185.112.33.88 +185.112.33.89 +185.112.33.108 +185.112.33.109 +185.112.33.178 +185.112.33.179 +185.112.33.253 +185.112.34.115 +185.112.35.84 +185.112.35.202 +185.112.35.205 +185.112.35.206 +185.112.36.8 +185.112.36.17 +185.112.36.51 +185.112.36.70 +185.112.36.94 +185.112.36.106 +185.112.36.134 +185.112.36.188 +185.112.36.197 +185.112.36.200 +185.112.36.201 +185.112.36.205 +185.112.36.215 +185.112.36.223 +185.112.36.253 +185.112.37.5 +185.112.37.43 +185.112.37.56 +185.112.37.57 +185.112.37.65 +185.112.37.83 +185.112.37.94 +185.112.37.110 +185.112.37.112 +185.112.37.119 +185.112.37.128 +185.112.37.155 +185.112.37.181 +185.112.37.186 +185.112.37.214 +185.112.37.218 +185.112.37.238 +185.112.38.16 +185.112.38.18 +185.112.38.44 +185.112.38.46 +185.112.38.55 +185.112.38.64 +185.112.38.71 +185.112.38.91 +185.112.38.96 +185.112.38.104 +185.112.38.112 +185.112.38.118 +185.112.38.121 +185.112.38.131 +185.112.38.137 +185.112.38.225 +185.112.38.227 +185.112.38.230 +185.112.39.7 +185.112.39.21 +185.112.39.36 +185.112.39.45 +185.112.39.60 +185.112.39.88 +185.112.39.123 +185.112.39.129 +185.112.39.151 +185.112.39.160 +185.112.150.24 +185.112.150.26 +185.112.151.4 +185.112.151.178 +185.112.151.207 +185.112.151.223 +185.112.151.246 +185.113.9.56 +185.113.9.124 +185.113.9.211 +185.113.9.246 +185.113.56.1 +185.113.56.11 +185.113.56.19 +185.113.56.30 +185.113.56.35 +185.113.56.125 +185.113.56.130 +185.113.57.17 +185.113.57.208 +185.113.58.71 +185.113.58.73 +185.113.58.92 +185.113.58.221 +185.113.58.229 +185.113.58.231 +185.113.58.235 +185.113.59.161 +185.115.149.29 +185.115.149.228 +185.115.168.1 +185.115.168.3 +185.115.168.4 +185.115.168.11 +185.115.168.24 +185.115.168.55 +185.115.168.109 +185.115.168.157 +185.115.168.181 +185.115.168.195 +185.115.169.29 +185.115.169.46 +185.115.169.90 +185.115.169.162 +185.115.169.179 +185.115.169.186 +185.115.169.223 +185.115.171.5 +185.115.171.29 +185.115.171.31 +185.115.171.42 +185.115.171.45 +185.115.171.46 +185.115.171.48 +185.115.171.81 +185.115.171.91 +185.115.171.110 +185.115.171.171 +185.115.171.174 +185.115.171.184 +185.115.171.187 +185.115.171.194 +185.115.171.219 +185.115.171.252 +185.116.20.97 +185.116.20.169 +185.116.20.173 +185.116.20.190 +185.116.21.75 +185.116.21.76 +185.116.21.77 +185.116.22.5 +185.116.22.8 +185.116.22.49 +185.116.22.50 +185.116.22.66 +185.116.22.81 +185.116.22.177 +185.116.23.2 +185.116.23.13 +185.116.23.17 +185.116.23.57 +185.116.23.129 +185.116.23.193 +185.116.160.30 +185.116.160.73 +185.116.160.125 +185.116.160.131 +185.116.161.31 +185.116.161.108 +185.116.161.114 +185.116.162.37 +185.116.162.88 +185.116.163.32 +185.116.163.71 +185.116.163.76 +185.116.163.95 +185.116.163.99 +185.116.163.105 +185.116.163.142 +185.116.163.214 +185.117.48.248 +185.117.139.83 +185.117.139.84 +185.117.139.89 +185.117.139.91 +185.117.139.132 +185.117.139.162 +185.117.139.163 +185.117.139.164 +185.117.139.166 +185.117.139.167 +185.117.139.170 +185.117.139.171 +185.117.139.172 +185.117.139.174 +185.118.13.101 +185.118.13.140 +185.118.13.185 +185.118.13.229 +185.118.14.22 +185.118.14.83 +185.118.14.99 +185.118.14.113 +185.118.14.122 +185.118.14.130 +185.118.14.142 +185.118.14.149 +185.118.14.165 +185.118.14.176 +185.118.14.191 +185.118.14.199 +185.118.14.201 +185.118.14.203 +185.118.152.13 +185.118.152.17 +185.118.152.21 +185.118.152.22 +185.118.152.30 +185.118.152.93 +185.118.152.101 +185.118.152.169 +185.118.152.182 +185.118.152.185 +185.118.152.193 +185.118.152.214 +185.118.152.248 +185.118.152.249 +185.118.153.19 +185.118.153.98 +185.118.155.25 +185.118.155.33 +185.118.155.84 +185.118.155.95 +185.118.155.114 +185.118.155.122 +185.118.155.212 +185.118.155.233 +185.118.155.237 +185.119.164.61 +185.119.164.65 +185.119.164.66 +185.119.164.67 +185.119.164.69 +185.119.164.70 +185.119.164.77 +185.119.164.166 +185.119.166.8 +185.119.166.165 +185.120.162.49 +185.120.162.60 +185.120.162.61 +185.120.162.62 +185.120.162.97 +185.120.169.150 +185.120.201.34 +185.120.201.35 +185.120.201.42 +185.120.201.56 +185.120.220.27 +185.120.220.28 +185.120.220.155 +185.120.220.193 +185.120.220.194 +185.120.221.84 +185.120.221.180 +185.120.221.249 +185.120.222.138 +185.120.222.166 +185.120.222.184 +185.120.222.198 +185.120.222.211 +185.120.222.217 +185.120.222.218 +185.120.222.232 +185.120.222.242 +185.120.249.175 +185.120.250.225 +185.121.128.102 +185.121.128.115 +185.121.128.119 +185.121.128.130 +185.121.128.162 +185.121.128.163 +185.121.128.164 +185.121.129.66 +185.121.129.67 +185.121.129.69 +185.121.129.75 +185.121.129.85 +185.121.129.86 +185.121.129.89 +185.121.129.134 +185.121.129.158 +185.121.129.170 +185.121.130.106 +185.121.130.154 +185.121.130.210 +185.121.130.230 +185.121.131.6 +185.121.131.105 +185.121.131.107 +185.121.131.109 +185.121.131.110 +185.121.131.186 +185.124.112.51 +185.124.112.59 +185.124.112.153 +185.124.113.224 +185.124.114.37 +185.124.114.40 +185.124.114.96 +185.124.114.131 +185.124.114.214 +185.124.114.220 +185.124.115.104 +185.124.115.137 +185.124.173.90 +185.124.173.100 +185.125.20.4 +185.125.20.5 +185.125.20.32 +185.125.20.102 +185.125.20.119 +185.125.20.204 +185.125.20.205 +185.125.20.229 +185.125.20.254 +185.125.23.106 +185.125.23.107 +185.125.23.108 +185.125.23.109 +185.125.23.113 +185.125.23.203 +185.125.23.218 +185.125.23.228 +185.125.23.229 +185.125.248.129 +185.125.248.154 +185.125.248.217 +185.125.248.229 +185.125.249.42 +185.125.251.90 +185.125.252.77 +185.125.252.102 +185.125.252.190 +185.125.252.226 +185.125.255.82 +185.125.255.83 +185.125.255.84 +185.125.255.85 +185.125.255.86 +185.125.255.88 +185.125.255.89 +185.125.255.90 +185.125.255.91 +185.125.255.92 +185.125.255.93 +185.125.255.113 +185.125.255.114 +185.125.255.119 +185.125.255.120 +185.125.255.241 +185.126.0.84 +185.126.1.4 +185.126.1.166 +185.126.2.44 +185.126.3.91 +185.126.3.92 +185.126.3.132 +185.126.3.133 +185.126.3.134 +185.126.3.135 +185.126.3.136 +185.126.3.137 +185.126.3.138 +185.126.3.140 +185.126.3.141 +185.126.3.142 +185.126.3.143 +185.126.3.145 +185.126.3.146 +185.126.3.147 +185.126.3.149 +185.126.3.151 +185.126.3.152 +185.126.3.153 +185.126.3.154 +185.126.3.155 +185.126.3.158 +185.126.4.62 +185.126.4.170 +185.126.5.14 +185.126.5.40 +185.126.5.41 +185.126.5.49 +185.126.5.221 +185.126.6.174 +185.126.6.241 +185.126.8.18 +185.126.8.19 +185.126.8.21 +185.126.8.22 +185.126.9.147 +185.126.9.148 +185.126.9.150 +185.126.9.186 +185.126.10.180 +185.126.10.205 +185.126.11.97 +185.126.14.109 +185.126.14.110 +185.126.14.114 +185.126.14.115 +185.126.14.116 +185.126.14.117 +185.126.14.118 +185.126.14.119 +185.126.14.120 +185.126.14.121 +185.126.14.122 +185.126.14.123 +185.126.14.124 +185.126.14.126 +185.126.14.196 +185.126.14.202 +185.126.14.204 +185.126.14.205 +185.126.14.212 +185.126.14.244 +185.126.14.245 +185.126.15.13 +185.126.15.202 +185.126.16.9 +185.126.18.9 +185.126.40.95 +185.126.40.106 +185.126.40.109 +185.126.40.125 +185.126.40.175 +185.126.40.178 +185.126.40.226 +185.126.201.3 +185.126.201.4 +185.126.201.5 +185.126.201.6 +185.126.201.7 +185.126.201.8 +185.126.201.9 +185.126.201.19 +185.126.201.22 +185.126.201.25 +185.126.201.26 +185.126.201.27 +185.126.201.235 +185.126.202.12 +185.126.202.13 +185.126.202.202 +185.126.203.26 +185.126.203.44 +185.128.40.16 +185.128.80.32 +185.128.80.68 +185.128.81.50 +185.128.81.225 +185.128.136.12 +185.128.137.200 +185.128.137.202 +185.128.138.2 +185.128.138.100 +185.128.138.102 +185.128.138.103 +185.128.138.175 +185.128.139.73 +185.128.139.133 +185.128.152.13 +185.128.152.59 +185.128.152.137 +185.128.152.201 +185.128.152.246 +185.129.116.74 +185.129.116.75 +185.129.116.76 +185.129.116.77 +185.129.117.67 +185.129.117.68 +185.129.117.69 +185.129.117.105 +185.129.117.195 +185.129.117.196 +185.129.117.197 +185.129.118.2 +185.129.118.3 +185.129.118.5 +185.129.118.6 +185.129.118.53 +185.129.118.131 +185.129.118.132 +185.129.118.237 +185.129.119.3 +185.129.119.4 +185.129.119.5 +185.129.119.131 +185.129.119.132 +185.129.119.133 +185.129.168.41 +185.129.168.166 +185.129.168.193 +185.129.169.131 +185.129.169.132 +185.129.169.137 +185.129.170.87 +185.129.197.155 +185.129.197.173 +185.129.197.215 +185.129.197.226 +185.129.197.227 +185.129.197.233 +185.129.197.235 +185.129.199.74 +185.129.199.186 +185.129.200.201 +185.129.202.131 +185.129.212.178 +185.129.213.145 +185.129.213.149 +185.129.213.170 +185.129.213.189 +185.129.213.203 +185.129.213.210 +185.129.213.221 +185.129.214.25 +185.129.214.47 +185.129.214.200 +185.129.215.29 +185.129.215.45 +185.129.215.165 +185.129.216.19 +185.129.216.30 +185.129.216.152 +185.129.217.10 +185.129.217.38 +185.129.217.59 +185.129.217.116 +185.129.217.129 +185.129.217.203 +185.129.218.0 +185.129.230.18 +185.129.230.213 +185.129.236.49 +185.129.236.194 +185.129.236.195 +185.129.237.45 +185.129.238.52 +185.129.238.178 +185.129.238.255 +185.129.239.37 +185.129.239.43 +185.129.239.48 +185.129.239.93 +185.129.239.123 +185.129.239.171 +185.129.239.232 +185.129.239.235 +185.130.50.92 +185.130.50.113 +185.130.50.130 +185.130.50.141 +185.130.50.142 +185.130.50.154 +185.130.50.248 +185.130.79.230 +185.131.30.14 +185.131.30.18 +185.131.30.21 +185.131.30.49 +185.131.30.50 +185.131.30.58 +185.131.30.83 +185.131.30.104 +185.131.30.122 +185.131.30.135 +185.131.30.168 +185.131.30.205 +185.131.30.237 +185.131.30.250 +185.131.117.125 +185.131.117.145 +185.131.119.78 +185.131.155.109 +185.132.80.3 +185.132.80.18 +185.132.80.20 +185.132.80.30 +185.132.80.65 +185.132.80.131 +185.132.80.228 +185.132.80.232 +185.132.80.244 +185.132.81.17 +185.132.81.19 +185.132.81.25 +185.132.81.53 +185.132.81.74 +185.132.81.109 +185.132.81.148 +185.132.81.172 +185.132.81.197 +185.132.81.200 +185.132.81.249 +185.132.82.7 +185.132.82.19 +185.132.82.20 +185.132.82.58 +185.132.82.84 +185.132.82.94 +185.132.82.128 +185.132.82.160 +185.132.82.168 +185.132.82.177 +185.132.82.178 +185.132.82.191 +185.134.96.114 +185.134.96.150 +185.134.96.161 +185.134.96.162 +185.134.96.230 +185.134.97.98 +185.134.97.230 +185.134.97.250 +185.134.98.10 +185.134.98.130 +185.134.98.142 +185.134.98.144 +185.134.98.166 +185.134.98.196 +185.134.98.213 +185.134.98.220 +185.134.98.250 +185.134.99.250 +185.135.47.70 +185.135.228.209 +185.135.229.72 +185.135.229.227 +185.135.230.4 +185.135.230.84 +185.135.230.246 +185.135.230.251 +185.135.231.122 +185.136.100.43 +185.136.100.78 +185.136.101.31 +185.136.101.142 +185.136.102.96 +185.136.102.124 +185.136.102.229 +185.136.103.90 +185.136.103.125 +185.136.133.36 +185.136.133.37 +185.136.133.38 +185.136.133.106 +185.136.133.107 +185.136.180.9 +185.136.180.75 +185.136.180.91 +185.136.180.102 +185.136.180.176 +185.136.180.184 +185.136.180.218 +185.136.180.221 +185.136.180.234 +185.136.180.255 +185.136.182.160 +185.136.182.185 +185.136.183.168 +185.136.183.180 +185.136.183.210 +185.136.183.220 +185.136.183.252 +185.136.192.18 +185.136.192.114 +185.136.193.22 +185.136.193.41 +185.136.193.207 +185.136.193.233 +185.136.194.33 +185.136.194.34 +185.136.194.36 +185.136.194.38 +185.136.194.39 +185.136.194.42 +185.136.194.43 +185.136.194.44 +185.136.194.45 +185.136.194.46 +185.136.195.206 +185.137.25.70 +185.137.25.98 +185.137.25.210 +185.137.26.54 +185.137.26.138 +185.137.26.155 +185.137.26.182 +185.137.60.112 +185.137.60.150 +185.137.60.202 +185.137.108.1 +185.137.108.106 +185.137.108.128 +185.137.108.137 +185.137.108.163 +185.137.108.192 +185.137.108.193 +185.137.108.197 +185.137.108.198 +185.137.108.211 +185.137.108.215 +185.137.108.230 +185.137.108.238 +185.137.109.0 +185.137.109.1 +185.137.109.2 +185.137.109.13 +185.137.109.36 +185.137.109.49 +185.137.109.131 +185.137.109.254 +185.137.109.255 +185.137.110.0 +185.137.110.18 +185.137.110.25 +185.137.110.31 +185.137.110.57 +185.137.110.64 +185.137.110.74 +185.137.110.76 +185.137.110.106 +185.137.110.109 +185.137.111.0 +185.140.4.8 +185.140.4.14 +185.140.4.24 +185.140.4.26 +185.140.4.27 +185.140.4.28 +185.140.4.29 +185.140.4.65 +185.140.4.66 +185.140.4.67 +185.140.4.72 +185.140.4.73 +185.140.4.76 +185.140.4.77 +185.140.4.78 +185.140.4.79 +185.140.5.6 +185.140.7.130 +185.140.7.131 +185.140.7.142 +185.140.56.21 +185.140.58.179 +185.140.59.3 +185.140.59.162 +185.140.59.186 +185.140.232.9 +185.140.232.28 +185.140.232.47 +185.140.232.57 +185.140.232.66 +185.140.232.242 +185.140.232.249 +185.140.232.251 +185.140.232.252 +185.140.234.1 +185.140.234.47 +185.140.234.251 +185.140.235.2 +185.140.240.1 +185.140.240.16 +185.140.240.49 +185.140.240.50 +185.140.240.54 +185.140.240.89 +185.140.240.94 +185.140.240.159 +185.140.240.161 +185.140.240.162 +185.140.240.239 +185.140.240.250 +185.140.240.252 +185.140.241.148 +185.140.242.33 +185.140.242.35 +185.140.242.41 +185.140.242.42 +185.140.242.44 +185.140.242.49 +185.140.242.50 +185.140.242.129 +185.140.242.133 +185.140.242.134 +185.140.242.146 +185.140.242.155 +185.140.243.13 +185.140.243.104 +185.140.243.121 +185.140.243.128 +185.140.243.133 +185.140.243.169 +185.140.243.175 +185.140.243.176 +185.140.243.179 +185.140.243.182 +185.140.243.254 +185.140.243.255 +185.141.36.58 +185.141.36.67 +185.141.39.3 +185.141.39.21 +185.141.39.30 +185.141.39.39 +185.141.39.114 +185.141.39.225 +185.141.49.129 +185.141.104.120 +185.141.104.209 +185.141.105.209 +185.141.105.210 +185.141.105.211 +185.141.105.212 +185.141.105.213 +185.141.105.235 +185.141.105.236 +185.141.106.51 +185.141.106.217 +185.141.106.219 +185.141.106.220 +185.141.106.221 +185.141.106.222 +185.141.106.223 +185.141.107.137 +185.141.107.220 +185.141.132.64 +185.141.132.65 +185.141.132.66 +185.141.133.164 +185.141.134.48 +185.141.134.149 +185.141.134.150 +185.141.134.219 +185.141.134.253 +185.141.168.12 +185.141.168.98 +185.141.168.118 +185.141.169.233 +185.141.171.114 +185.141.171.115 +185.141.171.116 +185.141.171.117 +185.141.213.130 +185.141.213.169 +185.141.213.246 +185.141.213.252 +185.141.214.19 +185.141.214.92 +185.141.215.61 +185.142.92.104 +185.142.92.107 +185.142.92.111 +185.142.92.117 +185.142.92.156 +185.142.92.166 +185.142.92.177 +185.142.92.184 +185.142.92.198 +185.142.92.199 +185.142.92.214 +185.142.92.220 +185.142.92.222 +185.142.93.27 +185.142.93.48 +185.142.93.64 +185.142.93.81 +185.142.93.100 +185.142.93.137 +185.142.93.138 +185.142.93.144 +185.142.93.146 +185.142.93.173 +185.142.93.200 +185.142.93.203 +185.142.93.208 +185.142.93.214 +185.142.93.230 +185.142.93.234 +185.142.93.235 +185.142.93.236 +185.142.93.243 +185.142.94.6 +185.142.94.16 +185.142.94.31 +185.142.94.38 +185.142.94.42 +185.142.94.48 +185.142.94.53 +185.142.94.66 +185.142.94.68 +185.142.94.77 +185.142.94.79 +185.142.94.82 +185.142.94.121 +185.142.94.143 +185.142.94.152 +185.142.94.158 +185.142.94.159 +185.142.94.160 +185.142.95.52 +185.142.95.53 +185.142.95.55 +185.142.95.98 +185.142.95.178 +185.142.124.18 +185.142.124.19 +185.142.124.21 +185.142.124.65 +185.142.124.66 +185.142.124.67 +185.142.124.68 +185.142.124.69 +185.142.124.70 +185.142.124.103 +185.142.124.241 +185.142.125.6 +185.142.125.8 +185.142.125.13 +185.142.125.14 +185.142.125.46 +185.142.125.60 +185.142.125.63 +185.142.125.64 +185.142.125.84 +185.142.125.110 +185.142.125.119 +185.142.125.123 +185.142.125.132 +185.142.125.133 +185.142.125.143 +185.142.125.151 +185.142.125.155 +185.142.125.160 +185.142.125.170 +185.142.125.181 +185.142.125.212 +185.142.125.216 +185.142.125.220 +185.142.126.2 +185.142.126.25 +185.142.126.27 +185.142.126.28 +185.142.126.30 +185.142.126.31 +185.142.126.50 +185.142.126.59 +185.142.126.65 +185.142.126.66 +185.142.126.76 +185.142.126.82 +185.142.126.96 +185.142.126.130 +185.142.126.134 +185.142.126.177 +185.142.126.184 +185.142.126.188 +185.142.126.198 +185.142.126.224 +185.142.126.226 +185.142.126.236 +185.142.126.243 +185.142.127.58 +185.142.127.76 +185.142.127.101 +185.142.127.109 +185.142.127.126 +185.142.127.131 +185.142.127.161 +185.142.127.166 +185.142.127.212 +185.142.127.214 +185.142.156.216 +185.142.156.226 +185.142.157.54 +185.142.157.94 +185.142.157.250 +185.142.159.66 +185.142.159.85 +185.142.159.100 +185.142.159.110 +185.142.159.170 +185.142.159.187 +185.142.159.250 +185.142.159.252 +185.143.205.13 +185.143.205.62 +185.143.205.96 +185.143.205.141 +185.143.205.146 +185.143.205.150 +185.143.205.160 +185.143.205.226 +185.143.207.3 +185.143.207.13 +185.143.207.92 +185.143.207.111 +185.143.207.114 +185.143.207.117 +185.143.207.130 +185.143.207.142 +185.143.207.148 +185.143.207.150 +185.143.207.151 +185.143.207.182 +185.143.207.253 +185.145.8.254 +185.145.9.76 +185.145.9.80 +185.145.9.161 +185.145.184.91 +185.145.184.94 +185.145.184.145 +185.145.184.147 +185.145.184.148 +185.145.184.151 +185.145.184.175 +185.145.184.183 +185.145.184.192 +185.145.184.193 +185.145.184.195 +185.145.184.235 +185.145.185.13 +185.145.185.14 +185.145.185.15 +185.145.185.87 +185.145.185.96 +185.145.185.98 +185.145.185.99 +185.145.185.111 +185.145.185.127 +185.145.185.132 +185.145.185.170 +185.145.185.176 +185.145.186.45 +185.145.186.80 +185.145.186.134 +185.145.186.141 +185.145.186.161 +185.145.186.170 +185.145.186.240 +185.145.186.241 +185.145.186.242 +185.145.186.243 +185.145.186.244 +185.145.186.245 +185.145.186.246 +185.145.186.247 +185.145.187.13 +185.145.187.161 +185.145.187.169 +185.145.187.190 +185.145.187.196 +185.145.187.221 +185.145.187.229 +185.145.187.235 +185.145.187.241 +185.145.187.243 +185.147.40.10 +185.147.40.39 +185.147.40.88 +185.147.40.210 +185.147.41.28 +185.147.41.42 +185.147.41.176 +185.147.160.66 +185.147.160.101 +185.147.161.195 +185.147.161.200 +185.147.161.205 +185.147.162.11 +185.147.163.206 +185.149.192.20 +185.149.192.27 +185.149.192.37 +185.149.192.41 +185.149.192.137 +185.149.192.138 +185.153.185.11 +185.153.185.254 +185.153.208.6 +185.155.9.102 +185.155.10.3 +185.155.10.6 +185.155.10.7 +185.155.10.60 +185.155.10.66 +185.155.10.163 +185.155.10.169 +185.155.10.212 +185.155.14.83 +185.155.14.95 +185.155.14.126 +185.155.14.174 +185.155.14.199 +185.155.14.226 +185.155.15.27 +185.155.15.56 +185.155.15.92 +185.155.15.189 +185.155.15.244 +185.158.172.26 +185.158.172.30 +185.158.172.116 +185.158.172.129 +185.158.172.245 +185.158.173.53 +185.158.173.119 +185.158.173.181 +185.158.174.73 +185.158.174.237 +185.159.153.14 +185.159.153.28 +185.159.153.41 +185.159.153.117 +185.159.153.144 +185.159.153.188 +185.159.153.195 +185.159.153.206 +185.159.153.217 +185.159.153.229 +185.159.154.12 +185.159.154.121 +185.159.154.122 +185.159.154.148 +185.159.154.156 +185.159.154.203 +185.160.205.6 +185.161.38.18 +185.161.38.117 +185.161.38.121 +185.161.38.145 +185.161.38.253 +185.161.39.72 +185.161.39.73 +185.161.39.77 +185.161.39.116 +185.161.39.170 +185.161.39.194 +185.161.39.195 +185.161.39.196 +185.161.39.197 +185.161.39.222 +185.161.112.231 +185.161.112.249 +185.161.113.130 +185.161.113.134 +185.161.113.226 +185.161.113.228 +185.161.113.242 +185.161.114.10 +185.161.114.22 +185.161.114.74 +185.161.114.138 +185.161.114.189 +185.161.115.178 +185.161.115.243 +185.162.40.27 +185.162.41.69 +185.162.41.122 +185.164.72.41 +185.164.72.54 +185.164.72.90 +185.164.72.123 +185.164.72.158 +185.164.72.234 +185.164.73.172 +185.164.73.253 +185.164.74.240 +185.164.252.27 +185.164.252.28 +185.164.252.130 +185.164.253.24 +185.165.30.80 +185.165.40.11 +185.165.41.221 +185.165.41.222 +185.165.43.11 +185.165.116.18 +185.165.116.33 +185.165.116.35 +185.165.116.138 +185.165.116.147 +185.165.116.240 +185.165.118.55 +185.165.118.65 +185.165.119.49 +185.166.104.55 +185.169.6.62 +185.169.6.120 +185.169.6.197 +185.169.6.204 +185.169.20.34 +185.169.20.74 +185.169.20.234 +185.169.21.50 +185.169.21.118 +185.169.22.102 +185.170.236.18 +185.170.236.125 +185.170.236.130 +185.170.236.145 +185.170.236.171 +185.170.236.205 +185.170.237.141 +185.170.237.161 +185.170.237.204 +185.170.237.209 +185.170.238.64 +185.170.238.70 +185.170.238.159 +185.170.238.194 +185.170.238.200 +185.170.239.5 +185.170.239.59 +185.170.239.72 +185.170.239.130 +185.170.239.150 +185.170.239.210 +185.171.53.150 +185.171.54.18 +185.171.54.20 +185.171.54.21 +185.171.54.26 +185.171.54.29 +185.171.54.30 +185.171.54.34 +185.171.54.35 +185.171.54.36 +185.171.54.42 +185.171.54.67 +185.171.54.68 +185.171.54.69 +185.171.54.84 +185.171.54.86 +185.171.54.90 +185.171.54.138 +185.171.54.139 +185.171.54.140 +185.171.54.141 +185.171.54.156 +185.171.54.170 +185.171.54.234 +185.171.54.237 +185.171.54.238 +185.171.55.138 +185.171.55.139 +185.171.55.140 +185.171.55.141 +185.171.55.162 +185.171.55.163 +185.171.55.164 +185.171.55.219 +185.171.55.220 +185.171.55.221 +185.171.55.222 +185.171.55.233 +185.172.0.42 +185.172.0.206 +185.172.0.218 +185.172.0.238 +185.172.1.70 +185.172.1.126 +185.172.1.202 +185.172.1.214 +185.172.2.194 +185.172.2.195 +185.172.2.196 +185.172.2.198 +185.172.3.162 +185.172.3.242 +185.172.68.1 +185.172.68.3 +185.172.68.4 +185.172.68.13 +185.172.68.16 +185.172.68.31 +185.172.68.32 +185.172.68.37 +185.172.68.41 +185.172.68.64 +185.172.68.71 +185.172.68.75 +185.172.68.76 +185.172.69.9 +185.172.69.37 +185.172.70.70 +185.173.104.53 +185.173.104.194 +185.173.104.200 +185.173.104.208 +185.173.105.78 +185.173.105.141 +185.173.105.142 +185.173.106.117 +185.173.107.26 +185.173.129.35 +185.173.129.91 +185.173.129.185 +185.173.129.203 +185.173.129.240 +185.173.129.243 +185.174.132.21 +185.174.132.25 +185.174.132.130 +185.174.132.217 +185.174.132.254 +185.174.134.66 +185.175.76.162 +185.175.76.164 +185.175.76.165 +185.175.76.166 +185.175.77.67 +185.175.79.89 +185.175.79.177 +185.175.79.193 +185.176.32.38 +185.176.33.38 +185.176.34.16 +185.176.34.25 +185.176.34.94 +185.176.35.130 +185.176.56.253 +185.176.57.98 +185.176.57.138 +185.176.59.48 +185.176.59.49 +185.176.59.201 +185.176.59.209 +185.176.59.246 +185.176.59.254 +185.177.158.50 +185.177.158.53 +185.177.158.54 +185.177.158.60 +185.177.159.84 +185.177.159.227 +185.177.159.247 +185.179.90.107 +185.179.168.3 +185.179.168.4 +185.179.168.13 +185.179.168.14 +185.179.168.16 +185.179.168.17 +185.179.168.18 +185.179.168.19 +185.179.168.31 +185.179.168.36 +185.179.168.47 +185.179.168.114 +185.179.168.115 +185.179.168.116 +185.179.168.117 +185.179.168.118 +185.179.168.119 +185.179.168.120 +185.179.168.121 +185.179.168.122 +185.179.168.123 +185.179.168.124 +185.179.168.125 +185.179.168.126 +185.179.168.169 +185.179.169.28 +185.179.169.82 +185.179.169.141 +185.179.169.166 +185.179.169.169 +185.179.169.180 +185.179.169.203 +185.179.169.208 +185.179.169.212 +185.179.169.221 +185.179.169.244 +185.179.170.2 +185.179.170.124 +185.179.170.127 +185.179.170.217 +185.179.170.230 +185.179.170.239 +185.179.171.0 +185.179.171.25 +185.179.171.55 +185.179.171.72 +185.179.171.110 +185.179.171.121 +185.179.171.130 +185.179.171.200 +185.179.221.3 +185.179.221.168 +185.180.128.22 +185.180.128.54 +185.180.128.64 +185.180.128.69 +185.180.128.70 +185.180.128.100 +185.180.129.2 +185.180.129.4 +185.180.129.5 +185.180.129.6 +185.180.129.7 +185.180.129.40 +185.180.129.41 +185.180.129.100 +185.180.129.138 +185.180.129.142 +185.180.129.216 +185.180.129.222 +185.180.129.228 +185.180.129.229 +185.180.129.230 +185.180.129.231 +185.180.129.234 +185.180.130.1 +185.180.130.8 +185.180.130.47 +185.180.130.133 +185.180.130.136 +185.180.130.182 +185.180.130.184 +185.180.130.231 +185.180.130.240 +185.180.130.246 +185.180.130.254 +185.181.180.138 +185.181.181.64 +185.181.181.65 +185.181.181.67 +185.181.181.68 +185.181.181.69 +185.181.181.70 +185.181.181.71 +185.181.181.72 +185.181.181.73 +185.181.181.75 +185.181.181.77 +185.181.181.79 +185.181.181.167 +185.181.182.39 +185.181.182.61 +185.181.182.141 +185.181.182.169 +185.181.182.235 +185.181.183.4 +185.181.183.12 +185.181.183.17 +185.181.183.51 +185.181.183.100 +185.181.183.105 +185.181.183.117 +185.186.50.221 +185.186.51.49 +185.186.240.9 +185.186.240.64 +185.186.240.65 +185.186.240.66 +185.186.240.67 +185.186.240.177 +185.186.240.179 +185.186.240.180 +185.186.240.182 +185.186.240.232 +185.186.242.66 +185.186.242.146 +185.186.243.23 +185.186.243.30 +185.186.243.31 +185.186.243.39 +185.186.243.48 +185.186.243.57 +185.186.243.98 +185.186.243.99 +185.186.243.112 +185.186.243.124 +185.186.243.148 +185.186.243.176 +185.186.243.240 +185.186.243.241 +185.186.243.243 +185.186.243.245 +185.186.243.246 +185.187.48.30 +185.187.48.74 +185.187.48.122 +185.187.49.12 +185.187.49.15 +185.187.49.56 +185.187.49.60 +185.187.49.70 +185.187.50.39 +185.187.50.43 +185.187.51.0 +185.187.51.32 +185.187.51.42 +185.187.51.48 +185.187.51.49 +185.187.51.94 +185.187.51.245 +185.187.51.255 +185.187.87.253 +185.188.112.149 +185.188.112.154 +185.188.112.172 +185.188.112.193 +185.188.112.207 +185.188.113.21 +185.188.113.104 +185.188.113.113 +185.188.115.5 +185.188.115.10 +185.190.39.59 +185.190.39.242 +185.191.76.26 +185.191.77.170 +185.191.77.174 +185.191.77.253 +185.191.78.86 +185.191.78.233 +185.191.78.245 +185.191.79.6 +185.191.79.154 +185.191.79.210 +185.192.112.69 +185.192.112.73 +185.192.112.143 +185.192.112.252 +185.192.113.83 +185.192.113.87 +185.192.113.108 +185.192.113.121 +185.192.113.148 +185.196.148.111 +185.196.151.7 +185.201.48.17 +185.201.48.141 +185.201.48.172 +185.201.48.184 +185.201.48.231 +185.201.49.146 +185.201.50.90 +185.201.51.41 +185.201.51.67 +185.201.51.69 +185.201.51.75 +185.201.51.76 +185.201.51.116 +185.201.51.161 +185.201.51.217 +185.201.51.221 +185.201.51.229 +185.202.56.22 +185.202.56.74 +185.202.56.86 +185.202.56.98 +185.202.56.102 +185.202.56.210 +185.202.56.214 +185.202.58.150 +185.202.58.183 +185.202.58.236 +185.202.59.114 +185.204.182.10 +185.204.197.53 +185.204.197.149 +185.204.197.226 +185.205.203.242 +185.206.92.63 +185.206.93.203 +185.206.94.42 +185.206.95.9 +185.206.229.21 +185.206.229.22 +185.206.229.23 +185.206.229.24 +185.206.229.25 +185.206.229.30 +185.206.229.31 +185.206.229.32 +185.206.229.35 +185.206.229.36 +185.206.229.50 +185.206.229.72 +185.206.229.103 +185.206.229.104 +185.206.231.31 +185.206.231.68 +185.206.231.79 +185.206.231.216 +185.206.238.178 +185.208.76.101 +185.208.76.102 +185.208.76.103 +185.208.76.104 +185.208.76.105 +185.208.76.106 +185.208.77.85 +185.208.77.178 +185.208.77.179 +185.208.78.100 +185.208.78.101 +185.208.79.95 +185.208.148.3 +185.208.148.48 +185.208.148.216 +185.208.148.241 +185.208.149.138 +185.208.174.44 +185.208.174.68 +185.208.174.153 +185.208.175.11 +185.208.175.167 +185.208.175.209 +185.208.175.235 +185.208.180.9 +185.208.180.170 +185.208.181.66 +185.208.181.67 +185.208.181.68 +185.208.181.86 +185.208.182.3 +185.208.182.77 +185.208.182.117 +185.208.183.14 +185.208.183.164 +185.208.183.188 +185.209.42.6 +185.209.42.50 +185.209.42.95 +185.211.56.126 +185.211.57.166 +185.211.58.79 +185.211.58.146 +185.211.59.115 +185.211.59.134 +185.211.59.160 +185.212.50.5 +185.212.50.10 +185.212.51.73 +185.212.51.119 +185.212.51.144 +185.212.51.160 +185.212.51.201 +185.212.51.202 +185.212.51.203 +185.212.51.204 +185.212.51.205 +185.212.51.206 +185.212.51.241 +185.212.193.4 +185.212.193.21 +185.212.193.22 +185.212.193.23 +185.212.193.97 +185.212.193.160 +185.212.193.162 +185.212.193.163 +185.212.193.165 +185.212.193.170 +185.212.195.34 +185.212.195.35 +185.212.195.36 +185.212.195.37 +185.212.195.38 +185.212.195.41 +185.212.195.58 +185.212.195.113 +185.212.195.114 +185.212.195.116 +185.212.195.154 +185.212.195.161 +185.212.195.167 +185.212.195.174 +185.212.195.177 +185.212.195.178 +185.212.195.254 +185.213.10.99 +185.213.10.101 +185.213.11.23 +185.213.11.59 +185.213.11.75 +185.213.11.77 +185.213.11.93 +185.213.164.4 +185.213.164.5 +185.213.164.25 +185.213.164.105 +185.213.164.151 +185.213.164.191 +185.213.164.242 +185.213.165.30 +185.213.165.31 +185.213.165.32 +185.213.165.62 +185.213.165.70 +185.213.165.82 +185.213.165.97 +185.213.165.104 +185.213.165.224 +185.214.39.156 +185.215.154.23 +185.215.234.253 +185.217.6.30 +185.217.6.130 +185.218.139.80 +185.218.139.100 +185.218.139.133 +185.220.226.0 +185.220.226.1 +185.220.226.2 +185.220.226.3 +185.220.226.4 +185.220.226.5 +185.220.226.6 +185.220.226.7 +185.220.226.8 +185.220.226.10 +185.220.226.11 +185.220.226.12 +185.220.226.13 +185.220.226.15 +185.220.226.17 +185.220.226.18 +185.220.226.19 +185.220.226.21 +185.220.226.24 +185.220.226.25 +185.220.226.26 +185.220.226.27 +185.220.226.29 +185.220.226.30 +185.220.226.31 +185.220.226.33 +185.220.226.34 +185.220.226.35 +185.220.226.36 +185.220.226.37 +185.220.226.38 +185.220.226.39 +185.220.226.40 +185.220.226.42 +185.220.226.43 +185.220.226.44 +185.220.226.45 +185.220.226.46 +185.220.226.47 +185.220.226.48 +185.220.226.49 +185.220.226.50 +185.220.226.51 +185.220.226.52 +185.220.226.53 +185.220.226.54 +185.220.226.55 +185.220.226.56 +185.220.226.57 +185.220.226.58 +185.220.226.59 +185.220.226.60 +185.220.226.61 +185.220.226.62 +185.220.226.63 +185.220.226.64 +185.220.226.65 +185.220.226.66 +185.220.226.68 +185.220.226.69 +185.220.226.70 +185.220.226.71 +185.220.226.72 +185.220.226.73 +185.220.226.75 +185.220.226.76 +185.220.226.77 +185.220.226.78 +185.220.226.79 +185.220.226.80 +185.220.226.81 +185.220.226.83 +185.220.226.84 +185.220.226.85 +185.220.226.86 +185.220.226.87 +185.220.226.88 +185.220.226.89 +185.220.226.90 +185.220.226.92 +185.220.226.93 +185.220.226.94 +185.220.226.95 +185.220.226.96 +185.220.226.97 +185.220.226.98 +185.220.226.99 +185.220.226.100 +185.220.226.101 +185.220.226.103 +185.220.226.104 +185.220.226.105 +185.220.226.106 +185.220.226.108 +185.220.226.110 +185.220.226.111 +185.220.226.112 +185.220.226.114 +185.220.226.115 +185.220.226.116 +185.220.226.117 +185.220.226.118 +185.220.226.121 +185.220.226.122 +185.220.226.123 +185.220.226.124 +185.220.226.125 +185.220.226.126 +185.220.226.127 +185.220.226.128 +185.220.226.130 +185.220.226.131 +185.220.226.132 +185.220.226.133 +185.220.226.134 +185.220.226.135 +185.220.226.136 +185.220.226.137 +185.220.226.138 +185.220.226.139 +185.220.226.140 +185.220.226.141 +185.220.226.142 +185.220.226.143 +185.220.226.144 +185.220.226.145 +185.220.226.146 +185.220.226.147 +185.220.226.148 +185.220.226.150 +185.220.226.151 +185.220.226.152 +185.220.226.153 +185.220.226.154 +185.220.226.155 +185.220.226.156 +185.220.226.157 +185.220.226.158 +185.220.226.159 +185.220.226.160 +185.220.226.161 +185.220.226.162 +185.220.226.163 +185.220.226.164 +185.220.226.165 +185.220.226.166 +185.220.226.167 +185.220.226.168 +185.220.226.169 +185.220.226.170 +185.220.226.171 +185.220.226.172 +185.220.226.173 +185.220.226.174 +185.220.226.175 +185.220.226.176 +185.220.226.177 +185.220.226.178 +185.220.226.179 +185.220.226.181 +185.220.226.182 +185.220.226.183 +185.220.226.184 +185.220.226.185 +185.220.226.186 +185.220.226.187 +185.220.226.188 +185.220.226.189 +185.220.226.190 +185.220.226.191 +185.220.226.192 +185.220.226.193 +185.220.226.195 +185.220.226.198 +185.220.226.199 +185.220.226.200 +185.220.226.201 +185.220.226.202 +185.220.226.203 +185.220.226.204 +185.220.226.205 +185.220.226.206 +185.220.226.207 +185.220.226.208 +185.220.226.209 +185.220.226.211 +185.220.226.212 +185.220.226.213 +185.220.226.214 +185.220.226.215 +185.220.226.216 +185.220.226.217 +185.220.226.218 +185.220.226.219 +185.220.226.220 +185.220.226.221 +185.220.226.222 +185.220.226.223 +185.220.226.224 +185.220.226.225 +185.220.226.226 +185.220.226.227 +185.220.226.228 +185.220.226.230 +185.220.226.231 +185.220.226.232 +185.220.226.233 +185.220.226.234 +185.220.226.236 +185.220.226.237 +185.220.226.239 +185.220.226.240 +185.220.226.241 +185.220.226.242 +185.220.226.244 +185.220.226.247 +185.220.226.248 +185.220.226.249 +185.220.226.250 +185.220.226.251 +185.220.226.252 +185.220.226.253 +185.220.226.255 +185.221.192.6 +185.221.192.27 +185.221.192.28 +185.221.192.101 +185.221.192.215 +185.221.194.133 +185.221.194.195 +185.221.194.228 +185.221.194.238 +185.221.194.250 +185.221.194.252 +185.221.195.94 +185.221.195.95 +185.221.195.227 +185.221.195.235 +185.221.239.51 +185.221.239.59 +185.221.239.73 +185.221.239.89 +185.221.239.92 +185.221.239.164 +185.221.239.220 +185.222.163.1 +185.222.163.2 +185.222.163.65 +185.222.163.81 +185.222.163.89 +185.222.163.93 +185.222.163.97 +185.222.163.165 +185.222.163.169 +185.222.163.177 +185.222.163.185 +185.222.163.193 +185.222.163.201 +185.222.163.209 +185.224.176.14 +185.224.176.24 +185.224.176.41 +185.224.176.82 +185.224.176.112 +185.224.176.125 +185.224.176.127 +185.224.176.159 +185.224.176.163 +185.224.176.177 +185.224.176.190 +185.224.176.215 +185.224.176.220 +185.224.177.33 +185.224.177.91 +185.224.177.112 +185.224.177.163 +185.224.177.187 +185.224.177.244 +185.224.179.16 +185.224.179.17 +185.224.179.27 +185.224.179.38 +185.224.179.45 +185.224.179.47 +185.224.179.61 +185.224.179.75 +185.224.179.81 +185.224.179.90 +185.224.179.118 +185.224.179.176 +185.224.179.186 +185.224.179.204 +185.224.179.213 +185.224.179.227 +185.225.80.25 +185.225.80.73 +185.225.80.102 +185.225.80.130 +185.225.80.233 +185.225.80.234 +185.225.82.154 +185.225.82.162 +185.225.82.195 +185.225.83.64 +185.225.183.2 +185.225.240.138 +185.225.240.150 +185.225.241.230 +185.225.241.242 +185.225.241.250 +185.225.242.162 +185.225.242.178 +185.225.242.243 +185.225.243.83 +185.225.243.84 +185.225.243.86 +185.225.243.87 +185.225.243.88 +185.225.243.89 +185.225.243.91 +185.225.243.94 +185.226.116.89 +185.226.116.243 +185.226.117.8 +185.226.117.111 +185.226.117.156 +185.226.117.161 +185.226.117.193 +185.226.117.231 +185.226.118.2 +185.226.119.93 +185.226.119.113 +185.226.119.143 +185.226.119.220 +185.228.59.189 +185.229.28.46 +185.229.31.47 +185.229.31.73 +185.229.31.171 +185.229.31.193 +185.229.133.40 +185.229.204.98 +185.229.204.99 +185.229.204.100 +185.229.204.101 +185.229.204.145 +185.231.65.2 +185.231.65.213 +185.231.112.64 +185.231.112.114 +185.231.112.169 +185.231.115.28 +185.231.115.126 +185.231.115.201 +185.231.115.248 +185.231.180.157 +185.231.181.20 +185.231.181.30 +185.231.182.164 +185.231.182.250 +185.231.183.143 +185.232.154.125 +185.233.131.19 +185.233.131.30 +185.233.131.152 +185.233.131.154 +185.233.131.156 +185.233.131.170 +185.233.131.171 +185.233.131.183 +185.233.131.186 +185.234.14.114 +185.235.136.161 +185.235.136.184 +185.235.139.9 +185.235.139.41 +185.235.139.57 +185.235.139.70 +185.235.139.151 +185.235.196.3 +185.235.196.6 +185.235.196.21 +185.235.196.41 +185.235.196.42 +185.235.196.44 +185.235.196.51 +185.235.196.54 +185.235.196.58 +185.235.196.75 +185.235.196.91 +185.235.196.105 +185.235.196.109 +185.235.196.118 +185.235.196.144 +185.235.197.39 +185.235.245.149 +185.235.245.186 +185.236.36.154 +185.236.37.101 +185.236.37.199 +185.236.38.41 +185.236.38.42 +185.236.38.43 +185.236.38.47 +185.236.38.84 +185.237.84.36 +185.237.84.49 +185.237.84.188 +185.237.84.195 +185.237.84.241 +185.237.84.243 +185.237.84.245 +185.237.85.220 +185.237.86.140 +185.237.87.6 +185.237.87.65 +185.237.87.99 +185.237.87.128 +185.237.87.141 +185.237.87.158 +185.237.87.165 +185.237.87.195 +185.237.87.214 +185.238.44.24 +185.238.44.61 +185.238.44.72 +185.238.44.121 +185.238.44.134 +185.238.44.205 +185.238.44.212 +185.238.44.215 +185.238.44.232 +185.238.44.234 +185.238.45.15 +185.238.45.17 +185.238.45.25 +185.238.45.47 +185.238.45.71 +185.238.45.76 +185.238.45.119 +185.238.45.121 +185.238.45.129 +185.238.45.156 +185.238.45.173 +185.238.45.176 +185.238.45.181 +185.238.45.211 +185.238.45.214 +185.238.45.246 +185.238.45.250 +185.238.46.8 +185.238.46.18 +185.238.46.54 +185.238.46.59 +185.238.46.62 +185.238.46.118 +185.238.46.138 +185.238.46.140 +185.238.46.146 +185.238.46.158 +185.238.46.164 +185.238.46.172 +185.238.46.181 +185.238.46.200 +185.238.46.214 +185.238.46.215 +185.238.46.232 +185.238.46.237 +185.238.46.244 +185.238.47.6 +185.238.47.11 +185.238.47.13 +185.238.47.21 +185.238.47.36 +185.238.47.62 +185.238.47.109 +185.238.47.111 +185.238.47.115 +185.238.47.124 +185.238.47.147 +185.238.47.152 +185.238.47.185 +185.238.47.199 +185.238.47.216 +185.238.47.252 +185.238.140.75 +185.239.0.72 +185.239.1.30 +185.239.1.62 +185.239.2.3 +185.239.3.51 +185.239.3.69 +185.239.3.166 +185.239.3.189 +185.239.106.121 +185.243.48.103 +185.243.48.133 +185.243.48.190 +185.243.49.158 +185.243.50.1 +185.243.50.2 +185.246.6.94 +185.246.6.102 +185.251.76.53 +185.251.76.193 +185.251.76.194 +185.251.77.130 +185.252.29.116 +185.252.29.233 +185.252.30.130 +185.252.31.36 +185.252.31.58 +185.252.31.68 +185.252.31.113 +185.252.31.130 +185.252.31.156 +185.252.31.200 +185.252.31.210 +185.252.86.16 +185.252.86.40 +185.252.86.154 +185.252.86.182 +185.255.88.108 +185.255.88.114 +185.255.88.138 +185.255.88.155 +185.255.88.166 +185.255.88.204 +185.255.88.237 +185.255.89.69 +185.255.89.80 +185.255.89.100 +185.255.89.101 +185.255.89.108 +185.255.89.130 +185.255.89.146 +185.255.89.156 +185.255.89.158 +185.255.89.164 +185.255.89.246 +185.255.90.24 +185.255.90.114 +185.255.90.130 +185.255.90.140 +185.255.90.147 +185.255.91.1 +185.255.91.2 +185.255.91.10 +185.255.91.42 +185.255.91.84 +185.255.91.207 +185.255.208.35 +185.255.208.179 +185.255.208.194 +185.255.209.132 +185.255.209.134 +185.255.209.137 +185.255.209.224 +185.255.209.227 +185.255.209.253 +185.255.210.32 +185.255.210.151 +185.255.210.152 +185.255.210.167 +185.255.210.175 +185.255.210.195 +185.255.210.198 +185.255.210.199 +185.255.210.209 +185.255.211.6 +185.255.211.30 +185.255.211.48 +185.255.211.67 +185.255.211.121 +185.255.211.150 +185.255.211.158 +185.255.211.195 +188.0.240.2 +188.0.240.3 +188.0.240.5 +188.0.240.6 +188.0.241.253 +188.0.244.246 +188.0.244.254 +188.0.245.1 +188.0.246.55 +188.0.246.94 +188.0.247.47 +188.0.247.245 +188.0.249.124 +188.0.249.204 +188.0.249.219 +188.0.251.180 +188.0.252.126 +188.0.254.153 +188.0.255.161 +188.75.65.201 +188.75.78.42 +188.75.78.131 +188.75.78.173 +188.75.78.181 +188.75.80.42 +188.75.80.73 +188.75.80.192 +188.75.80.226 +188.75.80.227 +188.75.80.228 +188.75.80.234 +188.75.81.162 +188.75.81.163 +188.75.81.165 +188.75.81.166 +188.75.87.195 +188.75.88.147 +188.75.88.148 +188.75.88.149 +188.75.88.150 +188.75.93.203 +188.75.93.227 +188.75.104.114 +188.75.104.115 +188.75.104.116 +188.75.104.117 +188.75.104.118 +188.75.126.18 +188.75.126.74 +188.94.188.1 +188.121.96.87 +188.121.100.30 +188.121.100.110 +188.121.100.111 +188.121.100.169 +188.121.100.176 +188.121.100.203 +188.121.101.25 +188.121.101.133 +188.121.101.140 +188.121.102.5 +188.121.102.200 +188.121.103.53 +188.121.103.86 +188.121.103.248 +188.121.104.17 +188.121.104.40 +188.121.105.16 +188.121.105.93 +188.121.105.118 +188.121.105.171 +188.121.106.202 +188.121.107.141 +188.121.107.164 +188.121.108.55 +188.121.108.56 +188.121.109.133 +188.121.110.36 +188.121.110.61 +188.121.110.172 +188.121.110.202 +188.121.111.40 +188.121.111.127 +188.121.112.15 +188.121.113.136 +188.121.116.47 +188.121.116.238 +188.121.117.164 +188.121.117.166 +188.121.118.101 +188.121.118.105 +188.121.118.128 +188.121.118.162 +188.121.118.197 +188.121.118.206 +188.121.118.216 +188.121.119.99 +188.121.119.145 +188.121.119.191 +188.121.119.217 +188.121.120.91 +188.121.120.194 +188.121.122.208 +188.121.123.70 +188.121.123.188 +188.121.124.67 +188.121.128.40 +188.121.129.122 +188.121.129.226 +188.121.129.227 +188.121.129.228 +188.121.129.238 +188.121.144.30 +188.121.144.162 +188.121.144.194 +188.121.144.238 +188.121.145.6 +188.121.145.22 +188.121.145.78 +188.121.145.122 +188.121.145.154 +188.121.145.182 +188.121.146.50 +188.121.146.142 +188.121.146.198 +188.121.146.226 +188.121.147.34 +188.121.147.38 +188.121.147.42 +188.121.147.46 +188.121.147.50 +188.121.147.58 +188.121.147.62 +188.121.147.66 +188.121.147.70 +188.121.147.74 +188.121.147.78 +188.121.147.90 +188.121.147.118 +188.121.147.150 +188.121.147.166 +188.121.148.46 +188.121.148.66 +188.121.148.94 +188.121.148.162 +188.121.148.174 +188.121.148.186 +188.121.148.190 +188.121.149.50 +188.121.149.62 +188.121.149.114 +188.121.149.138 +188.121.149.186 +188.121.149.242 +188.121.156.211 +188.121.157.133 +188.121.157.170 +188.121.157.181 +188.121.157.186 +188.121.157.250 +188.121.159.2 +188.121.159.34 +188.121.159.36 +188.121.159.37 +188.136.130.1 +188.136.130.160 +188.136.133.33 +188.136.133.111 +188.136.133.114 +188.136.133.123 +188.136.133.218 +188.136.133.224 +188.136.133.225 +188.136.133.243 +188.136.142.81 +188.136.142.133 +188.136.142.134 +188.136.142.166 +188.136.143.13 +188.136.143.20 +188.136.144.38 +188.136.144.131 +188.136.144.143 +188.136.154.38 +188.136.154.51 +188.136.154.137 +188.136.154.186 +188.136.154.187 +188.136.154.188 +188.136.154.189 +188.136.155.41 +188.136.162.21 +188.136.162.30 +188.136.162.104 +188.136.162.213 +188.136.162.216 +188.136.162.217 +188.136.162.218 +188.136.162.220 +188.136.162.221 +188.136.165.1 +188.136.172.33 +188.136.172.34 +188.136.172.35 +188.136.172.37 +188.136.172.38 +188.136.172.44 +188.136.172.45 +188.136.172.47 +188.136.172.53 +188.136.172.76 +188.136.172.83 +188.136.172.86 +188.136.172.97 +188.136.172.101 +188.136.172.103 +188.136.172.120 +188.136.172.121 +188.136.172.126 +188.136.172.127 +188.136.172.129 +188.136.172.132 +188.136.172.136 +188.136.172.142 +188.136.172.148 +188.136.172.152 +188.136.172.157 +188.136.172.158 +188.136.172.160 +188.136.172.161 +188.136.172.165 +188.136.172.172 +188.136.172.176 +188.136.172.183 +188.136.172.184 +188.136.172.185 +188.136.172.186 +188.136.172.187 +188.136.172.188 +188.136.172.189 +188.136.172.190 +188.136.172.193 +188.136.172.224 +188.136.172.226 +188.136.172.227 +188.136.172.229 +188.136.172.230 +188.136.172.231 +188.136.172.232 +188.136.172.234 +188.136.172.235 +188.136.172.237 +188.136.172.240 +188.136.172.241 +188.136.172.242 +188.136.172.245 +188.136.172.247 +188.136.172.250 +188.136.172.251 +188.136.172.253 +188.136.174.34 +188.136.174.35 +188.136.174.42 +188.136.174.86 +188.136.174.118 +188.136.174.130 +188.136.174.131 +188.136.174.133 +188.136.174.134 +188.136.174.214 +188.136.174.234 +188.136.174.242 +188.136.194.54 +188.136.194.238 +188.136.196.1 +188.136.196.29 +188.136.196.36 +188.136.196.125 +188.136.196.164 +188.136.196.167 +188.136.196.178 +188.136.196.180 +188.136.196.216 +188.136.196.217 +188.136.196.220 +188.136.196.221 +188.136.196.222 +188.136.196.224 +188.136.196.226 +188.136.196.228 +188.136.196.229 +188.136.208.6 +188.136.208.82 +188.136.208.83 +188.136.208.114 +188.136.208.210 +188.136.208.211 +188.136.208.212 +188.136.208.242 +188.136.208.244 +188.136.208.250 +188.136.208.251 +188.136.208.253 +188.136.208.254 +188.208.56.88 +188.208.56.94 +188.208.56.134 +188.208.56.230 +188.208.63.30 +188.208.63.66 +188.208.144.7 +188.208.144.84 +188.208.144.141 +188.208.144.191 +188.208.144.218 +188.208.145.14 +188.208.145.39 +188.208.145.67 +188.208.145.83 +188.208.145.97 +188.208.145.124 +188.208.145.184 +188.208.145.187 +188.208.146.177 +188.208.146.210 +188.208.147.201 +188.208.149.13 +188.208.149.118 +188.208.149.200 +188.208.149.213 +188.208.149.246 +188.208.150.5 +188.208.150.68 +188.208.150.73 +188.208.150.91 +188.208.150.117 +188.208.150.161 +188.208.150.165 +188.208.150.171 +188.208.150.174 +188.208.150.179 +188.208.150.229 +188.208.151.8 +188.208.151.20 +188.208.151.188 +188.208.151.194 +188.208.151.216 +188.208.151.231 +188.208.151.235 +188.208.151.239 +188.208.152.48 +188.209.5.105 +188.209.7.237 +188.209.64.130 +188.209.67.86 +188.209.68.119 +188.209.68.154 +188.209.70.75 +188.209.71.99 +188.209.73.96 +188.209.73.213 +188.209.75.111 +188.209.152.41 +188.209.152.44 +188.209.152.45 +188.210.87.44 +188.210.101.67 +188.210.102.82 +188.210.106.213 +188.210.108.144 +188.210.111.45 +188.210.119.248 +188.210.122.152 +188.210.124.2 +188.211.0.21 +188.211.36.106 +188.211.37.41 +188.211.37.66 +188.211.37.241 +188.211.38.53 +188.211.38.123 +188.211.39.239 +188.211.64.58 +188.211.67.61 +188.211.68.251 +188.211.71.224 +188.211.72.217 +188.211.73.160 +188.211.74.213 +188.211.75.219 +188.211.77.39 +188.211.81.212 +188.211.83.2 +188.211.83.111 +188.211.85.38 +188.211.86.144 +188.211.87.74 +188.211.87.109 +188.211.88.213 +188.211.94.162 +188.211.97.75 +188.211.97.191 +188.211.101.64 +188.211.102.1 +188.211.102.63 +188.211.103.103 +188.211.104.86 +188.211.106.247 +188.211.108.91 +188.211.108.225 +188.211.108.233 +188.211.109.198 +188.211.109.231 +188.211.111.1 +188.211.111.88 +188.211.112.52 +188.211.116.32 +188.211.116.106 +188.211.117.99 +188.211.119.76 +188.211.119.151 +188.211.120.127 +188.211.122.83 +188.211.122.114 +188.211.122.196 +188.211.200.215 +188.211.201.191 +188.211.202.114 +188.211.203.139 +188.211.204.58 +188.211.205.92 +188.211.205.231 +188.211.206.93 +188.211.206.213 +188.212.22.1 +188.212.22.9 +188.212.22.33 +188.212.22.65 +188.212.22.129 +188.212.22.137 +188.212.22.141 +188.212.22.145 +188.212.22.162 +188.212.22.177 +188.212.22.181 +188.212.22.193 +188.212.22.225 +188.212.22.233 +188.212.22.241 +188.212.22.245 +188.212.64.211 +188.212.65.46 +188.212.66.9 +188.212.66.23 +188.212.66.156 +188.212.68.65 +188.212.68.198 +188.212.71.143 +188.212.71.247 +188.212.73.158 +188.212.74.73 +188.212.79.110 +188.212.82.196 +188.212.85.146 +188.212.85.156 +188.212.88.208 +188.212.88.236 +188.212.89.105 +188.212.89.188 +188.212.90.171 +188.212.92.230 +188.212.92.247 +188.212.93.138 +188.212.93.197 +188.212.95.78 +188.212.148.162 +188.212.160.208 +188.212.161.115 +188.212.169.84 +188.212.171.199 +188.212.172.58 +188.212.173.54 +188.212.177.247 +188.212.178.9 +188.212.178.118 +188.212.178.182 +188.212.183.13 +188.212.183.19 +188.212.186.53 +188.212.187.42 +188.212.242.211 +188.212.244.182 +188.213.66.15 +188.213.66.20 +188.213.66.25 +188.213.66.90 +188.213.66.131 +188.213.66.132 +188.213.66.133 +188.213.67.70 +188.213.67.98 +188.213.67.108 +188.213.67.131 +188.213.68.146 +188.213.71.62 +188.213.71.207 +188.213.76.19 +188.213.76.94 +188.213.76.183 +188.213.76.240 +188.213.77.26 +188.213.78.22 +188.213.79.21 +188.213.79.116 +188.213.79.221 +188.213.79.240 +188.213.176.97 +188.213.178.0 +188.213.178.61 +188.213.178.62 +188.213.179.251 +188.213.180.79 +188.213.181.176 +188.213.189.255 +188.213.191.85 +188.213.195.243 +188.213.196.20 +188.213.196.137 +188.213.196.138 +188.213.196.185 +188.213.198.13 +188.213.198.219 +188.213.209.124 +188.213.209.146 +188.213.209.149 +188.213.209.156 +188.213.209.168 +188.213.209.178 +188.213.209.218 +188.213.211.25 +188.213.211.170 +188.214.96.65 +188.214.96.106 +188.214.96.146 +188.214.96.150 +188.214.97.41 +188.214.98.114 +188.214.98.215 +188.214.98.253 +188.214.99.182 +188.214.173.57 +188.214.216.26 +188.214.216.59 +188.214.216.154 +188.214.216.255 +188.214.217.240 +188.214.218.34 +188.214.219.48 +188.214.219.103 +188.214.219.142 +188.214.219.206 +188.214.219.248 +188.214.220.225 +188.214.221.32 +188.214.221.78 +188.214.222.47 +188.214.223.109 +188.214.223.160 +188.214.223.232 +188.215.140.46 +188.215.140.220 +188.215.160.200 +188.215.162.36 +188.215.163.71 +188.215.164.144 +188.215.166.176 +188.215.167.35 +188.215.168.146 +188.215.168.152 +188.215.174.188 +188.215.213.76 +188.215.214.66 +188.240.212.9 +188.253.2.67 +188.253.2.115 +188.253.2.147 +188.253.2.210 +188.253.2.241 +188.253.2.250 +188.253.3.136 +188.253.3.185 +193.3.255.1 +193.3.255.2 +193.3.255.3 +193.3.255.4 +193.3.255.5 +193.3.255.6 +193.3.255.7 +193.3.255.8 +193.3.255.9 +193.3.255.12 +193.3.255.15 +193.3.255.16 +193.3.255.18 +193.3.255.19 +193.3.255.21 +193.3.255.22 +193.3.255.24 +193.3.255.25 +193.3.255.28 +193.3.255.29 +193.3.255.30 +193.3.255.31 +193.3.255.32 +193.3.255.33 +193.3.255.34 +193.3.255.35 +193.3.255.36 +193.3.255.37 +193.3.255.38 +193.3.255.39 +193.3.255.40 +193.3.255.41 +193.3.255.42 +193.3.255.43 +193.3.255.44 +193.3.255.45 +193.3.255.46 +193.3.255.47 +193.3.255.48 +193.3.255.49 +193.3.255.50 +193.3.255.51 +193.3.255.52 +193.3.255.53 +193.3.255.54 +193.3.255.55 +193.3.255.56 +193.3.255.57 +193.3.255.59 +193.3.255.60 +193.3.255.61 +193.3.255.62 +193.3.255.63 +193.3.255.65 +193.3.255.66 +193.3.255.67 +193.3.255.68 +193.3.255.69 +193.3.255.70 +193.3.255.71 +193.3.255.72 +193.3.255.73 +193.3.255.74 +193.3.255.75 +193.3.255.76 +193.3.255.77 +193.3.255.79 +193.3.255.80 +193.3.255.81 +193.3.255.82 +193.3.255.83 +193.3.255.84 +193.3.255.85 +193.3.255.86 +193.3.255.87 +193.3.255.88 +193.3.255.89 +193.3.255.90 +193.3.255.91 +193.3.255.92 +193.3.255.93 +193.3.255.95 +193.3.255.96 +193.3.255.98 +193.3.255.99 +193.3.255.100 +193.3.255.102 +193.3.255.103 +193.3.255.104 +193.3.255.105 +193.3.255.106 +193.3.255.107 +193.3.255.108 +193.3.255.109 +193.3.255.110 +193.3.255.111 +193.3.255.112 +193.3.255.113 +193.3.255.114 +193.3.255.115 +193.3.255.116 +193.3.255.117 +193.3.255.118 +193.3.255.119 +193.3.255.120 +193.3.255.121 +193.3.255.122 +193.3.255.123 +193.3.255.124 +193.3.255.125 +193.3.255.126 +193.3.255.128 +193.3.255.129 +193.3.255.130 +193.3.255.131 +193.3.255.133 +193.3.255.134 +193.3.255.135 +193.3.255.137 +193.3.255.139 +193.3.255.140 +193.3.255.141 +193.3.255.142 +193.3.255.143 +193.3.255.144 +193.3.255.145 +193.3.255.146 +193.3.255.147 +193.3.255.148 +193.3.255.149 +193.3.255.150 +193.3.255.151 +193.3.255.152 +193.3.255.153 +193.3.255.154 +193.3.255.155 +193.3.255.156 +193.3.255.157 +193.3.255.158 +193.3.255.160 +193.3.255.161 +193.3.255.164 +193.3.255.165 +193.3.255.166 +193.3.255.167 +193.3.255.168 +193.3.255.169 +193.3.255.170 +193.3.255.171 +193.3.255.172 +193.3.255.174 +193.3.255.175 +193.3.255.176 +193.3.255.177 +193.3.255.178 +193.3.255.179 +193.3.255.180 +193.3.255.182 +193.3.255.184 +193.3.255.185 +193.3.255.186 +193.3.255.187 +193.3.255.188 +193.3.255.191 +193.3.255.192 +193.3.255.193 +193.3.255.197 +193.3.255.198 +193.3.255.199 +193.3.255.200 +193.3.255.201 +193.3.255.202 +193.3.255.203 +193.3.255.204 +193.3.255.205 +193.3.255.206 +193.3.255.207 +193.3.255.208 +193.3.255.209 +193.3.255.210 +193.3.255.211 +193.3.255.212 +193.3.255.213 +193.3.255.214 +193.3.255.216 +193.3.255.217 +193.3.255.218 +193.3.255.219 +193.3.255.220 +193.3.255.221 +193.3.255.222 +193.3.255.223 +193.3.255.224 +193.3.255.225 +193.3.255.226 +193.3.255.227 +193.3.255.228 +193.3.255.229 +193.3.255.230 +193.3.255.231 +193.3.255.232 +193.3.255.233 +193.3.255.234 +193.3.255.235 +193.3.255.236 +193.3.255.237 +193.3.255.238 +193.3.255.239 +193.3.255.240 +193.3.255.241 +193.3.255.242 +193.3.255.243 +193.3.255.244 +193.3.255.245 +193.3.255.246 +193.3.255.248 +193.3.255.249 +193.3.255.250 +193.3.255.251 +193.3.255.252 +193.3.255.253 +193.5.44.24 +193.5.44.96 +193.5.44.201 +193.19.144.9 +193.19.144.10 +193.22.20.2 +193.24.103.1 +193.24.103.2 +193.35.230.2 +193.35.230.9 +193.35.230.34 +193.37.37.2 +193.37.37.3 +193.37.37.248 +193.39.9.66 +193.39.9.101 +193.39.9.115 +193.56.59.10 +193.56.61.140 +193.56.61.164 +193.56.107.107 +193.56.107.180 +193.56.107.196 +193.56.107.236 +193.56.107.238 +193.56.118.24 +193.56.118.26 +193.56.118.33 +193.56.118.126 +193.56.118.199 +193.58.119.39 +193.84.255.67 +193.104.29.50 +193.105.6.203 +193.105.234.5 +193.105.234.74 +193.105.234.122 +193.105.234.141 +193.105.234.153 +193.107.44.16 +193.107.44.19 +193.107.44.25 +193.107.44.61 +193.111.234.83 +193.111.234.117 +193.111.234.145 +193.111.234.205 +193.111.234.217 +193.111.235.31 +193.111.235.42 +193.111.235.91 +193.111.235.95 +193.111.235.231 +193.111.235.232 +193.111.235.235 +193.111.235.236 +193.111.236.136 +193.111.236.139 +193.111.236.180 +193.134.100.74 +193.134.100.108 +193.134.100.243 +193.134.101.67 +193.134.101.132 +193.141.64.6 +193.141.64.75 +193.141.64.79 +193.141.64.101 +193.141.64.106 +193.141.64.113 +193.141.64.135 +193.141.64.156 +193.141.64.163 +193.141.64.188 +193.141.64.236 +193.141.65.130 +193.148.65.41 +193.148.65.69 +193.148.65.240 +193.148.65.244 +193.148.66.21 +193.148.66.165 +193.148.67.117 +193.150.66.62 +193.150.66.134 +193.151.132.30 +193.151.137.70 +193.151.145.132 +193.151.153.134 +193.151.154.156 +193.151.157.155 +193.151.157.157 +193.151.157.158 +193.151.157.175 +193.151.157.190 +193.151.157.191 +193.151.157.192 +193.151.157.193 +193.151.159.229 +193.151.159.248 +193.176.97.103 +193.176.97.128 +193.176.97.141 +193.177.242.20 +193.178.200.20 +193.186.32.83 +193.186.32.102 +193.186.32.158 +193.186.32.226 +193.186.215.9 +193.200.148.10 +193.200.148.17 +193.200.148.48 +193.200.148.55 +193.200.148.58 +193.200.148.68 +193.200.148.149 +193.200.148.151 +193.200.148.162 +193.200.148.186 +193.200.148.197 +193.200.148.205 +193.201.192.250 +193.228.90.29 +193.228.90.30 +193.228.90.96 +193.228.90.118 +193.228.90.138 +193.228.90.153 +193.228.91.26 +193.228.91.27 +193.228.91.153 +193.228.136.128 +193.228.136.133 +193.228.136.138 +193.228.136.139 +193.228.136.140 +193.228.136.144 +193.228.136.145 +193.228.136.151 +193.228.136.155 +193.228.136.158 +193.228.136.192 +193.228.136.193 +193.228.136.204 +193.228.136.209 +193.228.136.210 +193.228.136.215 +193.228.136.218 +193.228.136.219 +193.228.136.220 +193.228.136.221 +193.228.136.223 +193.228.168.52 +193.228.168.162 +193.228.168.234 +193.228.169.10 +193.228.169.21 +193.228.169.25 +193.242.125.66 +193.246.200.1 +193.246.200.33 +193.246.200.34 +193.246.200.65 +193.246.200.81 +193.246.200.82 +193.246.200.114 +193.246.200.117 +193.246.200.119 +193.246.200.120 +193.246.200.124 +193.246.200.125 +193.246.200.126 +193.246.201.46 +194.1.155.9 +194.1.155.54 +194.5.175.112 +194.5.175.159 +194.5.175.225 +194.5.176.42 +194.5.177.1 +194.5.179.1 +194.5.188.44 +194.5.188.81 +194.5.188.109 +194.5.188.160 +194.5.195.19 +194.5.195.97 +194.5.195.166 +194.5.195.189 +194.5.205.51 +194.5.205.159 +194.5.205.227 +194.5.205.241 +194.9.57.32 +194.9.57.119 +194.9.57.148 +194.9.57.160 +194.9.57.167 +194.9.57.186 +194.9.57.199 +194.9.57.217 +194.9.80.21 +194.9.80.35 +194.9.80.38 +194.9.80.56 +194.9.80.84 +194.9.80.125 +194.9.80.127 +194.26.117.37 +194.26.117.40 +194.26.117.57 +194.26.117.68 +194.31.108.22 +194.31.108.45 +194.31.108.250 +194.31.108.251 +194.31.194.184 +194.31.194.203 +194.33.105.11 +194.33.105.12 +194.33.105.53 +194.33.105.96 +194.33.105.185 +194.33.107.242 +194.34.163.55 +194.36.174.30 +194.36.174.157 +194.36.174.251 +194.39.254.54 +194.41.48.21 +194.48.198.50 +194.48.198.204 +194.53.118.3 +194.53.119.3 +194.53.122.41 +194.53.122.61 +194.53.122.70 +194.53.122.75 +194.53.122.76 +194.53.122.83 +194.53.122.101 +194.53.122.119 +194.53.122.123 +194.53.122.126 +194.53.122.141 +194.53.122.150 +194.53.122.178 +194.53.122.248 +194.56.148.5 +194.56.148.129 +194.56.148.170 +194.56.148.177 +194.56.148.225 +194.56.148.235 +194.56.148.243 +194.59.170.3 +194.59.170.235 +194.59.171.12 +194.59.171.32 +194.59.171.71 +194.59.214.78 +194.59.214.89 +194.59.214.254 +194.59.215.114 +194.60.209.10 +194.60.209.11 +194.60.209.12 +194.60.209.14 +194.60.209.26 +194.60.209.98 +194.60.211.105 +194.60.211.125 +194.60.230.60 +194.60.230.118 +194.60.231.14 +194.60.231.55 +194.60.231.73 +194.60.231.153 +194.60.231.173 +194.60.231.226 +194.60.231.229 +194.62.17.34 +194.62.17.171 +194.62.17.246 +194.62.43.52 +194.62.43.119 +194.62.43.147 +194.62.43.173 +194.107.116.115 +194.107.116.116 +194.107.116.117 +194.107.116.118 +194.107.116.254 +194.146.151.21 +194.146.239.49 +194.146.239.72 +194.146.239.73 +194.147.142.11 +194.147.142.226 +194.147.167.48 +194.147.167.85 +194.147.167.221 +194.147.222.122 +194.150.68.0 +194.150.68.98 +194.150.68.101 +194.150.68.128 +194.150.68.144 +194.150.68.145 +194.150.68.147 +194.150.68.148 +194.150.68.149 +194.150.68.150 +194.150.68.151 +194.150.68.152 +194.150.68.153 +194.150.68.156 +194.150.68.158 +194.150.68.199 +194.150.68.207 +194.150.68.248 +194.150.68.249 +194.150.68.250 +194.150.68.251 +194.150.69.148 +194.150.69.159 +194.150.69.181 +194.150.69.197 +194.150.69.198 +194.150.69.199 +194.150.70.38 +194.150.70.44 +194.150.70.45 +194.150.70.46 +194.150.70.47 +194.150.70.55 +194.150.70.91 +194.150.70.180 +194.150.71.17 +194.150.71.37 +194.150.71.45 +194.150.71.57 +194.150.71.58 +194.150.71.174 +194.150.71.234 +194.150.71.251 +194.156.140.111 +194.156.140.167 +194.180.11.149 +194.180.11.197 +194.180.11.203 +194.180.11.252 +194.180.224.21 +194.225.24.15 +194.225.24.113 +194.225.26.41 +194.225.50.88 +194.225.70.5 +194.225.70.83 +194.225.101.1 +194.225.101.2 +194.225.101.3 +194.225.101.5 +194.225.101.7 +194.225.101.9 +194.225.101.11 +194.225.101.12 +194.225.101.13 +194.225.101.14 +194.225.101.15 +194.225.101.16 +194.225.101.17 +194.225.101.18 +194.225.101.19 +194.225.101.20 +194.225.101.21 +194.225.101.22 +194.225.101.24 +194.225.101.26 +194.225.101.27 +194.225.101.32 +194.225.116.10 +194.225.136.254 +194.225.230.72 +195.2.234.254 +195.8.102.50 +195.18.10.15 +195.26.27.158 +195.28.11.12 +195.28.11.56 +195.28.11.100 +195.28.11.107 +195.28.11.126 +195.28.11.227 +195.62.4.28 +195.62.4.29 +195.88.189.5 +195.88.189.18 +195.88.189.33 +195.88.189.38 +195.88.189.56 +195.88.189.67 +195.88.189.85 +195.88.189.89 +195.88.189.103 +195.88.189.105 +195.88.189.119 +195.88.189.144 +195.88.189.153 +195.88.189.189 +195.88.189.195 +195.110.38.130 +195.110.38.233 +195.114.8.145 +195.114.8.146 +195.114.8.147 +195.114.8.148 +195.114.8.149 +195.114.8.150 +195.114.8.151 +195.177.255.7 +195.177.255.92 +195.177.255.133 +195.177.255.138 +195.177.255.149 +195.177.255.198 +195.177.255.211 +195.181.36.29 +195.181.36.41 +195.181.36.63 +195.181.36.80 +195.181.37.11 +195.181.37.49 +195.181.37.75 +195.181.37.79 +195.181.37.190 +195.181.38.44 +195.181.38.48 +195.181.38.56 +195.181.38.128 +195.181.38.211 +195.181.38.234 +195.181.39.40 +195.181.40.13 +195.181.40.70 +195.181.41.140 +195.181.42.99 +195.181.42.100 +195.181.42.101 +195.181.42.102 +195.181.42.103 +195.181.42.105 +195.181.42.106 +195.181.42.107 +195.181.42.109 +195.181.42.110 +195.181.57.219 +195.181.58.29 +195.181.58.63 +195.181.59.49 +195.181.67.97 +195.181.67.208 +195.181.68.14 +195.181.68.235 +195.181.69.11 +195.181.69.69 +195.181.69.251 +195.181.70.117 +195.181.71.73 +195.181.74.147 +195.181.75.174 +195.181.76.140 +195.181.77.166 +195.181.78.111 +195.181.78.163 +195.181.79.112 +195.181.79.121 +195.181.79.153 +195.181.80.176 +195.181.81.96 +195.181.82.25 +195.181.82.183 +195.181.83.149 +195.181.85.207 +195.181.88.31 +195.181.88.147 +195.181.88.219 +195.181.88.223 +195.181.89.140 +195.181.94.39 +195.181.110.185 +195.181.112.94 +195.181.112.252 +195.181.113.38 +195.181.113.53 +195.181.113.189 +195.181.113.219 +195.181.113.255 +195.181.114.59 +195.181.114.81 +195.181.114.107 +195.181.114.196 +195.181.114.224 +195.181.117.163 +195.181.118.41 +195.181.118.57 +195.181.119.171 +195.181.119.186 +195.191.44.6 +195.191.44.7 +195.191.44.20 +195.191.44.70 +195.191.44.98 +195.191.44.115 +195.191.44.142 +195.191.44.154 +195.191.44.161 +195.191.44.170 +195.191.44.176 +195.191.44.191 +195.191.44.207 +195.191.44.213 +195.191.44.214 +195.191.44.254 +195.191.44.255 +195.191.45.12 +195.191.45.14 +195.191.45.26 +195.191.45.44 +195.191.45.45 +195.191.45.47 +195.191.45.61 +195.191.45.75 +195.191.45.88 +195.191.45.130 +195.191.45.149 +195.191.45.151 +195.191.45.155 +195.191.45.179 +195.191.45.189 +195.191.45.241 +195.191.45.250 +195.211.44.20 +195.211.44.25 +195.211.44.164 +195.211.44.165 +195.211.44.166 +195.211.44.175 +195.211.45.150 +195.211.45.222 +195.211.45.228 +195.211.46.63 +195.211.46.208 +195.211.46.243 +195.211.46.244 +195.211.47.66 +195.211.47.135 +195.211.47.195 +195.214.235.6 +195.214.235.27 +195.214.235.115 +195.214.235.120 +195.214.235.140 +195.214.235.173 +195.214.235.174 +195.214.235.208 +195.226.223.12 +195.226.223.27 +195.226.223.28 +195.226.223.29 +195.226.223.30 +195.226.223.214 +195.230.107.93 +195.238.231.97 +195.238.240.83 +195.238.240.84 +195.238.240.89 +195.245.70.1 +195.245.70.65 +212.16.68.41 +212.16.68.124 +212.16.75.253 +212.16.77.19 +212.16.77.20 +212.16.77.21 +212.16.77.22 +212.16.84.128 +212.16.86.17 +212.16.86.86 +212.16.86.112 +212.16.86.137 +212.16.86.139 +212.18.108.187 +212.18.108.235 +212.23.201.166 +212.23.201.238 +212.23.216.12 +212.23.216.99 +212.23.216.116 +212.23.216.123 +212.23.216.149 +212.33.195.77 +212.33.195.84 +212.33.195.201 +212.33.197.9 +212.33.198.66 +212.33.198.98 +212.33.198.245 +212.33.199.2 +212.33.201.73 +212.33.201.227 +212.33.202.146 +212.33.202.180 +212.33.204.94 +212.33.205.10 +212.33.205.59 +212.33.205.72 +212.33.205.148 +212.33.206.9 +212.33.206.21 +212.33.206.22 +212.33.206.23 +212.33.206.24 +212.33.206.25 +212.33.206.27 +212.33.206.181 +212.33.206.185 +212.33.206.186 +212.33.206.188 +212.33.206.189 +212.33.206.190 +212.33.206.191 +212.33.212.180 +212.33.213.154 +212.33.215.66 +212.33.216.151 +212.33.216.213 +212.33.218.29 +212.33.218.62 +212.33.218.134 +212.33.219.209 +212.33.222.251 +212.33.223.56 +212.33.223.66 +212.80.8.8 +212.80.8.58 +212.80.8.152 +212.80.10.78 +212.80.10.91 +212.80.20.98 +212.80.20.103 +212.80.24.14 +212.86.64.2 +212.86.64.68 +212.86.64.79 +212.86.64.83 +212.86.64.101 +212.86.64.102 +212.86.64.103 +212.86.64.105 +212.86.65.25 +212.86.65.40 +212.86.65.60 +212.86.65.126 +212.86.72.0 +212.86.72.11 +212.86.72.46 +212.86.72.56 +212.86.72.72 +212.86.72.91 +212.86.72.123 +212.86.72.217 +212.86.73.4 +212.86.73.41 +212.86.73.56 +212.86.73.97 +212.86.73.173 +212.86.73.175 +212.86.73.200 +212.86.73.217 +212.86.73.220 +212.86.74.0 +212.86.74.6 +212.86.74.10 +212.86.74.49 +212.86.74.60 +212.86.74.67 +212.86.74.76 +212.86.74.79 +212.86.74.158 +212.86.74.160 +212.86.74.188 +212.86.74.194 +212.86.74.216 +212.86.74.218 +212.86.74.225 +212.86.75.0 +212.86.75.36 +212.86.75.42 +212.86.75.52 +212.86.75.71 +212.86.75.94 +212.86.75.96 +212.86.75.160 +212.86.75.205 +212.86.75.240 +212.86.75.243 +212.86.76.41 +212.86.79.177 +212.86.79.179 +212.86.79.183 +212.86.79.186 +212.86.79.187 +212.86.82.225 +212.86.87.251 +212.86.93.93 +212.86.94.20 +212.86.94.74 +212.86.94.113 +212.86.94.144 +212.86.94.207 +212.108.125.92 +212.120.192.236 +212.120.192.254 +212.120.193.20 +212.120.193.241 +212.120.194.72 +212.120.196.90 +212.120.197.31 +212.120.198.232 +212.120.201.203 +212.120.203.88 +212.120.204.95 +212.120.210.84 +212.120.218.141 +212.120.219.9 +212.120.219.153 +212.120.221.10 +212.120.221.141 +212.120.222.106 +212.120.223.163 +212.120.223.165 +213.108.240.139 +213.108.240.153 +213.108.240.206 +213.108.241.134 +213.108.242.161 +213.108.242.193 +213.108.242.225 +213.108.242.227 +213.108.242.245 +213.109.199.3 +213.109.248.105 +213.109.250.84 +213.109.252.154 +213.109.253.62 +213.109.253.187 +213.109.254.199 +213.176.4.6 +213.176.6.230 +213.176.7.117 +213.176.28.53 +213.176.28.147 +213.176.29.10 +213.176.69.211 +213.176.120.16 +213.176.120.74 +213.176.122.5 +213.176.122.28 +213.176.122.56 +213.176.124.10 +213.176.124.55 +213.176.127.130 +213.177.176.3 +213.177.176.103 +213.177.176.107 +213.177.176.112 +213.177.178.212 +213.195.22.51 +213.195.22.67 +213.195.22.109 +213.195.22.122 +213.195.56.45 +213.195.58.44 +213.195.58.177 +213.195.59.0 +213.195.59.10 +213.195.59.11 +213.195.59.179 +213.195.60.19 +213.195.61.119 +213.195.61.124 +213.195.61.254 +213.195.62.126 +213.207.195.118 +213.207.195.194 +213.207.196.30 +213.207.196.114 +213.207.196.138 +213.207.196.214 +213.207.198.22 +213.207.198.91 +213.207.198.180 +213.207.198.226 +213.207.200.114 +213.207.200.115 +213.207.200.162 +213.207.200.186 +213.207.200.226 +213.207.202.44 +213.207.202.94 +213.207.202.235 +213.207.202.242 +213.207.203.2 +213.207.203.30 +213.207.203.106 +213.207.203.122 +213.207.203.123 +213.207.203.124 +213.207.204.70 +213.207.204.104 +213.207.204.107 +213.207.204.154 +213.207.204.186 +213.207.204.226 +213.207.204.242 +213.207.250.159 +213.207.250.218 +213.232.124.210 +213.232.124.250 +213.232.127.18 +213.232.127.28 +213.232.127.30 +213.233.177.103 +213.233.178.35 +213.233.181.18 +213.233.181.26 +213.233.181.27 +213.233.181.106 +213.233.182.146 +213.233.182.229 +213.233.184.40 +217.11.18.50 +217.11.18.51 +217.11.18.52 +217.11.18.53 +217.11.18.54 +217.11.18.58 +217.11.18.59 +217.11.18.61 +217.11.18.62 +217.11.18.114 +217.11.18.178 +217.11.18.179 +217.11.18.180 +217.11.18.183 +217.11.18.189 +217.11.22.34 +217.11.24.162 +217.11.24.249 +217.11.26.6 +217.11.27.138 +217.11.27.139 +217.11.27.140 +217.11.27.211 +217.11.27.213 +217.11.28.74 +217.11.28.138 +217.11.28.237 +217.11.29.185 +217.11.29.189 +217.11.30.34 +217.11.30.182 +217.24.144.122 +217.24.145.5 +217.24.146.16 +217.24.147.76 +217.24.148.1 +217.24.151.89 +217.24.151.109 +217.24.151.141 +217.24.153.18 +217.24.155.149 +217.24.155.244 +217.24.157.131 +217.25.48.63 +217.25.56.90 +217.26.222.235 +217.60.238.99 +217.60.243.183 +217.60.255.67 +217.60.255.231 +217.60.255.233 +217.60.255.234 +217.60.255.235 +217.60.255.236 +217.60.255.237 +217.60.255.238 +217.66.195.26 +217.66.200.206 +217.66.200.219 +217.66.201.115 +217.66.201.116 +217.66.201.118 +217.66.203.166 +217.66.207.1 +217.66.212.246 +217.66.213.5 +217.66.213.105 +217.66.213.133 +217.66.213.134 +217.66.213.145 +217.66.215.86 +217.66.215.138 +217.66.221.21 +217.114.40.23 +217.114.40.244 +217.114.46.130 +217.144.104.1 +217.144.104.45 +217.144.104.104 +217.144.104.115 +217.144.104.124 +217.144.104.252 +217.144.105.1 +217.144.105.92 +217.144.106.1 +217.144.106.42 +217.144.106.65 +217.144.106.97 +217.144.106.98 +217.144.106.161 +217.144.106.213 +217.144.107.1 +217.144.107.122 +217.144.107.165 +217.144.107.206 +217.146.208.2 +217.146.209.178 +217.146.209.179 +217.146.215.100 +217.146.217.247 +217.146.218.141 +217.146.218.142 +217.171.145.233 +217.171.148.147 +217.171.148.190 +217.171.148.236 +217.171.149.81 +217.171.149.188 +217.171.149.201 +217.171.150.82 +217.171.150.242 +217.171.151.3 +217.171.151.16 +217.172.98.11 +217.172.98.73 +217.172.98.107 +217.172.98.130 +217.172.98.134 +217.172.104.161 +217.172.108.198 +217.172.112.36 +217.172.113.123 +217.172.113.235 +217.172.114.74 +217.172.114.92 +217.172.116.133 +217.172.117.38 +217.172.121.49 +217.172.121.115 +217.172.121.116 +217.172.121.210 +217.172.122.154 +217.172.122.155 +217.172.122.156 +217.172.122.157 +217.172.122.158 +217.172.126.1 +217.172.126.20 +217.172.126.209 +217.172.126.215 +217.172.126.234 +217.172.126.237 +217.172.126.253 +217.172.127.43 +217.172.127.77 +217.172.127.78 +217.172.127.81 +217.172.127.93 +217.172.127.101 +217.172.127.118 +217.172.127.136 +217.172.127.186 +217.172.127.189 +217.198.190.41 +217.198.190.96 +217.218.3.51 +217.218.3.163 +217.218.3.233 +217.218.42.1 +217.218.42.21 +217.218.42.22 +217.218.42.246 +217.218.44.189 +217.218.46.34 +217.218.46.35 +217.218.46.36 +217.218.51.78 +217.218.54.250 +217.218.56.227 +217.218.56.228 +217.218.56.235 +217.218.62.62 +217.218.74.14 +217.218.77.131 +217.218.81.137 +217.218.82.32 +217.218.82.33 +217.218.82.34 +217.218.82.102 +217.218.83.138 +217.218.85.245 +217.218.87.29 +217.218.87.51 +217.218.91.8 +217.218.100.216 +217.218.100.239 +217.218.103.120 +217.218.108.177 +217.218.113.130 +217.218.114.240 +217.218.114.241 +217.218.114.242 +217.218.114.244 +217.218.114.245 +217.218.114.247 +217.218.114.250 +217.218.114.253 +217.218.115.42 +217.218.115.43 +217.218.115.44 +217.218.115.46 +217.218.115.178 +217.218.115.179 +217.218.120.2 +217.218.120.216 +217.218.121.54 +217.218.125.21 +217.218.126.164 +217.218.134.57 +217.218.134.58 +217.218.134.59 +217.218.134.60 +217.218.134.61 +217.218.134.62 +217.218.135.87 +217.218.136.230 +217.218.139.255 +217.218.159.68 +217.218.162.62 +217.218.164.77 +217.218.180.42 +217.218.180.225 +217.218.192.210 +217.218.192.211 +217.218.195.26 +217.218.196.183 +217.218.201.16 +217.218.201.24 +217.218.201.49 +217.218.201.101 +217.218.201.113 +217.218.201.140 +217.218.201.175 +217.218.201.184 +217.218.201.198 +217.218.201.213 +217.218.204.114 +217.218.204.115 +217.218.204.117 +217.218.204.130 +217.218.204.138 +217.218.204.170 +217.218.206.34 +217.218.206.189 +217.218.212.136 +217.218.212.137 +217.218.213.13 +217.218.219.105 +217.218.219.201 +217.218.222.187 +217.218.223.154 +217.218.224.2 +217.218.224.156 +217.218.224.157 +217.218.224.158 +217.218.224.210 +217.218.224.211 +217.218.224.213 +217.218.224.230 +217.218.225.190 +217.218.226.98 +217.218.226.131 +217.218.227.2 +217.218.227.3 +217.218.227.4 +217.218.227.5 +217.218.227.6 +217.218.227.10 +217.218.227.50 +217.218.227.245 +217.218.227.246 +217.218.235.138 +217.218.236.2 +217.218.236.68 +217.218.236.70 +217.218.236.76 +217.218.236.85 +217.218.236.86 +217.218.236.88 +217.218.236.89 +217.218.236.162 +217.218.236.171 +217.218.237.26 +217.218.237.27 +217.218.237.28 +217.218.238.76 +217.218.242.24 +217.218.242.75 +217.218.243.191 +217.218.243.204 +217.218.243.207 +217.218.243.249 +217.218.245.13 +217.218.248.170 +217.218.249.17 +217.218.249.63 +217.218.249.240 +217.218.250.16 +217.218.250.145 +217.218.250.172 +217.218.252.33 +217.218.252.64 +217.218.252.66 +217.218.253.93 +217.218.253.110 +217.219.1.1 +217.219.14.162 +217.219.14.164 +217.219.16.1 +217.219.21.26 +217.219.26.145 +217.219.27.58 +217.219.27.65 +217.219.29.186 +217.219.29.202 +217.219.29.203 +217.219.29.250 +217.219.30.153 +217.219.30.156 +217.219.31.90 +217.219.33.127 +217.219.34.6 +217.219.34.15 +217.219.34.57 +217.219.34.66 +217.219.34.95 +217.219.35.3 +217.219.35.7 +217.219.35.10 +217.219.35.14 +217.219.35.32 +217.219.35.49 +217.219.35.55 +217.219.35.63 +217.219.35.69 +217.219.35.73 +217.219.35.90 +217.219.35.129 +217.219.35.134 +217.219.35.171 +217.219.35.183 +217.219.35.196 +217.219.35.201 +217.219.35.254 +217.219.37.3 +217.219.39.74 +217.219.39.75 +217.219.39.76 +217.219.39.77 +217.219.39.122 +217.219.39.234 +217.219.42.135 +217.219.45.2 +217.219.45.3 +217.219.45.5 +217.219.45.138 +217.219.45.178 +217.219.45.210 +217.219.45.222 +217.219.47.66 +217.219.47.122 +217.219.47.138 +217.219.47.139 +217.219.47.146 +217.219.47.170 +217.219.47.186 +217.219.47.194 +217.219.47.195 +217.219.64.66 +217.219.64.117 +217.219.64.196 +217.219.67.166 +217.219.67.179 +217.219.67.221 +217.219.68.169 +217.219.68.190 +217.219.68.250 +217.219.70.61 +217.219.70.122 +217.219.71.242 +217.219.72.10 +217.219.72.18 +217.219.72.19 +217.219.72.98 +217.219.72.102 +217.219.72.111 +217.219.72.115 +217.219.72.116 +217.219.72.119 +217.219.72.121 +217.219.72.123 +217.219.72.194 +217.219.72.199 +217.219.72.200 +217.219.72.203 +217.219.72.206 +217.219.73.193 +217.219.77.69 +217.219.77.114 +217.219.77.194 +217.219.77.250 +217.219.78.151 +217.219.80.154 +217.219.80.241 +217.219.81.2 +217.219.81.3 +217.219.81.18 +217.219.81.74 +217.219.81.249 +217.219.82.110 +217.219.82.218 +217.219.82.226 +217.219.83.146 +217.219.84.83 +217.219.85.6 +217.219.86.98 +217.219.86.99 +217.219.86.100 +217.219.87.2 +217.219.87.26 +217.219.87.42 +217.219.87.122 +217.219.87.154 +217.219.87.170 +217.219.90.87 +217.219.106.144 +217.219.106.146 +217.219.113.164 +217.219.113.180 +217.219.121.156 +217.219.124.78 +217.219.124.96 +217.219.124.97 +217.219.124.98 +217.219.124.99 +217.219.124.100 +217.219.124.101 +217.219.124.102 +217.219.124.103 +217.219.124.104 +217.219.124.105 +217.219.124.106 +217.219.124.107 +217.219.124.108 +217.219.124.109 +217.219.124.110 +217.219.124.111 +217.219.129.19 +217.219.129.213 +217.219.129.215 +217.219.129.247 +217.219.131.14 +217.219.131.116 +217.219.131.130 +217.219.132.14 +217.219.132.22 +217.219.132.65 +217.219.132.75 +217.219.132.88 +217.219.132.104 +217.219.133.86 +217.219.135.137 +217.219.136.77 +217.219.136.156 +217.219.138.206 +217.219.141.170 +217.219.143.83 +217.219.143.87 +217.219.146.81 +217.219.146.201 +217.219.147.89 +217.219.147.90 +217.219.148.34 +217.219.148.209 +217.219.148.217 +217.219.149.137 +217.219.149.201 +217.219.150.49 +217.219.155.36 +217.219.157.65 +217.219.157.88 +217.219.157.121 +217.219.158.50 +217.219.158.113 +217.219.161.172 +217.219.161.190 +217.219.162.178 +217.219.163.1 +217.219.163.3 +217.219.163.10 +217.219.163.11 +217.219.163.13 +217.219.163.14 +217.219.163.15 +217.219.163.16 +217.219.163.17 +217.219.163.18 +217.219.163.19 +217.219.163.21 +217.219.163.23 +217.219.163.24 +217.219.163.25 +217.219.163.26 +217.219.163.28 +217.219.163.30 +217.219.163.31 +217.219.163.32 +217.219.163.33 +217.219.163.34 +217.219.163.35 +217.219.163.36 +217.219.163.37 +217.219.163.38 +217.219.163.39 +217.219.163.40 +217.219.163.41 +217.219.163.42 +217.219.163.43 +217.219.163.44 +217.219.163.47 +217.219.163.48 +217.219.163.49 +217.219.163.50 +217.219.163.51 +217.219.163.52 +217.219.163.54 +217.219.163.55 +217.219.163.56 +217.219.163.57 +217.219.163.59 +217.219.163.60 +217.219.163.62 +217.219.163.63 +217.219.163.64 +217.219.163.65 +217.219.163.66 +217.219.163.67 +217.219.163.68 +217.219.163.69 +217.219.163.71 +217.219.163.73 +217.219.163.74 +217.219.163.75 +217.219.163.76 +217.219.163.77 +217.219.163.80 +217.219.163.81 +217.219.163.82 +217.219.163.85 +217.219.163.88 +217.219.163.90 +217.219.163.95 +217.219.163.96 +217.219.163.99 +217.219.163.100 +217.219.163.101 +217.219.163.102 +217.219.163.103 +217.219.163.104 +217.219.163.105 +217.219.163.110 +217.219.163.111 +217.219.163.121 +217.219.163.122 +217.219.163.123 +217.219.163.125 +217.219.163.126 +217.219.163.133 +217.219.163.134 +217.219.163.143 +217.219.163.150 +217.219.163.152 +217.219.163.153 +217.219.163.154 +217.219.163.155 +217.219.163.156 +217.219.163.157 +217.219.163.159 +217.219.163.160 +217.219.163.161 +217.219.163.164 +217.219.163.165 +217.219.163.166 +217.219.163.170 +217.219.163.171 +217.219.163.174 +217.219.163.180 +217.219.163.190 +217.219.163.200 +217.219.163.201 +217.219.163.210 +217.219.163.211 +217.219.163.222 +217.219.163.223 +217.219.163.252 +217.219.164.105 +217.219.164.106 +217.219.164.129 +217.219.164.194 +217.219.165.201 +217.219.166.3 +217.219.166.228 +217.219.166.229 +217.219.166.231 +217.219.166.235 +217.219.168.23 +217.219.169.161 +217.219.169.180 +217.219.172.106 +217.219.172.185 +217.219.172.238 +217.219.175.61 +217.219.175.66 +217.219.178.154 +217.219.179.5 +217.219.179.6 +217.219.179.18 +217.219.179.33 +217.219.179.61 +217.219.179.62 +217.219.181.228 +217.219.191.169 +217.219.192.117 +217.219.196.67 +217.219.196.114 +217.219.196.115 +217.219.196.116 +217.219.196.117 +217.219.196.170 +217.219.196.218 +217.219.196.226 +217.219.199.50 +217.219.202.234 +217.219.203.210 +217.219.206.144 +217.219.207.226 +217.219.209.125 +217.219.209.130 +217.219.216.123 +217.219.217.128 +217.219.217.141 +217.219.217.217 +217.219.217.249 +217.219.219.10 +217.219.219.137 +217.219.219.138 +217.219.219.145 +217.219.219.150 +217.219.222.76 +217.219.223.1 +217.219.223.2 +217.219.223.14 +217.219.223.106 +217.219.224.2 +217.219.224.162 +217.219.224.194 +217.219.225.2 +217.219.226.70 +217.219.227.34 +217.219.227.40 +217.219.228.36 +217.219.228.88 +217.219.228.130 +217.219.229.154 +217.219.229.155 +217.219.229.156 +217.219.229.157 +217.219.230.22 +217.219.230.221 +217.219.243.10 +217.219.244.6 +217.219.245.3 +217.219.245.14 +217.219.245.70 +217.219.245.95 +217.219.245.106 +217.219.245.136 +217.219.245.175 +217.219.245.191 +217.219.246.98 +217.219.247.99 +217.219.247.122 +2.144.2.111 +2.144.4.21 +2.144.5.134 +2.144.5.203 +2.144.6.52 +2.144.6.158 +2.144.20.43 +2.144.21.174 +2.144.22.179 +2.144.23.66 +2.144.247.175 +2.176.61.28 +2.176.193.50 +2.176.197.47 +2.176.199.118 +2.176.202.164 +2.176.205.179 +2.176.231.4 +2.176.232.223 +2.176.237.47 +2.176.239.230 +2.176.240.92 +2.176.243.141 +2.176.244.41 +2.176.245.72 +2.176.245.231 +2.176.246.166 +2.176.250.217 +2.176.255.77 +2.177.13.185 +2.177.14.0 +2.177.14.32 +2.177.14.43 +2.177.31.166 +2.177.36.242 +2.177.37.184 +2.177.41.202 +2.177.48.152 +2.177.51.194 +2.177.64.43 +2.177.68.25 +2.177.73.48 +2.177.73.79 +2.177.79.37 +2.177.83.219 +2.177.99.220 +2.177.100.226 +2.177.107.104 +2.177.113.124 +2.177.117.169 +2.177.118.6 +2.177.129.148 +2.177.130.105 +2.177.137.86 +2.177.142.148 +2.177.151.132 +2.177.151.156 +2.177.165.9 +2.177.166.150 +2.177.168.234 +2.177.171.89 +2.177.182.115 +2.177.185.122 +2.177.187.200 +2.177.197.115 +2.177.198.53 +2.177.199.51 +2.177.199.107 +2.177.199.245 +2.177.204.214 +2.177.211.79 +2.177.216.209 +2.177.223.128 +2.177.225.220 +2.177.226.55 +2.177.227.249 +2.177.228.177 +2.177.230.204 +2.177.231.85 +2.177.233.155 +2.177.240.13 +2.177.241.251 +2.177.242.157 +2.177.248.198 +2.177.249.33 +2.178.10.49 +2.178.26.120 +2.178.29.129 +2.178.33.172 +2.178.39.222 +2.178.47.252 +2.178.50.226 +2.178.68.197 +2.178.75.96 +2.178.82.205 +2.178.96.216 +2.178.97.192 +2.178.106.14 +2.178.165.235 +2.178.166.233 +2.178.178.132 +2.178.190.214 +2.178.196.1 +2.178.212.26 +2.178.213.139 +2.178.222.197 +2.178.224.182 +2.178.248.58 +2.178.248.241 +2.179.110.69 +2.179.111.38 +2.179.127.252 +2.179.165.153 +2.179.175.184 +2.179.232.32 +2.180.40.2 +2.180.160.254 +2.180.198.17 +2.180.203.21 +2.180.228.113 +2.181.165.5 +2.181.167.6 +2.181.194.140 +2.181.198.224 +2.181.241.244 +2.182.46.37 +2.182.56.10 +2.182.184.108 +2.182.185.9 +2.182.188.221 +2.182.197.16 +2.182.215.254 +2.182.245.1 +2.183.173.236 +2.183.203.2 +2.185.17.37 +2.185.18.125 +2.185.108.67 +2.185.109.142 +2.185.122.248 +2.185.126.202 +2.185.142.5 +2.185.228.204 +2.185.230.220 +2.185.247.106 +2.185.248.133 +2.185.249.127 +2.185.249.210 +2.185.251.86 +2.187.18.3 +2.187.140.164 +2.188.3.60 +2.188.168.129 +2.188.175.242 +2.189.1.0 +2.189.9.97 +2.189.11.159 +2.189.86.97 +2.189.145.17 +2.189.145.173 +2.189.157.249 +2.189.217.144 +2.189.219.152 +2.189.242.12 +2.190.3.235 +2.190.26.244 +2.190.28.221 +2.190.28.244 +2.190.29.197 +2.190.40.249 +2.190.49.235 +2.190.59.172 +2.190.60.13 +2.190.60.27 +2.190.61.4 +2.190.61.175 +2.190.62.132 +2.190.71.116 +2.190.72.84 +2.190.87.152 +2.190.95.36 +2.190.97.50 +2.190.103.243 +2.190.110.83 +2.190.117.30 +2.190.131.33 +2.190.140.44 +2.190.142.104 +2.190.157.58 +2.190.159.232 +2.190.168.235 +2.190.173.111 +2.190.174.81 +2.190.180.13 +2.190.180.245 +2.190.182.58 +2.190.189.139 +2.190.193.93 +2.190.205.31 +2.190.210.124 +2.190.212.127 +2.190.216.0 +2.190.222.0 +2.190.226.150 +2.190.234.39 +2.190.240.24 +2.191.5.220 +2.191.6.55 +2.191.11.21 +2.191.19.182 +2.191.21.202 +2.191.26.194 +2.191.34.72 +2.191.40.236 +2.191.42.139 +2.191.46.127 +2.191.49.238 +2.191.52.197 +2.191.54.134 +2.191.57.99 +2.191.59.64 +2.191.61.46 +2.191.69.203 +2.191.73.215 +2.191.79.184 +2.191.88.247 +2.191.99.161 +2.191.100.238 +2.191.103.229 +2.191.111.210 +2.191.115.176 +2.191.116.220 +5.10.248.174 +5.10.249.91 +5.42.223.178 +5.57.32.117 +5.57.32.181 +5.57.32.184 +5.57.39.168 +5.63.8.22 +5.74.7.121 +5.74.21.26 +5.74.26.180 +5.74.26.234 +5.74.38.29 +5.74.47.64 +5.74.52.237 +5.74.53.212 +5.74.56.152 +5.74.68.57 +5.74.69.171 +5.74.72.160 +5.74.76.182 +5.74.86.57 +5.74.90.172 +5.74.99.0 +5.74.101.93 +5.74.105.40 +5.74.109.253 +5.74.111.45 +5.74.113.74 +5.74.115.250 +5.74.133.146 +5.74.137.184 +5.74.139.166 +5.74.142.206 +5.74.160.227 +5.74.173.21 +5.74.175.47 +5.74.184.125 +5.74.188.152 +5.74.201.91 +5.74.219.145 +5.74.239.134 +5.74.239.234 +5.74.246.169 +5.144.129.5 +5.144.129.11 +5.144.129.32 +5.144.129.40 +5.144.129.63 +5.144.129.64 +5.144.129.66 +5.144.129.69 +5.144.129.110 +5.144.129.148 +5.144.129.151 +5.144.129.166 +5.144.129.216 +5.144.130.19 +5.144.130.26 +5.144.130.82 +5.144.130.96 +5.144.130.117 +5.144.130.159 +5.144.130.196 +5.144.130.214 +5.144.130.219 +5.144.130.245 +5.144.130.249 +5.144.131.245 +5.159.48.21 +5.159.48.40 +5.159.51.194 +5.159.51.207 +5.159.53.8 +5.160.40.6 +5.160.46.203 +5.160.55.3 +5.160.55.13 +5.160.79.248 +5.160.95.165 +5.160.96.210 +5.160.118.66 +5.160.186.19 +5.160.225.168 +5.160.242.53 +5.182.44.177 +5.190.86.86 +5.190.87.87 +5.198.161.51 +5.198.161.55 +5.200.134.206 +5.200.144.52 +5.200.150.169 +5.200.183.90 +5.200.186.226 +5.200.238.224 +5.201.165.192 +5.202.5.131 +5.202.50.82 +5.202.52.36 +5.202.67.5 +5.202.97.22 +5.232.125.241 +5.232.135.255 +5.233.57.47 +5.233.68.161 +5.233.183.139 +5.233.193.222 +5.234.3.0 +5.234.8.25 +5.234.9.41 +5.234.15.25 +5.234.17.120 +5.234.18.35 +5.234.18.94 +5.234.22.170 +5.234.28.171 +5.234.160.153 +5.234.198.228 +5.234.207.158 +5.234.208.42 +5.234.211.176 +5.235.163.209 +5.235.164.4 +5.235.166.97 +5.235.166.254 +5.235.168.38 +5.235.174.230 +5.235.176.185 +5.235.183.53 +5.235.198.109 +5.236.64.61 +5.236.75.164 +5.236.80.59 +5.236.117.196 +5.236.125.34 +5.236.193.186 +5.236.218.37 +5.236.224.25 +5.236.231.47 +5.236.248.172 +5.236.251.85 +5.237.171.184 +5.237.182.67 +5.237.184.184 +5.237.190.101 +5.237.200.102 +5.237.249.162 +5.238.77.204 +5.238.84.14 +5.238.128.102 +5.238.145.107 +5.238.162.247 +5.238.169.180 +5.238.171.106 +5.238.179.1 +5.238.187.194 +5.238.189.61 +5.238.192.15 +5.238.205.201 +5.238.213.205 +5.238.219.22 +5.238.225.98 +5.238.225.134 +5.238.226.108 +5.238.226.157 +5.238.229.48 +5.238.229.227 +5.238.236.60 +5.238.243.239 +5.239.10.166 +5.239.18.23 +5.239.32.209 +5.239.34.3 +5.239.43.82 +5.239.46.65 +5.239.53.249 +5.239.62.129 +5.239.77.105 +5.239.79.88 +5.239.81.68 +5.239.82.223 +5.239.84.107 +5.239.89.157 +5.239.92.43 +5.239.105.226 +5.239.106.140 +5.239.120.67 +31.7.72.178 +31.7.111.214 +31.47.40.31 +31.170.50.93 +31.171.222.195 +31.171.222.199 +31.184.140.207 +31.216.62.142 +37.32.10.23 +37.148.24.109 +37.148.46.201 +37.148.50.109 +37.148.51.63 +37.148.82.202 +37.148.82.225 +37.152.176.135 +37.152.191.247 +37.156.31.22 +37.202.155.93 +37.202.225.135 +37.202.225.137 +37.202.225.156 +37.202.225.160 +37.228.137.89 +37.255.173.82 +37.255.243.165 +45.65.59.252 +45.82.138.154 +45.89.236.235 +45.92.92.10 +45.92.92.18 +45.92.92.25 +45.92.92.41 +45.92.92.53 +45.92.92.69 +45.92.92.73 +45.92.92.76 +45.92.92.151 +45.92.92.175 +45.92.92.196 +45.92.94.92 +45.92.94.96 +45.135.243.138 +45.138.132.238 +45.138.133.43 +45.138.133.55 +45.138.133.59 +45.138.133.63 +45.138.133.146 +45.138.133.161 +45.138.134.19 +45.138.134.44 +45.138.134.81 +45.138.134.134 +45.138.134.142 +45.138.134.169 +45.138.134.176 +45.138.134.251 +45.138.135.179 +45.147.77.157 +45.149.79.40 +45.156.185.85 +45.159.113.35 +45.159.149.196 +45.159.151.14 +45.159.151.83 +45.159.151.137 +46.18.248.134 +46.100.5.51 +46.100.47.7 +46.100.59.39 +46.100.145.243 +46.100.177.40 +46.167.141.24 +46.209.67.146 +46.209.212.75 +46.245.1.100 +46.245.8.151 +46.245.10.1 +46.245.16.4 +46.245.19.220 +46.245.27.65 +46.245.34.19 +46.245.39.187 +46.245.40.151 +46.245.43.89 +46.245.45.43 +46.245.77.2 +46.245.86.27 +46.245.91.230 +46.245.115.37 +62.3.41.64 +62.3.41.95 +62.3.42.23 +62.60.133.3 +62.60.133.8 +62.60.133.28 +62.60.133.35 +62.60.133.46 +62.60.133.57 +62.60.133.60 +62.60.133.81 +62.60.133.82 +62.60.133.101 +62.60.133.129 +62.60.133.193 +62.60.133.199 +62.60.133.204 +62.60.163.226 +62.60.195.95 +62.60.201.19 +62.60.201.23 +62.60.201.91 +62.60.201.114 +62.60.201.129 +62.60.201.150 +62.60.201.181 +62.60.201.188 +62.60.201.237 +62.60.204.166 +62.60.205.96 +62.60.205.165 +62.60.205.190 +62.60.205.205 +62.60.205.206 +62.60.205.252 +62.60.206.155 +62.60.207.78 +62.60.211.198 +62.60.211.201 +62.60.214.83 +62.204.61.159 +62.220.117.130 +66.79.110.174 +77.81.144.148 +77.81.146.166 +77.81.152.206 +77.104.118.2 +77.237.67.13 +77.237.75.148 +77.237.83.30 +78.38.29.129 +78.38.182.201 +78.38.189.230 +78.39.62.36 +78.39.81.46 +78.39.122.99 +78.39.146.149 +78.39.147.33 +78.39.194.10 +78.39.224.155 +78.39.245.8 +78.39.248.252 +78.39.255.27 +78.109.202.101 +78.157.33.201 +78.157.35.39 +78.157.35.47 +78.157.35.96 +78.157.38.200 +78.157.40.143 +78.157.40.184 +78.157.40.224 +78.157.41.79 +78.157.43.64 +78.157.43.127 +78.157.45.32 +78.157.45.63 +78.157.45.143 +78.157.46.111 +78.157.50.16 +78.157.50.248 +78.157.52.8 +78.157.52.10 +78.157.57.159 +78.157.57.233 +78.157.58.47 +78.158.179.134 +79.127.125.126 +79.127.126.68 +79.127.127.55 +79.175.134.65 +79.175.167.4 +79.175.171.67 +79.175.187.131 +80.191.62.6 +80.191.65.174 +80.191.103.98 +80.191.110.242 +80.191.170.173 +80.210.27.243 +80.210.34.217 +80.210.136.8 +80.210.140.143 +80.210.142.120 +80.210.160.220 +80.210.168.80 +80.210.170.69 +80.210.171.8 +80.210.171.49 +80.210.177.84 +81.12.20.85 +81.12.54.213 +81.12.54.248 +81.12.104.84 +81.12.106.190 +81.12.108.142 +81.16.121.120 +81.16.124.109 +81.91.156.130 +82.99.206.97 +84.47.239.13 +84.241.0.112 +84.241.2.163 +84.241.7.245 +84.241.19.157 +84.241.28.59 +84.241.34.116 +84.241.36.70 +85.9.98.70 +85.9.122.167 +85.9.125.118 +85.133.182.163 +85.133.184.122 +85.133.187.22 +85.185.17.75 +85.185.17.115 +85.185.52.32 +85.185.54.129 +85.185.55.190 +85.185.75.108 +85.185.86.212 +85.185.205.28 +85.185.216.27 +85.208.253.58 +85.208.254.65 +86.104.32.35 +86.104.104.68 +86.105.41.35 +86.105.42.20 +86.105.47.95 +86.106.142.123 +86.107.148.140 +86.107.158.131 +87.107.102.4 +87.107.104.60 +87.236.211.12 +87.236.211.83 +87.248.130.22 +87.248.131.112 +87.248.131.176 +88.135.75.144 +89.32.196.33 +89.32.248.22 +89.32.251.75 +89.34.96.78 +89.37.103.20 +89.38.24.35 +89.42.199.197 +89.42.209.50 +89.42.210.237 +89.42.211.230 +89.43.216.130 +89.43.219.10 +89.43.227.128 +89.46.94.42 +89.46.94.164 +89.46.218.69 +89.46.218.109 +89.46.218.127 +89.46.219.82 +89.46.219.199 +89.144.131.138 +89.144.137.223 +89.144.137.233 +89.144.137.244 +89.144.137.247 +89.144.137.255 +89.144.138.156 +89.144.139.1 +89.144.139.7 +89.144.139.11 +89.144.139.25 +89.144.139.36 +89.144.139.60 +89.144.139.61 +89.144.139.69 +89.144.139.80 +89.144.139.81 +89.144.139.83 +89.144.139.99 +89.144.139.100 +89.144.139.107 +89.144.139.109 +89.144.139.111 +89.144.139.113 +89.144.139.120 +89.144.149.191 +89.144.154.235 +89.144.166.172 +89.144.166.191 +89.144.166.193 +89.144.166.217 +89.144.166.242 +89.144.175.238 +89.144.189.137 +89.144.189.177 +89.144.189.191 +89.144.189.232 +89.144.189.234 +89.219.64.174 +89.219.66.175 +89.219.73.15 +89.219.74.106 +89.219.77.0 +89.219.78.56 +89.219.93.252 +89.219.97.108 +89.219.99.81 +89.219.107.135 +89.219.107.220 +89.219.110.207 +89.219.115.60 +89.219.116.22 +89.219.118.105 +89.219.120.2 +89.219.123.224 +89.219.197.210 +89.219.202.225 +89.219.233.5 +89.219.252.178 +89.219.255.108 +89.235.103.95 +91.92.126.7 +91.92.150.122 +91.92.164.144 +91.92.205.110 +91.92.208.114 +91.92.231.138 +91.199.27.108 +91.220.113.242 +91.223.116.140 +91.228.132.22 +91.247.171.251 +92.42.53.30 +92.114.48.234 +93.114.105.206 +93.115.149.15 +93.115.149.237 +93.117.33.12 +93.117.34.217 +93.117.35.8 +93.117.38.185 +93.117.43.30 +93.118.114.23 +93.118.116.117 +93.118.123.32 +93.118.125.189 +93.118.145.201 +93.126.61.205 +94.74.128.171 +94.101.176.247 +94.182.39.201 +94.182.39.230 +94.182.91.237 +94.182.92.68 +94.182.106.3 +94.182.178.90 +94.182.181.250 +94.182.186.6 +94.182.192.220 +94.182.195.189 +94.182.209.62 +94.182.210.200 +94.182.216.105 +94.182.227.239 +94.182.251.1 +94.183.180.71 +95.38.24.226 +95.38.34.104 +95.38.103.219 +95.38.209.209 +109.109.32.12 +109.109.32.89 +109.109.32.97 +109.109.32.98 +109.109.32.103 +109.109.32.217 +109.109.34.76 +109.109.34.85 +109.109.34.123 +109.109.34.139 +109.109.34.183 +109.109.34.185 +109.109.34.188 +109.109.34.199 +109.109.34.213 +109.109.34.218 +109.109.34.231 +109.109.34.234 +109.109.34.237 +109.109.34.249 +109.109.35.12 +109.109.35.75 +109.109.35.125 +109.109.37.159 +109.109.40.58 +109.109.41.24 +109.109.43.141 +109.109.43.142 +109.109.43.145 +109.109.43.148 +109.109.43.194 +109.109.43.207 +109.109.43.208 +109.109.43.219 +109.109.43.228 +109.109.43.231 +109.109.43.244 +109.109.43.251 +109.109.45.21 +109.109.45.69 +109.109.45.82 +109.109.45.86 +109.109.45.97 +109.109.47.39 +109.109.47.58 +109.109.47.61 +109.109.47.68 +109.109.47.71 +109.109.47.88 +109.109.47.89 +109.109.47.110 +109.109.47.111 +109.109.47.118 +109.109.53.30 +109.109.54.39 +109.109.54.50 +109.109.54.51 +109.109.54.77 +109.109.54.89 +109.109.54.109 +109.109.54.141 +109.109.54.146 +109.109.54.166 +109.109.54.168 +109.109.54.207 +109.109.54.209 +109.109.54.212 +109.109.58.8 +109.109.58.36 +109.109.58.43 +109.109.58.76 +109.109.58.166 +109.109.59.6 +109.109.59.24 +109.109.59.25 +109.109.59.26 +109.109.59.35 +109.109.59.52 +109.109.59.53 +109.109.59.55 +109.109.59.84 +109.109.59.88 +109.109.59.99 +109.109.59.231 +109.109.60.7 +109.109.60.40 +109.109.60.43 +109.109.60.69 +109.109.60.73 +109.109.60.93 +109.109.60.98 +109.109.60.102 +109.109.60.116 +109.109.60.176 +109.109.60.204 +109.109.61.208 +109.109.61.209 +109.109.61.216 +109.109.61.218 +109.109.61.232 +109.109.62.0 +109.109.62.2 +109.109.63.130 +109.109.63.136 +109.109.63.152 +109.109.63.193 +109.109.63.196 +109.109.63.208 +109.109.63.241 +109.109.63.253 +109.122.251.188 +109.206.252.43 +128.65.181.82 +130.185.77.137 +130.193.77.37 +130.193.77.124 +151.232.80.169 +151.234.25.189 +151.234.29.56 +151.234.75.123 +151.234.186.93 +151.234.245.87 +151.235.13.16 +151.235.18.98 +151.235.123.250 +151.235.125.168 +151.235.126.91 +159.20.99.117 +171.22.24.162 +176.62.144.37 +176.62.144.44 +176.65.224.95 +178.173.140.41 +178.173.140.129 +178.173.148.167 +178.173.211.35 +178.173.216.97 +178.173.218.96 +178.173.221.167 +178.216.248.85 +178.216.251.244 +178.236.105.153 +178.236.106.202 +178.238.196.223 +178.239.147.81 +178.252.134.188 +178.252.166.146 +178.252.181.101 +185.3.202.129 +185.4.30.50 +185.8.175.98 +185.10.75.8 +185.10.75.75 +185.10.75.199 +185.18.213.84 +185.41.3.106 +185.42.225.197 +185.44.36.51 +185.53.142.229 +185.55.224.119 +185.55.225.182 +185.58.241.89 +185.59.112.25 +185.73.112.229 +185.73.226.28 +185.79.156.242 +185.79.157.196 +185.83.113.170 +185.83.183.95 +185.83.183.125 +185.84.158.30 +185.88.153.190 +185.88.154.59 +185.88.154.223 +185.88.178.43 +185.88.178.53 +185.94.97.11 +185.94.99.26 +185.94.99.37 +185.94.99.245 +185.97.116.171 +185.100.46.141 +185.103.84.39 +185.109.74.238 +185.110.219.40 +185.111.83.10 +185.112.149.97 +185.112.149.115 +185.112.149.118 +185.113.9.175 +185.113.58.197 +185.113.59.202 +185.117.50.230 +185.118.155.62 +185.119.167.5 +185.120.219.76 +185.120.221.228 +185.124.115.15 +185.129.169.78 +185.129.200.200 +185.131.88.224 +185.131.152.196 +185.137.27.47 +185.140.240.8 +185.141.104.86 +185.141.105.237 +185.141.105.238 +185.141.132.69 +185.141.134.205 +185.147.41.71 +185.149.192.24 +185.149.192.165 +185.164.72.9 +185.164.73.213 +185.165.116.184 +185.165.118.34 +185.165.118.245 +185.169.6.195 +185.190.39.68 +185.191.76.146 +185.192.10.2 +185.192.112.4 +185.192.112.37 +185.192.114.72 +185.192.114.109 +185.192.114.136 +185.192.114.152 +185.192.114.251 +185.208.174.78 +185.208.174.125 +185.208.175.79 +185.211.59.123 +185.231.112.172 +185.231.115.129 +185.231.115.167 +185.231.181.225 +185.232.154.79 +185.235.196.141 +185.235.197.79 +185.243.50.0 +185.243.50.3 +185.243.50.4 +185.243.50.5 +185.243.50.6 +185.243.50.8 +185.243.50.9 +185.243.50.10 +185.243.50.11 +185.243.50.12 +185.243.50.13 +185.243.50.14 +185.243.50.15 +185.243.50.17 +185.243.50.18 +185.243.50.19 +185.243.50.20 +185.243.50.21 +185.243.50.22 +185.243.50.23 +185.243.50.24 +185.243.50.25 +185.243.50.26 +185.243.50.27 +185.243.50.28 +185.243.50.29 +185.243.50.30 +185.243.50.31 +185.243.50.32 +185.243.50.34 +185.243.50.35 +185.243.50.36 +185.243.50.37 +185.243.50.38 +185.243.50.39 +185.243.50.40 +185.243.50.41 +185.243.50.42 +185.243.50.43 +185.243.50.44 +185.243.50.45 +185.243.50.46 +185.243.50.47 +185.243.50.48 +185.243.50.49 +185.243.50.50 +185.243.50.51 +185.243.50.52 +185.243.50.53 +185.243.50.54 +185.243.50.55 +185.243.50.56 +185.243.50.57 +185.243.50.59 +185.243.50.60 +185.243.50.61 +185.243.50.62 +185.243.50.63 +185.243.50.64 +185.243.50.65 +185.243.50.67 +185.243.50.68 +185.243.50.70 +185.243.50.71 +185.243.50.72 +185.243.50.73 +185.243.50.74 +185.243.50.75 +185.243.50.76 +185.243.50.77 +185.243.50.78 +185.243.50.79 +185.243.50.80 +185.243.50.81 +185.243.50.82 +185.243.50.84 +185.243.50.85 +185.243.50.86 +185.243.50.87 +185.243.50.88 +185.243.50.89 +185.243.50.90 +185.243.50.91 +185.243.50.92 +185.243.50.93 +185.243.50.95 +185.243.50.96 +185.243.50.97 +185.243.50.98 +185.243.50.99 +185.243.50.100 +185.243.50.101 +185.243.50.103 +185.243.50.104 +185.243.50.105 +185.243.50.106 +185.243.50.107 +185.243.50.108 +185.243.50.109 +185.243.50.110 +185.243.50.111 +185.243.50.112 +185.243.50.113 +185.243.50.115 +185.243.50.116 +185.243.50.117 +185.243.50.118 +185.243.50.119 +185.243.50.121 +185.243.50.122 +185.243.50.123 +185.243.50.124 +185.243.50.126 +185.243.50.127 +185.243.50.129 +185.243.50.130 +185.243.50.131 +185.243.50.132 +185.243.50.133 +185.243.50.134 +185.243.50.135 +185.243.50.136 +185.243.50.137 +185.243.50.138 +185.243.50.139 +185.243.50.140 +185.243.50.141 +185.243.50.142 +185.243.50.143 +185.243.50.144 +185.243.50.145 +185.243.50.146 +185.243.50.147 +185.243.50.148 +185.243.50.149 +185.243.50.150 +185.243.50.151 +185.243.50.152 +185.243.50.153 +185.243.50.154 +185.243.50.157 +185.243.50.158 +185.243.50.159 +185.243.50.161 +185.243.50.162 +185.243.50.164 +185.243.50.166 +185.243.50.168 +185.243.50.169 +185.243.50.170 +185.243.50.171 +185.243.50.172 +185.243.50.173 +185.243.50.174 +185.243.50.175 +185.243.50.176 +185.243.50.177 +185.243.50.178 +185.243.50.179 +185.243.50.180 +185.243.50.181 +185.243.50.182 +185.243.50.184 +185.243.50.185 +185.243.50.186 +185.243.50.187 +185.243.50.188 +185.243.50.189 +185.243.50.190 +185.243.50.191 +185.243.50.193 +185.243.50.194 +185.243.50.196 +185.243.50.197 +185.243.50.200 +185.243.50.201 +185.243.50.202 +185.243.50.205 +185.243.50.206 +185.243.50.207 +185.243.50.208 +185.243.50.209 +185.243.50.210 +185.243.50.211 +185.243.50.212 +185.243.50.213 +185.243.50.214 +185.243.50.217 +185.243.50.218 +185.243.50.219 +185.243.50.220 +185.243.50.221 +185.243.50.222 +185.243.50.223 +185.243.50.224 +185.243.50.225 +185.243.50.226 +185.243.50.227 +185.243.50.228 +185.243.50.231 +185.243.50.232 +185.243.50.234 +185.243.50.236 +185.243.50.237 +185.243.50.238 +185.243.50.239 +185.243.50.241 +185.243.50.242 +185.243.50.243 +185.243.50.244 +185.243.50.245 +185.243.50.246 +185.243.50.247 +185.243.50.249 +185.243.50.250 +185.243.50.251 +185.243.50.252 +185.243.50.253 +185.243.50.254 +185.255.88.55 +185.255.88.128 +188.0.240.133 +188.0.249.52 +188.121.100.132 +188.121.104.21 +188.121.117.78 +188.121.119.175 +188.121.123.128 +188.208.59.159 +188.208.151.64 +188.209.6.170 +188.209.68.61 +188.209.71.69 +188.209.76.133 +188.209.78.143 +188.210.97.80 +188.210.116.62 +188.211.68.160 +188.211.70.150 +188.211.87.38 +188.211.99.14 +188.211.99.23 +188.211.103.126 +188.211.108.135 +188.211.108.255 +188.211.113.11 +188.211.114.213 +188.211.121.143 +188.211.123.106 +188.211.126.196 +188.212.22.21 +188.212.76.158 +188.212.80.155 +188.213.66.140 +188.213.79.54 +188.213.181.202 +188.213.190.157 +188.215.165.169 +193.39.9.161 +193.58.119.108 +193.58.119.223 +193.105.234.127 +193.141.64.116 +193.141.65.182 +193.162.129.210 +193.228.90.213 +193.228.136.200 +193.242.125.194 +194.5.179.18 +194.5.195.74 +194.5.195.170 +194.41.51.39 +194.62.43.124 +194.150.70.88 +194.225.40.115 +195.26.27.173 +195.245.70.230 +212.23.201.215 +212.33.202.232 +212.80.24.24 +212.120.193.152 +212.120.194.55 +212.120.205.188 +212.120.212.25 +212.120.216.60 +212.120.221.106 +212.120.223.208 +213.176.0.14 +213.176.29.251 +213.195.60.63 +213.232.124.11 +217.11.24.233 +217.77.117.252 +217.144.106.34 +217.144.107.175 +217.170.251.3 +217.170.252.66 +217.170.252.156 +217.170.252.165 +217.171.149.19 +217.171.150.0 +217.172.98.173 +217.218.59.18 +217.218.66.24 +217.218.66.54 +217.218.72.215 +217.218.91.126 +217.218.102.173 +217.218.121.219 +217.218.126.180 +217.218.126.192 +217.218.147.109 +217.218.162.250 +217.218.171.21 +217.218.231.165 +217.219.78.210 +217.219.113.176 +217.219.146.132 +217.219.162.171 +217.219.162.179 +217.219.162.190 +217.219.219.68 +217.219.247.178 +217.219.247.179 +217.219.247.180 +217.219.247.182 +217.219.247.186 +217.219.247.208 +217.219.247.226 +217.219.250.67 +217.219.251.250 +2.184.237.250 +2.184.239.96 +2.185.60.53 +2.185.100.114 +2.185.111.42 +2.185.115.187 +2.185.118.190 +2.185.119.25 +2.185.121.161 +2.185.123.125 +2.185.144.18 +2.185.144.71 +2.185.146.147 +2.185.152.23 +2.185.228.3 +2.185.228.166 +2.185.229.141 +2.185.229.223 +2.185.232.202 +2.185.233.97 +2.185.234.29 +2.185.251.169 +2.185.254.215 +2.186.12.33 +2.186.12.72 +2.186.12.169 +2.186.14.53 +2.186.114.102 +2.186.118.65 +2.186.121.65 +2.186.122.56 +2.186.123.180 +2.186.123.193 +2.187.25.177 +2.187.32.105 +2.187.59.28 +2.187.97.193 +2.187.150.211 +2.187.188.97 +2.187.189.192 +2.188.2.253 +2.188.15.200 +2.188.15.201 +2.188.15.202 +2.188.21.20 +2.188.21.90 +2.188.21.100 +2.188.21.120 +2.188.21.130 +2.188.21.190 +2.188.21.200 +2.188.21.230 +2.188.21.240 +2.188.26.10 +2.188.43.88 +2.188.54.1 +2.188.54.2 +2.188.54.54 +2.188.54.55 +2.188.58.12 +2.188.73.81 +2.188.163.135 +2.188.163.136 +2.188.164.29 +2.188.164.30 +2.188.165.106 +2.188.168.184 +2.188.184.19 +2.188.212.158 +2.188.218.179 +2.188.218.180 +2.188.218.181 +2.188.228.114 +2.188.230.127 +2.188.235.83 +2.188.236.82 +2.189.1.10 +2.189.1.13 +2.189.1.15 +2.189.1.19 +2.189.1.253 +2.189.4.29 +2.189.4.30 +2.189.5.183 +2.189.9.56 +2.189.9.87 +2.189.44.44 +2.189.58.3 +2.189.87.170 +2.189.90.130 +2.189.92.1 +2.189.120.111 +2.189.142.70 +2.189.147.101 +2.189.147.211 +2.189.160.131 +2.189.161.45 +2.189.162.131 +2.189.162.136 +2.189.172.227 +2.189.172.229 +2.189.172.233 +2.189.173.198 +2.189.188.54 +2.189.188.56 +2.189.188.62 +2.189.188.190 +2.189.217.95 +2.189.217.251 +2.189.222.223 +2.189.242.36 +2.189.242.86 +2.189.242.100 +2.189.242.107 +2.189.242.109 +2.189.243.18 +2.189.243.19 +2.189.243.21 +2.189.243.25 +2.189.243.101 +2.189.243.103 +2.189.243.226 +2.189.243.228 +2.189.254.58 +2.189.254.67 +2.189.255.21 +2.189.255.130 +2.189.255.227 +2.190.3.49 +2.190.9.251 +2.190.12.228 +2.190.13.15 +2.190.14.71 +2.190.17.170 +2.190.18.195 +2.190.31.215 +2.190.35.50 +2.190.36.61 +2.190.37.214 +2.190.37.229 +2.190.41.98 +2.190.43.76 +2.190.44.114 +2.190.44.119 +2.190.48.163 +2.190.49.111 +2.190.53.64 +2.190.53.68 +2.190.53.92 +2.190.53.100 +2.190.55.74 +2.190.55.120 +2.190.59.91 +2.190.59.155 +2.190.73.43 +2.190.73.67 +2.190.78.115 +2.190.79.245 +2.190.86.118 +2.190.92.26 +2.190.94.100 +2.190.100.174 +2.190.108.91 +2.190.111.134 +2.190.113.182 +2.190.133.106 +2.190.137.215 +2.190.139.102 +2.190.144.183 +2.190.147.151 +2.190.152.187 +2.190.153.67 +2.190.159.244 +2.190.163.207 +2.190.169.87 +2.190.179.133 +2.190.182.32 +2.190.183.92 +2.190.185.210 +2.190.189.114 +2.190.196.38 +2.190.197.33 +2.190.197.175 +2.190.208.237 +2.190.211.50 +2.190.218.16 +2.190.218.137 +2.190.222.23 +2.190.225.89 +2.190.226.224 +2.190.230.164 +2.190.239.52 +2.190.254.192 +2.191.1.142 +2.191.3.59 +2.191.7.101 +2.191.11.236 +2.191.13.68 +2.191.14.96 +2.191.17.34 +2.191.19.25 +2.191.20.200 +2.191.30.238 +2.191.34.90 +2.191.37.81 +2.191.37.155 +2.191.38.203 +2.191.42.29 +2.191.44.179 +2.191.46.6 +2.191.50.40 +2.191.53.152 +2.191.59.4 +2.191.59.95 +2.191.65.208 +2.191.66.14 +2.191.66.48 +2.191.70.85 +2.191.78.251 +2.191.79.86 +2.191.83.25 +2.191.92.195 +2.191.95.198 +2.191.112.91 +2.191.114.44 +2.191.119.38 +2.191.120.15 +5.10.248.4 +5.10.248.7 +5.10.248.13 +5.10.248.23 +5.10.248.26 +5.10.248.31 +5.10.248.33 +5.10.248.41 +5.10.248.57 +5.10.248.65 +5.10.248.67 +5.10.248.88 +5.10.248.92 +5.10.248.95 +5.10.248.99 +5.10.248.107 +5.10.248.110 +5.10.248.117 +5.10.248.119 +5.10.248.128 +5.10.248.138 +5.10.248.155 +5.10.248.158 +5.10.248.160 +5.10.248.164 +5.10.248.169 +5.10.248.170 +5.10.248.171 +5.10.248.183 +5.10.248.215 +5.10.248.225 +5.10.248.237 +5.10.248.240 +5.10.248.241 +5.10.249.2 +5.10.249.24 +5.10.249.44 +5.10.249.79 +5.10.249.105 +5.10.249.138 +5.10.249.140 +5.10.249.175 +5.10.249.184 +5.10.249.208 +5.10.249.233 +5.10.249.242 +5.22.193.2 +5.22.193.3 +5.22.200.250 +5.22.203.46 +5.22.203.47 +5.42.217.8 +5.42.217.25 +5.42.217.76 +5.42.217.181 +5.42.217.186 +5.42.217.207 +5.42.217.208 +5.42.223.9 +5.42.223.119 +5.42.223.160 +5.42.223.179 +5.42.223.189 +5.42.223.247 +5.42.223.254 +5.56.128.128 +5.56.132.47 +5.56.132.69 +5.56.132.70 +5.56.132.76 +5.56.132.79 +5.56.132.89 +5.56.132.93 +5.56.132.97 +5.56.132.99 +5.56.132.100 +5.56.132.109 +5.56.132.120 +5.56.132.121 +5.56.132.126 +5.56.132.132 +5.56.132.145 +5.56.132.148 +5.56.132.150 +5.56.132.157 +5.56.132.169 +5.56.132.170 +5.56.132.174 +5.56.132.176 +5.56.132.177 +5.56.132.180 +5.56.132.182 +5.56.132.183 +5.56.132.185 +5.56.132.186 +5.56.132.188 +5.56.132.189 +5.56.132.203 +5.56.132.218 +5.56.132.219 +5.56.132.220 +5.56.132.235 +5.56.132.251 +5.56.132.253 +5.56.134.9 +5.56.134.51 +5.56.134.95 +5.56.134.153 +5.56.134.237 +5.56.135.5 +5.56.135.15 +5.56.135.16 +5.56.135.25 +5.56.135.131 +5.56.135.132 +5.56.135.196 +5.56.135.218 +5.56.135.244 +5.57.32.2 +5.57.32.29 +5.57.32.32 +5.57.32.103 +5.57.32.106 +5.57.32.125 +5.57.32.140 +5.57.32.163 +5.57.32.164 +5.57.32.165 +5.57.32.167 +5.57.32.168 +5.57.32.169 +5.57.32.170 +5.57.32.171 +5.57.32.172 +5.57.32.174 +5.57.32.205 +5.57.32.226 +5.57.32.228 +5.57.32.229 +5.57.32.231 +5.57.32.232 +5.57.32.236 +5.57.32.237 +5.57.32.239 +5.57.32.240 +5.57.32.242 +5.57.32.243 +5.57.32.244 +5.57.32.245 +5.57.32.246 +5.57.32.248 +5.57.32.249 +5.57.32.252 +5.57.32.253 +5.57.34.4 +5.57.34.48 +5.57.34.53 +5.57.34.121 +5.57.34.140 +5.57.34.142 +5.57.34.194 +5.57.34.195 +5.57.34.197 +5.57.34.229 +5.57.34.230 +5.57.34.231 +5.57.34.232 +5.57.34.233 +5.57.34.234 +5.57.34.235 +5.57.34.236 +5.57.34.237 +5.57.34.238 +5.57.34.242 +5.57.34.243 +5.57.34.244 +5.57.34.246 +5.57.34.249 +5.57.34.250 +5.57.34.251 +5.57.34.252 +5.57.35.57 +5.57.35.112 +5.57.35.181 +5.57.35.229 +5.57.37.20 +5.57.37.40 +5.57.37.84 +5.57.37.89 +5.57.37.144 +5.57.37.224 +5.57.39.16 +5.57.39.80 +5.57.39.101 +5.57.39.114 +5.57.39.178 +5.57.39.189 +5.57.39.197 +5.57.39.230 +5.61.24.2 +5.61.24.5 +5.61.24.15 +5.61.24.24 +5.61.24.30 +5.61.24.64 +5.61.24.78 +5.61.24.126 +5.61.24.202 +5.61.24.236 +5.61.25.2 +5.61.25.34 +5.61.25.55 +5.61.25.132 +5.61.25.226 +5.61.26.4 +5.61.26.5 +5.61.28.2 +5.61.28.12 +5.61.28.34 +5.61.28.38 +5.61.28.44 +5.61.28.48 +5.61.28.102 +5.61.28.103 +5.61.28.109 +5.61.28.113 +5.61.28.236 +5.61.28.239 +5.61.28.245 +5.61.29.43 +5.61.29.113 +5.61.29.135 +5.61.29.140 +5.61.29.165 +5.61.29.168 +5.61.29.170 +5.61.29.181 +5.61.29.183 +5.61.29.210 +5.61.29.212 +5.61.29.230 +5.61.30.20 +5.61.30.42 +5.61.30.84 +5.61.30.99 +5.61.30.100 +5.61.30.101 +5.61.30.102 +5.61.30.116 +5.61.30.132 +5.61.30.135 +5.61.30.145 +5.61.30.164 +5.61.30.195 +5.61.30.230 +5.61.30.233 +5.61.30.238 +5.61.31.35 +5.61.31.72 +5.61.31.84 +5.61.31.88 +5.61.31.101 +5.61.31.103 +5.61.31.106 +5.61.31.148 +5.61.31.170 +5.61.31.184 +5.61.31.190 +5.61.31.214 +5.61.31.236 +5.61.31.241 +5.62.190.164 +5.63.8.38 +5.63.8.43 +5.63.8.44 +5.63.8.46 +5.63.8.62 +5.63.8.70 +5.63.8.83 +5.63.8.130 +5.63.8.134 +5.63.8.138 +5.63.8.142 +5.63.8.146 +5.63.8.155 +5.63.8.186 +5.63.8.190 +5.63.8.243 +5.63.8.247 +5.63.8.248 +5.63.8.252 +5.63.8.253 +5.63.8.254 +5.63.9.35 +5.63.9.36 +5.63.9.38 +5.63.9.39 +5.63.9.40 +5.63.9.41 +5.63.9.42 +5.63.9.43 +5.63.9.44 +5.63.9.45 +5.63.9.142 +5.63.9.179 +5.63.9.180 +5.63.9.181 +5.63.9.202 +5.63.9.205 +5.63.9.206 +5.63.9.219 +5.63.9.222 +5.63.9.242 +5.63.9.251 +5.63.10.115 +5.63.10.122 +5.63.10.202 +5.63.10.227 +5.63.10.228 +5.63.13.6 +5.63.13.10 +5.63.13.14 +5.63.13.61 +5.63.13.62 +5.63.13.91 +5.63.13.92 +5.63.13.93 +5.63.13.94 +5.63.13.98 +5.63.13.125 +5.63.13.147 +5.63.13.158 +5.63.13.172 +5.63.13.174 +5.63.13.179 +5.63.13.183 +5.63.13.187 +5.63.13.188 +5.63.13.212 +5.63.13.246 +5.63.13.250 +5.63.15.2 +5.63.15.95 +5.74.13.212 +5.74.20.118 +5.74.25.40 +5.74.27.137 +5.74.30.157 +5.74.35.175 +5.74.40.57 +5.74.64.61 +5.74.70.72 +5.74.80.124 +5.74.82.62 +5.74.85.54 +5.74.87.230 +5.74.89.141 +5.74.91.84 +5.74.93.18 +5.74.94.246 +5.74.103.0 +5.74.108.253 +5.74.116.99 +5.74.157.17 +5.74.159.248 +5.74.168.73 +5.74.169.165 +5.74.173.111 +5.74.177.58 +5.74.179.196 +5.74.193.165 +5.74.201.57 +5.74.204.86 +5.74.220.174 +5.74.226.60 +5.74.229.186 +5.74.230.137 +5.74.233.4 +5.74.247.49 +5.74.249.133 +5.74.249.152 +5.74.250.193 +5.74.254.130 +5.74.255.47 +5.106.6.30 +5.106.18.134 +5.106.19.21 +5.106.28.197 +5.106.50.108 +5.106.52.108 +5.134.192.129 +5.134.194.83 +5.144.128.17 +5.144.128.20 +5.144.128.21 +5.144.129.2 +5.144.129.4 +5.144.129.6 +5.144.129.7 +5.144.129.8 +5.144.129.9 +5.144.129.10 +5.144.129.12 +5.144.129.14 +5.144.129.15 +5.144.129.16 +5.144.129.18 +5.144.129.19 +5.144.129.20 +5.144.129.21 +5.144.129.22 +5.144.129.23 +5.144.129.24 +5.144.129.26 +5.144.129.27 +5.144.129.28 +5.144.129.29 +5.144.129.30 +5.144.129.31 +5.144.129.33 +5.144.129.34 +5.144.129.35 +5.144.129.36 +5.144.129.37 +5.144.129.38 +5.144.129.39 +5.144.129.41 +5.144.129.42 +5.144.129.43 +5.144.129.44 +5.144.129.45 +5.144.129.46 +5.144.129.47 +5.144.129.48 +5.144.129.50 +5.144.129.51 +5.144.129.53 +5.144.129.54 +5.144.129.55 +5.144.129.56 +5.144.129.58 +5.144.129.59 +5.144.129.60 +5.144.129.61 +5.144.129.62 +5.144.129.65 +5.144.129.67 +5.144.129.68 +5.144.129.70 +5.144.129.71 +5.144.129.72 +5.144.129.73 +5.144.129.74 +5.144.129.75 +5.144.129.76 +5.144.129.77 +5.144.129.78 +5.144.129.79 +5.144.129.80 +5.144.129.81 +5.144.129.82 +5.144.129.84 +5.144.129.85 +5.144.129.86 +5.144.129.87 +5.144.129.88 +5.144.129.89 +5.144.129.90 +5.144.129.91 +5.144.129.92 +5.144.129.93 +5.144.129.94 +5.144.129.95 +5.144.129.96 +5.144.129.97 +5.144.129.98 +5.144.129.99 +5.144.129.102 +5.144.129.103 +5.144.129.104 +5.144.129.105 +5.144.129.106 +5.144.129.107 +5.144.129.108 +5.144.129.109 +5.144.129.112 +5.144.129.113 +5.144.129.114 +5.144.129.115 +5.144.129.116 +5.144.129.117 +5.144.129.118 +5.144.129.119 +5.144.129.120 +5.144.129.121 +5.144.129.122 +5.144.129.123 +5.144.129.124 +5.144.129.125 +5.144.129.126 +5.144.129.127 +5.144.129.128 +5.144.129.129 +5.144.129.130 +5.144.129.131 +5.144.129.132 +5.144.129.133 +5.144.129.134 +5.144.129.135 +5.144.129.136 +5.144.129.137 +5.144.129.139 +5.144.129.140 +5.144.129.141 +5.144.129.142 +5.144.129.143 +5.144.129.144 +5.144.129.145 +5.144.129.146 +5.144.129.147 +5.144.129.149 +5.144.129.150 +5.144.129.152 +5.144.129.153 +5.144.129.154 +5.144.129.155 +5.144.129.156 +5.144.129.157 +5.144.129.158 +5.144.129.159 +5.144.129.160 +5.144.129.161 +5.144.129.162 +5.144.129.163 +5.144.129.164 +5.144.129.165 +5.144.129.167 +5.144.129.168 +5.144.129.169 +5.144.129.170 +5.144.129.171 +5.144.129.172 +5.144.129.173 +5.144.129.174 +5.144.129.175 +5.144.129.176 +5.144.129.178 +5.144.129.179 +5.144.129.180 +5.144.129.181 +5.144.129.182 +5.144.129.183 +5.144.129.184 +5.144.129.185 +5.144.129.186 +5.144.129.187 +5.144.129.188 +5.144.129.190 +5.144.129.191 +5.144.129.192 +5.144.129.193 +5.144.129.194 +5.144.129.195 +5.144.129.196 +5.144.129.197 +5.144.129.198 +5.144.129.199 +5.144.129.200 +5.144.129.201 +5.144.129.202 +5.144.129.203 +5.144.129.204 +5.144.129.205 +5.144.129.206 +5.144.129.207 +5.144.129.208 +5.144.129.209 +5.144.129.210 +5.144.129.211 +5.144.129.212 +5.144.129.213 +5.144.129.214 +5.144.129.215 +5.144.129.217 +5.144.129.218 +5.144.129.219 +5.144.129.220 +5.144.129.221 +5.144.129.222 +5.144.129.223 +5.144.129.224 +5.144.129.225 +5.144.129.226 +5.144.129.227 +5.144.129.228 +5.144.129.229 +5.144.129.230 +5.144.129.231 +5.144.129.232 +5.144.129.233 +5.144.129.234 +5.144.129.235 +5.144.129.237 +5.144.129.238 +5.144.129.239 +5.144.129.240 +5.144.129.241 +5.144.129.242 +5.144.129.243 +5.144.129.244 +5.144.129.245 +5.144.129.246 +5.144.129.247 +5.144.129.248 +5.144.129.249 +5.144.129.250 +5.144.129.251 +5.144.129.252 +5.144.129.253 +5.144.129.254 +5.144.130.3 +5.144.130.5 +5.144.130.7 +5.144.130.9 +5.144.130.10 +5.144.130.11 +5.144.130.12 +5.144.130.13 +5.144.130.14 +5.144.130.15 +5.144.130.16 +5.144.130.17 +5.144.130.20 +5.144.130.21 +5.144.130.23 +5.144.130.24 +5.144.130.25 +5.144.130.27 +5.144.130.28 +5.144.130.29 +5.144.130.30 +5.144.130.31 +5.144.130.32 +5.144.130.34 +5.144.130.35 +5.144.130.36 +5.144.130.37 +5.144.130.40 +5.144.130.42 +5.144.130.43 +5.144.130.44 +5.144.130.45 +5.144.130.46 +5.144.130.47 +5.144.130.48 +5.144.130.50 +5.144.130.51 +5.144.130.53 +5.144.130.57 +5.144.130.59 +5.144.130.60 +5.144.130.61 +5.144.130.62 +5.144.130.63 +5.144.130.64 +5.144.130.65 +5.144.130.66 +5.144.130.67 +5.144.130.68 +5.144.130.69 +5.144.130.70 +5.144.130.71 +5.144.130.72 +5.144.130.73 +5.144.130.74 +5.144.130.76 +5.144.130.77 +5.144.130.78 +5.144.130.79 +5.144.130.80 +5.144.130.81 +5.144.130.83 +5.144.130.85 +5.144.130.86 +5.144.130.87 +5.144.130.88 +5.144.130.89 +5.144.130.91 +5.144.130.92 +5.144.130.93 +5.144.130.95 +5.144.130.97 +5.144.130.98 +5.144.130.99 +5.144.130.100 +5.144.130.101 +5.144.130.102 +5.144.130.103 +5.144.130.104 +5.144.130.105 +5.144.130.106 +5.144.130.107 +5.144.130.108 +5.144.130.109 +5.144.130.110 +5.144.130.111 +5.144.130.112 +5.144.130.114 +5.144.130.115 +5.144.130.116 +5.144.130.118 +5.144.130.120 +5.144.130.121 +5.144.130.123 +5.144.130.125 +5.144.130.126 +5.144.130.127 +5.144.130.128 +5.144.130.129 +5.144.130.130 +5.144.130.131 +5.144.130.132 +5.144.130.133 +5.144.130.134 +5.144.130.135 +5.144.130.136 +5.144.130.137 +5.144.130.139 +5.144.130.140 +5.144.130.141 +5.144.130.142 +5.144.130.144 +5.144.130.146 +5.144.130.148 +5.144.130.149 +5.144.130.151 +5.144.130.152 +5.144.130.153 +5.144.130.154 +5.144.130.155 +5.144.130.156 +5.144.130.157 +5.144.130.158 +5.144.130.160 +5.144.130.161 +5.144.130.162 +5.144.130.163 +5.144.130.164 +5.144.130.165 +5.144.130.166 +5.144.130.167 +5.144.130.168 +5.144.130.169 +5.144.130.170 +5.144.130.172 +5.144.130.173 +5.144.130.174 +5.144.130.175 +5.144.130.176 +5.144.130.178 +5.144.130.179 +5.144.130.180 +5.144.130.181 +5.144.130.182 +5.144.130.183 +5.144.130.184 +5.144.130.185 +5.144.130.187 +5.144.130.188 +5.144.130.189 +5.144.130.191 +5.144.130.192 +5.144.130.193 +5.144.130.194 +5.144.130.195 +5.144.130.197 +5.144.130.198 +5.144.130.199 +5.144.130.201 +5.144.130.202 +5.144.130.203 +5.144.130.204 +5.144.130.205 +5.144.130.206 +5.144.130.207 +5.144.130.208 +5.144.130.209 +5.144.130.210 +5.144.130.211 +5.144.130.212 +5.144.130.213 +5.144.130.215 +5.144.130.216 +5.144.130.217 +5.144.130.218 +5.144.130.220 +5.144.130.221 +5.144.130.222 +5.144.130.223 +5.144.130.224 +5.144.130.225 +5.144.130.226 +5.144.130.227 +5.144.130.228 +5.144.130.229 +5.144.130.230 +5.144.130.231 +5.144.130.232 +5.144.130.233 +5.144.130.234 +5.144.130.235 +5.144.130.236 +5.144.130.238 +5.144.130.239 +5.144.130.241 +5.144.130.242 +5.144.130.243 +5.144.130.244 +5.144.130.246 +5.144.130.247 +5.144.130.248 +5.144.130.250 +5.144.130.251 +5.144.130.253 +5.144.130.254 +5.144.131.168 +5.144.131.226 +5.144.131.227 +5.144.131.228 +5.144.131.229 +5.144.131.230 +5.144.131.231 +5.144.131.234 +5.144.131.236 +5.144.131.237 +5.144.131.238 +5.144.131.239 +5.144.131.244 +5.144.131.249 +5.144.131.250 +5.144.131.251 +5.144.131.252 +5.144.131.254 +5.144.132.4 +5.144.132.6 +5.144.132.23 +5.144.132.24 +5.144.132.25 +5.144.132.29 +5.144.132.33 +5.144.132.38 +5.144.132.53 +5.144.132.111 +5.144.132.123 +5.144.132.137 +5.144.132.151 +5.144.132.155 +5.144.132.158 +5.144.132.167 +5.144.132.175 +5.144.132.178 +5.144.132.199 +5.144.132.237 +5.144.132.238 +5.144.132.241 +5.144.133.55 +5.144.133.156 +5.144.133.163 +5.144.133.164 +5.144.133.171 +5.144.133.182 +5.144.133.183 +5.144.133.195 +5.144.133.198 +5.144.133.244 +5.144.134.74 +5.144.134.85 +5.144.134.126 +5.144.134.210 +5.144.134.211 +5.144.134.212 +5.144.134.213 +5.144.134.214 +5.144.135.35 +5.144.135.36 +5.144.135.37 +5.144.135.186 +5.145.112.38 +5.145.112.39 +5.145.112.50 +5.145.112.51 +5.145.112.52 +5.145.112.54 +5.145.112.55 +5.145.112.56 +5.145.112.58 +5.145.112.59 +5.145.112.70 +5.145.112.112 +5.145.113.112 +5.145.115.90 +5.145.115.115 +5.145.115.125 +5.145.119.118 +5.145.119.119 +5.159.48.16 +5.159.48.25 +5.159.48.41 +5.159.48.42 +5.159.48.43 +5.159.48.49 +5.159.49.10 +5.159.49.28 +5.159.49.40 +5.159.49.59 +5.159.49.78 +5.159.49.95 +5.159.49.100 +5.159.49.118 +5.159.49.141 +5.159.49.154 +5.159.49.162 +5.159.49.177 +5.159.49.212 +5.159.49.233 +5.159.50.144 +5.159.50.146 +5.159.50.147 +5.159.50.148 +5.159.50.149 +5.159.50.150 +5.159.51.1 +5.159.51.2 +5.159.51.16 +5.159.51.17 +5.159.51.18 +5.159.51.19 +5.159.51.20 +5.159.51.21 +5.159.51.22 +5.159.51.23 +5.159.51.192 +5.159.51.193 +5.159.51.195 +5.159.51.196 +5.159.51.197 +5.159.51.198 +5.159.51.199 +5.159.51.200 +5.159.51.201 +5.159.51.202 +5.159.51.203 +5.159.51.204 +5.159.51.205 +5.159.51.206 +5.159.52.46 +5.159.52.47 +5.159.52.48 +5.159.52.49 +5.159.52.50 +5.159.52.51 +5.159.52.52 +5.159.52.54 +5.159.52.55 +5.159.52.56 +5.159.52.57 +5.159.52.58 +5.159.52.59 +5.159.52.60 +5.159.52.61 +5.159.52.62 +5.159.52.74 +5.159.52.75 +5.159.53.9 +5.159.53.10 +5.159.53.11 +5.159.55.51 +5.159.55.55 +5.159.55.106 +5.159.55.116 +5.159.55.123 +5.159.55.154 +5.159.55.226 +5.159.55.227 +5.160.0.37 +5.160.2.51 +5.160.2.157 +5.160.2.158 +5.160.5.66 +5.160.5.67 +5.160.5.68 +5.160.5.207 +5.160.6.67 +5.160.8.243 +5.160.10.151 +5.160.10.152 +5.160.10.204 +5.160.11.95 +5.160.12.98 +5.160.13.235 +5.160.14.4 +5.160.27.80 +5.160.28.35 +5.160.28.180 +5.160.29.154 +5.160.30.27 +5.160.42.43 +5.160.42.44 +5.160.42.120 +5.160.42.138 +5.160.42.180 +5.160.43.117 +5.160.43.195 +5.160.46.2 +5.160.46.234 +5.160.47.154 +5.160.47.196 +5.160.47.199 +5.160.48.202 +5.160.48.210 +5.160.48.212 +5.160.48.214 +5.160.48.215 +5.160.48.216 +5.160.48.217 +5.160.48.218 +5.160.48.219 +5.160.48.220 +5.160.50.34 +5.160.50.174 +5.160.53.92 +5.160.56.242 +5.160.56.244 +5.160.56.254 +5.160.61.43 +5.160.61.167 +5.160.63.135 +5.160.64.69 +5.160.65.23 +5.160.66.82 +5.160.66.91 +5.160.66.171 +5.160.66.172 +5.160.66.173 +5.160.68.228 +5.160.69.36 +5.160.71.168 +5.160.72.210 +5.160.72.211 +5.160.72.212 +5.160.75.2 +5.160.79.56 +5.160.79.219 +5.160.80.180 +5.160.80.183 +5.160.80.230 +5.160.83.253 +5.160.85.243 +5.160.85.244 +5.160.86.141 +5.160.90.91 +5.160.91.141 +5.160.91.243 +5.160.92.164 +5.160.93.189 +5.160.95.50 +5.160.96.34 +5.160.98.197 +5.160.99.121 +5.160.99.226 +5.160.99.238 +5.160.103.178 +5.160.103.179 +5.160.104.2 +5.160.104.3 +5.160.108.179 +5.160.108.181 +5.160.109.165 +5.160.109.229 +5.160.115.35 +5.160.116.174 +5.160.116.180 +5.160.118.138 +5.160.119.53 +5.160.120.52 +5.160.121.70 +5.160.121.186 +5.160.122.41 +5.160.123.165 +5.160.128.142 +5.160.134.2 +5.160.135.51 +5.160.138.21 +5.160.138.43 +5.160.138.66 +5.160.138.70 +5.160.138.157 +5.160.138.220 +5.160.139.18 +5.160.139.19 +5.160.139.74 +5.160.139.86 +5.160.139.91 +5.160.140.4 +5.160.142.7 +5.160.144.16 +5.160.146.1 +5.160.146.74 +5.160.146.194 +5.160.146.196 +5.160.146.197 +5.160.146.232 +5.160.146.234 +5.160.150.30 +5.160.151.59 +5.160.151.60 +5.160.152.135 +5.160.152.163 +5.160.152.164 +5.160.152.165 +5.160.152.171 +5.160.153.4 +5.160.154.34 +5.160.154.53 +5.160.154.61 +5.160.159.98 +5.160.166.36 +5.160.167.249 +5.160.178.229 +5.160.179.179 +5.160.179.227 +5.160.179.237 +5.160.186.163 +5.160.187.35 +5.160.187.51 +5.160.189.11 +5.160.189.12 +5.160.192.5 +5.160.192.9 +5.160.195.11 +5.160.196.134 +5.160.196.141 +5.160.196.142 +5.160.196.181 +5.160.197.195 +5.160.197.196 +5.160.197.197 +5.160.197.199 +5.160.197.200 +5.160.197.210 +5.160.197.221 +5.160.198.78 +5.160.200.18 +5.160.200.163 +5.160.200.168 +5.160.200.172 +5.160.200.174 +5.160.200.178 +5.160.200.197 +5.160.200.204 +5.160.203.70 +5.160.208.6 +5.160.211.32 +5.160.211.131 +5.160.211.132 +5.160.215.166 +5.160.217.10 +5.160.217.11 +5.160.218.102 +5.160.218.121 +5.160.218.125 +5.160.218.236 +5.160.218.245 +5.160.219.133 +5.160.219.139 +5.160.219.188 +5.160.223.51 +5.160.223.52 +5.160.225.4 +5.160.225.20 +5.160.227.245 +5.160.228.181 +5.160.230.66 +5.160.231.210 +5.160.232.104 +5.160.242.54 +5.160.242.184 +5.160.243.30 +5.160.243.66 +5.160.243.137 +5.160.243.151 +5.160.243.157 +5.160.243.227 +5.160.243.231 +5.160.246.47 +5.160.246.178 +5.160.246.252 +5.160.247.71 +5.160.247.147 +5.160.247.148 +5.160.247.158 +5.160.247.196 +5.160.247.198 +5.160.247.199 +5.160.247.200 +5.160.247.201 +5.160.247.202 +5.182.44.16 +5.182.44.19 +5.182.44.20 +5.182.44.26 +5.182.44.28 +5.182.44.33 +5.182.44.34 +5.182.44.39 +5.182.44.41 +5.182.44.43 +5.182.44.48 +5.182.44.55 +5.182.44.58 +5.182.44.61 +5.182.44.63 +5.182.44.65 +5.182.44.67 +5.182.44.71 +5.182.44.72 +5.182.44.74 +5.182.44.77 +5.182.44.92 +5.182.44.98 +5.182.44.99 +5.182.44.100 +5.182.44.101 +5.182.44.102 +5.182.44.103 +5.182.44.104 +5.182.44.105 +5.182.44.106 +5.182.44.107 +5.182.44.110 +5.182.44.112 +5.182.44.113 +5.182.44.114 +5.182.44.115 +5.182.44.116 +5.182.44.117 +5.182.44.119 +5.182.44.121 +5.182.44.127 +5.182.44.129 +5.182.44.130 +5.182.44.131 +5.182.44.138 +5.182.44.139 +5.182.44.141 +5.182.44.142 +5.182.44.144 +5.182.44.146 +5.182.44.148 +5.182.44.149 +5.182.44.152 +5.182.44.154 +5.182.44.155 +5.182.44.158 +5.182.44.160 +5.182.44.162 +5.182.44.163 +5.182.44.164 +5.182.44.165 +5.182.44.167 +5.182.44.171 +5.182.44.172 +5.182.44.173 +5.182.44.178 +5.182.44.180 +5.182.44.181 +5.182.44.182 +5.182.44.185 +5.182.44.187 +5.182.44.188 +5.182.44.189 +5.182.44.192 +5.182.44.194 +5.182.44.196 +5.182.44.198 +5.182.44.202 +5.182.44.204 +5.182.44.206 +5.182.44.207 +5.182.44.210 +5.182.44.217 +5.182.44.219 +5.182.44.223 +5.182.44.224 +5.182.44.226 +5.182.44.228 +5.182.44.229 +5.182.44.241 +5.182.44.244 +5.182.44.250 +5.182.45.24 +5.182.46.1 +5.182.46.2 +5.182.46.7 +5.182.46.23 +5.182.46.31 +5.182.46.34 +5.182.46.36 +5.182.46.41 +5.182.46.48 +5.182.46.57 +5.182.46.192 +5.182.46.195 +5.190.1.5 +5.190.1.25 +5.190.1.27 +5.190.2.65 +5.190.8.113 +5.190.15.130 +5.190.29.201 +5.190.48.6 +5.190.52.102 +5.190.52.245 +5.190.69.66 +5.190.77.209 +5.190.100.67 +5.190.101.130 +5.190.116.154 +5.190.154.36 +5.190.154.37 +5.190.166.2 +5.190.190.3 +5.190.209.10 +5.190.211.50 +5.190.211.52 +5.200.65.61 +5.200.69.88 +5.200.72.229 +5.200.128.135 +5.200.149.177 +5.200.150.244 +5.200.168.221 +5.200.169.199 +5.200.176.227 +5.200.189.251 +5.200.206.162 +5.200.228.95 +5.200.229.26 +5.200.229.99 +5.201.161.214 +5.201.164.149 +5.201.165.218 +5.201.175.194 +5.201.177.244 +5.202.5.222 +5.202.6.42 +5.202.6.46 +5.202.6.67 +5.202.6.68 +5.202.6.195 +5.202.6.198 +5.202.6.199 +5.202.6.200 +5.202.6.201 +5.202.6.202 +5.202.6.203 +5.202.6.207 +5.202.6.230 +5.202.7.28 +5.202.7.70 +5.202.7.85 +5.202.7.168 +5.202.7.170 +5.202.8.253 +5.202.9.71 +5.202.9.77 +5.202.9.78 +5.202.9.99 +5.202.10.2 +5.202.10.36 +5.202.10.39 +5.202.10.42 +5.202.10.44 +5.202.10.45 +5.202.10.48 +5.202.10.49 +5.202.10.50 +5.202.10.52 +5.202.10.54 +5.202.10.55 +5.202.10.58 +5.202.10.61 +5.202.12.20 +5.202.13.85 +5.202.13.86 +5.202.13.234 +5.202.13.235 +5.202.13.236 +5.202.14.2 +5.202.14.7 +5.202.15.246 +5.202.28.142 +5.202.45.250 +5.202.50.41 +5.202.50.42 +5.202.56.58 +5.202.60.146 +5.202.74.30 +5.202.75.244 +5.202.82.59 +5.202.84.226 +5.202.84.234 +5.202.85.159 +5.202.90.128 +5.202.90.131 +5.202.90.187 +5.202.93.180 +5.202.100.100 +5.202.100.101 +5.202.104.85 +5.202.104.163 +5.202.129.29 +5.202.129.30 +5.202.129.31 +5.202.129.32 +5.202.163.204 +5.202.171.138 +5.202.174.168 +5.202.174.211 +5.202.175.80 +5.202.179.237 +5.202.181.233 +5.202.181.246 +5.202.183.10 +5.202.183.165 +5.202.192.34 +5.202.195.114 +5.202.196.63 +5.202.196.245 +5.202.197.202 +5.202.240.33 +5.202.240.36 +5.202.240.37 +5.202.240.43 +5.202.243.94 +5.202.252.30 +5.202.254.25 +5.232.142.26 +5.232.200.53 +5.232.202.36 +5.232.207.123 +5.233.44.58 +5.233.44.60 +5.233.44.130 +5.233.46.104 +5.233.49.38 +5.233.64.155 +5.233.68.117 +5.233.69.52 +5.233.74.61 +5.233.79.124 +5.233.87.166 +5.233.184.241 +5.234.1.249 +5.234.2.97 +5.234.3.203 +5.234.6.249 +5.234.7.101 +5.234.14.184 +5.234.17.49 +5.234.18.240 +5.234.21.81 +5.234.23.206 +5.234.28.29 +5.234.28.201 +5.234.45.50 +5.234.45.240 +5.234.165.195 +5.234.194.136 +5.234.196.39 +5.234.201.187 +5.234.202.60 +5.234.213.173 +5.234.215.171 +5.235.161.28 +5.235.172.1 +5.235.175.177 +5.235.177.154 +5.235.178.108 +5.235.180.3 +5.235.186.172 +5.235.232.65 +5.235.254.187 +5.236.24.76 +5.236.25.20 +5.236.37.8 +5.236.96.242 +5.236.104.163 +5.236.105.251 +5.236.193.47 +5.236.193.128 +5.236.194.247 +5.236.195.113 +5.236.195.211 +5.237.179.70 +5.237.193.239 +5.237.201.65 +5.237.243.46 +5.237.243.168 +5.238.52.10 +5.238.52.133 +5.238.65.238 +5.238.88.214 +5.238.91.216 +5.238.110.141 +5.238.110.178 +5.238.129.145 +5.238.136.226 +5.238.139.75 +5.238.142.6 +5.238.146.31 +5.238.146.126 +5.238.152.244 +5.238.157.188 +5.238.158.161 +5.238.161.202 +5.238.163.58 +5.238.165.255 +5.238.167.20 +5.238.168.18 +5.238.169.163 +5.238.169.251 +5.238.171.210 +5.238.176.158 +5.238.184.49 +5.238.192.34 +5.238.192.161 +5.238.200.214 +5.238.205.46 +5.238.206.81 +5.238.211.141 +5.238.220.93 +5.238.221.36 +5.238.227.230 +5.238.228.234 +5.238.229.0 +5.238.230.8 +5.238.236.19 +5.238.239.99 +5.238.241.190 +5.238.242.251 +5.238.243.187 +5.238.245.7 +5.238.255.152 +5.239.10.28 +5.239.19.142 +5.239.39.175 +5.239.62.113 +5.239.62.215 +5.239.76.206 +5.239.78.243 +5.239.80.95 +5.239.80.138 +5.239.85.223 +5.239.100.27 +5.239.110.134 +5.239.122.122 +5.239.147.118 +5.239.160.77 +5.239.177.85 +5.239.241.236 +5.239.244.75 +10.1.24.51 +10.2.32.113 +10.3.8.211 +10.3.53.179 +10.3.53.180 +10.3.53.181 +10.3.53.238 +10.3.98.162 +10.3.98.163 +10.9.28.202 +10.9.28.206 +10.19.179.4 +10.21.201.140 +10.30.5.167 +10.30.116.171 +10.30.116.172 +10.30.236.16 +10.48.4.11 +10.56.130.141 +10.56.143.10 +10.58.108.186 +10.69.78.50 +10.75.10.3 +10.80.78.3 +10.80.78.19 +10.80.78.36 +10.80.78.67 +10.80.78.99 +10.80.78.116 +10.80.84.243 +10.80.85.10 +10.80.85.35 +10.80.85.51 +10.86.33.3 +10.86.33.11 +10.86.34.10 +10.86.34.90 +10.86.34.99 +10.86.36.10 +10.86.106.99 +10.87.78.51 +10.88.0.210 +10.88.4.126 +10.88.163.64 +10.88.163.65 +10.88.163.66 +10.88.165.26 +10.88.180.130 +10.88.180.138 +10.88.180.146 +10.88.195.57 +10.88.198.18 +10.88.198.21 +10.92.103.55 +10.103.10.3 +10.104.4.19 +10.104.25.154 +10.104.31.239 +10.104.31.255 +10.104.33.246 +10.104.36.24 +10.104.36.207 +10.104.204.0 +10.104.204.15 +10.104.204.23 +10.104.204.31 +10.104.204.32 +10.104.204.55 +10.104.204.63 +10.104.204.79 +10.104.204.80 +10.104.204.103 +10.104.204.111 +10.104.204.119 +10.104.204.151 +10.104.204.167 +10.104.204.175 +10.104.204.183 +10.104.204.191 +10.104.204.207 +10.104.204.255 +10.104.205.23 +10.104.205.79 +10.104.205.95 +10.104.205.119 +10.104.205.135 +10.104.205.143 +10.104.205.159 +10.104.205.183 +10.104.208.85 +10.104.208.87 +10.104.208.175 +10.104.208.231 +10.104.208.239 +10.104.209.7 +10.104.209.15 +10.104.209.40 +10.104.209.71 +10.104.209.87 +10.104.209.183 +10.104.209.247 +10.104.216.232 +10.104.217.44 +10.104.217.45 +10.104.219.149 +10.104.220.159 +10.104.224.61 +10.104.227.5 +10.104.227.7 +10.104.228.239 +10.104.229.109 +10.104.229.111 +10.104.230.101 +10.106.240.117 +10.106.240.119 +10.106.241.13 +10.106.241.15 +10.114.63.66 +10.114.143.170 +10.115.14.189 +10.117.250.3 +10.121.249.38 +10.124.252.253 +10.124.255.65 +10.124.255.139 +10.126.15.114 +10.126.15.115 +10.126.15.120 +10.140.40.4 +10.140.196.194 +10.141.194.65 +10.141.194.66 +10.141.194.67 +10.141.194.68 +10.141.194.69 +10.141.194.70 +10.141.194.74 +10.170.244.154 +10.170.248.102 +10.171.13.6 +10.175.46.18 +10.175.237.132 +10.175.238.20 +10.175.239.68 +10.176.75.146 +10.187.63.83 +10.201.32.163 +10.201.48.193 +10.201.66.118 +10.201.66.120 +10.201.66.121 +10.201.66.122 +10.201.66.123 +10.201.77.82 +10.202.3.212 +10.223.252.145 +10.224.254.58 +10.233.249.52 +10.233.249.57 +10.246.35.148 +10.249.2.10 +10.249.52.27 +10.249.52.58 +10.249.52.84 +10.249.52.233 +10.249.52.234 +10.249.52.239 +10.249.52.244 +10.250.251.7 +10.250.252.97 +12.15.19.238 +12.15.19.241 +31.7.65.29 +31.7.65.45 +31.7.65.61 +31.7.65.92 +31.7.65.102 +31.7.65.132 +31.7.65.133 +31.7.65.134 +31.7.65.135 +31.7.66.78 +31.7.66.235 +31.7.68.93 +31.7.68.201 +31.7.68.202 +31.7.68.206 +31.7.68.215 +31.7.68.220 +31.7.68.246 +31.7.68.248 +31.7.68.250 +31.7.68.251 +31.7.69.57 +31.7.69.218 +31.7.70.12 +31.7.70.104 +31.7.71.8 +31.7.71.11 +31.7.71.122 +31.7.71.197 +31.7.72.37 +31.7.72.114 +31.7.72.147 +31.7.72.148 +31.7.72.179 +31.7.72.180 +31.7.72.181 +31.7.72.182 +31.7.73.5 +31.7.73.6 +31.7.73.21 +31.7.73.91 +31.7.73.95 +31.7.73.117 +31.7.73.120 +31.7.73.131 +31.7.74.4 +31.7.74.164 +31.7.76.254 +31.7.77.226 +31.7.78.15 +31.7.78.39 +31.7.78.83 +31.7.78.124 +31.7.78.133 +31.7.78.140 +31.7.78.157 +31.7.78.180 +31.7.78.191 +31.7.79.69 +31.7.79.114 +31.7.79.130 +31.7.79.137 +31.7.79.140 +31.7.79.150 +31.7.79.155 +31.7.79.156 +31.7.79.200 +31.7.89.103 +31.7.89.104 +31.14.113.35 +31.14.113.146 +31.14.113.147 +31.14.113.148 +31.14.113.149 +31.14.113.150 +31.14.114.62 +31.14.115.2 +31.14.115.12 +31.14.115.70 +31.14.115.83 +31.14.115.196 +31.14.115.198 +31.14.116.2 +31.14.117.10 +31.14.117.194 +31.14.117.202 +31.14.117.207 +31.14.117.212 +31.14.118.146 +31.14.118.147 +31.14.118.228 +31.14.118.229 +31.14.119.165 +31.14.120.2 +31.14.121.116 +31.14.121.131 +31.14.123.2 +31.14.123.24 +31.14.123.127 +31.14.123.145 +31.14.124.74 +31.24.234.34 +31.24.234.35 +31.24.234.37 +31.25.90.7 +31.25.90.10 +31.25.90.11 +31.25.90.13 +31.25.90.22 +31.25.90.29 +31.25.90.76 +31.25.90.93 +31.25.90.94 +31.25.90.100 +31.25.90.110 +31.25.90.114 +31.25.90.116 +31.25.90.130 +31.25.90.131 +31.25.90.132 +31.25.90.142 +31.25.90.143 +31.25.90.145 +31.25.90.146 +31.25.90.147 +31.25.90.148 +31.25.90.149 +31.25.90.150 +31.25.90.151 +31.25.90.152 +31.25.90.158 +31.25.90.163 +31.25.90.164 +31.25.90.165 +31.25.90.166 +31.25.90.167 +31.25.90.168 +31.25.90.169 +31.25.90.170 +31.25.90.171 +31.25.90.172 +31.25.90.176 +31.25.90.177 +31.25.90.178 +31.25.90.179 +31.25.90.180 +31.25.90.182 +31.25.90.189 +31.25.90.194 +31.25.90.229 +31.25.90.242 +31.25.91.4 +31.25.91.5 +31.25.91.6 +31.25.91.9 +31.25.91.10 +31.25.91.11 +31.25.91.12 +31.25.91.13 +31.25.91.15 +31.25.91.22 +31.25.91.40 +31.25.91.41 +31.25.91.84 +31.25.91.85 +31.25.91.87 +31.25.91.97 +31.25.91.112 +31.25.91.113 +31.25.91.115 +31.25.91.124 +31.25.91.204 +31.25.95.227 +31.47.38.137 +31.47.42.2 +31.47.43.228 +31.47.43.241 +31.47.43.246 +31.47.45.2 +31.47.46.34 +31.47.46.35 +31.47.46.36 +31.47.46.37 +31.47.46.38 +31.47.46.41 +31.47.46.42 +31.47.46.44 +31.47.46.46 +31.47.46.51 +31.47.46.53 +31.47.46.54 +31.47.46.56 +31.47.46.59 +31.47.46.60 +31.47.46.61 +31.47.46.62 +31.47.46.67 +31.47.46.69 +31.47.46.70 +31.47.46.72 +31.47.46.73 +31.47.46.75 +31.47.46.76 +31.47.46.81 +31.47.46.83 +31.47.46.84 +31.47.46.86 +31.47.46.87 +31.47.51.2 +31.47.51.32 +31.47.51.102 +31.47.53.2 +31.47.55.162 +31.47.56.131 +31.47.61.33 +31.47.61.66 +31.47.61.99 +31.47.62.193 +31.58.237.101 +31.58.237.107 +31.130.182.10 +31.170.49.43 +31.171.222.180 +31.171.223.114 +31.184.129.66 +31.184.130.2 +31.184.130.252 +31.184.131.3 +31.184.134.159 +31.184.134.161 +31.184.134.178 +31.184.136.173 +31.184.160.109 +31.184.170.203 +31.184.171.128 +31.184.191.246 +31.193.186.2 +31.193.186.3 +31.193.186.4 +31.193.186.5 +31.193.186.6 +31.193.186.7 +31.193.186.8 +31.193.186.9 +31.193.186.10 +31.193.186.11 +31.193.186.12 +31.193.186.13 +31.193.186.14 +31.214.168.26 +31.214.168.35 +31.214.168.42 +31.214.168.68 +31.214.168.100 +31.214.168.132 +31.214.168.162 +31.214.168.164 +31.214.168.166 +31.214.168.179 +31.214.168.180 +31.214.168.229 +31.214.168.246 +31.214.169.21 +31.214.169.22 +31.214.169.206 +31.214.169.244 +31.214.170.74 +31.214.170.163 +31.214.170.194 +31.214.170.213 +31.214.171.86 +31.214.171.130 +31.214.171.132 +31.214.171.133 +31.214.171.134 +31.214.171.135 +31.214.171.170 +31.214.171.171 +31.214.171.174 +31.214.172.6 +31.214.172.11 +31.214.172.78 +31.214.172.183 +31.214.172.184 +31.214.173.30 +31.214.173.76 +31.214.173.170 +31.214.173.178 +31.214.173.210 +31.214.173.244 +31.214.173.254 +31.214.174.4 +31.214.174.6 +31.214.174.27 +31.214.174.90 +31.214.174.91 +31.214.174.92 +31.214.174.93 +31.214.174.99 +31.214.174.114 +31.214.174.125 +31.214.174.163 +31.214.174.166 +31.214.174.194 +31.214.174.195 +31.214.174.196 +31.214.174.197 +31.214.174.205 +31.214.175.10 +31.214.175.11 +31.214.175.13 +31.214.175.14 +31.214.175.146 +31.214.175.185 +31.214.175.214 +31.214.175.220 +31.214.175.246 +31.214.228.242 +31.214.228.243 +31.214.229.212 +31.214.231.121 +31.214.248.27 +31.214.248.60 +31.214.248.74 +31.214.248.75 +31.214.248.76 +31.214.248.77 +31.214.248.78 +31.214.248.102 +31.214.248.146 +31.214.248.147 +31.214.248.174 +31.214.250.14 +31.214.250.28 +31.214.250.42 +31.214.250.69 +31.214.250.70 +31.214.250.72 +31.214.250.75 +31.214.250.76 +31.214.250.77 +31.214.250.83 +31.214.250.84 +31.214.250.88 +31.214.250.90 +31.214.250.91 +31.214.250.92 +31.214.250.94 +31.214.250.96 +31.214.250.98 +31.214.250.99 +31.214.250.100 +31.214.250.101 +31.214.250.102 +31.214.250.106 +31.214.250.108 +31.214.250.109 +31.214.250.112 +31.214.250.113 +31.214.250.114 +31.214.250.116 +31.214.250.118 +31.214.250.121 +31.214.250.122 +31.214.250.123 +31.214.250.125 +31.214.250.198 +31.214.250.199 +31.214.250.201 +31.214.250.203 +31.214.250.209 +31.214.250.210 +31.214.250.211 +31.214.250.213 +31.214.250.214 +31.214.250.215 +31.214.250.216 +31.214.250.217 +31.214.250.218 +31.214.250.219 +31.214.250.220 +31.214.250.221 +31.214.251.6 +31.214.251.147 +31.214.251.148 +31.214.251.149 +31.214.251.194 +31.214.251.196 +31.214.251.199 +31.214.251.200 +31.214.251.201 +31.214.251.202 +31.214.251.203 +31.214.251.204 +31.214.251.206 +31.214.251.207 +31.214.251.208 +31.214.251.209 +31.214.251.215 +31.214.251.217 +31.214.251.219 +31.214.251.220 +31.214.251.228 +31.214.251.230 +31.214.251.231 +31.214.251.232 +31.214.251.233 +31.214.251.234 +31.214.251.238 +31.214.251.239 +31.214.251.241 +31.214.251.247 +31.214.251.253 +31.214.255.50 +31.214.255.53 +31.214.255.54 +31.214.255.139 +31.214.255.140 +31.214.255.152 +31.214.255.154 +31.214.255.155 +31.214.255.156 +31.214.255.160 +31.214.255.161 +31.214.255.163 +31.214.255.165 +31.214.255.166 +31.214.255.167 +31.214.255.168 +31.214.255.169 +31.214.255.170 +31.214.255.171 +31.214.255.176 +31.214.255.177 +31.214.255.178 +31.214.255.179 +31.214.255.180 +31.214.255.181 +31.214.255.182 +31.214.255.183 +31.214.255.184 +31.214.255.185 +31.214.255.186 +31.214.255.187 +31.214.255.188 +31.214.255.189 +31.216.62.30 +31.216.62.39 +31.216.62.58 +31.216.62.60 +31.216.62.82 +31.216.62.86 +31.216.62.93 +31.216.62.102 +31.216.62.107 +31.216.62.118 +31.216.62.131 +31.216.62.136 +31.216.62.137 +31.216.62.144 +31.216.62.146 +31.216.62.147 +31.216.62.158 +31.216.62.170 +31.216.62.203 +31.216.62.212 +31.216.62.227 +31.216.62.248 +37.9.253.32 +37.9.253.33 +37.10.67.10 +37.10.67.18 +37.10.109.29 +37.32.2.2 +37.32.4.41 +37.32.4.124 +37.32.8.52 +37.32.8.55 +37.32.8.100 +37.32.8.180 +37.32.8.208 +37.32.8.253 +37.32.9.100 +37.32.9.117 +37.32.9.118 +37.32.9.248 +37.32.10.21 +37.32.10.53 +37.32.10.120 +37.32.10.151 +37.32.10.227 +37.32.10.254 +37.32.11.26 +37.32.12.41 +37.32.12.48 +37.32.12.50 +37.32.12.52 +37.32.12.146 +37.32.13.105 +37.32.13.171 +37.32.14.47 +37.32.14.114 +37.32.14.184 +37.32.14.226 +37.32.14.234 +37.32.15.0 +37.32.15.18 +37.32.15.85 +37.32.15.155 +37.32.15.205 +37.32.15.208 +37.32.15.248 +37.32.22.248 +37.32.24.35 +37.32.24.126 +37.32.24.156 +37.32.24.192 +37.32.25.162 +37.32.25.174 +37.32.25.185 +37.32.26.165 +37.32.27.138 +37.32.27.222 +37.32.28.174 +37.32.29.195 +37.32.29.247 +37.32.43.208 +37.32.43.224 +37.32.44.58 +37.32.45.18 +37.32.45.20 +37.32.46.131 +37.32.46.132 +37.32.46.133 +37.32.46.134 +37.32.46.135 +37.32.46.136 +37.32.46.137 +37.32.46.138 +37.32.46.139 +37.32.46.141 +37.32.46.142 +37.32.46.143 +37.32.46.144 +37.32.46.145 +37.32.46.146 +37.32.46.147 +37.32.46.148 +37.32.46.149 +37.32.46.162 +37.32.46.163 +37.32.46.165 +37.32.46.166 +37.32.46.168 +37.32.46.169 +37.32.46.171 +37.32.46.172 +37.32.46.174 +37.32.46.175 +37.32.47.196 +37.32.120.138 +37.32.120.139 +37.32.120.148 +37.32.121.133 +37.32.125.138 +37.32.126.130 +37.32.127.99 +37.32.127.100 +37.32.127.106 +37.75.240.5 +37.75.240.14 +37.75.240.15 +37.75.240.16 +37.75.241.200 +37.130.202.14 +37.130.202.66 +37.130.206.6 +37.130.206.7 +37.143.148.145 +37.148.0.167 +37.148.0.222 +37.148.3.35 +37.148.3.51 +37.148.4.83 +37.148.13.79 +37.148.13.187 +37.148.15.80 +37.148.15.221 +37.148.18.165 +37.148.20.117 +37.148.21.64 +37.148.23.120 +37.148.23.172 +37.148.31.13 +37.148.31.44 +37.148.40.88 +37.148.42.47 +37.148.45.196 +37.148.48.54 +37.148.49.252 +37.148.50.125 +37.148.50.202 +37.148.57.6 +37.148.58.5 +37.148.59.134 +37.148.61.147 +37.148.81.227 +37.148.82.138 +37.148.83.154 +37.148.83.210 +37.148.84.245 +37.148.85.86 +37.148.85.180 +37.148.87.111 +37.148.91.8 +37.148.248.50 +37.148.248.77 +37.152.160.27 +37.152.173.170 +37.152.173.215 +37.152.175.67 +37.152.176.118 +37.152.176.179 +37.152.176.237 +37.152.177.84 +37.152.177.138 +37.152.179.31 +37.152.179.69 +37.152.180.124 +37.152.180.157 +37.152.180.240 +37.152.181.60 +37.152.181.115 +37.152.181.142 +37.152.182.41 +37.152.182.119 +37.152.182.187 +37.152.183.92 +37.152.186.170 +37.152.186.203 +37.152.186.224 +37.152.188.11 +37.152.188.16 +37.152.188.131 +37.152.188.132 +37.152.188.220 +37.152.188.254 +37.152.189.114 +37.152.189.128 +37.152.189.156 +37.152.190.8 +37.152.190.9 +37.152.190.80 +37.152.190.239 +37.152.190.252 +37.152.191.223 +37.156.8.90 +37.156.9.203 +37.156.9.255 +37.156.11.220 +37.156.14.221 +37.156.20.91 +37.156.144.82 +37.156.144.83 +37.156.144.85 +37.156.144.91 +37.156.144.125 +37.156.144.202 +37.156.144.203 +37.156.144.204 +37.156.145.84 +37.156.145.86 +37.156.145.136 +37.156.145.137 +37.156.145.138 +37.156.145.139 +37.156.145.141 +37.156.145.144 +37.156.145.146 +37.156.145.147 +37.156.145.152 +37.156.145.153 +37.156.145.155 +37.156.145.158 +37.156.145.197 +37.156.145.251 +37.156.145.252 +37.156.146.39 +37.156.146.40 +37.156.146.45 +37.156.146.61 +37.156.146.105 +37.156.146.109 +37.156.146.125 +37.156.146.166 +37.156.146.245 +37.156.147.43 +37.156.147.110 +37.156.147.116 +37.156.147.125 +37.156.147.220 +37.156.147.221 +37.156.147.242 +37.156.147.243 +37.156.147.244 +37.156.147.245 +37.156.147.246 +37.191.76.202 +37.191.77.81 +37.191.79.136 +37.191.92.114 +37.191.92.115 +37.191.92.116 +37.191.92.117 +37.191.92.118 +37.191.92.119 +37.191.93.195 +37.191.93.196 +37.191.93.202 +37.191.93.203 +37.191.94.35 +37.191.94.44 +37.191.95.6 +37.191.95.7 +37.191.95.13 +37.191.95.14 +37.191.95.70 +37.191.95.82 +37.191.95.86 +37.191.95.99 +37.191.95.100 +37.202.148.139 +37.202.154.171 +37.202.158.11 +37.202.159.84 +37.202.159.125 +37.202.159.230 +37.202.169.144 +37.202.169.202 +37.202.169.251 +37.202.170.32 +37.202.171.239 +37.202.172.64 +37.202.172.82 +37.202.182.143 +37.202.185.61 +37.202.185.66 +37.202.185.132 +37.202.188.85 +37.202.189.203 +37.202.191.176 +37.202.225.95 +37.202.225.173 +37.202.225.206 +37.202.225.224 +37.202.229.195 +37.202.229.200 +37.202.229.205 +37.202.229.209 +37.202.229.215 +37.202.229.220 +37.202.232.222 +37.202.236.4 +37.202.236.12 +37.202.236.100 +37.202.236.126 +37.202.237.2 +37.202.237.195 +37.202.237.242 +37.202.237.246 +37.202.238.11 +37.202.246.70 +37.228.136.98 +37.228.136.157 +37.228.136.181 +37.228.137.35 +37.228.137.66 +37.228.137.76 +37.228.138.34 +37.228.138.123 +37.228.138.134 +37.228.138.138 +37.228.138.154 +37.228.138.155 +37.228.138.201 +37.228.138.203 +37.228.139.3 +37.228.139.5 +37.228.139.26 +37.228.139.40 +37.228.139.41 +37.228.139.107 +37.228.139.229 +37.255.128.78 +37.255.128.250 +37.255.132.14 +37.255.132.54 +37.255.135.36 +37.255.148.218 +37.255.148.221 +37.255.173.171 +37.255.173.172 +37.255.177.60 +37.255.177.138 +37.255.177.153 +37.255.186.102 +37.255.187.164 +37.255.193.188 +37.255.194.13 +37.255.194.69 +37.255.198.80 +37.255.201.39 +37.255.202.25 +37.255.202.70 +37.255.208.38 +37.255.212.55 +37.255.213.220 +37.255.216.121 +37.255.223.36 +37.255.223.218 +37.255.223.240 +37.255.223.243 +37.255.223.244 +37.255.230.35 +37.255.234.14 +37.255.234.34 +37.255.236.67 +37.255.239.57 +37.255.239.175 +37.255.242.68 +37.255.245.39 +37.255.249.150 +37.255.249.166 +37.255.249.172 +37.255.249.205 +37.255.249.220 +45.9.252.79 +45.11.184.151 +45.11.185.10 +45.11.185.148 +45.11.187.103 +45.15.200.31 +45.81.16.36 +45.81.16.155 +45.81.16.217 +45.81.17.109 +45.81.17.159 +45.81.17.160 +45.81.17.177 +45.81.17.182 +45.81.17.183 +45.81.17.184 +45.81.17.196 +45.81.17.197 +45.81.17.199 +45.81.18.104 +45.81.18.109 +45.81.19.49 +45.81.19.60 +45.81.19.133 +45.82.136.141 +45.82.136.148 +45.82.138.40 +45.82.138.113 +45.82.138.224 +45.82.139.150 +45.86.87.11 +45.86.87.84 +45.86.87.85 +45.86.87.150 +45.86.87.242 +45.89.139.5 +45.89.236.7 +45.89.236.11 +45.89.236.13 +45.89.236.15 +45.89.236.16 +45.89.236.17 +45.89.236.28 +45.89.236.29 +45.89.236.30 +45.89.236.40 +45.89.236.41 +45.89.236.47 +45.89.236.48 +45.89.236.51 +45.89.236.52 +45.89.236.53 +45.89.236.55 +45.89.236.59 +45.89.236.67 +45.89.236.68 +45.89.236.73 +45.89.236.74 +45.89.236.87 +45.89.236.89 +45.89.236.133 +45.89.236.153 +45.89.236.154 +45.89.236.158 +45.89.236.163 +45.89.236.173 +45.89.236.179 +45.89.236.180 +45.89.236.183 +45.89.236.185 +45.89.236.187 +45.89.236.190 +45.89.236.227 +45.89.236.228 +45.89.236.247 +45.89.236.249 +45.89.236.252 +45.89.237.18 +45.89.237.24 +45.89.237.67 +45.89.237.68 +45.89.237.70 +45.89.237.72 +45.89.237.80 +45.89.237.81 +45.89.237.82 +45.89.237.85 +45.89.237.86 +45.89.237.101 +45.89.237.109 +45.89.237.158 +45.89.238.179 +45.89.238.183 +45.89.238.185 +45.89.238.187 +45.89.238.189 +45.89.238.190 +45.89.239.33 +45.89.239.35 +45.89.239.39 +45.89.239.40 +45.89.239.41 +45.89.239.42 +45.89.239.43 +45.89.239.44 +45.89.239.45 +45.89.239.109 +45.89.239.110 +45.89.239.121 +45.89.239.130 +45.89.239.190 +45.89.239.210 +45.89.239.228 +45.89.239.234 +45.89.239.238 +45.89.239.252 +45.89.239.253 +45.90.72.14 +45.90.72.85 +45.90.72.153 +45.90.72.161 +45.90.72.172 +45.90.72.227 +45.90.72.228 +45.90.73.12 +45.90.73.14 +45.90.73.15 +45.90.73.16 +45.90.73.30 +45.90.73.31 +45.90.73.56 +45.90.73.61 +45.90.73.66 +45.90.73.68 +45.90.73.153 +45.90.73.154 +45.90.73.187 +45.90.73.205 +45.90.73.206 +45.90.73.208 +45.90.73.209 +45.90.74.14 +45.90.74.24 +45.90.74.29 +45.90.74.61 +45.90.74.104 +45.90.74.114 +45.90.74.159 +45.90.74.174 +45.90.74.175 +45.90.74.176 +45.90.74.185 +45.90.74.192 +45.90.74.202 +45.90.74.204 +45.90.74.217 +45.90.74.241 +45.90.74.253 +45.90.75.75 +45.91.152.3 +45.91.152.10 +45.91.152.11 +45.91.153.10 +45.92.92.2 +45.92.92.3 +45.92.92.4 +45.92.92.5 +45.92.92.6 +45.92.92.7 +45.92.92.8 +45.92.92.9 +45.92.92.11 +45.92.92.12 +45.92.92.13 +45.92.92.14 +45.92.92.15 +45.92.92.16 +45.92.92.17 +45.92.92.19 +45.92.92.20 +45.92.92.22 +45.92.92.23 +45.92.92.24 +45.92.92.26 +45.92.92.27 +45.92.92.28 +45.92.92.29 +45.92.92.30 +45.92.92.31 +45.92.92.32 +45.92.92.34 +45.92.92.35 +45.92.92.36 +45.92.92.38 +45.92.92.39 +45.92.92.40 +45.92.92.42 +45.92.92.43 +45.92.92.44 +45.92.92.47 +45.92.92.50 +45.92.92.51 +45.92.92.52 +45.92.92.54 +45.92.92.55 +45.92.92.56 +45.92.92.57 +45.92.92.58 +45.92.92.59 +45.92.92.60 +45.92.92.61 +45.92.92.62 +45.92.92.63 +45.92.92.64 +45.92.92.65 +45.92.92.66 +45.92.92.67 +45.92.92.68 +45.92.92.70 +45.92.92.71 +45.92.92.72 +45.92.92.74 +45.92.92.75 +45.92.92.77 +45.92.92.78 +45.92.92.79 +45.92.92.80 +45.92.92.81 +45.92.92.82 +45.92.92.83 +45.92.92.84 +45.92.92.85 +45.92.92.86 +45.92.92.87 +45.92.92.88 +45.92.92.89 +45.92.92.90 +45.92.92.91 +45.92.92.93 +45.92.92.94 +45.92.92.95 +45.92.92.96 +45.92.92.97 +45.92.92.98 +45.92.92.99 +45.92.92.100 +45.92.92.101 +45.92.92.102 +45.92.92.103 +45.92.92.104 +45.92.92.105 +45.92.92.106 +45.92.92.107 +45.92.92.108 +45.92.92.109 +45.92.92.110 +45.92.92.111 +45.92.92.112 +45.92.92.113 +45.92.92.115 +45.92.92.116 +45.92.92.117 +45.92.92.118 +45.92.92.119 +45.92.92.120 +45.92.92.121 +45.92.92.122 +45.92.92.123 +45.92.92.124 +45.92.92.125 +45.92.92.127 +45.92.92.128 +45.92.92.129 +45.92.92.130 +45.92.92.131 +45.92.92.132 +45.92.92.133 +45.92.92.134 +45.92.92.135 +45.92.92.136 +45.92.92.137 +45.92.92.139 +45.92.92.140 +45.92.92.141 +45.92.92.142 +45.92.92.143 +45.92.92.144 +45.92.92.145 +45.92.92.146 +45.92.92.147 +45.92.92.148 +45.92.92.149 +45.92.92.150 +45.92.92.152 +45.92.92.153 +45.92.92.154 +45.92.92.156 +45.92.92.157 +45.92.92.158 +45.92.92.159 +45.92.92.161 +45.92.92.163 +45.92.92.164 +45.92.92.165 +45.92.92.166 +45.92.92.168 +45.92.92.169 +45.92.92.170 +45.92.92.172 +45.92.92.174 +45.92.92.176 +45.92.92.177 +45.92.92.178 +45.92.92.179 +45.92.92.181 +45.92.92.182 +45.92.92.183 +45.92.92.185 +45.92.92.186 +45.92.92.187 +45.92.92.188 +45.92.92.190 +45.92.92.191 +45.92.92.192 +45.92.92.193 +45.92.92.194 +45.92.92.195 +45.92.92.197 +45.92.92.199 +45.92.92.201 +45.92.92.202 +45.92.92.203 +45.92.92.204 +45.92.92.205 +45.92.92.206 +45.92.92.207 +45.92.92.208 +45.92.92.209 +45.92.92.210 +45.92.92.211 +45.92.92.212 +45.92.92.213 +45.92.92.214 +45.92.92.215 +45.92.92.216 +45.92.92.217 +45.92.92.218 +45.92.92.219 +45.92.92.220 +45.92.92.221 +45.92.92.222 +45.92.92.223 +45.92.92.224 +45.92.92.225 +45.92.92.226 +45.92.92.227 +45.92.92.228 +45.92.92.229 +45.92.92.231 +45.92.92.232 +45.92.92.233 +45.92.92.234 +45.92.92.235 +45.92.92.236 +45.92.92.237 +45.92.92.238 +45.92.92.239 +45.92.92.240 +45.92.92.241 +45.92.92.242 +45.92.92.243 +45.92.92.244 +45.92.92.245 +45.92.92.246 +45.92.92.248 +45.92.92.249 +45.92.92.250 +45.92.92.251 +45.92.92.252 +45.92.92.253 +45.92.93.18 +45.92.93.48 +45.92.93.70 +45.92.93.94 +45.92.93.97 +45.92.93.98 +45.92.93.104 +45.92.93.118 +45.92.93.137 +45.92.93.138 +45.92.93.139 +45.92.93.151 +45.92.93.162 +45.92.93.164 +45.92.93.172 +45.92.93.180 +45.92.93.249 +45.92.93.250 +45.92.93.251 +45.92.94.2 +45.92.94.3 +45.92.94.5 +45.92.94.6 +45.92.94.8 +45.92.94.9 +45.92.94.10 +45.92.94.16 +45.92.94.19 +45.92.94.21 +45.92.94.22 +45.92.94.34 +45.92.94.46 +45.92.94.47 +45.92.94.48 +45.92.94.49 +45.92.94.50 +45.92.94.51 +45.92.94.52 +45.92.94.53 +45.92.94.54 +45.92.94.55 +45.92.94.56 +45.92.94.57 +45.92.94.58 +45.92.94.59 +45.92.94.60 +45.92.94.89 +45.92.94.97 +45.92.94.109 +45.92.94.151 +45.92.94.170 +45.92.94.171 +45.92.94.226 +45.92.94.244 +45.92.95.67 +45.92.95.68 +45.92.95.70 +45.92.95.254 +45.94.212.13 +45.94.214.22 +45.94.214.230 +45.94.254.34 +45.94.254.69 +45.94.254.70 +45.94.255.189 +45.94.255.222 +45.129.36.6 +45.129.36.10 +45.129.36.19 +45.129.36.28 +45.129.36.30 +45.129.36.42 +45.129.36.58 +45.129.36.59 +45.129.36.60 +45.129.36.61 +45.129.36.62 +45.129.36.114 +45.129.36.162 +45.129.36.166 +45.129.36.182 +45.129.37.10 +45.129.37.70 +45.129.37.71 +45.129.37.72 +45.129.37.76 +45.129.37.98 +45.129.37.115 +45.129.37.116 +45.129.37.117 +45.129.37.118 +45.129.37.150 +45.129.37.151 +45.129.37.178 +45.129.37.183 +45.129.37.184 +45.129.38.34 +45.129.38.37 +45.129.38.52 +45.129.38.54 +45.129.38.61 +45.129.38.66 +45.129.38.130 +45.129.38.140 +45.129.38.170 +45.129.38.173 +45.129.39.90 +45.129.39.117 +45.129.39.146 +45.129.39.195 +45.129.39.206 +45.132.32.4 +45.132.32.5 +45.132.32.222 +45.132.172.90 +45.132.172.95 +45.132.172.241 +45.135.241.12 +45.135.241.22 +45.135.241.33 +45.135.241.70 +45.135.241.74 +45.135.241.83 +45.135.241.95 +45.135.241.129 +45.135.241.144 +45.135.241.228 +45.135.241.237 +45.135.241.245 +45.135.241.253 +45.135.243.22 +45.135.243.30 +45.135.243.34 +45.135.243.56 +45.135.243.58 +45.135.243.73 +45.135.243.76 +45.135.243.78 +45.135.243.79 +45.135.243.85 +45.135.243.87 +45.135.243.88 +45.135.243.92 +45.135.243.94 +45.135.243.105 +45.135.243.206 +45.135.243.216 +45.135.243.217 +45.135.243.222 +45.135.243.226 +45.135.243.227 +45.135.243.235 +45.137.16.10 +45.137.17.120 +45.137.17.133 +45.137.17.242 +45.137.17.243 +45.137.17.244 +45.137.17.245 +45.138.132.25 +45.138.132.26 +45.138.132.28 +45.138.132.29 +45.138.132.30 +45.138.132.31 +45.138.132.38 +45.138.132.43 +45.138.132.46 +45.138.132.47 +45.138.132.48 +45.138.132.57 +45.138.132.71 +45.138.132.80 +45.138.132.117 +45.138.132.118 +45.138.132.136 +45.138.132.174 +45.138.132.193 +45.138.132.204 +45.138.132.247 +45.138.132.249 +45.138.133.3 +45.138.133.4 +45.138.133.5 +45.138.133.6 +45.138.133.7 +45.138.133.8 +45.138.133.9 +45.138.133.10 +45.138.133.11 +45.138.133.12 +45.138.133.13 +45.138.133.14 +45.138.133.15 +45.138.133.16 +45.138.133.17 +45.138.133.18 +45.138.133.20 +45.138.133.21 +45.138.133.22 +45.138.133.23 +45.138.133.24 +45.138.133.25 +45.138.133.26 +45.138.133.28 +45.138.133.29 +45.138.133.30 +45.138.133.32 +45.138.133.33 +45.138.133.34 +45.138.133.35 +45.138.133.36 +45.138.133.37 +45.138.133.38 +45.138.133.39 +45.138.133.41 +45.138.133.42 +45.138.133.44 +45.138.133.45 +45.138.133.46 +45.138.133.47 +45.138.133.49 +45.138.133.51 +45.138.133.52 +45.138.133.53 +45.138.133.56 +45.138.133.57 +45.138.133.60 +45.138.133.61 +45.138.133.62 +45.138.133.64 +45.138.133.65 +45.138.133.66 +45.138.133.67 +45.138.133.69 +45.138.133.70 +45.138.133.71 +45.138.133.72 +45.138.133.73 +45.138.133.75 +45.138.133.76 +45.138.133.77 +45.138.133.78 +45.138.133.79 +45.138.133.81 +45.138.133.85 +45.138.133.89 +45.138.133.90 +45.138.133.91 +45.138.133.92 +45.138.133.93 +45.138.133.94 +45.138.133.95 +45.138.133.96 +45.138.133.98 +45.138.133.99 +45.138.133.100 +45.138.133.101 +45.138.133.102 +45.138.133.103 +45.138.133.104 +45.138.133.105 +45.138.133.107 +45.138.133.108 +45.138.133.110 +45.138.133.111 +45.138.133.112 +45.138.133.114 +45.138.133.115 +45.138.133.117 +45.138.133.118 +45.138.133.122 +45.138.133.123 +45.138.133.124 +45.138.133.125 +45.138.133.128 +45.138.133.129 +45.138.133.130 +45.138.133.131 +45.138.133.132 +45.138.133.133 +45.138.133.134 +45.138.133.136 +45.138.133.137 +45.138.133.138 +45.138.133.140 +45.138.133.141 +45.138.133.142 +45.138.133.143 +45.138.133.144 +45.138.133.147 +45.138.133.148 +45.138.133.149 +45.138.133.150 +45.138.133.151 +45.138.133.152 +45.138.133.154 +45.138.133.155 +45.138.133.156 +45.138.133.157 +45.138.133.158 +45.138.133.159 +45.138.133.160 +45.138.133.162 +45.138.133.163 +45.138.133.164 +45.138.133.165 +45.138.133.167 +45.138.133.168 +45.138.133.169 +45.138.133.170 +45.138.133.171 +45.138.133.172 +45.138.133.173 +45.138.133.174 +45.138.133.175 +45.138.133.176 +45.138.133.177 +45.138.133.178 +45.138.133.179 +45.138.133.180 +45.138.133.181 +45.138.133.182 +45.138.133.183 +45.138.133.184 +45.138.133.185 +45.138.133.186 +45.138.133.187 +45.138.133.188 +45.138.133.189 +45.138.133.190 +45.138.133.191 +45.138.133.192 +45.138.133.193 +45.138.133.194 +45.138.133.195 +45.138.133.196 +45.138.133.197 +45.138.133.199 +45.138.133.200 +45.138.133.201 +45.138.133.202 +45.138.133.203 +45.138.133.204 +45.138.133.206 +45.138.133.207 +45.138.133.208 +45.138.133.209 +45.138.133.211 +45.138.133.212 +45.138.133.213 +45.138.133.214 +45.138.133.215 +45.138.133.217 +45.138.133.218 +45.138.133.219 +45.138.133.220 +45.138.133.221 +45.138.133.223 +45.138.133.224 +45.138.133.225 +45.138.133.226 +45.138.133.227 +45.138.133.228 +45.138.133.229 +45.138.133.230 +45.138.133.231 +45.138.133.232 +45.138.133.233 +45.138.133.234 +45.138.133.235 +45.138.133.236 +45.138.133.237 +45.138.133.238 +45.138.133.239 +45.138.133.240 +45.138.133.241 +45.138.133.242 +45.138.133.243 +45.138.133.244 +45.138.133.245 +45.138.133.247 +45.138.133.248 +45.138.133.249 +45.138.133.250 +45.138.133.251 +45.138.133.252 +45.138.133.253 +45.138.133.254 +45.138.134.2 +45.138.134.3 +45.138.134.4 +45.138.134.5 +45.138.134.6 +45.138.134.7 +45.138.134.8 +45.138.134.9 +45.138.134.10 +45.138.134.11 +45.138.134.12 +45.138.134.13 +45.138.134.14 +45.138.134.16 +45.138.134.17 +45.138.134.18 +45.138.134.20 +45.138.134.21 +45.138.134.22 +45.138.134.23 +45.138.134.24 +45.138.134.25 +45.138.134.27 +45.138.134.28 +45.138.134.29 +45.138.134.30 +45.138.134.33 +45.138.134.34 +45.138.134.35 +45.138.134.36 +45.138.134.37 +45.138.134.38 +45.138.134.39 +45.138.134.40 +45.138.134.41 +45.138.134.42 +45.138.134.43 +45.138.134.45 +45.138.134.46 +45.138.134.47 +45.138.134.48 +45.138.134.49 +45.138.134.50 +45.138.134.51 +45.138.134.52 +45.138.134.53 +45.138.134.54 +45.138.134.55 +45.138.134.56 +45.138.134.57 +45.138.134.58 +45.138.134.59 +45.138.134.60 +45.138.134.61 +45.138.134.62 +45.138.134.63 +45.138.134.64 +45.138.134.65 +45.138.134.67 +45.138.134.68 +45.138.134.69 +45.138.134.70 +45.138.134.71 +45.138.134.72 +45.138.134.73 +45.138.134.74 +45.138.134.75 +45.138.134.76 +45.138.134.77 +45.138.134.78 +45.138.134.79 +45.138.134.80 +45.138.134.82 +45.138.134.83 +45.138.134.84 +45.138.134.85 +45.138.134.86 +45.138.134.87 +45.138.134.88 +45.138.134.89 +45.138.134.90 +45.138.134.91 +45.138.134.92 +45.138.134.93 +45.138.134.94 +45.138.134.97 +45.138.134.98 +45.138.134.100 +45.138.134.101 +45.138.134.102 +45.138.134.103 +45.138.134.104 +45.138.134.106 +45.138.134.107 +45.138.134.108 +45.138.134.109 +45.138.134.110 +45.138.134.111 +45.138.134.112 +45.138.134.113 +45.138.134.114 +45.138.134.115 +45.138.134.116 +45.138.134.118 +45.138.134.119 +45.138.134.120 +45.138.134.121 +45.138.134.122 +45.138.134.123 +45.138.134.124 +45.138.134.125 +45.138.134.126 +45.138.134.127 +45.138.134.130 +45.138.134.131 +45.138.134.132 +45.138.134.133 +45.138.134.135 +45.138.134.136 +45.138.134.137 +45.138.134.138 +45.138.134.139 +45.138.134.140 +45.138.134.141 +45.138.134.143 +45.138.134.145 +45.138.134.146 +45.138.134.147 +45.138.134.149 +45.138.134.150 +45.138.134.151 +45.138.134.152 +45.138.134.153 +45.138.134.155 +45.138.134.157 +45.138.134.158 +45.138.134.159 +45.138.134.161 +45.138.134.162 +45.138.134.163 +45.138.134.164 +45.138.134.165 +45.138.134.166 +45.138.134.167 +45.138.134.168 +45.138.134.171 +45.138.134.172 +45.138.134.173 +45.138.134.175 +45.138.134.177 +45.138.134.178 +45.138.134.179 +45.138.134.180 +45.138.134.181 +45.138.134.184 +45.138.134.185 +45.138.134.187 +45.138.134.188 +45.138.134.189 +45.138.134.190 +45.138.134.191 +45.138.134.192 +45.138.134.193 +45.138.134.195 +45.138.134.196 +45.138.134.197 +45.138.134.198 +45.138.134.200 +45.138.134.203 +45.138.134.205 +45.138.134.206 +45.138.134.207 +45.138.134.209 +45.138.134.210 +45.138.134.211 +45.138.134.212 +45.138.134.213 +45.138.134.214 +45.138.134.215 +45.138.134.216 +45.138.134.217 +45.138.134.218 +45.138.134.219 +45.138.134.220 +45.138.134.221 +45.138.134.223 +45.138.134.224 +45.138.134.225 +45.138.134.226 +45.138.134.227 +45.138.134.230 +45.138.134.233 +45.138.134.234 +45.138.134.236 +45.138.134.237 +45.138.134.238 +45.138.134.239 +45.138.134.240 +45.138.134.241 +45.138.134.242 +45.138.134.243 +45.138.134.244 +45.138.134.245 +45.138.134.246 +45.138.134.247 +45.138.134.250 +45.138.134.252 +45.138.134.253 +45.138.135.4 +45.138.135.6 +45.138.135.7 +45.138.135.8 +45.138.135.9 +45.138.135.10 +45.138.135.11 +45.138.135.12 +45.138.135.13 +45.138.135.14 +45.138.135.15 +45.138.135.16 +45.138.135.17 +45.138.135.18 +45.138.135.19 +45.138.135.21 +45.138.135.22 +45.138.135.23 +45.138.135.24 +45.138.135.25 +45.138.135.26 +45.138.135.27 +45.138.135.28 +45.138.135.29 +45.138.135.30 +45.138.135.31 +45.138.135.35 +45.138.135.38 +45.138.135.42 +45.138.135.43 +45.138.135.45 +45.138.135.48 +45.138.135.51 +45.138.135.52 +45.138.135.59 +45.138.135.83 +45.138.135.97 +45.138.135.114 +45.138.135.115 +45.138.135.116 +45.138.135.120 +45.138.135.131 +45.138.135.140 +45.138.135.145 +45.138.135.153 +45.138.135.158 +45.138.135.163 +45.138.135.171 +45.138.135.176 +45.138.135.182 +45.138.135.185 +45.138.135.186 +45.138.135.190 +45.138.135.202 +45.138.135.203 +45.138.135.205 +45.138.135.209 +45.138.135.222 +45.138.135.223 +45.138.135.231 +45.138.135.233 +45.138.135.238 +45.138.135.241 +45.138.135.250 +45.138.135.251 +45.139.10.8 +45.139.10.23 +45.139.10.37 +45.139.10.42 +45.139.10.45 +45.139.10.51 +45.139.10.52 +45.139.10.53 +45.139.10.58 +45.139.10.65 +45.139.10.68 +45.139.10.70 +45.139.10.82 +45.139.10.86 +45.139.10.87 +45.139.10.92 +45.139.10.100 +45.139.10.108 +45.139.10.110 +45.139.10.113 +45.139.10.119 +45.139.10.120 +45.139.10.124 +45.139.10.126 +45.139.10.130 +45.139.10.133 +45.139.10.136 +45.139.10.139 +45.139.10.143 +45.139.10.157 +45.139.10.167 +45.139.10.181 +45.139.10.190 +45.139.10.194 +45.139.10.195 +45.139.10.198 +45.139.10.203 +45.139.10.210 +45.139.10.213 +45.139.10.215 +45.139.10.224 +45.139.10.233 +45.139.10.242 +45.139.10.243 +45.139.10.246 +45.139.10.247 +45.139.10.251 +45.139.10.252 +45.139.10.253 +45.139.11.5 +45.139.11.7 +45.139.11.29 +45.139.11.43 +45.139.11.45 +45.139.11.46 +45.139.11.47 +45.139.11.48 +45.139.11.50 +45.139.11.51 +45.139.11.60 +45.139.11.72 +45.139.11.78 +45.139.11.83 +45.139.11.89 +45.139.11.98 +45.139.11.100 +45.139.11.102 +45.139.11.107 +45.139.11.108 +45.139.11.115 +45.139.11.124 +45.139.11.125 +45.139.11.128 +45.139.11.133 +45.139.11.135 +45.139.11.137 +45.139.11.139 +45.139.11.140 +45.139.11.142 +45.139.11.145 +45.139.11.146 +45.139.11.149 +45.139.11.155 +45.139.11.157 +45.139.11.164 +45.139.11.169 +45.139.11.170 +45.139.11.174 +45.139.11.177 +45.139.11.179 +45.139.11.187 +45.139.11.194 +45.139.11.212 +45.139.11.216 +45.139.11.219 +45.139.11.222 +45.139.11.226 +45.139.11.229 +45.139.11.244 +45.142.189.25 +45.142.189.68 +45.142.189.114 +45.142.189.200 +45.142.191.152 +45.144.18.2 +45.144.18.6 +45.144.18.10 +45.144.18.12 +45.144.18.17 +45.144.18.20 +45.144.18.50 +45.144.18.51 +45.144.18.58 +45.144.18.59 +45.144.18.60 +45.144.18.66 +45.144.18.72 +45.144.18.73 +45.144.18.100 +45.144.18.102 +45.144.18.104 +45.144.18.110 +45.144.18.115 +45.144.18.118 +45.144.18.119 +45.144.18.124 +45.144.18.129 +45.144.18.130 +45.147.76.66 +45.147.76.115 +45.147.76.118 +45.147.76.119 +45.147.76.120 +45.147.76.121 +45.147.76.138 +45.147.76.171 +45.147.76.173 +45.147.76.211 +45.147.76.249 +45.147.76.250 +45.147.76.251 +45.147.76.253 +45.147.76.254 +45.147.77.50 +45.147.77.53 +45.147.77.82 +45.147.77.83 +45.147.77.91 +45.147.77.221 +45.147.78.21 +45.147.78.40 +45.147.78.99 +45.147.78.198 +45.147.78.200 +45.147.78.201 +45.147.78.204 +45.147.78.243 +45.148.250.246 +45.149.76.11 +45.149.76.13 +45.149.76.14 +45.149.76.15 +45.149.76.16 +45.149.76.17 +45.149.76.22 +45.149.76.26 +45.149.76.27 +45.149.76.28 +45.149.76.29 +45.149.76.31 +45.149.76.34 +45.149.76.36 +45.149.76.37 +45.149.76.39 +45.149.76.47 +45.149.76.49 +45.149.76.50 +45.149.76.59 +45.149.76.64 +45.149.76.69 +45.149.76.87 +45.149.76.90 +45.149.76.93 +45.149.76.94 +45.149.76.96 +45.149.76.117 +45.149.76.130 +45.149.76.131 +45.149.76.141 +45.149.76.144 +45.149.76.145 +45.149.76.148 +45.149.76.151 +45.149.76.154 +45.149.76.156 +45.149.76.157 +45.149.76.164 +45.149.76.170 +45.149.76.172 +45.149.76.173 +45.149.76.174 +45.149.76.187 +45.149.76.192 +45.149.76.204 +45.149.76.210 +45.149.76.213 +45.149.76.218 +45.149.76.226 +45.149.76.229 +45.149.76.230 +45.149.76.231 +45.149.76.234 +45.149.76.244 +45.149.76.249 +45.149.76.250 +45.149.77.5 +45.149.77.7 +45.149.77.12 +45.149.77.25 +45.149.77.30 +45.149.77.31 +45.149.77.35 +45.149.77.45 +45.149.77.51 +45.149.77.61 +45.149.77.73 +45.149.77.77 +45.149.77.80 +45.149.77.84 +45.149.77.85 +45.149.77.87 +45.149.77.92 +45.149.77.94 +45.149.77.95 +45.149.77.97 +45.149.77.100 +45.149.77.101 +45.149.77.108 +45.149.77.126 +45.149.77.131 +45.149.77.139 +45.149.77.143 +45.149.77.147 +45.149.77.154 +45.149.77.162 +45.149.77.166 +45.149.77.167 +45.149.77.175 +45.149.77.181 +45.149.77.182 +45.149.77.191 +45.149.77.196 +45.149.77.204 +45.149.77.222 +45.149.77.224 +45.149.77.232 +45.149.77.233 +45.149.77.236 +45.149.77.247 +45.149.77.248 +45.149.78.5 +45.149.78.16 +45.149.78.17 +45.149.78.20 +45.149.78.21 +45.149.78.30 +45.149.78.33 +45.149.78.44 +45.149.78.45 +45.149.78.53 +45.149.78.56 +45.149.78.59 +45.149.78.61 +45.149.78.64 +45.149.78.69 +45.149.78.75 +45.149.78.82 +45.149.78.83 +45.149.78.87 +45.149.78.89 +45.149.78.92 +45.149.78.95 +45.149.78.96 +45.149.78.101 +45.149.78.106 +45.149.78.114 +45.149.78.120 +45.149.78.121 +45.149.78.131 +45.149.78.132 +45.149.78.135 +45.149.78.142 +45.149.78.143 +45.149.78.146 +45.149.78.159 +45.149.78.169 +45.149.78.172 +45.149.78.176 +45.149.78.180 +45.149.78.197 +45.149.78.207 +45.149.78.211 +45.149.78.220 +45.149.78.224 +45.149.78.233 +45.149.78.234 +45.149.78.246 +45.149.78.248 +45.149.78.253 +45.149.79.5 +45.149.79.8 +45.149.79.9 +45.149.79.13 +45.149.79.15 +45.149.79.18 +45.149.79.25 +45.149.79.26 +45.149.79.29 +45.149.79.36 +45.149.79.50 +45.149.79.54 +45.149.79.62 +45.149.79.64 +45.149.79.67 +45.149.79.71 +45.149.79.80 +45.149.79.84 +45.149.79.88 +45.149.79.98 +45.149.79.104 +45.149.79.105 +45.149.79.111 +45.149.79.114 +45.149.79.125 +45.149.79.127 +45.149.79.132 +45.149.79.133 +45.149.79.134 +45.149.79.139 +45.149.79.142 +45.149.79.152 +45.149.79.160 +45.149.79.162 +45.149.79.164 +45.149.79.165 +45.149.79.167 +45.149.79.169 +45.149.79.171 +45.149.79.174 +45.149.79.185 +45.149.79.186 +45.149.79.189 +45.149.79.190 +45.149.79.194 +45.149.79.199 +45.149.79.204 +45.149.79.210 +45.149.79.212 +45.149.79.223 +45.149.79.243 +45.149.79.250 +45.156.184.24 +45.156.184.25 +45.156.184.27 +45.156.184.28 +45.156.184.29 +45.156.184.30 +45.156.184.31 +45.156.184.33 +45.156.184.34 +45.156.184.35 +45.156.184.37 +45.156.184.38 +45.156.184.39 +45.156.184.41 +45.156.184.42 +45.156.184.47 +45.156.184.51 +45.156.184.104 +45.156.184.243 +45.156.185.5 +45.156.185.8 +45.156.185.43 +45.156.185.45 +45.156.185.48 +45.156.185.53 +45.156.185.69 +45.156.185.77 +45.156.185.78 +45.156.185.83 +45.156.185.88 +45.156.185.91 +45.156.185.92 +45.156.185.98 +45.156.185.131 +45.156.185.151 +45.156.185.157 +45.156.185.166 +45.156.185.170 +45.156.185.171 +45.156.185.188 +45.156.185.195 +45.156.185.199 +45.156.185.222 +45.156.185.230 +45.156.185.233 +45.156.185.249 +45.156.186.17 +45.156.186.19 +45.156.186.25 +45.156.186.41 +45.156.186.42 +45.156.186.92 +45.156.186.128 +45.156.186.183 +45.156.186.200 +45.156.186.246 +45.156.187.38 +45.156.187.48 +45.156.187.62 +45.156.187.99 +45.156.187.102 +45.156.187.108 +45.156.187.110 +45.156.187.134 +45.157.244.200 +45.157.244.201 +45.159.112.5 +45.159.112.11 +45.159.112.12 +45.159.112.14 +45.159.112.16 +45.159.112.17 +45.159.112.19 +45.159.112.21 +45.159.112.25 +45.159.112.30 +45.159.112.33 +45.159.112.34 +45.159.112.35 +45.159.112.36 +45.159.112.37 +45.159.112.60 +45.159.112.63 +45.159.112.65 +45.159.112.66 +45.159.112.68 +45.159.112.70 +45.159.112.72 +45.159.112.73 +45.159.112.81 +45.159.112.82 +45.159.112.85 +45.159.112.92 +45.159.112.96 +45.159.112.115 +45.159.112.116 +45.159.112.117 +45.159.112.118 +45.159.112.124 +45.159.112.130 +45.159.112.131 +45.159.112.132 +45.159.112.135 +45.159.112.136 +45.159.112.141 +45.159.112.147 +45.159.112.149 +45.159.112.152 +45.159.112.168 +45.159.112.171 +45.159.112.186 +45.159.112.203 +45.159.112.205 +45.159.112.216 +45.159.113.16 +45.159.113.18 +45.159.113.22 +45.159.113.39 +45.159.113.41 +45.159.113.51 +45.159.113.64 +45.159.113.66 +45.159.113.67 +45.159.113.72 +45.159.113.88 +45.159.113.91 +45.159.113.98 +45.159.113.101 +45.159.113.102 +45.159.113.103 +45.159.113.106 +45.159.113.108 +45.159.113.110 +45.159.113.111 +45.159.113.116 +45.159.113.117 +45.159.113.118 +45.159.113.119 +45.159.113.121 +45.159.113.123 +45.159.113.124 +45.159.113.126 +45.159.113.128 +45.159.113.129 +45.159.113.133 +45.159.113.134 +45.159.113.135 +45.159.113.137 +45.159.113.146 +45.159.113.149 +45.159.113.154 +45.159.113.157 +45.159.113.160 +45.159.113.163 +45.159.113.171 +45.159.113.194 +45.159.113.200 +45.159.113.202 +45.159.113.203 +45.159.113.204 +45.159.113.206 +45.159.113.208 +45.159.113.211 +45.159.113.216 +45.159.113.224 +45.159.113.225 +45.159.113.228 +45.159.113.236 +45.159.113.249 +45.159.114.211 +45.159.114.213 +45.159.114.215 +45.159.114.219 +45.159.114.220 +45.159.114.227 +45.159.114.229 +45.159.114.230 +45.159.114.231 +45.159.114.232 +45.159.114.239 +45.159.114.241 +45.159.114.251 +45.159.115.7 +45.159.115.10 +45.159.115.11 +45.159.115.12 +45.159.115.14 +45.159.115.15 +45.159.115.21 +45.159.115.32 +45.159.115.38 +45.159.115.39 +45.159.115.40 +45.159.115.44 +45.159.115.51 +45.159.115.52 +45.159.115.68 +45.159.115.79 +45.159.115.81 +45.159.115.86 +45.159.115.88 +45.159.115.90 +45.159.115.91 +45.159.115.92 +45.159.115.93 +45.159.115.98 +45.159.115.100 +45.159.115.101 +45.159.115.103 +45.159.115.105 +45.159.115.107 +45.159.115.108 +45.159.115.109 +45.159.115.110 +45.159.115.112 +45.159.115.114 +45.159.115.115 +45.159.115.116 +45.159.115.118 +45.159.115.120 +45.159.115.123 +45.159.115.124 +45.159.115.126 +45.159.115.128 +45.159.115.130 +45.159.115.136 +45.159.115.137 +45.159.115.138 +45.159.115.139 +45.159.115.141 +45.159.115.142 +45.159.115.146 +45.159.115.147 +45.159.115.149 +45.159.115.154 +45.159.115.156 +45.159.115.157 +45.159.115.158 +45.159.115.159 +45.159.115.162 +45.159.115.163 +45.159.115.165 +45.159.115.170 +45.159.115.173 +45.159.115.174 +45.159.115.175 +45.159.115.179 +45.159.115.180 +45.159.115.181 +45.159.115.183 +45.159.115.185 +45.159.115.186 +45.159.115.188 +45.159.115.190 +45.159.115.197 +45.159.115.200 +45.159.115.205 +45.159.115.215 +45.159.115.218 +45.159.115.230 +45.159.115.231 +45.159.115.232 +45.159.115.234 +45.159.115.238 +45.159.149.5 +45.159.149.6 +45.159.149.7 +45.159.149.8 +45.159.149.13 +45.159.149.15 +45.159.149.17 +45.159.149.19 +45.159.149.25 +45.159.149.29 +45.159.149.31 +45.159.149.42 +45.159.149.52 +45.159.149.55 +45.159.149.63 +45.159.149.64 +45.159.149.70 +45.159.149.79 +45.159.149.83 +45.159.149.89 +45.159.149.91 +45.159.149.92 +45.159.149.95 +45.159.149.101 +45.159.149.105 +45.159.149.109 +45.159.149.125 +45.159.149.127 +45.159.149.135 +45.159.149.146 +45.159.149.147 +45.159.149.154 +45.159.149.160 +45.159.149.165 +45.159.149.172 +45.159.149.180 +45.159.149.186 +45.159.149.209 +45.159.149.211 +45.159.149.215 +45.159.149.217 +45.159.149.232 +45.159.149.252 +45.159.149.253 +45.159.150.5 +45.159.150.12 +45.159.150.14 +45.159.150.20 +45.159.150.26 +45.159.150.27 +45.159.150.41 +45.159.150.53 +45.159.150.69 +45.159.150.75 +45.159.150.78 +45.159.150.88 +45.159.150.89 +45.159.150.100 +45.159.150.107 +45.159.150.115 +45.159.150.129 +45.159.150.133 +45.159.150.136 +45.159.150.142 +45.159.150.146 +45.159.150.152 +45.159.150.166 +45.159.150.173 +45.159.150.179 +45.159.150.181 +45.159.150.184 +45.159.150.194 +45.159.150.204 +45.159.150.221 +45.159.150.222 +45.159.150.223 +45.159.150.224 +45.159.150.231 +45.159.150.242 +45.159.150.244 +45.159.150.245 +45.159.151.8 +45.159.151.25 +45.159.151.33 +45.159.151.34 +45.159.151.35 +45.159.151.42 +45.159.151.44 +45.159.151.48 +45.159.151.53 +45.159.151.55 +45.159.151.56 +45.159.151.58 +45.159.151.59 +45.159.151.61 +45.159.151.63 +45.159.151.64 +45.159.151.65 +45.159.151.66 +45.159.151.67 +45.159.151.70 +45.159.151.71 +45.159.151.73 +45.159.151.74 +45.159.151.75 +45.159.151.76 +45.159.151.77 +45.159.151.78 +45.159.151.79 +45.159.151.80 +45.159.151.81 +45.159.151.82 +45.159.151.84 +45.159.151.85 +45.159.151.86 +45.159.151.87 +45.159.151.88 +45.159.151.89 +45.159.151.90 +45.159.151.91 +45.159.151.92 +45.159.151.93 +45.159.151.94 +45.159.151.95 +45.159.151.99 +45.159.151.100 +45.159.151.101 +45.159.151.102 +45.159.151.109 +45.159.151.110 +45.159.151.111 +45.159.151.112 +45.159.151.113 +45.159.151.114 +45.159.151.115 +45.159.151.116 +45.159.151.117 +45.159.151.118 +45.159.151.119 +45.159.151.120 +45.159.151.121 +45.159.151.122 +45.159.151.123 +45.159.151.124 +45.159.151.125 +45.159.151.126 +45.159.151.129 +45.159.151.133 +45.159.151.134 +45.159.151.135 +45.159.151.136 +45.159.151.138 +45.159.151.145 +45.159.151.146 +45.159.151.147 +45.159.151.149 +45.159.151.150 +45.159.151.151 +45.159.151.152 +45.159.151.154 +45.159.151.155 +45.159.151.156 +45.159.151.158 +45.159.151.159 +45.159.151.160 +45.159.151.161 +45.159.151.162 +45.159.151.163 +45.159.151.164 +45.159.151.165 +45.159.151.166 +45.159.151.167 +45.159.151.168 +45.159.151.170 +45.159.151.171 +45.159.196.5 +45.159.197.66 +45.159.197.99 +45.159.197.139 +45.159.197.141 +45.159.198.4 +45.159.198.5 +45.159.198.9 +45.159.198.10 +45.159.198.182 +45.159.198.251 +45.159.199.130 +46.18.248.136 +46.28.74.35 +46.28.78.11 +46.28.78.12 +46.32.4.137 +46.32.4.165 +46.32.4.244 +46.32.10.50 +46.32.11.130 +46.32.11.131 +46.32.12.72 +46.32.15.196 +46.32.16.68 +46.32.17.68 +46.32.17.70 +46.32.26.18 +46.32.30.74 +46.32.30.81 +46.32.31.29 +46.32.31.30 +46.34.160.41 +46.34.160.56 +46.34.162.76 +46.34.163.85 +46.34.165.1 +46.34.166.60 +46.34.166.210 +46.34.167.120 +46.36.96.1 +46.36.96.65 +46.36.96.66 +46.36.107.98 +46.36.107.99 +46.36.111.2 +46.36.111.3 +46.38.138.47 +46.38.138.84 +46.38.138.89 +46.38.138.92 +46.38.138.156 +46.38.138.168 +46.38.142.66 +46.38.146.131 +46.38.146.163 +46.38.147.10 +46.38.150.195 +46.38.150.243 +46.38.159.21 +46.38.159.23 +46.100.5.49 +46.100.8.21 +46.100.10.64 +46.100.12.158 +46.100.12.255 +46.100.13.60 +46.100.41.110 +46.100.41.240 +46.100.45.147 +46.100.47.154 +46.100.48.239 +46.100.58.214 +46.100.63.131 +46.100.74.83 +46.100.84.60 +46.100.85.5 +46.100.90.13 +46.100.90.93 +46.100.90.166 +46.100.95.206 +46.100.166.125 +46.100.167.215 +46.100.167.222 +46.100.177.148 +46.100.218.22 +46.100.249.63 +46.100.249.195 +46.102.129.202 +46.102.129.203 +46.102.130.2 +46.102.130.31 +46.102.130.116 +46.102.130.165 +46.102.130.166 +46.102.130.215 +46.102.130.229 +46.102.132.112 +46.102.136.2 +46.102.136.33 +46.102.136.34 +46.102.136.35 +46.102.136.36 +46.102.136.75 +46.102.136.77 +46.102.136.89 +46.102.136.93 +46.102.137.18 +46.102.138.219 +46.102.138.222 +46.102.139.45 +46.102.139.66 +46.102.139.67 +46.102.139.68 +46.102.139.69 +46.102.139.70 +46.102.139.98 +46.102.139.118 +46.102.140.16 +46.102.140.45 +46.102.141.156 +46.148.34.202 +46.148.35.162 +46.148.35.163 +46.148.36.9 +46.148.41.47 +46.148.47.1 +46.148.47.27 +46.167.135.183 +46.167.139.74 +46.167.139.125 +46.167.141.33 +46.167.141.196 +46.167.144.175 +46.167.146.37 +46.167.146.174 +46.167.146.202 +46.167.146.231 +46.167.158.79 +46.167.159.116 +46.209.7.114 +46.209.7.115 +46.209.7.116 +46.209.7.136 +46.209.9.178 +46.209.11.8 +46.209.11.30 +46.209.16.181 +46.209.20.86 +46.209.20.154 +46.209.20.181 +46.209.22.219 +46.209.25.195 +46.209.30.13 +46.209.31.248 +46.209.44.74 +46.209.50.15 +46.209.50.133 +46.209.59.66 +46.209.59.67 +46.209.62.250 +46.209.67.150 +46.209.69.121 +46.209.69.123 +46.209.73.211 +46.209.89.19 +46.209.92.54 +46.209.92.147 +46.209.115.6 +46.209.128.217 +46.209.150.220 +46.209.157.179 +46.209.191.3 +46.209.191.4 +46.209.206.205 +46.209.207.153 +46.209.207.154 +46.209.207.202 +46.209.207.203 +46.209.209.209 +46.209.211.13 +46.209.213.114 +46.209.216.247 +46.209.221.92 +46.209.239.189 +46.209.239.190 +46.209.240.130 +46.209.241.66 +46.209.244.246 +46.209.251.12 +46.235.77.195 +46.245.0.105 +46.245.4.205 +46.245.5.30 +46.245.6.140 +46.245.8.140 +46.245.13.16 +46.245.16.181 +46.245.17.163 +46.245.24.138 +46.245.28.206 +46.245.34.157 +46.245.38.100 +46.245.38.101 +46.245.39.140 +46.245.39.163 +46.245.43.60 +46.245.43.87 +46.245.45.87 +46.245.45.174 +46.245.64.24 +46.245.64.36 +46.245.64.37 +46.245.64.38 +46.245.64.55 +46.245.64.132 +46.245.66.13 +46.245.66.26 +46.245.66.61 +46.245.66.72 +46.245.66.91 +46.245.66.120 +46.245.66.137 +46.245.67.36 +46.245.67.101 +46.245.67.141 +46.245.67.162 +46.245.68.169 +46.245.69.35 +46.245.69.94 +46.245.69.109 +46.245.69.110 +46.245.69.133 +46.245.69.134 +46.245.69.135 +46.245.69.215 +46.245.69.222 +46.245.69.229 +46.245.69.234 +46.245.72.183 +46.245.73.17 +46.245.73.26 +46.245.76.4 +46.245.76.5 +46.245.76.6 +46.245.76.7 +46.245.76.8 +46.245.76.9 +46.245.76.10 +46.245.76.11 +46.245.76.12 +46.245.76.13 +46.245.76.14 +46.245.76.28 +46.245.76.29 +46.245.76.50 +46.245.76.51 +46.245.76.52 +46.245.76.53 +46.245.76.54 +46.245.76.116 +46.245.76.162 +46.245.76.171 +46.245.76.172 +46.245.76.173 +46.245.76.174 +46.245.77.7 +46.245.77.8 +46.245.77.70 +46.245.77.75 +46.245.77.76 +46.245.77.77 +46.245.77.78 +46.245.78.18 +46.245.78.19 +46.245.78.227 +46.245.79.11 +46.245.79.134 +46.245.79.135 +46.245.79.136 +46.245.79.137 +46.245.79.235 +46.245.80.35 +46.245.80.75 +46.245.80.77 +46.245.80.82 +46.245.80.206 +46.245.81.54 +46.245.81.61 +46.245.81.68 +46.245.81.90 +46.245.83.130 +46.245.84.44 +46.245.85.3 +46.245.85.22 +46.245.85.27 +46.245.85.28 +46.245.85.29 +46.245.85.36 +46.245.85.37 +46.245.85.44 +46.245.85.179 +46.245.86.26 +46.245.86.28 +46.245.86.29 +46.245.86.30 +46.245.86.177 +46.245.86.186 +46.245.86.190 +46.245.86.242 +46.245.86.245 +46.245.87.68 +46.245.87.69 +46.245.87.72 +46.245.87.74 +46.245.87.75 +46.245.87.105 +46.245.87.110 +46.245.87.132 +46.245.87.222 +46.245.88.170 +46.245.88.171 +46.245.88.172 +46.245.88.173 +46.245.88.174 +46.245.89.100 +46.245.89.130 +46.245.89.131 +46.245.89.133 +46.245.89.136 +46.245.89.138 +46.245.89.141 +46.245.89.203 +46.245.91.214 +46.245.91.227 +46.245.91.228 +46.245.91.229 +46.245.91.231 +46.245.91.233 +46.245.91.237 +46.245.91.238 +46.245.91.245 +46.245.92.2 +46.245.92.58 +46.245.92.85 +46.245.92.86 +46.245.92.107 +46.245.92.108 +46.245.92.109 +46.245.92.116 +46.245.92.185 +46.245.93.42 +46.245.93.115 +46.245.93.201 +46.245.93.202 +46.245.93.209 +46.245.94.109 +46.245.94.163 +46.245.94.170 +46.245.94.171 +46.245.94.172 +46.245.95.99 +46.245.95.133 +46.245.95.136 +46.245.95.152 +46.245.95.160 +46.245.95.162 +46.245.95.165 +46.245.95.237 +46.245.96.216 +46.245.98.13 +46.245.99.40 +46.245.99.142 +46.245.100.90 +46.245.107.28 +46.245.107.29 +46.245.107.164 +46.245.107.195 +46.245.107.198 +46.245.107.199 +46.245.107.201 +46.245.107.202 +46.245.107.205 +46.245.112.61 +46.245.114.228 +46.245.115.51 +46.245.115.56 +46.245.115.201 +46.245.115.213 +46.245.120.150 +46.245.122.8 +46.245.122.24 +46.245.122.25 +46.245.123.19 +46.245.123.20 +46.245.123.21 +46.245.123.23 +46.245.123.25 +46.245.124.20 +46.245.124.48 +46.249.122.162 +46.249.125.22 +62.3.14.59 +62.3.14.68 +62.3.14.132 +62.3.14.133 +62.3.14.134 +62.3.14.135 +62.3.14.140 +62.3.14.153 +62.3.14.154 +62.3.14.162 +62.3.14.163 +62.3.14.164 +62.3.14.167 +62.3.14.168 +62.3.14.169 +62.3.14.203 +62.3.14.204 +62.3.14.205 +62.3.41.8 +62.3.41.9 +62.3.41.28 +62.3.41.32 +62.3.41.36 +62.3.41.45 +62.3.41.47 +62.3.41.50 +62.3.41.53 +62.3.41.66 +62.3.41.73 +62.3.41.77 +62.3.41.82 +62.3.41.87 +62.3.41.89 +62.3.41.92 +62.3.41.111 +62.3.41.114 +62.3.41.117 +62.3.41.122 +62.3.41.129 +62.3.41.132 +62.3.41.135 +62.3.41.149 +62.3.41.159 +62.3.41.160 +62.3.41.178 +62.3.41.179 +62.3.41.191 +62.3.41.194 +62.3.41.198 +62.3.41.199 +62.3.41.200 +62.3.41.210 +62.3.41.215 +62.3.41.226 +62.3.41.227 +62.3.41.228 +62.3.41.229 +62.3.41.233 +62.3.41.237 +62.3.41.242 +62.3.41.253 +62.3.42.17 +62.3.42.32 +62.3.42.34 +62.3.42.47 +62.3.42.59 +62.3.42.85 +62.3.42.90 +62.3.42.99 +62.3.42.105 +62.3.42.106 +62.3.42.111 +62.3.42.119 +62.3.42.126 +62.3.42.131 +62.3.42.134 +62.3.42.137 +62.3.42.139 +62.3.42.145 +62.3.42.171 +62.3.42.176 +62.3.42.187 +62.3.42.198 +62.3.42.206 +62.3.42.215 +62.3.42.237 +62.3.42.238 +62.60.128.8 +62.60.128.11 +62.60.128.42 +62.60.128.49 +62.60.128.54 +62.60.128.56 +62.60.128.58 +62.60.128.59 +62.60.128.89 +62.60.128.105 +62.60.128.119 +62.60.128.120 +62.60.128.122 +62.60.128.124 +62.60.128.125 +62.60.128.126 +62.60.128.127 +62.60.128.209 +62.60.129.186 +62.60.129.189 +62.60.132.61 +62.60.132.133 +62.60.132.174 +62.60.132.248 +62.60.133.4 +62.60.133.5 +62.60.133.6 +62.60.133.7 +62.60.133.9 +62.60.133.10 +62.60.133.11 +62.60.133.12 +62.60.133.13 +62.60.133.14 +62.60.133.15 +62.60.133.16 +62.60.133.17 +62.60.133.18 +62.60.133.19 +62.60.133.20 +62.60.133.22 +62.60.133.23 +62.60.133.24 +62.60.133.25 +62.60.133.26 +62.60.133.27 +62.60.133.29 +62.60.133.30 +62.60.133.31 +62.60.133.32 +62.60.133.33 +62.60.133.34 +62.60.133.36 +62.60.133.37 +62.60.133.38 +62.60.133.39 +62.60.133.40 +62.60.133.41 +62.60.133.42 +62.60.133.43 +62.60.133.44 +62.60.133.45 +62.60.133.47 +62.60.133.48 +62.60.133.49 +62.60.133.50 +62.60.133.51 +62.60.133.52 +62.60.133.53 +62.60.133.54 +62.60.133.55 +62.60.133.56 +62.60.133.58 +62.60.133.59 +62.60.133.61 +62.60.133.62 +62.60.133.63 +62.60.133.64 +62.60.133.65 +62.60.133.66 +62.60.133.67 +62.60.133.68 +62.60.133.69 +62.60.133.70 +62.60.133.71 +62.60.133.72 +62.60.133.73 +62.60.133.74 +62.60.133.75 +62.60.133.76 +62.60.133.77 +62.60.133.78 +62.60.133.79 +62.60.133.80 +62.60.133.83 +62.60.133.84 +62.60.133.85 +62.60.133.86 +62.60.133.87 +62.60.133.88 +62.60.133.89 +62.60.133.90 +62.60.133.91 +62.60.133.92 +62.60.133.93 +62.60.133.94 +62.60.133.95 +62.60.133.96 +62.60.133.97 +62.60.133.98 +62.60.133.99 +62.60.133.100 +62.60.133.102 +62.60.133.103 +62.60.133.104 +62.60.133.105 +62.60.133.106 +62.60.133.107 +62.60.133.109 +62.60.133.110 +62.60.133.111 +62.60.133.112 +62.60.133.113 +62.60.133.114 +62.60.133.115 +62.60.133.116 +62.60.133.117 +62.60.133.118 +62.60.133.119 +62.60.133.120 +62.60.133.121 +62.60.133.122 +62.60.133.123 +62.60.133.124 +62.60.133.125 +62.60.133.126 +62.60.133.127 +62.60.133.128 +62.60.133.130 +62.60.133.131 +62.60.133.132 +62.60.133.133 +62.60.133.134 +62.60.133.135 +62.60.133.136 +62.60.133.137 +62.60.133.138 +62.60.133.139 +62.60.133.140 +62.60.133.141 +62.60.133.142 +62.60.133.143 +62.60.133.144 +62.60.133.145 +62.60.133.146 +62.60.133.147 +62.60.133.148 +62.60.133.149 +62.60.133.150 +62.60.133.152 +62.60.133.153 +62.60.133.154 +62.60.133.155 +62.60.133.156 +62.60.133.157 +62.60.133.158 +62.60.133.159 +62.60.133.160 +62.60.133.161 +62.60.133.162 +62.60.133.163 +62.60.133.164 +62.60.133.165 +62.60.133.166 +62.60.133.167 +62.60.133.168 +62.60.133.169 +62.60.133.170 +62.60.133.171 +62.60.133.172 +62.60.133.173 +62.60.133.174 +62.60.133.175 +62.60.133.176 +62.60.133.177 +62.60.133.178 +62.60.133.179 +62.60.133.180 +62.60.133.181 +62.60.133.182 +62.60.133.183 +62.60.133.184 +62.60.133.185 +62.60.133.186 +62.60.133.187 +62.60.133.188 +62.60.133.189 +62.60.133.190 +62.60.133.191 +62.60.133.192 +62.60.133.194 +62.60.133.195 +62.60.133.196 +62.60.133.197 +62.60.133.198 +62.60.133.201 +62.60.133.202 +62.60.133.203 +62.60.133.205 +62.60.133.207 +62.60.133.208 +62.60.133.209 +62.60.133.210 +62.60.133.228 +62.60.133.238 +62.60.133.243 +62.60.133.245 +62.60.133.246 +62.60.133.247 +62.60.133.249 +62.60.133.250 +62.60.133.251 +62.60.133.252 +62.60.133.253 +62.60.133.254 +62.60.136.105 +62.60.136.158 +62.60.136.190 +62.60.136.215 +62.60.136.225 +62.60.137.2 +62.60.140.47 +62.60.140.251 +62.60.141.4 +62.60.144.44 +62.60.144.45 +62.60.144.68 +62.60.144.85 +62.60.144.87 +62.60.160.28 +62.60.160.33 +62.60.160.34 +62.60.160.38 +62.60.160.40 +62.60.160.75 +62.60.160.79 +62.60.160.80 +62.60.160.100 +62.60.161.109 +62.60.161.115 +62.60.162.96 +62.60.162.100 +62.60.162.102 +62.60.162.104 +62.60.162.109 +62.60.162.110 +62.60.162.206 +62.60.162.247 +62.60.163.203 +62.60.164.19 +62.60.164.32 +62.60.164.43 +62.60.164.49 +62.60.164.133 +62.60.164.137 +62.60.164.158 +62.60.164.191 +62.60.164.197 +62.60.164.198 +62.60.164.206 +62.60.164.207 +62.60.164.214 +62.60.164.216 +62.60.164.217 +62.60.164.219 +62.60.164.242 +62.60.165.23 +62.60.165.151 +62.60.165.203 +62.60.165.216 +62.60.165.219 +62.60.165.230 +62.60.165.233 +62.60.165.236 +62.60.165.239 +62.60.165.241 +62.60.165.246 +62.60.165.248 +62.60.166.50 +62.60.167.38 +62.60.167.66 +62.60.167.67 +62.60.167.68 +62.60.167.69 +62.60.167.74 +62.60.167.194 +62.60.167.202 +62.60.167.203 +62.60.167.204 +62.60.167.205 +62.60.167.206 +62.60.167.210 +62.60.167.234 +62.60.167.242 +62.60.167.250 +62.60.190.61 +62.60.196.13 +62.60.196.26 +62.60.196.44 +62.60.196.235 +62.60.196.238 +62.60.197.3 +62.60.197.4 +62.60.197.5 +62.60.197.6 +62.60.197.7 +62.60.197.8 +62.60.197.9 +62.60.197.10 +62.60.197.12 +62.60.197.14 +62.60.197.83 +62.60.197.85 +62.60.197.222 +62.60.198.38 +62.60.198.239 +62.60.200.93 +62.60.200.94 +62.60.200.118 +62.60.200.131 +62.60.200.144 +62.60.200.196 +62.60.200.197 +62.60.200.203 +62.60.200.214 +62.60.200.252 +62.60.201.3 +62.60.201.4 +62.60.201.5 +62.60.201.6 +62.60.201.7 +62.60.201.8 +62.60.201.9 +62.60.201.10 +62.60.201.12 +62.60.201.13 +62.60.201.14 +62.60.201.15 +62.60.201.16 +62.60.201.17 +62.60.201.18 +62.60.201.20 +62.60.201.21 +62.60.201.22 +62.60.201.24 +62.60.201.25 +62.60.201.26 +62.60.201.27 +62.60.201.28 +62.60.201.29 +62.60.201.30 +62.60.201.31 +62.60.201.32 +62.60.201.33 +62.60.201.34 +62.60.201.62 +62.60.201.63 +62.60.201.64 +62.60.201.65 +62.60.201.66 +62.60.201.67 +62.60.201.68 +62.60.201.69 +62.60.201.70 +62.60.201.71 +62.60.201.72 +62.60.201.73 +62.60.201.74 +62.60.201.75 +62.60.201.76 +62.60.201.77 +62.60.201.78 +62.60.201.79 +62.60.201.80 +62.60.201.81 +62.60.201.82 +62.60.201.83 +62.60.201.84 +62.60.201.85 +62.60.201.86 +62.60.201.87 +62.60.201.88 +62.60.201.89 +62.60.201.90 +62.60.201.92 +62.60.201.93 +62.60.201.94 +62.60.201.95 +62.60.201.97 +62.60.201.99 +62.60.201.100 +62.60.201.102 +62.60.201.103 +62.60.201.104 +62.60.201.105 +62.60.201.106 +62.60.201.107 +62.60.201.108 +62.60.201.109 +62.60.201.110 +62.60.201.111 +62.60.201.112 +62.60.201.113 +62.60.201.115 +62.60.201.116 +62.60.201.117 +62.60.201.118 +62.60.201.119 +62.60.201.120 +62.60.201.121 +62.60.201.122 +62.60.201.123 +62.60.201.124 +62.60.201.125 +62.60.201.126 +62.60.201.127 +62.60.201.128 +62.60.201.130 +62.60.201.131 +62.60.201.132 +62.60.201.133 +62.60.201.134 +62.60.201.135 +62.60.201.136 +62.60.201.137 +62.60.201.138 +62.60.201.139 +62.60.201.140 +62.60.201.141 +62.60.201.142 +62.60.201.143 +62.60.201.144 +62.60.201.145 +62.60.201.146 +62.60.201.147 +62.60.201.148 +62.60.201.149 +62.60.201.151 +62.60.201.152 +62.60.201.153 +62.60.201.154 +62.60.201.155 +62.60.201.156 +62.60.201.157 +62.60.201.158 +62.60.201.159 +62.60.201.160 +62.60.201.162 +62.60.201.163 +62.60.201.164 +62.60.201.165 +62.60.201.166 +62.60.201.167 +62.60.201.168 +62.60.201.169 +62.60.201.170 +62.60.201.171 +62.60.201.173 +62.60.201.174 +62.60.201.175 +62.60.201.176 +62.60.201.177 +62.60.201.180 +62.60.201.182 +62.60.201.183 +62.60.201.184 +62.60.201.185 +62.60.201.186 +62.60.201.187 +62.60.201.189 +62.60.201.190 +62.60.201.191 +62.60.201.192 +62.60.201.193 +62.60.201.194 +62.60.201.196 +62.60.201.197 +62.60.201.198 +62.60.201.199 +62.60.201.200 +62.60.201.201 +62.60.201.203 +62.60.201.204 +62.60.201.205 +62.60.201.206 +62.60.201.207 +62.60.201.208 +62.60.201.209 +62.60.201.210 +62.60.201.211 +62.60.201.212 +62.60.201.213 +62.60.201.214 +62.60.201.215 +62.60.201.218 +62.60.201.219 +62.60.201.220 +62.60.201.221 +62.60.201.222 +62.60.201.223 +62.60.201.224 +62.60.201.225 +62.60.201.226 +62.60.201.227 +62.60.201.228 +62.60.201.229 +62.60.201.230 +62.60.201.231 +62.60.201.232 +62.60.201.233 +62.60.201.234 +62.60.201.235 +62.60.201.236 +62.60.201.238 +62.60.201.239 +62.60.201.240 +62.60.201.241 +62.60.201.243 +62.60.201.244 +62.60.201.245 +62.60.201.246 +62.60.201.247 +62.60.201.248 +62.60.201.249 +62.60.201.250 +62.60.201.251 +62.60.201.252 +62.60.201.253 +62.60.202.5 +62.60.202.33 +62.60.202.50 +62.60.202.61 +62.60.202.158 +62.60.202.243 +62.60.203.14 +62.60.203.16 +62.60.203.23 +62.60.203.46 +62.60.203.47 +62.60.204.5 +62.60.204.17 +62.60.204.123 +62.60.204.162 +62.60.204.187 +62.60.204.207 +62.60.204.233 +62.60.204.234 +62.60.205.3 +62.60.205.4 +62.60.205.5 +62.60.205.6 +62.60.205.7 +62.60.205.8 +62.60.205.9 +62.60.205.10 +62.60.205.11 +62.60.205.12 +62.60.205.13 +62.60.205.14 +62.60.205.15 +62.60.205.16 +62.60.205.17 +62.60.205.18 +62.60.205.19 +62.60.205.20 +62.60.205.21 +62.60.205.22 +62.60.205.23 +62.60.205.25 +62.60.205.26 +62.60.205.27 +62.60.205.28 +62.60.205.29 +62.60.205.30 +62.60.205.31 +62.60.205.32 +62.60.205.33 +62.60.205.34 +62.60.205.35 +62.60.205.36 +62.60.205.37 +62.60.205.38 +62.60.205.39 +62.60.205.40 +62.60.205.41 +62.60.205.42 +62.60.205.43 +62.60.205.44 +62.60.205.45 +62.60.205.46 +62.60.205.47 +62.60.205.48 +62.60.205.49 +62.60.205.50 +62.60.205.51 +62.60.205.52 +62.60.205.53 +62.60.205.54 +62.60.205.55 +62.60.205.56 +62.60.205.57 +62.60.205.58 +62.60.205.59 +62.60.205.60 +62.60.205.61 +62.60.205.62 +62.60.205.63 +62.60.205.64 +62.60.205.65 +62.60.205.68 +62.60.205.69 +62.60.205.70 +62.60.205.71 +62.60.205.72 +62.60.205.73 +62.60.205.74 +62.60.205.76 +62.60.205.77 +62.60.205.78 +62.60.205.79 +62.60.205.80 +62.60.205.81 +62.60.205.83 +62.60.205.84 +62.60.205.85 +62.60.205.86 +62.60.205.88 +62.60.205.89 +62.60.205.90 +62.60.205.91 +62.60.205.92 +62.60.205.93 +62.60.205.94 +62.60.205.95 +62.60.205.97 +62.60.205.98 +62.60.205.99 +62.60.205.101 +62.60.205.102 +62.60.205.103 +62.60.205.104 +62.60.205.105 +62.60.205.106 +62.60.205.107 +62.60.205.108 +62.60.205.109 +62.60.205.110 +62.60.205.111 +62.60.205.112 +62.60.205.113 +62.60.205.115 +62.60.205.116 +62.60.205.117 +62.60.205.118 +62.60.205.119 +62.60.205.120 +62.60.205.121 +62.60.205.122 +62.60.205.123 +62.60.205.124 +62.60.205.125 +62.60.205.126 +62.60.205.128 +62.60.205.129 +62.60.205.131 +62.60.205.132 +62.60.205.133 +62.60.205.134 +62.60.205.135 +62.60.205.136 +62.60.205.137 +62.60.205.138 +62.60.205.139 +62.60.205.140 +62.60.205.141 +62.60.205.142 +62.60.205.143 +62.60.205.144 +62.60.205.145 +62.60.205.146 +62.60.205.147 +62.60.205.148 +62.60.205.150 +62.60.205.151 +62.60.205.152 +62.60.205.153 +62.60.205.154 +62.60.205.155 +62.60.205.156 +62.60.205.157 +62.60.205.158 +62.60.205.159 +62.60.205.160 +62.60.205.161 +62.60.205.162 +62.60.205.163 +62.60.205.164 +62.60.205.166 +62.60.205.167 +62.60.205.168 +62.60.205.169 +62.60.205.170 +62.60.205.171 +62.60.205.172 +62.60.205.173 +62.60.205.174 +62.60.205.175 +62.60.205.176 +62.60.205.177 +62.60.205.178 +62.60.205.179 +62.60.205.180 +62.60.205.181 +62.60.205.182 +62.60.205.183 +62.60.205.184 +62.60.205.185 +62.60.205.186 +62.60.205.187 +62.60.205.188 +62.60.205.189 +62.60.205.191 +62.60.205.192 +62.60.205.193 +62.60.205.194 +62.60.205.195 +62.60.205.196 +62.60.205.197 +62.60.205.198 +62.60.205.199 +62.60.205.200 +62.60.205.201 +62.60.205.202 +62.60.205.203 +62.60.205.204 +62.60.205.207 +62.60.205.208 +62.60.205.209 +62.60.205.210 +62.60.205.211 +62.60.205.212 +62.60.205.213 +62.60.205.214 +62.60.205.215 +62.60.205.216 +62.60.205.217 +62.60.205.218 +62.60.205.219 +62.60.205.220 +62.60.205.221 +62.60.205.222 +62.60.205.223 +62.60.205.224 +62.60.205.225 +62.60.205.226 +62.60.205.227 +62.60.205.228 +62.60.205.229 +62.60.205.231 +62.60.205.232 +62.60.205.233 +62.60.205.234 +62.60.205.235 +62.60.205.236 +62.60.205.237 +62.60.205.238 +62.60.205.239 +62.60.205.240 +62.60.205.241 +62.60.205.242 +62.60.205.243 +62.60.205.244 +62.60.205.245 +62.60.205.246 +62.60.205.248 +62.60.205.249 +62.60.205.250 +62.60.205.251 +62.60.205.253 +62.60.206.35 +62.60.206.50 +62.60.206.65 +62.60.206.71 +62.60.206.83 +62.60.206.111 +62.60.206.132 +62.60.206.133 +62.60.206.182 +62.60.206.205 +62.60.207.14 +62.60.207.22 +62.60.207.47 +62.60.207.70 +62.60.207.75 +62.60.207.77 +62.60.207.97 +62.60.207.104 +62.60.207.132 +62.60.207.138 +62.60.207.223 +62.60.207.241 +62.60.210.3 +62.60.210.4 +62.60.210.5 +62.60.210.10 +62.60.210.17 +62.60.210.24 +62.60.210.68 +62.60.210.69 +62.60.210.70 +62.60.210.74 +62.60.210.77 +62.60.210.85 +62.60.210.91 +62.60.210.94 +62.60.210.95 +62.60.210.96 +62.60.210.102 +62.60.210.110 +62.60.210.130 +62.60.210.131 +62.60.210.141 +62.60.210.143 +62.60.210.176 +62.60.210.181 +62.60.210.186 +62.60.210.199 +62.60.210.234 +62.60.210.235 +62.60.210.248 +62.60.210.250 +62.60.211.56 +62.60.211.85 +62.60.211.90 +62.60.211.104 +62.60.211.114 +62.60.211.118 +62.60.211.119 +62.60.211.123 +62.60.211.145 +62.60.211.153 +62.60.211.155 +62.60.211.156 +62.60.211.163 +62.60.211.164 +62.60.211.174 +62.60.211.175 +62.60.211.183 +62.60.211.184 +62.60.211.185 +62.60.211.189 +62.60.211.190 +62.60.211.191 +62.60.211.192 +62.60.211.195 +62.60.211.196 +62.60.211.197 +62.60.211.199 +62.60.211.203 +62.60.211.211 +62.60.211.212 +62.60.211.213 +62.60.211.215 +62.60.211.218 +62.60.211.222 +62.60.211.226 +62.60.211.229 +62.60.211.230 +62.60.211.234 +62.60.211.235 +62.60.211.237 +62.60.211.239 +62.60.211.241 +62.60.211.248 +62.60.211.251 +62.60.211.253 +62.60.212.26 +62.60.212.27 +62.60.212.32 +62.60.212.91 +62.60.212.158 +62.60.212.159 +62.60.212.193 +62.60.212.222 +62.60.212.232 +62.60.212.243 +62.60.212.247 +62.60.213.10 +62.60.213.11 +62.60.213.14 +62.60.213.26 +62.60.213.58 +62.60.213.66 +62.60.213.122 +62.60.213.126 +62.60.213.206 +62.60.213.220 +62.60.214.60 +62.60.214.64 +62.60.214.80 +62.60.214.91 +62.60.214.151 +62.60.214.228 +62.60.219.3 +62.106.95.14 +62.106.95.25 +62.106.95.28 +62.106.95.34 +62.106.95.37 +62.106.95.43 +62.106.95.74 +62.106.95.76 +62.106.95.81 +62.106.95.87 +62.106.95.99 +62.106.95.106 +62.106.95.129 +62.106.95.134 +62.106.95.141 +62.106.95.147 +62.106.95.149 +62.106.95.152 +62.106.95.153 +62.106.95.156 +62.106.95.165 +62.106.95.169 +62.106.95.182 +62.106.95.192 +62.106.95.193 +62.106.95.198 +62.106.95.199 +62.106.95.217 +62.106.95.239 +62.106.95.242 +62.106.95.247 +62.106.95.251 +62.106.95.252 +62.193.13.5 +62.193.13.9 +62.204.61.2 +62.204.61.5 +62.204.61.6 +62.204.61.7 +62.204.61.8 +62.204.61.9 +62.204.61.12 +62.204.61.13 +62.204.61.14 +62.204.61.15 +62.204.61.17 +62.204.61.18 +62.204.61.19 +62.204.61.22 +62.204.61.23 +62.204.61.25 +62.204.61.30 +62.204.61.33 +62.204.61.35 +62.204.61.36 +62.204.61.37 +62.204.61.44 +62.204.61.46 +62.204.61.47 +62.204.61.60 +62.204.61.63 +62.204.61.72 +62.204.61.93 +62.204.61.112 +62.204.61.114 +62.204.61.115 +62.204.61.116 +62.204.61.117 +62.204.61.118 +62.204.61.119 +62.204.61.122 +62.204.61.130 +62.204.61.141 +62.204.61.143 +62.204.61.145 +62.204.61.148 +62.204.61.155 +62.204.61.156 +62.204.61.160 +62.204.61.164 +62.204.61.177 +62.204.61.185 +62.204.61.202 +62.204.61.222 +62.204.61.224 +62.204.61.233 +62.204.61.254 +62.220.98.5 +62.220.100.168 +62.220.100.182 +62.220.100.187 +62.220.112.46 +62.220.112.71 +62.220.114.204 +62.220.114.253 +62.220.115.101 +62.220.126.218 +62.220.127.13 +62.220.127.165 +77.36.160.4 +77.36.160.7 +77.36.164.20 +77.36.164.21 +77.36.164.34 +77.36.164.36 +77.36.227.3 +77.74.202.74 +77.74.202.202 +77.81.40.244 +77.81.76.2 +77.81.76.4 +77.81.76.7 +77.81.76.8 +77.81.76.10 +77.81.76.12 +77.81.76.13 +77.81.76.14 +77.81.76.15 +77.81.76.17 +77.81.76.18 +77.81.76.19 +77.81.76.20 +77.81.76.21 +77.81.76.22 +77.81.76.23 +77.81.76.24 +77.81.76.25 +77.81.76.26 +77.81.76.27 +77.81.76.28 +77.81.76.30 +77.81.76.32 +77.81.76.33 +77.81.76.34 +77.81.76.35 +77.81.76.37 +77.81.76.39 +77.81.76.40 +77.81.76.41 +77.81.76.42 +77.81.76.43 +77.81.76.44 +77.81.76.45 +77.81.76.46 +77.81.76.47 +77.81.76.48 +77.81.76.51 +77.81.76.52 +77.81.76.55 +77.81.76.59 +77.81.76.62 +77.81.76.64 +77.81.76.66 +77.81.76.68 +77.81.76.72 +77.81.76.77 +77.81.76.82 +77.81.76.83 +77.81.76.84 +77.81.76.85 +77.81.76.87 +77.81.76.99 +77.81.76.100 +77.81.76.101 +77.81.76.102 +77.81.76.103 +77.81.76.104 +77.81.76.105 +77.81.76.118 +77.81.76.120 +77.81.147.31 +77.81.149.211 +77.81.157.153 +77.81.157.202 +77.81.158.4 +77.81.159.61 +77.104.65.73 +77.104.66.41 +77.104.66.165 +77.104.67.55 +77.104.67.56 +77.104.69.40 +77.104.73.147 +77.104.73.229 +77.104.74.71 +77.104.76.45 +77.104.79.30 +77.104.80.3 +77.104.81.127 +77.104.81.242 +77.104.82.2 +77.104.83.201 +77.104.83.206 +77.104.85.163 +77.104.88.139 +77.104.90.18 +77.104.98.115 +77.104.98.205 +77.104.98.231 +77.104.103.147 +77.104.104.104 +77.104.106.55 +77.104.116.15 +77.104.117.2 +77.104.119.6 +77.237.64.209 +77.237.66.66 +77.237.66.67 +77.237.66.71 +77.237.66.76 +77.237.66.77 +77.237.66.78 +77.237.66.80 +77.237.66.81 +77.237.66.84 +77.237.66.89 +77.237.66.98 +77.237.66.126 +77.237.66.195 +77.237.66.238 +77.237.67.10 +77.237.67.11 +77.237.67.12 +77.237.67.14 +77.237.67.15 +77.237.67.16 +77.237.67.17 +77.237.67.18 +77.237.67.20 +77.237.67.21 +77.237.67.22 +77.237.67.23 +77.237.67.24 +77.237.67.25 +77.237.70.140 +77.237.70.170 +77.237.71.19 +77.237.71.20 +77.237.79.5 +77.237.79.6 +77.237.81.6 +77.237.82.123 +77.237.85.210 +77.237.87.187 +77.237.87.188 +77.237.87.189 +77.237.91.141 +77.238.104.132 +77.238.106.15 +77.238.108.91 +77.238.108.100 +77.238.108.124 +77.238.108.125 +77.238.109.67 +77.238.109.76 +77.238.109.77 +77.238.110.42 +77.238.110.45 +77.238.110.80 +77.238.110.83 +77.238.110.84 +77.238.110.85 +77.238.110.86 +77.238.110.106 +77.238.110.140 +77.238.110.157 +77.238.110.158 +77.238.110.162 +77.238.110.181 +77.238.110.187 +77.238.110.191 +77.238.110.226 +77.238.110.250 +77.238.110.251 +77.238.110.252 +77.238.111.234 +77.238.111.236 +77.238.111.237 +77.238.111.238 +77.238.113.8 +77.238.117.53 +77.238.117.158 +77.238.120.17 +77.238.120.21 +77.238.120.52 +77.238.120.98 +77.238.120.99 +77.238.120.100 +77.238.120.101 +77.238.120.102 +77.238.120.121 +77.238.121.54 +77.238.121.55 +77.238.121.107 +77.238.121.155 +77.238.121.242 +77.238.121.243 +77.238.121.245 +77.238.121.247 +77.238.121.249 +77.238.122.130 +77.238.122.132 +77.238.122.133 +77.238.122.134 +77.238.122.135 +77.238.122.136 +77.238.122.139 +77.238.122.140 +77.238.122.141 +77.238.122.142 +77.238.122.143 +77.238.122.145 +77.238.122.147 +77.238.122.148 +77.238.122.149 +77.238.122.152 +77.238.122.153 +77.238.122.156 +77.238.122.157 +77.238.122.158 +77.238.122.159 +77.238.122.160 +77.238.122.161 +77.238.122.162 +77.238.122.164 +77.238.122.165 +77.238.122.166 +77.238.122.167 +77.238.122.169 +77.238.122.172 +77.238.122.174 +77.238.122.175 +77.238.122.178 +77.238.122.179 +77.238.122.180 +77.238.122.181 +77.238.122.182 +77.238.122.185 +77.238.122.187 +77.238.122.189 +77.238.122.196 +77.238.122.206 +77.238.123.84 +77.238.123.179 +77.238.123.237 +78.31.232.3 +78.31.235.119 +78.38.0.131 +78.38.0.132 +78.38.17.28 +78.38.25.2 +78.38.25.3 +78.38.34.125 +78.38.35.11 +78.38.41.96 +78.38.45.2 +78.38.45.3 +78.38.49.84 +78.38.50.218 +78.38.64.156 +78.38.69.2 +78.38.76.251 +78.38.77.2 +78.38.77.3 +78.38.80.251 +78.38.84.11 +78.38.90.70 +78.38.100.34 +78.38.101.37 +78.38.108.48 +78.38.109.130 +78.38.113.2 +78.38.114.69 +78.38.114.85 +78.38.114.99 +78.38.114.102 +78.38.120.95 +78.38.120.131 +78.38.121.66 +78.38.123.247 +78.38.131.114 +78.38.138.158 +78.38.138.159 +78.38.152.74 +78.38.153.74 +78.38.153.86 +78.38.153.132 +78.38.156.68 +78.38.156.72 +78.38.157.241 +78.38.158.1 +78.38.166.162 +78.38.174.30 +78.38.175.2 +78.38.175.3 +78.38.176.10 +78.38.201.120 +78.38.208.156 +78.38.221.187 +78.38.227.2 +78.38.227.3 +78.38.237.49 +78.38.239.67 +78.38.239.134 +78.38.239.202 +78.38.248.66 +78.38.248.94 +78.38.248.107 +78.39.9.27 +78.39.9.224 +78.39.9.251 +78.39.10.44 +78.39.11.42 +78.39.35.65 +78.39.35.66 +78.39.39.139 +78.39.40.11 +78.39.43.137 +78.39.51.25 +78.39.51.26 +78.39.53.204 +78.39.56.43 +78.39.56.114 +78.39.57.172 +78.39.57.254 +78.39.59.246 +78.39.62.35 +78.39.80.9 +78.39.80.190 +78.39.80.199 +78.39.81.33 +78.39.81.220 +78.39.85.8 +78.39.85.9 +78.39.98.10 +78.39.98.27 +78.39.98.28 +78.39.101.2 +78.39.103.28 +78.39.103.54 +78.39.103.252 +78.39.107.114 +78.39.107.115 +78.39.108.132 +78.39.112.126 +78.39.114.34 +78.39.121.110 +78.39.121.117 +78.39.122.251 +78.39.129.20 +78.39.129.148 +78.39.129.149 +78.39.136.13 +78.39.136.49 +78.39.136.67 +78.39.136.97 +78.39.136.98 +78.39.136.101 +78.39.136.106 +78.39.136.114 +78.39.136.247 +78.39.136.251 +78.39.136.254 +78.39.140.113 +78.39.141.71 +78.39.152.60 +78.39.152.135 +78.39.152.249 +78.39.159.3 +78.39.159.12 +78.39.159.48 +78.39.159.49 +78.39.159.61 +78.39.159.111 +78.39.164.212 +78.39.180.34 +78.39.188.253 +78.39.188.254 +78.39.205.34 +78.39.205.35 +78.39.208.212 +78.39.212.8 +78.39.212.9 +78.39.217.156 +78.39.217.170 +78.39.226.36 +78.39.226.209 +78.39.227.3 +78.39.234.140 +78.39.234.230 +78.39.234.237 +78.39.250.101 +78.39.250.128 +78.39.251.25 +78.39.252.167 +78.41.62.20 +78.41.62.196 +78.109.193.2 +78.109.194.2 +78.109.195.42 +78.109.195.50 +78.109.195.148 +78.109.195.149 +78.109.196.135 +78.109.196.153 +78.109.196.159 +78.109.196.173 +78.109.196.177 +78.109.198.60 +78.109.198.61 +78.109.199.2 +78.109.199.5 +78.109.199.13 +78.109.199.36 +78.109.199.65 +78.109.199.203 +78.109.199.218 +78.109.200.2 +78.109.200.29 +78.109.200.64 +78.109.200.199 +78.109.200.207 +78.109.201.2 +78.109.201.71 +78.109.201.78 +78.109.201.119 +78.109.201.201 +78.109.202.100 +78.109.206.2 +78.109.206.91 +78.109.206.102 +78.109.206.124 +78.109.206.186 +78.109.206.206 +78.110.120.64 +78.110.120.67 +78.110.120.87 +78.110.120.96 +78.110.120.97 +78.110.120.98 +78.110.120.99 +78.110.120.120 +78.110.120.124 +78.110.120.145 +78.110.120.150 +78.110.120.220 +78.110.120.231 +78.110.120.234 +78.110.120.243 +78.110.121.92 +78.110.121.93 +78.110.121.94 +78.110.121.95 +78.110.121.96 +78.110.121.127 +78.110.121.130 +78.110.121.155 +78.110.121.156 +78.110.121.157 +78.110.121.158 +78.110.121.166 +78.110.121.169 +78.110.121.170 +78.110.121.184 +78.110.121.185 +78.110.122.56 +78.110.122.57 +78.110.122.61 +78.110.122.62 +78.110.122.112 +78.110.122.148 +78.110.122.150 +78.110.122.181 +78.110.122.182 +78.110.122.191 +78.110.122.236 +78.110.123.71 +78.110.125.131 +78.111.0.67 +78.111.2.84 +78.111.5.186 +78.111.5.190 +78.111.5.194 +78.111.5.198 +78.111.13.100 +78.157.33.200 +78.157.34.68 +78.157.34.69 +78.157.34.85 +78.157.34.125 +78.157.34.210 +78.157.35.8 +78.157.35.15 +78.157.35.32 +78.157.35.59 +78.157.35.70 +78.157.35.75 +78.157.35.76 +78.157.35.77 +78.157.35.78 +78.157.35.127 +78.157.36.186 +78.157.37.90 +78.157.37.141 +78.157.37.142 +78.157.37.152 +78.157.37.157 +78.157.37.158 +78.157.37.255 +78.157.38.66 +78.157.38.67 +78.157.38.68 +78.157.38.69 +78.157.38.70 +78.157.38.72 +78.157.38.73 +78.157.38.74 +78.157.38.75 +78.157.38.76 +78.157.38.78 +78.157.38.79 +78.157.38.80 +78.157.38.81 +78.157.38.82 +78.157.38.83 +78.157.38.84 +78.157.38.86 +78.157.38.87 +78.157.38.88 +78.157.38.90 +78.157.38.91 +78.157.38.92 +78.157.38.93 +78.157.38.97 +78.157.38.98 +78.157.38.99 +78.157.38.101 +78.157.38.104 +78.157.38.106 +78.157.38.107 +78.157.38.108 +78.157.38.111 +78.157.38.112 +78.157.38.113 +78.157.38.116 +78.157.38.117 +78.157.38.120 +78.157.38.121 +78.157.38.122 +78.157.38.123 +78.157.38.168 +78.157.38.192 +78.157.38.199 +78.157.38.203 +78.157.38.208 +78.157.38.215 +78.157.39.21 +78.157.39.22 +78.157.39.60 +78.157.39.106 +78.157.40.34 +78.157.40.35 +78.157.40.38 +78.157.40.39 +78.157.40.40 +78.157.40.41 +78.157.40.42 +78.157.40.43 +78.157.40.44 +78.157.40.46 +78.157.40.70 +78.157.40.158 +78.157.40.168 +78.157.40.175 +78.157.40.191 +78.157.40.192 +78.157.40.200 +78.157.40.201 +78.157.40.207 +78.157.41.60 +78.157.41.63 +78.157.41.119 +78.157.41.127 +78.157.41.149 +78.157.41.175 +78.157.41.224 +78.157.41.231 +78.157.42.23 +78.157.42.62 +78.157.42.201 +78.157.42.202 +78.157.42.203 +78.157.42.204 +78.157.42.205 +78.157.42.209 +78.157.42.210 +78.157.42.214 +78.157.42.215 +78.157.42.216 +78.157.42.217 +78.157.42.218 +78.157.42.219 +78.157.42.235 +78.157.43.229 +78.157.43.230 +78.157.43.241 +78.157.44.74 +78.157.44.212 +78.157.44.215 +78.157.45.47 +78.157.45.210 +78.157.45.212 +78.157.45.214 +78.157.45.215 +78.157.46.95 +78.157.46.104 +78.157.46.120 +78.157.46.128 +78.157.46.143 +78.157.46.213 +78.157.47.201 +78.157.47.202 +78.157.47.203 +78.157.47.204 +78.157.47.205 +78.157.47.206 +78.157.47.207 +78.157.47.209 +78.157.47.210 +78.157.48.16 +78.157.48.44 +78.157.48.51 +78.157.48.52 +78.157.48.112 +78.157.48.135 +78.157.48.150 +78.157.48.162 +78.157.48.175 +78.157.48.176 +78.157.49.22 +78.157.49.100 +78.157.49.105 +78.157.49.110 +78.157.49.111 +78.157.49.120 +78.157.50.22 +78.157.50.24 +78.157.50.31 +78.157.50.255 +78.157.51.3 +78.157.51.5 +78.157.51.28 +78.157.51.49 +78.157.51.64 +78.157.51.93 +78.157.51.136 +78.157.51.159 +78.157.51.163 +78.157.51.185 +78.157.51.192 +78.157.51.203 +78.157.51.232 +78.157.51.240 +78.157.51.248 +78.157.53.67 +78.157.53.187 +78.157.56.19 +78.157.56.24 +78.157.56.25 +78.157.56.53 +78.157.56.133 +78.157.56.181 +78.157.56.215 +78.157.57.39 +78.157.57.120 +78.157.57.152 +78.157.57.154 +78.157.57.162 +78.157.57.199 +78.157.57.224 +78.157.57.230 +78.157.57.231 +78.157.57.234 +78.157.57.241 +78.157.57.245 +78.157.57.247 +78.157.57.252 +78.157.57.255 +78.157.58.38 +78.157.58.39 +78.157.58.42 +78.157.58.170 +78.157.59.20 +78.157.59.22 +78.157.59.23 +78.157.59.24 +78.157.59.25 +78.157.59.28 +78.157.59.29 +78.157.59.34 +78.157.59.195 +78.157.59.196 +78.157.59.197 +78.157.59.198 +78.157.59.201 +78.157.60.77 +78.157.61.79 +78.157.62.99 +78.157.63.105 +78.157.63.121 +78.158.161.146 +78.158.166.38 +78.158.166.66 +78.158.166.71 +79.127.2.30 +79.127.2.168 +79.127.4.203 +79.127.4.207 +79.127.5.97 +79.127.5.98 +79.127.6.1 +79.127.7.239 +79.127.12.89 +79.127.12.156 +79.127.41.218 +79.127.45.26 +79.127.47.173 +79.127.48.173 +79.127.52.5 +79.127.52.76 +79.127.61.70 +79.127.67.36 +79.127.69.230 +79.127.69.233 +79.127.73.202 +79.127.87.170 +79.127.87.174 +79.127.92.60 +79.127.94.222 +79.127.96.153 +79.127.106.228 +79.127.114.46 +79.127.125.7 +79.127.125.13 +79.127.125.36 +79.127.125.37 +79.127.125.38 +79.127.125.80 +79.127.125.156 +79.127.125.178 +79.127.125.179 +79.127.125.180 +79.127.125.181 +79.127.125.182 +79.127.125.250 +79.127.125.252 +79.127.126.26 +79.127.126.27 +79.127.126.28 +79.127.126.29 +79.127.126.30 +79.127.126.66 +79.127.126.69 +79.127.126.70 +79.127.126.71 +79.127.126.72 +79.127.126.73 +79.127.126.74 +79.127.126.76 +79.127.126.77 +79.127.126.78 +79.127.126.85 +79.127.126.92 +79.127.126.104 +79.127.126.111 +79.127.126.112 +79.127.126.113 +79.127.126.116 +79.127.126.123 +79.127.126.124 +79.127.126.126 +79.127.126.133 +79.127.126.226 +79.127.126.228 +79.127.126.242 +79.127.126.245 +79.127.126.246 +79.127.126.247 +79.127.126.249 +79.127.126.251 +79.127.127.6 +79.127.127.11 +79.127.127.17 +79.127.127.18 +79.127.127.20 +79.127.127.23 +79.127.127.24 +79.127.127.25 +79.127.127.27 +79.127.127.28 +79.127.127.29 +79.127.127.31 +79.127.127.32 +79.127.127.33 +79.127.127.34 +79.127.127.35 +79.127.127.36 +79.127.127.37 +79.127.127.38 +79.127.127.39 +79.127.127.43 +79.127.127.44 +79.127.127.45 +79.127.127.46 +79.127.127.47 +79.127.127.49 +79.127.127.51 +79.127.127.62 +79.127.127.64 +79.127.127.65 +79.127.127.66 +79.127.127.67 +79.127.127.68 +79.127.127.69 +79.127.127.70 +79.127.127.72 +79.127.127.74 +79.127.127.75 +79.127.127.76 +79.127.127.77 +79.127.127.78 +79.127.127.79 +79.127.127.80 +79.127.127.82 +79.127.127.84 +79.127.127.85 +79.127.127.86 +79.127.127.87 +79.127.127.88 +79.127.127.90 +79.127.127.91 +79.127.127.93 +79.127.127.94 +79.127.127.95 +79.127.127.96 +79.127.127.97 +79.127.127.98 +79.127.127.99 +79.127.127.100 +79.127.127.101 +79.127.127.103 +79.127.127.105 +79.127.127.106 +79.127.127.111 +79.127.127.113 +79.127.127.114 +79.127.127.115 +79.127.127.116 +79.127.127.118 +79.127.127.119 +79.127.127.120 +79.127.127.123 +79.127.127.125 +79.127.127.134 +79.127.127.138 +79.127.127.151 +79.127.127.154 +79.127.127.190 +79.127.127.210 +79.127.127.213 +79.127.127.215 +79.132.192.15 +79.132.192.82 +79.132.192.196 +79.132.192.233 +79.132.192.238 +79.132.193.26 +79.132.193.36 +79.132.193.38 +79.132.193.39 +79.132.193.43 +79.132.193.44 +79.132.193.46 +79.132.193.70 +79.132.193.72 +79.132.193.74 +79.132.193.78 +79.132.193.83 +79.132.193.94 +79.132.193.96 +79.132.193.101 +79.132.193.105 +79.132.193.107 +79.132.193.116 +79.132.193.125 +79.132.193.133 +79.132.193.134 +79.132.193.135 +79.132.193.143 +79.132.193.144 +79.132.193.147 +79.132.193.148 +79.132.193.150 +79.132.193.153 +79.132.193.155 +79.132.193.162 +79.132.193.168 +79.132.193.180 +79.132.193.186 +79.132.193.194 +79.132.193.200 +79.132.193.201 +79.132.193.206 +79.132.193.210 +79.132.193.216 +79.132.193.225 +79.132.193.226 +79.132.193.228 +79.132.193.229 +79.132.193.236 +79.132.193.238 +79.132.193.239 +79.132.193.240 +79.132.193.243 +79.132.193.245 +79.132.193.248 +79.132.193.249 +79.132.193.251 +79.132.193.253 +79.132.206.249 +79.132.206.250 +79.132.210.66 +79.143.84.18 +79.143.84.77 +79.143.84.114 +79.143.84.118 +79.143.84.126 +79.143.84.162 +79.143.84.166 +79.143.86.4 +79.143.86.6 +79.143.86.7 +79.143.86.154 +79.143.86.156 +79.143.86.165 +79.143.86.166 +79.143.86.172 +79.174.161.219 +79.174.161.222 +79.174.165.135 +79.174.165.136 +79.175.129.2 +79.175.130.58 +79.175.130.59 +79.175.130.60 +79.175.130.61 +79.175.130.62 +79.175.131.17 +79.175.131.19 +79.175.131.38 +79.175.131.85 +79.175.131.145 +79.175.133.5 +79.175.133.6 +79.175.133.14 +79.175.133.31 +79.175.133.93 +79.175.133.102 +79.175.133.108 +79.175.133.127 +79.175.133.138 +79.175.133.149 +79.175.133.154 +79.175.133.196 +79.175.133.220 +79.175.133.233 +79.175.133.239 +79.175.133.241 +79.175.134.2 +79.175.134.20 +79.175.134.68 +79.175.134.85 +79.175.134.141 +79.175.134.172 +79.175.134.176 +79.175.134.220 +79.175.136.2 +79.175.136.3 +79.175.136.4 +79.175.136.13 +79.175.136.72 +79.175.136.86 +79.175.136.194 +79.175.137.2 +79.175.137.3 +79.175.137.4 +79.175.138.2 +79.175.138.4 +79.175.138.5 +79.175.139.179 +79.175.139.180 +79.175.139.181 +79.175.139.182 +79.175.139.183 +79.175.139.184 +79.175.139.187 +79.175.139.188 +79.175.139.189 +79.175.139.190 +79.175.142.147 +79.175.142.150 +79.175.145.90 +79.175.145.147 +79.175.145.149 +79.175.146.74 +79.175.148.66 +79.175.148.67 +79.175.148.200 +79.175.148.242 +79.175.149.194 +79.175.151.2 +79.175.151.27 +79.175.151.31 +79.175.151.40 +79.175.151.57 +79.175.151.63 +79.175.151.74 +79.175.151.80 +79.175.151.84 +79.175.151.90 +79.175.151.102 +79.175.151.122 +79.175.151.130 +79.175.151.173 +79.175.151.174 +79.175.151.213 +79.175.151.226 +79.175.151.233 +79.175.151.250 +79.175.154.20 +79.175.155.2 +79.175.155.3 +79.175.155.66 +79.175.155.68 +79.175.155.69 +79.175.155.82 +79.175.155.83 +79.175.155.84 +79.175.155.88 +79.175.155.91 +79.175.157.2 +79.175.157.3 +79.175.157.4 +79.175.157.5 +79.175.157.6 +79.175.157.66 +79.175.157.67 +79.175.157.68 +79.175.157.69 +79.175.157.70 +79.175.157.102 +79.175.157.103 +79.175.157.163 +79.175.160.90 +79.175.162.2 +79.175.164.171 +79.175.164.172 +79.175.165.3 +79.175.165.170 +79.175.166.90 +79.175.166.98 +79.175.166.100 +79.175.166.102 +79.175.166.146 +79.175.166.147 +79.175.167.2 +79.175.167.238 +79.175.168.67 +79.175.168.94 +79.175.168.142 +79.175.168.219 +79.175.169.51 +79.175.169.53 +79.175.169.54 +79.175.169.55 +79.175.169.56 +79.175.169.57 +79.175.169.58 +79.175.169.59 +79.175.169.62 +79.175.169.120 +79.175.169.125 +79.175.169.138 +79.175.169.139 +79.175.169.141 +79.175.169.217 +79.175.169.218 +79.175.169.219 +79.175.169.222 +79.175.169.226 +79.175.169.229 +79.175.171.179 +79.175.172.98 +79.175.172.101 +79.175.172.147 +79.175.172.150 +79.175.172.155 +79.175.172.156 +79.175.173.179 +79.175.173.198 +79.175.173.217 +79.175.173.234 +79.175.174.124 +79.175.174.125 +79.175.176.3 +79.175.176.22 +79.175.176.36 +79.175.176.37 +79.175.176.46 +79.175.176.49 +79.175.176.60 +79.175.176.69 +79.175.176.70 +79.175.176.74 +79.175.176.76 +79.175.176.99 +79.175.176.118 +79.175.176.123 +79.175.176.136 +79.175.176.177 +79.175.176.184 +79.175.176.188 +79.175.176.191 +79.175.176.209 +79.175.177.2 +79.175.177.17 +79.175.177.30 +79.175.177.67 +79.175.177.103 +79.175.177.112 +79.175.177.113 +79.175.177.136 +79.175.177.138 +79.175.181.148 +79.175.181.150 +79.175.181.153 +79.175.181.156 +79.175.181.158 +79.175.181.171 +79.175.181.203 +79.175.181.206 +79.175.181.213 +79.175.186.162 +79.175.188.2 +79.175.188.41 +79.175.188.135 +79.175.188.186 +79.175.189.104 +79.175.190.162 +79.175.190.166 +79.175.190.180 +79.175.190.181 +79.175.190.182 +79.175.190.237 +80.66.177.4 +80.66.177.5 +80.66.177.22 +80.66.177.49 +80.71.119.2 +80.71.149.3 +80.71.149.4 +80.71.149.40 +80.71.149.42 +80.71.149.127 +80.71.149.152 +80.71.149.158 +80.71.149.175 +80.71.149.241 +80.71.149.249 +80.75.0.2 +80.75.1.42 +80.75.2.15 +80.75.2.135 +80.75.3.7 +80.75.3.17 +80.75.3.86 +80.75.3.93 +80.75.3.227 +80.75.4.101 +80.75.4.167 +80.75.4.168 +80.75.7.10 +80.75.7.26 +80.75.7.188 +80.75.9.7 +80.75.10.134 +80.75.13.179 +80.75.14.70 +80.75.14.71 +80.75.14.96 +80.75.14.99 +80.75.14.102 +80.75.14.103 +80.75.14.108 +80.75.14.115 +80.75.14.116 +80.75.14.118 +80.75.14.124 +80.75.213.70 +80.75.213.90 +80.75.213.131 +80.75.213.146 +80.75.213.147 +80.75.213.148 +80.75.213.149 +80.75.213.155 +80.75.213.253 +80.91.208.18 +80.91.208.206 +80.191.0.17 +80.191.0.18 +80.191.2.218 +80.191.8.40 +80.191.11.134 +80.191.24.2 +80.191.24.9 +80.191.27.199 +80.191.27.203 +80.191.27.204 +80.191.27.205 +80.191.27.206 +80.191.32.3 +80.191.32.56 +80.191.36.20 +80.191.41.83 +80.191.41.84 +80.191.47.2 +80.191.49.130 +80.191.49.131 +80.191.49.133 +80.191.56.7 +80.191.56.100 +80.191.56.110 +80.191.62.5 +80.191.65.241 +80.191.68.247 +80.191.70.34 +80.191.75.110 +80.191.80.9 +80.191.89.50 +80.191.89.98 +80.191.90.54 +80.191.90.62 +80.191.92.15 +80.191.92.179 +80.191.92.188 +80.191.92.199 +80.191.92.233 +80.191.100.246 +80.191.103.92 +80.191.105.200 +80.191.105.245 +80.191.105.249 +80.191.105.252 +80.191.110.138 +80.191.111.4 +80.191.111.5 +80.191.111.170 +80.191.131.131 +80.191.153.109 +80.191.156.134 +80.191.156.234 +80.191.163.249 +80.191.163.251 +80.191.170.194 +80.191.172.4 +80.191.172.69 +80.191.174.111 +80.191.174.112 +80.191.192.6 +80.191.192.115 +80.191.193.40 +80.191.202.15 +80.191.203.10 +80.191.214.20 +80.191.216.2 +80.191.221.12 +80.191.221.14 +80.191.221.19 +80.191.221.20 +80.191.221.21 +80.191.221.22 +80.191.221.25 +80.191.221.28 +80.191.221.29 +80.191.221.30 +80.191.221.32 +80.191.221.40 +80.191.221.41 +80.191.221.61 +80.191.221.72 +80.191.228.3 +80.191.236.54 +80.191.242.1 +80.191.242.90 +80.191.242.228 +80.191.248.61 +80.191.248.62 +80.210.17.2 +80.210.20.226 +80.210.22.97 +80.210.24.8 +80.210.26.114 +80.210.26.166 +80.210.30.38 +80.210.30.78 +80.210.30.156 +80.210.32.30 +80.210.32.38 +80.210.32.132 +80.210.32.162 +80.210.38.9 +80.210.38.11 +80.210.40.54 +80.210.40.208 +80.210.41.96 +80.210.48.145 +80.210.51.88 +80.210.53.80 +80.210.57.212 +80.210.59.19 +80.210.59.98 +80.210.61.28 +80.210.62.28 +80.210.62.245 +80.210.63.54 +80.210.141.120 +80.210.166.118 +80.210.167.23 +80.210.168.153 +80.210.173.249 +80.253.136.12 +80.253.145.150 +80.253.147.222 +81.12.0.3 +81.12.4.10 +81.12.5.164 +81.12.5.221 +81.12.7.54 +81.12.12.201 +81.12.12.202 +81.12.13.11 +81.12.13.74 +81.12.25.53 +81.12.27.38 +81.12.27.122 +81.12.27.171 +81.12.27.210 +81.12.27.218 +81.12.27.222 +81.12.28.206 +81.12.30.13 +81.12.30.82 +81.12.30.139 +81.12.30.140 +81.12.30.141 +81.12.30.195 +81.12.31.48 +81.12.34.177 +81.12.35.100 +81.12.39.2 +81.12.39.3 +81.12.39.4 +81.12.39.8 +81.12.39.9 +81.12.39.10 +81.12.39.11 +81.12.39.12 +81.12.39.13 +81.12.39.14 +81.12.39.15 +81.12.39.16 +81.12.39.17 +81.12.39.18 +81.12.39.19 +81.12.39.20 +81.12.39.21 +81.12.39.22 +81.12.39.23 +81.12.39.24 +81.12.39.25 +81.12.39.26 +81.12.39.27 +81.12.39.28 +81.12.39.29 +81.12.39.31 +81.12.39.54 +81.12.39.81 +81.12.39.116 +81.12.39.134 +81.12.39.135 +81.12.39.137 +81.12.39.138 +81.12.39.159 +81.12.39.193 +81.12.39.194 +81.12.39.195 +81.12.39.197 +81.12.39.198 +81.12.39.199 +81.12.39.202 +81.12.39.210 +81.12.39.233 +81.12.52.4 +81.12.52.58 +81.12.52.90 +81.12.52.122 +81.12.52.126 +81.12.52.146 +81.12.52.155 +81.12.52.158 +81.12.52.162 +81.12.52.172 +81.12.52.178 +81.12.52.190 +81.12.52.235 +81.12.53.76 +81.12.53.90 +81.12.53.115 +81.12.53.116 +81.12.53.124 +81.12.53.126 +81.12.53.131 +81.12.53.150 +81.12.53.162 +81.12.53.182 +81.12.53.206 +81.12.53.217 +81.12.54.123 +81.12.55.235 +81.12.59.137 +81.12.59.138 +81.12.62.101 +81.12.63.109 +81.12.65.141 +81.12.65.144 +81.12.70.4 +81.12.72.123 +81.12.72.218 +81.12.75.140 +81.12.87.104 +81.12.88.52 +81.12.91.51 +81.12.91.52 +81.12.91.53 +81.12.93.250 +81.12.93.251 +81.12.93.252 +81.12.93.253 +81.12.97.60 +81.12.97.124 +81.12.98.18 +81.12.98.19 +81.12.98.21 +81.12.98.22 +81.12.101.62 +81.12.105.194 +81.12.108.99 +81.12.108.100 +81.12.108.101 +81.12.108.102 +81.12.112.180 +81.12.113.26 +81.12.116.130 +81.12.116.131 +81.12.116.133 +81.12.116.134 +81.12.116.135 +81.12.116.136 +81.12.116.137 +81.12.116.138 +81.12.116.139 +81.12.116.140 +81.12.120.27 +81.12.120.131 +81.12.122.130 +81.12.123.151 +81.16.114.126 +81.16.115.130 +81.16.116.178 +81.16.121.124 +81.16.121.161 +81.16.123.125 +81.16.124.169 +81.16.126.103 +81.16.126.237 +81.16.127.253 +81.28.52.30 +81.28.61.72 +81.29.245.169 +81.29.255.35 +81.31.160.34 +81.31.160.35 +81.31.169.19 +81.31.250.184 +81.31.250.186 +81.31.250.188 +81.31.250.189 +81.90.144.210 +81.90.147.2 +81.90.147.189 +81.90.152.150 +81.90.158.26 +81.90.159.170 +81.91.129.226 +81.91.129.227 +81.91.129.229 +81.91.129.230 +81.91.139.109 +81.91.139.118 +81.91.140.90 +81.91.145.181 +81.91.146.254 +81.91.147.3 +81.91.147.250 +81.91.151.172 +81.91.155.46 +81.91.155.94 +81.91.157.181 +82.99.202.102 +82.99.202.212 +82.99.204.74 +82.99.204.93 +82.99.227.214 +82.99.240.57 +82.99.250.149 +82.99.252.117 +82.99.253.93 +82.102.14.4 +82.102.14.35 +82.102.14.50 +82.102.15.90 +82.102.15.91 +82.102.15.92 +82.102.15.93 +82.102.15.94 +82.102.15.210 +83.147.193.2 +83.147.193.3 +83.147.193.4 +83.147.193.240 +83.150.192.12 +83.150.193.12 +84.47.239.2 +84.47.239.3 +84.47.239.4 +84.47.239.6 +84.47.239.7 +84.47.239.8 +84.47.239.9 +84.47.239.10 +84.47.239.11 +84.47.239.12 +84.47.239.14 +84.47.239.15 +84.47.239.16 +84.47.239.17 +84.47.239.18 +84.47.239.19 +84.47.239.20 +84.47.239.22 +84.47.239.23 +84.47.239.25 +84.241.0.237 +84.241.1.202 +84.241.2.22 +84.241.2.42 +84.241.2.100 +84.241.2.253 +84.241.4.59 +84.241.7.39 +84.241.7.200 +84.241.10.229 +84.241.11.102 +84.241.11.222 +84.241.13.118 +84.241.14.122 +84.241.16.25 +84.241.16.49 +84.241.19.230 +84.241.22.21 +84.241.22.121 +84.241.22.236 +84.241.26.155 +84.241.27.20 +84.241.27.165 +84.241.28.236 +84.241.30.11 +84.241.30.192 +84.241.32.141 +84.241.35.82 +84.241.38.8 +84.241.39.230 +84.241.39.250 +84.241.41.23 +84.241.41.93 +84.241.41.195 +84.241.43.83 +84.241.45.39 +84.241.47.146 +84.241.56.89 +84.241.63.178 +85.9.66.19 +85.9.71.176 +85.9.91.133 +85.9.104.2 +85.9.105.37 +85.9.105.46 +85.9.107.3 +85.9.107.123 +85.9.107.154 +85.9.107.163 +85.9.107.165 +85.9.107.169 +85.9.107.217 +85.9.107.228 +85.9.107.229 +85.9.108.3 +85.9.108.4 +85.9.108.98 +85.9.108.100 +85.9.108.101 +85.9.108.102 +85.9.108.103 +85.9.108.104 +85.9.108.108 +85.9.109.5 +85.9.111.122 +85.9.111.123 +85.9.111.125 +85.9.111.126 +85.9.111.154 +85.9.111.172 +85.9.111.186 +85.9.112.212 +85.9.112.227 +85.9.112.228 +85.9.112.230 +85.9.112.232 +85.9.120.53 +85.9.123.33 +85.9.125.22 +85.15.6.12 +85.15.6.13 +85.15.24.14 +85.133.128.248 +85.133.128.249 +85.133.148.126 +85.133.149.54 +85.133.153.149 +85.133.153.170 +85.133.153.172 +85.133.153.173 +85.133.153.174 +85.133.155.216 +85.133.155.217 +85.133.155.218 +85.133.155.219 +85.133.155.220 +85.133.155.221 +85.133.155.222 +85.133.155.223 +85.133.159.76 +85.133.159.94 +85.133.167.166 +85.133.167.187 +85.133.168.52 +85.133.168.115 +85.133.169.53 +85.133.169.61 +85.133.171.186 +85.133.171.187 +85.133.173.53 +85.133.176.113 +85.133.179.34 +85.133.180.90 +85.133.187.102 +85.133.188.38 +85.133.193.31 +85.133.193.38 +85.133.193.43 +85.133.193.53 +85.133.193.55 +85.133.193.58 +85.133.193.59 +85.133.193.71 +85.133.193.79 +85.133.193.80 +85.133.193.82 +85.133.193.83 +85.133.193.128 +85.133.193.139 +85.133.193.143 +85.133.193.148 +85.133.193.152 +85.133.193.153 +85.133.193.199 +85.133.195.212 +85.133.195.216 +85.133.197.63 +85.133.197.198 +85.133.198.72 +85.133.198.73 +85.133.200.9 +85.133.200.250 +85.133.200.254 +85.133.205.40 +85.133.205.76 +85.133.205.113 +85.133.205.133 +85.133.206.130 +85.133.206.137 +85.133.206.138 +85.133.206.139 +85.133.206.142 +85.133.207.169 +85.133.212.54 +85.133.212.218 +85.133.212.243 +85.133.212.244 +85.133.212.246 +85.133.212.247 +85.133.212.249 +85.133.217.9 +85.133.217.13 +85.133.217.14 +85.133.217.26 +85.133.217.66 +85.133.217.67 +85.133.217.69 +85.133.217.70 +85.133.217.110 +85.133.217.242 +85.133.217.253 +85.133.217.254 +85.133.220.130 +85.133.227.61 +85.133.235.180 +85.133.237.237 +85.133.240.32 +85.133.243.5 +85.133.243.6 +85.133.243.8 +85.133.243.9 +85.133.243.10 +85.133.243.12 +85.133.243.13 +85.133.243.14 +85.133.243.15 +85.133.243.16 +85.133.243.18 +85.133.243.19 +85.133.243.20 +85.133.243.21 +85.133.243.22 +85.133.243.23 +85.133.243.24 +85.133.243.25 +85.133.243.26 +85.133.243.27 +85.133.243.28 +85.133.243.29 +85.133.243.30 +85.133.243.31 +85.133.243.32 +85.133.243.33 +85.133.243.35 +85.133.243.36 +85.133.243.37 +85.133.243.38 +85.133.243.39 +85.133.243.40 +85.133.243.41 +85.133.243.42 +85.133.243.43 +85.133.243.44 +85.133.243.45 +85.133.243.46 +85.133.243.47 +85.133.243.48 +85.133.243.49 +85.133.243.50 +85.133.243.51 +85.133.243.52 +85.133.243.53 +85.133.243.54 +85.133.243.55 +85.133.243.56 +85.133.243.57 +85.133.243.58 +85.133.243.60 +85.133.243.61 +85.133.243.62 +85.133.243.63 +85.133.243.64 +85.133.243.65 +85.133.243.66 +85.133.243.67 +85.133.243.68 +85.133.243.69 +85.133.243.70 +85.133.243.71 +85.133.243.72 +85.133.243.73 +85.133.243.74 +85.133.243.75 +85.133.243.76 +85.133.243.77 +85.133.243.78 +85.133.243.79 +85.133.243.80 +85.133.243.81 +85.133.243.82 +85.133.243.83 +85.133.243.84 +85.133.243.85 +85.133.243.86 +85.133.243.87 +85.133.243.88 +85.133.243.89 +85.133.243.90 +85.133.243.91 +85.133.243.92 +85.133.243.150 +85.133.250.125 +85.133.250.126 +85.133.250.184 +85.133.253.100 +85.133.253.103 +85.185.1.10 +85.185.1.125 +85.185.8.204 +85.185.27.131 +85.185.36.132 +85.185.41.131 +85.185.45.50 +85.185.53.30 +85.185.54.61 +85.185.67.253 +85.185.67.254 +85.185.69.83 +85.185.75.110 +85.185.75.202 +85.185.81.116 +85.185.81.130 +85.185.81.132 +85.185.82.100 +85.185.82.130 +85.185.82.152 +85.185.83.67 +85.185.83.172 +85.185.85.195 +85.185.88.138 +85.185.91.3 +85.185.93.64 +85.185.93.69 +85.185.93.90 +85.185.93.115 +85.185.93.117 +85.185.93.118 +85.185.95.134 +85.185.95.141 +85.185.99.235 +85.185.101.66 +85.185.101.67 +85.185.101.69 +85.185.104.21 +85.185.105.99 +85.185.123.3 +85.185.123.4 +85.185.123.5 +85.185.123.121 +85.185.123.122 +85.185.152.66 +85.185.153.10 +85.185.157.2 +85.185.158.236 +85.185.159.197 +85.185.161.4 +85.185.161.5 +85.185.161.25 +85.185.161.30 +85.185.161.49 +85.185.161.73 +85.185.161.77 +85.185.161.83 +85.185.161.140 +85.185.163.4 +85.185.163.101 +85.185.163.106 +85.185.163.123 +85.185.165.12 +85.185.165.13 +85.185.170.245 +85.185.201.6 +85.185.201.7 +85.185.201.11 +85.185.201.13 +85.185.201.18 +85.185.201.20 +85.185.201.21 +85.185.201.22 +85.185.201.26 +85.185.201.35 +85.185.206.226 +85.185.211.17 +85.185.211.19 +85.185.215.43 +85.185.231.22 +85.185.231.42 +85.185.232.135 +85.185.236.66 +85.185.237.114 +85.185.250.67 +85.185.250.250 +85.185.251.253 +85.185.254.95 +85.198.1.10 +85.198.1.26 +85.198.9.221 +85.198.12.178 +85.198.14.3 +85.198.16.43 +85.198.17.29 +85.198.17.91 +85.198.26.46 +85.198.29.12 +85.204.77.179 +85.204.104.34 +85.204.104.47 +85.208.253.13 +85.208.253.15 +85.208.253.16 +85.208.253.19 +85.208.253.22 +85.208.253.26 +85.208.253.37 +85.208.253.39 +85.208.253.40 +85.208.253.44 +85.208.253.48 +85.208.253.50 +85.208.253.52 +85.208.253.53 +85.208.253.60 +85.208.253.75 +85.208.253.77 +85.208.253.78 +85.208.253.91 +85.208.253.92 +85.208.253.102 +85.208.253.103 +85.208.253.106 +85.208.253.107 +85.208.253.112 +85.208.253.127 +85.208.253.129 +85.208.253.133 +85.208.253.142 +85.208.253.149 +85.208.253.150 +85.208.253.151 +85.208.253.153 +85.208.253.155 +85.208.253.159 +85.208.253.169 +85.208.253.173 +85.208.253.180 +85.208.253.181 +85.208.253.182 +85.208.253.183 +85.208.253.186 +85.208.253.191 +85.208.253.194 +85.208.253.196 +85.208.253.199 +85.208.253.202 +85.208.253.205 +85.208.253.208 +85.208.253.211 +85.208.253.218 +85.208.253.219 +85.208.253.224 +85.208.253.226 +85.208.253.235 +85.208.253.237 +85.208.253.239 +85.208.253.242 +85.208.253.249 +85.208.253.253 +85.208.254.38 +85.208.254.51 +85.208.254.55 +85.208.254.58 +85.208.254.59 +85.208.254.64 +85.208.254.143 +85.208.254.145 +85.208.254.147 +85.208.254.155 +85.208.254.156 +85.208.254.168 +85.208.254.173 +85.208.254.175 +85.208.254.177 +85.208.254.183 +85.208.254.192 +85.208.254.194 +85.208.254.195 +85.208.254.198 +85.208.254.199 +85.208.254.202 +85.208.254.208 +85.208.254.214 +85.208.254.216 +85.208.254.225 +85.208.254.228 +85.208.254.231 +85.208.254.234 +85.208.254.237 +85.208.254.248 +85.208.254.250 +86.104.32.2 +86.104.32.36 +86.104.32.60 +86.104.32.73 +86.104.32.84 +86.104.32.119 +86.104.32.131 +86.104.32.163 +86.104.32.188 +86.104.32.192 +86.104.32.245 +86.104.32.248 +86.104.32.252 +86.104.33.230 +86.104.33.235 +86.104.35.182 +86.104.35.183 +86.104.35.184 +86.104.35.186 +86.104.35.187 +86.104.35.188 +86.104.35.189 +86.104.37.4 +86.104.37.8 +86.104.37.12 +86.104.37.37 +86.104.37.63 +86.104.37.66 +86.104.37.106 +86.104.39.4 +86.104.39.5 +86.104.39.101 +86.104.44.51 +86.104.44.52 +86.104.44.53 +86.104.44.54 +86.104.44.55 +86.104.44.56 +86.104.44.57 +86.104.44.58 +86.104.44.59 +86.104.244.145 +86.106.142.7 +86.106.142.10 +86.106.142.12 +86.106.142.13 +86.106.142.14 +86.106.142.17 +86.106.142.18 +86.106.142.19 +86.106.142.20 +86.106.142.27 +86.106.142.31 +86.106.142.32 +86.106.142.33 +86.106.142.42 +86.106.142.45 +86.106.142.47 +86.106.142.53 +86.106.142.54 +86.106.142.66 +86.106.142.101 +86.106.142.109 +86.106.142.111 +86.106.142.115 +86.106.142.118 +86.106.142.120 +86.106.142.121 +86.106.142.122 +86.106.142.124 +86.107.14.240 +86.107.144.157 +86.107.146.145 +86.107.155.83 +86.107.158.199 +87.107.6.2 +87.107.6.3 +87.107.6.4 +87.107.6.5 +87.107.6.6 +87.107.6.7 +87.107.6.8 +87.107.6.9 +87.107.6.10 +87.107.6.11 +87.107.6.12 +87.107.6.13 +87.107.6.14 +87.107.6.15 +87.107.6.16 +87.107.6.18 +87.107.6.19 +87.107.6.20 +87.107.6.21 +87.107.6.22 +87.107.6.24 +87.107.6.25 +87.107.6.26 +87.107.6.27 +87.107.6.28 +87.107.6.29 +87.107.6.30 +87.107.6.31 +87.107.6.32 +87.107.6.33 +87.107.6.34 +87.107.6.35 +87.107.6.37 +87.107.6.38 +87.107.6.39 +87.107.6.40 +87.107.6.41 +87.107.6.42 +87.107.6.43 +87.107.6.44 +87.107.6.45 +87.107.6.46 +87.107.6.47 +87.107.6.48 +87.107.6.49 +87.107.6.50 +87.107.6.51 +87.107.6.52 +87.107.6.53 +87.107.6.54 +87.107.6.55 +87.107.6.56 +87.107.6.57 +87.107.6.58 +87.107.6.61 +87.107.6.62 +87.107.6.63 +87.107.6.64 +87.107.6.65 +87.107.6.66 +87.107.6.67 +87.107.6.68 +87.107.6.69 +87.107.6.70 +87.107.6.71 +87.107.6.72 +87.107.6.73 +87.107.6.74 +87.107.6.75 +87.107.6.76 +87.107.6.77 +87.107.6.78 +87.107.6.79 +87.107.6.80 +87.107.6.81 +87.107.6.82 +87.107.6.83 +87.107.6.85 +87.107.6.86 +87.107.6.87 +87.107.6.88 +87.107.6.89 +87.107.6.90 +87.107.6.91 +87.107.6.92 +87.107.6.93 +87.107.6.94 +87.107.6.95 +87.107.6.96 +87.107.6.98 +87.107.6.99 +87.107.6.100 +87.107.6.101 +87.107.6.102 +87.107.6.103 +87.107.6.104 +87.107.6.105 +87.107.6.106 +87.107.6.107 +87.107.6.108 +87.107.6.109 +87.107.6.110 +87.107.6.111 +87.107.6.112 +87.107.6.113 +87.107.6.114 +87.107.6.115 +87.107.6.116 +87.107.6.117 +87.107.6.118 +87.107.6.119 +87.107.6.120 +87.107.6.121 +87.107.6.122 +87.107.6.123 +87.107.6.124 +87.107.6.125 +87.107.6.126 +87.107.6.171 +87.107.6.178 +87.107.6.198 +87.107.7.19 +87.107.7.30 +87.107.7.54 +87.107.7.67 +87.107.7.69 +87.107.7.70 +87.107.7.71 +87.107.7.74 +87.107.7.75 +87.107.7.78 +87.107.7.81 +87.107.7.88 +87.107.7.92 +87.107.7.110 +87.107.7.168 +87.107.7.173 +87.107.7.210 +87.107.8.21 +87.107.8.86 +87.107.8.92 +87.107.8.98 +87.107.8.138 +87.107.8.170 +87.107.8.174 +87.107.8.227 +87.107.9.37 +87.107.9.67 +87.107.9.68 +87.107.9.69 +87.107.9.71 +87.107.9.75 +87.107.9.76 +87.107.9.78 +87.107.9.91 +87.107.9.206 +87.107.9.232 +87.107.9.233 +87.107.9.236 +87.107.9.237 +87.107.9.238 +87.107.9.241 +87.107.9.248 +87.107.10.46 +87.107.10.99 +87.107.10.100 +87.107.10.147 +87.107.10.210 +87.107.10.234 +87.107.11.82 +87.107.12.4 +87.107.12.70 +87.107.12.71 +87.107.12.124 +87.107.12.136 +87.107.12.188 +87.107.12.211 +87.107.12.217 +87.107.13.138 +87.107.13.139 +87.107.13.140 +87.107.13.141 +87.107.13.142 +87.107.13.190 +87.107.19.195 +87.107.19.197 +87.107.27.3 +87.107.28.26 +87.107.28.27 +87.107.28.222 +87.107.29.82 +87.107.29.83 +87.107.29.84 +87.107.29.230 +87.107.30.149 +87.107.45.9 +87.107.49.14 +87.107.50.36 +87.107.52.98 +87.107.52.198 +87.107.52.202 +87.107.52.206 +87.107.52.212 +87.107.54.18 +87.107.54.47 +87.107.54.49 +87.107.54.76 +87.107.54.144 +87.107.54.153 +87.107.54.156 +87.107.54.212 +87.107.54.224 +87.107.54.243 +87.107.55.10 +87.107.55.11 +87.107.55.55 +87.107.55.70 +87.107.55.85 +87.107.55.101 +87.107.55.106 +87.107.55.117 +87.107.55.178 +87.107.55.179 +87.107.55.180 +87.107.55.182 +87.107.55.186 +87.107.55.194 +87.107.68.36 +87.107.68.52 +87.107.68.59 +87.107.68.62 +87.107.68.69 +87.107.68.79 +87.107.68.92 +87.107.68.156 +87.107.68.171 +87.107.69.3 +87.107.69.4 +87.107.69.163 +87.107.69.169 +87.107.69.171 +87.107.69.172 +87.107.69.179 +87.107.69.180 +87.107.73.55 +87.107.73.61 +87.107.73.212 +87.107.73.214 +87.107.76.91 +87.107.76.92 +87.107.82.245 +87.107.86.240 +87.107.86.241 +87.107.87.67 +87.107.87.68 +87.107.87.69 +87.107.91.66 +87.107.100.215 +87.107.102.5 +87.107.102.6 +87.107.102.7 +87.107.102.15 +87.107.102.16 +87.107.102.18 +87.107.102.19 +87.107.102.21 +87.107.102.23 +87.107.102.24 +87.107.102.25 +87.107.102.27 +87.107.102.28 +87.107.102.29 +87.107.102.30 +87.107.102.61 +87.107.102.68 +87.107.102.90 +87.107.102.122 +87.107.102.126 +87.107.104.14 +87.107.104.21 +87.107.104.26 +87.107.104.33 +87.107.104.74 +87.107.104.81 +87.107.104.86 +87.107.104.91 +87.107.104.107 +87.107.104.117 +87.107.104.120 +87.107.104.122 +87.107.104.124 +87.107.104.144 +87.107.104.147 +87.107.104.211 +87.107.104.247 +87.107.104.250 +87.107.104.252 +87.107.105.20 +87.107.105.37 +87.107.105.51 +87.107.105.65 +87.107.105.67 +87.107.105.72 +87.107.105.84 +87.107.105.86 +87.107.105.97 +87.107.105.102 +87.107.105.137 +87.107.105.139 +87.107.105.163 +87.107.105.179 +87.107.105.181 +87.107.105.190 +87.107.105.230 +87.107.105.234 +87.107.108.13 +87.107.108.15 +87.107.108.28 +87.107.108.43 +87.107.108.44 +87.107.108.61 +87.107.108.86 +87.107.108.87 +87.107.108.90 +87.107.108.111 +87.107.108.152 +87.107.108.158 +87.107.108.162 +87.107.108.172 +87.107.108.212 +87.107.108.216 +87.107.109.11 +87.107.109.12 +87.107.109.13 +87.107.109.22 +87.107.109.34 +87.107.109.77 +87.107.109.84 +87.107.109.105 +87.107.109.106 +87.107.109.109 +87.107.109.110 +87.107.109.143 +87.107.109.174 +87.107.109.254 +87.107.110.6 +87.107.110.7 +87.107.110.8 +87.107.110.11 +87.107.110.12 +87.107.110.13 +87.107.110.14 +87.107.110.15 +87.107.110.20 +87.107.110.21 +87.107.110.24 +87.107.110.27 +87.107.110.29 +87.107.110.30 +87.107.110.109 +87.107.110.110 +87.107.110.165 +87.107.110.196 +87.107.111.21 +87.107.111.32 +87.107.111.37 +87.107.111.38 +87.107.111.58 +87.107.111.104 +87.107.111.113 +87.107.111.123 +87.107.116.4 +87.107.116.218 +87.107.121.16 +87.107.121.24 +87.107.121.59 +87.107.121.60 +87.107.121.61 +87.107.121.66 +87.107.121.70 +87.107.121.84 +87.107.121.110 +87.107.121.197 +87.107.121.227 +87.107.121.234 +87.107.124.19 +87.107.124.100 +87.107.124.109 +87.107.124.116 +87.107.124.118 +87.107.132.36 +87.107.132.38 +87.107.132.50 +87.107.132.77 +87.107.133.73 +87.107.133.81 +87.107.134.94 +87.107.139.60 +87.107.143.24 +87.107.144.26 +87.107.144.51 +87.107.144.53 +87.107.144.55 +87.107.144.56 +87.107.144.57 +87.107.144.58 +87.107.144.60 +87.107.144.61 +87.107.144.212 +87.107.144.234 +87.107.144.246 +87.107.146.13 +87.107.146.21 +87.107.146.41 +87.107.146.43 +87.107.146.54 +87.107.146.70 +87.107.146.72 +87.107.146.79 +87.107.146.83 +87.107.146.95 +87.107.146.133 +87.107.146.157 +87.107.146.160 +87.107.146.170 +87.107.146.171 +87.107.146.178 +87.107.146.199 +87.107.146.205 +87.107.146.214 +87.107.146.221 +87.107.147.34 +87.107.147.46 +87.107.147.86 +87.107.147.106 +87.107.147.122 +87.107.147.158 +87.107.150.10 +87.107.152.10 +87.107.152.22 +87.107.152.23 +87.107.152.84 +87.107.152.118 +87.107.152.133 +87.107.152.134 +87.107.152.139 +87.107.152.150 +87.107.152.186 +87.107.153.5 +87.107.153.10 +87.107.153.11 +87.107.153.60 +87.107.153.84 +87.107.153.102 +87.107.153.131 +87.107.153.254 +87.107.154.18 +87.107.154.20 +87.107.154.21 +87.107.154.22 +87.107.154.42 +87.107.154.43 +87.107.154.44 +87.107.154.94 +87.107.154.107 +87.107.154.109 +87.107.154.123 +87.107.154.140 +87.107.155.59 +87.107.155.63 +87.107.155.70 +87.107.155.78 +87.107.155.94 +87.107.155.95 +87.107.155.98 +87.107.155.110 +87.107.155.119 +87.107.155.143 +87.107.155.147 +87.107.155.157 +87.107.155.181 +87.107.155.221 +87.107.155.230 +87.107.155.243 +87.107.159.57 +87.107.164.10 +87.107.164.68 +87.107.164.81 +87.107.164.85 +87.107.164.99 +87.107.164.119 +87.107.164.120 +87.107.164.134 +87.107.164.138 +87.107.164.142 +87.107.164.167 +87.107.164.191 +87.107.164.223 +87.107.164.230 +87.107.164.233 +87.107.164.234 +87.107.164.238 +87.107.164.241 +87.107.165.4 +87.107.165.10 +87.107.165.24 +87.107.165.66 +87.107.165.86 +87.107.165.87 +87.107.165.95 +87.107.165.113 +87.107.165.114 +87.107.165.119 +87.107.165.149 +87.107.165.168 +87.107.165.175 +87.107.165.184 +87.107.165.207 +87.107.165.217 +87.107.165.222 +87.107.165.249 +87.107.166.10 +87.107.166.35 +87.107.166.203 +87.107.166.213 +87.107.172.8 +87.107.172.84 +87.107.174.19 +87.107.174.52 +87.107.174.100 +87.107.174.182 +87.107.174.189 +87.107.175.5 +87.107.175.7 +87.107.175.104 +87.107.175.107 +87.107.175.162 +87.107.175.166 +87.107.175.182 +87.107.175.218 +87.107.184.40 +87.107.184.123 +87.107.185.33 +87.107.185.34 +87.107.185.234 +87.107.186.4 +87.107.190.4 +87.107.190.117 +87.107.191.49 +87.236.38.106 +87.236.38.107 +87.236.39.40 +87.236.39.43 +87.236.39.120 +87.236.39.133 +87.236.166.137 +87.236.209.20 +87.236.209.38 +87.236.209.77 +87.236.209.154 +87.236.209.196 +87.236.209.199 +87.236.209.201 +87.236.209.203 +87.236.209.207 +87.236.209.209 +87.236.209.210 +87.236.209.211 +87.236.209.219 +87.236.209.220 +87.236.209.222 +87.236.209.227 +87.236.209.228 +87.236.209.235 +87.236.209.236 +87.236.210.4 +87.236.210.5 +87.236.210.6 +87.236.210.9 +87.236.210.11 +87.236.210.12 +87.236.210.15 +87.236.210.18 +87.236.210.20 +87.236.210.21 +87.236.210.22 +87.236.210.25 +87.236.210.26 +87.236.210.27 +87.236.210.28 +87.236.210.30 +87.236.210.32 +87.236.210.36 +87.236.210.37 +87.236.210.38 +87.236.210.39 +87.236.210.44 +87.236.210.45 +87.236.210.47 +87.236.210.51 +87.236.210.52 +87.236.210.55 +87.236.210.56 +87.236.210.58 +87.236.210.59 +87.236.210.61 +87.236.210.66 +87.236.210.67 +87.236.210.68 +87.236.210.71 +87.236.210.72 +87.236.210.73 +87.236.210.74 +87.236.210.77 +87.236.210.79 +87.236.210.81 +87.236.210.82 +87.236.210.85 +87.236.210.86 +87.236.210.87 +87.236.210.88 +87.236.210.89 +87.236.210.92 +87.236.210.93 +87.236.210.99 +87.236.210.101 +87.236.210.102 +87.236.210.106 +87.236.210.107 +87.236.210.108 +87.236.210.109 +87.236.210.110 +87.236.210.112 +87.236.210.114 +87.236.210.115 +87.236.210.116 +87.236.210.117 +87.236.210.118 +87.236.210.120 +87.236.210.122 +87.236.210.123 +87.236.210.124 +87.236.210.127 +87.236.210.130 +87.236.210.131 +87.236.210.132 +87.236.210.133 +87.236.210.134 +87.236.210.136 +87.236.210.138 +87.236.210.139 +87.236.210.140 +87.236.210.142 +87.236.210.143 +87.236.210.144 +87.236.210.145 +87.236.210.148 +87.236.210.149 +87.236.210.150 +87.236.210.153 +87.236.210.154 +87.236.210.155 +87.236.210.156 +87.236.210.158 +87.236.210.159 +87.236.210.160 +87.236.210.161 +87.236.210.162 +87.236.210.163 +87.236.210.164 +87.236.210.170 +87.236.210.171 +87.236.210.173 +87.236.210.175 +87.236.210.182 +87.236.210.183 +87.236.210.185 +87.236.210.188 +87.236.210.189 +87.236.210.190 +87.236.210.193 +87.236.210.195 +87.236.210.196 +87.236.210.199 +87.236.210.202 +87.236.210.203 +87.236.210.205 +87.236.210.207 +87.236.210.208 +87.236.210.209 +87.236.210.212 +87.236.210.214 +87.236.210.215 +87.236.210.216 +87.236.210.217 +87.236.210.218 +87.236.210.221 +87.236.210.224 +87.236.210.225 +87.236.210.228 +87.236.211.3 +87.236.211.8 +87.236.211.10 +87.236.211.13 +87.236.211.14 +87.236.211.16 +87.236.211.21 +87.236.211.22 +87.236.211.23 +87.236.211.24 +87.236.211.28 +87.236.211.29 +87.236.211.67 +87.236.211.69 +87.236.211.72 +87.236.211.74 +87.236.211.81 +87.236.211.82 +87.236.211.85 +87.236.211.87 +87.236.211.88 +87.236.211.89 +87.236.211.90 +87.236.211.91 +87.236.211.93 +87.236.211.96 +87.236.211.98 +87.236.211.99 +87.236.211.100 +87.236.211.101 +87.236.211.104 +87.236.211.106 +87.236.211.107 +87.236.211.108 +87.236.211.109 +87.236.211.110 +87.236.211.111 +87.236.211.113 +87.236.211.114 +87.236.211.115 +87.236.211.116 +87.236.211.117 +87.236.211.118 +87.236.211.123 +87.236.211.126 +87.236.211.210 +87.236.211.211 +87.236.211.212 +87.236.211.213 +87.236.211.214 +87.236.211.215 +87.236.211.218 +87.236.211.219 +87.236.211.221 +87.236.211.226 +87.236.211.227 +87.236.211.228 +87.236.211.229 +87.236.211.231 +87.236.211.232 +87.236.211.234 +87.236.211.237 +87.236.212.68 +87.236.212.126 +87.236.212.162 +87.236.212.195 +87.236.213.139 +87.236.215.42 +87.236.215.46 +87.236.215.106 +87.236.215.166 +87.236.215.246 +87.247.171.247 +87.247.174.180 +87.247.175.72 +87.247.175.212 +87.247.176.22 +87.247.176.46 +87.247.176.49 +87.247.176.50 +87.247.176.54 +87.247.176.55 +87.247.176.56 +87.247.176.64 +87.247.176.67 +87.247.176.241 +87.247.177.4 +87.247.177.6 +87.247.177.22 +87.247.177.23 +87.247.177.26 +87.247.177.28 +87.247.177.31 +87.247.177.62 +87.247.177.68 +87.247.177.69 +87.247.177.70 +87.247.177.71 +87.247.177.72 +87.247.177.73 +87.247.177.74 +87.247.177.80 +87.247.177.109 +87.247.177.131 +87.247.177.241 +87.247.177.242 +87.247.185.75 +87.247.185.123 +87.247.186.129 +87.247.186.200 +87.247.188.212 +87.248.128.10 +87.248.128.11 +87.248.128.20 +87.248.128.23 +87.248.128.26 +87.248.128.128 +87.248.128.207 +87.248.130.71 +87.248.130.80 +87.248.130.97 +87.248.130.122 +87.248.130.146 +87.248.130.151 +87.248.130.166 +87.248.130.192 +87.248.130.206 +87.248.130.212 +87.248.130.244 +87.248.131.2 +87.248.131.33 +87.248.131.148 +87.248.131.158 +87.248.131.161 +87.248.131.223 +87.248.131.244 +87.248.133.90 +87.248.133.115 +87.248.133.139 +87.248.137.2 +87.248.137.11 +87.248.137.75 +87.248.137.170 +87.248.138.93 +87.248.138.114 +87.248.138.170 +87.248.138.210 +87.248.138.218 +87.248.138.246 +87.248.139.45 +87.248.139.70 +87.248.139.74 +87.248.139.92 +87.248.139.98 +87.248.139.179 +87.248.139.197 +87.248.139.200 +87.248.139.201 +87.248.139.216 +87.248.139.252 +87.248.145.5 +87.248.145.81 +87.248.145.90 +87.248.145.121 +87.248.145.164 +87.248.145.179 +87.248.145.201 +87.248.145.207 +87.248.145.208 +87.248.145.246 +87.248.147.224 +87.248.147.227 +87.248.147.228 +87.248.147.233 +87.248.147.239 +87.248.147.248 +87.248.147.251 +87.248.147.252 +87.248.150.4 +87.248.150.9 +87.248.150.40 +87.248.150.41 +87.248.150.52 +87.248.150.138 +87.248.150.142 +87.248.150.170 +87.248.150.171 +87.248.150.172 +87.248.150.175 +87.248.150.176 +87.248.150.204 +87.248.150.208 +87.248.150.222 +87.248.150.250 +87.248.151.100 +87.248.151.102 +87.248.151.114 +87.248.151.115 +87.248.151.116 +87.248.151.118 +87.248.151.122 +87.248.151.123 +87.248.151.125 +87.248.151.126 +87.248.151.183 +87.248.151.194 +87.248.152.10 +87.248.152.16 +87.248.152.29 +87.248.152.40 +87.248.152.53 +87.248.152.79 +87.248.152.88 +87.248.152.102 +87.248.152.124 +87.248.152.125 +87.248.152.138 +87.248.152.140 +87.248.152.149 +87.248.152.161 +87.248.152.210 +87.248.152.223 +87.248.152.240 +87.248.152.247 +87.248.153.24 +87.248.153.38 +87.248.153.43 +87.248.153.52 +87.248.153.62 +87.248.153.64 +87.248.153.83 +87.248.153.86 +87.248.153.87 +87.248.153.88 +87.248.153.90 +87.248.153.93 +87.248.153.95 +87.248.153.96 +87.248.153.101 +87.248.153.109 +87.248.153.133 +87.248.153.141 +87.248.153.142 +87.248.153.147 +87.248.153.167 +87.248.153.192 +87.248.153.217 +87.248.153.220 +87.248.153.244 +87.248.153.252 +87.248.153.253 +87.248.155.12 +87.248.155.27 +87.248.155.38 +87.248.155.44 +87.248.155.56 +87.248.155.113 +87.248.155.122 +87.248.155.129 +87.248.155.167 +87.248.155.175 +87.248.155.179 +87.248.155.203 +87.248.155.206 +87.248.155.216 +87.248.155.222 +87.248.155.227 +87.248.155.237 +87.248.156.14 +87.248.156.15 +87.248.156.17 +87.248.156.31 +87.248.156.41 +87.248.156.75 +87.248.156.91 +87.248.156.98 +87.248.156.108 +87.248.156.113 +87.248.156.127 +87.248.156.128 +87.248.156.129 +87.248.156.140 +87.248.156.150 +87.248.156.162 +87.248.156.183 +87.248.156.191 +87.248.156.204 +87.248.156.239 +87.248.156.242 +88.135.36.35 +88.135.36.39 +88.135.36.65 +88.135.37.47 +88.135.37.70 +88.135.37.85 +88.135.38.19 +88.135.38.54 +88.135.39.28 +88.135.68.1 +88.135.68.3 +88.135.68.5 +88.135.68.6 +88.135.68.7 +88.135.68.9 +88.135.68.10 +88.135.68.11 +88.135.68.12 +88.135.68.13 +88.135.68.16 +88.135.68.17 +88.135.68.25 +88.135.68.26 +88.135.68.27 +88.135.68.28 +88.135.68.29 +88.135.68.30 +88.135.68.98 +88.135.68.99 +88.135.68.100 +88.135.68.247 +88.135.68.248 +88.135.68.249 +88.135.75.22 +88.135.75.23 +88.135.75.31 +88.135.75.132 +88.135.75.133 +88.135.75.134 +88.135.75.135 +88.135.75.138 +88.135.75.141 +88.135.75.142 +88.135.75.146 +88.135.75.150 +88.135.75.151 +88.135.75.152 +88.135.75.153 +88.135.75.155 +88.135.75.156 +88.135.75.157 +88.135.75.178 +88.135.75.179 +88.135.75.181 +88.135.75.194 +88.135.75.202 +88.135.75.206 +88.218.16.122 +89.32.197.22 +89.32.197.226 +89.32.248.3 +89.32.248.5 +89.32.248.6 +89.32.248.7 +89.32.248.8 +89.32.248.10 +89.32.248.11 +89.32.248.12 +89.32.248.13 +89.32.248.14 +89.32.248.16 +89.32.248.18 +89.32.248.19 +89.32.248.20 +89.32.248.23 +89.32.248.24 +89.32.248.25 +89.32.248.26 +89.32.248.27 +89.32.248.28 +89.32.248.35 +89.32.248.37 +89.32.248.38 +89.32.248.41 +89.32.248.49 +89.32.248.50 +89.32.248.51 +89.32.248.54 +89.32.248.60 +89.32.248.66 +89.32.248.67 +89.32.248.68 +89.32.248.69 +89.32.248.70 +89.32.248.74 +89.32.248.75 +89.32.248.76 +89.32.248.77 +89.32.248.83 +89.32.248.84 +89.32.248.86 +89.32.248.88 +89.32.248.89 +89.32.248.90 +89.32.248.91 +89.32.248.93 +89.32.248.94 +89.32.248.131 +89.32.248.138 +89.32.248.139 +89.32.248.140 +89.32.248.142 +89.32.248.158 +89.32.248.164 +89.32.248.170 +89.32.248.190 +89.32.248.193 +89.32.248.195 +89.32.248.196 +89.32.248.197 +89.32.248.198 +89.32.248.205 +89.32.248.211 +89.32.248.212 +89.32.248.213 +89.32.248.216 +89.32.248.219 +89.32.248.231 +89.32.248.233 +89.32.248.235 +89.32.248.236 +89.32.248.238 +89.32.248.245 +89.32.248.246 +89.32.248.247 +89.32.248.251 +89.32.248.252 +89.32.249.3 +89.32.249.10 +89.32.249.14 +89.32.249.15 +89.32.249.27 +89.32.249.29 +89.32.249.30 +89.32.249.66 +89.32.249.67 +89.32.249.69 +89.32.249.72 +89.32.249.73 +89.32.249.75 +89.32.249.76 +89.32.249.77 +89.32.249.78 +89.32.249.84 +89.32.249.87 +89.32.249.88 +89.32.249.101 +89.32.249.103 +89.32.249.106 +89.32.249.117 +89.32.249.118 +89.32.249.119 +89.32.249.120 +89.32.249.121 +89.32.249.124 +89.32.249.125 +89.32.249.130 +89.32.249.146 +89.32.249.147 +89.32.249.148 +89.32.249.150 +89.32.249.154 +89.32.249.155 +89.32.249.156 +89.32.249.158 +89.32.249.166 +89.32.249.167 +89.32.249.168 +89.32.249.169 +89.32.249.171 +89.32.249.172 +89.32.249.178 +89.32.249.187 +89.32.249.194 +89.32.249.202 +89.32.249.215 +89.32.249.232 +89.32.249.238 +89.32.249.241 +89.32.249.242 +89.32.249.245 +89.32.249.246 +89.32.249.247 +89.32.249.253 +89.32.250.7 +89.32.250.14 +89.32.250.18 +89.32.250.20 +89.32.250.21 +89.32.250.22 +89.32.250.26 +89.32.250.28 +89.32.250.45 +89.32.250.51 +89.32.250.52 +89.32.250.58 +89.32.250.60 +89.32.250.66 +89.32.250.68 +89.32.250.69 +89.32.250.71 +89.32.250.74 +89.32.250.75 +89.32.250.79 +89.32.250.82 +89.32.250.104 +89.32.250.109 +89.32.250.117 +89.32.250.118 +89.32.250.156 +89.32.250.162 +89.32.250.163 +89.32.250.164 +89.32.250.165 +89.32.250.166 +89.32.250.167 +89.32.250.168 +89.32.250.169 +89.32.250.170 +89.32.250.171 +89.32.250.172 +89.32.250.173 +89.32.250.174 +89.32.250.178 +89.32.250.179 +89.32.250.180 +89.32.250.182 +89.32.250.184 +89.32.250.185 +89.32.250.190 +89.32.250.202 +89.32.250.204 +89.32.250.207 +89.32.250.209 +89.32.250.211 +89.32.250.214 +89.32.250.215 +89.32.250.219 +89.32.250.222 +89.32.250.225 +89.32.250.226 +89.32.250.227 +89.32.250.228 +89.32.250.232 +89.32.250.234 +89.32.250.236 +89.32.250.239 +89.32.250.240 +89.32.250.246 +89.32.250.248 +89.32.250.253 +89.32.250.254 +89.32.251.2 +89.32.251.3 +89.32.251.4 +89.32.251.5 +89.32.251.6 +89.32.251.7 +89.32.251.9 +89.32.251.10 +89.32.251.11 +89.32.251.14 +89.32.251.70 +89.32.251.71 +89.32.251.73 +89.32.251.79 +89.32.251.80 +89.32.251.84 +89.32.251.85 +89.32.251.90 +89.32.251.94 +89.32.251.96 +89.32.251.102 +89.32.251.103 +89.32.251.104 +89.32.251.105 +89.32.251.110 +89.32.251.112 +89.32.251.115 +89.32.251.122 +89.32.251.124 +89.32.251.125 +89.32.251.132 +89.32.251.167 +89.33.129.171 +89.33.241.44 +89.34.96.13 +89.34.96.110 +89.34.96.111 +89.34.96.112 +89.34.96.131 +89.34.96.134 +89.34.96.142 +89.34.96.145 +89.34.96.148 +89.34.97.21 +89.34.176.99 +89.35.133.178 +89.35.176.20 +89.35.176.24 +89.37.0.211 +89.37.5.189 +89.37.6.162 +89.37.12.76 +89.37.13.115 +89.37.15.116 +89.37.219.155 +89.37.219.186 +89.37.219.187 +89.37.250.122 +89.39.208.11 +89.39.208.13 +89.39.208.14 +89.39.208.18 +89.39.208.19 +89.39.208.20 +89.39.208.22 +89.39.208.28 +89.39.208.30 +89.39.208.34 +89.39.208.35 +89.39.208.36 +89.39.208.38 +89.39.208.41 +89.39.208.42 +89.39.208.44 +89.39.208.45 +89.39.208.48 +89.39.208.49 +89.39.208.50 +89.39.208.55 +89.39.208.57 +89.39.208.62 +89.39.208.66 +89.39.208.67 +89.39.208.68 +89.39.208.70 +89.39.208.84 +89.39.208.86 +89.39.208.87 +89.39.208.88 +89.39.208.89 +89.39.208.90 +89.39.208.91 +89.39.208.93 +89.39.208.94 +89.39.208.98 +89.39.208.99 +89.39.208.100 +89.39.208.101 +89.39.208.102 +89.39.208.110 +89.39.208.119 +89.39.208.130 +89.39.208.131 +89.39.208.132 +89.39.208.134 +89.39.208.135 +89.39.208.136 +89.39.208.137 +89.39.208.146 +89.39.208.147 +89.39.208.148 +89.39.208.152 +89.39.208.153 +89.39.208.154 +89.39.208.155 +89.39.208.162 +89.39.208.163 +89.39.208.164 +89.39.208.165 +89.39.208.170 +89.39.208.173 +89.39.208.178 +89.39.208.179 +89.39.208.180 +89.39.208.181 +89.39.208.184 +89.39.208.185 +89.39.208.186 +89.39.208.187 +89.39.208.188 +89.39.208.190 +89.39.208.195 +89.39.208.196 +89.39.208.197 +89.39.208.198 +89.39.208.199 +89.39.208.200 +89.39.208.202 +89.39.208.203 +89.39.208.204 +89.39.208.205 +89.39.208.206 +89.39.208.209 +89.39.208.210 +89.39.208.212 +89.39.208.213 +89.39.208.216 +89.39.208.218 +89.39.208.219 +89.39.208.240 +89.39.208.241 +89.39.208.242 +89.39.208.246 +89.39.208.247 +89.39.208.250 +89.39.208.251 +89.40.78.156 +89.40.246.40 +89.40.247.81 +89.42.136.6 +89.42.136.67 +89.42.136.94 +89.42.137.119 +89.42.137.122 +89.42.137.195 +89.42.138.21 +89.42.138.66 +89.42.138.71 +89.42.138.74 +89.42.138.98 +89.42.138.124 +89.42.138.212 +89.42.138.243 +89.42.138.244 +89.42.138.245 +89.42.138.246 +89.42.138.247 +89.42.138.248 +89.42.138.249 +89.42.138.250 +89.42.138.251 +89.42.138.252 +89.42.138.253 +89.42.138.254 +89.42.139.142 +89.42.139.180 +89.42.139.181 +89.42.139.182 +89.42.139.187 +89.42.197.254 +89.42.199.49 +89.42.199.137 +89.42.208.14 +89.42.208.15 +89.42.208.16 +89.42.208.17 +89.42.208.21 +89.42.208.25 +89.42.208.26 +89.42.208.27 +89.42.208.28 +89.42.208.29 +89.42.208.33 +89.42.208.34 +89.42.208.40 +89.42.208.46 +89.42.208.51 +89.42.208.53 +89.42.208.66 +89.42.208.99 +89.42.208.101 +89.42.208.105 +89.42.208.109 +89.42.208.111 +89.42.208.119 +89.42.208.121 +89.42.208.124 +89.42.208.139 +89.42.208.146 +89.42.208.147 +89.42.208.150 +89.42.208.155 +89.42.208.156 +89.42.208.157 +89.42.208.179 +89.42.208.180 +89.42.208.181 +89.42.208.182 +89.42.208.195 +89.42.208.196 +89.42.208.202 +89.42.208.203 +89.42.208.210 +89.42.208.213 +89.42.208.214 +89.42.208.219 +89.42.208.246 +89.42.208.249 +89.42.209.4 +89.42.209.13 +89.42.209.16 +89.42.209.17 +89.42.209.19 +89.42.209.23 +89.42.209.26 +89.42.209.35 +89.42.209.40 +89.42.209.43 +89.42.209.45 +89.42.209.48 +89.42.209.49 +89.42.209.58 +89.42.209.68 +89.42.209.69 +89.42.209.70 +89.42.209.71 +89.42.209.72 +89.42.209.73 +89.42.209.74 +89.42.209.76 +89.42.209.77 +89.42.209.79 +89.42.209.80 +89.42.209.81 +89.42.209.82 +89.42.209.87 +89.42.209.108 +89.42.209.110 +89.42.209.167 +89.42.209.190 +89.42.209.194 +89.42.209.196 +89.42.209.197 +89.42.209.198 +89.42.209.203 +89.42.209.204 +89.42.209.205 +89.42.209.206 +89.42.209.226 +89.42.209.227 +89.42.209.229 +89.42.209.230 +89.42.209.234 +89.42.209.235 +89.42.209.236 +89.42.209.237 +89.42.209.238 +89.42.209.242 +89.42.209.244 +89.42.209.246 +89.42.210.8 +89.42.210.16 +89.42.210.30 +89.42.210.36 +89.42.210.39 +89.42.210.45 +89.42.210.47 +89.42.210.48 +89.42.210.49 +89.42.210.51 +89.42.210.52 +89.42.210.54 +89.42.210.57 +89.42.210.64 +89.42.210.74 +89.42.210.83 +89.42.210.113 +89.42.210.115 +89.42.210.116 +89.42.210.118 +89.42.210.119 +89.42.210.122 +89.42.210.123 +89.42.210.146 +89.42.210.162 +89.42.210.165 +89.42.210.166 +89.42.210.168 +89.42.210.169 +89.42.210.171 +89.42.210.176 +89.42.210.177 +89.42.210.179 +89.42.210.180 +89.42.210.181 +89.42.210.182 +89.42.210.183 +89.42.210.186 +89.42.210.187 +89.42.210.188 +89.42.210.189 +89.42.210.200 +89.42.210.201 +89.42.210.222 +89.42.210.227 +89.42.210.228 +89.42.210.229 +89.42.210.230 +89.42.210.234 +89.42.210.236 +89.42.210.243 +89.42.210.244 +89.42.210.245 +89.42.210.246 +89.42.210.250 +89.42.210.251 +89.42.210.254 +89.42.211.2 +89.42.211.3 +89.42.211.10 +89.42.211.13 +89.42.211.14 +89.42.211.18 +89.42.211.22 +89.42.211.23 +89.42.211.44 +89.42.211.47 +89.42.211.48 +89.42.211.50 +89.42.211.52 +89.42.211.55 +89.42.211.67 +89.42.211.70 +89.42.211.71 +89.42.211.72 +89.42.211.74 +89.42.211.77 +89.42.211.78 +89.42.211.80 +89.42.211.81 +89.42.211.82 +89.42.211.85 +89.42.211.87 +89.42.211.93 +89.42.211.98 +89.42.211.99 +89.42.211.101 +89.42.211.102 +89.42.211.108 +89.42.211.109 +89.42.211.110 +89.42.211.114 +89.42.211.162 +89.42.211.163 +89.42.211.164 +89.42.211.165 +89.42.211.166 +89.42.211.170 +89.42.211.171 +89.42.211.172 +89.42.211.173 +89.42.211.174 +89.42.211.220 +89.42.211.222 +89.42.211.226 +89.42.211.227 +89.42.211.228 +89.42.211.236 +89.43.0.33 +89.43.0.232 +89.43.4.178 +89.43.5.218 +89.43.5.219 +89.43.7.222 +89.43.10.43 +89.43.10.44 +89.43.224.135 +89.43.226.192 +89.43.231.91 +89.43.231.203 +89.44.112.5 +89.44.118.10 +89.44.134.50 +89.44.182.229 +89.45.89.3 +89.45.89.8 +89.45.89.9 +89.46.60.176 +89.46.61.56 +89.46.216.6 +89.46.216.7 +89.46.216.20 +89.46.216.21 +89.46.216.22 +89.46.216.23 +89.46.216.25 +89.46.216.26 +89.46.216.28 +89.46.216.29 +89.46.216.30 +89.46.216.31 +89.46.216.112 +89.46.216.113 +89.46.216.114 +89.46.216.115 +89.46.216.116 +89.46.216.117 +89.46.216.118 +89.46.216.119 +89.46.216.200 +89.46.216.201 +89.46.216.202 +89.46.216.203 +89.46.216.204 +89.46.216.205 +89.46.216.206 +89.46.216.207 +89.46.218.12 +89.46.218.13 +89.46.218.14 +89.46.218.15 +89.46.218.24 +89.46.218.50 +89.46.218.56 +89.46.218.57 +89.46.218.58 +89.46.218.59 +89.46.218.60 +89.46.218.61 +89.46.218.62 +89.46.218.63 +89.46.218.64 +89.46.218.65 +89.46.218.66 +89.46.218.67 +89.46.218.68 +89.46.218.70 +89.46.218.71 +89.46.218.72 +89.46.218.73 +89.46.218.74 +89.46.218.75 +89.46.218.76 +89.46.218.77 +89.46.218.78 +89.46.218.79 +89.46.218.80 +89.46.218.81 +89.46.218.82 +89.46.218.83 +89.46.218.84 +89.46.218.85 +89.46.218.86 +89.46.218.87 +89.46.218.88 +89.46.218.89 +89.46.218.90 +89.46.218.91 +89.46.218.92 +89.46.218.93 +89.46.218.94 +89.46.218.95 +89.46.218.96 +89.46.218.97 +89.46.218.98 +89.46.218.99 +89.46.218.100 +89.46.218.101 +89.46.218.102 +89.46.218.103 +89.46.218.104 +89.46.218.105 +89.46.218.106 +89.46.218.107 +89.46.218.108 +89.46.218.110 +89.46.218.111 +89.46.218.112 +89.46.218.113 +89.46.218.114 +89.46.218.115 +89.46.218.116 +89.46.218.117 +89.46.218.118 +89.46.218.119 +89.46.218.120 +89.46.218.121 +89.46.218.122 +89.46.218.123 +89.46.218.124 +89.46.218.125 +89.46.218.126 +89.46.218.128 +89.46.218.129 +89.46.218.130 +89.46.218.131 +89.46.218.132 +89.46.219.16 +89.46.219.18 +89.46.219.64 +89.46.219.65 +89.46.219.66 +89.46.219.67 +89.46.219.68 +89.46.219.69 +89.46.219.70 +89.46.219.71 +89.46.219.80 +89.46.219.81 +89.46.219.83 +89.46.219.84 +89.46.219.85 +89.46.219.86 +89.46.219.87 +89.46.219.88 +89.46.219.89 +89.46.219.90 +89.46.219.91 +89.46.219.92 +89.46.219.93 +89.46.219.94 +89.46.219.95 +89.46.219.104 +89.46.219.105 +89.46.219.106 +89.46.219.107 +89.46.219.108 +89.46.219.109 +89.46.219.110 +89.46.219.111 +89.46.219.112 +89.46.219.113 +89.46.219.114 +89.46.219.115 +89.46.219.116 +89.46.219.117 +89.46.219.118 +89.46.219.119 +89.46.219.176 +89.46.219.177 +89.46.219.178 +89.46.219.179 +89.46.219.180 +89.46.219.181 +89.46.219.182 +89.46.219.183 +89.46.219.192 +89.46.219.193 +89.46.219.194 +89.46.219.195 +89.46.219.196 +89.46.219.197 +89.46.219.198 +89.46.219.220 +89.46.219.221 +89.46.219.222 +89.46.219.223 +89.144.128.21 +89.144.129.18 +89.144.129.234 +89.144.131.136 +89.144.131.137 +89.144.131.139 +89.144.131.140 +89.144.131.141 +89.144.131.142 +89.144.131.143 +89.144.132.168 +89.144.132.169 +89.144.132.170 +89.144.132.171 +89.144.132.172 +89.144.132.174 +89.144.132.175 +89.144.132.184 +89.144.132.185 +89.144.132.186 +89.144.132.187 +89.144.132.188 +89.144.132.190 +89.144.132.191 +89.144.132.193 +89.144.132.194 +89.144.137.192 +89.144.137.193 +89.144.137.194 +89.144.137.195 +89.144.137.196 +89.144.137.198 +89.144.137.201 +89.144.137.202 +89.144.137.203 +89.144.137.205 +89.144.137.206 +89.144.137.209 +89.144.137.211 +89.144.137.213 +89.144.137.214 +89.144.137.216 +89.144.137.218 +89.144.137.219 +89.144.137.221 +89.144.137.222 +89.144.137.224 +89.144.137.225 +89.144.137.226 +89.144.137.227 +89.144.137.228 +89.144.137.230 +89.144.137.231 +89.144.137.232 +89.144.137.235 +89.144.137.239 +89.144.137.241 +89.144.137.242 +89.144.137.243 +89.144.137.245 +89.144.137.246 +89.144.137.248 +89.144.137.249 +89.144.137.250 +89.144.137.252 +89.144.138.138 +89.144.138.153 +89.144.138.154 +89.144.138.157 +89.144.138.158 +89.144.138.159 +89.144.139.0 +89.144.139.2 +89.144.139.3 +89.144.139.5 +89.144.139.6 +89.144.139.8 +89.144.139.9 +89.144.139.10 +89.144.139.12 +89.144.139.13 +89.144.139.15 +89.144.139.17 +89.144.139.18 +89.144.139.20 +89.144.139.21 +89.144.139.22 +89.144.139.23 +89.144.139.24 +89.144.139.26 +89.144.139.27 +89.144.139.28 +89.144.139.29 +89.144.139.30 +89.144.139.31 +89.144.139.32 +89.144.139.33 +89.144.139.34 +89.144.139.35 +89.144.139.37 +89.144.139.38 +89.144.139.39 +89.144.139.40 +89.144.139.41 +89.144.139.42 +89.144.139.43 +89.144.139.44 +89.144.139.45 +89.144.139.46 +89.144.139.47 +89.144.139.48 +89.144.139.49 +89.144.139.50 +89.144.139.51 +89.144.139.52 +89.144.139.53 +89.144.139.54 +89.144.139.55 +89.144.139.56 +89.144.139.57 +89.144.139.59 +89.144.139.62 +89.144.139.63 +89.144.139.64 +89.144.139.65 +89.144.139.66 +89.144.139.67 +89.144.139.68 +89.144.139.70 +89.144.139.71 +89.144.139.72 +89.144.139.73 +89.144.139.74 +89.144.139.75 +89.144.139.76 +89.144.139.77 +89.144.139.78 +89.144.139.79 +89.144.139.82 +89.144.139.84 +89.144.139.85 +89.144.139.87 +89.144.139.88 +89.144.139.89 +89.144.139.90 +89.144.139.91 +89.144.139.92 +89.144.139.93 +89.144.139.95 +89.144.139.96 +89.144.139.97 +89.144.139.102 +89.144.139.103 +89.144.139.104 +89.144.139.105 +89.144.139.106 +89.144.139.108 +89.144.139.110 +89.144.139.112 +89.144.139.114 +89.144.139.115 +89.144.139.116 +89.144.139.117 +89.144.139.118 +89.144.139.121 +89.144.139.122 +89.144.139.123 +89.144.139.124 +89.144.139.125 +89.144.139.126 +89.144.139.127 +89.144.140.34 +89.144.141.159 +89.144.141.220 +89.144.143.130 +89.144.144.118 +89.144.144.144 +89.144.145.210 +89.144.145.212 +89.144.145.213 +89.144.145.214 +89.144.145.215 +89.144.151.22 +89.144.151.251 +89.144.151.252 +89.144.154.98 +89.144.156.249 +89.144.166.144 +89.144.166.145 +89.144.166.146 +89.144.166.147 +89.144.166.148 +89.144.166.149 +89.144.166.150 +89.144.166.151 +89.144.166.152 +89.144.166.153 +89.144.166.154 +89.144.166.155 +89.144.166.156 +89.144.166.157 +89.144.166.158 +89.144.166.159 +89.144.166.160 +89.144.166.166 +89.144.166.167 +89.144.166.168 +89.144.166.169 +89.144.166.170 +89.144.166.171 +89.144.166.173 +89.144.166.174 +89.144.166.175 +89.144.166.176 +89.144.166.177 +89.144.166.178 +89.144.166.179 +89.144.166.180 +89.144.166.181 +89.144.166.182 +89.144.166.183 +89.144.166.184 +89.144.166.185 +89.144.166.186 +89.144.166.187 +89.144.166.189 +89.144.166.190 +89.144.166.192 +89.144.166.194 +89.144.166.195 +89.144.166.196 +89.144.166.197 +89.144.166.198 +89.144.166.199 +89.144.166.200 +89.144.166.201 +89.144.166.202 +89.144.166.203 +89.144.166.204 +89.144.166.205 +89.144.166.206 +89.144.166.207 +89.144.166.208 +89.144.166.209 +89.144.166.210 +89.144.166.211 +89.144.166.212 +89.144.166.213 +89.144.166.214 +89.144.166.215 +89.144.166.216 +89.144.166.218 +89.144.166.219 +89.144.166.220 +89.144.166.221 +89.144.166.222 +89.144.166.223 +89.144.166.224 +89.144.166.225 +89.144.166.226 +89.144.166.227 +89.144.166.228 +89.144.166.229 +89.144.166.230 +89.144.166.231 +89.144.166.240 +89.144.166.241 +89.144.166.243 +89.144.166.244 +89.144.166.245 +89.144.166.246 +89.144.166.247 +89.144.166.248 +89.144.166.249 +89.144.166.250 +89.144.166.251 +89.144.166.252 +89.144.166.253 +89.144.166.254 +89.144.166.255 +89.144.176.190 +89.144.182.3 +89.144.183.0 +89.144.183.1 +89.144.183.2 +89.144.183.3 +89.144.183.4 +89.144.183.5 +89.144.183.6 +89.144.183.7 +89.144.183.16 +89.144.183.17 +89.144.183.18 +89.144.183.19 +89.144.183.22 +89.144.183.23 +89.144.183.42 +89.144.187.66 +89.144.187.67 +89.144.187.68 +89.144.188.87 +89.144.189.112 +89.144.189.115 +89.144.189.118 +89.144.189.119 +89.144.189.121 +89.144.189.122 +89.144.189.123 +89.144.189.125 +89.144.189.126 +89.144.189.127 +89.144.189.128 +89.144.189.130 +89.144.189.131 +89.144.189.134 +89.144.189.135 +89.144.189.139 +89.144.189.141 +89.144.189.143 +89.144.189.144 +89.144.189.145 +89.144.189.146 +89.144.189.148 +89.144.189.149 +89.144.189.150 +89.144.189.158 +89.144.189.163 +89.144.189.167 +89.144.189.169 +89.144.189.171 +89.144.189.173 +89.144.189.175 +89.144.189.178 +89.144.189.179 +89.144.189.180 +89.144.189.182 +89.144.189.183 +89.144.189.184 +89.144.189.187 +89.144.189.188 +89.144.189.190 +89.144.189.193 +89.144.189.195 +89.144.189.199 +89.144.189.200 +89.144.189.201 +89.144.189.203 +89.144.189.205 +89.144.189.206 +89.144.189.210 +89.144.189.212 +89.144.189.215 +89.144.189.216 +89.144.189.217 +89.144.189.218 +89.144.189.220 +89.144.189.221 +89.144.189.222 +89.144.189.224 +89.144.189.225 +89.144.189.226 +89.144.189.229 +89.144.189.231 +89.144.189.233 +89.144.189.235 +89.144.189.238 +89.144.189.242 +89.144.189.243 +89.144.189.244 +89.144.189.245 +89.144.189.246 +89.144.189.247 +89.144.189.249 +89.144.189.250 +89.144.189.251 +89.144.189.253 +89.144.189.254 +89.144.189.255 +89.165.0.13 +89.165.0.14 +89.165.1.212 +89.165.1.232 +89.165.56.19 +89.219.64.58 +89.219.64.157 +89.219.65.151 +89.219.66.83 +89.219.66.163 +89.219.79.161 +89.219.81.18 +89.219.83.86 +89.219.83.227 +89.219.84.161 +89.219.84.162 +89.219.89.61 +89.219.92.21 +89.219.94.12 +89.219.96.28 +89.219.112.149 +89.219.192.22 +89.219.200.102 +89.219.216.206 +89.219.219.255 +89.219.220.231 +89.219.233.8 +89.219.237.50 +89.219.237.81 +89.219.237.239 +89.219.239.108 +89.219.252.233 +89.221.80.9 +89.221.80.10 +89.221.81.31 +89.221.81.140 +89.221.83.12 +89.221.83.20 +89.235.64.65 +89.235.64.66 +89.235.66.5 +89.235.66.6 +89.235.67.4 +89.235.67.5 +89.235.68.2 +89.235.68.5 +89.235.69.2 +89.235.79.4 +89.235.79.5 +89.235.79.6 +89.235.79.8 +89.235.79.9 +89.235.79.13 +89.235.79.15 +89.235.79.16 +89.235.79.19 +89.235.79.20 +89.235.79.21 +89.235.79.22 +89.235.79.23 +89.235.79.24 +89.235.79.26 +89.235.79.27 +89.235.79.29 +89.235.79.32 +89.235.79.35 +89.235.79.39 +89.235.79.41 +89.235.79.43 +89.235.79.44 +89.235.79.45 +89.235.79.46 +89.235.79.47 +89.235.79.48 +89.235.79.51 +89.235.79.52 +89.235.79.54 +89.235.79.55 +89.235.79.56 +89.235.79.57 +89.235.79.58 +89.235.79.59 +89.235.79.60 +89.235.79.67 +89.235.79.68 +89.235.79.110 +89.235.116.201 +89.235.116.202 +89.235.116.203 +89.235.116.211 +89.235.116.216 +91.92.114.189 +91.92.121.183 +91.92.121.200 +91.92.125.90 +91.92.126.55 +91.92.127.236 +91.92.130.216 +91.92.132.42 +91.92.158.85 +91.92.158.146 +91.92.158.186 +91.92.159.116 +91.92.180.77 +91.92.180.198 +91.92.182.182 +91.92.186.205 +91.92.187.146 +91.92.188.192 +91.92.190.5 +91.92.190.17 +91.92.191.165 +91.92.204.208 +91.92.204.220 +91.92.209.6 +91.92.209.151 +91.92.213.228 +91.92.214.28 +91.92.214.110 +91.92.214.241 +91.92.215.66 +91.92.215.250 +91.106.65.46 +91.106.65.47 +91.106.78.194 +91.106.85.10 +91.106.86.218 +91.106.87.54 +91.106.91.5 +91.106.91.6 +91.108.128.18 +91.108.129.121 +91.108.130.18 +91.108.130.203 +91.108.130.204 +91.108.130.206 +91.108.152.115 +91.108.152.116 +91.185.133.81 +91.186.193.4 +91.186.193.20 +91.186.193.21 +91.186.193.29 +91.186.193.32 +91.186.193.35 +91.186.193.41 +91.186.193.89 +91.186.193.108 +91.186.193.125 +91.186.193.130 +91.186.193.131 +91.186.193.132 +91.186.193.133 +91.186.193.134 +91.186.193.162 +91.186.193.192 +91.186.193.193 +91.186.193.194 +91.186.193.196 +91.186.193.229 +91.186.193.230 +91.186.193.232 +91.197.242.5 +91.197.242.6 +91.199.9.87 +91.199.9.199 +91.199.27.62 +91.199.27.83 +91.199.27.185 +91.199.27.237 +91.199.30.171 +91.199.30.196 +91.199.215.3 +91.199.215.26 +91.199.215.30 +91.199.215.69 +91.199.215.70 +91.199.215.72 +91.199.215.73 +91.199.215.77 +91.199.215.78 +91.199.215.115 +91.199.215.248 +91.207.138.55 +91.207.205.4 +91.207.205.5 +91.207.205.6 +91.207.205.8 +91.207.205.9 +91.207.205.10 +91.207.205.11 +91.207.205.12 +91.207.205.13 +91.207.205.14 +91.207.205.15 +91.207.205.16 +91.207.205.17 +91.207.205.18 +91.207.205.19 +91.207.205.20 +91.207.205.21 +91.207.205.22 +91.207.205.24 +91.207.205.26 +91.207.205.29 +91.207.205.30 +91.207.205.34 +91.207.205.35 +91.207.205.36 +91.207.205.37 +91.207.205.38 +91.207.205.41 +91.207.205.44 +91.207.205.46 +91.207.205.47 +91.207.205.48 +91.207.205.50 +91.208.163.196 +91.212.174.5 +91.212.174.10 +91.212.174.21 +91.212.174.28 +91.212.174.39 +91.212.174.43 +91.212.174.48 +91.212.174.50 +91.212.174.56 +91.212.174.80 +91.212.174.86 +91.212.174.95 +91.212.174.98 +91.212.174.100 +91.212.174.105 +91.212.174.114 +91.212.174.119 +91.212.174.131 +91.212.174.133 +91.212.174.136 +91.212.174.138 +91.212.174.141 +91.212.174.148 +91.212.174.149 +91.212.174.163 +91.212.174.178 +91.212.174.183 +91.212.174.184 +91.212.174.185 +91.212.174.191 +91.212.174.196 +91.212.174.205 +91.212.174.226 +91.212.174.233 +91.212.174.250 +91.212.252.53 +91.212.252.54 +91.212.252.184 +91.212.252.200 +91.212.252.201 +91.216.171.3 +91.216.171.4 +91.216.171.8 +91.220.113.2 +91.220.113.7 +91.220.113.18 +91.220.113.22 +91.220.113.40 +91.220.113.47 +91.220.113.119 +91.220.113.125 +91.220.113.151 +91.220.113.157 +91.220.113.167 +91.220.113.172 +91.220.113.185 +91.220.113.199 +91.220.113.200 +91.220.113.207 +91.220.113.221 +91.220.113.231 +91.220.113.239 +91.220.113.241 +91.220.113.245 +91.220.113.246 +91.220.113.251 +91.220.113.252 +91.221.241.200 +91.222.199.9 +91.222.199.95 +91.222.199.98 +91.222.199.250 +91.222.199.251 +91.222.199.252 +91.223.116.1 +91.223.116.15 +91.223.116.50 +91.223.116.52 +91.223.116.61 +91.223.116.116 +91.223.116.128 +91.223.116.130 +91.223.116.134 +91.223.116.135 +91.223.116.136 +91.223.116.137 +91.223.116.138 +91.223.116.143 +91.223.116.144 +91.223.116.146 +91.223.116.147 +91.223.116.152 +91.223.116.153 +91.223.116.156 +91.223.116.158 +91.223.116.161 +91.223.116.163 +91.223.116.167 +91.223.116.168 +91.223.116.170 +91.223.116.171 +91.223.116.172 +91.223.116.175 +91.223.116.187 +91.223.116.194 +91.223.116.196 +91.223.116.200 +91.223.116.206 +91.223.116.220 +91.223.116.221 +91.223.116.234 +91.223.116.249 +91.223.146.10 +91.223.146.20 +91.224.110.2 +91.224.110.56 +91.224.110.58 +91.224.111.101 +91.225.54.67 +91.225.54.70 +91.228.132.6 +91.228.132.21 +91.228.132.23 +91.228.132.91 +91.228.133.91 +91.232.73.11 +91.232.73.12 +91.232.73.55 +91.232.73.56 +91.232.73.66 +91.232.73.67 +91.234.39.11 +91.234.39.15 +91.234.39.16 +91.234.39.32 +91.234.39.33 +91.234.39.44 +91.234.39.58 +91.234.39.61 +91.234.39.68 +91.234.39.112 +91.234.39.115 +91.234.39.179 +91.234.39.181 +91.234.39.203 +91.234.39.208 +91.234.39.214 +91.234.39.228 +91.234.39.234 +91.236.168.56 +91.236.168.68 +91.236.168.84 +91.236.168.121 +91.236.168.145 +91.236.168.164 +91.236.168.167 +91.236.168.178 +91.236.168.179 +91.236.168.208 +91.236.168.210 +91.236.168.220 +91.236.168.249 +91.236.168.250 +91.236.168.251 +91.236.168.252 +91.236.168.253 +91.236.169.187 +91.239.14.3 +91.239.14.4 +91.239.214.2 +91.239.214.130 +91.240.60.5 +91.240.60.10 +91.240.60.15 +91.240.60.60 +91.240.60.61 +91.240.61.110 +91.240.62.2 +91.240.62.10 +91.240.62.11 +91.240.63.37 +91.240.63.114 +91.240.63.115 +91.240.180.10 +91.240.180.11 +91.240.180.215 +91.240.180.216 +91.240.182.210 +91.240.182.211 +91.242.44.228 +91.243.127.102 +91.243.171.83 +91.243.171.172 +91.245.228.96 +91.245.228.101 +91.245.228.102 +91.245.228.250 +91.245.228.251 +91.245.229.1 +91.245.229.66 +91.246.31.25 +91.246.44.9 +91.246.44.112 +91.247.171.14 +91.247.171.36 +91.247.171.61 +91.247.171.65 +91.247.171.75 +91.247.171.80 +91.247.171.135 +91.247.171.146 +91.247.171.154 +91.247.171.203 +91.247.171.231 +91.247.174.1 +92.42.50.58 +92.42.50.209 +92.42.50.210 +92.42.51.109 +92.42.51.209 +92.42.53.28 +92.42.53.29 +92.42.207.35 +92.43.163.198 +92.61.181.55 +92.61.181.181 +92.61.181.253 +92.61.181.254 +92.61.183.8 +92.61.183.136 +92.61.183.141 +92.61.183.146 +92.114.17.228 +92.114.17.229 +92.114.17.230 +92.114.18.46 +92.114.18.100 +92.114.18.102 +92.114.18.104 +92.114.18.111 +92.114.18.116 +92.114.18.117 +92.114.19.9 +92.114.19.28 +92.114.19.29 +92.114.19.41 +92.114.19.226 +92.114.19.251 +92.114.19.252 +92.114.24.110 +92.114.28.66 +92.114.31.112 +92.114.50.2 +92.114.51.151 +92.119.57.68 +92.119.57.75 +92.119.57.82 +92.119.57.108 +92.119.57.109 +92.119.57.110 +92.119.57.134 +92.119.57.147 +92.119.57.164 +92.119.57.176 +92.119.57.210 +92.119.58.22 +92.119.58.32 +92.119.58.59 +92.119.58.60 +92.119.58.62 +92.119.58.114 +92.119.58.158 +92.242.195.53 +92.242.195.54 +92.242.198.7 +92.242.198.21 +92.242.198.22 +92.242.198.42 +92.242.198.50 +92.242.198.51 +92.242.198.52 +92.242.198.123 +92.242.198.166 +92.242.198.181 +92.242.198.230 +92.242.198.233 +92.242.198.253 +92.242.201.2 +92.242.201.10 +92.242.201.11 +92.242.203.85 +92.242.204.73 +92.242.204.74 +92.242.205.40 +92.242.207.200 +92.242.210.6 +92.242.212.103 +92.242.216.50 +92.242.216.150 +92.242.216.248 +92.242.220.116 +92.242.220.122 +92.242.220.162 +92.242.220.200 +92.242.220.228 +92.242.220.238 +92.242.223.115 +92.242.223.116 +92.246.144.179 +92.246.144.205 +92.246.145.99 +92.246.146.139 +93.113.224.58 +93.113.224.60 +93.113.224.62 +93.113.225.7 +93.113.225.8 +93.113.226.82 +93.113.234.62 +93.113.234.67 +93.113.235.105 +93.113.235.106 +93.113.235.107 +93.113.235.108 +93.113.236.50 +93.113.236.52 +93.113.236.53 +93.113.237.170 +93.113.238.2 +93.113.239.55 +93.113.239.56 +93.113.239.57 +93.113.239.58 +93.114.110.20 +93.115.126.14 +93.115.127.9 +93.115.144.212 +93.115.148.150 +93.115.149.88 +93.115.150.27 +93.115.150.28 +93.115.150.29 +93.115.150.66 +93.115.150.68 +93.115.150.229 +93.115.150.230 +93.115.150.231 +93.115.150.235 +93.115.150.236 +93.115.150.237 +93.115.150.238 +93.115.151.75 +93.115.151.76 +93.115.151.77 +93.115.151.78 +93.115.151.100 +93.115.151.138 +93.115.219.48 +93.117.27.61 +93.117.30.40 +93.117.30.53 +93.117.36.33 +93.117.37.237 +93.117.39.171 +93.117.43.82 +93.117.46.181 +93.117.46.187 +93.117.47.43 +93.117.127.141 +93.118.99.182 +93.118.104.137 +93.118.108.162 +93.118.109.213 +93.118.110.144 +93.118.112.28 +93.118.112.76 +93.118.112.118 +93.118.115.173 +93.118.116.13 +93.118.117.12 +93.118.121.130 +93.118.122.27 +93.118.123.140 +93.118.125.167 +93.118.126.176 +93.118.126.196 +93.118.127.96 +93.118.132.34 +93.118.134.242 +93.118.135.237 +93.118.137.33 +93.118.137.209 +93.118.138.109 +93.118.140.95 +93.118.140.160 +93.118.144.69 +93.118.145.139 +93.118.146.112 +93.118.148.134 +93.118.153.59 +93.118.153.218 +93.118.156.84 +93.118.160.148 +93.118.160.151 +93.118.163.159 +93.118.163.238 +93.118.164.87 +93.118.164.193 +93.118.165.89 +93.118.166.2 +93.118.167.175 +93.118.182.221 +93.118.183.120 +93.119.57.119 +93.126.2.3 +93.126.2.4 +93.126.2.77 +93.126.2.212 +93.126.3.99 +93.126.3.169 +93.126.6.131 +93.126.6.132 +93.126.6.133 +93.126.6.134 +93.126.6.145 +93.126.6.146 +93.126.6.147 +93.126.6.150 +93.126.6.151 +93.126.6.176 +93.126.6.178 +93.126.6.182 +93.126.6.186 +93.126.23.245 +93.126.23.251 +93.126.25.8 +93.126.25.10 +93.126.25.15 +93.126.31.166 +94.74.128.2 +94.74.128.3 +94.74.128.4 +94.74.128.12 +94.74.128.13 +94.74.128.18 +94.74.128.110 +94.74.128.129 +94.74.128.234 +94.74.128.252 +94.74.145.11 +94.74.145.14 +94.74.145.16 +94.74.145.23 +94.74.145.24 +94.74.145.27 +94.74.145.122 +94.74.145.130 +94.74.145.155 +94.74.145.173 +94.74.145.178 +94.74.145.191 +94.74.145.202 +94.74.163.130 +94.74.168.36 +94.74.168.179 +94.74.170.119 +94.74.170.162 +94.74.170.167 +94.101.136.138 +94.101.140.196 +94.101.176.219 +94.101.177.8 +94.101.177.34 +94.101.177.39 +94.101.178.79 +94.101.179.19 +94.101.179.98 +94.101.179.105 +94.101.180.19 +94.101.180.39 +94.101.180.41 +94.101.184.158 +94.101.184.190 +94.101.184.222 +94.101.184.254 +94.101.185.65 +94.101.185.73 +94.101.185.75 +94.101.185.151 +94.101.186.22 +94.101.186.40 +94.101.186.107 +94.101.186.138 +94.101.186.151 +94.101.187.89 +94.101.187.195 +94.101.190.208 +94.101.190.209 +94.182.17.202 +94.182.17.205 +94.182.17.206 +94.182.17.214 +94.182.18.246 +94.182.20.10 +94.182.28.3 +94.182.28.56 +94.182.37.50 +94.182.40.34 +94.182.40.35 +94.182.40.70 +94.182.40.78 +94.182.42.194 +94.182.44.54 +94.182.50.77 +94.182.83.197 +94.182.83.200 +94.182.83.201 +94.182.83.202 +94.182.83.205 +94.182.83.206 +94.182.83.209 +94.182.83.210 +94.182.83.211 +94.182.83.212 +94.182.83.214 +94.182.83.216 +94.182.83.219 +94.182.83.233 +94.182.83.234 +94.182.83.235 +94.182.83.236 +94.182.83.237 +94.182.83.238 +94.182.83.246 +94.182.83.247 +94.182.83.248 +94.182.83.252 +94.182.84.172 +94.182.85.227 +94.182.85.231 +94.182.85.232 +94.182.85.237 +94.182.85.244 +94.182.85.250 +94.182.85.252 +94.182.86.187 +94.182.86.190 +94.182.87.209 +94.182.87.210 +94.182.87.211 +94.182.87.212 +94.182.87.214 +94.182.87.215 +94.182.87.216 +94.182.87.217 +94.182.87.218 +94.182.87.219 +94.182.87.220 +94.182.87.222 +94.182.88.18 +94.182.88.20 +94.182.88.21 +94.182.88.22 +94.182.88.116 +94.182.88.120 +94.182.89.41 +94.182.89.107 +94.182.89.131 +94.182.89.132 +94.182.89.133 +94.182.89.135 +94.182.89.136 +94.182.89.137 +94.182.89.138 +94.182.89.139 +94.182.89.142 +94.182.89.143 +94.182.89.145 +94.182.89.146 +94.182.89.147 +94.182.89.148 +94.182.89.150 +94.182.89.152 +94.182.89.154 +94.182.89.155 +94.182.89.156 +94.182.89.157 +94.182.89.159 +94.182.89.161 +94.182.89.163 +94.182.89.167 +94.182.89.169 +94.182.89.170 +94.182.89.174 +94.182.89.175 +94.182.89.179 +94.182.89.182 +94.182.89.224 +94.182.89.227 +94.182.89.235 +94.182.89.236 +94.182.89.237 +94.182.89.247 +94.182.90.18 +94.182.90.19 +94.182.90.20 +94.182.90.21 +94.182.90.66 +94.182.90.149 +94.182.90.170 +94.182.90.200 +94.182.90.210 +94.182.90.213 +94.182.90.216 +94.182.90.217 +94.182.91.114 +94.182.91.126 +94.182.91.130 +94.182.91.133 +94.182.91.134 +94.182.92.19 +94.182.92.57 +94.182.92.80 +94.182.92.165 +94.182.93.45 +94.182.93.133 +94.182.94.5 +94.182.94.67 +94.182.94.75 +94.182.94.121 +94.182.94.170 +94.182.94.217 +94.182.95.2 +94.182.95.3 +94.182.95.4 +94.182.95.6 +94.182.95.8 +94.182.95.246 +94.182.97.45 +94.182.102.102 +94.182.102.200 +94.182.104.11 +94.182.104.12 +94.182.104.13 +94.182.104.14 +94.182.105.213 +94.182.105.215 +94.182.107.227 +94.182.108.98 +94.182.108.150 +94.182.110.234 +94.182.110.235 +94.182.110.236 +94.182.110.237 +94.182.110.238 +94.182.113.117 +94.182.115.130 +94.182.115.173 +94.182.128.123 +94.182.128.124 +94.182.129.5 +94.182.129.114 +94.182.129.121 +94.182.130.2 +94.182.130.91 +94.182.130.93 +94.182.133.6 +94.182.133.36 +94.182.133.70 +94.182.133.73 +94.182.133.130 +94.182.133.254 +94.182.135.77 +94.182.135.254 +94.182.136.19 +94.182.136.39 +94.182.136.45 +94.182.136.50 +94.182.136.51 +94.182.136.106 +94.182.138.37 +94.182.140.18 +94.182.140.19 +94.182.140.20 +94.182.140.21 +94.182.140.22 +94.182.142.40 +94.182.142.41 +94.182.142.42 +94.182.142.43 +94.182.142.45 +94.182.142.48 +94.182.142.49 +94.182.142.50 +94.182.142.54 +94.182.142.152 +94.182.142.176 +94.182.142.177 +94.182.142.201 +94.182.142.205 +94.182.142.216 +94.182.142.248 +94.182.146.2 +94.182.146.116 +94.182.146.191 +94.182.146.252 +94.182.147.138 +94.182.150.180 +94.182.150.181 +94.182.151.14 +94.182.153.4 +94.182.153.26 +94.182.153.30 +94.182.153.34 +94.182.153.38 +94.182.153.78 +94.182.153.99 +94.182.153.100 +94.182.153.164 +94.182.153.169 +94.182.153.170 +94.182.153.173 +94.182.153.174 +94.182.153.175 +94.182.153.197 +94.182.154.12 +94.182.154.20 +94.182.154.22 +94.182.154.67 +94.182.154.68 +94.182.154.69 +94.182.154.71 +94.182.154.105 +94.182.154.111 +94.182.154.201 +94.182.154.220 +94.182.154.246 +94.182.154.247 +94.182.154.251 +94.182.155.10 +94.182.155.111 +94.182.155.121 +94.182.156.91 +94.182.156.118 +94.182.156.122 +94.182.156.187 +94.182.156.220 +94.182.157.181 +94.182.157.185 +94.182.157.187 +94.182.159.34 +94.182.159.35 +94.182.159.36 +94.182.159.37 +94.182.159.38 +94.182.159.41 +94.182.159.51 +94.182.159.55 +94.182.159.56 +94.182.169.52 +94.182.169.120 +94.182.169.124 +94.182.170.219 +94.182.171.83 +94.182.171.85 +94.182.171.94 +94.182.172.17 +94.182.172.18 +94.182.172.21 +94.182.172.36 +94.182.172.46 +94.182.172.47 +94.182.172.48 +94.182.172.49 +94.182.172.51 +94.182.172.52 +94.182.172.53 +94.182.172.194 +94.182.172.196 +94.182.172.197 +94.182.172.198 +94.182.172.200 +94.182.172.203 +94.182.172.204 +94.182.172.206 +94.182.172.209 +94.182.172.210 +94.182.172.211 +94.182.172.212 +94.182.172.213 +94.182.172.216 +94.182.172.218 +94.182.172.219 +94.182.172.251 +94.182.172.254 +94.182.173.82 +94.182.173.189 +94.182.174.43 +94.182.174.165 +94.182.174.172 +94.182.174.173 +94.182.175.2 +94.182.175.4 +94.182.175.5 +94.182.175.6 +94.182.175.10 +94.182.175.11 +94.182.175.12 +94.182.175.14 +94.182.175.15 +94.182.175.17 +94.182.175.20 +94.182.175.35 +94.182.175.38 +94.182.175.44 +94.182.175.50 +94.182.175.53 +94.182.175.55 +94.182.175.56 +94.182.176.184 +94.182.176.188 +94.182.177.43 +94.182.177.67 +94.182.177.68 +94.182.177.70 +94.182.177.72 +94.182.177.73 +94.182.177.80 +94.182.177.81 +94.182.177.82 +94.182.178.50 +94.182.178.52 +94.182.178.53 +94.182.178.54 +94.182.178.55 +94.182.178.71 +94.182.178.73 +94.182.178.75 +94.182.178.80 +94.182.178.81 +94.182.178.83 +94.182.178.85 +94.182.178.88 +94.182.178.91 +94.182.178.92 +94.182.178.93 +94.182.178.94 +94.182.178.95 +94.182.178.97 +94.182.178.98 +94.182.178.100 +94.182.178.107 +94.182.178.109 +94.182.178.110 +94.182.178.111 +94.182.178.112 +94.182.178.113 +94.182.178.114 +94.182.178.115 +94.182.178.116 +94.182.178.117 +94.182.178.118 +94.182.178.119 +94.182.178.120 +94.182.178.121 +94.182.178.122 +94.182.178.132 +94.182.178.233 +94.182.179.232 +94.182.180.140 +94.182.181.50 +94.182.181.222 +94.182.182.98 +94.182.183.211 +94.182.184.190 +94.182.185.95 +94.182.185.97 +94.182.185.126 +94.182.185.226 +94.182.185.227 +94.182.186.79 +94.182.186.231 +94.182.186.232 +94.182.186.250 +94.182.187.148 +94.182.187.149 +94.182.190.229 +94.182.190.245 +94.182.192.178 +94.182.195.42 +94.182.195.54 +94.182.195.107 +94.182.195.158 +94.182.195.164 +94.182.195.182 +94.182.195.183 +94.182.195.188 +94.182.195.194 +94.182.195.218 +94.182.195.223 +94.182.195.225 +94.182.195.231 +94.182.195.243 +94.182.195.247 +94.182.196.187 +94.182.198.62 +94.182.198.146 +94.182.199.222 +94.182.202.78 +94.182.208.45 +94.182.208.46 +94.182.209.58 +94.182.209.66 +94.182.209.70 +94.182.209.71 +94.182.214.34 +94.182.214.182 +94.182.215.6 +94.182.215.72 +94.182.215.75 +94.182.215.90 +94.182.227.5 +94.182.227.8 +94.182.227.10 +94.182.227.19 +94.182.227.34 +94.182.227.37 +94.182.227.38 +94.182.227.40 +94.182.227.42 +94.182.227.56 +94.182.227.61 +94.182.227.80 +94.182.227.83 +94.182.227.85 +94.182.227.96 +94.182.227.99 +94.182.227.103 +94.182.227.120 +94.182.227.121 +94.182.227.131 +94.182.227.134 +94.182.227.164 +94.182.227.165 +94.182.227.166 +94.182.227.167 +94.182.227.170 +94.182.227.181 +94.182.227.194 +94.182.227.195 +94.182.227.200 +94.182.227.209 +94.182.227.219 +94.182.227.225 +94.182.227.229 +94.182.227.237 +94.182.227.238 +94.182.227.241 +94.182.227.242 +94.182.227.246 +94.182.227.247 +94.182.242.13 +94.182.249.11 +94.182.250.30 +94.182.251.16 +94.182.255.154 +94.183.0.19 +94.183.2.164 +94.183.3.131 +94.183.3.199 +94.183.3.233 +94.183.8.70 +94.183.10.192 +94.183.10.193 +94.183.12.86 +94.183.14.129 +94.183.14.154 +94.183.17.153 +94.183.25.223 +94.183.26.90 +94.183.27.1 +94.183.29.64 +94.183.30.17 +94.183.31.47 +94.183.32.45 +94.183.33.193 +94.183.36.82 +94.183.42.239 +94.183.47.239 +94.183.51.96 +94.183.51.149 +94.183.53.16 +94.183.53.17 +94.183.53.18 +94.183.53.19 +94.183.57.172 +94.183.58.73 +94.183.59.36 +94.183.62.83 +94.183.62.232 +94.183.84.184 +94.183.84.227 +94.183.85.177 +94.183.88.99 +94.183.90.243 +94.183.100.148 +94.183.108.245 +94.183.110.118 +94.183.117.238 +94.183.118.64 +94.183.119.8 +94.183.122.41 +94.183.123.216 +94.183.124.28 +94.183.124.97 +94.183.124.129 +94.183.125.86 +94.183.125.191 +94.183.126.155 +94.183.127.185 +94.183.128.221 +94.183.130.253 +94.183.132.106 +94.183.132.251 +94.183.137.113 +94.183.141.177 +94.183.146.170 +94.183.146.198 +94.183.146.205 +94.183.150.71 +94.183.150.80 +94.183.150.85 +94.183.150.86 +94.183.150.122 +94.183.156.141 +94.183.157.7 +94.183.157.188 +94.183.160.175 +94.183.160.239 +94.183.162.8 +94.183.163.63 +94.183.163.90 +94.183.163.132 +94.183.163.180 +94.183.163.181 +94.183.172.3 +94.183.172.103 +94.183.172.253 +94.183.175.24 +94.183.175.25 +94.183.176.60 +94.183.176.65 +94.183.177.100 +94.184.9.31 +94.184.10.82 +94.184.16.6 +94.184.17.197 +94.184.22.5 +94.184.22.62 +94.184.29.217 +94.184.29.219 +94.184.29.220 +94.184.89.2 +94.184.120.67 +94.184.141.137 +94.184.141.141 +94.184.176.5 +94.184.176.6 +94.184.176.201 +94.184.224.196 +94.184.225.25 +94.184.236.20 +94.184.236.32 +94.184.236.50 +94.184.239.20 +94.199.0.166 +94.232.168.26 +94.232.168.103 +94.232.168.105 +94.232.168.151 +94.232.168.211 +94.232.170.65 +94.232.170.66 +94.232.170.67 +94.232.170.68 +94.232.171.86 +94.232.171.140 +94.232.171.141 +94.232.171.150 +94.232.171.151 +94.232.172.2 +94.232.172.96 +94.232.172.104 +94.232.172.135 +94.232.173.4 +94.232.173.28 +94.232.173.52 +94.232.173.69 +94.232.173.70 +94.232.173.71 +94.232.173.100 +94.232.173.114 +94.232.173.128 +94.232.173.137 +94.232.173.148 +94.232.173.150 +94.232.173.153 +94.232.173.154 +94.232.173.155 +94.232.173.199 +94.232.173.229 +94.232.174.1 +94.232.174.28 +94.232.174.35 +94.232.174.84 +94.232.174.176 +94.232.174.181 +94.232.174.222 +94.232.174.247 +94.232.174.249 +94.232.174.250 +94.232.175.29 +94.232.175.32 +94.232.175.85 +94.232.175.194 +95.38.13.239 +95.38.24.138 +95.38.34.53 +95.38.40.169 +95.38.47.170 +95.38.47.171 +95.38.47.172 +95.38.47.174 +95.38.48.56 +95.38.60.141 +95.38.60.173 +95.38.60.185 +95.38.60.251 +95.38.65.161 +95.38.65.237 +95.38.72.30 +95.38.77.158 +95.38.86.17 +95.38.102.86 +95.38.102.117 +95.38.102.190 +95.38.103.189 +95.38.103.220 +95.38.103.221 +95.38.103.222 +95.38.125.44 +95.38.130.112 +95.38.130.131 +95.38.134.99 +95.38.135.150 +95.38.137.129 +95.38.138.3 +95.38.153.168 +95.38.155.35 +95.38.155.58 +95.38.201.199 +95.38.209.3 +95.38.245.135 +95.38.250.86 +95.80.141.218 +95.80.155.159 +95.80.164.5 +95.80.164.6 +95.80.164.10 +95.80.168.55 +95.80.173.169 +95.80.182.185 +95.80.184.15 +95.80.184.19 +95.80.184.180 +95.80.184.184 +95.80.185.180 +95.80.185.185 +95.130.56.37 +95.130.56.40 +95.130.245.66 +95.142.224.142 +95.142.226.4 +95.142.226.26 +95.142.227.6 +95.142.227.7 +95.142.227.50 +95.142.227.178 +95.142.227.195 +95.142.227.206 +95.142.227.227 +95.142.227.230 +95.142.227.231 +95.142.231.137 +95.142.235.40 +95.142.237.3 +95.142.237.4 +95.142.237.20 +95.142.237.52 +95.142.237.82 +95.142.237.190 +95.142.237.254 +95.156.252.219 +95.156.253.8 +95.156.253.9 +95.156.253.10 +95.156.253.11 +95.156.253.12 +95.156.253.13 +95.156.253.14 +95.156.253.17 +95.156.253.18 +95.156.253.20 +95.156.253.21 +95.156.253.24 +95.156.253.27 +95.156.253.30 +95.156.253.31 +95.156.253.33 +95.156.253.36 +95.156.253.38 +95.156.253.39 +95.156.253.43 +95.156.253.44 +95.156.253.45 +95.156.253.49 +95.156.253.52 +95.156.253.53 +95.156.253.54 +95.156.253.55 +95.156.253.59 +95.156.253.61 +95.156.253.99 +95.156.253.100 +95.156.253.174 +95.156.253.177 +95.156.254.227 +95.156.254.228 +95.156.254.229 +95.156.254.234 +95.156.254.235 +95.156.254.236 +95.215.59.90 +95.215.59.121 +95.215.59.147 +95.215.59.173 +95.215.59.189 +95.215.59.195 +95.215.173.178 +95.215.173.180 +95.216.224.204 +95.216.224.214 +95.216.224.227 +95.216.224.232 +95.216.224.245 +102.22.27.125 +102.22.31.74 +102.22.72.46 +102.22.80.182 +102.22.80.183 +102.22.80.250 +102.22.81.11 +102.22.81.13 +102.22.81.19 +102.22.81.20 +102.22.81.34 +102.22.81.37 +102.22.81.41 +102.22.81.42 +102.22.81.45 +102.22.81.52 +102.22.81.74 +102.22.81.75 +102.22.81.85 +102.22.81.99 +102.22.81.110 +102.22.81.122 +102.22.81.140 +102.22.81.141 +102.22.81.151 +102.22.81.171 +102.22.81.175 +102.22.81.193 +102.22.81.195 +102.22.81.201 +102.22.81.211 +102.22.81.212 +102.22.81.213 +102.22.81.225 +102.22.81.249 +102.22.81.254 +102.22.82.4 +102.22.82.5 +102.22.82.17 +102.22.82.45 +102.22.82.53 +102.22.82.56 +102.22.82.59 +102.22.82.70 +102.22.82.71 +102.22.82.94 +102.22.82.95 +102.22.82.129 +102.22.82.214 +102.22.82.221 +102.22.82.227 +102.22.82.233 +102.22.82.242 +102.22.83.12 +102.22.83.14 +102.22.83.46 +102.22.83.71 +102.22.83.72 +102.22.83.78 +102.22.83.102 +102.22.83.103 +102.22.83.113 +102.22.83.114 +102.22.83.139 +102.22.83.142 +102.22.83.149 +102.22.83.155 +102.22.83.207 +102.22.108.2 +102.22.108.13 +102.22.108.14 +102.22.108.20 +102.22.108.21 +102.22.108.22 +102.22.108.23 +102.22.108.25 +102.22.109.1 +102.22.109.225 +102.22.124.69 +102.22.124.70 +102.22.127.147 +102.22.195.106 +102.22.195.246 +102.22.207.4 +102.22.207.42 +102.22.237.8 +102.22.237.9 +102.22.247.162 +102.22.254.116 +102.22.254.232 +102.23.5.166 +102.23.7.83 +102.23.121.15 +102.23.121.16 +102.23.122.120 +102.23.122.251 +102.23.123.90 +102.23.123.91 +102.23.132.42 +102.23.133.10 +102.23.133.12 +102.23.136.215 +102.23.140.2 +102.23.140.42 +102.23.163.244 +102.23.168.24 +102.23.196.253 +102.23.198.210 +102.23.208.4 +102.23.208.30 +102.23.226.27 +102.23.226.70 +102.23.226.71 +102.23.230.23 +102.23.231.69 +102.23.231.82 +102.23.231.84 +102.23.231.87 +102.23.231.172 +102.23.231.253 +102.23.237.76 +102.23.252.83 +102.37.16.226 +102.37.21.224 +102.37.109.39 +102.37.113.11 +102.37.113.89 +102.37.116.18 +102.37.118.18 +102.37.142.149 +102.37.144.57 +102.37.145.202 +102.37.158.250 +102.37.176.63 +102.37.213.24 +102.37.214.202 +102.37.216.211 +102.37.217.70 +102.37.217.174 +102.37.218.157 +102.37.220.188 +102.133.128.244 +102.133.129.203 +102.133.132.86 +102.133.132.90 +102.133.136.51 +102.133.136.53 +102.133.136.58 +102.133.136.65 +102.133.138.26 +102.133.138.30 +102.133.138.66 +102.133.139.5 +102.133.139.31 +102.133.139.152 +102.133.139.153 +102.133.139.154 +102.133.146.143 +102.133.160.77 +102.133.171.26 +102.133.227.168 +102.133.235.79 +102.215.242.87 +103.130.144.1 +103.130.144.2 +103.130.144.3 +103.215.222.11 +103.215.222.12 +103.215.222.33 +103.215.222.44 +103.215.222.55 +103.215.222.66 +103.215.222.102 +103.215.222.103 +103.215.222.104 +103.215.222.105 +103.215.222.106 +103.215.222.107 +103.215.222.108 +103.215.222.109 +103.215.222.112 +103.215.222.113 +103.215.222.114 +103.215.222.115 +103.215.222.116 +103.215.222.118 +103.215.222.119 +103.215.222.122 +103.215.222.124 +103.215.222.126 +103.215.222.127 +103.215.222.128 +103.215.222.129 +103.215.222.130 +103.215.222.131 +103.215.222.132 +103.215.222.133 +103.215.222.135 +103.215.222.136 +103.215.222.140 +103.215.222.141 +103.215.222.142 +103.215.222.143 +103.215.222.144 +103.215.222.145 +103.215.222.146 +103.215.222.147 +103.215.222.149 +103.215.222.150 +103.215.222.151 +103.215.222.153 +103.215.222.154 +103.215.222.157 +103.215.223.4 +103.215.223.9 +103.215.223.10 +103.215.223.11 +103.215.223.14 +103.215.223.19 +103.215.223.26 +103.215.223.40 +103.215.223.42 +103.215.223.58 +103.215.223.166 +103.215.223.214 +103.215.223.240 +103.215.223.241 +103.216.60.29 +103.216.60.37 +103.216.60.40 +103.216.60.46 +103.216.61.154 +103.216.62.68 +103.216.62.102 +109.72.192.37 +109.72.192.40 +109.72.197.38 +109.95.61.174 +109.95.61.207 +109.95.61.239 +109.95.61.249 +109.95.61.251 +109.95.67.210 +109.95.68.12 +109.95.68.14 +109.95.68.16 +109.95.68.21 +109.95.68.29 +109.95.68.32 +109.95.70.246 +109.95.71.29 +109.109.32.0 +109.109.32.1 +109.109.32.4 +109.109.32.5 +109.109.32.6 +109.109.32.7 +109.109.32.8 +109.109.32.10 +109.109.32.11 +109.109.32.13 +109.109.32.14 +109.109.32.15 +109.109.32.16 +109.109.32.17 +109.109.32.18 +109.109.32.19 +109.109.32.20 +109.109.32.21 +109.109.32.22 +109.109.32.23 +109.109.32.24 +109.109.32.25 +109.109.32.26 +109.109.32.27 +109.109.32.28 +109.109.32.29 +109.109.32.30 +109.109.32.31 +109.109.32.44 +109.109.32.45 +109.109.32.47 +109.109.32.56 +109.109.32.58 +109.109.32.59 +109.109.32.60 +109.109.32.61 +109.109.32.62 +109.109.32.63 +109.109.32.64 +109.109.32.65 +109.109.32.66 +109.109.32.67 +109.109.32.68 +109.109.32.69 +109.109.32.70 +109.109.32.71 +109.109.32.72 +109.109.32.73 +109.109.32.74 +109.109.32.75 +109.109.32.76 +109.109.32.77 +109.109.32.78 +109.109.32.79 +109.109.32.88 +109.109.32.90 +109.109.32.91 +109.109.32.92 +109.109.32.93 +109.109.32.94 +109.109.32.95 +109.109.32.96 +109.109.32.99 +109.109.32.100 +109.109.32.101 +109.109.32.102 +109.109.32.104 +109.109.32.105 +109.109.32.106 +109.109.32.107 +109.109.32.108 +109.109.32.109 +109.109.32.110 +109.109.32.111 +109.109.32.112 +109.109.32.113 +109.109.32.114 +109.109.32.115 +109.109.32.116 +109.109.32.117 +109.109.32.118 +109.109.32.119 +109.109.32.120 +109.109.32.121 +109.109.32.122 +109.109.32.123 +109.109.32.124 +109.109.32.125 +109.109.32.126 +109.109.32.152 +109.109.32.153 +109.109.32.154 +109.109.32.155 +109.109.32.156 +109.109.32.158 +109.109.32.159 +109.109.32.216 +109.109.32.218 +109.109.32.219 +109.109.32.220 +109.109.32.221 +109.109.32.222 +109.109.32.223 +109.109.34.72 +109.109.34.73 +109.109.34.74 +109.109.34.75 +109.109.34.77 +109.109.34.78 +109.109.34.79 +109.109.34.80 +109.109.34.81 +109.109.34.82 +109.109.34.83 +109.109.34.84 +109.109.34.86 +109.109.34.87 +109.109.34.88 +109.109.34.89 +109.109.34.90 +109.109.34.91 +109.109.34.92 +109.109.34.93 +109.109.34.94 +109.109.34.95 +109.109.34.96 +109.109.34.98 +109.109.34.99 +109.109.34.100 +109.109.34.101 +109.109.34.102 +109.109.34.103 +109.109.34.104 +109.109.34.105 +109.109.34.106 +109.109.34.107 +109.109.34.108 +109.109.34.109 +109.109.34.110 +109.109.34.111 +109.109.34.112 +109.109.34.113 +109.109.34.114 +109.109.34.115 +109.109.34.116 +109.109.34.117 +109.109.34.118 +109.109.34.119 +109.109.34.120 +109.109.34.121 +109.109.34.122 +109.109.34.124 +109.109.34.125 +109.109.34.127 +109.109.34.128 +109.109.34.129 +109.109.34.130 +109.109.34.131 +109.109.34.132 +109.109.34.133 +109.109.34.134 +109.109.34.135 +109.109.34.136 +109.109.34.137 +109.109.34.138 +109.109.34.140 +109.109.34.141 +109.109.34.142 +109.109.34.143 +109.109.34.144 +109.109.34.145 +109.109.34.146 +109.109.34.147 +109.109.34.148 +109.109.34.149 +109.109.34.150 +109.109.34.151 +109.109.34.152 +109.109.34.153 +109.109.34.154 +109.109.34.155 +109.109.34.156 +109.109.34.157 +109.109.34.158 +109.109.34.159 +109.109.34.160 +109.109.34.161 +109.109.34.162 +109.109.34.163 +109.109.34.164 +109.109.34.165 +109.109.34.167 +109.109.34.168 +109.109.34.169 +109.109.34.170 +109.109.34.171 +109.109.34.172 +109.109.34.173 +109.109.34.174 +109.109.34.175 +109.109.34.176 +109.109.34.177 +109.109.34.178 +109.109.34.179 +109.109.34.180 +109.109.34.181 +109.109.34.182 +109.109.34.184 +109.109.34.186 +109.109.34.187 +109.109.34.189 +109.109.34.191 +109.109.34.192 +109.109.34.193 +109.109.34.194 +109.109.34.195 +109.109.34.196 +109.109.34.197 +109.109.34.198 +109.109.34.200 +109.109.34.201 +109.109.34.202 +109.109.34.203 +109.109.34.204 +109.109.34.205 +109.109.34.206 +109.109.34.207 +109.109.34.208 +109.109.34.209 +109.109.34.210 +109.109.34.211 +109.109.34.212 +109.109.34.214 +109.109.34.215 +109.109.34.216 +109.109.34.217 +109.109.34.219 +109.109.34.220 +109.109.34.221 +109.109.34.222 +109.109.34.223 +109.109.34.224 +109.109.34.225 +109.109.34.226 +109.109.34.227 +109.109.34.228 +109.109.34.229 +109.109.34.230 +109.109.34.232 +109.109.34.233 +109.109.34.235 +109.109.34.236 +109.109.34.238 +109.109.34.239 +109.109.34.240 +109.109.34.241 +109.109.34.242 +109.109.34.243 +109.109.34.244 +109.109.34.245 +109.109.34.246 +109.109.34.247 +109.109.34.248 +109.109.34.250 +109.109.34.251 +109.109.34.252 +109.109.34.253 +109.109.34.254 +109.109.35.0 +109.109.35.1 +109.109.35.2 +109.109.35.3 +109.109.35.4 +109.109.35.5 +109.109.35.6 +109.109.35.7 +109.109.35.8 +109.109.35.9 +109.109.35.10 +109.109.35.11 +109.109.35.13 +109.109.35.14 +109.109.35.15 +109.109.35.16 +109.109.35.17 +109.109.35.18 +109.109.35.19 +109.109.35.20 +109.109.35.21 +109.109.35.22 +109.109.35.23 +109.109.35.24 +109.109.35.25 +109.109.35.26 +109.109.35.27 +109.109.35.28 +109.109.35.29 +109.109.35.30 +109.109.35.31 +109.109.35.32 +109.109.35.33 +109.109.35.34 +109.109.35.35 +109.109.35.36 +109.109.35.37 +109.109.35.38 +109.109.35.39 +109.109.35.40 +109.109.35.41 +109.109.35.42 +109.109.35.43 +109.109.35.44 +109.109.35.45 +109.109.35.46 +109.109.35.47 +109.109.35.48 +109.109.35.49 +109.109.35.50 +109.109.35.51 +109.109.35.52 +109.109.35.53 +109.109.35.54 +109.109.35.55 +109.109.35.56 +109.109.35.57 +109.109.35.59 +109.109.35.60 +109.109.35.61 +109.109.35.62 +109.109.35.63 +109.109.35.64 +109.109.35.65 +109.109.35.66 +109.109.35.67 +109.109.35.69 +109.109.35.70 +109.109.35.71 +109.109.35.72 +109.109.35.73 +109.109.35.76 +109.109.35.77 +109.109.35.78 +109.109.35.79 +109.109.35.80 +109.109.35.81 +109.109.35.82 +109.109.35.83 +109.109.35.84 +109.109.35.85 +109.109.35.86 +109.109.35.88 +109.109.35.89 +109.109.35.90 +109.109.35.91 +109.109.35.92 +109.109.35.93 +109.109.35.94 +109.109.35.95 +109.109.35.96 +109.109.35.97 +109.109.35.98 +109.109.35.99 +109.109.35.100 +109.109.35.101 +109.109.35.102 +109.109.35.103 +109.109.35.104 +109.109.35.105 +109.109.35.106 +109.109.35.108 +109.109.35.109 +109.109.35.110 +109.109.35.111 +109.109.35.112 +109.109.35.113 +109.109.35.114 +109.109.35.115 +109.109.35.116 +109.109.35.117 +109.109.35.118 +109.109.35.119 +109.109.35.120 +109.109.35.121 +109.109.35.122 +109.109.35.123 +109.109.35.124 +109.109.35.126 +109.109.35.127 +109.109.42.73 +109.109.43.128 +109.109.43.130 +109.109.43.131 +109.109.43.133 +109.109.43.134 +109.109.43.135 +109.109.43.138 +109.109.43.139 +109.109.43.140 +109.109.43.143 +109.109.43.144 +109.109.43.151 +109.109.43.155 +109.109.43.162 +109.109.43.164 +109.109.43.168 +109.109.43.172 +109.109.43.173 +109.109.43.174 +109.109.43.175 +109.109.43.177 +109.109.43.180 +109.109.43.183 +109.109.43.185 +109.109.43.188 +109.109.43.191 +109.109.43.193 +109.109.43.195 +109.109.43.196 +109.109.43.200 +109.109.43.201 +109.109.43.206 +109.109.43.209 +109.109.43.212 +109.109.43.213 +109.109.43.215 +109.109.43.220 +109.109.43.221 +109.109.43.223 +109.109.43.229 +109.109.43.230 +109.109.43.232 +109.109.43.233 +109.109.43.235 +109.109.43.236 +109.109.43.241 +109.109.43.242 +109.109.43.243 +109.109.43.247 +109.109.43.250 +109.109.43.255 +109.109.45.0 +109.109.45.1 +109.109.45.2 +109.109.45.3 +109.109.45.4 +109.109.45.5 +109.109.45.6 +109.109.45.7 +109.109.45.8 +109.109.45.9 +109.109.45.10 +109.109.45.11 +109.109.45.12 +109.109.45.13 +109.109.45.14 +109.109.45.15 +109.109.45.16 +109.109.45.17 +109.109.45.18 +109.109.45.19 +109.109.45.20 +109.109.45.22 +109.109.45.23 +109.109.45.24 +109.109.45.25 +109.109.45.26 +109.109.45.27 +109.109.45.28 +109.109.45.29 +109.109.45.30 +109.109.45.31 +109.109.45.40 +109.109.45.41 +109.109.45.42 +109.109.45.43 +109.109.45.44 +109.109.45.45 +109.109.45.46 +109.109.45.47 +109.109.45.48 +109.109.45.49 +109.109.45.50 +109.109.45.51 +109.109.45.52 +109.109.45.54 +109.109.45.55 +109.109.45.56 +109.109.45.57 +109.109.45.58 +109.109.45.59 +109.109.45.60 +109.109.45.61 +109.109.45.62 +109.109.45.63 +109.109.45.64 +109.109.45.65 +109.109.45.66 +109.109.45.67 +109.109.45.68 +109.109.45.70 +109.109.45.71 +109.109.45.72 +109.109.45.73 +109.109.45.74 +109.109.45.75 +109.109.45.76 +109.109.45.77 +109.109.45.78 +109.109.45.79 +109.109.45.80 +109.109.45.81 +109.109.45.84 +109.109.45.87 +109.109.45.88 +109.109.45.89 +109.109.45.90 +109.109.45.91 +109.109.45.92 +109.109.45.93 +109.109.45.94 +109.109.45.95 +109.109.45.96 +109.109.45.98 +109.109.45.99 +109.109.45.100 +109.109.45.101 +109.109.45.102 +109.109.45.103 +109.109.45.104 +109.109.45.105 +109.109.45.106 +109.109.45.107 +109.109.45.108 +109.109.45.109 +109.109.45.110 +109.109.45.111 +109.109.45.112 +109.109.45.113 +109.109.45.115 +109.109.45.116 +109.109.45.117 +109.109.45.119 +109.109.45.120 +109.109.45.121 +109.109.45.123 +109.109.45.124 +109.109.45.125 +109.109.45.126 +109.109.45.127 +109.109.47.0 +109.109.47.2 +109.109.47.3 +109.109.47.4 +109.109.47.5 +109.109.47.8 +109.109.47.10 +109.109.47.13 +109.109.47.16 +109.109.47.17 +109.109.47.18 +109.109.47.19 +109.109.47.23 +109.109.47.25 +109.109.47.27 +109.109.47.29 +109.109.47.32 +109.109.47.34 +109.109.47.35 +109.109.47.43 +109.109.47.50 +109.109.47.51 +109.109.47.52 +109.109.47.54 +109.109.47.57 +109.109.47.59 +109.109.47.62 +109.109.47.63 +109.109.47.64 +109.109.47.65 +109.109.47.67 +109.109.47.70 +109.109.47.72 +109.109.47.73 +109.109.47.74 +109.109.47.76 +109.109.47.78 +109.109.47.80 +109.109.47.81 +109.109.47.82 +109.109.47.83 +109.109.47.84 +109.109.47.85 +109.109.47.86 +109.109.47.87 +109.109.47.90 +109.109.47.91 +109.109.47.92 +109.109.47.94 +109.109.47.95 +109.109.47.97 +109.109.47.101 +109.109.47.102 +109.109.47.103 +109.109.47.104 +109.109.47.106 +109.109.47.107 +109.109.47.108 +109.109.47.109 +109.109.47.113 +109.109.47.115 +109.109.47.116 +109.109.47.117 +109.109.47.119 +109.109.47.120 +109.109.47.121 +109.109.47.122 +109.109.47.123 +109.109.47.124 +109.109.47.125 +109.109.47.126 +109.109.51.111 +109.109.51.178 +109.109.52.111 +109.109.54.0 +109.109.54.4 +109.109.54.7 +109.109.54.11 +109.109.54.12 +109.109.54.13 +109.109.54.14 +109.109.54.20 +109.109.54.22 +109.109.54.23 +109.109.54.34 +109.109.54.35 +109.109.54.36 +109.109.54.37 +109.109.54.40 +109.109.54.41 +109.109.54.42 +109.109.54.45 +109.109.54.49 +109.109.54.52 +109.109.54.56 +109.109.54.57 +109.109.54.59 +109.109.54.61 +109.109.54.63 +109.109.54.64 +109.109.54.65 +109.109.54.66 +109.109.54.67 +109.109.54.70 +109.109.54.71 +109.109.54.72 +109.109.54.73 +109.109.54.74 +109.109.54.75 +109.109.54.76 +109.109.54.78 +109.109.54.79 +109.109.54.80 +109.109.54.81 +109.109.54.82 +109.109.54.85 +109.109.54.88 +109.109.54.90 +109.109.54.95 +109.109.54.96 +109.109.54.99 +109.109.54.100 +109.109.54.102 +109.109.54.103 +109.109.54.105 +109.109.54.106 +109.109.54.107 +109.109.54.110 +109.109.54.111 +109.109.54.112 +109.109.54.121 +109.109.54.123 +109.109.54.124 +109.109.54.125 +109.109.54.127 +109.109.54.136 +109.109.54.137 +109.109.54.138 +109.109.54.139 +109.109.54.140 +109.109.54.143 +109.109.54.144 +109.109.54.145 +109.109.54.147 +109.109.54.149 +109.109.54.151 +109.109.54.154 +109.109.54.157 +109.109.54.158 +109.109.54.160 +109.109.54.161 +109.109.54.162 +109.109.54.163 +109.109.54.165 +109.109.54.167 +109.109.54.170 +109.109.54.172 +109.109.54.175 +109.109.54.176 +109.109.54.179 +109.109.54.180 +109.109.54.181 +109.109.54.182 +109.109.54.184 +109.109.54.185 +109.109.54.186 +109.109.54.188 +109.109.54.190 +109.109.54.191 +109.109.54.192 +109.109.54.193 +109.109.54.194 +109.109.54.195 +109.109.54.196 +109.109.54.197 +109.109.54.201 +109.109.54.202 +109.109.54.203 +109.109.54.204 +109.109.54.205 +109.109.54.206 +109.109.54.208 +109.109.54.210 +109.109.54.211 +109.109.54.213 +109.109.54.214 +109.109.54.215 +109.109.54.216 +109.109.54.217 +109.109.54.218 +109.109.54.220 +109.109.54.221 +109.109.54.222 +109.109.54.223 +109.109.54.224 +109.109.54.225 +109.109.54.226 +109.109.54.227 +109.109.54.228 +109.109.54.229 +109.109.54.230 +109.109.54.231 +109.109.54.237 +109.109.54.238 +109.109.54.240 +109.109.54.241 +109.109.54.242 +109.109.54.243 +109.109.54.244 +109.109.54.245 +109.109.54.246 +109.109.54.247 +109.109.54.248 +109.109.54.249 +109.109.54.250 +109.109.54.251 +109.109.54.252 +109.109.54.254 +109.109.54.255 +109.109.56.137 +109.109.58.0 +109.109.58.1 +109.109.58.2 +109.109.58.3 +109.109.58.4 +109.109.58.5 +109.109.58.6 +109.109.58.7 +109.109.58.9 +109.109.58.10 +109.109.58.11 +109.109.58.12 +109.109.58.13 +109.109.58.14 +109.109.58.15 +109.109.58.16 +109.109.58.17 +109.109.58.18 +109.109.58.19 +109.109.58.20 +109.109.58.21 +109.109.58.22 +109.109.58.23 +109.109.58.24 +109.109.58.25 +109.109.58.26 +109.109.58.27 +109.109.58.28 +109.109.58.29 +109.109.58.30 +109.109.58.31 +109.109.58.32 +109.109.58.33 +109.109.58.34 +109.109.58.35 +109.109.58.37 +109.109.58.38 +109.109.58.39 +109.109.58.40 +109.109.58.41 +109.109.58.42 +109.109.58.44 +109.109.58.45 +109.109.58.46 +109.109.58.47 +109.109.58.48 +109.109.58.49 +109.109.58.50 +109.109.58.51 +109.109.58.52 +109.109.58.53 +109.109.58.54 +109.109.58.55 +109.109.58.56 +109.109.58.57 +109.109.58.58 +109.109.58.59 +109.109.58.60 +109.109.58.61 +109.109.58.62 +109.109.58.63 +109.109.58.128 +109.109.58.129 +109.109.58.131 +109.109.58.132 +109.109.58.133 +109.109.58.134 +109.109.58.135 +109.109.58.136 +109.109.58.137 +109.109.58.138 +109.109.58.139 +109.109.58.140 +109.109.58.141 +109.109.58.142 +109.109.58.143 +109.109.58.160 +109.109.58.161 +109.109.58.162 +109.109.58.163 +109.109.58.164 +109.109.58.165 +109.109.58.168 +109.109.58.169 +109.109.58.170 +109.109.58.171 +109.109.58.172 +109.109.58.173 +109.109.58.174 +109.109.58.175 +109.109.59.0 +109.109.59.2 +109.109.59.3 +109.109.59.4 +109.109.59.5 +109.109.59.7 +109.109.59.32 +109.109.59.33 +109.109.59.37 +109.109.59.38 +109.109.59.39 +109.109.59.40 +109.109.59.41 +109.109.59.42 +109.109.59.43 +109.109.59.44 +109.109.59.45 +109.109.59.46 +109.109.59.47 +109.109.59.48 +109.109.59.49 +109.109.59.50 +109.109.59.51 +109.109.59.60 +109.109.59.61 +109.109.59.62 +109.109.59.63 +109.109.59.64 +109.109.59.65 +109.109.59.66 +109.109.59.67 +109.109.59.68 +109.109.59.69 +109.109.59.70 +109.109.59.71 +109.109.59.72 +109.109.59.73 +109.109.59.74 +109.109.59.75 +109.109.59.76 +109.109.59.78 +109.109.59.80 +109.109.59.81 +109.109.59.82 +109.109.59.83 +109.109.59.85 +109.109.59.87 +109.109.59.89 +109.109.59.90 +109.109.59.91 +109.109.59.92 +109.109.59.93 +109.109.59.94 +109.109.59.95 +109.109.59.96 +109.109.59.97 +109.109.59.98 +109.109.59.100 +109.109.59.101 +109.109.59.102 +109.109.59.103 +109.109.59.104 +109.109.59.105 +109.109.59.106 +109.109.59.107 +109.109.59.109 +109.109.59.110 +109.109.59.111 +109.109.59.112 +109.109.59.113 +109.109.59.114 +109.109.59.115 +109.109.59.116 +109.109.59.117 +109.109.59.118 +109.109.59.119 +109.109.59.120 +109.109.59.121 +109.109.59.122 +109.109.59.123 +109.109.59.124 +109.109.59.125 +109.109.59.126 +109.109.59.127 +109.109.60.0 +109.109.60.1 +109.109.60.2 +109.109.60.3 +109.109.60.4 +109.109.60.5 +109.109.60.6 +109.109.60.8 +109.109.60.10 +109.109.60.11 +109.109.60.12 +109.109.60.13 +109.109.60.14 +109.109.60.15 +109.109.60.17 +109.109.60.18 +109.109.60.19 +109.109.60.20 +109.109.60.21 +109.109.60.22 +109.109.60.27 +109.109.60.28 +109.109.60.29 +109.109.60.30 +109.109.60.33 +109.109.60.35 +109.109.60.37 +109.109.60.38 +109.109.60.41 +109.109.60.45 +109.109.60.47 +109.109.60.50 +109.109.60.52 +109.109.60.53 +109.109.60.55 +109.109.60.57 +109.109.60.58 +109.109.60.59 +109.109.60.60 +109.109.60.61 +109.109.60.62 +109.109.60.63 +109.109.60.64 +109.109.60.65 +109.109.60.66 +109.109.60.67 +109.109.60.68 +109.109.60.72 +109.109.60.76 +109.109.60.77 +109.109.60.79 +109.109.60.84 +109.109.60.85 +109.109.60.87 +109.109.60.88 +109.109.60.89 +109.109.60.92 +109.109.60.94 +109.109.60.96 +109.109.60.97 +109.109.60.103 +109.109.60.104 +109.109.60.106 +109.109.60.109 +109.109.60.110 +109.109.60.111 +109.109.60.112 +109.109.60.114 +109.109.60.117 +109.109.60.118 +109.109.60.120 +109.109.60.122 +109.109.60.123 +109.109.60.125 +109.109.60.126 +109.109.60.127 +109.109.60.128 +109.109.60.129 +109.109.60.130 +109.109.60.131 +109.109.60.132 +109.109.60.133 +109.109.60.134 +109.109.60.135 +109.109.60.136 +109.109.60.137 +109.109.60.138 +109.109.60.139 +109.109.60.140 +109.109.60.141 +109.109.60.142 +109.109.60.143 +109.109.60.144 +109.109.60.145 +109.109.60.146 +109.109.60.147 +109.109.60.148 +109.109.60.149 +109.109.60.152 +109.109.60.153 +109.109.60.154 +109.109.60.155 +109.109.60.156 +109.109.60.157 +109.109.60.158 +109.109.60.159 +109.109.60.160 +109.109.60.161 +109.109.60.162 +109.109.60.163 +109.109.60.164 +109.109.60.165 +109.109.60.166 +109.109.60.167 +109.109.60.168 +109.109.60.169 +109.109.60.170 +109.109.60.171 +109.109.60.172 +109.109.60.173 +109.109.60.174 +109.109.60.175 +109.109.60.177 +109.109.60.178 +109.109.60.179 +109.109.60.180 +109.109.60.181 +109.109.60.182 +109.109.60.183 +109.109.60.184 +109.109.60.185 +109.109.60.186 +109.109.60.187 +109.109.60.188 +109.109.60.189 +109.109.60.190 +109.109.60.191 +109.109.60.192 +109.109.60.193 +109.109.60.194 +109.109.60.195 +109.109.60.196 +109.109.60.197 +109.109.60.198 +109.109.60.199 +109.109.60.200 +109.109.60.201 +109.109.60.202 +109.109.60.203 +109.109.60.205 +109.109.60.207 +109.109.60.208 +109.109.60.209 +109.109.60.210 +109.109.60.211 +109.109.60.212 +109.109.60.213 +109.109.60.214 +109.109.60.215 +109.109.60.216 +109.109.60.217 +109.109.60.218 +109.109.60.219 +109.109.60.220 +109.109.60.221 +109.109.60.222 +109.109.60.223 +109.109.60.224 +109.109.60.225 +109.109.60.226 +109.109.60.227 +109.109.60.228 +109.109.60.229 +109.109.60.230 +109.109.60.231 +109.109.60.232 +109.109.60.233 +109.109.60.234 +109.109.60.235 +109.109.60.236 +109.109.60.237 +109.109.60.238 +109.109.60.239 +109.109.61.99 +109.109.61.192 +109.109.61.193 +109.109.61.194 +109.109.61.196 +109.109.61.197 +109.109.61.198 +109.109.61.199 +109.109.61.200 +109.109.61.201 +109.109.61.202 +109.109.61.203 +109.109.61.204 +109.109.61.205 +109.109.61.206 +109.109.61.207 +109.109.61.210 +109.109.61.211 +109.109.61.212 +109.109.61.213 +109.109.61.214 +109.109.61.215 +109.109.61.217 +109.109.61.219 +109.109.61.220 +109.109.61.221 +109.109.61.222 +109.109.61.223 +109.109.61.224 +109.109.61.225 +109.109.61.226 +109.109.61.227 +109.109.61.228 +109.109.61.229 +109.109.61.230 +109.109.61.231 +109.109.61.233 +109.109.61.234 +109.109.61.235 +109.109.61.236 +109.109.61.237 +109.109.61.238 +109.109.61.240 +109.109.61.241 +109.109.61.242 +109.109.61.243 +109.109.61.244 +109.109.61.245 +109.109.61.246 +109.109.61.247 +109.109.61.248 +109.109.61.249 +109.109.61.250 +109.109.61.251 +109.109.61.252 +109.109.61.253 +109.109.61.254 +109.109.61.255 +109.109.62.1 +109.109.62.3 +109.109.62.4 +109.109.62.5 +109.109.62.6 +109.109.62.40 +109.109.62.41 +109.109.62.42 +109.109.62.43 +109.109.62.44 +109.109.62.45 +109.109.62.46 +109.109.62.47 +109.109.62.48 +109.109.62.49 +109.109.62.50 +109.109.62.51 +109.109.62.52 +109.109.62.54 +109.109.62.55 +109.109.63.128 +109.109.63.129 +109.109.63.131 +109.109.63.132 +109.109.63.133 +109.109.63.134 +109.109.63.135 +109.109.63.137 +109.109.63.138 +109.109.63.139 +109.109.63.140 +109.109.63.141 +109.109.63.142 +109.109.63.143 +109.109.63.144 +109.109.63.145 +109.109.63.146 +109.109.63.147 +109.109.63.148 +109.109.63.149 +109.109.63.150 +109.109.63.151 +109.109.63.153 +109.109.63.154 +109.109.63.155 +109.109.63.156 +109.109.63.157 +109.109.63.158 +109.109.63.159 +109.109.63.160 +109.109.63.161 +109.109.63.162 +109.109.63.163 +109.109.63.164 +109.109.63.165 +109.109.63.166 +109.109.63.167 +109.109.63.168 +109.109.63.169 +109.109.63.170 +109.109.63.171 +109.109.63.172 +109.109.63.173 +109.109.63.174 +109.109.63.175 +109.109.63.184 +109.109.63.185 +109.109.63.186 +109.109.63.187 +109.109.63.188 +109.109.63.189 +109.109.63.190 +109.109.63.191 +109.109.63.192 +109.109.63.194 +109.109.63.195 +109.109.63.197 +109.109.63.198 +109.109.63.199 +109.109.63.200 +109.109.63.201 +109.109.63.202 +109.109.63.203 +109.109.63.204 +109.109.63.205 +109.109.63.206 +109.109.63.207 +109.109.63.209 +109.109.63.210 +109.109.63.211 +109.109.63.212 +109.109.63.213 +109.109.63.214 +109.109.63.215 +109.109.63.224 +109.109.63.226 +109.109.63.227 +109.109.63.228 +109.109.63.229 +109.109.63.230 +109.109.63.231 +109.109.63.232 +109.109.63.233 +109.109.63.234 +109.109.63.235 +109.109.63.236 +109.109.63.237 +109.109.63.238 +109.109.63.239 +109.109.63.240 +109.109.63.242 +109.109.63.243 +109.109.63.244 +109.109.63.245 +109.109.63.246 +109.109.63.247 +109.109.63.248 +109.109.63.249 +109.109.63.250 +109.109.63.251 +109.109.63.252 +109.109.63.254 +109.109.63.255 +109.122.235.163 +109.122.240.164 +109.122.245.12 +109.122.245.14 +109.122.245.20 +109.122.245.28 +109.122.245.32 +109.122.245.34 +109.122.245.37 +109.122.245.47 +109.122.245.48 +109.122.245.49 +109.122.245.50 +109.122.245.52 +109.122.245.55 +109.122.245.57 +109.122.245.83 +109.122.245.90 +109.122.245.94 +109.122.245.114 +109.122.245.115 +109.122.245.116 +109.122.245.117 +109.122.245.118 +109.122.245.122 +109.122.245.138 +109.122.245.163 +109.122.245.196 +109.122.246.22 +109.122.246.32 +109.122.246.41 +109.122.246.51 +109.122.246.122 +109.122.246.172 +109.122.246.210 +109.122.246.226 +109.122.246.250 +109.122.246.252 +109.122.251.4 +109.122.251.5 +109.122.251.10 +109.122.251.12 +109.122.251.13 +109.122.251.118 +109.122.253.18 +109.125.129.255 +109.125.130.0 +109.125.131.87 +109.125.135.109 +109.125.150.34 +109.125.151.67 +109.125.160.14 +109.125.160.170 +109.201.24.195 +109.201.24.197 +109.201.24.198 +109.201.24.199 +109.201.24.200 +109.201.24.201 +109.201.24.202 +109.201.24.203 +109.201.24.204 +109.201.24.205 +109.201.24.206 +109.206.252.44 +109.206.252.198 +109.206.254.13 +109.206.254.140 +109.206.255.13 +109.206.255.146 +109.206.255.148 +109.206.255.149 +109.206.255.178 +109.206.255.188 +109.230.81.238 +109.230.82.34 +109.230.83.227 +109.230.83.234 +109.230.88.171 +109.230.89.74 +109.230.89.75 +109.230.89.76 +109.230.89.77 +109.230.89.78 +109.230.89.90 +109.230.89.91 +109.230.89.94 +109.230.92.50 +109.230.94.82 +109.230.95.153 +109.230.95.164 +109.230.200.117 +109.230.200.118 +109.230.200.119 +109.230.200.120 +109.230.200.188 +109.230.204.17 +109.230.204.95 +109.230.204.180 +109.230.205.16 +109.230.205.20 +109.230.205.21 +109.230.205.139 +109.230.205.154 +109.230.205.164 +109.230.206.171 +109.230.206.174 +109.230.206.175 +109.230.206.180 +109.230.221.102 +109.230.221.103 +109.230.221.106 +109.230.223.74 +109.230.223.75 +109.230.223.170 +109.230.223.182 +109.230.242.117 +109.232.1.6 +109.232.1.59 +109.232.1.60 +109.232.1.62 +109.232.1.138 +109.232.1.217 +109.232.1.219 +109.232.2.123 +109.238.191.251 +128.65.181.137 +128.65.183.223 +128.65.184.126 +128.65.187.239 +128.65.188.31 +130.185.72.53 +130.185.72.61 +130.185.72.64 +130.185.72.84 +130.185.72.85 +130.185.72.94 +130.185.72.98 +130.185.72.105 +130.185.72.107 +130.185.72.108 +130.185.72.115 +130.185.72.119 +130.185.72.120 +130.185.72.125 +130.185.72.131 +130.185.72.138 +130.185.72.142 +130.185.72.149 +130.185.72.170 +130.185.72.172 +130.185.72.181 +130.185.72.213 +130.185.72.216 +130.185.72.223 +130.185.72.248 +130.185.72.249 +130.185.73.3 +130.185.73.4 +130.185.73.5 +130.185.73.6 +130.185.73.8 +130.185.73.12 +130.185.73.13 +130.185.73.18 +130.185.73.19 +130.185.73.22 +130.185.73.30 +130.185.73.32 +130.185.73.34 +130.185.73.58 +130.185.73.64 +130.185.73.65 +130.185.73.70 +130.185.73.88 +130.185.73.90 +130.185.73.96 +130.185.73.100 +130.185.73.102 +130.185.73.104 +130.185.73.105 +130.185.73.106 +130.185.73.108 +130.185.73.111 +130.185.73.126 +130.185.73.131 +130.185.73.134 +130.185.73.137 +130.185.73.140 +130.185.73.141 +130.185.73.144 +130.185.73.146 +130.185.73.149 +130.185.73.153 +130.185.73.156 +130.185.73.160 +130.185.73.165 +130.185.73.176 +130.185.73.189 +130.185.73.190 +130.185.73.194 +130.185.73.197 +130.185.73.200 +130.185.73.209 +130.185.73.214 +130.185.73.217 +130.185.73.220 +130.185.73.230 +130.185.73.239 +130.185.73.242 +130.185.73.245 +130.185.73.246 +130.185.74.11 +130.185.74.18 +130.185.74.26 +130.185.74.28 +130.185.74.33 +130.185.74.43 +130.185.74.47 +130.185.74.51 +130.185.74.65 +130.185.74.76 +130.185.74.79 +130.185.74.84 +130.185.74.85 +130.185.74.86 +130.185.74.90 +130.185.74.91 +130.185.74.94 +130.185.74.96 +130.185.74.97 +130.185.74.105 +130.185.74.106 +130.185.74.116 +130.185.74.120 +130.185.74.122 +130.185.74.123 +130.185.74.125 +130.185.74.126 +130.185.74.133 +130.185.74.134 +130.185.74.136 +130.185.74.140 +130.185.74.145 +130.185.74.147 +130.185.74.150 +130.185.74.152 +130.185.74.155 +130.185.74.160 +130.185.74.161 +130.185.74.163 +130.185.74.168 +130.185.74.170 +130.185.74.183 +130.185.74.185 +130.185.74.193 +130.185.74.197 +130.185.74.198 +130.185.74.204 +130.185.74.208 +130.185.74.218 +130.185.74.227 +130.185.74.234 +130.185.74.239 +130.185.74.245 +130.185.74.246 +130.185.74.249 +130.185.74.250 +130.185.75.5 +130.185.75.7 +130.185.75.8 +130.185.75.11 +130.185.75.15 +130.185.75.27 +130.185.75.29 +130.185.75.33 +130.185.75.40 +130.185.75.43 +130.185.75.46 +130.185.75.54 +130.185.75.57 +130.185.75.75 +130.185.75.76 +130.185.75.78 +130.185.75.80 +130.185.75.85 +130.185.75.92 +130.185.75.93 +130.185.75.100 +130.185.75.116 +130.185.75.121 +130.185.75.123 +130.185.75.135 +130.185.75.138 +130.185.75.144 +130.185.75.149 +130.185.75.152 +130.185.75.159 +130.185.75.163 +130.185.75.165 +130.185.75.168 +130.185.75.170 +130.185.75.179 +130.185.75.180 +130.185.75.182 +130.185.75.184 +130.185.75.185 +130.185.75.186 +130.185.75.189 +130.185.75.190 +130.185.75.197 +130.185.75.206 +130.185.75.207 +130.185.75.216 +130.185.75.217 +130.185.75.219 +130.185.75.224 +130.185.75.229 +130.185.75.232 +130.185.75.233 +130.185.75.234 +130.185.75.241 +130.185.75.243 +130.185.75.244 +130.185.75.252 +130.185.76.7 +130.185.76.9 +130.185.76.12 +130.185.76.20 +130.185.76.21 +130.185.76.26 +130.185.76.28 +130.185.76.31 +130.185.76.48 +130.185.76.52 +130.185.76.53 +130.185.76.64 +130.185.76.66 +130.185.76.69 +130.185.76.70 +130.185.76.74 +130.185.76.78 +130.185.76.84 +130.185.76.86 +130.185.76.91 +130.185.76.94 +130.185.76.103 +130.185.76.104 +130.185.76.106 +130.185.76.114 +130.185.76.123 +130.185.76.127 +130.185.76.132 +130.185.76.140 +130.185.76.149 +130.185.76.150 +130.185.76.152 +130.185.76.156 +130.185.76.162 +130.185.76.165 +130.185.76.170 +130.185.76.172 +130.185.76.176 +130.185.76.185 +130.185.76.188 +130.185.76.191 +130.185.76.197 +130.185.76.200 +130.185.76.205 +130.185.76.225 +130.185.76.231 +130.185.76.238 +130.185.76.240 +130.185.76.243 +130.185.76.248 +130.185.76.251 +130.185.77.10 +130.185.77.12 +130.185.77.16 +130.185.77.17 +130.185.77.22 +130.185.77.29 +130.185.77.34 +130.185.77.40 +130.185.77.43 +130.185.77.44 +130.185.77.46 +130.185.77.48 +130.185.77.54 +130.185.77.61 +130.185.77.62 +130.185.77.63 +130.185.77.64 +130.185.77.67 +130.185.77.69 +130.185.77.71 +130.185.77.72 +130.185.77.77 +130.185.77.78 +130.185.77.79 +130.185.77.86 +130.185.77.88 +130.185.77.89 +130.185.77.91 +130.185.77.97 +130.185.77.101 +130.185.77.114 +130.185.77.118 +130.185.77.124 +130.185.77.129 +130.185.77.131 +130.185.77.132 +130.185.77.133 +130.185.77.145 +130.185.77.146 +130.185.77.158 +130.185.77.160 +130.185.77.162 +130.185.77.165 +130.185.77.173 +130.185.77.181 +130.185.77.195 +130.185.77.199 +130.185.77.206 +130.185.77.214 +130.185.77.218 +130.185.77.221 +130.185.77.222 +130.185.77.225 +130.185.77.227 +130.185.77.230 +130.185.77.237 +130.185.77.242 +130.185.77.246 +130.185.77.247 +130.185.77.249 +130.185.78.5 +130.185.78.18 +130.185.78.40 +130.185.78.44 +130.185.78.51 +130.185.78.64 +130.185.78.67 +130.185.78.72 +130.185.78.74 +130.185.78.76 +130.185.78.81 +130.185.78.84 +130.185.78.85 +130.185.78.87 +130.185.78.95 +130.185.78.100 +130.185.78.101 +130.185.78.113 +130.185.78.117 +130.185.78.124 +130.185.78.127 +130.185.78.140 +130.185.78.148 +130.185.78.151 +130.185.78.152 +130.185.78.155 +130.185.78.167 +130.185.78.170 +130.185.78.171 +130.185.78.177 +130.185.78.179 +130.185.78.182 +130.185.78.184 +130.185.78.185 +130.185.78.186 +130.185.78.189 +130.185.78.201 +130.185.78.212 +130.185.78.217 +130.185.78.218 +130.185.78.220 +130.185.78.223 +130.185.78.225 +130.185.78.228 +130.185.78.229 +130.185.78.230 +130.185.78.233 +130.185.78.234 +130.185.78.247 +130.185.78.248 +130.185.78.249 +130.185.79.138 +130.185.79.170 +130.185.79.180 +130.185.79.181 +130.185.79.184 +130.185.79.187 +130.185.79.197 +130.193.77.5 +130.193.77.35 +130.193.77.61 +130.193.77.62 +130.193.77.82 +130.193.77.91 +130.193.77.98 +130.193.77.99 +130.193.77.100 +130.193.77.101 +130.193.77.102 +130.193.77.103 +130.193.77.104 +130.193.77.105 +130.193.77.106 +130.193.77.107 +130.193.77.108 +130.193.77.109 +130.193.77.110 +130.193.77.111 +130.193.77.112 +130.193.77.113 +130.193.77.114 +130.193.77.115 +130.193.77.116 +130.193.77.117 +130.193.77.118 +130.193.77.119 +130.193.77.120 +130.193.77.121 +130.193.77.122 +130.193.77.123 +130.193.77.126 +130.193.77.198 +130.193.77.199 +130.193.77.201 +130.193.77.202 +130.193.77.204 +130.193.77.205 +130.193.77.207 +130.193.77.211 +130.193.77.212 +130.193.77.214 +130.193.77.215 +130.193.77.216 +130.193.77.217 +130.193.77.222 +130.193.77.236 +130.193.77.247 +130.193.77.248 +130.193.77.249 +134.255.200.50 +134.255.200.52 +134.255.200.54 +134.255.200.130 +134.255.200.133 +134.255.200.147 +134.255.200.151 +134.255.200.154 +134.255.200.218 +134.255.200.250 +134.255.201.29 +134.255.201.91 +134.255.201.92 +134.255.201.94 +134.255.201.162 +134.255.202.7 +134.255.202.82 +134.255.202.164 +134.255.202.188 +134.255.203.50 +134.255.203.67 +134.255.203.68 +134.255.203.142 +134.255.203.171 +134.255.203.172 +134.255.203.174 +134.255.204.3 +138.128.224.53 +138.128.225.53 +146.19.212.5 +146.19.212.10 +146.19.212.12 +146.19.212.14 +146.19.212.22 +146.19.212.34 +146.19.212.51 +146.19.212.69 +146.19.212.70 +146.19.212.71 +146.19.212.74 +146.19.212.79 +146.19.212.80 +146.19.212.87 +146.19.212.88 +146.19.212.94 +146.19.212.99 +146.19.212.103 +146.19.212.104 +146.19.212.107 +146.19.212.108 +146.19.212.120 +146.19.212.121 +146.19.212.122 +146.19.212.136 +146.19.212.144 +146.19.212.145 +146.19.212.154 +146.19.212.156 +146.19.212.161 +146.19.212.163 +146.19.212.172 +146.19.212.173 +146.19.212.178 +146.19.212.183 +146.19.212.185 +146.19.212.186 +146.19.212.187 +146.19.212.192 +146.19.212.206 +146.19.212.212 +146.19.212.215 +146.19.212.217 +146.19.212.220 +146.19.212.224 +146.19.212.225 +146.19.212.227 +146.19.212.245 +146.19.212.249 +146.66.134.55 +146.66.135.55 +151.232.0.133 +151.232.8.90 +151.232.10.25 +151.232.20.66 +151.232.36.4 +151.232.36.5 +151.232.36.131 +151.232.36.132 +151.232.39.202 +151.232.103.94 +151.232.103.130 +151.232.133.244 +151.233.50.143 +151.233.50.216 +151.233.52.156 +151.233.53.44 +151.233.54.25 +151.233.54.200 +151.233.55.181 +151.233.56.110 +151.233.59.182 +151.233.74.80 +151.234.29.148 +151.234.121.165 +151.234.123.36 +151.234.127.139 +151.234.161.32 +151.234.161.33 +151.234.161.34 +151.235.91.187 +151.235.98.108 +151.235.99.85 +151.235.102.146 +151.235.113.194 +151.235.113.225 +151.235.117.185 +151.235.122.252 +151.235.127.103 +151.235.136.138 +151.235.181.165 +151.235.218.81 +151.235.237.253 +157.119.188.90 +157.119.188.122 +157.119.188.140 +157.119.190.146 +157.119.190.148 +157.119.190.150 +157.119.190.151 +157.119.190.153 +157.119.190.154 +157.119.190.155 +157.119.190.156 +157.119.190.157 +157.119.190.229 +157.119.191.50 +157.119.191.132 +157.119.191.134 +157.119.191.181 +158.58.184.68 +158.58.184.73 +158.58.184.74 +158.58.184.75 +158.58.184.76 +158.58.184.118 +158.58.184.138 +158.58.184.147 +158.58.184.148 +158.58.184.166 +158.58.184.170 +158.58.184.180 +158.58.184.243 +158.58.184.250 +158.58.184.251 +158.58.184.252 +158.58.184.253 +158.58.184.254 +158.58.185.5 +158.58.185.6 +158.58.185.7 +158.58.185.8 +158.58.185.12 +158.58.185.13 +158.58.185.38 +158.58.185.39 +158.58.185.56 +158.58.185.58 +158.58.185.59 +158.58.185.60 +158.58.185.61 +158.58.185.62 +158.58.185.72 +158.58.185.78 +158.58.185.79 +158.58.185.83 +158.58.185.85 +158.58.185.88 +158.58.185.89 +158.58.185.94 +158.58.185.95 +158.58.185.100 +158.58.185.103 +158.58.185.108 +158.58.185.111 +158.58.185.114 +158.58.185.115 +158.58.185.117 +158.58.185.120 +158.58.185.121 +158.58.185.122 +158.58.185.164 +158.58.185.174 +158.58.185.209 +158.58.185.210 +158.58.185.244 +158.58.185.246 +158.58.185.248 +158.58.185.250 +158.58.185.252 +158.58.186.34 +158.58.187.4 +158.58.187.6 +158.58.187.12 +158.58.187.13 +158.58.187.14 +158.58.187.20 +158.58.187.22 +158.58.187.38 +158.58.187.44 +158.58.187.50 +158.58.187.68 +158.58.187.70 +158.58.187.75 +158.58.187.76 +158.58.187.78 +158.58.187.91 +158.58.187.108 +158.58.187.109 +158.58.187.116 +158.58.187.118 +158.58.187.124 +158.58.187.131 +158.58.187.132 +158.58.187.134 +158.58.187.139 +158.58.187.141 +158.58.187.142 +158.58.187.148 +158.58.187.149 +158.58.187.155 +158.58.187.157 +158.58.187.163 +158.58.187.164 +158.58.187.165 +158.58.187.166 +158.58.187.171 +158.58.187.172 +158.58.187.173 +158.58.187.179 +158.58.187.180 +158.58.187.187 +158.58.187.194 +158.58.187.203 +158.58.187.204 +158.58.187.205 +158.58.187.211 +158.58.187.214 +158.58.187.220 +158.58.187.221 +158.58.187.226 +158.58.187.234 +158.58.187.243 +158.58.187.244 +158.58.187.246 +158.58.188.147 +158.58.188.148 +158.58.188.152 +158.58.188.155 +158.58.190.43 +158.58.191.20 +158.58.191.21 +158.58.191.36 +158.58.191.37 +158.58.191.39 +158.58.191.40 +158.58.191.41 +158.58.191.42 +158.58.191.43 +158.58.191.44 +158.58.191.45 +158.58.191.107 +158.58.191.108 +158.58.191.109 +158.58.191.130 +158.58.191.186 +158.58.191.190 +158.255.74.8 +158.255.74.27 +158.255.74.33 +158.255.74.38 +158.255.74.49 +158.255.74.52 +158.255.74.54 +158.255.74.60 +158.255.74.69 +158.255.74.71 +158.255.74.75 +158.255.74.79 +158.255.74.89 +158.255.74.100 +158.255.74.117 +158.255.74.124 +158.255.74.130 +158.255.74.133 +158.255.74.144 +158.255.74.150 +158.255.74.159 +158.255.74.164 +158.255.74.166 +158.255.74.167 +158.255.74.172 +158.255.74.174 +158.255.74.177 +158.255.74.184 +158.255.74.185 +158.255.74.190 +158.255.74.194 +158.255.74.196 +158.255.74.198 +158.255.74.200 +158.255.74.208 +158.255.74.212 +158.255.74.216 +158.255.74.218 +158.255.74.221 +158.255.74.223 +158.255.74.229 +158.255.74.246 +158.255.74.251 +158.255.78.168 +159.20.96.122 +159.20.102.136 +159.20.102.137 +159.20.102.138 +159.20.102.139 +159.20.102.140 +159.20.102.141 +159.20.102.142 +159.20.102.143 +164.138.16.1 +164.138.16.2 +164.138.16.4 +164.138.16.6 +164.138.16.7 +164.138.16.9 +164.138.16.40 +164.138.16.41 +164.138.16.42 +164.138.16.43 +164.138.16.44 +164.138.16.45 +164.138.16.47 +164.138.16.48 +164.138.16.49 +164.138.16.50 +164.138.16.51 +164.138.16.52 +164.138.16.53 +164.138.16.54 +164.138.16.55 +164.138.16.56 +164.138.16.57 +164.138.16.58 +164.138.16.59 +164.138.16.89 +164.138.16.100 +164.138.16.110 +164.138.16.111 +164.138.16.112 +164.138.16.130 +164.138.16.131 +164.138.16.132 +164.138.16.133 +164.138.16.134 +164.138.16.135 +164.138.16.136 +164.138.16.137 +164.138.16.138 +164.138.16.139 +164.138.16.140 +164.138.16.141 +164.138.16.142 +164.138.16.143 +164.138.16.144 +164.138.16.145 +164.138.16.150 +164.138.16.151 +164.138.16.164 +164.138.16.180 +164.138.16.183 +164.138.16.187 +164.138.17.1 +164.138.17.9 +164.138.17.10 +164.138.17.11 +164.138.17.12 +164.138.17.13 +164.138.17.66 +164.138.17.99 +164.138.17.100 +164.138.17.114 +164.138.17.122 +164.138.17.137 +164.138.17.161 +164.138.17.162 +164.138.17.163 +164.138.17.164 +164.138.17.165 +164.138.17.170 +164.138.17.201 +164.138.17.233 +164.138.17.235 +164.138.17.236 +164.138.17.250 +164.138.17.251 +164.138.17.252 +164.138.17.253 +164.138.18.65 +164.138.18.66 +164.138.18.67 +164.138.18.68 +164.138.18.69 +164.138.18.71 +164.138.18.72 +164.138.18.74 +164.138.18.75 +164.138.18.76 +164.138.18.105 +164.138.18.106 +164.138.18.153 +164.138.18.154 +164.138.18.155 +164.138.18.156 +164.138.18.157 +164.138.18.169 +164.138.18.170 +164.138.18.171 +164.138.18.172 +164.138.18.173 +164.138.19.1 +164.138.19.2 +164.138.19.4 +164.138.19.5 +164.138.19.6 +164.138.19.7 +164.138.19.8 +164.138.19.11 +164.138.19.13 +164.138.19.17 +164.138.19.18 +164.138.19.19 +164.138.19.34 +164.138.19.35 +164.138.19.49 +164.138.19.50 +164.138.19.97 +164.138.19.98 +164.138.19.99 +164.138.19.100 +164.138.19.101 +164.138.19.139 +164.138.19.185 +164.138.19.193 +164.138.19.194 +164.138.19.195 +164.138.19.196 +164.138.19.197 +164.138.19.198 +164.138.19.199 +164.138.19.200 +164.138.19.201 +164.138.19.202 +164.138.19.204 +164.138.19.225 +164.138.19.226 +164.138.19.227 +164.138.19.234 +164.138.19.250 +164.138.19.251 +164.138.19.252 +164.138.19.253 +164.138.20.81 +164.138.20.82 +164.138.20.83 +164.138.20.84 +164.138.20.85 +164.138.20.89 +164.138.20.90 +164.138.20.91 +164.138.20.92 +164.138.20.93 +164.138.20.98 +164.138.20.99 +164.138.20.100 +164.138.20.101 +164.138.20.121 +164.138.20.122 +164.138.20.123 +164.138.20.124 +164.138.20.125 +164.138.20.129 +164.138.20.130 +164.138.20.131 +164.138.20.132 +164.138.20.155 +164.138.20.156 +164.138.20.217 +164.138.20.218 +164.138.20.221 +164.138.21.1 +164.138.21.2 +164.138.21.4 +164.138.21.17 +164.138.21.18 +164.138.21.49 +164.138.21.57 +164.138.21.58 +164.138.21.59 +164.138.21.60 +164.138.21.73 +164.138.21.81 +164.138.21.82 +164.138.21.83 +164.138.21.84 +164.138.21.85 +164.138.21.105 +164.138.21.106 +164.138.21.107 +164.138.21.108 +164.138.21.109 +164.138.21.121 +164.138.21.122 +164.138.21.123 +164.138.21.124 +164.138.21.169 +164.138.21.170 +164.138.21.171 +164.138.21.172 +164.138.21.173 +164.138.21.201 +164.138.21.202 +164.138.21.204 +164.138.21.209 +164.138.21.210 +164.138.21.211 +164.138.22.9 +164.138.22.10 +164.138.22.11 +164.138.22.13 +164.138.22.65 +164.138.22.66 +164.138.22.114 +164.138.22.117 +164.138.22.118 +164.138.22.119 +164.138.22.120 +164.138.22.121 +164.138.22.122 +164.138.22.123 +164.138.22.124 +164.138.22.129 +164.138.22.130 +164.138.22.131 +164.138.22.132 +164.138.22.133 +164.138.22.134 +164.138.22.137 +164.138.22.138 +164.138.22.139 +164.138.22.140 +164.138.22.141 +164.138.22.169 +164.138.22.177 +164.138.22.179 +164.138.22.180 +164.138.22.181 +164.138.22.186 +164.138.22.187 +164.138.23.50 +164.138.23.51 +164.138.23.52 +164.138.23.173 +164.138.23.233 +164.138.23.237 +164.138.206.100 +164.215.57.55 +164.215.57.56 +164.215.59.37 +164.215.59.117 +168.119.88.234 +171.22.24.5 +171.22.24.13 +171.22.24.15 +171.22.24.16 +171.22.24.24 +171.22.24.34 +171.22.24.38 +171.22.24.42 +171.22.24.50 +171.22.24.54 +171.22.24.80 +171.22.24.88 +171.22.24.91 +171.22.24.103 +171.22.24.105 +171.22.24.112 +171.22.24.115 +171.22.24.130 +171.22.24.134 +171.22.24.137 +171.22.24.146 +171.22.24.150 +171.22.24.155 +171.22.24.157 +171.22.24.159 +171.22.24.161 +171.22.24.164 +171.22.24.165 +171.22.24.169 +171.22.24.176 +171.22.24.182 +171.22.24.187 +171.22.24.188 +171.22.24.211 +171.22.24.221 +171.22.24.231 +171.22.24.236 +171.22.24.246 +171.22.25.10 +171.22.25.18 +171.22.25.19 +171.22.25.20 +171.22.25.24 +171.22.25.25 +171.22.25.26 +171.22.25.31 +171.22.25.32 +171.22.25.41 +171.22.25.44 +171.22.25.49 +171.22.25.50 +171.22.25.54 +171.22.25.63 +171.22.25.64 +171.22.25.65 +171.22.25.69 +171.22.25.71 +171.22.25.73 +171.22.25.75 +171.22.25.79 +171.22.25.80 +171.22.25.83 +171.22.25.84 +171.22.25.86 +171.22.25.90 +171.22.25.91 +171.22.25.96 +171.22.25.99 +171.22.25.100 +171.22.25.102 +171.22.25.104 +171.22.25.105 +171.22.25.107 +171.22.25.110 +171.22.25.113 +171.22.25.128 +171.22.25.133 +171.22.25.140 +171.22.25.148 +171.22.25.149 +171.22.25.150 +171.22.25.155 +171.22.25.156 +171.22.25.161 +171.22.25.174 +171.22.25.185 +171.22.25.190 +171.22.25.191 +171.22.25.198 +171.22.25.222 +171.22.25.234 +171.22.25.253 +171.22.26.13 +171.22.26.31 +171.22.26.35 +171.22.26.41 +171.22.26.44 +171.22.26.46 +171.22.26.50 +171.22.26.69 +171.22.26.73 +171.22.26.75 +171.22.26.76 +171.22.26.82 +171.22.26.88 +171.22.26.90 +171.22.26.92 +171.22.26.95 +171.22.26.99 +171.22.26.104 +171.22.26.106 +171.22.26.108 +171.22.26.116 +171.22.26.120 +171.22.26.121 +171.22.26.127 +171.22.26.129 +171.22.26.131 +171.22.26.135 +171.22.26.140 +171.22.26.143 +171.22.26.145 +171.22.26.146 +171.22.26.151 +171.22.26.157 +171.22.26.165 +171.22.26.172 +171.22.26.176 +171.22.26.180 +171.22.26.193 +171.22.26.194 +171.22.26.196 +171.22.26.199 +171.22.26.202 +171.22.26.204 +171.22.26.205 +171.22.26.206 +171.22.26.207 +171.22.26.210 +171.22.26.212 +171.22.26.225 +171.22.26.233 +171.22.26.234 +171.22.26.238 +171.22.26.250 +171.22.27.13 +171.22.27.16 +171.22.27.29 +171.22.27.45 +171.22.27.49 +171.22.27.57 +171.22.27.75 +171.22.27.76 +171.22.27.83 +171.22.27.93 +171.22.27.98 +171.22.27.106 +171.22.27.108 +171.22.27.109 +171.22.27.110 +171.22.27.115 +171.22.27.119 +171.22.27.122 +171.22.27.123 +171.22.27.125 +171.22.27.130 +171.22.27.132 +171.22.27.133 +171.22.27.137 +171.22.27.143 +171.22.27.145 +171.22.27.146 +171.22.27.151 +171.22.27.152 +171.22.27.155 +171.22.27.158 +171.22.27.159 +171.22.27.160 +171.22.27.162 +171.22.27.164 +171.22.27.170 +171.22.27.172 +171.22.27.173 +171.22.27.175 +171.22.27.184 +171.22.27.189 +171.22.27.194 +171.22.27.197 +171.22.27.200 +171.22.27.201 +171.22.27.207 +171.22.27.225 +171.22.27.240 +171.22.27.244 +171.22.27.247 +171.22.27.251 +176.10.95.10 +176.56.156.4 +176.56.157.4 +176.62.144.36 +176.62.144.43 +176.62.144.45 +176.62.144.46 +176.65.191.39 +176.65.224.205 +176.65.225.161 +176.65.240.45 +176.65.240.46 +176.65.240.50 +176.65.240.51 +176.65.240.87 +176.65.240.89 +176.65.240.162 +176.65.240.163 +176.65.241.162 +176.65.241.164 +176.65.241.165 +176.65.242.131 +176.65.242.204 +176.65.242.205 +176.65.243.44 +176.65.243.51 +176.65.243.52 +176.65.243.53 +176.65.243.57 +176.65.243.61 +176.65.243.196 +176.65.243.230 +176.65.252.158 +176.97.218.7 +176.97.218.9 +176.97.218.30 +176.97.218.31 +176.97.218.35 +176.97.218.36 +176.97.218.37 +176.97.218.42 +176.97.218.46 +176.97.218.49 +176.97.218.50 +176.97.218.65 +176.97.218.66 +176.97.218.70 +176.97.218.73 +176.97.218.74 +176.97.218.75 +176.97.218.86 +176.97.218.98 +176.97.218.99 +176.97.218.105 +176.97.218.142 +176.97.218.144 +176.97.218.150 +176.97.218.152 +176.97.218.156 +176.97.218.159 +176.97.218.161 +176.97.218.166 +176.97.218.189 +176.97.218.215 +176.97.218.217 +176.97.218.226 +176.97.218.237 +176.97.218.246 +176.97.218.248 +176.97.218.252 +176.97.220.34 +176.97.220.69 +176.97.220.70 +176.97.220.162 +176.97.220.235 +176.97.220.236 +176.97.220.237 +176.97.220.238 +176.101.33.161 +176.101.48.167 +176.101.48.192 +176.101.48.195 +176.102.224.6 +176.102.224.7 +176.120.16.4 +176.120.16.21 +176.120.16.25 +176.120.16.55 +176.120.16.56 +176.120.16.60 +176.120.16.85 +176.120.16.88 +176.120.16.96 +176.120.16.104 +176.120.16.122 +176.120.16.136 +176.120.16.163 +176.120.16.181 +176.120.16.186 +176.120.16.205 +176.120.16.212 +176.120.16.217 +176.120.16.224 +176.120.16.228 +176.120.16.229 +176.120.16.232 +176.120.16.252 +176.120.16.254 +176.122.210.2 +176.122.210.6 +176.122.210.10 +176.122.210.78 +176.122.210.98 +176.126.120.3 +176.126.120.5 +176.126.120.8 +176.126.120.19 +176.126.120.33 +176.126.120.101 +176.126.120.108 +176.126.120.199 +176.126.120.207 +176.126.120.208 +176.126.120.209 +176.126.120.217 +176.126.120.218 +176.126.120.234 +176.126.120.235 +176.126.120.236 +176.126.120.237 +176.126.120.238 +176.126.120.243 +178.21.40.17 +178.21.40.20 +178.21.40.21 +178.21.41.20 +178.21.45.10 +178.21.45.11 +178.21.46.10 +178.21.46.11 +178.21.161.12 +178.21.161.13 +178.21.161.53 +178.21.161.137 +178.21.162.38 +178.21.162.44 +178.21.162.47 +178.21.162.49 +178.21.162.56 +178.21.163.51 +178.21.163.60 +178.21.163.81 +178.21.163.97 +178.21.164.113 +178.21.164.247 +178.22.120.85 +178.22.120.100 +178.22.120.103 +178.22.120.110 +178.22.120.113 +178.22.120.140 +178.22.120.238 +178.22.121.70 +178.22.121.72 +178.22.121.139 +178.22.121.148 +178.22.121.149 +178.22.121.212 +178.22.121.214 +178.22.122.72 +178.22.122.75 +178.22.122.101 +178.22.122.103 +178.22.122.122 +178.22.122.232 +178.22.123.10 +178.22.123.42 +178.22.123.43 +178.22.123.44 +178.22.123.45 +178.22.123.114 +178.22.123.198 +178.22.124.7 +178.22.124.17 +178.22.124.20 +178.22.124.21 +178.22.124.25 +178.22.124.29 +178.22.124.230 +178.22.124.231 +178.22.124.237 +178.22.126.2 +178.22.126.18 +178.22.126.26 +178.22.126.30 +178.22.126.42 +178.22.127.57 +178.22.127.58 +178.22.127.179 +178.22.127.180 +178.131.8.229 +178.131.78.211 +178.131.88.93 +178.131.89.118 +178.131.109.152 +178.131.110.148 +178.131.119.93 +178.131.120.31 +178.131.120.32 +178.131.120.33 +178.131.120.34 +178.131.123.31 +178.131.123.32 +178.131.124.31 +178.131.124.32 +178.131.125.31 +178.131.125.32 +178.131.127.31 +178.173.128.7 +178.173.128.16 +178.173.128.28 +178.173.128.29 +178.173.140.108 +178.173.141.28 +178.173.141.47 +178.173.143.43 +178.173.144.93 +178.173.145.26 +178.173.145.137 +178.173.149.25 +178.173.151.16 +178.173.151.23 +178.173.161.190 +178.173.170.83 +178.173.171.82 +178.173.190.9 +178.173.190.58 +178.173.212.160 +178.173.214.4 +178.173.219.7 +178.215.8.18 +178.215.8.54 +178.216.248.4 +178.216.248.30 +178.216.248.100 +178.216.248.106 +178.216.248.108 +178.216.248.110 +178.216.248.251 +178.216.249.55 +178.216.249.194 +178.216.249.202 +178.216.249.206 +178.216.250.162 +178.216.250.163 +178.216.250.167 +178.216.250.173 +178.216.250.181 +178.216.250.182 +178.216.250.183 +178.216.250.184 +178.216.250.186 +178.216.250.188 +178.216.250.189 +178.216.251.14 +178.216.251.34 +178.216.251.106 +178.216.251.219 +178.216.251.220 +178.216.251.226 +178.216.251.229 +178.216.251.231 +178.216.251.232 +178.216.251.233 +178.216.251.234 +178.216.251.235 +178.216.251.240 +178.216.251.241 +178.216.251.242 +178.216.251.243 +178.216.251.245 +178.216.251.246 +178.216.251.248 +178.216.251.249 +178.216.251.252 +178.216.251.253 +178.216.251.254 +178.219.238.55 +178.236.105.45 +178.236.108.24 +178.236.108.72 +178.238.193.14 +178.238.195.158 +178.238.197.244 +178.239.145.30 +178.239.147.62 +178.239.147.71 +178.239.147.86 +178.239.147.91 +178.239.147.98 +178.239.147.99 +178.239.147.100 +178.239.147.101 +178.239.147.102 +178.239.147.106 +178.239.147.107 +178.239.147.108 +178.239.147.109 +178.239.147.114 +178.239.147.115 +178.239.147.116 +178.239.147.117 +178.239.147.118 +178.239.147.119 +178.239.147.120 +178.239.147.121 +178.239.147.122 +178.239.147.123 +178.239.147.137 +178.239.147.139 +178.239.147.146 +178.239.148.110 +178.239.148.129 +178.239.148.133 +178.239.151.13 +178.239.151.16 +178.239.151.20 +178.239.151.26 +178.239.151.28 +178.239.151.42 +178.239.151.60 +178.239.151.64 +178.239.151.84 +178.239.151.86 +178.239.151.87 +178.239.151.89 +178.239.151.97 +178.239.151.100 +178.239.151.112 +178.239.151.123 +178.239.151.130 +178.239.151.136 +178.239.151.137 +178.239.151.139 +178.239.151.160 +178.239.151.179 +178.239.151.185 +178.239.151.186 +178.239.151.193 +178.239.151.196 +178.239.151.200 +178.239.151.207 +178.239.151.225 +178.239.151.226 +178.239.151.228 +178.239.151.239 +178.239.151.240 +178.239.158.14 +178.239.158.56 +178.239.158.60 +178.239.158.61 +178.239.158.64 +178.239.158.65 +178.239.158.67 +178.239.158.76 +178.239.158.80 +178.239.158.90 +178.239.158.91 +178.239.158.95 +178.239.158.96 +178.239.158.103 +178.239.158.117 +178.239.158.118 +178.239.158.128 +178.239.158.140 +178.239.158.141 +178.239.158.145 +178.252.128.66 +178.252.128.67 +178.252.130.200 +178.252.132.131 +178.252.132.202 +178.252.134.6 +178.252.134.106 +178.252.137.98 +178.252.137.99 +178.252.137.100 +178.252.138.62 +178.252.140.186 +178.252.140.198 +178.252.140.210 +178.252.140.211 +178.252.140.212 +178.252.141.66 +178.252.141.72 +178.252.141.73 +178.252.141.74 +178.252.141.75 +178.252.141.76 +178.252.141.77 +178.252.141.78 +178.252.141.228 +178.252.143.122 +178.252.143.123 +178.252.143.124 +178.252.143.125 +178.252.143.126 +178.252.143.132 +178.252.143.133 +178.252.144.227 +178.252.144.228 +178.252.146.186 +178.252.147.66 +178.252.147.69 +178.252.147.219 +178.252.148.244 +178.252.149.134 +178.252.149.237 +178.252.153.60 +178.252.153.197 +178.252.166.35 +178.252.166.147 +178.252.166.148 +178.252.166.150 +178.252.166.151 +178.252.166.152 +178.252.167.151 +178.252.178.34 +178.252.178.35 +178.252.178.40 +178.252.178.44 +178.252.180.195 +178.252.181.45 +178.252.181.52 +178.252.181.73 +178.252.181.246 +178.252.181.247 +178.252.182.180 +178.252.183.11 +178.252.190.15 +185.2.13.98 +185.2.13.193 +185.2.13.207 +185.2.13.208 +185.2.13.211 +185.2.13.212 +185.2.13.213 +185.2.13.214 +185.2.13.217 +185.2.13.218 +185.2.13.219 +185.2.13.220 +185.2.13.221 +185.2.13.222 +185.2.14.3 +185.2.14.4 +185.2.14.5 +185.2.14.7 +185.2.14.12 +185.2.14.37 +185.2.14.39 +185.2.14.42 +185.2.14.59 +185.2.14.61 +185.2.14.62 +185.2.14.67 +185.2.14.70 +185.2.14.75 +185.2.14.76 +185.2.14.77 +185.2.14.86 +185.2.14.91 +185.2.14.93 +185.2.14.126 +185.2.14.131 +185.2.14.132 +185.2.14.156 +185.2.14.157 +185.2.14.164 +185.2.14.179 +185.2.14.180 +185.2.14.181 +185.2.14.197 +185.2.14.198 +185.2.14.201 +185.2.14.214 +185.2.14.219 +185.2.14.220 +185.2.14.227 +185.2.14.236 +185.2.14.237 +185.2.14.238 +185.2.14.243 +185.2.14.252 +185.2.14.254 +185.3.124.35 +185.3.124.36 +185.3.124.42 +185.3.124.51 +185.3.124.52 +185.3.124.53 +185.3.124.54 +185.3.124.58 +185.3.124.68 +185.3.124.94 +185.3.124.98 +185.3.124.99 +185.3.124.106 +185.3.124.131 +185.3.124.150 +185.3.124.206 +185.3.124.226 +185.3.124.247 +185.3.124.253 +185.3.126.3 +185.3.126.4 +185.3.201.195 +185.3.201.240 +185.3.201.241 +185.3.201.242 +185.3.201.243 +185.3.201.244 +185.3.201.245 +185.3.201.246 +185.3.201.247 +185.3.201.248 +185.3.201.249 +185.3.201.250 +185.3.201.251 +185.3.201.252 +185.3.201.253 +185.3.201.254 +185.3.201.255 +185.3.202.148 +185.3.203.32 +185.3.203.33 +185.3.203.35 +185.3.203.203 +185.4.1.14 +185.4.1.15 +185.4.1.20 +185.4.1.21 +185.4.1.23 +185.4.1.26 +185.4.1.30 +185.4.1.37 +185.4.1.38 +185.4.1.39 +185.4.1.41 +185.4.1.44 +185.4.1.47 +185.4.1.62 +185.4.1.64 +185.4.1.67 +185.4.1.70 +185.4.1.72 +185.4.1.76 +185.4.1.87 +185.4.1.90 +185.4.1.98 +185.4.1.100 +185.4.1.154 +185.4.1.170 +185.4.2.50 +185.4.3.12 +185.4.3.14 +185.4.3.15 +185.4.3.16 +185.4.3.17 +185.4.3.26 +185.4.3.27 +185.4.3.28 +185.4.3.29 +185.4.3.30 +185.4.3.31 +185.4.3.32 +185.4.3.33 +185.4.3.34 +185.4.3.37 +185.4.3.42 +185.4.3.43 +185.4.3.44 +185.4.3.45 +185.4.3.54 +185.4.3.64 +185.4.3.65 +185.4.3.66 +185.4.3.74 +185.4.3.75 +185.4.3.78 +185.4.28.184 +185.4.29.181 +185.4.29.196 +185.4.30.4 +185.4.30.13 +185.4.30.24 +185.4.30.31 +185.4.30.32 +185.4.30.33 +185.4.30.41 +185.4.30.42 +185.4.30.44 +185.4.30.54 +185.4.30.62 +185.4.30.64 +185.4.30.66 +185.4.30.69 +185.4.30.73 +185.4.30.74 +185.4.30.82 +185.4.30.87 +185.4.30.88 +185.4.30.89 +185.4.30.90 +185.4.30.91 +185.4.30.111 +185.4.30.120 +185.4.30.130 +185.4.30.173 +185.4.30.174 +185.4.30.175 +185.4.30.176 +185.4.30.177 +185.4.30.178 +185.4.30.179 +185.4.30.180 +185.4.30.181 +185.4.30.182 +185.4.30.234 +185.4.31.6 +185.7.172.4 +185.7.172.5 +185.7.172.11 +185.7.172.12 +185.7.172.34 +185.7.172.53 +185.7.172.56 +185.7.172.58 +185.7.172.59 +185.7.172.66 +185.7.172.67 +185.7.172.68 +185.7.172.74 +185.7.172.82 +185.7.172.122 +185.7.172.130 +185.7.172.133 +185.7.172.134 +185.7.172.136 +185.7.172.137 +185.7.172.150 +185.7.212.4 +185.7.212.5 +185.7.212.16 +185.7.212.29 +185.7.212.32 +185.7.212.37 +185.7.212.53 +185.7.212.60 +185.7.212.63 +185.7.212.72 +185.7.212.83 +185.7.212.87 +185.7.212.95 +185.7.212.101 +185.7.212.109 +185.7.212.111 +185.7.212.112 +185.7.212.120 +185.7.212.135 +185.7.212.139 +185.7.212.145 +185.7.212.154 +185.7.212.155 +185.7.212.166 +185.7.212.168 +185.7.212.169 +185.7.212.170 +185.7.212.171 +185.7.212.172 +185.7.212.181 +185.7.212.183 +185.7.212.192 +185.7.212.195 +185.7.212.201 +185.7.212.204 +185.7.212.217 +185.7.212.218 +185.7.212.219 +185.7.212.221 +185.7.212.232 +185.7.212.236 +185.7.212.245 +185.7.212.246 +185.7.212.252 +185.8.172.6 +185.8.172.12 +185.8.172.14 +185.8.172.15 +185.8.172.19 +185.8.172.22 +185.8.172.25 +185.8.172.35 +185.8.172.56 +185.8.172.58 +185.8.172.67 +185.8.172.68 +185.8.172.69 +185.8.172.81 +185.8.172.83 +185.8.172.101 +185.8.172.104 +185.8.172.105 +185.8.172.107 +185.8.172.117 +185.8.172.118 +185.8.172.119 +185.8.172.120 +185.8.172.125 +185.8.172.137 +185.8.172.139 +185.8.172.145 +185.8.172.146 +185.8.172.147 +185.8.172.151 +185.8.172.155 +185.8.172.156 +185.8.172.159 +185.8.172.161 +185.8.172.165 +185.8.172.168 +185.8.172.170 +185.8.172.172 +185.8.172.180 +185.8.172.196 +185.8.172.197 +185.8.172.198 +185.8.172.205 +185.8.172.208 +185.8.172.211 +185.8.172.216 +185.8.172.217 +185.8.172.218 +185.8.172.227 +185.8.172.233 +185.8.172.243 +185.8.172.249 +185.8.173.13 +185.8.173.16 +185.8.173.36 +185.8.173.39 +185.8.173.42 +185.8.173.44 +185.8.173.45 +185.8.173.53 +185.8.173.59 +185.8.173.71 +185.8.173.72 +185.8.173.79 +185.8.173.83 +185.8.173.102 +185.8.173.104 +185.8.173.107 +185.8.173.110 +185.8.173.122 +185.8.173.126 +185.8.173.127 +185.8.173.128 +185.8.173.129 +185.8.173.131 +185.8.173.136 +185.8.173.137 +185.8.173.140 +185.8.173.142 +185.8.173.146 +185.8.173.151 +185.8.173.153 +185.8.173.154 +185.8.173.159 +185.8.173.163 +185.8.173.168 +185.8.173.170 +185.8.173.172 +185.8.173.181 +185.8.173.182 +185.8.173.193 +185.8.173.196 +185.8.173.198 +185.8.173.201 +185.8.173.206 +185.8.173.212 +185.8.173.213 +185.8.173.217 +185.8.173.218 +185.8.173.226 +185.8.173.229 +185.8.173.248 +185.8.173.249 +185.8.174.6 +185.8.174.7 +185.8.174.16 +185.8.174.18 +185.8.174.37 +185.8.174.39 +185.8.174.40 +185.8.174.43 +185.8.174.49 +185.8.174.55 +185.8.174.62 +185.8.174.65 +185.8.174.70 +185.8.174.82 +185.8.174.86 +185.8.174.87 +185.8.174.88 +185.8.174.92 +185.8.174.98 +185.8.174.100 +185.8.174.105 +185.8.174.110 +185.8.174.111 +185.8.174.112 +185.8.174.116 +185.8.174.117 +185.8.174.121 +185.8.174.123 +185.8.174.133 +185.8.174.139 +185.8.174.140 +185.8.174.143 +185.8.174.144 +185.8.174.145 +185.8.174.148 +185.8.174.149 +185.8.174.150 +185.8.174.151 +185.8.174.155 +185.8.174.159 +185.8.174.160 +185.8.174.162 +185.8.174.167 +185.8.174.168 +185.8.174.171 +185.8.174.178 +185.8.174.179 +185.8.174.181 +185.8.174.182 +185.8.174.189 +185.8.174.190 +185.8.174.193 +185.8.174.196 +185.8.174.198 +185.8.174.199 +185.8.174.200 +185.8.174.211 +185.8.174.218 +185.8.174.219 +185.8.174.222 +185.8.174.224 +185.8.174.231 +185.8.174.232 +185.8.174.234 +185.8.174.244 +185.8.174.245 +185.8.174.247 +185.8.174.251 +185.8.175.13 +185.8.175.27 +185.8.175.29 +185.8.175.31 +185.8.175.37 +185.8.175.40 +185.8.175.45 +185.8.175.46 +185.8.175.49 +185.8.175.51 +185.8.175.52 +185.8.175.58 +185.8.175.61 +185.8.175.64 +185.8.175.92 +185.8.175.96 +185.8.175.101 +185.8.175.102 +185.8.175.104 +185.8.175.107 +185.8.175.109 +185.8.175.117 +185.8.175.118 +185.8.175.126 +185.8.175.132 +185.8.175.134 +185.8.175.137 +185.8.175.144 +185.8.175.145 +185.8.175.146 +185.8.175.159 +185.8.175.162 +185.8.175.163 +185.8.175.169 +185.8.175.170 +185.8.175.171 +185.8.175.173 +185.8.175.198 +185.8.175.199 +185.8.175.203 +185.8.175.206 +185.8.175.208 +185.8.175.213 +185.8.175.223 +185.8.175.224 +185.8.175.228 +185.8.175.230 +185.8.175.234 +185.8.175.238 +185.10.71.138 +185.10.72.35 +185.10.72.41 +185.10.72.49 +185.10.72.64 +185.10.72.65 +185.10.72.66 +185.10.72.67 +185.10.72.70 +185.10.72.76 +185.10.72.83 +185.10.72.90 +185.10.72.121 +185.10.72.123 +185.10.72.136 +185.10.72.141 +185.10.72.150 +185.10.72.156 +185.10.72.165 +185.10.72.168 +185.10.72.182 +185.10.72.184 +185.10.72.216 +185.10.73.10 +185.10.73.11 +185.10.73.12 +185.10.73.15 +185.10.73.17 +185.10.73.19 +185.10.73.53 +185.10.73.72 +185.10.73.83 +185.10.73.84 +185.10.73.88 +185.10.73.96 +185.10.73.114 +185.10.73.116 +185.10.73.145 +185.10.73.152 +185.10.73.190 +185.10.73.192 +185.10.73.200 +185.10.73.227 +185.10.73.230 +185.10.74.2 +185.10.74.33 +185.10.74.37 +185.10.74.44 +185.10.74.47 +185.10.74.55 +185.10.74.56 +185.10.74.64 +185.10.74.72 +185.10.74.91 +185.10.74.105 +185.10.74.110 +185.10.74.123 +185.10.74.137 +185.10.74.149 +185.10.74.154 +185.10.74.156 +185.10.74.159 +185.10.74.184 +185.10.74.207 +185.10.74.240 +185.10.74.244 +185.10.74.245 +185.10.74.251 +185.10.74.253 +185.10.75.10 +185.10.75.51 +185.10.75.67 +185.10.75.69 +185.10.75.71 +185.10.75.73 +185.10.75.79 +185.10.75.85 +185.10.75.88 +185.10.75.89 +185.10.75.93 +185.10.75.94 +185.10.75.100 +185.10.75.102 +185.10.75.103 +185.10.75.104 +185.10.75.106 +185.10.75.109 +185.10.75.110 +185.10.75.112 +185.10.75.114 +185.10.75.116 +185.10.75.117 +185.10.75.122 +185.10.75.125 +185.10.75.126 +185.10.75.132 +185.10.75.136 +185.10.75.139 +185.10.75.142 +185.10.75.147 +185.10.75.154 +185.10.75.155 +185.10.75.160 +185.10.75.163 +185.10.75.165 +185.10.75.172 +185.10.75.174 +185.10.75.178 +185.10.75.179 +185.10.75.180 +185.10.75.181 +185.10.75.182 +185.10.75.183 +185.10.75.185 +185.10.75.186 +185.10.75.188 +185.10.75.189 +185.10.75.195 +185.10.75.216 +185.11.68.7 +185.11.68.8 +185.11.68.14 +185.11.68.29 +185.11.68.30 +185.11.69.99 +185.11.89.100 +185.12.101.11 +185.12.101.18 +185.13.228.45 +185.13.228.50 +185.13.228.59 +185.13.228.130 +185.13.228.135 +185.13.228.136 +185.13.228.137 +185.13.228.139 +185.13.228.150 +185.13.228.153 +185.13.228.154 +185.13.228.155 +185.13.228.157 +185.13.228.158 +185.13.228.162 +185.13.228.218 +185.13.228.244 +185.13.229.3 +185.13.229.4 +185.13.229.53 +185.13.229.228 +185.13.230.75 +185.13.230.115 +185.13.230.117 +185.13.230.118 +185.13.230.156 +185.13.231.11 +185.14.162.68 +185.14.162.80 +185.14.163.6 +185.14.163.7 +185.14.163.75 +185.18.212.19 +185.18.212.65 +185.18.212.71 +185.18.212.83 +185.18.212.85 +185.18.212.96 +185.18.212.100 +185.18.212.102 +185.18.212.113 +185.18.212.132 +185.18.212.136 +185.18.212.137 +185.18.212.176 +185.18.212.177 +185.18.212.185 +185.18.212.188 +185.18.212.200 +185.18.212.216 +185.18.213.17 +185.18.213.18 +185.18.213.20 +185.18.213.22 +185.18.213.43 +185.18.213.52 +185.18.213.56 +185.18.213.59 +185.18.213.78 +185.18.213.82 +185.18.213.86 +185.18.213.164 +185.18.213.191 +185.18.213.211 +185.18.213.218 +185.18.213.224 +185.18.213.225 +185.18.213.226 +185.18.213.227 +185.18.214.23 +185.18.214.26 +185.18.214.28 +185.18.214.76 +185.18.214.82 +185.18.214.92 +185.18.214.130 +185.18.214.154 +185.18.214.243 +185.18.214.249 +185.18.214.250 +185.18.215.6 +185.18.215.18 +185.18.215.35 +185.18.215.42 +185.18.215.43 +185.18.215.109 +185.18.215.110 +185.18.215.120 +185.18.215.134 +185.18.215.136 +185.18.215.138 +185.18.215.209 +185.18.215.213 +185.18.215.214 +185.18.215.241 +185.19.201.19 +185.19.201.21 +185.19.201.25 +185.19.201.39 +185.19.201.41 +185.19.201.43 +185.19.201.49 +185.19.201.59 +185.19.201.60 +185.19.201.65 +185.19.201.78 +185.19.201.89 +185.19.201.106 +185.19.201.110 +185.19.201.116 +185.19.201.125 +185.19.201.142 +185.19.201.144 +185.19.201.148 +185.19.201.162 +185.19.201.167 +185.19.201.183 +185.19.201.190 +185.19.201.200 +185.20.162.8 +185.20.162.9 +185.21.70.234 +185.24.136.90 +185.24.139.71 +185.24.139.91 +185.24.252.133 +185.24.252.166 +185.24.252.195 +185.24.252.199 +185.24.255.71 +185.24.255.75 +185.24.255.195 +185.24.255.200 +185.26.32.33 +185.26.32.137 +185.31.124.3 +185.31.124.7 +185.31.125.3 +185.31.125.7 +185.31.126.3 +185.31.127.3 +185.36.228.117 +185.36.228.160 +185.36.228.161 +185.36.228.174 +185.36.231.11 +185.36.231.47 +185.36.231.88 +185.36.231.91 +185.36.231.93 +185.36.231.157 +185.36.231.231 +185.37.52.36 +185.37.52.37 +185.37.52.126 +185.37.53.209 +185.37.54.19 +185.37.54.20 +185.37.54.30 +185.37.54.112 +185.37.54.113 +185.37.54.133 +185.37.54.152 +185.37.54.191 +185.37.54.253 +185.37.54.254 +185.37.55.30 +185.37.55.127 +185.37.55.169 +185.37.55.203 +185.37.55.243 +185.37.55.247 +185.39.180.119 +185.39.180.120 +185.39.180.181 +185.39.182.228 +185.39.182.230 +185.39.182.232 +185.39.182.233 +185.39.182.234 +185.40.16.42 +185.40.16.194 +185.40.16.198 +185.41.1.130 +185.41.1.134 +185.41.1.141 +185.41.3.2 +185.41.3.3 +185.41.3.4 +185.41.3.6 +185.41.3.7 +185.41.3.8 +185.41.3.9 +185.41.3.10 +185.41.3.11 +185.41.3.12 +185.41.3.13 +185.41.3.14 +185.41.3.15 +185.41.3.16 +185.41.3.17 +185.41.3.18 +185.41.3.19 +185.41.3.20 +185.41.3.21 +185.41.3.22 +185.41.3.23 +185.41.3.24 +185.41.3.25 +185.41.3.26 +185.41.3.27 +185.41.3.28 +185.41.3.29 +185.41.3.30 +185.41.3.34 +185.41.3.98 +185.41.3.99 +185.41.3.100 +185.41.3.101 +185.41.3.102 +185.41.3.103 +185.41.3.104 +185.41.3.105 +185.41.3.107 +185.41.3.108 +185.41.3.109 +185.41.3.110 +185.41.3.111 +185.41.3.112 +185.41.3.113 +185.41.3.114 +185.41.3.115 +185.41.3.116 +185.41.3.117 +185.41.3.118 +185.41.3.119 +185.41.3.120 +185.41.3.121 +185.41.3.122 +185.41.3.123 +185.41.3.124 +185.41.3.125 +185.41.3.126 +185.42.224.5 +185.42.224.7 +185.42.225.31 +185.42.226.18 +185.44.36.36 +185.44.36.216 +185.44.36.248 +185.46.217.234 +185.47.48.26 +185.47.50.162 +185.47.50.166 +185.49.84.18 +185.49.84.34 +185.49.84.51 +185.49.84.68 +185.49.84.83 +185.49.84.84 +185.49.84.90 +185.49.84.91 +185.49.84.92 +185.49.84.93 +185.49.84.134 +185.49.84.143 +185.49.84.157 +185.49.84.166 +185.49.84.209 +185.49.84.226 +185.49.84.228 +185.49.84.229 +185.49.84.230 +185.49.84.231 +185.49.84.232 +185.49.84.233 +185.49.84.234 +185.49.84.235 +185.49.84.236 +185.49.84.237 +185.49.84.238 +185.49.84.239 +185.49.84.240 +185.49.84.241 +185.49.84.242 +185.49.84.243 +185.49.84.244 +185.49.84.245 +185.49.84.246 +185.49.84.248 +185.49.84.252 +185.49.85.2 +185.49.85.5 +185.49.85.19 +185.49.85.27 +185.49.85.28 +185.49.85.34 +185.49.85.35 +185.49.85.36 +185.49.85.37 +185.49.85.38 +185.49.85.39 +185.49.85.40 +185.49.85.41 +185.49.85.42 +185.49.85.43 +185.49.85.44 +185.49.85.45 +185.49.85.46 +185.49.85.130 +185.49.85.131 +185.49.85.133 +185.49.85.135 +185.49.85.138 +185.49.85.142 +185.49.85.143 +185.49.85.146 +185.49.85.149 +185.49.85.152 +185.49.85.154 +185.49.85.157 +185.49.85.163 +185.49.85.168 +185.49.85.172 +185.49.85.173 +185.49.85.204 +185.49.85.206 +185.49.85.212 +185.49.85.244 +185.49.86.13 +185.49.86.42 +185.49.86.56 +185.49.86.57 +185.49.86.111 +185.49.86.194 +185.49.86.202 +185.49.86.213 +185.49.87.94 +185.49.87.180 +185.49.87.181 +185.49.87.182 +185.49.87.218 +185.49.87.219 +185.49.87.240 +185.49.96.252 +185.49.96.253 +185.50.37.15 +185.50.37.46 +185.50.37.48 +185.50.37.52 +185.50.37.56 +185.50.37.66 +185.50.37.91 +185.50.37.166 +185.50.37.168 +185.50.37.171 +185.50.37.174 +185.50.37.175 +185.50.38.29 +185.50.38.33 +185.50.38.38 +185.50.38.72 +185.50.38.128 +185.50.38.135 +185.50.38.145 +185.50.38.148 +185.50.38.168 +185.50.38.171 +185.50.38.191 +185.50.38.201 +185.50.38.216 +185.50.38.220 +185.50.38.225 +185.50.38.236 +185.50.39.3 +185.50.39.32 +185.50.39.49 +185.50.39.50 +185.50.39.53 +185.50.39.94 +185.50.39.96 +185.50.39.97 +185.50.39.159 +185.51.43.2 +185.51.200.1 +185.51.200.5 +185.51.200.58 +185.51.200.182 +185.51.201.58 +185.51.201.77 +185.51.201.195 +185.51.201.255 +185.51.202.251 +185.53.140.6 +185.53.140.43 +185.53.141.45 +185.53.141.54 +185.53.141.120 +185.53.141.182 +185.53.141.187 +185.53.141.194 +185.53.141.195 +185.53.141.218 +185.53.141.229 +185.53.142.2 +185.53.142.3 +185.53.142.4 +185.53.142.5 +185.53.142.6 +185.53.142.35 +185.53.142.37 +185.53.142.123 +185.53.142.174 +185.53.142.180 +185.53.142.211 +185.53.142.212 +185.53.142.213 +185.53.142.214 +185.53.143.3 +185.53.143.74 +185.53.143.133 +185.53.143.134 +185.53.143.135 +185.53.143.136 +185.53.143.137 +185.53.143.139 +185.53.143.141 +185.53.143.142 +185.53.143.143 +185.53.143.146 +185.53.143.156 +185.53.143.157 +185.53.143.186 +185.53.143.244 +185.55.224.3 +185.55.224.4 +185.55.224.8 +185.55.224.10 +185.55.224.11 +185.55.224.12 +185.55.224.13 +185.55.224.14 +185.55.224.15 +185.55.224.16 +185.55.224.17 +185.55.224.18 +185.55.224.20 +185.55.224.21 +185.55.224.22 +185.55.224.23 +185.55.224.27 +185.55.224.28 +185.55.224.29 +185.55.224.30 +185.55.224.31 +185.55.224.32 +185.55.224.33 +185.55.224.34 +185.55.224.35 +185.55.224.36 +185.55.224.38 +185.55.224.39 +185.55.224.45 +185.55.224.46 +185.55.224.48 +185.55.224.49 +185.55.224.50 +185.55.224.51 +185.55.224.52 +185.55.224.53 +185.55.224.54 +185.55.224.55 +185.55.224.56 +185.55.224.57 +185.55.224.59 +185.55.224.60 +185.55.224.61 +185.55.224.62 +185.55.224.63 +185.55.224.64 +185.55.224.65 +185.55.224.66 +185.55.224.67 +185.55.224.68 +185.55.224.69 +185.55.224.70 +185.55.224.71 +185.55.224.72 +185.55.224.73 +185.55.224.74 +185.55.224.75 +185.55.224.76 +185.55.224.77 +185.55.224.78 +185.55.224.80 +185.55.224.81 +185.55.224.82 +185.55.224.83 +185.55.224.84 +185.55.224.85 +185.55.224.86 +185.55.224.87 +185.55.224.88 +185.55.224.93 +185.55.224.94 +185.55.224.96 +185.55.224.97 +185.55.224.98 +185.55.224.99 +185.55.224.101 +185.55.224.102 +185.55.224.104 +185.55.224.105 +185.55.224.106 +185.55.224.108 +185.55.224.110 +185.55.224.111 +185.55.224.112 +185.55.224.113 +185.55.224.114 +185.55.224.117 +185.55.224.118 +185.55.224.120 +185.55.224.122 +185.55.224.123 +185.55.224.124 +185.55.224.125 +185.55.224.126 +185.55.224.127 +185.55.224.128 +185.55.224.129 +185.55.224.132 +185.55.224.134 +185.55.224.135 +185.55.224.136 +185.55.224.137 +185.55.224.138 +185.55.224.139 +185.55.224.140 +185.55.224.141 +185.55.224.142 +185.55.224.143 +185.55.224.144 +185.55.224.145 +185.55.224.146 +185.55.224.148 +185.55.224.149 +185.55.224.150 +185.55.224.152 +185.55.224.153 +185.55.224.154 +185.55.224.155 +185.55.224.156 +185.55.224.157 +185.55.224.158 +185.55.224.159 +185.55.224.160 +185.55.224.161 +185.55.224.162 +185.55.224.165 +185.55.224.166 +185.55.224.172 +185.55.224.179 +185.55.224.180 +185.55.224.181 +185.55.224.182 +185.55.224.183 +185.55.224.184 +185.55.224.185 +185.55.224.188 +185.55.224.191 +185.55.224.192 +185.55.224.194 +185.55.224.195 +185.55.224.196 +185.55.224.197 +185.55.224.198 +185.55.224.199 +185.55.224.201 +185.55.224.203 +185.55.224.204 +185.55.224.206 +185.55.224.207 +185.55.224.208 +185.55.224.209 +185.55.224.211 +185.55.224.212 +185.55.224.213 +185.55.224.215 +185.55.224.216 +185.55.224.217 +185.55.224.218 +185.55.224.219 +185.55.224.220 +185.55.224.221 +185.55.224.227 +185.55.224.229 +185.55.224.230 +185.55.224.231 +185.55.224.232 +185.55.224.233 +185.55.224.234 +185.55.224.235 +185.55.224.236 +185.55.224.237 +185.55.224.245 +185.55.224.246 +185.55.224.247 +185.55.224.251 +185.55.225.2 +185.55.225.3 +185.55.225.4 +185.55.225.5 +185.55.225.6 +185.55.225.7 +185.55.225.8 +185.55.225.9 +185.55.225.10 +185.55.225.11 +185.55.225.15 +185.55.225.16 +185.55.225.17 +185.55.225.18 +185.55.225.19 +185.55.225.20 +185.55.225.21 +185.55.225.22 +185.55.225.23 +185.55.225.24 +185.55.225.26 +185.55.225.27 +185.55.225.28 +185.55.225.29 +185.55.225.34 +185.55.225.35 +185.55.225.36 +185.55.225.37 +185.55.225.38 +185.55.225.39 +185.55.225.41 +185.55.225.42 +185.55.225.43 +185.55.225.44 +185.55.225.45 +185.55.225.46 +185.55.225.47 +185.55.225.48 +185.55.225.49 +185.55.225.50 +185.55.225.51 +185.55.225.52 +185.55.225.53 +185.55.225.54 +185.55.225.55 +185.55.225.56 +185.55.225.57 +185.55.225.59 +185.55.225.60 +185.55.225.61 +185.55.225.62 +185.55.225.63 +185.55.225.65 +185.55.225.66 +185.55.225.67 +185.55.225.69 +185.55.225.72 +185.55.225.73 +185.55.225.77 +185.55.225.78 +185.55.225.79 +185.55.225.80 +185.55.225.81 +185.55.225.82 +185.55.225.83 +185.55.225.84 +185.55.225.85 +185.55.225.86 +185.55.225.87 +185.55.225.88 +185.55.225.89 +185.55.225.90 +185.55.225.91 +185.55.225.92 +185.55.225.93 +185.55.225.94 +185.55.225.95 +185.55.225.96 +185.55.225.98 +185.55.225.99 +185.55.225.100 +185.55.225.102 +185.55.225.103 +185.55.225.104 +185.55.225.105 +185.55.225.106 +185.55.225.107 +185.55.225.108 +185.55.225.110 +185.55.225.111 +185.55.225.112 +185.55.225.113 +185.55.225.114 +185.55.225.115 +185.55.225.116 +185.55.225.117 +185.55.225.118 +185.55.225.119 +185.55.225.120 +185.55.225.121 +185.55.225.122 +185.55.225.123 +185.55.225.124 +185.55.225.125 +185.55.225.126 +185.55.225.127 +185.55.225.128 +185.55.225.129 +185.55.225.130 +185.55.225.131 +185.55.225.132 +185.55.225.133 +185.55.225.134 +185.55.225.135 +185.55.225.136 +185.55.225.137 +185.55.225.138 +185.55.225.139 +185.55.225.140 +185.55.225.141 +185.55.225.142 +185.55.225.143 +185.55.225.144 +185.55.225.145 +185.55.225.146 +185.55.225.147 +185.55.225.148 +185.55.225.149 +185.55.225.152 +185.55.225.153 +185.55.225.154 +185.55.225.155 +185.55.225.156 +185.55.225.157 +185.55.225.159 +185.55.225.160 +185.55.225.161 +185.55.225.162 +185.55.225.164 +185.55.225.165 +185.55.225.166 +185.55.225.167 +185.55.225.168 +185.55.225.169 +185.55.225.170 +185.55.225.172 +185.55.225.173 +185.55.225.174 +185.55.225.175 +185.55.225.177 +185.55.225.178 +185.55.225.179 +185.55.225.180 +185.55.225.181 +185.55.225.183 +185.55.225.184 +185.55.225.185 +185.55.225.186 +185.55.225.187 +185.55.225.188 +185.55.225.191 +185.55.225.192 +185.55.225.193 +185.55.225.194 +185.55.225.195 +185.55.225.196 +185.55.225.197 +185.55.225.198 +185.55.225.199 +185.55.225.200 +185.55.225.201 +185.55.225.202 +185.55.225.203 +185.55.225.204 +185.55.225.205 +185.55.225.206 +185.55.225.207 +185.55.225.208 +185.55.225.209 +185.55.225.211 +185.55.225.212 +185.55.225.213 +185.55.225.214 +185.55.225.215 +185.55.225.216 +185.55.225.217 +185.55.225.218 +185.55.225.219 +185.55.225.220 +185.55.225.221 +185.55.225.222 +185.55.225.223 +185.55.225.224 +185.55.225.225 +185.55.225.227 +185.55.225.228 +185.55.225.229 +185.55.225.230 +185.55.225.231 +185.55.225.232 +185.55.225.233 +185.55.225.234 +185.55.225.235 +185.55.225.236 +185.55.225.237 +185.55.225.240 +185.55.225.241 +185.55.225.242 +185.55.225.243 +185.55.225.244 +185.55.225.245 +185.55.225.246 +185.55.225.247 +185.55.225.248 +185.55.225.251 +185.55.226.20 +185.55.226.39 +185.55.226.100 +185.55.226.116 +185.55.226.121 +185.55.226.124 +185.55.226.145 +185.55.226.171 +185.55.226.173 +185.55.226.181 +185.55.226.194 +185.55.226.211 +185.55.226.226 +185.55.226.231 +185.55.226.239 +185.55.227.2 +185.55.227.7 +185.55.227.9 +185.55.227.12 +185.55.227.15 +185.55.227.20 +185.55.227.21 +185.55.227.22 +185.55.227.23 +185.55.227.24 +185.55.227.26 +185.55.227.29 +185.55.227.31 +185.55.227.32 +185.55.227.33 +185.55.227.35 +185.55.227.44 +185.55.227.49 +185.55.227.50 +185.55.227.51 +185.55.227.53 +185.55.227.54 +185.55.227.55 +185.55.227.56 +185.55.227.57 +185.55.227.58 +185.55.227.59 +185.55.227.60 +185.55.227.61 +185.55.227.62 +185.55.227.63 +185.55.227.64 +185.55.227.65 +185.55.227.66 +185.55.227.67 +185.55.227.68 +185.55.227.69 +185.55.227.70 +185.55.227.71 +185.55.227.72 +185.55.227.73 +185.55.227.76 +185.55.227.77 +185.55.227.78 +185.55.227.79 +185.55.227.80 +185.55.227.81 +185.55.227.82 +185.55.227.83 +185.55.227.84 +185.55.227.85 +185.55.227.86 +185.55.227.87 +185.55.227.88 +185.55.227.89 +185.55.227.91 +185.55.227.92 +185.55.227.93 +185.55.227.94 +185.55.227.95 +185.55.227.96 +185.55.227.97 +185.55.227.98 +185.55.227.99 +185.55.227.100 +185.55.227.102 +185.55.227.103 +185.55.227.104 +185.55.227.106 +185.55.227.107 +185.55.227.108 +185.55.227.109 +185.55.227.110 +185.55.227.111 +185.55.227.112 +185.55.227.113 +185.55.227.114 +185.55.227.115 +185.55.227.116 +185.55.227.117 +185.55.227.118 +185.55.227.119 +185.55.227.120 +185.55.227.122 +185.55.227.123 +185.55.227.124 +185.55.227.125 +185.55.227.126 +185.55.227.127 +185.55.227.128 +185.55.227.129 +185.55.227.130 +185.55.227.131 +185.55.227.132 +185.55.227.133 +185.55.227.134 +185.55.227.139 +185.55.227.140 +185.55.227.144 +185.55.227.145 +185.55.227.146 +185.55.227.148 +185.55.227.149 +185.55.227.150 +185.55.227.151 +185.55.227.152 +185.55.227.153 +185.55.227.154 +185.55.227.155 +185.55.227.156 +185.55.227.157 +185.55.227.158 +185.55.227.159 +185.55.227.160 +185.55.227.161 +185.55.227.162 +185.55.227.163 +185.55.227.164 +185.55.227.165 +185.55.227.166 +185.55.227.168 +185.55.227.169 +185.55.227.171 +185.55.227.172 +185.55.227.174 +185.55.227.176 +185.55.227.177 +185.55.227.178 +185.55.227.179 +185.55.227.180 +185.55.227.181 +185.55.227.182 +185.55.227.183 +185.55.227.184 +185.55.227.185 +185.55.227.189 +185.55.227.190 +185.55.227.191 +185.55.227.194 +185.55.227.196 +185.55.227.197 +185.55.227.198 +185.55.227.199 +185.55.227.201 +185.55.227.202 +185.55.227.203 +185.55.227.204 +185.55.227.206 +185.55.227.207 +185.55.227.208 +185.55.227.210 +185.55.227.211 +185.55.227.213 +185.55.227.215 +185.55.227.216 +185.55.227.218 +185.55.227.220 +185.55.227.222 +185.55.227.223 +185.55.227.224 +185.55.227.225 +185.55.227.226 +185.55.227.228 +185.55.227.229 +185.55.227.231 +185.55.227.233 +185.55.227.235 +185.55.227.236 +185.55.227.239 +185.55.227.240 +185.55.227.241 +185.55.227.243 +185.55.227.245 +185.56.93.100 +185.56.93.101 +185.57.164.4 +185.57.164.5 +185.57.164.177 +185.57.165.93 +185.58.240.15 +185.58.240.80 +185.58.240.81 +185.58.240.105 +185.58.240.114 +185.58.240.115 +185.58.240.120 +185.58.240.167 +185.58.240.216 +185.58.240.251 +185.58.241.5 +185.58.241.12 +185.58.241.27 +185.58.241.44 +185.58.241.54 +185.58.241.62 +185.58.241.139 +185.58.241.148 +185.58.241.169 +185.58.241.225 +185.58.241.226 +185.58.241.227 +185.58.241.235 +185.58.242.19 +185.58.242.37 +185.58.242.129 +185.58.242.215 +185.58.242.230 +185.58.242.236 +185.58.242.253 +185.58.243.65 +185.59.112.218 +185.60.136.90 +185.60.136.128 +185.60.136.140 +185.60.137.149 +185.63.113.16 +185.63.113.17 +185.63.113.18 +185.63.113.20 +185.63.113.25 +185.63.113.30 +185.63.113.35 +185.63.113.37 +185.63.113.131 +185.63.113.134 +185.63.113.137 +185.63.113.141 +185.63.113.145 +185.63.113.148 +185.63.113.156 +185.63.113.158 +185.63.113.173 +185.63.113.194 +185.63.113.196 +185.63.113.211 +185.63.113.212 +185.63.113.219 +185.63.113.236 +185.63.113.242 +185.63.113.245 +185.63.114.119 +185.63.237.12 +185.66.224.4 +185.66.224.24 +185.66.226.83 +185.66.230.194 +185.66.230.195 +185.66.230.253 +185.67.12.11 +185.67.12.12 +185.67.14.11 +185.67.156.5 +185.67.156.7 +185.67.213.50 +185.67.214.50 +185.71.193.84 +185.71.194.171 +185.72.25.204 +185.72.25.205 +185.72.25.226 +185.73.78.10 +185.73.112.39 +185.73.112.41 +185.73.112.62 +185.73.112.163 +185.73.112.180 +185.73.112.227 +185.73.112.228 +185.73.114.28 +185.73.114.67 +185.73.114.82 +185.73.114.86 +185.73.114.123 +185.73.114.207 +185.73.114.232 +185.73.114.247 +185.73.114.254 +185.73.226.237 +185.73.226.241 +185.73.226.242 +185.73.226.243 +185.74.221.2 +185.74.221.51 +185.74.221.155 +185.74.221.180 +185.74.221.197 +185.74.221.238 +185.78.21.33 +185.78.21.35 +185.78.21.40 +185.78.21.42 +185.78.21.43 +185.78.21.49 +185.78.21.50 +185.78.21.51 +185.78.22.2 +185.78.22.174 +185.78.22.175 +185.78.23.202 +185.79.62.4 +185.79.62.5 +185.79.96.67 +185.79.156.78 +185.79.156.88 +185.79.156.92 +185.79.156.105 +185.79.156.107 +185.79.156.122 +185.79.156.132 +185.79.156.148 +185.79.156.149 +185.79.156.151 +185.79.156.155 +185.79.156.160 +185.79.156.165 +185.79.156.172 +185.79.156.173 +185.79.156.174 +185.79.156.199 +185.79.156.214 +185.79.156.228 +185.79.156.229 +185.79.156.234 +185.79.157.2 +185.79.157.3 +185.79.157.5 +185.79.157.7 +185.79.157.16 +185.79.157.23 +185.79.157.60 +185.79.157.153 +185.79.157.161 +185.79.157.188 +185.79.157.192 +185.79.157.195 +185.79.157.199 +185.79.157.205 +185.79.157.208 +185.79.157.232 +185.79.157.247 +185.79.157.254 +185.79.158.8 +185.79.158.22 +185.79.158.23 +185.79.158.33 +185.79.158.75 +185.79.158.165 +185.79.158.166 +185.79.158.169 +185.79.158.212 +185.79.159.136 +185.81.96.5 +185.81.96.10 +185.81.96.15 +185.81.96.16 +185.81.96.20 +185.81.96.27 +185.81.96.28 +185.81.96.31 +185.81.96.35 +185.81.96.38 +185.81.96.42 +185.81.96.103 +185.81.96.114 +185.81.96.129 +185.81.96.195 +185.81.96.211 +185.81.96.221 +185.81.96.240 +185.81.97.5 +185.81.97.17 +185.81.97.25 +185.81.97.26 +185.81.97.28 +185.81.97.55 +185.81.97.56 +185.81.97.59 +185.81.97.69 +185.81.97.77 +185.81.97.89 +185.81.97.148 +185.81.97.155 +185.81.97.163 +185.81.97.165 +185.81.97.175 +185.81.97.181 +185.81.97.191 +185.81.97.193 +185.81.97.222 +185.81.97.235 +185.81.97.250 +185.81.99.46 +185.81.99.76 +185.81.99.119 +185.81.99.120 +185.81.99.123 +185.81.99.135 +185.81.99.140 +185.81.99.151 +185.81.99.156 +185.81.99.171 +185.81.99.185 +185.81.99.222 +185.81.99.223 +185.81.99.228 +185.81.99.239 +185.82.136.19 +185.82.216.236 +185.83.88.19 +185.83.88.23 +185.83.88.24 +185.83.88.27 +185.83.91.3 +185.83.112.67 +185.83.112.99 +185.83.112.110 +185.83.112.212 +185.83.113.28 +185.83.113.39 +185.83.113.46 +185.83.113.57 +185.83.113.59 +185.83.113.77 +185.83.113.78 +185.83.113.80 +185.83.113.92 +185.83.113.104 +185.83.113.123 +185.83.113.124 +185.83.113.142 +185.83.113.172 +185.83.113.176 +185.83.113.178 +185.83.113.180 +185.83.113.184 +185.83.113.192 +185.83.113.198 +185.83.113.210 +185.83.113.213 +185.83.113.214 +185.83.113.216 +185.83.113.217 +185.83.113.218 +185.83.113.219 +185.83.113.220 +185.83.113.225 +185.83.113.226 +185.83.113.227 +185.83.113.228 +185.83.113.230 +185.83.113.231 +185.83.113.234 +185.83.113.236 +185.83.113.241 +185.83.113.244 +185.83.113.246 +185.83.113.247 +185.83.113.251 +185.83.113.252 +185.83.114.57 +185.83.114.67 +185.83.114.69 +185.83.114.75 +185.83.114.110 +185.83.114.116 +185.83.114.118 +185.83.114.122 +185.83.114.128 +185.83.114.142 +185.83.114.148 +185.83.114.153 +185.83.114.159 +185.83.114.162 +185.83.114.172 +185.83.114.186 +185.83.114.244 +185.83.114.251 +185.83.115.42 +185.83.115.43 +185.83.115.44 +185.83.115.46 +185.83.115.182 +185.83.181.58 +185.83.181.100 +185.83.181.101 +185.83.181.120 +185.83.181.121 +185.83.181.190 +185.83.182.19 +185.83.182.22 +185.83.182.29 +185.83.182.85 +185.83.182.107 +185.83.182.120 +185.83.182.172 +185.83.183.3 +185.83.183.4 +185.83.183.5 +185.83.183.8 +185.83.183.14 +185.83.183.17 +185.83.183.19 +185.83.183.23 +185.83.183.24 +185.83.183.27 +185.83.183.30 +185.83.183.37 +185.83.183.38 +185.83.183.39 +185.83.183.41 +185.83.183.44 +185.83.183.45 +185.83.183.46 +185.83.183.49 +185.83.183.56 +185.83.183.57 +185.83.183.59 +185.83.183.92 +185.83.183.94 +185.83.183.97 +185.83.183.99 +185.83.183.100 +185.83.183.102 +185.83.183.107 +185.83.183.114 +185.83.183.115 +185.83.183.116 +185.83.183.128 +185.83.183.130 +185.83.183.131 +185.83.183.132 +185.83.183.134 +185.83.183.135 +185.83.183.136 +185.83.183.139 +185.83.183.147 +185.83.183.152 +185.83.183.153 +185.83.183.154 +185.83.183.159 +185.83.183.161 +185.83.183.163 +185.83.183.164 +185.83.183.184 +185.83.183.187 +185.83.183.195 +185.83.183.197 +185.83.183.199 +185.83.183.200 +185.83.183.202 +185.83.183.203 +185.83.183.205 +185.83.183.207 +185.83.183.210 +185.83.183.215 +185.83.183.216 +185.83.183.217 +185.83.183.218 +185.83.183.220 +185.83.183.221 +185.83.183.223 +185.83.183.225 +185.83.183.228 +185.83.183.229 +185.83.183.234 +185.83.183.236 +185.83.183.240 +185.83.183.241 +185.83.183.242 +185.83.183.244 +185.83.183.245 +185.83.183.246 +185.83.183.247 +185.83.183.248 +185.83.183.249 +185.83.183.254 +185.83.197.81 +185.83.197.83 +185.83.197.84 +185.83.197.87 +185.83.198.46 +185.83.199.97 +185.83.208.2 +185.83.208.3 +185.83.208.10 +185.83.208.13 +185.83.208.14 +185.83.208.15 +185.83.208.23 +185.83.208.24 +185.83.208.25 +185.83.208.26 +185.83.208.35 +185.83.208.39 +185.83.208.43 +185.83.208.44 +185.83.208.45 +185.83.208.106 +185.83.208.109 +185.83.208.111 +185.83.208.112 +185.83.208.114 +185.83.208.115 +185.83.208.116 +185.83.208.118 +185.83.208.119 +185.83.208.136 +185.83.208.140 +185.83.208.150 +185.83.208.158 +185.83.208.160 +185.83.208.162 +185.83.208.167 +185.83.208.169 +185.83.208.172 +185.83.208.173 +185.83.208.174 +185.83.208.175 +185.83.208.177 +185.83.208.182 +185.83.208.184 +185.83.208.188 +185.83.208.189 +185.83.208.201 +185.83.208.204 +185.83.208.205 +185.83.208.207 +185.83.208.208 +185.83.208.209 +185.83.208.226 +185.83.208.234 +185.83.208.249 +185.83.209.9 +185.83.209.10 +185.83.209.34 +185.83.209.35 +185.83.209.36 +185.83.209.37 +185.83.209.38 +185.83.209.39 +185.83.210.2 +185.83.210.3 +185.83.210.5 +185.83.210.6 +185.83.210.7 +185.83.210.9 +185.83.210.14 +185.83.210.34 +185.83.210.66 +185.83.210.67 +185.83.210.68 +185.83.210.70 +185.83.210.82 +185.83.210.98 +185.83.210.136 +185.83.210.138 +185.83.210.143 +185.83.210.184 +185.84.158.6 +185.84.158.79 +185.84.220.5 +185.84.222.22 +185.86.36.97 +185.86.36.98 +185.86.36.99 +185.86.36.100 +185.86.36.101 +185.86.36.102 +185.86.36.103 +185.86.36.104 +185.86.36.105 +185.86.36.106 +185.86.36.107 +185.86.36.108 +185.86.36.109 +185.86.36.161 +185.86.36.185 +185.86.36.201 +185.86.36.202 +185.86.180.71 +185.86.180.218 +185.86.181.21 +185.86.181.28 +185.86.181.29 +185.86.181.30 +185.86.181.102 +185.86.181.106 +185.86.181.108 +185.86.181.117 +185.86.181.146 +185.86.181.182 +185.86.181.183 +185.86.181.184 +185.86.181.214 +185.86.181.235 +185.86.181.237 +185.86.181.238 +185.88.152.3 +185.88.152.4 +185.88.152.12 +185.88.152.16 +185.88.152.22 +185.88.152.26 +185.88.152.27 +185.88.152.31 +185.88.152.40 +185.88.152.41 +185.88.152.42 +185.88.152.44 +185.88.152.45 +185.88.152.46 +185.88.152.63 +185.88.152.64 +185.88.152.66 +185.88.152.68 +185.88.152.70 +185.88.152.76 +185.88.152.77 +185.88.152.78 +185.88.152.81 +185.88.152.85 +185.88.152.86 +185.88.152.87 +185.88.152.88 +185.88.152.92 +185.88.152.93 +185.88.152.94 +185.88.152.97 +185.88.152.98 +185.88.152.103 +185.88.152.109 +185.88.152.110 +185.88.152.113 +185.88.152.115 +185.88.152.117 +185.88.152.123 +185.88.152.124 +185.88.152.126 +185.88.152.127 +185.88.152.129 +185.88.152.130 +185.88.152.134 +185.88.152.138 +185.88.152.140 +185.88.152.142 +185.88.152.154 +185.88.152.156 +185.88.152.161 +185.88.152.162 +185.88.152.163 +185.88.152.173 +185.88.152.178 +185.88.152.186 +185.88.152.187 +185.88.152.192 +185.88.152.193 +185.88.152.197 +185.88.152.199 +185.88.152.201 +185.88.152.202 +185.88.152.206 +185.88.152.213 +185.88.152.215 +185.88.152.218 +185.88.152.222 +185.88.152.223 +185.88.152.228 +185.88.152.232 +185.88.152.236 +185.88.152.238 +185.88.152.242 +185.88.152.248 +185.88.152.250 +185.88.152.254 +185.88.153.10 +185.88.153.15 +185.88.153.18 +185.88.153.20 +185.88.153.29 +185.88.153.31 +185.88.153.33 +185.88.153.34 +185.88.153.39 +185.88.153.42 +185.88.153.43 +185.88.153.50 +185.88.153.52 +185.88.153.64 +185.88.153.66 +185.88.153.67 +185.88.153.70 +185.88.153.71 +185.88.153.72 +185.88.153.75 +185.88.153.76 +185.88.153.80 +185.88.153.82 +185.88.153.83 +185.88.153.85 +185.88.153.88 +185.88.153.90 +185.88.153.98 +185.88.153.99 +185.88.153.103 +185.88.153.104 +185.88.153.108 +185.88.153.111 +185.88.153.112 +185.88.153.115 +185.88.153.116 +185.88.153.117 +185.88.153.118 +185.88.153.119 +185.88.153.122 +185.88.153.123 +185.88.153.124 +185.88.153.128 +185.88.153.130 +185.88.153.146 +185.88.153.147 +185.88.153.150 +185.88.153.153 +185.88.153.162 +185.88.153.166 +185.88.153.169 +185.88.153.170 +185.88.153.172 +185.88.153.173 +185.88.153.179 +185.88.153.180 +185.88.153.186 +185.88.153.189 +185.88.153.191 +185.88.153.196 +185.88.153.198 +185.88.153.199 +185.88.153.214 +185.88.153.218 +185.88.153.219 +185.88.153.222 +185.88.153.227 +185.88.153.228 +185.88.153.233 +185.88.153.234 +185.88.153.237 +185.88.153.240 +185.88.153.241 +185.88.153.243 +185.88.153.249 +185.88.153.254 +185.88.154.2 +185.88.154.22 +185.88.154.23 +185.88.154.26 +185.88.154.27 +185.88.154.29 +185.88.154.32 +185.88.154.35 +185.88.154.37 +185.88.154.38 +185.88.154.40 +185.88.154.41 +185.88.154.47 +185.88.154.55 +185.88.154.56 +185.88.154.62 +185.88.154.71 +185.88.154.76 +185.88.154.78 +185.88.154.85 +185.88.154.101 +185.88.154.108 +185.88.154.111 +185.88.154.116 +185.88.154.121 +185.88.154.123 +185.88.154.124 +185.88.154.125 +185.88.154.126 +185.88.154.128 +185.88.154.129 +185.88.154.130 +185.88.154.133 +185.88.154.134 +185.88.154.138 +185.88.154.151 +185.88.154.153 +185.88.154.160 +185.88.154.167 +185.88.154.174 +185.88.154.175 +185.88.154.181 +185.88.154.183 +185.88.154.187 +185.88.154.189 +185.88.154.192 +185.88.154.195 +185.88.154.197 +185.88.154.198 +185.88.154.215 +185.88.154.221 +185.88.154.224 +185.88.154.225 +185.88.154.233 +185.88.154.235 +185.88.154.240 +185.88.154.242 +185.88.154.248 +185.88.155.7 +185.88.155.53 +185.88.155.98 +185.88.155.122 +185.88.155.125 +185.88.155.150 +185.88.155.166 +185.88.155.167 +185.88.155.172 +185.88.155.189 +185.88.155.192 +185.88.176.138 +185.88.176.152 +185.88.176.210 +185.88.176.211 +185.88.176.212 +185.88.176.213 +185.88.176.218 +185.88.176.219 +185.88.176.221 +185.88.176.222 +185.88.177.2 +185.88.177.3 +185.88.177.5 +185.88.177.6 +185.88.177.7 +185.88.177.8 +185.88.177.9 +185.88.177.17 +185.88.177.18 +185.88.177.19 +185.88.177.20 +185.88.177.22 +185.88.177.25 +185.88.177.26 +185.88.177.27 +185.88.177.33 +185.88.177.34 +185.88.177.35 +185.88.177.50 +185.88.177.55 +185.88.177.56 +185.88.177.57 +185.88.177.60 +185.88.177.64 +185.88.177.67 +185.88.177.68 +185.88.177.69 +185.88.177.70 +185.88.177.72 +185.88.177.79 +185.88.177.81 +185.88.177.88 +185.88.177.90 +185.88.177.92 +185.88.177.94 +185.88.177.103 +185.88.177.104 +185.88.177.105 +185.88.177.110 +185.88.177.111 +185.88.177.112 +185.88.177.113 +185.88.177.117 +185.88.177.118 +185.88.177.120 +185.88.177.122 +185.88.177.123 +185.88.177.124 +185.88.177.135 +185.88.177.138 +185.88.177.139 +185.88.177.140 +185.88.177.141 +185.88.177.142 +185.88.177.143 +185.88.177.144 +185.88.177.145 +185.88.177.146 +185.88.177.147 +185.88.177.148 +185.88.177.149 +185.88.177.150 +185.88.177.151 +185.88.177.152 +185.88.177.153 +185.88.177.154 +185.88.177.155 +185.88.177.156 +185.88.177.157 +185.88.177.159 +185.88.177.162 +185.88.177.163 +185.88.177.164 +185.88.177.165 +185.88.177.167 +185.88.177.173 +185.88.177.175 +185.88.177.181 +185.88.177.182 +185.88.177.183 +185.88.177.184 +185.88.177.185 +185.88.177.188 +185.88.177.199 +185.88.177.206 +185.88.177.214 +185.88.177.222 +185.88.177.223 +185.88.177.226 +185.88.177.227 +185.88.177.228 +185.88.177.231 +185.88.177.232 +185.88.177.233 +185.88.177.236 +185.88.177.242 +185.88.177.243 +185.88.177.244 +185.88.177.246 +185.88.177.247 +185.88.177.248 +185.88.177.250 +185.88.177.252 +185.88.178.5 +185.88.178.6 +185.88.178.7 +185.88.178.9 +185.88.178.11 +185.88.178.15 +185.88.178.16 +185.88.178.17 +185.88.178.18 +185.88.178.20 +185.88.178.27 +185.88.178.28 +185.88.178.30 +185.88.178.35 +185.88.178.38 +185.88.178.39 +185.88.178.40 +185.88.178.42 +185.88.178.44 +185.88.178.45 +185.88.178.47 +185.88.178.49 +185.88.178.52 +185.88.178.55 +185.88.178.56 +185.88.178.58 +185.88.178.66 +185.88.178.67 +185.88.178.68 +185.88.178.69 +185.88.178.71 +185.88.178.72 +185.88.178.73 +185.88.178.75 +185.88.178.76 +185.88.178.77 +185.88.178.81 +185.88.178.89 +185.88.178.90 +185.88.178.91 +185.88.178.98 +185.88.178.101 +185.88.178.102 +185.88.178.103 +185.88.178.106 +185.88.178.107 +185.88.178.108 +185.88.178.109 +185.88.178.110 +185.88.178.113 +185.88.178.115 +185.88.178.117 +185.88.178.118 +185.88.178.119 +185.88.178.122 +185.88.178.123 +185.88.178.125 +185.88.178.132 +185.88.178.158 +185.88.178.177 +185.88.178.179 +185.88.178.196 +185.88.178.197 +185.88.178.199 +185.88.178.202 +185.88.178.206 +185.88.179.68 +185.88.179.71 +185.88.179.72 +185.88.179.73 +185.88.179.74 +185.88.179.76 +185.88.179.78 +185.88.179.83 +185.88.179.85 +185.88.179.87 +185.88.179.88 +185.88.179.90 +185.88.179.91 +185.88.179.92 +185.88.179.146 +185.88.179.148 +185.88.179.149 +185.88.179.196 +185.88.179.197 +185.88.179.198 +185.88.179.199 +185.88.179.200 +185.88.179.201 +185.88.179.202 +185.88.179.203 +185.88.179.204 +185.88.179.206 +185.88.179.211 +185.88.179.212 +185.88.179.213 +185.88.179.217 +185.88.179.218 +185.88.179.219 +185.88.179.226 +185.88.179.230 +185.88.179.231 +185.88.179.236 +185.88.179.237 +185.88.179.238 +185.88.179.252 +185.88.179.253 +185.88.255.10 +185.89.22.2 +185.89.22.6 +185.89.22.14 +185.89.22.100 +185.89.22.138 +185.89.22.186 +185.89.22.194 +185.89.22.210 +185.89.22.234 +185.89.112.20 +185.89.112.26 +185.89.113.121 +185.89.115.195 +185.94.96.2 +185.94.96.6 +185.94.96.10 +185.94.96.22 +185.94.96.34 +185.94.96.38 +185.94.96.42 +185.94.96.46 +185.94.96.56 +185.94.96.58 +185.94.96.76 +185.94.96.87 +185.94.96.89 +185.94.96.98 +185.94.96.100 +185.94.96.101 +185.94.96.102 +185.94.96.106 +185.94.96.110 +185.94.96.118 +185.94.96.130 +185.94.96.134 +185.94.96.138 +185.94.96.146 +185.94.96.154 +185.94.96.158 +185.94.96.162 +185.94.96.174 +185.94.96.178 +185.94.96.186 +185.94.96.190 +185.94.96.194 +185.94.96.196 +185.94.96.197 +185.94.96.204 +185.94.96.207 +185.94.96.210 +185.94.96.214 +185.94.96.216 +185.94.96.217 +185.94.96.218 +185.94.96.226 +185.94.96.229 +185.94.96.231 +185.94.96.233 +185.94.96.237 +185.94.96.242 +185.94.97.2 +185.94.97.5 +185.94.97.6 +185.94.97.7 +185.94.97.8 +185.94.97.9 +185.94.97.10 +185.94.97.13 +185.94.97.14 +185.94.97.18 +185.94.97.30 +185.94.97.34 +185.94.97.37 +185.94.97.46 +185.94.97.50 +185.94.97.54 +185.94.97.62 +185.94.97.74 +185.94.97.85 +185.94.97.87 +185.94.97.90 +185.94.97.92 +185.94.97.98 +185.94.97.102 +185.94.97.150 +185.94.97.198 +185.94.97.206 +185.94.97.210 +185.94.97.222 +185.94.97.227 +185.94.97.229 +185.94.97.230 +185.94.97.231 +185.94.97.233 +185.94.97.235 +185.94.97.236 +185.94.97.254 +185.94.98.3 +185.94.98.4 +185.94.98.6 +185.94.98.11 +185.94.98.17 +185.94.98.34 +185.94.98.42 +185.94.98.44 +185.94.98.45 +185.94.98.54 +185.94.98.69 +185.94.98.74 +185.94.98.78 +185.94.98.90 +185.94.98.92 +185.94.98.93 +185.94.98.94 +185.94.98.110 +185.94.98.114 +185.94.98.117 +185.94.98.118 +185.94.98.119 +185.94.98.121 +185.94.98.122 +185.94.98.123 +185.94.98.126 +185.94.98.130 +185.94.98.133 +185.94.98.134 +185.94.98.146 +185.94.98.151 +185.94.98.156 +185.94.98.171 +185.94.98.172 +185.94.98.194 +185.94.98.197 +185.94.98.198 +185.94.98.199 +185.94.98.201 +185.94.98.202 +185.94.98.203 +185.94.98.205 +185.94.98.206 +185.94.98.209 +185.94.98.216 +185.94.98.218 +185.94.98.227 +185.94.98.229 +185.94.98.230 +185.94.98.234 +185.94.98.236 +185.94.98.237 +185.94.98.242 +185.94.98.250 +185.94.98.251 +185.94.98.252 +185.94.98.253 +185.94.99.11 +185.94.99.13 +185.94.99.15 +185.94.99.18 +185.94.99.27 +185.94.99.36 +185.94.99.45 +185.94.99.50 +185.94.99.70 +185.94.99.82 +185.94.99.93 +185.94.99.100 +185.94.99.103 +185.94.99.105 +185.94.99.106 +185.94.99.130 +185.94.99.138 +185.94.99.142 +185.94.99.162 +185.94.99.166 +185.94.99.170 +185.94.99.171 +185.94.99.172 +185.94.99.174 +185.94.99.178 +185.94.99.181 +185.94.99.195 +185.94.99.201 +185.94.99.202 +185.94.99.203 +185.94.99.204 +185.94.99.209 +185.94.99.211 +185.94.99.215 +185.94.99.219 +185.94.99.220 +185.94.99.226 +185.94.99.228 +185.94.99.229 +185.94.99.230 +185.94.99.231 +185.94.99.232 +185.94.99.233 +185.94.99.234 +185.94.99.235 +185.94.99.236 +185.94.99.237 +185.94.99.238 +185.94.99.239 +185.94.99.240 +185.94.99.241 +185.94.99.243 +185.94.99.244 +185.94.99.246 +185.94.99.247 +185.94.99.248 +185.94.99.249 +185.94.99.250 +185.94.99.251 +185.94.99.253 +185.94.99.254 +185.95.152.11 +185.95.152.12 +185.95.152.155 +185.95.154.130 +185.95.155.164 +185.95.155.167 +185.95.180.131 +185.95.180.230 +185.97.116.107 +185.97.116.131 +185.97.116.166 +185.97.116.180 +185.97.116.204 +185.97.116.205 +185.97.117.54 +185.97.117.66 +185.97.117.159 +185.97.118.139 +185.97.119.4 +185.97.119.111 +185.98.113.34 +185.98.113.113 +185.98.113.115 +185.98.113.141 +185.98.113.142 +185.98.114.114 +185.98.114.116 +185.100.45.85 +185.100.47.7 +185.100.47.8 +185.100.47.11 +185.100.47.16 +185.100.47.82 +185.103.129.109 +185.103.129.113 +185.103.130.201 +185.103.130.202 +185.103.201.147 +185.103.250.250 +185.104.243.9 +185.105.101.58 +185.105.121.10 +185.105.121.11 +185.105.184.40 +185.105.184.41 +185.105.184.42 +185.105.184.52 +185.105.184.53 +185.105.184.70 +185.105.184.76 +185.105.184.89 +185.105.184.91 +185.105.184.108 +185.105.184.121 +185.105.184.205 +185.105.184.206 +185.105.184.227 +185.105.185.25 +185.105.185.28 +185.105.185.36 +185.105.185.41 +185.105.185.149 +185.105.185.151 +185.105.185.155 +185.105.187.14 +185.105.187.160 +185.105.187.186 +185.105.187.189 +185.105.187.226 +185.105.187.242 +185.105.187.243 +185.105.237.96 +185.105.237.252 +185.105.237.253 +185.105.238.196 +185.105.238.200 +185.105.238.210 +185.105.238.253 +185.105.239.10 +185.105.239.11 +185.105.239.29 +185.105.239.50 +185.105.239.68 +185.105.239.78 +185.105.239.79 +185.105.239.97 +185.105.239.102 +185.105.239.104 +185.105.239.107 +185.105.239.115 +185.105.239.119 +185.105.239.123 +185.105.239.129 +185.105.239.132 +185.105.239.139 +185.105.239.141 +185.105.239.169 +185.105.239.172 +185.105.239.175 +185.105.239.178 +185.105.239.187 +185.105.239.189 +185.105.239.191 +185.105.239.194 +185.105.239.198 +185.105.239.201 +185.105.239.208 +185.105.239.211 +185.105.239.214 +185.105.239.216 +185.105.239.217 +185.105.239.220 +185.105.239.231 +185.105.239.232 +185.105.239.236 +185.106.144.149 +185.106.146.179 +185.106.146.180 +185.107.32.115 +185.107.32.117 +185.107.32.120 +185.107.32.122 +185.109.61.27 +185.109.75.241 +185.109.75.242 +185.109.80.137 +185.109.82.15 +185.109.244.91 +185.109.244.94 +185.109.250.245 +185.110.29.251 +185.110.219.23 +185.110.219.139 +185.110.236.10 +185.110.236.120 +185.110.236.122 +185.110.244.7 +185.110.244.23 +185.110.244.98 +185.110.244.105 +185.110.244.112 +185.110.244.120 +185.110.244.150 +185.110.244.151 +185.110.245.16 +185.110.245.19 +185.110.245.26 +185.110.245.30 +185.110.245.83 +185.110.245.94 +185.110.245.100 +185.110.245.140 +185.110.245.178 +185.110.245.187 +185.110.245.197 +185.110.245.213 +185.110.247.9 +185.110.247.11 +185.110.247.16 +185.110.247.63 +185.110.247.72 +185.110.247.93 +185.110.247.101 +185.110.247.110 +185.110.247.118 +185.110.247.154 +185.110.247.179 +185.110.247.195 +185.110.247.213 +185.110.253.2 +185.111.83.58 +185.112.32.43 +185.112.32.45 +185.112.32.52 +185.112.32.56 +185.112.32.62 +185.112.32.212 +185.112.33.51 +185.112.33.54 +185.112.33.60 +185.112.33.107 +185.112.33.110 +185.112.33.219 +185.112.33.220 +185.112.33.221 +185.112.33.222 +185.112.34.83 +185.112.34.92 +185.112.34.139 +185.112.34.181 +185.112.34.235 +185.112.34.236 +185.112.35.2 +185.112.35.20 +185.112.35.38 +185.112.35.194 +185.112.35.195 +185.112.35.196 +185.112.35.197 +185.112.39.18 +185.112.39.33 +185.112.39.112 +185.112.149.98 +185.112.149.116 +185.112.149.117 +185.112.149.119 +185.112.150.7 +185.112.150.13 +185.112.150.54 +185.112.150.55 +185.112.150.56 +185.112.150.92 +185.112.150.134 +185.112.150.149 +185.112.150.204 +185.112.150.242 +185.112.150.246 +185.112.151.97 +185.112.151.198 +185.112.151.213 +185.112.151.214 +185.112.151.231 +185.112.151.232 +185.112.151.249 +185.112.151.254 +185.113.9.3 +185.113.9.5 +185.113.9.6 +185.113.9.7 +185.113.9.9 +185.113.9.10 +185.113.9.13 +185.113.9.16 +185.113.9.18 +185.113.9.20 +185.113.9.22 +185.113.9.24 +185.113.9.33 +185.113.9.34 +185.113.9.35 +185.113.9.37 +185.113.9.38 +185.113.9.43 +185.113.9.44 +185.113.9.45 +185.113.9.46 +185.113.9.47 +185.113.9.48 +185.113.9.49 +185.113.9.50 +185.113.9.52 +185.113.9.53 +185.113.9.54 +185.113.9.55 +185.113.9.57 +185.113.9.61 +185.113.9.65 +185.113.9.66 +185.113.9.68 +185.113.9.71 +185.113.9.73 +185.113.9.74 +185.113.9.75 +185.113.9.76 +185.113.9.77 +185.113.9.80 +185.113.9.82 +185.113.9.86 +185.113.9.87 +185.113.9.88 +185.113.9.90 +185.113.9.91 +185.113.9.92 +185.113.9.94 +185.113.9.97 +185.113.9.102 +185.113.9.106 +185.113.9.108 +185.113.9.110 +185.113.9.111 +185.113.9.118 +185.113.9.120 +185.113.9.122 +185.113.9.123 +185.113.9.125 +185.113.9.133 +185.113.9.135 +185.113.9.144 +185.113.9.151 +185.113.9.157 +185.113.9.162 +185.113.9.163 +185.113.9.165 +185.113.9.170 +185.113.9.171 +185.113.9.172 +185.113.9.173 +185.113.9.174 +185.113.9.176 +185.113.9.178 +185.113.9.181 +185.113.9.186 +185.113.9.189 +185.113.9.190 +185.113.9.194 +185.113.9.196 +185.113.9.197 +185.113.9.205 +185.113.9.210 +185.113.9.213 +185.113.9.214 +185.113.9.215 +185.113.9.216 +185.113.9.220 +185.113.9.222 +185.113.9.225 +185.113.9.226 +185.113.9.229 +185.113.9.231 +185.113.9.236 +185.113.9.237 +185.113.9.238 +185.113.9.239 +185.113.9.241 +185.113.9.242 +185.113.9.245 +185.113.9.251 +185.113.56.68 +185.113.56.123 +185.113.56.124 +185.113.56.126 +185.113.59.131 +185.113.59.181 +185.113.115.23 +185.113.115.33 +185.113.115.34 +185.114.188.20 +185.114.188.34 +185.115.168.163 +185.116.20.54 +185.116.23.23 +185.116.24.2 +185.116.24.100 +185.116.112.12 +185.116.160.10 +185.116.160.18 +185.116.160.19 +185.116.160.22 +185.116.160.26 +185.116.160.27 +185.116.160.28 +185.116.160.32 +185.116.160.38 +185.116.160.39 +185.116.160.41 +185.116.160.44 +185.116.160.51 +185.116.160.55 +185.116.160.68 +185.116.160.83 +185.116.160.91 +185.116.160.107 +185.116.160.110 +185.116.160.111 +185.116.160.113 +185.116.160.121 +185.116.160.130 +185.116.160.132 +185.116.160.139 +185.116.160.140 +185.116.160.141 +185.116.160.142 +185.116.160.154 +185.116.160.233 +185.116.161.5 +185.116.161.7 +185.116.161.15 +185.116.161.24 +185.116.161.32 +185.116.161.34 +185.116.161.39 +185.116.161.42 +185.116.161.49 +185.116.161.59 +185.116.161.60 +185.116.161.61 +185.116.161.67 +185.116.161.80 +185.116.161.87 +185.116.161.89 +185.116.161.90 +185.116.161.93 +185.116.161.94 +185.116.161.95 +185.116.161.96 +185.116.161.98 +185.116.161.99 +185.116.161.103 +185.116.161.105 +185.116.161.106 +185.116.161.109 +185.116.161.116 +185.116.161.125 +185.116.161.126 +185.116.161.135 +185.116.161.156 +185.116.161.157 +185.116.161.167 +185.116.161.184 +185.116.161.230 +185.116.161.231 +185.116.161.232 +185.116.161.248 +185.116.162.11 +185.116.162.16 +185.116.162.20 +185.116.162.22 +185.116.162.23 +185.116.162.24 +185.116.163.1 +185.116.163.2 +185.116.163.4 +185.116.163.6 +185.116.163.12 +185.116.163.14 +185.116.163.16 +185.116.163.18 +185.116.163.19 +185.116.163.20 +185.116.163.21 +185.116.163.24 +185.116.163.27 +185.116.163.31 +185.116.163.43 +185.116.163.46 +185.116.163.49 +185.116.163.56 +185.116.163.61 +185.116.163.63 +185.116.163.64 +185.116.163.69 +185.116.163.73 +185.116.163.78 +185.116.163.101 +185.116.163.102 +185.116.163.103 +185.116.163.115 +185.116.163.117 +185.116.163.123 +185.116.163.144 +185.116.163.149 +185.116.163.150 +185.116.163.161 +185.116.163.180 +185.116.163.184 +185.116.163.186 +185.116.163.204 +185.116.163.215 +185.116.163.225 +185.117.139.47 +185.117.139.77 +185.118.14.244 +185.118.152.195 +185.118.152.197 +185.119.4.216 +185.119.5.15 +185.119.5.16 +185.119.241.241 +185.120.160.3 +185.120.160.4 +185.120.160.5 +185.120.160.6 +185.120.162.76 +185.120.168.30 +185.120.201.133 +185.120.201.141 +185.120.217.242 +185.120.220.29 +185.120.220.75 +185.120.220.76 +185.120.220.77 +185.120.220.110 +185.120.220.114 +185.120.220.139 +185.120.220.164 +185.120.220.166 +185.120.220.179 +185.120.220.180 +185.120.220.181 +185.120.220.182 +185.120.220.188 +185.120.220.200 +185.120.221.44 +185.120.221.46 +185.120.221.106 +185.120.221.108 +185.120.221.110 +185.120.221.195 +185.120.221.244 +185.120.221.247 +185.120.221.248 +185.120.221.250 +185.120.221.252 +185.120.221.253 +185.120.221.254 +185.120.222.106 +185.120.222.107 +185.120.222.108 +185.120.222.109 +185.120.222.110 +185.120.222.132 +185.120.222.133 +185.120.222.135 +185.120.222.137 +185.120.222.140 +185.120.222.141 +185.120.222.142 +185.120.222.144 +185.120.222.146 +185.120.222.147 +185.120.222.148 +185.120.222.149 +185.120.222.151 +185.120.222.152 +185.120.222.153 +185.120.222.154 +185.120.222.155 +185.120.222.156 +185.120.222.157 +185.120.222.158 +185.120.222.159 +185.120.222.160 +185.120.222.162 +185.120.222.163 +185.120.222.164 +185.120.222.167 +185.120.222.168 +185.120.222.169 +185.120.222.171 +185.120.222.172 +185.120.222.173 +185.120.222.175 +185.120.222.177 +185.120.222.178 +185.120.222.179 +185.120.222.180 +185.120.222.181 +185.120.222.182 +185.120.222.183 +185.120.222.186 +185.120.222.187 +185.120.222.188 +185.120.222.190 +185.120.222.191 +185.120.222.192 +185.120.222.193 +185.120.222.194 +185.120.222.195 +185.120.222.196 +185.120.222.197 +185.120.222.199 +185.120.222.200 +185.120.222.201 +185.120.222.202 +185.120.222.203 +185.120.222.205 +185.120.222.207 +185.120.222.208 +185.120.222.210 +185.120.222.212 +185.120.222.213 +185.120.222.214 +185.120.222.215 +185.120.222.216 +185.120.222.220 +185.120.222.221 +185.120.222.222 +185.120.222.223 +185.120.222.224 +185.120.222.225 +185.120.222.226 +185.120.222.227 +185.120.222.228 +185.120.222.236 +185.120.222.237 +185.120.222.238 +185.120.222.240 +185.120.222.241 +185.120.222.243 +185.120.222.244 +185.120.222.246 +185.120.222.249 +185.120.222.250 +185.120.222.252 +185.120.239.45 +185.120.248.116 +185.120.250.9 +185.121.130.187 +185.122.80.68 +185.122.81.4 +185.123.68.2 +185.123.68.36 +185.124.112.19 +185.124.112.25 +185.124.115.18 +185.124.157.91 +185.124.172.8 +185.124.172.9 +185.124.174.19 +185.126.2.239 +185.126.3.244 +185.126.4.18 +185.126.4.52 +185.126.4.53 +185.126.4.54 +185.126.4.55 +185.126.4.59 +185.126.4.61 +185.126.4.70 +185.126.4.104 +185.126.4.130 +185.126.4.133 +185.126.4.134 +185.126.4.135 +185.126.4.222 +185.126.5.50 +185.126.5.62 +185.126.6.204 +185.126.6.206 +185.126.6.219 +185.126.6.220 +185.126.6.234 +185.126.6.235 +185.126.7.6 +185.126.7.7 +185.126.7.8 +185.126.7.18 +185.126.7.20 +185.126.8.35 +185.126.8.52 +185.126.8.58 +185.126.8.77 +185.126.8.158 +185.126.8.234 +185.126.9.181 +185.126.9.188 +185.126.9.189 +185.126.10.58 +185.126.10.100 +185.126.10.130 +185.126.10.131 +185.126.10.133 +185.126.10.135 +185.126.10.139 +185.126.10.141 +185.126.10.182 +185.126.10.220 +185.126.10.221 +185.126.10.222 +185.126.11.84 +185.126.11.130 +185.126.11.131 +185.126.11.132 +185.126.11.133 +185.126.11.135 +185.126.11.142 +185.126.11.211 +185.126.11.212 +185.126.11.213 +185.126.11.219 +185.126.11.221 +185.126.14.11 +185.126.14.12 +185.126.14.20 +185.126.14.21 +185.126.14.99 +185.126.14.100 +185.126.14.139 +185.126.14.210 +185.126.14.243 +185.126.14.246 +185.126.14.248 +185.126.15.215 +185.126.16.27 +185.126.16.55 +185.126.40.242 +185.126.133.37 +185.126.200.14 +185.126.200.21 +185.126.200.32 +185.126.200.39 +185.126.200.45 +185.126.200.65 +185.126.200.127 +185.126.200.159 +185.126.200.200 +185.126.200.212 +185.126.201.29 +185.126.201.183 +185.126.201.198 +185.126.201.246 +185.126.202.44 +185.126.202.52 +185.126.202.62 +185.126.202.99 +185.126.202.100 +185.126.202.110 +185.126.202.120 +185.126.202.122 +185.126.202.124 +185.126.202.156 +185.126.202.175 +185.126.202.235 +185.126.202.236 +185.126.202.247 +185.126.203.22 +185.126.203.23 +185.126.203.24 +185.126.203.61 +185.126.203.103 +185.126.203.147 +185.126.203.148 +185.126.203.150 +185.126.203.178 +185.126.203.210 +185.126.203.211 +185.126.203.252 +185.128.80.27 +185.128.80.28 +185.128.80.30 +185.128.80.33 +185.128.80.34 +185.128.80.35 +185.128.80.39 +185.128.80.40 +185.128.80.41 +185.128.80.42 +185.128.80.46 +185.128.80.48 +185.128.80.50 +185.128.80.53 +185.128.80.65 +185.128.80.79 +185.128.80.81 +185.128.81.35 +185.128.81.37 +185.128.81.45 +185.128.81.60 +185.128.81.70 +185.128.81.78 +185.128.81.80 +185.128.81.90 +185.128.81.95 +185.128.81.100 +185.128.81.101 +185.128.81.120 +185.128.81.125 +185.128.81.130 +185.128.81.135 +185.128.81.140 +185.128.81.155 +185.128.81.160 +185.128.81.193 +185.128.81.220 +185.128.82.5 +185.128.82.90 +185.128.83.10 +185.128.136.7 +185.128.136.48 +185.128.136.49 +185.128.136.60 +185.128.136.163 +185.128.136.167 +185.128.136.169 +185.128.136.185 +185.128.136.186 +185.128.136.188 +185.128.137.154 +185.128.137.187 +185.128.137.192 +185.128.137.193 +185.128.137.212 +185.128.138.1 +185.128.138.13 +185.128.138.14 +185.128.138.129 +185.128.138.130 +185.128.138.132 +185.128.138.142 +185.128.138.172 +185.128.138.174 +185.128.138.196 +185.128.138.199 +185.128.138.206 +185.128.138.214 +185.128.138.218 +185.128.138.249 +185.128.138.250 +185.128.138.251 +185.128.139.26 +185.128.139.29 +185.128.139.45 +185.128.139.48 +185.128.139.57 +185.128.139.61 +185.128.139.65 +185.128.139.84 +185.128.139.116 +185.128.139.119 +185.128.139.149 +185.128.139.168 +185.128.139.188 +185.128.139.194 +185.128.139.216 +185.128.139.222 +185.129.83.20 +185.129.83.32 +185.129.111.248 +185.129.116.109 +185.129.116.115 +185.129.116.147 +185.129.116.242 +185.129.117.204 +185.129.118.51 +185.129.118.62 +185.129.118.72 +185.129.118.125 +185.129.118.207 +185.129.119.12 +185.129.119.36 +185.129.119.55 +185.129.119.59 +185.129.119.105 +185.129.119.202 +185.129.119.210 +185.129.168.15 +185.129.168.16 +185.129.168.27 +185.129.168.28 +185.129.168.31 +185.129.168.35 +185.129.168.50 +185.129.168.51 +185.129.168.53 +185.129.168.60 +185.129.168.62 +185.129.168.65 +185.129.168.68 +185.129.168.72 +185.129.168.75 +185.129.168.81 +185.129.168.85 +185.129.168.86 +185.129.168.90 +185.129.168.97 +185.129.168.99 +185.129.168.104 +185.129.168.114 +185.129.168.120 +185.129.168.125 +185.129.168.128 +185.129.168.132 +185.129.168.133 +185.129.168.139 +185.129.168.143 +185.129.168.144 +185.129.168.148 +185.129.168.188 +185.129.168.191 +185.129.168.192 +185.129.168.194 +185.129.168.217 +185.129.168.220 +185.129.168.229 +185.129.168.232 +185.129.168.233 +185.129.168.241 +185.129.168.249 +185.129.168.250 +185.129.168.251 +185.129.168.253 +185.129.169.21 +185.129.169.22 +185.129.169.25 +185.129.169.26 +185.129.169.27 +185.129.169.35 +185.129.169.37 +185.129.169.40 +185.129.169.44 +185.129.169.49 +185.129.169.53 +185.129.169.65 +185.129.169.71 +185.129.169.76 +185.129.169.79 +185.129.169.84 +185.129.169.88 +185.129.169.89 +185.129.169.107 +185.129.169.109 +185.129.169.110 +185.129.169.111 +185.129.169.112 +185.129.169.121 +185.129.169.125 +185.129.169.130 +185.129.169.136 +185.129.169.138 +185.129.169.145 +185.129.169.147 +185.129.169.149 +185.129.169.156 +185.129.169.163 +185.129.169.175 +185.129.169.187 +185.129.169.196 +185.129.169.198 +185.129.169.199 +185.129.169.206 +185.129.169.208 +185.129.169.209 +185.129.169.210 +185.129.169.212 +185.129.169.231 +185.129.169.238 +185.129.169.240 +185.129.169.241 +185.129.169.243 +185.129.169.244 +185.129.169.245 +185.129.169.247 +185.129.169.248 +185.129.169.250 +185.129.169.251 +185.129.169.253 +185.129.170.31 +185.129.170.46 +185.129.170.75 +185.129.170.77 +185.129.170.89 +185.129.170.90 +185.129.170.100 +185.129.170.116 +185.129.170.138 +185.129.170.150 +185.129.171.10 +185.129.171.15 +185.129.171.22 +185.129.171.56 +185.129.171.57 +185.129.171.61 +185.129.171.75 +185.129.171.80 +185.129.171.83 +185.129.171.89 +185.129.171.93 +185.129.171.102 +185.129.171.117 +185.129.171.127 +185.129.197.4 +185.129.203.240 +185.129.230.35 +185.130.50.5 +185.130.50.6 +185.130.50.7 +185.130.50.8 +185.130.50.9 +185.130.50.10 +185.130.50.15 +185.130.50.19 +185.130.50.37 +185.130.50.38 +185.130.50.39 +185.130.50.40 +185.130.50.41 +185.130.50.42 +185.130.50.43 +185.130.50.44 +185.130.50.45 +185.130.50.46 +185.130.50.47 +185.130.50.50 +185.130.50.51 +185.130.50.63 +185.130.50.64 +185.130.50.65 +185.130.50.66 +185.130.50.68 +185.130.50.69 +185.130.50.70 +185.130.50.71 +185.130.50.72 +185.130.50.77 +185.130.50.78 +185.130.50.80 +185.130.50.81 +185.130.50.83 +185.130.50.84 +185.130.50.85 +185.130.50.89 +185.130.50.91 +185.130.50.93 +185.130.50.97 +185.130.50.99 +185.130.50.100 +185.130.50.101 +185.130.50.109 +185.130.50.110 +185.130.50.112 +185.130.50.115 +185.130.50.116 +185.130.50.117 +185.130.50.118 +185.130.50.119 +185.130.50.133 +185.130.50.139 +185.130.50.140 +185.130.50.143 +185.130.50.144 +185.130.50.146 +185.130.50.149 +185.130.50.150 +185.130.50.160 +185.130.50.166 +185.130.50.169 +185.130.50.171 +185.130.50.180 +185.130.50.184 +185.130.50.188 +185.130.50.192 +185.130.50.201 +185.130.50.222 +185.130.50.224 +185.130.50.233 +185.130.50.239 +185.130.50.242 +185.130.50.251 +185.130.76.2 +185.130.76.4 +185.130.76.72 +185.130.76.74 +185.130.78.123 +185.130.78.142 +185.130.79.21 +185.130.79.253 +185.131.28.254 +185.131.92.31 +185.131.116.150 +185.131.116.173 +185.132.124.53 +185.132.124.198 +185.132.124.199 +185.132.212.29 +185.132.212.129 +185.132.212.130 +185.132.212.135 +185.132.212.136 +185.132.213.19 +185.132.213.61 +185.132.213.62 +185.132.213.63 +185.132.213.64 +185.134.97.210 +185.135.47.10 +185.135.47.11 +185.135.47.12 +185.135.47.13 +185.135.47.14 +185.135.47.15 +185.135.47.16 +185.135.47.17 +185.135.47.18 +185.135.47.21 +185.135.47.22 +185.135.47.23 +185.135.47.24 +185.135.47.25 +185.135.47.30 +185.135.47.52 +185.135.47.57 +185.135.47.60 +185.135.47.61 +185.135.47.64 +185.135.47.74 +185.135.47.107 +185.135.47.108 +185.135.47.120 +185.136.133.72 +185.136.180.2 +185.136.181.99 +185.136.181.105 +185.136.181.197 +185.137.24.10 +185.137.24.18 +185.137.24.83 +185.137.24.100 +185.137.24.165 +185.137.24.202 +185.137.24.206 +185.137.24.245 +185.137.25.41 +185.137.25.72 +185.137.25.74 +185.137.25.78 +185.137.25.79 +185.137.25.102 +185.137.25.146 +185.137.25.213 +185.137.25.214 +185.137.25.215 +185.137.25.216 +185.137.25.223 +185.137.25.234 +185.137.26.14 +185.137.26.50 +185.137.26.58 +185.137.26.165 +185.137.26.184 +185.137.26.185 +185.137.26.186 +185.137.26.189 +185.137.26.190 +185.137.26.197 +185.137.26.227 +185.137.26.237 +185.137.26.243 +185.137.26.244 +185.137.27.3 +185.137.27.4 +185.137.27.22 +185.137.27.27 +185.137.27.40 +185.137.27.42 +185.137.27.43 +185.137.27.44 +185.137.27.45 +185.137.27.46 +185.137.27.86 +185.137.27.138 +185.137.27.164 +185.137.27.179 +185.137.27.188 +185.137.27.217 +185.137.27.227 +185.139.64.9 +185.139.64.21 +185.140.4.4 +185.140.4.5 +185.140.5.127 +185.140.5.232 +185.140.5.244 +185.140.240.238 +185.141.36.54 +185.141.36.82 +185.141.48.53 +185.141.48.54 +185.141.104.69 +185.141.104.107 +185.141.104.118 +185.141.104.126 +185.141.104.136 +185.141.104.141 +185.141.104.142 +185.141.104.211 +185.141.104.242 +185.141.105.27 +185.141.105.100 +185.141.105.105 +185.141.105.107 +185.141.105.109 +185.141.105.113 +185.141.105.119 +185.141.105.138 +185.141.105.139 +185.141.105.217 +185.141.105.230 +185.141.105.232 +185.141.105.233 +185.141.106.39 +185.141.106.64 +185.141.106.68 +185.141.106.71 +185.141.106.82 +185.141.106.84 +185.141.106.117 +185.141.106.121 +185.141.106.126 +185.141.106.149 +185.141.106.150 +185.141.106.153 +185.141.106.154 +185.141.106.169 +185.141.106.225 +185.141.106.228 +185.141.106.230 +185.141.106.231 +185.141.106.232 +185.141.106.238 +185.141.106.240 +185.141.106.243 +185.141.107.17 +185.141.107.27 +185.141.107.38 +185.141.107.50 +185.141.107.51 +185.141.107.163 +185.141.107.197 +185.141.107.228 +185.141.107.244 +185.141.132.11 +185.141.132.23 +185.141.132.44 +185.141.132.45 +185.141.132.46 +185.141.132.47 +185.141.132.60 +185.141.132.89 +185.141.132.92 +185.141.132.95 +185.141.132.127 +185.141.132.216 +185.141.132.218 +185.141.133.3 +185.141.133.5 +185.141.133.6 +185.141.133.18 +185.141.133.19 +185.141.133.28 +185.141.133.50 +185.141.133.66 +185.141.133.67 +185.141.133.76 +185.141.133.120 +185.141.133.145 +185.141.133.216 +185.141.133.217 +185.141.133.218 +185.141.134.49 +185.141.134.64 +185.141.134.68 +185.141.134.74 +185.141.134.77 +185.141.134.88 +185.141.134.93 +185.141.134.114 +185.141.134.151 +185.141.134.191 +185.141.134.206 +185.141.134.221 +185.141.135.113 +185.141.135.115 +185.141.168.117 +185.141.168.122 +185.141.168.123 +185.141.168.124 +185.141.168.130 +185.141.168.133 +185.141.168.134 +185.141.168.135 +185.141.168.136 +185.141.168.137 +185.141.168.138 +185.141.168.139 +185.141.168.140 +185.141.168.141 +185.141.169.42 +185.141.169.155 +185.141.169.220 +185.141.169.226 +185.141.169.227 +185.141.169.228 +185.141.169.229 +185.141.169.230 +185.141.169.231 +185.141.169.232 +185.141.169.234 +185.141.169.235 +185.141.169.236 +185.141.169.237 +185.141.169.238 +185.141.169.239 +185.141.169.241 +185.141.169.242 +185.141.169.243 +185.141.169.244 +185.141.169.245 +185.141.169.246 +185.141.169.247 +185.141.170.2 +185.141.170.74 +185.141.170.99 +185.141.170.132 +185.141.170.195 +185.141.171.30 +185.141.212.11 +185.141.212.139 +185.141.212.140 +185.141.212.141 +185.141.212.142 +185.141.212.170 +185.141.212.171 +185.141.212.172 +185.141.212.173 +185.141.212.174 +185.141.213.4 +185.141.213.5 +185.141.213.12 +185.141.213.61 +185.141.213.133 +185.141.213.166 +185.141.213.167 +185.141.213.172 +185.141.213.180 +185.141.213.181 +185.141.213.244 +185.141.213.248 +185.141.213.249 +185.141.213.250 +185.141.213.251 +185.141.214.71 +185.142.95.68 +185.142.95.69 +185.142.156.11 +185.142.156.12 +185.142.156.85 +185.142.156.214 +185.142.156.227 +185.142.156.235 +185.142.157.5 +185.142.157.12 +185.142.157.29 +185.142.157.37 +185.142.157.39 +185.142.157.53 +185.142.157.75 +185.142.157.81 +185.142.157.95 +185.142.157.96 +185.142.157.119 +185.142.157.120 +185.142.157.121 +185.142.157.123 +185.142.157.124 +185.142.157.151 +185.142.157.168 +185.142.157.231 +185.142.157.246 +185.142.157.251 +185.142.158.28 +185.142.158.98 +185.142.158.99 +185.142.158.140 +185.142.158.141 +185.142.158.162 +185.142.159.172 +185.142.159.195 +185.142.159.210 +185.142.159.212 +185.142.159.229 +185.142.159.230 +185.142.159.232 +185.142.159.234 +185.142.159.235 +185.142.159.243 +185.142.159.251 +185.143.207.131 +185.143.207.246 +185.143.232.253 +185.143.233.253 +185.143.234.253 +185.143.235.253 +185.144.65.6 +185.145.11.246 +185.145.186.32 +185.145.186.130 +185.147.40.115 +185.147.40.160 +185.147.84.105 +185.147.85.105 +185.147.160.21 +185.147.160.22 +185.147.160.107 +185.147.160.109 +185.147.161.170 +185.147.162.26 +185.147.162.68 +185.147.162.130 +185.147.162.132 +185.147.162.195 +185.147.163.46 +185.147.163.62 +185.147.163.168 +185.147.163.169 +185.147.163.196 +185.147.163.202 +185.147.176.189 +185.147.176.221 +185.147.177.111 +185.147.177.112 +185.147.177.113 +185.147.177.224 +185.147.177.232 +185.149.192.18 +185.149.192.28 +185.149.192.30 +185.149.192.32 +185.149.192.33 +185.149.192.47 +185.149.192.54 +185.149.192.132 +185.149.192.133 +185.149.192.134 +185.149.192.150 +185.149.192.155 +185.149.192.218 +185.153.208.77 +185.155.9.40 +185.155.14.130 +185.155.15.140 +185.155.236.2 +185.155.236.3 +185.155.236.4 +185.158.172.3 +185.158.173.8 +185.158.173.165 +185.159.153.2 +185.159.153.3 +185.159.153.5 +185.159.153.6 +185.159.153.10 +185.159.153.12 +185.159.153.13 +185.159.153.15 +185.159.153.16 +185.159.153.17 +185.159.153.19 +185.159.153.20 +185.159.153.21 +185.159.153.23 +185.159.153.24 +185.159.153.25 +185.159.153.26 +185.159.153.27 +185.159.153.29 +185.159.153.30 +185.159.153.31 +185.159.153.32 +185.159.153.33 +185.159.153.34 +185.159.153.35 +185.159.153.36 +185.159.153.38 +185.159.153.39 +185.159.153.40 +185.159.153.42 +185.159.153.44 +185.159.153.45 +185.159.153.46 +185.159.153.47 +185.159.153.48 +185.159.153.50 +185.159.153.51 +185.159.153.52 +185.159.153.53 +185.159.153.54 +185.159.153.55 +185.159.153.56 +185.159.153.57 +185.159.153.58 +185.159.153.59 +185.159.153.60 +185.159.153.61 +185.159.153.64 +185.159.153.66 +185.159.153.67 +185.159.153.68 +185.159.153.69 +185.159.153.70 +185.159.153.71 +185.159.153.72 +185.159.153.73 +185.159.153.74 +185.159.153.75 +185.159.153.77 +185.159.153.78 +185.159.153.79 +185.159.153.80 +185.159.153.81 +185.159.153.82 +185.159.153.83 +185.159.153.84 +185.159.153.85 +185.159.153.86 +185.159.153.87 +185.159.153.88 +185.159.153.89 +185.159.153.90 +185.159.153.91 +185.159.153.92 +185.159.153.93 +185.159.153.98 +185.159.153.99 +185.159.153.100 +185.159.153.102 +185.159.153.103 +185.159.153.104 +185.159.153.105 +185.159.153.106 +185.159.153.108 +185.159.153.110 +185.159.153.111 +185.159.153.112 +185.159.153.113 +185.159.153.114 +185.159.153.115 +185.159.153.116 +185.159.153.119 +185.159.153.120 +185.159.153.121 +185.159.153.122 +185.159.153.123 +185.159.153.124 +185.159.153.125 +185.159.153.126 +185.159.153.127 +185.159.153.128 +185.159.153.129 +185.159.153.131 +185.159.153.132 +185.159.153.133 +185.159.153.134 +185.159.153.135 +185.159.153.136 +185.159.153.137 +185.159.153.140 +185.159.153.141 +185.159.153.142 +185.159.153.145 +185.159.153.147 +185.159.153.148 +185.159.153.149 +185.159.153.150 +185.159.153.151 +185.159.153.152 +185.159.153.153 +185.159.153.154 +185.159.153.156 +185.159.153.157 +185.159.153.158 +185.159.153.159 +185.159.153.160 +185.159.153.161 +185.159.153.162 +185.159.153.163 +185.159.153.165 +185.159.153.166 +185.159.153.171 +185.159.153.172 +185.159.153.174 +185.159.153.175 +185.159.153.176 +185.159.153.177 +185.159.153.186 +185.159.153.187 +185.159.153.189 +185.159.153.191 +185.159.153.193 +185.159.153.194 +185.159.153.196 +185.159.153.199 +185.159.153.200 +185.159.153.201 +185.159.153.202 +185.159.153.203 +185.159.153.204 +185.159.153.205 +185.159.153.207 +185.159.153.208 +185.159.153.211 +185.159.153.214 +185.159.153.216 +185.159.153.218 +185.159.153.219 +185.159.153.220 +185.159.153.226 +185.159.153.227 +185.159.153.228 +185.159.153.235 +185.159.153.253 +185.159.154.2 +185.159.154.3 +185.159.154.4 +185.159.154.5 +185.159.154.7 +185.159.154.9 +185.159.154.10 +185.159.154.14 +185.159.154.16 +185.159.154.17 +185.159.154.19 +185.159.154.20 +185.159.154.21 +185.159.154.22 +185.159.154.23 +185.159.154.25 +185.159.154.26 +185.159.154.27 +185.159.154.28 +185.159.154.29 +185.159.154.30 +185.159.154.31 +185.159.154.32 +185.159.154.33 +185.159.154.34 +185.159.154.35 +185.159.154.36 +185.159.154.37 +185.159.154.38 +185.159.154.39 +185.159.154.40 +185.159.154.41 +185.159.154.42 +185.159.154.43 +185.159.154.44 +185.159.154.45 +185.159.154.46 +185.159.154.47 +185.159.154.48 +185.159.154.49 +185.159.154.50 +185.159.154.51 +185.159.154.52 +185.159.154.53 +185.159.154.54 +185.159.154.55 +185.159.154.56 +185.159.154.57 +185.159.154.61 +185.159.154.63 +185.159.154.64 +185.159.154.65 +185.159.154.66 +185.159.154.67 +185.159.154.68 +185.159.154.69 +185.159.154.70 +185.159.154.71 +185.159.154.72 +185.159.154.73 +185.159.154.74 +185.159.154.75 +185.159.154.79 +185.159.154.81 +185.159.154.82 +185.159.154.84 +185.159.154.85 +185.159.154.86 +185.159.154.87 +185.159.154.88 +185.159.154.89 +185.159.154.90 +185.159.154.91 +185.159.154.92 +185.159.154.93 +185.159.154.94 +185.159.154.98 +185.159.154.99 +185.159.154.100 +185.159.154.102 +185.159.154.104 +185.159.154.105 +185.159.154.106 +185.159.154.107 +185.159.154.108 +185.159.154.109 +185.159.154.111 +185.159.154.114 +185.159.154.115 +185.159.154.118 +185.159.154.120 +185.159.154.124 +185.159.154.125 +185.159.154.127 +185.159.154.128 +185.159.154.130 +185.159.154.132 +185.159.154.133 +185.159.154.134 +185.159.154.137 +185.159.154.143 +185.159.154.147 +185.159.154.149 +185.159.154.150 +185.159.154.151 +185.159.154.152 +185.159.154.153 +185.159.154.154 +185.159.154.157 +185.159.154.159 +185.159.154.161 +185.159.154.162 +185.159.154.163 +185.159.154.164 +185.159.154.165 +185.159.154.167 +185.159.154.168 +185.159.154.169 +185.159.154.170 +185.159.154.171 +185.159.154.172 +185.159.154.173 +185.159.154.177 +185.159.154.178 +185.159.154.179 +185.159.154.180 +185.159.154.181 +185.159.154.188 +185.159.154.189 +185.159.154.190 +185.159.154.191 +185.159.154.192 +185.159.154.197 +185.159.154.198 +185.159.154.199 +185.159.154.200 +185.159.154.201 +185.159.154.202 +185.159.154.204 +185.159.154.207 +185.159.154.208 +185.159.154.209 +185.159.154.210 +185.159.154.211 +185.159.154.212 +185.159.154.213 +185.159.154.214 +185.159.154.215 +185.159.154.219 +185.159.154.221 +185.159.154.222 +185.159.154.224 +185.159.154.225 +185.159.154.226 +185.159.154.227 +185.159.154.228 +185.159.154.242 +185.159.154.245 +185.159.154.249 +185.159.154.250 +185.159.154.251 +185.159.154.252 +185.159.154.253 +185.159.155.2 +185.159.155.3 +185.159.179.1 +185.159.179.2 +185.159.179.71 +185.159.179.72 +185.159.179.74 +185.159.179.75 +185.159.179.80 +185.160.104.84 +185.160.105.54 +185.160.205.18 +185.161.39.242 +185.161.112.33 +185.161.112.34 +185.161.112.35 +185.161.112.247 +185.161.113.253 +185.161.121.152 +185.164.72.4 +185.164.72.5 +185.164.72.7 +185.164.72.10 +185.164.72.14 +185.164.72.18 +185.164.72.23 +185.164.72.24 +185.164.72.25 +185.164.72.27 +185.164.72.36 +185.164.72.44 +185.164.72.46 +185.164.72.53 +185.164.72.56 +185.164.72.62 +185.164.72.70 +185.164.72.71 +185.164.72.72 +185.164.72.77 +185.164.72.82 +185.164.72.83 +185.164.72.85 +185.164.72.89 +185.164.72.92 +185.164.72.97 +185.164.72.99 +185.164.72.107 +185.164.72.108 +185.164.72.118 +185.164.72.119 +185.164.72.128 +185.164.72.131 +185.164.72.135 +185.164.72.140 +185.164.72.142 +185.164.72.149 +185.164.72.157 +185.164.72.160 +185.164.72.170 +185.164.72.173 +185.164.72.174 +185.164.72.179 +185.164.72.185 +185.164.72.194 +185.164.72.195 +185.164.72.199 +185.164.72.215 +185.164.72.219 +185.164.72.225 +185.164.72.227 +185.164.72.237 +185.164.72.249 +185.164.72.252 +185.164.73.9 +185.164.73.14 +185.164.73.18 +185.164.73.21 +185.164.73.22 +185.164.73.30 +185.164.73.34 +185.164.73.38 +185.164.73.47 +185.164.73.49 +185.164.73.53 +185.164.73.57 +185.164.73.64 +185.164.73.65 +185.164.73.73 +185.164.73.75 +185.164.73.76 +185.164.73.81 +185.164.73.87 +185.164.73.94 +185.164.73.103 +185.164.73.104 +185.164.73.108 +185.164.73.109 +185.164.73.115 +185.164.73.119 +185.164.73.121 +185.164.73.123 +185.164.73.125 +185.164.73.133 +185.164.73.135 +185.164.73.140 +185.164.73.141 +185.164.73.152 +185.164.73.158 +185.164.73.160 +185.164.73.164 +185.164.73.168 +185.164.73.171 +185.164.73.174 +185.164.73.177 +185.164.73.180 +185.164.73.187 +185.164.73.190 +185.164.73.191 +185.164.73.205 +185.164.73.224 +185.164.73.229 +185.164.73.243 +185.164.253.26 +185.165.28.12 +185.165.28.33 +185.165.28.34 +185.165.28.251 +185.165.28.252 +185.165.29.17 +185.165.29.28 +185.165.29.33 +185.165.29.50 +185.165.29.65 +185.165.29.128 +185.165.29.136 +185.165.29.175 +185.165.29.176 +185.165.29.177 +185.165.29.179 +185.165.29.182 +185.165.29.186 +185.165.29.189 +185.165.29.194 +185.165.29.206 +185.165.29.211 +185.165.29.220 +185.165.29.223 +185.165.29.240 +185.165.29.242 +185.165.29.247 +185.165.30.93 +185.165.30.94 +185.165.116.1 +185.165.116.13 +185.165.116.19 +185.165.116.20 +185.165.116.21 +185.165.116.22 +185.165.116.23 +185.165.116.24 +185.165.116.26 +185.165.116.30 +185.165.116.31 +185.165.116.32 +185.165.116.34 +185.165.116.36 +185.165.116.41 +185.165.116.43 +185.165.116.47 +185.165.116.49 +185.165.116.52 +185.165.116.57 +185.165.116.58 +185.165.116.60 +185.165.116.62 +185.165.116.63 +185.165.116.66 +185.165.116.72 +185.165.116.75 +185.165.116.79 +185.165.116.80 +185.165.116.85 +185.165.116.90 +185.165.116.93 +185.165.116.102 +185.165.116.116 +185.165.116.133 +185.165.116.134 +185.165.116.135 +185.165.116.136 +185.165.116.139 +185.165.116.140 +185.165.116.144 +185.165.116.145 +185.165.116.149 +185.165.116.152 +185.165.116.155 +185.165.116.156 +185.165.116.158 +185.165.116.159 +185.165.116.161 +185.165.116.164 +185.165.116.168 +185.165.116.173 +185.165.116.182 +185.165.116.195 +185.165.116.196 +185.165.116.198 +185.165.116.202 +185.165.116.204 +185.165.116.210 +185.165.116.213 +185.165.116.214 +185.165.116.219 +185.165.116.235 +185.165.116.253 +185.165.117.1 +185.165.117.11 +185.165.117.120 +185.165.117.244 +185.165.118.1 +185.165.118.35 +185.165.118.49 +185.165.118.51 +185.165.118.58 +185.165.118.59 +185.165.118.72 +185.165.118.74 +185.165.118.80 +185.165.118.92 +185.165.118.98 +185.165.118.138 +185.165.118.193 +185.165.118.195 +185.165.118.197 +185.165.118.201 +185.165.118.205 +185.165.118.210 +185.165.118.211 +185.165.118.215 +185.165.118.216 +185.165.118.224 +185.165.118.227 +185.165.118.228 +185.165.118.235 +185.165.118.237 +185.165.119.1 +185.165.119.59 +185.165.119.60 +185.165.119.63 +185.166.63.43 +185.166.104.53 +185.166.104.54 +185.167.73.25 +185.167.75.25 +185.167.124.244 +185.169.6.18 +185.169.6.44 +185.169.6.58 +185.169.6.94 +185.169.6.106 +185.169.6.146 +185.169.6.154 +185.169.6.166 +185.169.6.174 +185.169.6.194 +185.169.6.196 +185.169.6.211 +185.169.6.226 +185.169.20.106 +185.169.20.107 +185.169.21.62 +185.170.8.5 +185.170.8.11 +185.170.8.195 +185.170.8.236 +185.170.8.241 +185.170.8.248 +185.170.8.254 +185.170.236.26 +185.170.236.27 +185.170.236.28 +185.170.236.30 +185.171.53.3 +185.171.53.4 +185.171.53.57 +185.171.53.58 +185.171.53.60 +185.171.53.61 +185.171.53.62 +185.171.53.66 +185.171.53.70 +185.171.53.75 +185.171.53.149 +185.171.53.158 +185.171.53.163 +185.171.53.164 +185.171.53.171 +185.171.53.174 +185.171.53.186 +185.172.212.26 +185.172.212.210 +185.172.213.2 +185.172.213.3 +185.172.213.4 +185.172.213.5 +185.172.213.6 +185.172.213.7 +185.172.213.51 +185.173.104.18 +185.173.104.41 +185.173.104.44 +185.173.104.46 +185.173.104.48 +185.173.104.50 +185.173.104.59 +185.173.104.68 +185.173.104.72 +185.173.104.74 +185.173.104.78 +185.173.104.81 +185.173.104.83 +185.173.104.87 +185.173.104.88 +185.173.104.90 +185.173.104.95 +185.173.104.102 +185.173.104.107 +185.173.104.119 +185.173.104.129 +185.173.104.134 +185.173.104.141 +185.173.104.146 +185.173.104.148 +185.173.104.151 +185.173.104.152 +185.173.104.167 +185.173.104.177 +185.173.104.179 +185.173.104.181 +185.173.104.202 +185.173.104.206 +185.173.104.207 +185.173.104.211 +185.173.104.228 +185.173.104.229 +185.173.104.230 +185.173.104.235 +185.173.104.238 +185.173.104.246 +185.173.105.6 +185.173.105.22 +185.173.105.26 +185.173.105.122 +185.173.105.126 +185.173.105.134 +185.173.105.154 +185.173.105.170 +185.173.105.210 +185.173.105.211 +185.173.105.218 +185.173.105.219 +185.173.105.221 +185.173.106.22 +185.173.106.32 +185.173.106.54 +185.173.106.56 +185.173.106.90 +185.173.106.105 +185.173.106.109 +185.173.106.155 +185.173.106.164 +185.173.106.171 +185.173.106.172 +185.173.106.177 +185.173.106.188 +185.173.106.196 +185.173.106.199 +185.173.106.200 +185.173.106.207 +185.173.106.238 +185.173.106.239 +185.173.107.40 +185.173.107.68 +185.173.107.125 +185.173.107.154 +185.173.107.158 +185.173.107.171 +185.173.107.179 +185.173.107.207 +185.173.107.212 +185.173.129.48 +185.173.129.137 +185.173.129.143 +185.173.129.200 +185.173.129.208 +185.173.130.100 +185.173.130.149 +185.173.130.160 +185.173.130.249 +185.173.168.3 +185.173.168.14 +185.173.168.18 +185.173.168.57 +185.173.168.59 +185.173.168.107 +185.173.168.175 +185.173.168.183 +185.173.168.185 +185.173.168.209 +185.173.168.210 +185.173.168.244 +185.173.168.245 +185.173.168.246 +185.173.168.247 +185.173.168.248 +185.173.168.249 +185.173.168.252 +185.173.168.254 +185.173.169.169 +185.173.169.254 +185.173.170.11 +185.173.171.251 +185.173.171.252 +185.173.171.253 +185.173.171.254 +185.174.250.20 +185.174.250.51 +185.174.250.52 +185.174.251.22 +185.175.76.140 +185.176.58.58 +185.177.158.4 +185.178.220.15 +185.178.220.55 +185.179.90.10 +185.179.90.54 +185.179.90.61 +185.179.90.66 +185.179.90.75 +185.179.90.125 +185.179.90.130 +185.179.90.150 +185.179.90.179 +185.179.90.227 +185.179.168.9 +185.179.168.12 +185.179.168.24 +185.179.168.30 +185.179.168.50 +185.179.168.51 +185.179.168.56 +185.179.168.168 +185.179.169.13 +185.179.169.90 +185.179.171.127 +185.179.220.9 +185.180.128.92 +185.180.129.219 +185.181.180.14 +185.181.180.54 +185.181.180.62 +185.181.180.116 +185.181.180.119 +185.181.180.120 +185.181.180.122 +185.181.180.123 +185.181.180.130 +185.181.180.156 +185.181.180.205 +185.181.180.206 +185.181.180.208 +185.181.180.209 +185.181.180.222 +185.181.180.237 +185.181.180.244 +185.181.181.2 +185.181.181.5 +185.181.181.9 +185.181.181.30 +185.181.181.85 +185.181.181.87 +185.181.181.88 +185.181.181.166 +185.181.181.183 +185.181.181.187 +185.181.181.188 +185.181.182.15 +185.181.182.17 +185.181.182.52 +185.181.182.64 +185.181.182.75 +185.181.182.84 +185.181.182.85 +185.181.182.88 +185.181.182.91 +185.181.182.95 +185.181.182.102 +185.181.182.107 +185.181.182.128 +185.181.182.133 +185.181.182.157 +185.181.182.161 +185.181.182.173 +185.181.182.177 +185.181.182.178 +185.181.182.190 +185.181.182.202 +185.181.182.206 +185.181.182.224 +185.181.182.230 +185.181.183.25 +185.181.183.38 +185.181.183.47 +185.181.183.56 +185.181.183.58 +185.181.183.87 +185.181.183.94 +185.181.183.109 +185.181.183.111 +185.181.183.112 +185.181.183.114 +185.181.183.125 +185.181.183.144 +185.181.183.145 +185.181.183.157 +185.181.183.166 +185.182.222.85 +185.182.222.219 +185.185.16.116 +185.185.240.4 +185.186.50.44 +185.186.50.67 +185.186.50.108 +185.186.50.124 +185.186.50.130 +185.186.50.243 +185.186.50.245 +185.186.50.247 +185.186.51.10 +185.186.51.115 +185.186.51.243 +185.186.240.240 +185.186.241.3 +185.186.241.8 +185.186.241.10 +185.186.241.133 +185.186.242.21 +185.186.242.135 +185.186.242.141 +185.186.242.157 +185.186.243.250 +185.187.48.3 +185.187.50.4 +185.187.50.37 +185.187.50.40 +185.187.51.250 +185.187.86.5 +185.187.86.10 +185.188.104.10 +185.188.104.11 +185.188.104.12 +185.188.104.13 +185.188.105.10 +185.188.105.11 +185.188.105.12 +185.188.106.10 +185.188.106.11 +185.190.39.7 +185.190.39.18 +185.190.39.27 +185.190.39.31 +185.190.39.34 +185.190.39.40 +185.190.39.46 +185.190.39.63 +185.190.39.69 +185.190.39.75 +185.190.39.85 +185.190.39.91 +185.190.39.93 +185.190.39.103 +185.190.39.113 +185.190.39.120 +185.190.39.133 +185.190.39.137 +185.190.39.143 +185.190.39.145 +185.190.39.148 +185.190.39.154 +185.190.39.159 +185.190.39.166 +185.190.39.172 +185.190.39.176 +185.190.39.179 +185.190.39.189 +185.190.39.193 +185.190.39.195 +185.190.39.213 +185.190.39.222 +185.190.39.225 +185.190.39.227 +185.190.39.231 +185.190.39.232 +185.190.39.235 +185.190.39.241 +185.190.39.251 +185.191.76.3 +185.191.76.5 +185.191.76.8 +185.191.76.9 +185.191.76.10 +185.191.76.11 +185.191.76.12 +185.191.76.13 +185.191.76.19 +185.191.76.27 +185.191.76.30 +185.191.76.84 +185.191.76.142 +185.191.76.147 +185.191.76.148 +185.191.76.149 +185.191.76.150 +185.191.76.163 +185.191.76.210 +185.191.77.50 +185.191.77.54 +185.191.77.122 +185.191.77.138 +185.191.77.142 +185.191.77.197 +185.191.77.210 +185.191.77.221 +185.191.77.229 +185.191.77.235 +185.191.77.236 +185.191.77.237 +185.191.77.241 +185.191.77.244 +185.191.77.245 +185.191.77.249 +185.191.77.250 +185.191.77.251 +185.191.77.252 +185.191.78.22 +185.191.78.74 +185.191.78.187 +185.191.78.214 +185.191.78.226 +185.191.78.227 +185.191.78.228 +185.191.78.231 +185.191.79.8 +185.191.79.10 +185.191.79.13 +185.191.79.14 +185.191.79.17 +185.191.79.22 +185.191.79.23 +185.191.79.55 +185.191.79.102 +185.191.79.138 +185.191.79.208 +185.191.79.211 +185.192.10.3 +185.192.112.2 +185.192.112.3 +185.192.112.5 +185.192.112.7 +185.192.112.8 +185.192.112.9 +185.192.112.13 +185.192.112.14 +185.192.112.15 +185.192.112.19 +185.192.112.20 +185.192.112.21 +185.192.112.22 +185.192.112.24 +185.192.112.26 +185.192.112.29 +185.192.112.30 +185.192.112.31 +185.192.112.38 +185.192.112.41 +185.192.112.44 +185.192.112.49 +185.192.112.53 +185.192.112.55 +185.192.112.56 +185.192.112.57 +185.192.112.58 +185.192.112.64 +185.192.112.68 +185.192.112.70 +185.192.112.75 +185.192.112.79 +185.192.112.81 +185.192.112.82 +185.192.112.83 +185.192.112.84 +185.192.112.87 +185.192.112.88 +185.192.112.93 +185.192.112.96 +185.192.112.97 +185.192.112.98 +185.192.112.99 +185.192.112.101 +185.192.112.103 +185.192.112.104 +185.192.112.108 +185.192.112.109 +185.192.112.110 +185.192.112.114 +185.192.112.117 +185.192.112.118 +185.192.112.119 +185.192.112.125 +185.192.112.126 +185.192.112.130 +185.192.112.131 +185.192.112.132 +185.192.112.133 +185.192.112.137 +185.192.112.138 +185.192.112.139 +185.192.112.144 +185.192.112.145 +185.192.112.146 +185.192.112.152 +185.192.112.153 +185.192.112.157 +185.192.112.161 +185.192.112.162 +185.192.112.163 +185.192.112.164 +185.192.112.201 +185.192.112.202 +185.192.112.203 +185.192.112.205 +185.192.112.207 +185.192.112.208 +185.192.112.209 +185.192.112.211 +185.192.112.212 +185.192.112.213 +185.192.112.214 +185.192.112.215 +185.192.112.216 +185.192.112.217 +185.192.112.218 +185.192.112.219 +185.192.112.221 +185.192.112.223 +185.192.112.224 +185.192.112.225 +185.192.112.227 +185.192.112.229 +185.192.112.230 +185.192.112.231 +185.192.112.241 +185.192.112.243 +185.192.112.244 +185.192.112.245 +185.192.112.246 +185.192.112.247 +185.192.112.248 +185.192.112.251 +185.192.112.253 +185.192.112.254 +185.192.113.6 +185.192.113.11 +185.192.113.12 +185.192.113.14 +185.192.113.15 +185.192.113.16 +185.192.113.17 +185.192.113.18 +185.192.113.20 +185.192.113.23 +185.192.113.25 +185.192.113.26 +185.192.113.30 +185.192.113.31 +185.192.113.32 +185.192.113.34 +185.192.113.35 +185.192.113.43 +185.192.113.44 +185.192.113.47 +185.192.113.49 +185.192.113.82 +185.192.113.86 +185.192.113.90 +185.192.113.92 +185.192.113.102 +185.192.113.103 +185.192.113.110 +185.192.113.111 +185.192.113.112 +185.192.113.114 +185.192.113.115 +185.192.113.117 +185.192.113.118 +185.192.113.119 +185.192.113.122 +185.192.113.123 +185.192.113.124 +185.192.113.126 +185.192.113.127 +185.192.113.128 +185.192.113.129 +185.192.113.130 +185.192.113.133 +185.192.113.140 +185.192.113.141 +185.192.113.143 +185.192.113.145 +185.192.113.150 +185.192.113.151 +185.192.113.152 +185.192.113.153 +185.192.113.157 +185.192.113.159 +185.192.113.163 +185.192.113.164 +185.192.113.174 +185.192.113.175 +185.192.113.177 +185.192.113.183 +185.192.113.185 +185.192.113.186 +185.192.113.193 +185.192.113.201 +185.192.113.203 +185.192.113.206 +185.192.113.209 +185.192.113.242 +185.192.113.251 +185.192.113.252 +185.192.114.12 +185.192.114.13 +185.192.114.15 +185.192.114.16 +185.192.114.17 +185.192.114.20 +185.192.114.23 +185.192.114.24 +185.192.114.25 +185.192.114.26 +185.192.114.28 +185.192.114.30 +185.192.114.39 +185.192.114.40 +185.192.114.41 +185.192.114.47 +185.192.114.48 +185.192.114.49 +185.192.114.53 +185.192.114.54 +185.192.114.65 +185.192.114.83 +185.192.114.101 +185.192.114.102 +185.192.114.103 +185.192.114.104 +185.192.114.105 +185.192.114.108 +185.192.114.112 +185.192.114.114 +185.192.114.116 +185.192.114.117 +185.192.114.118 +185.192.114.123 +185.192.114.126 +185.192.114.127 +185.192.114.129 +185.192.114.130 +185.192.114.133 +185.192.114.135 +185.192.114.140 +185.192.114.148 +185.192.114.153 +185.192.114.154 +185.192.114.158 +185.192.114.159 +185.192.114.162 +185.192.114.165 +185.192.114.172 +185.192.114.201 +185.192.114.203 +185.192.114.204 +185.192.114.205 +185.192.114.209 +185.192.114.211 +185.192.114.254 +185.193.47.15 +185.193.47.16 +185.194.79.2 +185.194.79.3 +185.194.79.4 +185.196.148.112 +185.198.160.2 +185.198.160.3 +185.201.49.149 +185.201.49.228 +185.201.50.101 +185.201.50.104 +185.201.50.105 +185.201.50.220 +185.202.59.66 +185.204.197.12 +185.204.197.13 +185.204.197.15 +185.204.197.16 +185.204.197.19 +185.204.197.21 +185.204.197.24 +185.204.197.28 +185.204.197.33 +185.204.197.41 +185.204.197.42 +185.204.197.60 +185.204.197.71 +185.204.197.85 +185.204.197.96 +185.204.197.97 +185.204.197.98 +185.204.197.99 +185.204.197.106 +185.204.197.110 +185.204.197.116 +185.204.197.120 +185.204.197.123 +185.204.197.129 +185.204.197.132 +185.204.197.133 +185.204.197.140 +185.204.197.145 +185.204.197.155 +185.204.197.160 +185.204.197.164 +185.204.197.169 +185.204.197.171 +185.204.197.172 +185.204.197.178 +185.204.197.182 +185.204.197.186 +185.204.197.187 +185.204.197.188 +185.204.197.195 +185.204.197.198 +185.204.197.199 +185.204.197.203 +185.204.197.204 +185.204.197.210 +185.204.197.215 +185.204.197.216 +185.204.197.225 +185.204.197.232 +185.204.197.235 +185.204.197.239 +185.204.197.248 +185.205.203.85 +185.205.203.153 +185.205.203.189 +185.205.203.230 +185.205.203.253 +185.206.92.90 +185.206.92.250 +185.206.92.253 +185.206.93.158 +185.206.93.186 +185.206.93.230 +185.206.94.60 +185.206.94.193 +185.206.95.17 +185.206.95.33 +185.206.95.87 +185.206.95.198 +185.206.229.76 +185.206.229.79 +185.206.229.81 +185.206.229.82 +185.206.229.99 +185.206.229.100 +185.206.229.109 +185.206.229.134 +185.206.229.137 +185.206.229.142 +185.206.229.210 +185.206.231.5 +185.206.238.18 +185.206.238.20 +185.206.238.23 +185.206.238.42 +185.206.238.146 +185.206.238.147 +185.206.238.170 +185.207.72.6 +185.207.73.6 +185.208.77.86 +185.208.78.38 +185.208.78.214 +185.208.79.6 +185.208.79.48 +185.208.79.49 +185.208.79.65 +185.208.79.68 +185.208.79.72 +185.208.79.82 +185.208.79.99 +185.208.79.104 +185.208.79.106 +185.208.79.128 +185.208.79.129 +185.208.79.191 +185.208.79.199 +185.208.79.206 +185.208.79.209 +185.208.79.210 +185.208.79.221 +185.208.79.230 +185.208.79.243 +185.208.149.56 +185.208.149.142 +185.208.174.5 +185.208.174.7 +185.208.174.8 +185.208.174.16 +185.208.174.22 +185.208.174.23 +185.208.174.28 +185.208.174.33 +185.208.174.35 +185.208.174.37 +185.208.174.42 +185.208.174.45 +185.208.174.48 +185.208.174.51 +185.208.174.59 +185.208.174.60 +185.208.174.61 +185.208.174.64 +185.208.174.65 +185.208.174.67 +185.208.174.69 +185.208.174.76 +185.208.174.79 +185.208.174.85 +185.208.174.88 +185.208.174.95 +185.208.174.97 +185.208.174.99 +185.208.174.100 +185.208.174.103 +185.208.174.113 +185.208.174.115 +185.208.174.116 +185.208.174.124 +185.208.174.128 +185.208.174.129 +185.208.174.130 +185.208.174.134 +185.208.174.135 +185.208.174.136 +185.208.174.139 +185.208.174.141 +185.208.174.144 +185.208.174.147 +185.208.174.152 +185.208.174.155 +185.208.174.156 +185.208.174.159 +185.208.174.164 +185.208.174.165 +185.208.174.166 +185.208.174.167 +185.208.174.173 +185.208.174.176 +185.208.174.180 +185.208.174.181 +185.208.174.182 +185.208.174.183 +185.208.174.190 +185.208.174.198 +185.208.174.200 +185.208.174.201 +185.208.174.205 +185.208.174.206 +185.208.174.214 +185.208.174.219 +185.208.174.220 +185.208.174.222 +185.208.174.225 +185.208.174.231 +185.208.174.242 +185.208.174.249 +185.208.174.251 +185.208.175.4 +185.208.175.15 +185.208.175.17 +185.208.175.18 +185.208.175.21 +185.208.175.24 +185.208.175.31 +185.208.175.41 +185.208.175.43 +185.208.175.53 +185.208.175.59 +185.208.175.60 +185.208.175.64 +185.208.175.67 +185.208.175.69 +185.208.175.70 +185.208.175.71 +185.208.175.75 +185.208.175.82 +185.208.175.84 +185.208.175.92 +185.208.175.93 +185.208.175.94 +185.208.175.97 +185.208.175.98 +185.208.175.104 +185.208.175.108 +185.208.175.109 +185.208.175.115 +185.208.175.118 +185.208.175.123 +185.208.175.125 +185.208.175.126 +185.208.175.131 +185.208.175.132 +185.208.175.139 +185.208.175.140 +185.208.175.144 +185.208.175.149 +185.208.175.151 +185.208.175.158 +185.208.175.163 +185.208.175.170 +185.208.175.172 +185.208.175.174 +185.208.175.176 +185.208.175.182 +185.208.175.186 +185.208.175.189 +185.208.175.191 +185.208.175.192 +185.208.175.193 +185.208.175.195 +185.208.175.197 +185.208.175.205 +185.208.175.206 +185.208.175.210 +185.208.175.214 +185.208.175.217 +185.208.175.220 +185.208.175.223 +185.208.175.228 +185.208.175.239 +185.208.175.246 +185.208.175.250 +185.208.175.251 +185.208.180.23 +185.208.180.35 +185.208.180.44 +185.208.180.52 +185.208.180.60 +185.208.180.91 +185.208.180.92 +185.208.180.94 +185.208.180.132 +185.208.180.133 +185.208.180.134 +185.208.180.135 +185.208.180.136 +185.208.180.137 +185.208.180.139 +185.208.180.141 +185.208.180.149 +185.208.180.151 +185.208.180.153 +185.208.180.155 +185.208.180.157 +185.208.180.166 +185.208.180.169 +185.208.180.171 +185.208.180.244 +185.208.180.249 +185.208.180.251 +185.208.180.253 +185.208.181.34 +185.208.181.130 +185.208.181.211 +185.208.181.252 +185.208.181.253 +185.208.181.254 +185.208.182.20 +185.208.182.24 +185.208.182.28 +185.208.182.42 +185.208.182.53 +185.208.182.60 +185.208.182.78 +185.208.182.139 +185.208.182.140 +185.208.182.142 +185.208.182.152 +185.208.182.153 +185.208.182.154 +185.208.182.159 +185.208.182.178 +185.208.182.212 +185.208.182.214 +185.208.182.251 +185.208.182.252 +185.208.183.21 +185.208.183.29 +185.208.183.35 +185.208.183.126 +185.208.183.133 +185.208.183.245 +185.209.42.88 +185.209.42.151 +185.209.42.219 +185.209.42.224 +185.210.200.253 +185.210.200.254 +185.211.56.3 +185.211.56.4 +185.211.56.14 +185.211.56.22 +185.211.56.24 +185.211.56.25 +185.211.56.33 +185.211.56.40 +185.211.56.43 +185.211.56.44 +185.211.56.59 +185.211.56.63 +185.211.56.64 +185.211.56.76 +185.211.56.82 +185.211.56.84 +185.211.56.96 +185.211.56.99 +185.211.56.112 +185.211.56.130 +185.211.56.132 +185.211.56.134 +185.211.56.147 +185.211.56.163 +185.211.56.165 +185.211.56.168 +185.211.56.169 +185.211.56.183 +185.211.56.188 +185.211.56.210 +185.211.56.233 +185.211.56.254 +185.211.57.7 +185.211.57.18 +185.211.57.24 +185.211.57.34 +185.211.57.53 +185.211.57.54 +185.211.57.69 +185.211.57.76 +185.211.57.78 +185.211.57.84 +185.211.57.89 +185.211.57.98 +185.211.57.106 +185.211.57.107 +185.211.57.129 +185.211.57.140 +185.211.57.163 +185.211.57.172 +185.211.57.176 +185.211.57.181 +185.211.57.191 +185.211.57.193 +185.211.57.197 +185.211.57.217 +185.211.57.222 +185.211.58.8 +185.211.58.37 +185.211.58.41 +185.211.58.44 +185.211.58.83 +185.211.58.84 +185.211.58.91 +185.211.58.192 +185.211.58.198 +185.211.58.214 +185.211.58.216 +185.211.58.217 +185.211.58.235 +185.211.58.242 +185.211.58.243 +185.211.59.7 +185.211.59.27 +185.211.59.41 +185.211.59.56 +185.211.59.84 +185.211.59.103 +185.211.59.111 +185.211.59.121 +185.211.59.127 +185.211.59.128 +185.211.59.131 +185.211.59.141 +185.211.59.142 +185.211.59.143 +185.211.59.144 +185.211.59.151 +185.211.59.163 +185.211.59.168 +185.211.59.175 +185.211.59.184 +185.211.59.196 +185.211.59.198 +185.211.59.202 +185.211.59.204 +185.211.59.211 +185.211.59.216 +185.211.59.232 +185.211.88.6 +185.211.89.14 +185.211.90.9 +185.212.51.67 +185.212.51.69 +185.212.51.70 +185.212.51.72 +185.212.51.75 +185.212.51.76 +185.212.51.78 +185.212.51.79 +185.212.51.81 +185.212.51.110 +185.212.51.114 +185.212.51.211 +185.212.51.242 +185.212.194.28 +185.212.194.53 +185.212.194.66 +185.212.194.141 +185.212.194.146 +185.212.194.161 +185.212.195.160 +185.213.8.40 +185.213.9.40 +185.213.10.18 +185.213.10.22 +185.213.10.29 +185.213.10.35 +185.213.10.37 +185.213.10.38 +185.213.10.39 +185.213.10.40 +185.213.10.41 +185.213.10.42 +185.213.10.43 +185.213.10.44 +185.213.10.45 +185.213.10.46 +185.213.10.70 +185.213.10.100 +185.213.10.116 +185.213.10.167 +185.213.10.172 +185.213.10.227 +185.213.10.230 +185.213.10.234 +185.213.10.237 +185.213.11.19 +185.213.11.20 +185.213.11.21 +185.213.11.22 +185.213.11.34 +185.213.11.60 +185.213.11.82 +185.213.11.84 +185.213.11.85 +185.213.11.201 +185.213.11.202 +185.213.11.217 +185.213.164.7 +185.213.164.12 +185.213.164.15 +185.213.164.24 +185.213.164.26 +185.213.164.28 +185.213.164.29 +185.213.164.31 +185.213.164.38 +185.213.164.39 +185.213.164.40 +185.213.164.41 +185.213.164.42 +185.213.164.43 +185.213.164.50 +185.213.164.53 +185.213.164.56 +185.213.164.59 +185.213.164.62 +185.213.164.65 +185.213.164.68 +185.213.164.69 +185.213.164.71 +185.213.164.78 +185.213.164.84 +185.213.164.96 +185.213.164.112 +185.213.164.113 +185.213.164.120 +185.213.164.121 +185.213.164.124 +185.213.164.128 +185.213.164.130 +185.213.164.131 +185.213.164.137 +185.213.164.138 +185.213.164.144 +185.213.164.149 +185.213.164.155 +185.213.164.160 +185.213.164.167 +185.213.164.168 +185.213.164.185 +185.213.164.187 +185.213.164.188 +185.213.164.189 +185.213.164.192 +185.213.164.195 +185.213.164.220 +185.213.164.221 +185.213.164.230 +185.213.164.231 +185.213.164.232 +185.213.164.235 +185.213.164.239 +185.213.164.245 +185.213.164.252 +185.213.165.10 +185.213.165.12 +185.213.165.16 +185.213.165.18 +185.213.165.24 +185.213.165.39 +185.213.165.44 +185.213.165.47 +185.213.165.51 +185.213.165.59 +185.213.165.63 +185.213.165.68 +185.213.165.74 +185.213.165.76 +185.213.165.79 +185.213.165.81 +185.213.165.94 +185.213.165.100 +185.213.165.119 +185.213.165.127 +185.213.165.134 +185.213.165.143 +185.213.165.148 +185.213.165.162 +185.213.165.165 +185.213.165.173 +185.213.165.179 +185.213.165.181 +185.213.165.185 +185.213.165.188 +185.213.165.191 +185.213.165.197 +185.213.165.205 +185.213.165.208 +185.213.165.209 +185.213.165.217 +185.213.165.229 +185.213.165.231 +185.213.165.234 +185.213.165.235 +185.213.165.236 +185.213.165.237 +185.213.195.127 +185.213.195.190 +185.215.125.6 +185.215.126.9 +185.215.126.220 +185.215.154.123 +185.215.229.2 +185.215.235.253 +185.217.6.82 +185.217.6.87 +185.217.6.89 +185.217.6.118 +185.218.139.28 +185.218.139.30 +185.218.139.36 +185.218.139.45 +185.218.139.74 +185.218.139.103 +185.218.139.138 +185.218.139.150 +185.218.139.162 +185.218.139.196 +185.218.139.201 +185.218.139.205 +185.218.139.223 +185.218.139.225 +185.218.139.229 +185.218.139.248 +185.218.139.252 +185.218.139.253 +185.221.239.27 +185.221.239.85 +185.221.239.97 +185.221.239.105 +185.221.239.108 +185.221.239.112 +185.221.239.131 +185.221.239.137 +185.221.239.144 +185.221.239.150 +185.221.239.161 +185.221.239.192 +185.221.239.198 +185.221.239.219 +185.221.239.235 +185.221.239.245 +185.222.163.16 +185.222.163.28 +185.222.163.82 +185.222.163.90 +185.222.163.91 +185.222.163.92 +185.222.163.94 +185.222.163.107 +185.222.163.112 +185.222.163.117 +185.222.163.123 +185.222.163.124 +185.222.163.125 +185.222.163.131 +185.222.163.141 +185.222.163.144 +185.222.163.145 +185.222.163.146 +185.222.163.147 +185.222.163.148 +185.222.163.149 +185.222.163.150 +185.222.163.153 +185.222.163.174 +185.222.163.181 +185.222.163.186 +185.222.163.202 +185.222.163.210 +185.222.182.90 +185.222.183.90 +185.225.80.15 +185.225.80.17 +185.225.80.40 +185.225.80.41 +185.225.80.42 +185.225.80.46 +185.225.80.50 +185.225.80.55 +185.225.80.57 +185.225.80.60 +185.225.80.112 +185.225.80.132 +185.225.80.133 +185.225.80.134 +185.225.80.136 +185.225.80.137 +185.225.82.115 +185.225.83.236 +185.226.117.3 +185.226.117.66 +185.226.117.240 +185.226.118.54 +185.226.118.108 +185.226.118.201 +185.226.119.17 +185.226.119.48 +185.226.134.182 +185.226.141.41 +185.226.141.141 +185.226.143.43 +185.226.143.143 +185.229.28.6 +185.229.28.28 +185.229.28.29 +185.229.28.135 +185.229.28.136 +185.229.28.162 +185.229.28.163 +185.229.29.147 +185.229.29.148 +185.229.29.149 +185.229.29.150 +185.229.133.6 +185.229.133.44 +185.229.204.52 +185.229.204.107 +185.229.204.108 +185.229.204.120 +185.229.204.123 +185.229.204.124 +185.229.204.125 +185.229.204.141 +185.229.204.151 +185.229.204.155 +185.229.204.231 +185.231.65.27 +185.231.112.9 +185.231.112.10 +185.231.112.26 +185.231.112.34 +185.231.112.45 +185.231.112.46 +185.231.112.53 +185.231.112.56 +185.231.112.61 +185.231.112.68 +185.231.112.88 +185.231.112.103 +185.231.112.110 +185.231.112.115 +185.231.112.116 +185.231.112.117 +185.231.112.131 +185.231.112.134 +185.231.112.138 +185.231.112.143 +185.231.112.158 +185.231.112.159 +185.231.112.164 +185.231.112.167 +185.231.112.178 +185.231.112.182 +185.231.112.190 +185.231.112.191 +185.231.112.192 +185.231.112.198 +185.231.112.203 +185.231.112.208 +185.231.112.210 +185.231.112.213 +185.231.112.215 +185.231.112.221 +185.231.112.231 +185.231.112.239 +185.231.112.241 +185.231.112.244 +185.231.113.22 +185.231.114.218 +185.231.115.15 +185.231.115.16 +185.231.115.18 +185.231.115.23 +185.231.115.31 +185.231.115.32 +185.231.115.34 +185.231.115.47 +185.231.115.50 +185.231.115.54 +185.231.115.58 +185.231.115.67 +185.231.115.73 +185.231.115.91 +185.231.115.103 +185.231.115.112 +185.231.115.125 +185.231.115.127 +185.231.115.130 +185.231.115.135 +185.231.115.143 +185.231.115.147 +185.231.115.170 +185.231.115.174 +185.231.115.175 +185.231.115.177 +185.231.115.178 +185.231.115.183 +185.231.115.187 +185.231.115.193 +185.231.115.197 +185.231.115.202 +185.231.115.203 +185.231.115.206 +185.231.115.216 +185.231.115.226 +185.231.115.235 +185.231.115.238 +185.231.115.247 +185.231.115.252 +185.231.180.29 +185.231.180.181 +185.231.180.203 +185.231.180.228 +185.231.181.14 +185.231.181.143 +185.231.181.159 +185.231.181.206 +185.231.181.221 +185.231.182.207 +185.231.182.208 +185.231.183.19 +185.231.183.238 +185.232.154.69 +185.234.14.34 +185.234.14.43 +185.234.14.65 +185.234.14.94 +185.234.14.101 +185.234.14.102 +185.234.14.111 +185.234.14.129 +185.234.14.137 +185.234.192.17 +185.234.195.13 +185.235.136.20 +185.235.196.9 +185.235.196.10 +185.235.196.11 +185.235.196.12 +185.235.196.13 +185.235.196.14 +185.235.196.16 +185.235.196.17 +185.235.196.18 +185.235.196.19 +185.235.196.20 +185.235.196.22 +185.235.196.25 +185.235.196.38 +185.235.196.39 +185.235.196.48 +185.235.196.49 +185.235.196.52 +185.235.196.53 +185.235.196.136 +185.235.196.242 +185.235.196.243 +185.235.196.244 +185.235.196.245 +185.235.197.6 +185.235.197.8 +185.235.197.31 +185.235.197.216 +185.235.198.200 +185.235.245.12 +185.235.245.30 +185.235.245.49 +185.235.245.51 +185.235.245.91 +185.235.245.239 +185.235.245.246 +185.235.245.247 +185.236.36.21 +185.236.37.15 +185.236.37.179 +185.236.37.198 +185.236.38.92 +185.237.84.10 +185.237.84.11 +185.237.85.4 +185.237.85.5 +185.237.85.9 +185.237.85.10 +185.237.85.16 +185.237.85.21 +185.237.85.26 +185.237.85.40 +185.237.85.42 +185.237.85.47 +185.237.85.54 +185.237.85.55 +185.237.85.56 +185.237.85.95 +185.237.85.96 +185.237.85.97 +185.237.85.114 +185.237.85.117 +185.237.85.118 +185.237.85.252 +185.237.87.84 +185.237.87.85 +185.237.87.139 +185.238.140.195 +185.238.143.229 +185.239.0.12 +185.239.0.22 +185.239.0.53 +185.239.0.86 +185.239.0.127 +185.239.0.190 +185.239.0.232 +185.239.0.239 +185.239.0.246 +185.239.1.179 +185.239.2.31 +185.239.3.47 +185.239.3.86 +185.239.3.112 +185.239.3.117 +185.239.3.124 +185.239.3.151 +185.239.3.152 +185.239.3.187 +185.239.3.199 +185.239.3.208 +185.239.3.220 +185.239.3.222 +185.239.3.224 +185.239.3.236 +185.239.104.106 +185.239.105.144 +185.239.106.86 +185.243.48.4 +185.243.48.13 +185.243.48.15 +185.243.48.28 +185.243.48.31 +185.243.48.45 +185.243.48.52 +185.243.48.57 +185.243.48.58 +185.243.48.70 +185.243.48.78 +185.243.48.102 +185.243.48.109 +185.243.48.126 +185.243.48.127 +185.243.48.131 +185.243.48.132 +185.243.48.134 +185.243.48.135 +185.243.48.140 +185.243.48.145 +185.243.48.159 +185.243.48.170 +185.243.48.173 +185.243.48.176 +185.243.48.181 +185.243.48.189 +185.243.48.196 +185.243.48.198 +185.243.48.199 +185.243.48.200 +185.243.48.202 +185.243.48.204 +185.243.48.211 +185.243.48.215 +185.243.48.226 +185.243.48.227 +185.243.48.231 +185.243.48.233 +185.243.48.237 +185.243.48.242 +185.243.48.243 +185.243.48.251 +185.243.51.50 +185.243.51.58 +185.246.6.178 +185.246.6.179 +185.252.28.13 +185.252.28.16 +185.252.28.19 +185.252.28.20 +185.252.28.21 +185.252.28.30 +185.252.28.31 +185.252.28.32 +185.252.28.34 +185.252.28.42 +185.252.28.44 +185.252.28.46 +185.252.28.47 +185.252.28.56 +185.252.28.63 +185.252.28.68 +185.252.28.77 +185.252.28.86 +185.252.28.105 +185.252.28.106 +185.252.28.116 +185.252.28.117 +185.252.28.120 +185.252.28.123 +185.252.28.124 +185.252.28.127 +185.252.28.128 +185.252.28.129 +185.252.28.130 +185.252.28.136 +185.252.28.137 +185.252.28.138 +185.252.28.140 +185.252.28.150 +185.252.28.165 +185.252.28.166 +185.252.28.170 +185.252.28.178 +185.252.28.179 +185.252.28.180 +185.252.28.182 +185.252.28.186 +185.252.28.188 +185.252.28.192 +185.252.28.194 +185.252.28.195 +185.252.28.200 +185.252.28.207 +185.252.28.210 +185.252.28.211 +185.252.28.228 +185.252.28.229 +185.252.28.230 +185.252.29.11 +185.252.29.14 +185.252.29.17 +185.252.29.18 +185.252.29.27 +185.252.29.29 +185.252.29.31 +185.252.29.32 +185.252.29.35 +185.252.29.47 +185.252.29.50 +185.252.29.53 +185.252.29.54 +185.252.29.60 +185.252.29.62 +185.252.29.68 +185.252.29.72 +185.252.29.74 +185.252.29.99 +185.252.29.102 +185.252.29.121 +185.252.29.133 +185.252.29.141 +185.252.29.143 +185.252.29.145 +185.252.29.151 +185.252.29.154 +185.252.29.160 +185.252.29.161 +185.252.29.162 +185.252.29.234 +185.252.29.235 +185.252.29.239 +185.252.30.39 +185.252.30.40 +185.252.30.41 +185.252.30.42 +185.252.30.43 +185.252.30.175 +185.252.30.188 +185.252.30.200 +185.252.30.206 +185.252.31.4 +185.252.31.5 +185.252.31.8 +185.252.31.10 +185.252.31.19 +185.252.31.21 +185.252.31.22 +185.252.31.27 +185.252.31.28 +185.252.31.32 +185.252.31.33 +185.252.31.40 +185.252.31.42 +185.252.31.43 +185.252.31.62 +185.252.31.63 +185.252.31.65 +185.252.31.99 +185.252.31.103 +185.252.31.105 +185.252.31.116 +185.252.31.119 +185.252.31.120 +185.252.31.138 +185.252.31.140 +185.252.31.142 +185.252.31.146 +185.252.31.161 +185.252.31.164 +185.252.31.165 +185.252.31.166 +185.252.31.201 +185.252.31.203 +185.252.31.213 +185.252.31.223 +185.252.86.15 +185.252.86.17 +185.252.86.20 +185.252.86.30 +185.252.86.31 +185.252.86.34 +185.252.86.36 +185.252.86.38 +185.252.86.43 +185.252.86.49 +185.252.86.56 +185.252.86.75 +185.252.86.77 +185.252.86.79 +185.252.86.81 +185.252.86.102 +185.252.86.107 +185.252.86.110 +185.252.86.120 +185.252.86.121 +185.252.86.125 +185.252.86.126 +185.252.86.145 +185.252.86.164 +185.252.86.165 +185.252.86.168 +185.252.86.171 +185.252.86.181 +185.252.86.183 +185.252.86.185 +185.252.200.157 +185.254.165.5 +185.255.88.3 +185.255.88.11 +185.255.88.12 +185.255.88.13 +185.255.88.15 +185.255.88.18 +185.255.88.19 +185.255.88.24 +185.255.88.36 +185.255.88.42 +185.255.88.59 +185.255.88.61 +185.255.88.63 +185.255.88.64 +185.255.88.66 +185.255.88.68 +185.255.88.74 +185.255.88.79 +185.255.88.85 +185.255.88.102 +185.255.88.106 +185.255.88.107 +185.255.88.109 +185.255.88.110 +185.255.88.112 +185.255.88.113 +185.255.88.115 +185.255.88.117 +185.255.88.118 +185.255.88.119 +185.255.88.123 +185.255.88.124 +185.255.88.126 +185.255.88.130 +185.255.88.131 +185.255.88.132 +185.255.88.134 +185.255.88.135 +185.255.88.136 +185.255.88.137 +185.255.88.139 +185.255.88.140 +185.255.88.142 +185.255.88.145 +185.255.88.148 +185.255.88.149 +185.255.88.150 +185.255.88.152 +185.255.88.156 +185.255.88.157 +185.255.88.160 +185.255.88.161 +185.255.88.164 +185.255.88.167 +185.255.88.169 +185.255.88.171 +185.255.88.172 +185.255.88.173 +185.255.88.174 +185.255.88.175 +185.255.88.176 +185.255.88.177 +185.255.88.182 +185.255.88.188 +185.255.88.190 +185.255.88.191 +185.255.88.194 +185.255.88.198 +185.255.88.200 +185.255.88.203 +185.255.88.207 +185.255.88.212 +185.255.88.214 +185.255.88.216 +185.255.88.217 +185.255.88.218 +185.255.88.219 +185.255.89.6 +185.255.89.7 +185.255.89.8 +185.255.89.10 +185.255.89.12 +185.255.89.13 +185.255.89.14 +185.255.89.15 +185.255.89.18 +185.255.89.26 +185.255.89.37 +185.255.89.38 +185.255.89.40 +185.255.89.46 +185.255.89.57 +185.255.89.60 +185.255.89.66 +185.255.89.86 +185.255.89.90 +185.255.89.94 +185.255.89.102 +185.255.89.103 +185.255.89.104 +185.255.89.106 +185.255.89.110 +185.255.89.112 +185.255.89.116 +185.255.89.118 +185.255.89.119 +185.255.89.120 +185.255.89.122 +185.255.89.123 +185.255.89.126 +185.255.89.128 +185.255.89.131 +185.255.89.132 +185.255.89.134 +185.255.89.139 +185.255.89.142 +185.255.89.143 +185.255.89.147 +185.255.89.149 +185.255.89.150 +185.255.89.151 +185.255.89.152 +185.255.89.153 +185.255.89.157 +185.255.89.160 +185.255.89.166 +185.255.89.168 +185.255.89.171 +185.255.89.173 +185.255.89.181 +185.255.89.191 +185.255.89.199 +185.255.89.200 +185.255.89.205 +185.255.89.207 +185.255.89.210 +185.255.89.216 +185.255.89.229 +185.255.89.233 +185.255.89.237 +185.255.89.243 +185.255.89.244 +185.255.90.15 +185.255.90.17 +185.255.90.41 +185.255.90.48 +185.255.90.82 +185.255.90.104 +185.255.90.105 +185.255.90.109 +185.255.90.123 +185.255.90.125 +185.255.90.129 +185.255.90.137 +185.255.90.148 +185.255.90.152 +185.255.90.159 +185.255.90.162 +185.255.90.163 +185.255.90.202 +185.255.90.205 +185.255.91.15 +185.255.91.20 +185.255.91.32 +185.255.91.36 +185.255.91.48 +185.255.91.58 +185.255.91.60 +185.255.91.80 +185.255.91.116 +185.255.91.120 +185.255.91.132 +185.255.91.140 +185.255.91.144 +185.255.91.147 +185.255.91.160 +185.255.91.173 +185.255.91.182 +185.255.91.190 +185.255.91.208 +185.255.91.212 +185.255.91.213 +185.255.91.215 +185.255.91.219 +185.255.91.227 +185.255.91.229 +185.255.91.232 +185.255.91.234 +185.255.91.235 +185.255.91.237 +185.255.91.242 +185.255.91.243 +185.255.91.244 +185.255.208.144 +185.255.208.221 +185.255.208.222 +185.255.208.243 +185.255.209.48 +188.0.240.12 +188.0.240.13 +188.0.240.42 +188.0.240.43 +188.0.240.44 +188.0.240.85 +188.0.240.130 +188.0.240.131 +188.0.240.134 +188.0.240.186 +188.0.240.187 +188.0.240.188 +188.0.240.189 +188.0.240.190 +188.0.240.194 +188.0.240.195 +188.0.240.217 +188.0.241.19 +188.0.241.76 +188.0.241.77 +188.0.241.78 +188.0.241.101 +188.0.241.248 +188.0.248.66 +188.0.248.233 +188.0.253.168 +188.75.65.222 +188.75.78.130 +188.75.78.136 +188.75.78.138 +188.75.78.162 +188.75.78.164 +188.75.78.172 +188.75.78.174 +188.75.78.184 +188.75.78.187 +188.75.78.189 +188.75.80.176 +188.75.82.172 +188.75.82.173 +188.75.95.22 +188.75.95.66 +188.75.106.66 +188.75.106.69 +188.75.106.70 +188.75.106.178 +188.75.106.179 +188.75.106.184 +188.75.106.185 +188.121.96.94 +188.121.97.2 +188.121.99.119 +188.121.99.121 +188.121.99.199 +188.121.100.85 +188.121.100.96 +188.121.100.104 +188.121.100.200 +188.121.100.251 +188.121.101.238 +188.121.102.172 +188.121.102.175 +188.121.102.244 +188.121.103.36 +188.121.103.186 +188.121.103.193 +188.121.104.41 +188.121.105.164 +188.121.105.234 +188.121.107.156 +188.121.109.173 +188.121.110.50 +188.121.110.163 +188.121.110.254 +188.121.112.58 +188.121.112.78 +188.121.112.92 +188.121.112.127 +188.121.112.198 +188.121.113.10 +188.121.113.74 +188.121.113.102 +188.121.113.191 +188.121.113.210 +188.121.113.213 +188.121.114.188 +188.121.114.240 +188.121.114.247 +188.121.115.45 +188.121.115.61 +188.121.116.50 +188.121.116.80 +188.121.116.139 +188.121.116.237 +188.121.116.252 +188.121.117.27 +188.121.117.64 +188.121.117.146 +188.121.117.170 +188.121.118.14 +188.121.118.52 +188.121.118.134 +188.121.118.179 +188.121.119.37 +188.121.119.58 +188.121.119.90 +188.121.119.103 +188.121.119.202 +188.121.120.171 +188.121.122.33 +188.121.123.26 +188.121.123.159 +188.121.124.11 +188.121.125.89 +188.121.132.140 +188.121.157.234 +188.121.158.211 +188.136.136.9 +188.136.136.18 +188.136.136.44 +188.136.137.137 +188.136.137.138 +188.136.138.16 +188.136.138.17 +188.136.138.18 +188.136.138.19 +188.136.138.21 +188.136.138.23 +188.136.138.30 +188.136.138.31 +188.136.138.32 +188.136.138.33 +188.136.138.35 +188.136.138.36 +188.136.138.37 +188.136.138.38 +188.136.138.39 +188.136.138.40 +188.136.138.41 +188.136.138.42 +188.136.138.43 +188.136.138.44 +188.136.138.100 +188.136.142.225 +188.136.154.151 +188.136.172.3 +188.136.172.170 +188.136.194.102 +188.136.208.245 +188.191.176.47 +188.191.176.48 +188.208.144.158 +188.208.149.251 +188.209.7.172 +188.209.8.200 +188.209.9.1 +188.209.67.214 +188.209.68.97 +188.209.72.9 +188.209.79.76 +188.209.152.106 +188.210.112.2 +188.210.115.164 +188.210.115.245 +188.210.120.63 +188.210.121.66 +188.211.36.201 +188.211.36.224 +188.211.66.248 +188.211.69.120 +188.211.72.197 +188.211.74.26 +188.211.79.18 +188.211.79.68 +188.211.81.31 +188.211.83.163 +188.211.85.185 +188.211.86.127 +188.211.91.220 +188.211.98.243 +188.211.100.56 +188.211.100.115 +188.211.102.37 +188.211.102.179 +188.211.102.212 +188.211.108.253 +188.211.111.51 +188.211.113.70 +188.211.119.60 +188.211.124.229 +188.212.22.5 +188.212.22.7 +188.212.22.10 +188.212.22.17 +188.212.22.19 +188.212.22.20 +188.212.22.22 +188.212.22.23 +188.212.22.36 +188.212.22.38 +188.212.22.39 +188.212.22.40 +188.212.22.41 +188.212.22.46 +188.212.22.53 +188.212.22.54 +188.212.22.59 +188.212.22.81 +188.212.22.82 +188.212.22.99 +188.212.22.106 +188.212.22.116 +188.212.22.122 +188.212.22.130 +188.212.22.131 +188.212.22.133 +188.212.22.134 +188.212.22.152 +188.212.22.158 +188.212.22.163 +188.212.22.164 +188.212.22.167 +188.212.22.171 +188.212.22.178 +188.212.22.179 +188.212.22.180 +188.212.22.187 +188.212.22.190 +188.212.22.195 +188.212.22.198 +188.212.22.199 +188.212.22.200 +188.212.22.201 +188.212.22.205 +188.212.22.213 +188.212.22.214 +188.212.22.216 +188.212.22.226 +188.212.22.227 +188.212.22.228 +188.212.22.229 +188.212.22.230 +188.212.22.234 +188.212.22.235 +188.212.22.236 +188.212.22.237 +188.212.22.238 +188.212.22.242 +188.212.22.243 +188.212.22.248 +188.212.22.249 +188.212.22.251 +188.212.22.252 +188.212.22.253 +188.212.22.254 +188.212.86.72 +188.212.148.101 +188.212.163.97 +188.213.65.54 +188.213.65.140 +188.213.65.141 +188.213.65.156 +188.213.66.6 +188.213.66.9 +188.213.66.12 +188.213.66.13 +188.213.66.14 +188.213.66.16 +188.213.66.17 +188.213.66.18 +188.213.66.21 +188.213.66.23 +188.213.66.24 +188.213.66.26 +188.213.66.30 +188.213.66.91 +188.213.66.139 +188.213.66.141 +188.213.66.156 +188.213.66.157 +188.213.66.158 +188.213.66.172 +188.213.68.3 +188.213.68.4 +188.213.68.5 +188.213.68.7 +188.213.68.12 +188.213.68.13 +188.213.68.99 +188.213.72.13 +188.213.72.71 +188.213.72.84 +188.213.72.85 +188.213.75.5 +188.213.75.51 +188.213.154.188 +188.213.187.132 +188.213.188.41 +188.213.188.123 +188.213.196.191 +188.213.196.202 +188.213.198.4 +188.213.198.90 +188.213.198.236 +188.213.199.7 +188.213.199.80 +188.213.199.141 +188.213.209.31 +188.213.209.49 +188.215.160.93 +188.215.174.178 +188.240.212.208 +188.240.212.209 +188.240.212.210 +188.240.212.211 +188.240.212.212 +188.240.212.213 +188.240.212.214 +188.240.212.215 +188.253.2.6 +188.253.2.16 +188.253.2.19 +188.253.2.22 +188.253.2.24 +188.253.2.25 +188.253.2.30 +188.253.2.31 +188.253.2.33 +188.253.2.37 +188.253.2.38 +188.253.2.42 +188.253.2.50 +188.253.2.52 +188.253.2.53 +188.253.2.56 +188.253.2.57 +188.253.2.58 +188.253.2.63 +188.253.2.64 +188.253.2.65 +188.253.2.68 +188.253.2.70 +188.253.2.76 +188.253.2.79 +188.253.2.85 +188.253.2.88 +188.253.2.92 +188.253.2.95 +188.253.2.98 +188.253.2.99 +188.253.2.112 +188.253.2.120 +188.253.2.122 +188.253.2.123 +188.253.2.124 +188.253.2.125 +188.253.2.136 +188.253.2.138 +188.253.2.146 +188.253.2.150 +188.253.2.156 +188.253.2.165 +188.253.2.166 +188.253.2.171 +188.253.2.174 +188.253.2.180 +188.253.2.184 +188.253.2.186 +188.253.2.188 +188.253.2.189 +188.253.2.191 +188.253.2.198 +188.253.2.200 +188.253.2.206 +188.253.2.207 +188.253.2.211 +188.253.2.214 +188.253.2.220 +188.253.2.245 +188.253.2.248 +188.253.2.249 +188.253.2.251 +188.253.3.7 +188.253.3.17 +188.253.3.18 +188.253.3.34 +188.253.3.35 +188.253.3.37 +188.253.3.38 +188.253.3.39 +188.253.3.43 +188.253.3.50 +188.253.3.54 +188.253.3.59 +188.253.3.60 +188.253.3.61 +188.253.3.69 +188.253.3.74 +188.253.3.77 +188.253.3.78 +188.253.3.79 +188.253.3.89 +188.253.3.104 +188.253.3.124 +188.253.3.125 +188.253.3.127 +188.253.3.130 +188.253.3.137 +188.253.3.148 +188.253.3.149 +188.253.3.188 +188.253.3.194 +188.253.3.195 +188.253.3.196 +188.253.3.197 +188.253.3.198 +188.253.3.226 +188.253.3.250 +192.36.148.17 +193.3.31.11 +193.3.31.12 +193.3.31.20 +193.3.182.25 +193.3.182.59 +193.3.231.146 +193.3.231.150 +193.3.231.158 +193.3.231.194 +193.3.231.210 +193.3.231.218 +193.3.231.222 +193.3.231.230 +193.3.231.254 +193.5.44.11 +193.5.44.12 +193.5.44.14 +193.5.44.26 +193.5.44.30 +193.5.44.58 +193.5.44.62 +193.5.44.67 +193.5.44.80 +193.5.44.86 +193.5.44.88 +193.5.44.92 +193.5.44.94 +193.5.44.105 +193.5.44.107 +193.5.44.110 +193.5.44.115 +193.5.44.152 +193.5.44.158 +193.5.44.159 +193.5.44.161 +193.5.44.192 +193.5.44.237 +193.8.139.55 +193.8.139.56 +193.19.147.244 +193.19.147.245 +193.35.62.1 +193.35.230.4 +193.35.230.5 +193.35.230.6 +193.35.230.7 +193.35.230.8 +193.35.230.10 +193.35.230.13 +193.35.230.14 +193.35.230.15 +193.35.230.16 +193.35.230.17 +193.35.230.18 +193.35.230.19 +193.35.230.20 +193.35.230.21 +193.35.230.22 +193.35.230.23 +193.35.230.24 +193.35.230.25 +193.35.230.26 +193.35.230.27 +193.35.230.28 +193.35.230.29 +193.35.230.30 +193.35.230.31 +193.35.230.32 +193.37.37.38 +193.37.37.243 +193.39.9.3 +193.39.9.32 +193.39.9.54 +193.39.9.92 +193.39.9.105 +193.39.9.107 +193.39.9.117 +193.39.9.162 +193.39.9.166 +193.39.9.188 +193.39.9.191 +193.39.9.214 +193.39.9.236 +193.39.9.250 +193.56.61.66 +193.56.61.67 +193.56.118.1 +193.58.119.50 +193.58.119.130 +193.58.119.198 +193.58.119.234 +193.58.119.247 +193.84.255.2 +193.84.255.20 +193.84.255.66 +193.84.255.95 +193.105.2.90 +193.105.2.91 +193.105.6.15 +193.105.6.30 +193.105.6.118 +193.105.6.120 +193.105.6.121 +193.105.6.131 +193.105.6.133 +193.105.6.235 +193.105.234.13 +193.105.234.17 +193.105.234.19 +193.105.234.20 +193.105.234.29 +193.105.234.38 +193.105.234.58 +193.105.234.68 +193.105.234.81 +193.105.234.85 +193.105.234.87 +193.105.234.88 +193.105.234.90 +193.105.234.93 +193.105.234.96 +193.105.234.100 +193.105.234.104 +193.105.234.105 +193.105.234.111 +193.105.234.115 +193.105.234.119 +193.105.234.131 +193.105.234.143 +193.105.234.144 +193.105.234.151 +193.105.234.164 +193.105.234.165 +193.105.234.167 +193.105.234.180 +193.105.234.191 +193.105.234.207 +193.105.234.216 +193.105.234.223 +193.105.234.229 +193.105.234.230 +193.105.234.231 +193.105.234.234 +193.105.234.237 +193.105.234.243 +193.107.48.72 +193.107.48.203 +193.107.48.222 +193.111.76.3 +193.111.76.10 +193.111.234.35 +193.111.234.40 +193.111.234.61 +193.111.234.72 +193.111.234.114 +193.111.234.147 +193.111.234.152 +193.111.234.153 +193.111.234.196 +193.111.235.7 +193.111.235.77 +193.111.235.103 +193.111.235.115 +193.111.235.160 +193.111.235.179 +193.111.236.99 +193.111.236.101 +193.111.236.103 +193.134.100.12 +193.134.100.60 +193.134.100.148 +193.134.100.155 +193.134.101.20 +193.134.101.172 +193.134.101.173 +193.141.64.5 +193.141.64.7 +193.141.64.10 +193.141.64.11 +193.141.64.23 +193.141.64.32 +193.141.64.35 +193.141.64.38 +193.141.64.44 +193.141.64.47 +193.141.64.48 +193.141.64.55 +193.141.64.61 +193.141.64.66 +193.141.64.68 +193.141.64.88 +193.141.64.90 +193.141.64.103 +193.141.64.107 +193.141.64.109 +193.141.64.110 +193.141.64.111 +193.141.64.115 +193.141.64.117 +193.141.64.118 +193.141.64.119 +193.141.64.120 +193.141.64.121 +193.141.64.122 +193.141.64.123 +193.141.64.125 +193.141.64.126 +193.141.64.128 +193.141.64.129 +193.141.64.130 +193.141.64.134 +193.141.64.140 +193.141.64.142 +193.141.64.143 +193.141.64.146 +193.141.64.149 +193.141.64.152 +193.141.64.153 +193.141.64.154 +193.141.64.157 +193.141.64.158 +193.141.64.160 +193.141.64.174 +193.141.64.182 +193.141.64.191 +193.141.64.192 +193.141.64.212 +193.141.64.213 +193.141.64.215 +193.141.64.217 +193.141.64.221 +193.141.64.231 +193.141.64.239 +193.141.64.241 +193.141.64.242 +193.141.64.247 +193.141.64.248 +193.141.65.17 +193.141.65.24 +193.141.65.26 +193.141.65.28 +193.141.65.34 +193.141.65.42 +193.141.65.45 +193.141.65.56 +193.141.65.67 +193.141.65.68 +193.141.65.73 +193.141.65.78 +193.141.65.81 +193.141.65.82 +193.141.65.92 +193.141.65.94 +193.141.65.97 +193.141.65.99 +193.141.65.100 +193.141.65.101 +193.141.65.106 +193.141.65.107 +193.141.65.112 +193.141.65.115 +193.141.65.125 +193.141.65.129 +193.141.65.131 +193.141.65.142 +193.141.65.146 +193.141.65.155 +193.141.65.160 +193.141.65.167 +193.141.65.168 +193.141.65.169 +193.141.65.178 +193.141.65.185 +193.141.65.186 +193.141.65.188 +193.141.65.221 +193.141.65.222 +193.141.65.224 +193.141.65.228 +193.141.65.234 +193.141.65.244 +193.141.65.253 +193.148.64.119 +193.148.65.65 +193.148.66.1 +193.148.66.11 +193.148.66.66 +193.148.66.190 +193.151.128.225 +193.151.130.4 +193.151.131.113 +193.151.131.151 +193.151.134.65 +193.151.134.90 +193.151.134.104 +193.151.134.156 +193.151.134.206 +193.151.135.112 +193.151.135.182 +193.151.136.80 +193.151.137.194 +193.151.140.11 +193.151.144.140 +193.151.144.201 +193.151.145.107 +193.151.145.125 +193.151.146.234 +193.151.149.64 +193.151.149.160 +193.151.153.95 +193.151.153.225 +193.151.154.24 +193.151.155.149 +193.151.157.12 +193.151.157.13 +193.151.157.14 +193.151.157.15 +193.151.157.16 +193.151.157.17 +193.151.157.19 +193.151.157.20 +193.151.157.29 +193.151.157.30 +193.151.159.58 +193.151.159.59 +193.151.159.60 +193.151.159.61 +193.151.159.62 +193.151.159.226 +193.151.159.227 +193.151.159.228 +193.151.159.245 +193.151.159.246 +193.151.159.247 +193.151.159.253 +193.151.159.254 +193.162.129.10 +193.162.129.11 +193.162.129.14 +193.162.129.35 +193.162.129.59 +193.162.129.63 +193.162.129.77 +193.162.129.78 +193.162.129.156 +193.162.129.170 +193.162.129.212 +193.162.129.216 +193.162.129.217 +193.162.129.223 +193.162.129.224 +193.162.129.226 +193.162.129.233 +193.162.129.242 +193.162.129.248 +193.176.97.94 +193.178.200.3 +193.178.200.5 +193.186.32.5 +193.186.32.7 +193.186.32.32 +193.186.32.45 +193.186.32.56 +193.186.32.58 +193.186.32.59 +193.186.32.81 +193.186.32.87 +193.186.32.105 +193.186.32.106 +193.186.32.108 +193.186.32.109 +193.186.32.111 +193.186.32.113 +193.186.32.123 +193.186.32.139 +193.186.32.141 +193.186.32.145 +193.186.32.148 +193.186.32.152 +193.186.32.155 +193.186.32.156 +193.186.32.162 +193.186.32.170 +193.186.32.178 +193.186.32.184 +193.186.32.197 +193.186.32.199 +193.186.32.201 +193.186.32.203 +193.186.32.216 +193.186.32.222 +193.186.32.227 +193.186.32.239 +193.186.32.242 +193.186.32.246 +193.186.32.247 +193.186.32.248 +193.186.32.249 +193.186.32.251 +193.186.32.252 +193.200.148.9 +193.200.148.66 +193.222.51.1 +193.228.90.2 +193.228.90.3 +193.228.90.4 +193.228.90.5 +193.228.90.6 +193.228.90.7 +193.228.90.8 +193.228.90.13 +193.228.90.19 +193.228.90.20 +193.228.90.21 +193.228.90.23 +193.228.90.25 +193.228.90.27 +193.228.90.28 +193.228.90.35 +193.228.90.37 +193.228.90.39 +193.228.90.40 +193.228.90.42 +193.228.90.43 +193.228.90.46 +193.228.90.48 +193.228.90.57 +193.228.90.63 +193.228.90.64 +193.228.90.70 +193.228.90.72 +193.228.90.76 +193.228.90.78 +193.228.90.82 +193.228.90.87 +193.228.90.90 +193.228.90.91 +193.228.90.92 +193.228.90.93 +193.228.90.99 +193.228.90.101 +193.228.90.102 +193.228.90.111 +193.228.90.112 +193.228.90.113 +193.228.90.119 +193.228.90.120 +193.228.90.130 +193.228.90.131 +193.228.90.132 +193.228.90.136 +193.228.90.139 +193.228.90.141 +193.228.90.144 +193.228.90.145 +193.228.90.147 +193.228.90.148 +193.228.90.156 +193.228.90.161 +193.228.90.180 +193.228.90.182 +193.228.90.185 +193.228.90.186 +193.228.90.188 +193.228.90.189 +193.228.90.191 +193.228.90.198 +193.228.90.199 +193.228.90.238 +193.228.90.244 +193.228.90.245 +193.228.90.253 +193.228.90.254 +193.228.169.51 +193.228.169.234 +193.242.125.12 +193.242.125.15 +193.242.125.25 +193.242.125.28 +193.242.125.29 +193.242.125.35 +193.242.125.38 +193.242.125.47 +193.242.125.51 +193.242.125.61 +193.242.125.63 +193.242.125.65 +193.242.125.69 +193.242.125.73 +193.242.125.83 +193.242.125.93 +193.242.125.112 +193.242.125.124 +193.242.125.135 +193.242.125.141 +193.242.125.156 +193.242.125.186 +193.242.125.189 +193.242.125.197 +193.242.125.198 +193.242.125.217 +193.242.125.221 +193.242.125.223 +193.242.125.232 +193.242.125.241 +193.242.125.251 +193.242.125.254 +193.242.208.10 +193.242.208.21 +193.242.208.94 +193.242.208.110 +193.242.208.124 +193.242.208.128 +193.242.208.166 +193.242.208.185 +193.242.208.245 +193.246.200.118 +193.246.200.121 +194.1.155.45 +194.5.175.15 +194.5.175.22 +194.5.175.24 +194.5.175.29 +194.5.175.33 +194.5.175.35 +194.5.175.37 +194.5.175.38 +194.5.175.42 +194.5.175.44 +194.5.175.46 +194.5.175.52 +194.5.175.61 +194.5.175.73 +194.5.175.74 +194.5.175.75 +194.5.175.76 +194.5.175.81 +194.5.175.89 +194.5.175.90 +194.5.175.104 +194.5.175.113 +194.5.175.114 +194.5.175.115 +194.5.175.117 +194.5.175.119 +194.5.175.132 +194.5.175.139 +194.5.175.144 +194.5.175.145 +194.5.175.146 +194.5.175.154 +194.5.175.157 +194.5.175.158 +194.5.175.164 +194.5.175.169 +194.5.175.170 +194.5.175.178 +194.5.175.181 +194.5.175.183 +194.5.175.186 +194.5.175.190 +194.5.175.200 +194.5.175.202 +194.5.175.216 +194.5.175.217 +194.5.175.236 +194.5.175.242 +194.5.175.244 +194.5.175.246 +194.5.175.247 +194.5.175.249 +194.5.175.251 +194.5.176.46 +194.5.176.203 +194.5.176.206 +194.5.177.120 +194.5.178.48 +194.5.178.81 +194.5.179.79 +194.5.179.181 +194.5.188.18 +194.5.188.40 +194.5.188.41 +194.5.188.42 +194.5.188.53 +194.5.188.56 +194.5.188.59 +194.5.188.63 +194.5.188.66 +194.5.188.76 +194.5.188.79 +194.5.188.83 +194.5.188.93 +194.5.188.112 +194.5.188.124 +194.5.188.126 +194.5.188.128 +194.5.188.132 +194.5.188.136 +194.5.188.147 +194.5.188.148 +194.5.188.159 +194.5.188.161 +194.5.188.169 +194.5.188.183 +194.5.188.193 +194.5.188.194 +194.5.188.198 +194.5.188.208 +194.5.188.214 +194.5.188.216 +194.5.188.222 +194.5.188.225 +194.5.188.226 +194.5.188.230 +194.5.188.231 +194.5.188.236 +194.5.188.237 +194.5.188.240 +194.5.188.241 +194.5.188.242 +194.5.188.243 +194.5.188.246 +194.5.188.247 +194.5.188.251 +194.5.188.252 +194.5.195.14 +194.5.195.20 +194.5.195.23 +194.5.195.24 +194.5.195.33 +194.5.195.37 +194.5.195.50 +194.5.195.53 +194.5.195.62 +194.5.195.66 +194.5.195.69 +194.5.195.76 +194.5.195.79 +194.5.195.80 +194.5.195.89 +194.5.195.91 +194.5.195.92 +194.5.195.93 +194.5.195.96 +194.5.195.100 +194.5.195.101 +194.5.195.104 +194.5.195.117 +194.5.195.120 +194.5.195.126 +194.5.195.129 +194.5.195.133 +194.5.195.134 +194.5.195.143 +194.5.195.146 +194.5.195.152 +194.5.195.153 +194.5.195.154 +194.5.195.155 +194.5.195.156 +194.5.195.158 +194.5.195.161 +194.5.195.162 +194.5.195.163 +194.5.195.164 +194.5.195.167 +194.5.195.168 +194.5.195.172 +194.5.195.175 +194.5.195.176 +194.5.195.185 +194.5.195.188 +194.5.195.191 +194.5.195.192 +194.5.195.200 +194.5.195.205 +194.5.195.215 +194.5.195.219 +194.5.195.221 +194.5.195.226 +194.5.195.227 +194.5.195.234 +194.5.195.244 +194.5.195.247 +194.5.195.248 +194.5.195.249 +194.5.205.10 +194.5.205.11 +194.5.205.12 +194.5.205.16 +194.5.205.31 +194.5.205.32 +194.5.205.34 +194.5.205.36 +194.5.205.37 +194.5.205.39 +194.5.205.40 +194.5.205.64 +194.5.205.70 +194.5.205.85 +194.5.205.86 +194.5.205.88 +194.5.205.89 +194.5.205.91 +194.5.205.92 +194.5.205.93 +194.5.205.97 +194.5.205.110 +194.5.205.117 +194.5.205.121 +194.5.205.128 +194.5.205.149 +194.5.205.150 +194.5.205.158 +194.5.205.161 +194.5.205.163 +194.5.205.166 +194.5.205.168 +194.5.205.169 +194.5.205.170 +194.5.205.172 +194.5.205.176 +194.5.205.178 +194.5.205.179 +194.5.205.181 +194.5.205.191 +194.5.205.192 +194.5.205.193 +194.5.205.200 +194.5.205.203 +194.5.205.213 +194.5.205.214 +194.5.205.231 +194.5.205.237 +194.5.205.239 +194.5.205.247 +194.5.205.249 +194.5.205.250 +194.9.56.58 +194.9.56.62 +194.9.56.102 +194.9.56.124 +194.9.56.138 +194.9.57.212 +194.9.80.10 +194.9.80.19 +194.9.80.45 +194.9.80.58 +194.9.80.75 +194.9.80.153 +194.9.80.154 +194.9.80.155 +194.9.80.156 +194.9.80.157 +194.9.80.158 +194.9.80.160 +194.9.80.193 +194.9.80.202 +194.9.81.51 +194.9.81.131 +194.31.108.2 +194.31.108.3 +194.31.108.4 +194.31.108.7 +194.31.108.8 +194.31.108.9 +194.31.108.14 +194.31.108.15 +194.31.108.16 +194.31.108.24 +194.31.108.27 +194.31.108.28 +194.31.108.83 +194.31.108.88 +194.31.194.2 +194.31.194.36 +194.31.194.37 +194.31.194.42 +194.31.194.100 +194.31.194.105 +194.31.194.114 +194.31.194.125 +194.31.194.130 +194.31.194.143 +194.31.194.145 +194.31.194.146 +194.31.194.148 +194.31.194.158 +194.31.194.163 +194.31.194.178 +194.31.194.194 +194.31.194.195 +194.31.194.205 +194.31.194.217 +194.31.194.221 +194.33.105.28 +194.33.105.69 +194.33.105.71 +194.33.105.72 +194.33.105.134 +194.33.105.139 +194.33.105.152 +194.33.105.168 +194.33.105.169 +194.33.105.206 +194.33.105.209 +194.33.105.222 +194.33.105.232 +194.33.105.234 +194.33.106.103 +194.33.107.107 +194.33.107.108 +194.33.107.240 +194.34.162.150 +194.34.163.53 +194.34.163.54 +194.36.174.2 +194.36.174.31 +194.36.174.56 +194.36.174.57 +194.36.174.64 +194.36.174.78 +194.36.174.80 +194.36.174.108 +194.36.174.110 +194.36.174.111 +194.36.174.151 +194.36.174.152 +194.36.174.153 +194.36.174.161 +194.36.174.162 +194.36.174.167 +194.36.174.172 +194.36.174.175 +194.36.174.190 +194.36.174.200 +194.36.174.209 +194.36.174.231 +194.39.254.42 +194.39.254.43 +194.39.254.50 +194.39.254.51 +194.39.254.52 +194.39.254.53 +194.39.254.59 +194.41.48.20 +194.41.51.34 +194.48.198.15 +194.48.198.16 +194.48.198.18 +194.48.198.29 +194.48.198.37 +194.48.198.38 +194.48.198.40 +194.48.198.65 +194.48.198.70 +194.48.198.74 +194.48.198.75 +194.48.198.85 +194.48.198.92 +194.48.198.93 +194.48.198.95 +194.48.198.100 +194.48.198.105 +194.48.198.108 +194.48.198.121 +194.48.198.133 +194.48.198.145 +194.48.198.147 +194.48.198.164 +194.48.198.178 +194.48.198.193 +194.48.198.206 +194.48.198.208 +194.48.198.216 +194.48.198.226 +194.48.198.232 +194.48.198.234 +194.48.198.239 +194.48.198.244 +194.48.198.248 +194.50.169.217 +194.53.122.10 +194.53.122.11 +194.53.122.13 +194.53.122.14 +194.53.122.15 +194.53.122.16 +194.53.122.17 +194.53.122.18 +194.53.122.19 +194.53.122.20 +194.53.122.21 +194.53.122.25 +194.53.122.26 +194.53.122.27 +194.53.122.28 +194.53.122.29 +194.53.122.30 +194.53.122.31 +194.53.122.32 +194.53.122.33 +194.53.122.34 +194.53.122.35 +194.53.122.36 +194.53.122.37 +194.53.122.38 +194.53.122.39 +194.53.122.40 +194.53.122.42 +194.53.122.43 +194.53.122.44 +194.53.122.45 +194.53.122.46 +194.53.122.47 +194.53.122.48 +194.53.122.49 +194.53.122.50 +194.53.122.51 +194.53.122.52 +194.53.122.53 +194.53.122.54 +194.53.122.55 +194.53.122.56 +194.53.122.57 +194.53.122.58 +194.53.122.59 +194.53.122.60 +194.53.122.62 +194.53.122.63 +194.53.122.64 +194.53.122.65 +194.53.122.66 +194.53.122.67 +194.53.122.68 +194.53.122.69 +194.53.122.71 +194.53.122.72 +194.53.122.73 +194.53.122.74 +194.53.122.77 +194.53.122.78 +194.53.122.79 +194.53.122.80 +194.53.122.81 +194.53.122.82 +194.53.122.84 +194.53.122.85 +194.53.122.86 +194.53.122.87 +194.53.122.88 +194.53.122.89 +194.53.122.90 +194.53.122.91 +194.53.122.92 +194.53.122.93 +194.53.122.94 +194.53.122.95 +194.53.122.96 +194.53.122.97 +194.53.122.98 +194.53.122.99 +194.53.122.100 +194.53.122.102 +194.53.122.103 +194.53.122.104 +194.53.122.105 +194.53.122.106 +194.53.122.107 +194.53.122.108 +194.53.122.109 +194.53.122.110 +194.53.122.111 +194.53.122.112 +194.53.122.113 +194.53.122.114 +194.53.122.115 +194.53.122.116 +194.53.122.117 +194.53.122.118 +194.53.122.120 +194.53.122.121 +194.53.122.122 +194.53.122.124 +194.53.122.125 +194.53.122.127 +194.53.122.128 +194.53.122.129 +194.53.122.130 +194.53.122.131 +194.53.122.132 +194.53.122.133 +194.53.122.134 +194.53.122.135 +194.53.122.136 +194.53.122.137 +194.53.122.138 +194.53.122.139 +194.53.122.140 +194.53.122.142 +194.53.122.144 +194.53.122.145 +194.53.122.146 +194.53.122.147 +194.53.122.148 +194.53.122.149 +194.53.122.151 +194.53.122.152 +194.53.122.153 +194.53.122.154 +194.53.122.155 +194.53.122.156 +194.53.122.157 +194.53.122.158 +194.53.122.159 +194.53.122.160 +194.53.122.161 +194.53.122.162 +194.53.122.163 +194.53.122.164 +194.53.122.165 +194.53.122.166 +194.53.122.167 +194.53.122.168 +194.53.122.169 +194.53.122.170 +194.53.122.171 +194.53.122.172 +194.53.122.173 +194.53.122.174 +194.53.122.175 +194.53.122.176 +194.53.122.177 +194.53.122.179 +194.53.122.180 +194.53.122.181 +194.53.122.182 +194.53.122.183 +194.53.122.184 +194.53.122.185 +194.53.122.186 +194.53.122.187 +194.53.122.188 +194.53.122.189 +194.53.122.190 +194.53.122.191 +194.53.122.192 +194.53.122.193 +194.53.122.194 +194.53.122.195 +194.53.122.196 +194.53.122.197 +194.53.122.198 +194.53.122.199 +194.53.122.200 +194.53.122.201 +194.53.122.202 +194.53.122.203 +194.53.122.204 +194.53.122.205 +194.53.122.206 +194.53.122.207 +194.53.122.208 +194.53.122.209 +194.53.122.210 +194.53.122.211 +194.53.122.212 +194.53.122.213 +194.53.122.214 +194.53.122.215 +194.53.122.216 +194.53.122.217 +194.53.122.218 +194.53.122.219 +194.53.122.220 +194.53.122.221 +194.53.122.222 +194.53.122.223 +194.53.122.225 +194.53.122.226 +194.53.122.227 +194.53.122.228 +194.53.122.229 +194.53.122.230 +194.53.122.231 +194.53.122.232 +194.53.122.233 +194.53.122.234 +194.53.122.235 +194.53.122.236 +194.53.122.237 +194.53.122.238 +194.53.122.239 +194.53.122.240 +194.53.122.241 +194.53.122.242 +194.53.122.243 +194.53.122.244 +194.53.122.245 +194.53.122.246 +194.53.122.247 +194.53.122.249 +194.53.122.250 +194.56.148.22 +194.56.148.76 +194.56.148.179 +194.56.148.222 +194.59.170.5 +194.59.170.6 +194.59.170.15 +194.59.170.19 +194.59.170.21 +194.59.170.22 +194.59.170.23 +194.59.170.24 +194.59.170.25 +194.59.170.26 +194.59.170.27 +194.59.170.28 +194.59.170.29 +194.59.170.30 +194.59.170.49 +194.59.170.60 +194.59.170.61 +194.59.170.75 +194.59.170.82 +194.59.170.83 +194.59.170.90 +194.59.170.100 +194.59.170.115 +194.59.170.117 +194.59.170.119 +194.59.170.120 +194.59.170.132 +194.59.170.138 +194.59.170.144 +194.59.170.146 +194.59.170.154 +194.59.170.166 +194.59.170.174 +194.59.170.192 +194.59.170.207 +194.59.170.228 +194.59.170.232 +194.59.170.243 +194.59.171.11 +194.59.171.53 +194.59.171.153 +194.59.171.154 +194.59.171.185 +194.59.214.2 +194.59.214.3 +194.59.214.4 +194.59.214.7 +194.59.214.10 +194.59.214.30 +194.59.214.31 +194.59.214.35 +194.59.214.36 +194.59.214.37 +194.59.214.38 +194.59.214.55 +194.59.214.71 +194.59.214.74 +194.59.214.75 +194.59.214.76 +194.59.214.85 +194.59.214.91 +194.59.214.92 +194.59.214.93 +194.59.214.99 +194.59.214.100 +194.59.214.102 +194.59.214.103 +194.59.214.104 +194.59.214.105 +194.59.214.106 +194.59.214.108 +194.59.214.110 +194.59.214.116 +194.59.214.120 +194.59.214.121 +194.59.214.123 +194.59.214.124 +194.59.214.125 +194.59.214.129 +194.59.214.148 +194.59.214.160 +194.59.214.161 +194.59.214.162 +194.59.214.163 +194.59.214.164 +194.59.214.165 +194.59.214.166 +194.59.214.174 +194.59.214.176 +194.59.214.195 +194.59.214.200 +194.59.214.202 +194.59.214.203 +194.59.214.205 +194.59.214.209 +194.59.214.210 +194.59.215.11 +194.59.215.36 +194.59.215.37 +194.59.215.49 +194.59.215.51 +194.59.215.242 +194.59.215.246 +194.60.209.91 +194.60.210.66 +194.60.210.67 +194.60.230.25 +194.60.230.28 +194.60.230.50 +194.60.230.53 +194.60.230.55 +194.60.230.58 +194.60.230.75 +194.60.230.78 +194.60.230.84 +194.60.230.88 +194.60.230.92 +194.60.230.148 +194.60.230.161 +194.60.230.174 +194.60.230.194 +194.60.230.195 +194.60.230.203 +194.60.230.208 +194.60.231.12 +194.60.231.30 +194.60.231.34 +194.60.231.47 +194.60.231.77 +194.60.231.84 +194.60.231.112 +194.60.231.122 +194.60.231.149 +194.60.231.156 +194.60.231.157 +194.60.231.182 +194.60.231.196 +194.60.231.234 +194.62.17.18 +194.62.17.22 +194.62.17.26 +194.62.17.43 +194.62.17.50 +194.62.17.59 +194.62.17.60 +194.62.17.70 +194.62.17.146 +194.62.17.155 +194.62.17.174 +194.62.17.178 +194.62.17.190 +194.62.17.203 +194.62.17.206 +194.62.17.212 +194.62.17.234 +194.62.17.254 +194.62.43.5 +194.62.43.6 +194.62.43.14 +194.62.43.15 +194.62.43.44 +194.62.43.45 +194.62.43.55 +194.62.43.58 +194.62.43.66 +194.62.43.67 +194.62.43.80 +194.62.43.83 +194.62.43.91 +194.62.43.92 +194.62.43.94 +194.62.43.99 +194.62.43.100 +194.62.43.101 +194.62.43.102 +194.62.43.104 +194.62.43.108 +194.62.43.123 +194.62.43.126 +194.62.43.129 +194.62.43.139 +194.62.43.140 +194.62.43.141 +194.62.43.146 +194.62.43.155 +194.62.43.175 +194.62.43.180 +194.62.43.183 +194.62.43.184 +194.62.43.194 +194.62.43.207 +194.62.43.208 +194.62.43.216 +194.62.43.218 +194.62.43.227 +194.62.43.233 +194.62.43.236 +194.62.43.237 +194.62.43.238 +194.62.43.240 +194.62.43.241 +194.143.140.9 +194.143.141.9 +194.146.151.18 +194.146.151.19 +194.146.151.20 +194.146.151.22 +194.146.239.28 +194.146.239.32 +194.146.239.35 +194.146.239.39 +194.146.239.40 +194.146.239.41 +194.146.239.44 +194.146.239.57 +194.146.239.58 +194.146.239.60 +194.146.239.65 +194.146.239.68 +194.146.239.70 +194.146.239.77 +194.146.239.78 +194.146.239.79 +194.146.239.111 +194.146.239.124 +194.146.239.154 +194.146.239.155 +194.146.239.165 +194.146.239.171 +194.146.239.173 +194.146.239.178 +194.146.239.194 +194.146.239.197 +194.146.239.200 +194.147.142.10 +194.147.142.29 +194.147.142.35 +194.147.142.39 +194.147.142.41 +194.147.142.44 +194.147.142.57 +194.147.142.64 +194.147.142.67 +194.147.142.71 +194.147.142.72 +194.147.142.78 +194.147.142.79 +194.147.142.82 +194.147.142.94 +194.147.142.100 +194.147.142.104 +194.147.142.122 +194.147.142.123 +194.147.142.125 +194.147.142.131 +194.147.142.136 +194.147.142.153 +194.147.142.174 +194.147.142.180 +194.147.142.186 +194.147.142.192 +194.147.142.195 +194.147.142.200 +194.147.142.213 +194.147.142.218 +194.147.142.233 +194.147.142.239 +194.147.142.242 +194.147.142.243 +194.147.142.250 +194.147.164.49 +194.147.222.37 +194.147.222.53 +194.147.222.68 +194.147.222.76 +194.147.222.82 +194.147.222.84 +194.147.222.85 +194.147.222.86 +194.147.222.87 +194.147.222.94 +194.147.222.96 +194.147.222.131 +194.147.222.137 +194.147.222.143 +194.147.222.144 +194.147.222.146 +194.147.222.150 +194.147.222.153 +194.147.222.162 +194.147.222.176 +194.147.222.228 +194.147.222.229 +194.147.222.230 +194.150.68.28 +194.150.68.29 +194.150.68.44 +194.150.68.45 +194.150.68.51 +194.150.69.119 +194.150.69.164 +194.150.70.11 +194.150.70.12 +194.150.71.198 +194.150.71.230 +194.180.11.12 +194.180.11.14 +194.180.11.17 +194.180.11.26 +194.180.11.28 +194.180.11.37 +194.180.11.43 +194.180.11.56 +194.180.11.64 +194.180.11.79 +194.180.11.85 +194.180.11.94 +194.180.11.110 +194.180.11.117 +194.180.11.118 +194.180.11.128 +194.180.11.134 +194.180.11.142 +194.180.11.148 +194.180.11.153 +194.180.11.177 +194.180.11.185 +194.180.11.204 +194.180.11.206 +194.180.11.212 +194.180.11.213 +194.180.11.216 +194.180.11.217 +194.180.11.219 +194.180.11.225 +194.180.11.226 +194.180.11.229 +194.180.11.233 +194.180.11.236 +194.180.11.243 +194.180.11.254 +194.225.16.3 +194.225.24.20 +194.225.25.15 +194.225.25.16 +194.225.40.49 +194.225.40.114 +194.225.50.6 +194.225.62.66 +194.225.62.80 +194.225.62.98 +194.225.62.107 +194.225.73.141 +194.225.73.151 +194.225.90.4 +194.225.90.5 +194.225.92.15 +194.225.92.17 +194.225.100.42 +194.225.100.43 +194.225.106.21 +194.225.106.22 +194.225.115.9 +194.225.115.10 +194.225.117.10 +194.225.131.64 +194.225.133.40 +194.225.136.3 +194.225.136.4 +194.225.144.2 +194.225.144.3 +194.225.152.10 +194.225.152.12 +194.225.166.7 +194.225.166.8 +194.225.183.61 +194.225.183.62 +194.225.228.4 +194.225.228.5 +195.24.233.75 +195.28.11.3 +195.28.11.27 +195.28.11.35 +195.28.11.37 +195.28.11.47 +195.28.11.58 +195.28.11.60 +195.28.11.63 +195.28.11.64 +195.28.11.67 +195.28.11.68 +195.28.11.70 +195.28.11.99 +195.28.11.104 +195.28.11.109 +195.28.11.156 +195.28.11.179 +195.28.11.220 +195.28.11.236 +195.28.11.249 +195.28.11.250 +195.28.11.251 +195.28.11.253 +195.28.169.201 +195.28.169.248 +195.28.169.253 +195.62.4.20 +195.62.4.21 +195.62.4.22 +195.62.4.24 +195.62.4.25 +195.62.4.26 +195.88.189.70 +195.88.189.71 +195.88.189.74 +195.88.189.76 +195.88.189.77 +195.88.189.79 +195.88.189.81 +195.88.189.84 +195.88.189.86 +195.88.189.91 +195.88.189.98 +195.88.189.101 +195.88.189.110 +195.88.189.111 +195.88.189.112 +195.88.208.7 +195.88.208.9 +195.88.208.15 +195.88.208.17 +195.88.208.28 +195.88.208.49 +195.88.208.50 +195.88.208.53 +195.88.208.67 +195.88.208.82 +195.88.208.107 +195.88.208.111 +195.88.208.112 +195.88.208.115 +195.88.208.123 +195.88.208.125 +195.88.208.130 +195.88.208.134 +195.88.208.139 +195.88.208.141 +195.88.208.143 +195.88.208.147 +195.88.208.149 +195.88.208.157 +195.88.208.164 +195.88.208.167 +195.88.208.180 +195.88.208.181 +195.88.208.198 +195.88.208.204 +195.88.208.207 +195.88.208.210 +195.88.208.216 +195.88.208.236 +195.88.208.237 +195.88.208.241 +195.96.128.24 +195.96.128.130 +195.96.128.144 +195.96.128.148 +195.110.38.129 +195.110.38.141 +195.110.38.156 +195.110.38.190 +195.110.38.203 +195.110.38.205 +195.110.38.214 +195.110.38.217 +195.110.38.223 +195.110.38.230 +195.110.38.232 +195.110.38.234 +195.110.38.244 +195.110.38.245 +195.110.38.246 +195.110.38.248 +195.110.39.192 +195.110.39.194 +195.110.39.195 +195.110.39.198 +195.110.39.199 +195.110.39.220 +195.110.39.221 +195.110.39.222 +195.110.39.223 +195.114.4.16 +195.114.4.21 +195.114.4.23 +195.114.4.25 +195.114.4.117 +195.114.4.180 +195.114.4.182 +195.114.5.243 +195.114.8.10 +195.146.59.62 +195.146.59.249 +195.146.59.253 +195.146.60.2 +195.177.255.4 +195.177.255.15 +195.177.255.30 +195.177.255.47 +195.177.255.54 +195.177.255.60 +195.177.255.64 +195.177.255.74 +195.177.255.89 +195.177.255.90 +195.177.255.95 +195.177.255.103 +195.177.255.120 +195.177.255.125 +195.177.255.126 +195.177.255.132 +195.177.255.136 +195.177.255.141 +195.177.255.142 +195.177.255.164 +195.177.255.169 +195.177.255.170 +195.177.255.180 +195.177.255.184 +195.177.255.187 +195.177.255.201 +195.177.255.205 +195.177.255.208 +195.177.255.217 +195.177.255.226 +195.177.255.253 +195.181.12.184 +195.181.41.225 +195.181.41.229 +195.181.81.185 +195.181.84.14 +195.181.91.99 +195.191.74.141 +195.200.77.125 +195.200.77.134 +195.200.77.135 +195.200.77.136 +195.200.77.194 +195.200.77.227 +195.200.77.230 +195.200.77.242 +195.200.77.243 +195.200.77.244 +195.200.77.245 +195.200.77.251 +195.211.44.16 +195.211.44.17 +195.211.44.21 +195.211.44.24 +195.211.44.41 +195.211.44.69 +195.211.44.76 +195.211.44.96 +195.211.44.104 +195.211.44.107 +195.211.44.120 +195.211.44.136 +195.211.44.138 +195.211.44.160 +195.211.44.161 +195.211.44.162 +195.211.44.173 +195.211.44.211 +195.211.44.216 +195.211.44.218 +195.211.44.223 +195.211.44.227 +195.211.44.232 +195.211.44.235 +195.211.45.169 +195.211.45.184 +195.211.45.190 +195.211.45.230 +195.211.45.239 +195.211.45.240 +195.211.45.255 +195.211.46.64 +195.211.46.65 +195.211.46.111 +195.211.46.117 +195.211.46.211 +195.211.46.212 +195.211.46.242 +195.211.47.24 +195.211.47.27 +195.211.47.65 +195.211.47.199 +195.211.47.213 +195.211.47.220 +195.214.235.3 +195.214.235.4 +195.214.235.5 +195.214.235.11 +195.214.235.12 +195.214.235.13 +195.214.235.14 +195.214.235.19 +195.214.235.20 +195.214.235.21 +195.214.235.22 +195.214.235.46 +195.214.235.56 +195.214.235.57 +195.214.235.62 +195.214.235.92 +195.214.235.99 +195.214.235.100 +195.214.235.101 +195.214.235.102 +195.214.235.114 +195.214.235.116 +195.214.235.117 +195.214.235.118 +195.214.235.119 +195.214.235.121 +195.214.235.122 +195.214.235.123 +195.214.235.124 +195.214.235.141 +195.214.235.142 +195.214.235.147 +195.214.235.148 +195.214.235.163 +195.214.235.164 +195.214.235.171 +195.214.235.172 +195.214.235.179 +195.214.235.180 +195.214.235.181 +195.214.235.182 +195.214.235.206 +195.214.235.216 +195.226.223.3 +195.226.223.6 +195.226.223.11 +195.226.223.13 +195.226.223.14 +195.226.223.44 +195.226.223.70 +195.226.223.100 +195.226.223.131 +195.226.223.134 +195.226.223.135 +195.226.223.140 +195.226.223.147 +195.226.223.155 +195.226.223.157 +195.226.223.211 +195.226.223.213 +195.230.107.2 +195.230.107.49 +195.230.107.130 +195.234.191.130 +195.234.191.133 +195.234.191.137 +195.234.191.138 +195.234.191.141 +195.234.191.144 +195.234.191.197 +195.234.191.200 +195.238.231.8 +195.238.231.55 +195.238.231.56 +195.238.231.132 +195.238.231.133 +195.238.231.134 +195.238.231.146 +195.238.231.187 +195.238.231.234 +195.238.231.245 +195.238.240.2 +195.238.240.5 +195.238.240.19 +195.238.240.22 +195.238.240.91 +195.238.240.215 +195.245.70.210 +212.16.64.74 +212.16.68.4 +212.16.70.4 +212.16.70.8 +212.16.70.19 +212.16.76.19 +212.16.76.72 +212.16.76.117 +212.16.76.119 +212.16.76.120 +212.16.76.250 +212.16.82.16 +212.16.84.147 +212.16.84.148 +212.16.86.4 +212.16.86.15 +212.16.88.21 +212.16.88.22 +212.16.89.20 +212.16.89.25 +212.16.95.143 +212.18.108.115 +212.18.108.131 +212.18.108.132 +212.18.108.173 +212.18.108.174 +212.18.108.188 +212.18.108.189 +212.18.108.190 +212.18.108.203 +212.18.108.205 +212.18.108.211 +212.18.108.212 +212.18.108.214 +212.18.108.228 +212.18.108.237 +212.23.201.8 +212.23.201.10 +212.23.201.13 +212.23.201.15 +212.23.201.27 +212.23.201.29 +212.23.201.30 +212.23.201.36 +212.23.201.41 +212.23.201.44 +212.23.201.50 +212.23.201.53 +212.23.201.57 +212.23.201.58 +212.23.201.70 +212.23.201.72 +212.23.201.73 +212.23.201.74 +212.23.201.75 +212.23.201.76 +212.23.201.86 +212.23.201.91 +212.23.201.92 +212.23.201.93 +212.23.201.95 +212.23.201.98 +212.23.201.101 +212.23.201.103 +212.23.201.104 +212.23.201.107 +212.23.201.114 +212.23.201.116 +212.23.201.118 +212.23.201.122 +212.23.201.123 +212.23.201.133 +212.23.201.135 +212.23.201.143 +212.23.201.147 +212.23.201.151 +212.23.201.154 +212.23.201.158 +212.23.201.159 +212.23.201.162 +212.23.201.165 +212.23.201.168 +212.23.201.170 +212.23.201.172 +212.23.201.174 +212.23.201.176 +212.23.201.177 +212.23.201.182 +212.23.201.186 +212.23.201.192 +212.23.201.196 +212.23.201.203 +212.23.201.205 +212.23.201.208 +212.23.201.216 +212.23.201.218 +212.23.201.220 +212.23.201.225 +212.23.201.232 +212.23.201.248 +212.23.201.251 +212.23.216.25 +212.23.216.55 +212.23.216.56 +212.23.216.60 +212.23.216.100 +212.23.216.117 +212.23.216.121 +212.23.216.125 +212.23.216.150 +212.23.216.152 +212.33.194.26 +212.33.194.27 +212.33.194.28 +212.33.194.29 +212.33.194.30 +212.33.194.58 +212.33.194.60 +212.33.194.108 +212.33.194.148 +212.33.194.149 +212.33.195.66 +212.33.195.80 +212.33.195.85 +212.33.195.86 +212.33.195.88 +212.33.195.91 +212.33.195.92 +212.33.195.93 +212.33.195.94 +212.33.195.165 +212.33.195.166 +212.33.195.168 +212.33.195.252 +212.33.197.69 +212.33.197.71 +212.33.197.82 +212.33.197.85 +212.33.197.92 +212.33.197.94 +212.33.197.100 +212.33.197.124 +212.33.197.131 +212.33.198.15 +212.33.198.57 +212.33.198.60 +212.33.198.90 +212.33.198.227 +212.33.201.26 +212.33.201.27 +212.33.201.55 +212.33.201.133 +212.33.201.143 +212.33.201.167 +212.33.201.215 +212.33.201.220 +212.33.202.21 +212.33.202.24 +212.33.202.76 +212.33.202.97 +212.33.202.107 +212.33.202.113 +212.33.202.122 +212.33.202.153 +212.33.202.167 +212.33.202.192 +212.33.202.213 +212.33.202.214 +212.33.202.215 +212.33.202.216 +212.33.202.227 +212.33.203.10 +212.33.203.92 +212.33.203.186 +212.33.203.187 +212.33.203.188 +212.33.203.189 +212.33.203.190 +212.33.203.226 +212.33.204.93 +212.33.205.21 +212.33.205.55 +212.33.205.81 +212.33.205.82 +212.33.205.95 +212.33.205.96 +212.33.205.99 +212.33.205.110 +212.33.205.186 +212.33.205.223 +212.33.205.254 +212.33.206.11 +212.33.206.20 +212.33.206.46 +212.33.206.55 +212.33.206.93 +212.33.206.138 +212.33.206.140 +212.33.206.156 +212.33.206.168 +212.33.206.199 +212.33.206.205 +212.33.206.239 +212.33.217.224 +212.33.222.179 +212.80.5.205 +212.80.8.70 +212.80.8.84 +212.80.8.103 +212.80.8.105 +212.80.8.115 +212.80.8.212 +212.80.9.228 +212.80.9.229 +212.80.9.230 +212.80.9.231 +212.80.9.235 +212.80.10.252 +212.80.10.253 +212.80.16.72 +212.80.18.83 +212.80.18.182 +212.80.18.213 +212.80.19.2 +212.80.19.12 +212.80.19.244 +212.80.20.92 +212.80.20.93 +212.80.20.101 +212.80.20.132 +212.80.20.134 +212.80.20.165 +212.80.21.10 +212.80.21.68 +212.80.21.157 +212.80.21.179 +212.80.21.184 +212.80.22.18 +212.80.22.22 +212.80.25.138 +212.80.25.217 +212.80.27.250 +212.80.30.189 +212.80.30.190 +212.86.64.99 +212.86.64.100 +212.86.64.193 +212.86.66.11 +212.86.66.50 +212.86.66.90 +212.86.66.96 +212.86.67.138 +212.86.67.147 +212.86.67.149 +212.86.74.64 +212.86.74.148 +212.86.76.69 +212.86.77.54 +212.86.79.180 +212.86.79.184 +212.86.79.188 +212.86.79.189 +212.120.192.7 +212.120.192.10 +212.120.192.13 +212.120.192.21 +212.120.198.165 +212.120.200.58 +212.120.201.176 +212.120.205.150 +212.120.208.80 +212.120.211.206 +212.120.214.42 +212.120.214.61 +212.120.214.230 +212.120.223.248 +213.108.240.52 +213.108.240.53 +213.108.240.54 +213.108.240.67 +213.108.240.82 +213.108.240.86 +213.108.240.91 +213.108.240.131 +213.108.240.132 +213.108.240.134 +213.108.240.136 +213.108.240.141 +213.108.240.143 +213.108.240.144 +213.108.240.145 +213.108.240.151 +213.108.240.152 +213.108.240.155 +213.108.240.157 +213.108.240.158 +213.108.240.162 +213.108.240.167 +213.108.240.169 +213.108.240.170 +213.108.240.173 +213.108.240.180 +213.108.240.194 +213.108.240.204 +213.108.240.205 +213.108.241.73 +213.108.241.106 +213.108.241.110 +213.108.241.132 +213.108.241.133 +213.108.241.135 +213.108.241.140 +213.108.241.145 +213.108.241.146 +213.108.241.155 +213.108.241.196 +213.108.241.199 +213.108.241.200 +213.108.241.216 +213.108.241.235 +213.108.241.237 +213.108.241.243 +213.108.242.211 +213.108.242.215 +213.108.242.216 +213.108.242.217 +213.108.242.219 +213.108.242.250 +213.109.199.133 +213.176.1.165 +213.176.1.194 +213.176.5.20 +213.176.5.21 +213.176.6.146 +213.176.6.153 +213.176.6.198 +213.176.7.126 +213.176.7.151 +213.176.28.37 +213.176.28.45 +213.176.28.91 +213.176.28.143 +213.176.28.149 +213.176.28.151 +213.176.28.170 +213.176.28.199 +213.176.28.231 +213.176.28.245 +213.176.28.247 +213.176.29.6 +213.176.29.7 +213.176.29.8 +213.176.29.9 +213.176.29.26 +213.176.29.31 +213.176.29.32 +213.176.29.33 +213.176.29.34 +213.176.29.35 +213.176.29.58 +213.176.29.63 +213.176.29.66 +213.176.29.75 +213.176.29.78 +213.176.29.85 +213.176.29.87 +213.176.29.88 +213.176.29.89 +213.176.29.107 +213.176.29.108 +213.176.29.115 +213.176.29.121 +213.176.29.124 +213.176.29.125 +213.176.29.126 +213.176.29.128 +213.176.29.132 +213.176.29.133 +213.176.29.141 +213.176.29.142 +213.176.29.145 +213.176.29.151 +213.176.29.158 +213.176.29.159 +213.176.29.161 +213.176.29.162 +213.176.29.163 +213.176.29.189 +213.176.29.192 +213.176.29.201 +213.176.29.207 +213.176.29.208 +213.176.29.209 +213.176.29.212 +213.176.29.214 +213.176.29.219 +213.176.29.229 +213.176.29.236 +213.176.29.243 +213.176.29.244 +213.176.29.246 +213.176.31.7 +213.176.31.66 +213.176.31.74 +213.176.31.98 +213.176.31.99 +213.176.31.110 +213.176.31.122 +213.176.31.154 +213.176.31.214 +213.176.120.9 +213.176.120.34 +213.176.120.40 +213.176.120.114 +213.176.120.130 +213.176.121.78 +213.176.123.5 +213.195.22.3 +213.195.22.4 +213.195.22.106 +213.195.22.238 +213.195.56.113 +213.195.58.167 +213.207.192.19 +213.207.198.66 +213.207.203.90 +213.207.203.211 +213.207.204.10 +213.207.204.66 +213.207.209.21 +213.207.250.50 +213.207.250.59 +213.207.250.90 +213.232.124.4 +213.232.124.10 +213.232.124.34 +213.232.124.78 +213.232.124.154 +213.232.124.222 +213.232.125.115 +213.232.125.186 +213.232.126.49 +213.232.126.180 +213.232.126.235 +213.232.126.236 +213.232.126.237 +213.233.161.81 +213.233.161.190 +213.233.173.10 +213.233.176.155 +213.233.176.156 +213.233.177.172 +213.233.177.219 +213.233.177.221 +213.233.178.82 +213.233.178.102 +213.233.178.133 +213.233.178.226 +213.233.182.147 +213.233.182.236 +213.233.184.221 +217.11.18.100 +217.11.18.190 +217.11.19.10 +217.11.20.217 +217.11.20.227 +217.11.21.11 +217.11.23.6 +217.11.24.166 +217.11.27.116 +217.11.27.117 +217.11.28.51 +217.11.28.53 +217.11.29.178 +217.11.29.184 +217.11.29.186 +217.11.29.187 +217.11.29.188 +217.11.29.190 +217.11.29.191 +217.20.252.9 +217.20.252.12 +217.20.252.20 +217.20.252.24 +217.20.252.42 +217.20.252.68 +217.20.252.249 +217.24.147.254 +217.26.222.2 +217.26.222.234 +217.60.15.187 +217.60.236.60 +217.60.243.221 +217.60.243.244 +217.66.200.240 +217.66.215.114 +217.114.40.18 +217.114.40.19 +217.114.40.32 +217.114.40.34 +217.114.40.43 +217.114.40.49 +217.114.40.54 +217.114.40.75 +217.114.40.104 +217.114.40.107 +217.114.40.112 +217.114.40.203 +217.114.40.206 +217.114.40.215 +217.114.40.216 +217.114.40.246 +217.114.46.156 +217.114.46.157 +217.144.104.6 +217.144.104.7 +217.144.104.9 +217.144.104.11 +217.144.104.13 +217.144.104.14 +217.144.104.15 +217.144.104.16 +217.144.104.18 +217.144.104.19 +217.144.104.21 +217.144.104.22 +217.144.104.23 +217.144.104.25 +217.144.104.26 +217.144.104.27 +217.144.104.35 +217.144.104.37 +217.144.104.42 +217.144.104.44 +217.144.104.47 +217.144.104.49 +217.144.104.50 +217.144.104.54 +217.144.104.56 +217.144.104.58 +217.144.104.64 +217.144.104.65 +217.144.104.68 +217.144.104.69 +217.144.104.70 +217.144.104.74 +217.144.104.78 +217.144.104.80 +217.144.104.82 +217.144.104.83 +217.144.104.84 +217.144.104.87 +217.144.104.88 +217.144.104.92 +217.144.104.95 +217.144.104.97 +217.144.104.100 +217.144.104.101 +217.144.104.102 +217.144.104.106 +217.144.104.111 +217.144.104.112 +217.144.104.113 +217.144.104.119 +217.144.104.120 +217.144.104.122 +217.144.104.123 +217.144.104.127 +217.144.104.128 +217.144.104.129 +217.144.104.130 +217.144.104.131 +217.144.104.133 +217.144.104.134 +217.144.104.136 +217.144.104.139 +217.144.104.141 +217.144.104.142 +217.144.104.146 +217.144.104.155 +217.144.104.159 +217.144.104.160 +217.144.104.161 +217.144.104.162 +217.144.104.163 +217.144.104.166 +217.144.104.168 +217.144.104.170 +217.144.104.173 +217.144.104.174 +217.144.104.177 +217.144.104.178 +217.144.104.179 +217.144.104.180 +217.144.104.182 +217.144.104.184 +217.144.104.185 +217.144.104.189 +217.144.104.190 +217.144.104.191 +217.144.104.193 +217.144.104.197 +217.144.104.203 +217.144.104.205 +217.144.104.210 +217.144.104.212 +217.144.104.214 +217.144.104.217 +217.144.104.218 +217.144.104.222 +217.144.104.225 +217.144.104.226 +217.144.104.227 +217.144.104.234 +217.144.104.235 +217.144.104.242 +217.144.104.243 +217.144.104.249 +217.144.104.250 +217.144.104.254 +217.144.105.39 +217.144.105.42 +217.144.105.49 +217.144.105.52 +217.144.105.57 +217.144.105.58 +217.144.105.61 +217.144.105.63 +217.144.105.73 +217.144.105.77 +217.144.105.79 +217.144.105.80 +217.144.105.84 +217.144.105.90 +217.144.105.94 +217.144.105.101 +217.144.105.156 +217.144.105.163 +217.144.105.168 +217.144.105.169 +217.144.105.175 +217.144.105.176 +217.144.105.181 +217.144.105.195 +217.144.105.197 +217.144.105.198 +217.144.105.200 +217.144.105.205 +217.144.105.206 +217.144.105.207 +217.144.105.209 +217.144.105.210 +217.144.105.212 +217.144.105.217 +217.144.105.218 +217.144.105.221 +217.144.105.226 +217.144.105.230 +217.144.105.233 +217.144.105.252 +217.144.105.254 +217.144.106.7 +217.144.106.11 +217.144.106.16 +217.144.106.17 +217.144.106.18 +217.144.106.20 +217.144.106.21 +217.144.106.22 +217.144.106.26 +217.144.106.30 +217.144.106.31 +217.144.106.32 +217.144.106.36 +217.144.106.39 +217.144.106.40 +217.144.106.44 +217.144.106.48 +217.144.106.50 +217.144.106.54 +217.144.106.55 +217.144.106.59 +217.144.106.61 +217.144.106.62 +217.144.106.68 +217.144.106.93 +217.144.106.94 +217.144.106.100 +217.144.106.104 +217.144.106.110 +217.144.106.111 +217.144.106.113 +217.144.106.115 +217.144.106.116 +217.144.106.117 +217.144.106.118 +217.144.106.125 +217.144.106.126 +217.144.106.132 +217.144.106.138 +217.144.106.141 +217.144.106.143 +217.144.106.153 +217.144.106.155 +217.144.106.157 +217.144.106.201 +217.144.106.202 +217.144.106.203 +217.144.106.204 +217.144.106.205 +217.144.106.208 +217.144.106.212 +217.144.106.218 +217.144.106.222 +217.144.106.227 +217.144.106.236 +217.144.106.242 +217.144.106.252 +217.144.107.2 +217.144.107.4 +217.144.107.5 +217.144.107.6 +217.144.107.15 +217.144.107.20 +217.144.107.22 +217.144.107.52 +217.144.107.55 +217.144.107.61 +217.144.107.70 +217.144.107.83 +217.144.107.85 +217.144.107.94 +217.144.107.96 +217.144.107.98 +217.144.107.104 +217.144.107.111 +217.144.107.113 +217.144.107.119 +217.144.107.126 +217.144.107.127 +217.144.107.130 +217.144.107.132 +217.144.107.139 +217.144.107.147 +217.144.107.148 +217.144.107.149 +217.144.107.159 +217.144.107.160 +217.144.107.161 +217.144.107.162 +217.144.107.163 +217.144.107.166 +217.144.107.169 +217.144.107.171 +217.144.107.174 +217.144.107.176 +217.144.107.181 +217.144.107.182 +217.144.107.183 +217.144.107.184 +217.144.107.186 +217.144.107.194 +217.144.107.195 +217.144.107.197 +217.144.107.200 +217.144.107.203 +217.144.107.205 +217.144.107.208 +217.144.107.226 +217.144.107.228 +217.144.107.230 +217.144.107.235 +217.144.107.239 +217.144.107.241 +217.144.107.242 +217.144.107.246 +217.144.107.247 +217.146.209.50 +217.170.251.26 +217.170.252.10 +217.170.252.47 +217.170.252.155 +217.170.253.10 +217.172.98.71 +217.172.98.72 +217.172.98.74 +217.172.98.75 +217.172.98.77 +217.172.98.78 +217.172.98.79 +217.172.98.87 +217.172.98.90 +217.172.98.91 +217.172.98.92 +217.172.98.93 +217.172.98.95 +217.172.98.96 +217.172.98.97 +217.172.98.98 +217.172.98.102 +217.172.98.108 +217.172.98.109 +217.172.98.111 +217.172.98.116 +217.172.98.124 +217.172.98.132 +217.172.98.138 +217.172.98.147 +217.172.98.148 +217.172.98.149 +217.172.98.158 +217.172.98.160 +217.172.98.161 +217.172.98.162 +217.172.98.163 +217.172.98.169 +217.172.98.172 +217.172.98.175 +217.172.98.228 +217.172.98.231 +217.172.98.233 +217.172.98.234 +217.172.98.237 +217.172.98.241 +217.172.98.251 +217.172.126.251 +217.198.190.5 +217.198.190.56 +217.198.190.66 +217.198.190.100 +217.198.190.125 +217.198.190.132 +217.198.190.145 +217.198.190.146 +217.198.190.147 +217.198.190.150 +217.198.190.151 +217.198.190.153 +217.198.190.156 +217.198.190.162 +217.198.190.178 +217.198.190.209 +217.198.190.212 +217.198.190.214 +217.198.190.215 +217.198.190.225 +217.218.2.100 +217.218.6.5 +217.218.7.4 +217.218.7.5 +217.218.9.140 +217.218.9.141 +217.218.11.11 +217.218.11.12 +217.218.12.58 +217.218.13.168 +217.218.21.104 +217.218.21.122 +217.218.21.124 +217.218.26.220 +217.218.32.74 +217.218.40.5 +217.218.40.6 +217.218.46.11 +217.218.53.153 +217.218.54.16 +217.218.56.21 +217.218.56.210 +217.218.56.249 +217.218.60.101 +217.218.62.65 +217.218.62.66 +217.218.70.10 +217.218.71.153 +217.218.75.202 +217.218.82.85 +217.218.82.140 +217.218.85.234 +217.218.86.50 +217.218.98.56 +217.218.98.157 +217.218.102.174 +217.218.106.140 +217.218.106.141 +217.218.113.50 +217.218.114.8 +217.218.124.10 +217.218.126.162 +217.218.127.126 +217.218.127.127 +217.218.155.154 +217.218.155.155 +217.218.168.3 +217.218.171.210 +217.218.179.31 +217.218.179.39 +217.218.200.218 +217.218.204.123 +217.218.204.124 +217.218.204.125 +217.218.209.221 +217.218.212.140 +217.218.214.4 +217.218.214.16 +217.218.215.25 +217.218.215.78 +217.218.215.140 +217.218.227.241 +217.218.227.242 +217.218.227.243 +217.218.227.244 +217.218.227.247 +217.218.227.248 +217.218.227.249 +217.218.227.250 +217.218.227.251 +217.218.227.252 +217.218.227.253 +217.218.227.254 +217.218.230.194 +217.218.234.252 +217.218.242.66 +217.218.242.67 +217.218.251.178 +217.219.29.73 +217.219.29.107 +217.219.31.147 +217.219.33.33 +217.219.33.34 +217.219.33.35 +217.219.33.69 +217.219.34.30 +217.219.39.116 +217.219.42.18 +217.219.42.134 +217.219.66.2 +217.219.66.3 +217.219.66.8 +217.219.67.130 +217.219.68.45 +217.219.70.92 +217.219.70.197 +217.219.78.138 +217.219.78.144 +217.219.78.152 +217.219.78.153 +217.219.78.154 +217.219.78.156 +217.219.78.200 +217.219.79.82 +217.219.85.3 +217.219.85.4 +217.219.85.5 +217.219.87.100 +217.219.91.132 +217.219.91.135 +217.219.93.2 +217.219.120.82 +217.219.121.35 +217.219.121.36 +217.219.124.203 +217.219.129.246 +217.219.135.200 +217.219.136.95 +217.219.137.210 +217.219.138.140 +217.219.138.205 +217.219.139.219 +217.219.147.197 +217.219.148.251 +217.219.157.69 +217.219.157.78 +217.219.157.82 +217.219.157.112 +217.219.157.115 +217.219.157.122 +217.219.157.123 +217.219.157.124 +217.219.157.125 +217.219.157.126 +217.219.158.99 +217.219.162.163 +217.219.162.165 +217.219.165.2 +217.219.165.3 +217.219.168.164 +217.219.169.170 +217.219.173.3 +217.219.182.42 +217.219.187.3 +217.219.192.197 +217.219.199.52 +217.219.202.3 +217.219.204.10 +217.219.217.179 +217.219.223.3 +217.219.223.4 +217.219.223.195 +217.219.226.98 +217.219.226.103 +217.219.227.30 +217.219.244.3 +217.219.244.33 +217.219.251.131 +217.219.254.5 +193.134.100.123 +2.179.113.235 +2.179.114.71 +2.187.34.221 +2.179.162.163 +2.182.52.30 +2.179.162.72 +2.190.249.146 +2.176.245.137 +2.176.254.42 +2.177.48.37 +2.177.52.221 +2.177.95.39 +2.177.104.14 +2.177.105.80 +2.177.150.72 +2.177.168.52 +2.178.31.22 +2.182.152.219 +2.182.154.12 +2.190.33.197 +2.190.45.194 +2.190.87.30 +2.190.90.36 +2.190.227.52 +2.190.240.232 +2.191.7.159 +2.191.74.29 +2.191.78.44 +2.191.83.211 +2.191.181.170 +2.177.164.134 +2.180.26.106 +2.190.155.142 +2.176.238.212 +2.177.45.68 +2.177.54.23 +2.177.114.60 +2.178.137.218 +2.190.158.13 +2.190.176.218 +2.176.234.128 +2.176.247.54 +2.176.250.245 +2.177.45.25 +2.177.92.255 +2.177.109.58 +2.177.158.21 +2.177.219.23 +2.178.142.224 +2.178.215.98 +2.178.245.98 +2.178.253.51 +2.179.105.155 +2.179.164.199 +2.179.164.237 +2.184.156.151 +2.190.25.70 +2.190.96.211 +2.190.160.70 +2.190.188.156 +2.190.195.246 +2.191.5.173 +2.191.19.31 +2.191.116.58 +2.176.176.154 +2.176.244.30 +2.177.211.193 +2.180.33.238 +2.182.226.202 +2.183.9.23 +2.183.29.240 +2.190.201.38 +2.190.202.101 +2.191.1.229 +2.176.242.22 +2.177.51.166 +2.177.106.45 +2.177.150.38 +2.178.83.133 +2.180.43.33 +2.182.223.152 +2.186.115.37 +2.189.139.181 +2.189.139.227 +2.189.139.226 +2.189.164.30 +2.190.201.62 +2.191.79.18 +2.191.105.26 +2.191.115.143 +2.191.126.83 +2.191.187.230 +2.177.115.69 +2.178.245.83 +2.182.54.48 +2.191.65.217 +2.189.139.212 +2.191.13.49 +2.191.67.102 +2.176.235.174 +2.176.237.127 +2.176.242.7 +2.177.41.82 +2.177.98.70 +2.177.152.179 +2.178.16.146 +2.178.71.207 +2.179.64.79 +2.179.67.90 +2.179.229.254 +2.180.29.142 +2.180.30.167 +2.182.153.70 +2.190.194.227 +2.191.16.108 +2.176.198.213 +2.176.232.208 +2.176.238.75 +2.176.242.124 +2.177.92.52 +2.177.108.109 +2.177.153.8 +2.177.172.249 +2.177.175.40 +2.177.219.183 +2.177.223.28 +2.178.23.43 +2.178.80.11 +2.178.189.61 +2.179.109.19 +2.179.117.179 +2.182.154.9 +2.182.225.237 +2.189.139.231 +2.190.42.94 +2.190.138.115 +2.190.216.245 +2.191.5.137 +2.191.9.130 +2.177.106.125 +2.177.110.187 +2.177.158.155 +2.177.216.60 +2.178.13.244 +2.178.142.152 +2.178.186.226 +2.180.29.17 +2.189.148.130 +2.177.151.229 +2.177.152.199 +2.177.176.61 +2.178.74.99 +2.183.29.242 +2.184.237.51 +2.189.139.228 +2.191.73.34 +2.177.227.61 +2.178.30.208 +2.178.129.235 +2.178.140.0 +2.178.204.102 +2.180.26.19 +2.190.100.38 +2.190.131.48 +2.190.218.64 +2.190.225.30 +2.191.79.57 +2.191.114.208 +2.191.119.217 +2.191.123.60 +2.176.128.105 +2.176.174.251 +2.176.246.7 +2.177.41.184 +2.177.42.153 +2.177.47.149 +2.177.68.208 +2.177.111.71 +2.177.221.131 +2.178.17.165 +2.178.133.2 +2.179.124.171 +2.182.222.226 +2.189.139.146 +2.189.148.93 +2.189.148.149 +2.190.38.43 +2.190.38.168 +2.191.21.72 +2.191.66.80 +2.177.37.77 +2.177.108.126 +2.177.211.148 +2.177.231.29 +2.189.148.122 +2.189.148.121 +2.189.148.127 +2.189.148.128 +2.191.2.86 +2.191.127.125 +2.177.228.72 +2.179.113.178 +2.179.161.147 +2.177.169.30 +2.191.16.201 +2.191.73.58 +2.176.179.89 +2.177.43.209 +2.177.115.49 +2.177.159.38 +2.177.231.47 +2.182.214.198 +2.189.148.91 +2.189.148.89 +2.189.148.90 +2.189.148.132 +2.190.73.186 +2.190.93.129 +2.178.78.5 +2.190.38.244 +2.177.38.182 +2.177.165.101 +2.178.145.58 +2.180.202.254 +2.182.88.204 +2.186.122.108 +2.190.93.101 +2.190.151.62 +2.177.110.46 +2.182.152.182 +2.187.128.167 +2.180.218.207 +2.180.42.227 +2.181.236.27 +2.181.250.52 +2.183.201.25 +2.184.16.254 +2.185.102.157 +2.185.147.195 +2.186.12.123 +2.187.39.139 +2.187.147.65 +2.190.159.73 +2.191.63.230 +2.191.125.110 +2.182.215.189 +2.189.148.118 +2.189.148.254 +2.176.244.117 +2.189.122.179 +2.189.139.162 +2.189.139.184 +2.190.207.58 +2.191.47.177 +2.185.108.199 +2.190.93.162 +2.177.157.104 +2.181.85.90 +2.189.148.92 +2.189.148.119 +2.191.187.56 +2.188.226.244 +2.180.5.196 +2.189.10.143 +2.177.99.255 +2.180.19.106 +2.189.148.129 +2.190.153.37 +2.180.33.248 +2.182.214.99 +2.189.139.20 +2.180.165.148 +2.189.139.22 +2.190.203.58 +2.179.165.76 +2.191.81.12 +2.181.1.163 +2.178.10.185 +2.179.113.3 +2.190.153.144 +2.191.118.46 +2.179.108.17 +2.188.73.132 +2.184.60.90 +2.178.194.150 +2.191.69.61 +2.144.243.102 +2.176.77.239 +2.176.139.231 +2.176.155.226 +2.176.198.253 +2.176.199.97 +2.176.199.228 +2.176.202.231 +2.176.207.137 +2.176.207.237 +2.176.208.185 +2.176.212.136 +2.176.212.148 +2.176.213.17 +2.176.214.75 +2.176.214.67 +2.176.214.140 +2.176.215.74 +2.176.215.104 +2.176.215.116 +2.176.226.183 +2.176.255.109 +2.176.255.225 +2.177.18.194 +2.177.21.242 +2.177.29.172 +2.177.42.65 +2.177.64.236 +2.177.65.11 +2.177.73.173 +2.177.74.143 +2.177.78.163 +2.177.82.240 +2.177.84.65 +2.177.84.84 +2.177.86.86 +2.177.87.89 +2.177.88.150 +2.177.88.129 +2.177.88.183 +2.177.89.83 +2.177.94.139 +2.177.97.172 +2.177.118.29 +2.177.130.106 +2.177.140.113 +2.177.149.223 +2.177.151.13 +2.177.151.42 +2.177.151.74 +2.177.151.137 +2.177.154.57 +2.177.219.36 +2.177.220.103 +2.177.221.181 +2.177.229.250 +2.177.232.104 +2.177.233.99 +2.177.243.139 +2.177.244.82 +2.178.32.142 +2.178.35.0 +2.178.43.119 +2.178.56.53 +2.178.56.149 +2.178.79.195 +2.178.110.158 +2.178.110.133 +2.178.111.164 +2.178.111.252 +2.178.114.114 +2.178.115.36 +2.178.115.143 +2.178.122.163 +2.178.129.61 +2.178.156.168 +2.178.166.203 +2.178.169.72 +2.178.170.193 +2.178.176.212 +2.178.178.16 +2.178.178.217 +2.178.224.25 +2.179.43.185 +2.179.44.186 +2.179.46.75 +2.179.91.125 +2.179.100.200 +2.179.100.165 +2.179.102.161 +2.179.102.166 +2.179.110.20 +2.179.111.182 +2.179.124.77 +2.179.124.180 +2.180.155.152 +2.180.168.204 +2.181.34.56 +2.181.89.209 +2.181.90.137 +2.181.224.89 +2.181.231.135 +2.182.48.18 +2.182.48.64 +2.182.49.108 +2.182.50.207 +2.182.51.240 +2.182.109.74 +2.182.127.113 +2.182.181.171 +85.185.41.64 +85.185.62.143 +85.185.75.75 +85.185.166.18 +85.185.166.170 +85.185.210.194 +85.185.235.44 +85.185.241.82 +85.185.242.34 +85.185.247.208 +85.204.82.68 +86.104.111.7 +86.104.111.126 +86.105.44.162 +86.107.148.106 +86.107.149.130 +86.107.149.136 +87.107.48.137 +87.107.49.52 +87.107.55.204 +87.107.73.83 +87.107.73.82 +87.107.82.198 +87.107.139.210 +87.107.185.6 +87.251.147.166 +185.206.231.51 +188.121.146.34 +185.8.175.222 +185.192.113.13 +185.213.165.177 +188.136.174.82 +188.136.174.210 +185.110.231.1 +185.121.131.178 +188.0.240.4 +188.211.98.218 +46.100.12.247 +46.100.60.230 +46.100.134.33 +185.46.218.250 +185.46.219.53 +31.7.90.122 +188.75.65.182 +188.75.95.21 +188.211.55.158 +46.245.72.221 +46.245.118.148 +178.252.191.18 +185.111.83.55 +185.112.39.5 +185.112.36.217 +37.114.251.107 +185.172.0.242 +185.169.6.245 +185.206.95.135 +178.236.106.230 +185.46.217.124 +185.46.218.130 +185.44.36.5 +31.7.73.126 +37.255.149.138 +46.100.94.121 +46.245.122.21 +185.79.158.138 +185.112.38.182 +185.112.36.185 +46.34.166.162 +46.100.1.109 +185.140.4.30 +46.245.118.164 +46.245.118.163 +185.193.209.13 +185.175.79.173 +185.193.209.44 +185.214.37.82 +185.193.209.38 +188.211.125.25 +178.239.149.193 +37.153.128.137 +185.110.28.118 +37.255.222.132 +37.255.203.116 +185.162.41.202 +185.193.209.25 +185.186.240.233 +185.193.209.12 +185.175.79.140 +185.193.209.33 +188.210.154.132 +188.211.101.233 +188.211.68.59 +188.209.31.92 +46.245.118.162 +46.245.122.28 +178.131.30.4 +178.131.89.13 +178.131.95.154 +178.131.158.148 +178.131.165.9 +178.131.155.93 +178.173.144.210 +178.173.196.71 +178.239.149.225 +178.239.149.1 +178.239.156.19 +185.83.88.105 +185.110.30.64 +185.112.36.181 +185.112.38.56 +5.201.132.213 +5.201.133.136 +5.201.141.56 +5.201.145.207 +37.153.128.140 +37.255.198.6 +37.202.153.179 +46.38.132.238 +185.106.137.92 +188.75.65.179 +188.75.65.181 +188.211.73.133 +188.210.156.33 +188.213.154.236 +188.211.104.243 +188.213.147.34 +188.211.103.49 +46.245.78.85 +178.131.79.145 +178.131.31.79 +178.173.140.145 +178.173.217.221 +185.193.209.37 +185.193.209.24 +188.211.113.172 +178.131.95.2 +178.131.94.35 +45.142.191.36 +188.75.65.180 +188.75.65.178 +188.211.101.239 +188.211.73.123 +188.211.99.131 +188.209.66.119 +178.131.27.231 +178.131.165.97 +178.131.101.41 +178.131.147.208 +178.131.191.141 +178.173.138.218 +178.173.184.189 +178.173.215.59 +185.193.209.15 +185.193.209.3 +185.193.209.23 +185.175.78.199 +188.208.144.51 +188.209.79.123 +188.211.127.238 +46.235.76.161 +178.131.75.107 +178.131.91.5 +178.131.89.197 +178.131.93.191 +178.236.104.186 +37.153.130.202 +46.100.60.44 +46.245.4.4 +178.131.100.139 +178.131.153.91 +185.120.214.240 +178.173.185.216 +185.162.41.59 +188.211.101.127 +178.131.81.219 +178.131.76.141 +178.131.183.169 +178.173.215.53 +5.201.128.211 +5.201.129.159 +5.201.132.11 +37.143.146.56 +37.148.2.225 +188.211.104.216 +178.131.93.225 +45.8.160.217 +178.131.21.179 +178.131.111.182 +178.131.147.249 +178.131.111.183 +46.34.164.62 +185.175.78.97 +188.211.115.34 +37.202.188.182 +188.210.124.67 +178.131.132.168 +185.3.212.58 +188.209.65.12 +188.211.80.86 +188.211.122.188 +178.131.85.186 +178.131.181.76 +188.211.93.212 +188.211.113.28 +37.148.10.14 +178.131.91.185 +185.193.209.45 +37.148.5.188 +185.193.209.28 +178.131.89.60 +5.201.137.183 +185.162.40.190 +5.201.136.198 +185.176.34.216 +178.131.93.104 +178.131.190.233 +188.121.147.14 +2.144.6.75 +2.144.234.8 +2.176.0.149 +2.176.1.2 +2.176.3.199 +2.176.4.253 +2.176.5.147 +2.176.10.31 +2.176.13.185 +2.176.14.103 +2.176.14.183 +2.176.19.229 +2.176.21.116 +2.176.22.40 +2.176.23.196 +2.176.26.161 +2.176.28.124 +2.176.32.50 +2.176.32.152 +2.176.33.3 +2.176.33.49 +2.176.33.252 +2.176.37.116 +2.176.38.115 +2.176.40.17 +2.176.46.159 +2.176.48.151 +2.176.49.16 +2.176.51.21 +2.176.54.114 +2.176.57.203 +2.176.59.199 +2.176.63.26 +2.176.63.164 +2.176.64.13 +2.176.64.62 +2.176.67.195 +2.176.68.200 +2.176.69.30 +2.176.70.163 +2.176.70.194 +2.176.70.255 +2.176.72.122 +2.176.73.187 +2.176.75.51 +2.176.79.19 +2.176.79.205 +2.176.82.84 +2.176.89.185 +2.176.91.175 +2.176.96.244 +2.176.97.95 +2.176.110.198 +2.176.111.242 +2.176.112.190 +2.176.114.152 +2.176.115.171 +2.176.116.29 +2.176.118.141 +2.176.125.63 +2.176.125.180 +2.176.127.4 +2.176.128.63 +2.176.128.77 +2.176.128.112 +2.176.128.228 +2.176.129.91 +2.176.129.231 +2.176.130.105 +2.176.131.65 +2.176.131.77 +2.176.132.230 +2.176.137.87 +2.176.138.199 +2.176.139.170 +2.176.142.146 +2.176.144.136 +2.176.144.166 +2.176.144.243 +2.176.145.7 +2.176.146.150 +2.176.146.196 +2.176.148.38 +2.176.148.44 +2.176.150.91 +2.176.152.223 +2.176.153.246 +2.176.155.87 +2.176.157.202 +2.176.158.62 +2.176.158.249 +2.176.159.236 +2.176.164.204 +2.176.170.96 +2.176.172.196 +2.176.174.221 +2.176.180.253 +2.176.182.199 +2.176.186.237 +2.176.192.190 +2.176.194.95 +2.176.194.219 +2.176.194.252 +2.176.195.55 +2.176.195.166 +2.176.196.6 +2.176.198.119 +2.176.198.194 +2.176.199.32 +2.176.200.173 +2.176.200.224 +2.176.203.56 +2.176.203.132 +2.176.204.68 +2.176.207.44 +2.176.208.140 +2.176.210.13 +2.176.214.194 +2.176.217.128 +2.176.218.15 +2.176.218.38 +2.176.218.157 +2.176.219.106 +2.176.220.0 +2.176.220.41 +2.176.220.74 +2.176.220.159 +2.176.220.232 +2.176.223.22 +2.176.225.123 +2.176.228.243 +2.176.235.178 +2.176.236.18 +2.176.236.73 +2.176.237.101 +2.176.239.112 +2.176.239.166 +2.176.240.214 +2.176.241.29 +2.176.241.192 +2.176.243.199 +2.176.246.103 +2.176.246.118 +2.176.249.144 +2.176.250.140 +2.176.251.152 +2.176.252.239 +2.176.253.59 +2.177.0.4 +2.177.0.102 +2.177.1.27 +2.177.2.24 +2.177.2.191 +2.177.4.142 +2.177.4.190 +2.177.5.6 +2.177.5.226 +2.177.7.132 +2.177.8.24 +2.177.9.2 +2.177.9.92 +2.177.11.190 +2.177.13.184 +2.177.14.233 +2.177.16.78 +2.177.17.153 +2.177.20.69 +2.177.21.120 +2.177.21.132 +2.177.21.177 +2.177.22.81 +2.177.22.101 +2.177.23.57 +2.177.24.65 +2.177.24.180 +2.177.26.202 +2.177.28.36 +2.177.35.28 +2.177.35.203 +2.177.35.241 +2.177.37.57 +2.177.44.216 +2.177.45.132 +2.177.46.215 +2.177.48.88 +2.177.51.76 +2.177.51.229 +2.177.55.110 +2.177.56.122 +2.177.60.52 +2.177.60.86 +2.177.60.149 +2.177.65.54 +2.177.67.107 +2.177.67.109 +2.177.68.140 +2.177.69.213 +2.177.72.120 +2.177.72.160 +2.177.73.27 +2.177.73.194 +2.177.75.6 +2.177.77.139 +2.177.78.50 +2.177.79.209 +2.177.80.105 +2.177.81.171 +2.177.85.139 +2.177.86.104 +2.177.88.168 +2.177.89.205 +2.177.97.64 +2.177.100.34 +2.177.102.82 +2.177.104.97 +2.177.105.74 +2.177.109.182 +2.177.110.188 +2.177.112.34 +2.177.113.111 +2.177.115.30 +2.177.115.139 +2.177.116.83 +2.177.116.163 +2.177.117.151 +2.177.120.18 +2.177.121.15 +2.177.124.17 +2.177.125.26 +2.177.125.210 +2.177.126.89 +2.177.129.25 +2.177.130.99 +2.177.131.124 +2.177.132.3 +2.177.132.150 +2.177.133.146 +2.177.133.241 +2.177.137.23 +2.177.138.130 +2.177.138.241 +2.177.141.203 +2.177.142.59 +2.177.143.254 +2.177.144.14 +2.177.144.176 +2.177.145.91 +2.177.145.107 +2.177.146.124 +2.177.146.253 +2.177.147.104 +2.177.147.146 +2.177.148.14 +2.177.148.18 +2.177.150.204 +2.177.150.232 +2.177.153.97 +2.177.153.117 +2.177.154.117 +2.177.154.163 +2.177.155.114 +2.177.157.64 +2.177.160.50 +2.177.160.134 +2.177.162.103 +2.177.162.117 +2.177.163.247 +2.177.165.197 +2.177.166.204 +2.177.166.244 +2.177.171.61 +2.177.171.185 +2.177.172.140 +2.177.172.168 +2.177.174.32 +2.177.174.65 +2.177.174.80 +2.177.175.61 +2.177.176.87 +2.177.179.220 +2.177.180.217 +2.177.181.160 +2.177.181.248 +2.177.181.252 +2.177.182.225 +2.177.182.249 +2.177.185.220 +2.177.187.117 +2.177.187.218 +2.177.188.154 +2.177.190.97 +2.177.194.12 +2.177.194.227 +2.177.195.220 +2.177.196.18 +2.177.197.5 +2.177.198.140 +2.177.199.1 +2.177.199.219 +2.177.200.176 +2.177.202.44 +2.177.202.188 +2.177.205.239 +2.177.206.194 +2.177.208.38 +2.177.208.191 +2.177.209.17 +2.177.212.13 +2.177.212.85 +2.177.214.26 +2.177.214.42 +2.177.214.238 +2.177.217.0 +2.177.217.81 +2.177.218.70 +2.177.221.170 +2.177.221.178 +2.177.224.225 +2.177.225.4 +2.177.225.205 +2.177.226.0 +2.177.226.178 +2.177.230.72 +2.177.231.76 +2.177.231.200 +2.177.233.62 +2.177.236.2 +2.177.236.30 +2.177.236.221 +2.177.237.95 +2.177.240.71 +2.177.243.171 +2.177.244.185 +2.177.245.168 +2.177.245.206 +2.177.245.207 +2.177.246.141 +2.177.250.215 +2.177.250.239 +2.177.251.74 +2.178.0.88 +2.178.1.123 +2.178.2.120 +2.178.2.138 +2.178.12.135 +2.178.15.73 +2.178.17.73 +2.178.18.219 +2.178.20.173 +2.178.21.53 +2.178.25.0 +2.178.28.209 +2.178.28.241 +2.178.29.173 +2.178.34.223 +2.178.37.140 +2.178.41.120 +2.178.43.178 +2.178.44.137 +2.178.48.99 +2.178.48.207 +2.178.50.81 +2.178.53.39 +2.178.54.58 +2.178.58.202 +2.178.59.38 +2.178.67.118 +2.178.68.83 +2.178.70.36 +2.178.71.48 +2.178.72.192 +2.178.75.5 +2.178.79.202 +2.178.80.60 +2.178.80.166 +2.178.81.3 +2.178.87.134 +2.178.89.141 +2.178.92.217 +2.178.95.100 +2.178.98.24 +2.178.99.137 +2.178.99.171 +2.178.100.51 +2.178.101.158 +2.178.103.59 +2.178.104.72 +2.178.106.47 +2.178.111.162 +2.178.114.185 +2.178.116.7 +2.178.118.23 +2.178.118.113 +2.178.120.176 +2.178.121.69 +2.178.123.81 +2.178.125.192 +2.178.127.86 +2.178.131.81 +2.178.131.208 +2.178.134.227 +2.178.141.17 +2.178.147.116 +2.178.149.161 +2.178.150.176 +2.178.152.155 +2.178.162.109 +2.178.165.47 +2.178.167.203 +2.178.168.166 +2.178.176.44 +2.178.180.145 +2.178.183.88 +2.178.185.208 +2.178.188.32 +2.178.195.84 +2.178.195.119 +2.178.201.117 +2.178.203.141 +2.178.208.230 +2.178.214.202 +2.178.220.103 +2.178.229.49 +2.178.230.103 +2.178.232.133 +2.178.232.202 +2.178.232.209 +2.178.233.77 +2.178.234.139 +2.178.236.87 +2.178.241.103 +2.178.247.85 +2.179.20.77 +2.179.21.89 +2.179.21.253 +2.179.22.24 +2.179.46.173 +2.179.47.241 +2.179.67.96 +2.179.70.156 +2.179.71.6 +2.179.71.183 +2.179.78.139 +2.179.80.86 +2.179.83.31 +2.179.83.40 +2.179.84.26 +2.179.84.223 +2.179.86.198 +2.179.96.57 +2.179.96.164 +2.179.96.196 +2.179.100.5 +2.179.100.90 +2.179.101.193 +2.179.103.136 +2.179.103.247 +2.179.105.36 +2.179.106.42 +2.179.115.152 +2.179.115.171 +2.179.117.25 +2.179.117.157 +2.179.117.227 +2.179.118.254 +2.179.121.31 +2.179.124.214 +2.179.125.87 +2.179.126.163 +2.179.127.65 +2.179.162.144 +2.179.163.232 +2.179.167.231 +2.179.178.115 +2.179.186.238 +2.179.193.146 +2.179.194.45 +2.179.194.138 +2.179.211.167 +2.179.212.14 +2.179.217.132 +2.179.232.21 +2.179.252.135 +2.180.0.177 +2.180.1.91 +2.180.1.95 +2.180.3.207 +2.180.6.135 +2.180.8.54 +2.180.8.97 +2.180.15.75 +2.180.21.43 +2.180.21.218 +2.180.22.62 +2.180.22.233 +2.180.24.112 +2.180.25.192 +2.180.29.94 +2.180.31.174 +2.180.32.48 +2.180.32.211 +2.180.33.137 +2.180.35.195 +2.180.35.227 +2.180.36.43 +2.180.39.39 +2.180.41.242 +2.180.42.127 +2.180.43.95 +2.180.47.53 +2.181.1.61 +2.181.30.184 +2.181.31.165 +2.181.35.20 +2.181.35.134 +2.181.60.180 +2.181.80.158 +2.181.81.16 +2.181.85.245 +2.181.89.248 +2.181.93.51 +2.181.101.208 +2.181.102.4 +2.181.109.155 +2.181.177.81 +2.181.224.175 +2.181.224.244 +2.181.232.2 +2.181.232.233 +2.181.237.83 +2.181.238.104 +2.181.245.168 +2.181.247.131 +2.181.249.200 +2.181.251.228 +2.181.255.52 +2.182.51.83 +2.182.51.102 +2.182.51.211 +2.182.52.8 +2.182.53.4 +2.182.53.28 +2.182.54.16 +2.182.55.126 +2.182.59.207 +2.182.64.250 +2.182.65.3 +2.182.67.56 +2.182.67.151 +2.182.67.168 +2.182.67.197 +2.182.68.82 +2.182.68.142 +2.182.70.153 +2.182.73.35 +2.182.75.49 +2.182.75.133 +2.182.75.162 +2.182.75.254 +2.182.77.238 +2.182.84.145 +2.182.85.146 +2.182.86.49 +2.182.88.229 +2.182.90.64 +2.182.90.230 +2.182.93.10 +2.182.93.122 +2.182.93.241 +2.182.95.158 +2.182.95.239 +2.182.116.229 +2.182.118.163 +2.182.119.115 +2.182.127.44 +2.182.127.71 +2.182.166.5 +2.182.184.150 +2.182.190.134 +2.182.191.10 +2.182.192.22 +2.182.192.106 +2.182.196.132 +2.182.197.46 +2.182.201.6 +2.182.208.44 +2.182.215.172 +2.182.217.172 +2.182.217.217 +2.182.218.73 +2.182.221.90 +2.182.222.208 +2.182.226.35 +2.182.226.205 +2.182.227.83 +2.182.228.6 +2.182.228.77 +2.182.228.197 +2.182.229.125 +2.182.231.26 +2.182.234.126 +2.182.234.153 +2.182.234.201 +2.182.235.5 +2.182.235.216 +2.182.241.138 +2.182.249.72 +2.182.251.205 +2.183.8.116 +2.183.88.79 +2.183.89.135 +2.183.99.60 +2.183.99.80 +2.183.115.168 +2.183.123.1 +2.183.142.201 +2.183.143.93 +2.183.195.164 +2.183.198.32 +2.183.200.141 +2.183.202.66 +2.183.202.70 +2.183.202.71 +2.183.203.130 +2.183.203.186 +2.184.2.165 +2.184.2.184 +2.184.3.191 +2.184.15.56 +2.184.16.149 +2.184.16.208 +2.184.18.54 +2.184.18.130 +2.184.22.102 +2.184.29.224 +2.184.30.140 +2.184.31.224 +2.184.31.226 +2.184.53.203 +2.184.55.72 +2.184.57.76 +2.184.70.111 +2.184.156.10 +2.184.156.166 +2.184.157.37 +2.184.158.58 +2.184.158.225 +2.184.159.1 +2.184.159.172 +2.184.182.83 +2.184.204.101 +2.184.216.242 +2.184.238.105 +2.184.238.193 +2.184.239.43 +2.185.17.16 +2.185.17.227 +2.185.66.102 +2.185.97.114 +2.185.100.126 +2.185.102.55 +2.185.104.61 +2.185.109.51 +2.185.109.210 +2.185.110.156 +2.185.113.54 +2.185.116.135 +2.185.122.111 +2.185.141.99 +2.185.143.19 +2.185.143.54 +2.185.144.8 +2.185.144.53 +2.185.144.86 +2.185.146.24 +2.185.148.251 +2.185.175.77 +2.185.230.179 +2.185.232.154 +2.185.232.162 +2.185.233.7 +2.185.233.26 +2.185.237.103 +2.185.243.230 +2.185.244.248 +2.185.247.84 +2.185.251.200 +2.185.252.143 +2.186.13.114 +2.186.15.84 +2.186.112.159 +2.186.116.88 +2.186.116.124 +2.186.118.250 +2.186.120.77 +2.186.121.189 +2.186.122.128 +2.186.229.13 +2.187.2.155 +2.187.9.112 +2.187.20.133 +2.187.32.176 +2.187.34.93 +2.187.36.108 +2.187.56.151 +2.187.57.2 +2.187.59.40 +2.187.129.147 +2.187.138.186 +2.187.146.168 +2.187.148.114 +2.187.151.18 +2.187.188.92 +2.187.189.189 +2.187.249.102 +2.187.250.158 +2.188.72.139 +2.188.162.210 +2.188.167.197 +2.188.168.191 +2.188.174.129 +2.188.174.149 +2.188.174.154 +2.188.210.1 +2.188.210.9 +2.188.210.11 +2.188.210.60 +2.188.212.190 +2.188.218.178 +2.188.226.186 +2.188.226.249 +2.188.230.202 +2.188.236.66 +2.188.236.68 +2.188.236.69 +2.189.20.134 +2.189.20.221 +2.189.33.59 +2.189.89.26 +2.189.103.0 +2.189.103.1 +2.189.103.2 +2.189.103.3 +2.189.103.5 +2.189.103.6 +2.189.103.7 +2.189.103.8 +2.189.103.10 +2.189.103.11 +2.189.103.13 +2.189.103.16 +2.189.103.17 +2.189.103.18 +2.189.103.19 +2.189.103.20 +2.189.103.21 +2.189.103.22 +2.189.103.23 +2.189.103.24 +2.189.103.25 +2.189.103.26 +2.189.103.27 +2.189.103.28 +2.189.103.30 +2.189.103.32 +2.189.103.33 +2.189.103.34 +2.189.103.35 +2.189.103.36 +2.189.103.37 +2.189.103.39 +2.189.103.40 +2.189.103.41 +2.189.103.42 +2.189.103.43 +2.189.103.44 +2.189.103.45 +2.189.103.46 +2.189.103.47 +2.189.103.48 +2.189.103.49 +2.189.103.50 +2.189.103.51 +2.189.103.52 +2.189.103.53 +2.189.103.55 +2.189.103.56 +2.189.103.57 +2.189.103.59 +2.189.103.60 +2.189.103.61 +2.189.103.63 +2.189.103.64 +2.189.103.65 +2.189.103.67 +2.189.103.68 +2.189.103.69 +2.189.103.72 +2.189.103.73 +2.189.103.74 +2.189.103.75 +2.189.103.76 +2.189.103.78 +2.189.103.79 +2.189.103.80 +2.189.103.81 +2.189.103.82 +2.189.103.84 +2.189.103.85 +2.189.103.86 +2.189.103.87 +2.189.103.88 +2.189.103.89 +2.189.103.90 +2.189.103.91 +2.189.103.92 +2.189.103.93 +2.189.103.95 +2.189.103.96 +2.189.103.97 +2.189.103.98 +2.189.103.99 +2.189.103.100 +2.189.103.101 +2.189.103.102 +2.189.103.105 +2.189.103.106 +2.189.103.107 +2.189.103.108 +2.189.103.109 +2.189.103.110 +2.189.103.111 +2.189.103.112 +2.189.103.113 +2.189.103.114 +2.189.103.115 +2.189.103.116 +2.189.103.117 +2.189.103.118 +2.189.103.119 +2.189.103.120 +2.189.103.121 +2.189.103.122 +2.189.103.123 +2.189.103.124 +2.189.103.125 +2.189.103.128 +2.189.103.129 +2.189.103.130 +2.189.103.131 +2.189.103.132 +2.189.103.134 +2.189.103.135 +2.189.103.136 +2.189.103.138 +2.189.103.139 +2.189.103.140 +2.189.103.141 +2.189.103.142 +2.189.103.143 +2.189.103.144 +2.189.103.145 +2.189.103.146 +2.189.103.147 +2.189.103.148 +2.189.103.149 +2.189.103.150 +2.189.103.152 +2.189.103.153 +2.189.103.154 +2.189.103.155 +2.189.103.156 +2.189.103.158 +2.189.103.159 +2.189.103.160 +2.189.103.161 +2.189.103.162 +2.189.103.163 +2.189.103.164 +2.189.103.166 +2.189.103.167 +2.189.103.168 +2.189.103.169 +2.189.103.170 +2.189.103.171 +2.189.103.172 +2.189.103.173 +2.189.103.174 +2.189.103.176 +2.189.103.177 +2.189.103.178 +2.189.103.179 +2.189.103.180 +2.189.103.181 +2.189.103.182 +2.189.103.183 +2.189.103.184 +2.189.103.186 +2.189.103.187 +2.189.103.188 +2.189.103.189 +2.189.103.190 +2.189.103.192 +2.189.103.193 +2.189.103.194 +2.189.103.195 +2.189.103.196 +2.189.103.197 +2.189.103.198 +2.189.103.199 +2.189.103.200 +2.189.103.202 +2.189.103.203 +2.189.103.204 +2.189.103.205 +2.189.103.206 +2.189.103.208 +2.189.103.209 +2.189.103.210 +2.189.103.212 +2.189.103.213 +2.189.103.214 +2.189.103.215 +2.189.103.216 +2.189.103.217 +2.189.103.219 +2.189.103.220 +2.189.103.222 +2.189.103.223 +2.189.103.224 +2.189.103.225 +2.189.103.226 +2.189.103.227 +2.189.103.228 +2.189.103.229 +2.189.103.230 +2.189.103.232 +2.189.103.233 +2.189.103.234 +2.189.103.235 +2.189.103.236 +2.189.103.238 +2.189.103.239 +2.189.103.240 +2.189.103.241 +2.189.103.242 +2.189.103.244 +2.189.103.245 +2.189.103.246 +2.189.103.247 +2.189.103.248 +2.189.103.249 +2.189.103.250 +2.189.103.251 +2.189.103.252 +2.189.103.253 +2.189.103.254 +2.189.103.255 +2.189.142.24 +2.189.144.51 +2.189.146.0 +2.189.146.1 +2.189.146.2 +2.189.146.3 +2.189.146.4 +2.189.146.5 +2.189.146.6 +2.189.146.7 +2.189.146.8 +2.189.146.9 +2.189.146.10 +2.189.146.11 +2.189.146.12 +2.189.146.13 +2.189.146.14 +2.189.146.15 +2.189.146.16 +2.189.146.17 +2.189.146.19 +2.189.146.20 +2.189.146.21 +2.189.146.22 +2.189.146.24 +2.189.146.25 +2.189.146.26 +2.189.146.27 +2.189.146.28 +2.189.146.30 +2.189.146.31 +2.189.146.32 +2.189.146.33 +2.189.146.34 +2.189.146.35 +2.189.146.36 +2.189.146.37 +2.189.146.38 +2.189.146.39 +2.189.146.40 +2.189.146.41 +2.189.146.42 +2.189.146.43 +2.189.146.44 +2.189.146.45 +2.189.146.46 +2.189.146.47 +2.189.146.49 +2.189.146.50 +2.189.146.51 +2.189.146.52 +2.189.146.53 +2.189.146.54 +2.189.146.55 +2.189.146.57 +2.189.146.58 +2.189.146.59 +2.189.146.60 +2.189.146.61 +2.189.146.62 +2.189.146.63 +2.189.146.64 +2.189.146.65 +2.189.146.66 +2.189.146.68 +2.189.146.69 +2.189.146.70 +2.189.146.71 +2.189.146.72 +2.189.146.73 +2.189.146.74 +2.189.146.76 +2.189.146.77 +2.189.146.79 +2.189.146.80 +2.189.146.81 +2.189.146.82 +2.189.146.83 +2.189.146.84 +2.189.146.85 +2.189.146.86 +2.189.146.87 +2.189.146.88 +2.189.146.89 +2.189.146.90 +2.189.146.91 +2.189.146.92 +2.189.146.93 +2.189.146.95 +2.189.146.96 +2.189.146.98 +2.189.146.99 +2.189.146.100 +2.189.146.101 +2.189.146.103 +2.189.146.104 +2.189.146.105 +2.189.146.106 +2.189.146.109 +2.189.146.110 +2.189.146.111 +2.189.146.112 +2.189.146.114 +2.189.146.115 +2.189.146.116 +2.189.146.117 +2.189.146.119 +2.189.146.120 +2.189.146.121 +2.189.146.122 +2.189.146.123 +2.189.146.124 +2.189.146.125 +2.189.146.126 +2.189.146.127 +2.189.146.128 +2.189.146.130 +2.189.146.131 +2.189.146.134 +2.189.146.135 +2.189.146.136 +2.189.146.137 +2.189.146.138 +2.189.146.141 +2.189.146.142 +2.189.146.143 +2.189.146.144 +2.189.146.145 +2.189.146.146 +2.189.146.148 +2.189.146.150 +2.189.146.151 +2.189.146.153 +2.189.146.154 +2.189.146.155 +2.189.146.156 +2.189.146.157 +2.189.146.158 +2.189.146.159 +2.189.146.160 +2.189.146.161 +2.189.146.162 +2.189.146.163 +2.189.146.164 +2.189.146.165 +2.189.146.166 +2.189.146.169 +2.189.146.170 +2.189.146.171 +2.189.146.172 +2.189.146.173 +2.189.146.174 +2.189.146.176 +2.189.146.177 +2.189.146.178 +2.189.146.179 +2.189.146.180 +2.189.146.181 +2.189.146.183 +2.189.146.184 +2.189.146.185 +2.189.146.186 +2.189.146.188 +2.189.146.189 +2.189.146.190 +2.189.146.191 +2.189.146.192 +2.189.146.193 +2.189.146.194 +2.189.146.195 +2.189.146.196 +2.189.146.197 +2.189.146.198 +2.189.146.200 +2.189.146.201 +2.189.146.202 +2.189.146.203 +2.189.146.204 +2.189.146.205 +2.189.146.206 +2.189.146.207 +2.189.146.208 +2.189.146.209 +2.189.146.210 +2.189.146.211 +2.189.146.212 +2.189.146.213 +2.189.146.214 +2.189.146.215 +2.189.146.216 +2.189.146.217 +2.189.146.218 +2.189.146.219 +2.189.146.220 +2.189.146.221 +2.189.146.222 +2.189.146.223 +2.189.146.224 +2.189.146.225 +2.189.146.226 +2.189.146.227 +2.189.146.228 +2.189.146.229 +2.189.146.230 +2.189.146.231 +2.189.146.232 +2.189.146.233 +2.189.146.234 +2.189.146.235 +2.189.146.236 +2.189.146.238 +2.189.146.239 +2.189.146.240 +2.189.146.241 +2.189.146.243 +2.189.146.244 +2.189.146.245 +2.189.146.246 +2.189.146.247 +2.189.146.248 +2.189.146.249 +2.189.146.250 +2.189.146.251 +2.189.146.252 +2.189.146.254 +2.189.146.255 +2.189.147.203 +2.189.167.182 +2.189.248.85 +2.189.253.227 +2.190.0.219 +2.190.3.109 +2.190.4.52 +2.190.6.196 +2.190.7.228 +2.190.9.118 +2.190.11.141 +2.190.15.136 +2.190.19.245 +2.190.20.60 +2.190.23.236 +2.190.26.141 +2.190.27.169 +2.190.27.197 +2.190.37.39 +2.190.38.55 +2.190.38.87 +2.190.38.142 +2.190.38.211 +2.190.39.77 +2.190.39.130 +2.190.40.156 +2.190.42.21 +2.190.42.183 +2.190.45.166 +2.190.46.116 +2.190.51.254 +2.190.52.74 +2.190.53.198 +2.190.59.26 +2.190.59.204 +2.190.60.248 +2.190.63.200 +2.190.64.4 +2.190.64.131 +2.190.65.190 +2.190.76.222 +2.190.78.34 +2.190.78.187 +2.190.78.200 +2.190.80.240 +2.190.85.139 +2.190.87.233 +2.190.98.198 +2.190.99.4 +2.190.100.201 +2.190.101.82 +2.190.102.90 +2.190.102.135 +2.190.103.7 +2.190.104.114 +2.190.109.119 +2.190.110.193 +2.190.111.127 +2.190.113.4 +2.190.113.163 +2.190.119.48 +2.190.122.249 +2.190.124.53 +2.190.124.161 +2.190.125.194 +2.190.127.59 +2.190.128.73 +2.190.129.19 +2.190.130.21 +2.190.130.101 +2.190.131.105 +2.190.131.202 +2.190.136.56 +2.190.136.169 +2.190.137.45 +2.190.138.190 +2.190.139.44 +2.190.140.121 +2.190.141.59 +2.190.142.75 +2.190.144.94 +2.190.146.240 +2.190.148.66 +2.190.148.218 +2.190.149.244 +2.190.152.106 +2.190.154.170 +2.190.155.54 +2.190.155.103 +2.190.158.27 +2.190.160.83 +2.190.160.127 +2.190.162.121 +2.190.162.212 +2.190.163.198 +2.190.163.219 +2.190.164.173 +2.190.166.177 +2.190.168.116 +2.190.170.245 +2.190.172.131 +2.190.173.242 +2.190.175.7 +2.190.176.19 +2.190.176.114 +2.190.176.170 +2.190.177.8 +2.190.177.128 +2.190.178.171 +2.190.179.93 +2.190.180.3 +2.190.180.123 +2.190.183.211 +2.190.184.51 +2.190.184.94 +2.190.184.245 +2.190.187.101 +2.190.188.246 +2.190.190.162 +2.190.197.84 +2.190.200.30 +2.190.201.140 +2.190.202.90 +2.190.207.43 +2.190.211.126 +2.190.213.109 +2.190.221.31 +2.190.223.151 +2.190.224.23 +2.190.225.129 +2.190.227.220 +2.190.230.241 +2.190.233.13 +2.190.235.238 +2.190.238.96 +2.190.239.255 +2.190.240.70 +2.190.240.227 +2.190.245.242 +2.190.247.236 +2.190.249.207 +2.190.250.172 +2.190.251.210 +2.190.254.120 +2.190.254.154 +2.190.255.53 +2.191.0.6 +2.191.0.224 +2.191.1.50 +2.191.1.113 +2.191.1.242 +2.191.4.59 +2.191.5.65 +2.191.7.180 +2.191.10.52 +2.191.12.81 +2.191.16.31 +2.191.17.99 +2.191.18.29 +2.191.18.142 +2.191.18.180 +2.191.19.78 +2.191.20.194 +2.191.21.17 +2.191.22.179 +2.191.23.246 +2.191.24.148 +2.191.24.199 +2.191.24.212 +2.191.25.48 +2.191.25.243 +2.191.26.247 +2.191.31.63 +2.191.31.199 +2.191.31.237 +2.191.33.102 +2.191.33.218 +2.191.36.95 +2.191.38.0 +2.191.38.215 +2.191.39.12 +2.191.39.33 +2.191.44.159 +2.191.46.156 +2.191.47.193 +2.191.47.212 +2.191.48.80 +2.191.50.18 +2.191.50.221 +2.191.51.19 +2.191.51.147 +2.191.52.111 +2.191.53.176 +2.191.53.235 +2.191.55.88 +2.191.55.143 +2.191.57.193 +2.191.60.111 +2.191.60.174 +2.191.64.245 +2.191.65.114 +2.191.67.13 +2.191.67.122 +2.191.67.132 +2.191.68.132 +2.191.72.96 +2.191.75.140 +2.191.77.110 +2.191.79.203 +2.191.81.104 +2.191.83.222 +2.191.85.229 +2.191.88.158 +2.191.88.227 +2.191.89.5 +2.191.89.181 +2.191.90.111 +2.191.92.21 +2.191.93.51 +2.191.96.43 +2.191.98.55 +2.191.98.216 +2.191.102.103 +2.191.102.147 +2.191.102.197 +2.191.104.0 +2.191.109.200 +2.191.110.107 +2.191.111.70 +2.191.111.155 +2.191.111.206 +2.191.112.240 +2.191.117.110 +2.191.122.151 +2.191.123.43 +2.191.123.207 +2.191.123.233 +2.191.125.122 +2.191.126.113 +2.191.127.237 +2.191.128.179 +2.191.129.228 +2.191.130.41 +2.191.130.94 +2.191.136.130 +2.191.142.36 +2.191.147.12 +2.191.150.45 +2.191.152.160 +2.191.152.233 +2.191.153.212 +2.191.153.213 +2.191.155.105 +2.191.156.224 +2.191.158.7 +2.191.159.18 +2.191.161.238 +2.191.162.18 +2.191.165.91 +2.191.166.250 +2.191.167.59 +2.191.167.133 +2.191.167.183 +2.191.169.113 +2.191.173.112 +2.191.173.135 +2.191.174.192 +2.191.177.52 +2.191.180.74 +2.191.181.20 +2.191.181.136 +2.191.190.126 +2.191.196.245 +2.191.199.52 +2.191.203.66 +2.191.207.176 +2.191.212.121 +2.191.215.7 +2.191.221.159 +2.191.230.3 +2.191.231.150 +5.22.198.2 +5.22.199.225 +5.22.199.227 +5.22.199.233 +5.61.24.140 +5.61.25.140 +5.62.161.12 +5.62.164.234 +5.62.167.252 +5.62.168.205 +5.62.168.209 +5.62.169.32 +5.62.170.171 +5.62.171.239 +5.62.172.230 +5.62.177.52 +5.62.180.232 +5.62.184.217 +5.62.185.88 +5.62.185.142 +5.62.187.24 +5.62.190.208 +5.74.1.46 +5.74.2.235 +5.74.4.38 +5.74.4.133 +5.74.5.163 +5.74.8.73 +5.74.10.109 +5.74.10.150 +5.74.10.215 +5.74.11.204 +5.74.13.185 +5.74.14.152 +5.74.14.155 +5.74.15.104 +5.74.15.181 +5.74.15.246 +5.74.16.146 +5.74.17.89 +5.74.18.0 +5.74.21.244 +5.74.23.161 +5.74.25.63 +5.74.25.64 +5.74.26.88 +5.74.26.94 +5.74.31.226 +5.74.32.237 +5.74.33.49 +5.74.42.117 +5.74.44.120 +5.74.45.152 +5.74.46.132 +5.74.47.40 +5.74.48.79 +5.74.48.107 +5.74.48.243 +5.74.49.7 +5.74.49.68 +5.74.51.101 +5.74.51.243 +5.74.53.164 +5.74.56.24 +5.74.68.122 +5.74.69.14 +5.74.70.57 +5.74.70.206 +5.74.72.139 +5.74.72.153 +5.74.73.211 +5.74.81.76 +5.74.81.109 +5.74.81.245 +5.74.82.14 +5.74.83.74 +5.74.84.91 +5.74.85.195 +5.74.86.234 +5.74.87.26 +5.74.89.115 +5.74.90.185 +5.74.93.95 +5.74.94.194 +5.74.96.192 +5.74.101.232 +5.74.102.212 +5.74.103.201 +5.74.104.46 +5.74.104.251 +5.74.105.6 +5.74.105.160 +5.74.106.186 +5.74.107.255 +5.74.108.196 +5.74.111.225 +5.74.112.152 +5.74.114.57 +5.74.114.157 +5.74.119.110 +5.74.122.30 +5.74.122.185 +5.74.124.110 +5.74.124.128 +5.74.129.51 +5.74.137.139 +5.74.140.172 +5.74.141.173 +5.74.143.207 +5.74.144.104 +5.74.145.28 +5.74.146.231 +5.74.150.112 +5.74.151.182 +5.74.152.5 +5.74.153.178 +5.74.159.57 +5.74.167.27 +5.74.172.121 +5.74.172.152 +5.74.174.105 +5.74.176.41 +5.74.180.83 +5.74.180.115 +5.74.184.161 +5.74.184.196 +5.74.185.175 +5.74.185.189 +5.74.186.120 +5.74.187.88 +5.74.190.109 +5.74.191.121 +5.74.192.203 +5.74.197.119 +5.74.200.185 +5.74.210.56 +5.74.212.34 +5.74.213.219 +5.74.217.255 +5.74.218.230 +5.74.224.78 +5.74.227.206 +5.74.227.218 +5.74.229.87 +5.74.230.51 +5.74.232.30 +5.74.233.67 +5.74.234.125 +5.74.234.204 +5.74.239.116 +5.74.245.238 +5.74.249.28 +5.74.249.161 +5.74.250.248 +5.74.251.44 +5.74.255.5 +5.74.255.195 +5.106.18.195 +5.106.19.94 +5.106.19.219 +5.134.193.5 +5.134.196.186 +5.159.48.64 +5.159.48.65 +5.159.48.66 +5.159.48.67 +5.159.48.68 +5.159.48.69 +5.159.48.70 +5.159.48.71 +5.159.50.80 +5.159.50.81 +5.159.50.82 +5.159.50.83 +5.159.50.84 +5.159.50.85 +5.159.50.86 +5.159.50.87 +5.159.51.208 +5.159.51.209 +5.159.51.210 +5.159.51.211 +5.159.51.212 +5.159.51.213 +5.159.51.214 +5.159.52.128 +5.159.52.130 +5.159.52.131 +5.159.52.133 +5.159.52.134 +5.159.52.135 +5.159.52.240 +5.159.52.242 +5.159.52.243 +5.159.53.57 +5.159.53.59 +5.159.53.60 +5.159.53.61 +5.159.53.63 +5.159.55.32 +5.159.55.33 +5.159.55.34 +5.159.55.35 +5.159.55.36 +5.159.55.37 +5.159.55.38 +5.159.55.39 +5.160.4.2 +5.160.4.3 +5.160.4.4 +5.160.4.5 +5.160.4.7 +5.160.4.8 +5.160.4.9 +5.160.4.10 +5.160.4.11 +5.160.4.12 +5.160.12.122 +5.160.13.202 +5.160.24.22 +5.160.25.130 +5.160.36.9 +5.160.45.254 +5.160.56.246 +5.160.62.1 +5.160.65.254 +5.160.92.121 +5.160.92.226 +5.160.92.227 +5.160.92.228 +5.160.92.229 +5.160.92.230 +5.160.94.34 +5.160.96.225 +5.160.109.184 +5.160.123.194 +5.160.126.138 +5.160.136.10 +5.160.148.66 +5.160.148.67 +5.160.148.68 +5.160.148.69 +5.160.148.70 +5.160.148.71 +5.160.148.72 +5.160.148.74 +5.160.148.77 +5.160.163.134 +5.160.171.205 +5.160.178.11 +5.160.186.158 +5.160.224.230 +5.160.236.206 +5.190.6.209 +5.190.15.14 +5.190.15.24 +5.190.19.169 +5.190.31.223 +5.190.35.66 +5.190.35.97 +5.190.38.2 +5.190.38.49 +5.190.69.7 +5.190.112.72 +5.190.113.6 +5.190.113.140 +5.190.113.183 +5.190.128.8 +5.190.128.129 +5.190.160.53 +5.190.160.57 +5.190.220.34 +5.198.177.232 +5.200.65.62 +5.200.72.170 +5.200.72.171 +5.200.72.172 +5.200.72.173 +5.200.74.42 +5.200.74.50 +5.200.92.86 +5.200.92.244 +5.200.128.20 +5.200.132.3 +5.200.134.101 +5.200.146.99 +5.200.163.136 +5.200.164.247 +5.200.170.71 +5.200.171.36 +5.200.176.48 +5.200.184.32 +5.200.187.76 +5.200.194.128 +5.200.195.164 +5.200.197.185 +5.200.198.18 +5.200.201.89 +5.200.210.162 +5.200.215.15 +5.200.240.130 +5.200.242.159 +5.200.244.61 +5.201.137.96 +5.201.179.171 +5.201.188.29 +5.201.190.178 +5.202.7.227 +5.202.8.189 +5.202.18.102 +5.202.31.165 +5.202.31.223 +5.202.33.71 +5.202.37.55 +5.202.44.204 +5.202.45.176 +5.202.46.130 +5.202.50.117 +5.202.53.129 +5.202.56.141 +5.202.60.116 +5.202.67.98 +5.202.68.103 +5.202.84.189 +5.202.101.56 +5.202.101.146 +5.202.104.46 +5.202.105.105 +5.202.130.8 +5.202.130.15 +5.202.131.150 +5.202.133.117 +5.202.134.189 +5.202.134.199 +5.202.168.111 +5.202.168.119 +5.202.168.152 +5.202.169.223 +5.202.170.19 +5.202.171.163 +5.202.171.191 +5.202.172.103 +5.202.172.114 +5.202.172.183 +5.202.174.52 +5.202.176.249 +5.202.180.114 +5.202.192.33 +5.202.193.131 +5.202.195.45 +5.202.195.51 +5.202.195.180 +5.202.197.82 +5.202.198.47 +5.202.243.73 +5.202.244.49 +5.202.244.217 +5.202.248.100 +5.202.248.115 +5.202.251.74 +5.202.252.251 +5.232.193.40 +5.232.195.40 +5.232.199.145 +5.232.201.225 +5.232.208.59 +5.232.208.178 +5.232.210.50 +5.232.210.132 +5.232.210.222 +5.232.214.197 +5.233.44.62 +5.233.46.93 +5.233.48.136 +5.233.48.194 +5.233.52.86 +5.233.53.56 +5.233.55.148 +5.233.58.136 +5.233.58.144 +5.233.59.44 +5.233.59.230 +5.233.61.98 +5.233.64.200 +5.233.67.248 +5.233.69.125 +5.233.74.76 +5.233.75.240 +5.233.75.250 +5.233.77.37 +5.233.81.193 +5.233.85.66 +5.233.91.53 +5.233.93.115 +5.233.93.131 +5.233.94.60 +5.233.97.234 +5.233.106.189 +5.233.181.73 +5.233.250.11 +5.233.250.69 +5.234.0.79 +5.234.33.50 +5.234.34.57 +5.234.196.166 +5.234.199.241 +5.234.200.92 +5.234.200.137 +5.234.202.94 +5.234.202.190 +5.234.204.177 +5.234.204.253 +5.234.207.165 +5.234.208.89 +5.234.210.1 +5.234.219.151 +5.235.156.18 +5.235.158.237 +5.235.160.167 +5.235.163.89 +5.235.163.98 +5.235.165.39 +5.235.168.186 +5.235.183.5 +5.235.183.159 +5.235.184.199 +5.235.186.254 +5.235.204.78 +5.235.208.203 +5.235.225.63 +5.235.237.139 +5.236.27.202 +5.236.36.77 +5.236.37.103 +5.236.37.159 +5.236.52.142 +5.236.68.129 +5.236.73.237 +5.236.74.210 +5.236.84.190 +5.236.84.192 +5.236.85.62 +5.236.107.158 +5.236.118.91 +5.236.118.105 +5.236.119.175 +5.236.119.229 +5.236.120.190 +5.236.120.193 +5.236.120.235 +5.236.123.214 +5.236.124.167 +5.236.126.76 +5.236.126.227 +5.236.194.234 +5.236.195.148 +5.236.209.26 +5.236.229.2 +5.236.230.82 +5.236.231.10 +5.236.232.140 +5.236.232.215 +5.236.232.238 +5.236.234.18 +5.236.235.10 +5.236.235.199 +5.236.237.236 +5.236.239.22 +5.236.241.109 +5.236.241.133 +5.236.245.47 +5.236.245.86 +5.236.245.163 +5.236.246.226 +5.236.249.2 +5.236.250.105 +5.236.250.176 +5.236.251.135 +5.237.48.134 +5.237.51.190 +5.237.172.191 +5.237.174.161 +5.237.190.133 +5.237.206.248 +5.237.213.90 +5.237.245.132 +5.237.255.27 +5.238.0.89 +5.238.7.139 +5.238.24.206 +5.238.30.209 +5.238.31.104 +5.238.65.35 +5.238.78.40 +5.238.88.24 +5.238.89.91 +5.238.97.239 +5.238.110.3 +5.238.110.167 +5.238.110.213 +5.238.130.4 +5.238.133.19 +5.238.134.79 +5.238.135.96 +5.238.141.127 +5.238.143.185 +5.238.150.47 +5.238.150.53 +5.238.150.56 +5.238.151.220 +5.238.152.177 +5.238.152.198 +5.238.152.219 +5.238.154.229 +5.238.156.79 +5.238.156.165 +5.238.157.70 +5.238.157.199 +5.238.158.245 +5.238.159.131 +5.238.160.36 +5.238.165.192 +5.238.169.30 +5.238.171.5 +5.238.172.60 +5.238.173.41 +5.238.175.197 +5.238.177.107 +5.238.178.33 +5.238.179.239 +5.238.181.231 +5.238.181.255 +5.238.182.247 +5.238.185.124 +5.238.187.1 +5.238.187.140 +5.238.193.30 +5.238.193.244 +5.238.200.210 +5.238.201.203 +5.238.203.199 +5.238.204.198 +5.238.207.195 +5.238.212.93 +5.238.212.230 +5.238.215.18 +5.238.217.118 +5.238.219.142 +5.238.220.91 +5.238.220.102 +5.238.220.140 +5.238.220.186 +5.238.220.220 +5.238.220.248 +5.238.221.65 +5.238.223.141 +5.238.223.191 +5.238.224.243 +5.238.225.34 +5.238.229.124 +5.238.232.103 +5.238.232.177 +5.238.236.69 +5.238.238.240 +5.238.240.163 +5.238.241.173 +5.238.242.42 +5.238.242.208 +5.238.243.13 +5.238.245.212 +5.238.246.10 +5.238.246.172 +5.238.247.166 +5.238.248.237 +5.238.251.105 +5.238.253.179 +5.238.255.209 +5.239.1.48 +5.239.2.189 +5.239.7.66 +5.239.8.61 +5.239.8.91 +5.239.9.27 +5.239.9.95 +5.239.15.238 +5.239.16.146 +5.239.18.169 +5.239.19.59 +5.239.21.123 +5.239.21.243 +5.239.23.168 +5.239.28.79 +5.239.28.187 +5.239.28.190 +5.239.32.187 +5.239.33.153 +5.239.39.141 +5.239.43.186 +5.239.44.114 +5.239.45.55 +5.239.45.119 +5.239.46.122 +5.239.50.227 +5.239.52.92 +5.239.53.98 +5.239.56.200 +5.239.57.135 +5.239.58.62 +5.239.59.64 +5.239.59.107 +5.239.60.67 +5.239.62.104 +5.239.63.8 +5.239.64.243 +5.239.67.160 +5.239.67.218 +5.239.68.12 +5.239.72.145 +5.239.75.70 +5.239.77.124 +5.239.77.235 +5.239.78.153 +5.239.81.155 +5.239.82.24 +5.239.82.141 +5.239.84.50 +5.239.86.80 +5.239.86.114 +5.239.86.213 +5.239.86.250 +5.239.87.163 +5.239.88.27 +5.239.88.97 +5.239.89.181 +5.239.95.160 +5.239.97.128 +5.239.97.243 +5.239.106.96 +5.239.107.197 +5.239.114.119 +5.239.114.191 +5.239.115.200 +5.239.118.70 +5.239.120.171 +5.239.121.241 +5.239.122.71 +5.239.123.122 +5.239.146.31 +5.239.156.62 +5.239.160.78 +5.239.229.90 +5.239.229.93 +5.239.229.141 +5.239.230.163 +5.239.231.128 +5.239.232.233 +5.239.234.104 +5.239.234.118 +5.239.234.251 +5.239.237.197 +5.239.240.243 +5.239.241.88 +10.20.35.250 +10.59.1.1 +10.87.91.58 +10.87.96.166 +10.87.97.70 +10.88.67.214 +10.101.97.90 +10.114.50.150 +10.114.50.206 +10.114.61.26 +10.114.123.38 +10.114.123.62 +10.114.141.78 +10.114.141.214 +10.114.143.50 +10.140.39.98 +10.176.254.22 +10.233.240.106 +10.239.1.71 +31.7.65.42 +31.7.65.43 +31.7.65.44 +31.7.65.46 +31.7.89.106 +31.7.89.126 +31.7.90.115 +31.24.201.82 +31.25.92.29 +31.25.92.100 +31.25.92.161 +31.25.129.114 +31.25.132.114 +31.25.135.146 +31.47.51.23 +31.47.51.149 +31.171.222.82 +31.184.141.30 +31.184.188.88 +37.32.47.176 +37.32.121.210 +37.32.121.225 +37.32.125.194 +37.75.244.6 +37.75.244.51 +37.75.245.24 +37.75.245.59 +37.75.245.130 +37.75.245.132 +37.75.245.133 +37.75.246.1 +37.75.246.50 +37.75.246.52 +37.75.246.53 +37.143.147.31 +37.148.0.71 +37.148.17.66 +37.148.25.167 +37.148.58.95 +37.148.81.211 +37.148.86.227 +37.152.164.50 +37.153.128.176 +37.153.130.55 +37.156.8.33 +37.156.8.85 +37.156.9.190 +37.156.11.70 +37.156.12.28 +37.156.13.215 +37.156.15.184 +37.156.15.246 +37.156.30.206 +37.156.144.81 +37.191.78.213 +37.191.78.214 +37.202.156.75 +37.202.156.98 +37.202.157.22 +37.202.170.112 +37.202.171.204 +37.202.189.21 +37.228.139.163 +37.228.139.164 +37.228.139.165 +37.228.139.166 +37.255.128.218 +37.255.128.220 +37.255.129.56 +37.255.132.26 +37.255.134.87 +37.255.134.143 +37.255.149.128 +37.255.149.221 +37.255.173.50 +37.255.176.18 +37.255.192.204 +37.255.195.143 +37.255.196.74 +37.255.196.175 +37.255.208.116 +37.255.210.1 +37.255.211.212 +37.255.213.72 +37.255.213.226 +37.255.217.2 +37.255.220.2 +37.255.220.101 +37.255.222.28 +37.255.222.121 +37.255.223.74 +37.255.226.13 +37.255.231.150 +37.255.235.76 +37.255.236.74 +37.255.238.0 +37.255.238.113 +37.255.238.136 +37.255.238.156 +37.255.240.167 +37.255.241.74 +37.255.242.142 +37.255.242.252 +37.255.255.243 +45.9.253.179 +45.87.4.91 +45.87.4.130 +45.94.212.5 +45.132.175.121 +45.135.241.251 +45.138.135.75 +45.142.191.27 +45.142.191.196 +45.142.191.197 +45.142.191.202 +45.142.191.203 +45.159.150.79 +46.32.31.195 +46.34.161.193 +46.34.166.142 +46.34.166.236 +46.51.37.142 +46.100.2.195 +46.100.3.123 +46.100.8.118 +46.100.10.122 +46.100.10.123 +46.100.10.237 +46.100.11.36 +46.100.11.103 +46.100.11.168 +46.100.32.107 +46.100.32.206 +46.100.33.20 +46.100.40.43 +46.100.41.93 +46.100.42.58 +46.100.45.65 +46.100.45.228 +46.100.45.234 +46.100.47.174 +46.100.49.96 +46.100.50.129 +46.100.52.76 +46.100.53.30 +46.100.53.68 +46.100.55.77 +46.100.56.65 +46.100.57.133 +46.100.58.82 +46.100.58.238 +46.100.59.62 +46.100.59.121 +46.100.60.8 +46.100.60.29 +46.100.60.125 +46.100.60.151 +46.100.61.40 +46.100.61.74 +46.100.61.171 +46.100.61.247 +46.100.62.89 +46.100.75.205 +46.100.92.38 +46.100.92.64 +46.100.93.209 +46.100.94.175 +46.100.99.218 +46.100.106.113 +46.100.116.237 +46.100.132.82 +46.100.132.250 +46.100.146.123 +46.100.147.227 +46.100.148.51 +46.100.149.144 +46.100.164.204 +46.100.165.3 +46.100.165.41 +46.100.165.154 +46.100.167.151 +46.100.167.203 +46.100.168.233 +46.100.169.22 +46.100.169.38 +46.100.169.142 +46.100.171.14 +46.100.171.105 +46.100.187.239 +46.100.196.108 +46.100.216.23 +46.100.219.91 +46.100.220.219 +46.100.220.225 +46.100.221.40 +46.100.221.85 +46.100.231.142 +46.100.243.137 +46.100.248.35 +46.102.130.37 +46.143.245.4 +46.148.34.220 +46.148.38.10 +46.148.38.19 +46.148.38.43 +46.148.39.101 +46.148.40.49 +46.148.43.190 +46.148.44.204 +46.167.133.214 +46.167.136.35 +46.167.140.202 +46.167.144.122 +46.167.144.253 +46.167.145.195 +46.167.147.150 +46.167.151.122 +46.167.151.238 +46.167.159.23 +46.209.22.231 +46.209.30.46 +46.209.44.2 +46.209.51.158 +46.209.106.204 +46.209.223.243 +46.209.255.243 +46.235.76.19 +46.235.77.78 +46.245.2.181 +46.245.12.52 +46.245.13.243 +46.245.14.45 +46.245.14.119 +46.245.15.57 +46.245.23.49 +46.245.26.167 +46.245.27.5 +46.245.31.147 +46.245.40.26 +46.245.43.146 +46.245.43.156 +46.245.45.95 +46.245.45.132 +46.245.46.94 +46.245.76.247 +46.245.76.249 +46.245.78.84 +46.245.78.86 +46.245.87.3 +46.245.89.48 +46.245.89.51 +46.245.89.52 +46.245.89.53 +46.245.89.55 +46.245.89.56 +46.245.89.57 +46.245.91.72 +46.245.91.73 +46.245.91.74 +46.245.91.75 +46.245.91.78 +46.245.91.80 +46.245.94.190 +46.245.114.114 +46.249.120.224 +46.249.120.227 +46.249.120.228 +46.249.120.244 +46.249.121.42 +46.249.121.227 +46.249.122.28 +46.249.122.86 +46.249.122.87 +46.249.122.90 +46.249.122.91 +46.249.124.1 +46.249.126.177 +62.60.162.236 +62.60.163.244 +62.60.207.217 +62.60.223.196 +62.220.112.249 +66.79.99.200 +66.79.112.89 +66.79.113.200 +77.36.146.37 +77.36.243.5 +77.81.34.180 +77.81.34.221 +77.81.35.43 +77.81.37.184 +77.81.40.10 +77.81.41.69 +77.81.46.254 +77.81.146.230 +77.81.147.29 +77.81.148.90 +77.81.148.172 +77.81.148.187 +77.81.150.212 +77.81.153.245 +77.81.154.53 +77.81.156.52 +77.81.158.150 +77.81.158.173 +77.81.158.250 +77.104.90.154 +77.104.93.43 +77.104.115.152 +77.104.115.161 +77.104.126.13 +77.104.126.136 +77.238.106.88 +77.238.106.108 +77.238.106.154 +77.238.107.79 +77.238.109.90 +78.38.26.128 +78.38.26.132 +78.38.26.134 +78.38.26.135 +78.38.26.151 +78.38.26.155 +78.38.26.159 +78.38.26.164 +78.38.26.181 +78.38.26.183 +78.38.26.188 +78.38.26.210 +78.38.26.226 +78.38.27.3 +78.38.29.31 +78.38.30.118 +78.38.34.90 +78.38.88.75 +78.38.89.48 +78.38.90.169 +78.38.91.170 +78.38.105.11 +78.38.110.82 +78.38.123.58 +78.38.126.189 +78.38.152.39 +78.38.153.189 +78.38.166.50 +78.38.166.161 +78.38.168.165 +78.38.169.230 +78.38.183.35 +78.38.194.203 +78.38.195.162 +78.38.195.168 +78.38.210.89 +78.38.210.119 +78.38.210.206 +78.38.217.110 +78.38.243.235 +78.39.11.74 +78.39.26.103 +78.39.28.149 +78.39.29.203 +78.39.29.208 +78.39.31.90 +78.39.57.167 +78.39.81.36 +78.39.83.6 +78.39.83.21 +78.39.83.133 +78.39.88.109 +78.39.88.157 +78.39.88.243 +78.39.89.85 +78.39.89.162 +78.39.90.151 +78.39.91.177 +78.39.91.192 +78.39.92.139 +78.39.93.201 +78.39.94.33 +78.39.94.34 +78.39.95.51 +78.39.97.3 +78.39.99.16 +78.39.99.18 +78.39.99.19 +78.39.99.20 +78.39.99.21 +78.39.99.22 +78.39.99.23 +78.39.99.114 +78.39.99.118 +78.39.112.154 +78.39.119.115 +78.39.119.205 +78.39.120.28 +78.39.120.135 +78.39.120.208 +78.39.122.39 +78.39.123.99 +78.39.123.105 +78.39.126.216 +78.39.134.132 +78.39.139.32 +78.39.140.106 +78.39.140.166 +78.39.142.2 +78.39.145.18 +78.39.173.132 +78.39.201.210 +78.39.228.64 +78.39.228.77 +78.39.231.68 +78.39.233.249 +78.39.234.58 +78.39.235.85 +78.39.250.120 +78.110.122.102 +78.157.34.223 +78.157.35.16 +78.157.35.31 +78.157.35.40 +78.157.37.240 +78.157.38.175 +78.157.39.84 +78.157.39.112 +78.157.39.127 +78.157.41.112 +78.157.41.120 +78.157.43.176 +78.157.43.183 +78.157.45.48 +78.157.45.136 +78.157.45.224 +78.157.45.231 +78.157.45.240 +78.157.45.247 +78.157.46.80 +78.157.47.115 +78.157.48.23 +78.157.48.127 +78.157.48.128 +78.157.50.23 +78.157.57.32 +78.157.57.60 +78.157.57.67 +78.157.57.194 +78.157.57.227 +78.157.57.237 +78.157.58.31 +78.157.58.36 +78.157.58.40 +78.157.58.168 +78.157.61.40 +78.158.191.228 +79.127.2.107 +79.127.3.107 +79.127.3.122 +79.127.4.41 +79.127.4.154 +79.127.5.25 +79.127.5.209 +79.127.6.78 +79.127.6.134 +79.127.6.165 +79.127.7.120 +79.127.13.189 +79.127.14.88 +79.127.69.121 +79.127.69.125 +79.127.75.51 +79.127.75.52 +79.127.75.54 +79.127.77.51 +79.127.77.52 +79.127.77.53 +79.127.77.54 +79.127.101.13 +79.127.125.18 +79.127.126.91 +79.175.182.42 +79.175.186.60 +79.175.186.61 +80.75.5.26 +80.75.8.70 +80.191.16.186 +80.191.17.246 +80.191.38.128 +80.191.39.11 +80.191.39.185 +80.191.40.137 +80.191.40.152 +80.191.51.51 +80.191.52.19 +80.191.64.104 +80.191.71.89 +80.191.71.136 +80.191.77.163 +80.191.86.234 +80.191.87.143 +80.191.100.159 +80.191.102.86 +80.191.107.5 +80.191.189.166 +80.191.192.249 +80.191.194.187 +80.191.195.227 +80.191.203.4 +80.191.220.134 +80.191.221.10 +80.191.222.119 +80.191.230.86 +80.191.235.130 +80.191.240.70 +80.191.250.61 +80.191.253.225 +80.210.6.100 +80.210.7.254 +80.210.8.120 +80.210.9.170 +80.210.9.226 +80.210.12.253 +80.210.17.204 +80.210.18.80 +80.210.18.226 +80.210.21.43 +80.210.22.3 +80.210.22.27 +80.210.22.73 +80.210.22.115 +80.210.22.181 +80.210.22.219 +80.210.24.197 +80.210.25.137 +80.210.26.129 +80.210.27.23 +80.210.30.51 +80.210.30.117 +80.210.32.0 +80.210.32.240 +80.210.35.177 +80.210.35.231 +80.210.37.24 +80.210.41.3 +80.210.41.114 +80.210.42.127 +80.210.42.186 +80.210.44.69 +80.210.45.21 +80.210.45.71 +80.210.48.164 +80.210.48.185 +80.210.50.88 +80.210.50.152 +80.210.50.199 +80.210.51.52 +80.210.52.182 +80.210.54.72 +80.210.58.171 +80.210.59.165 +80.210.60.166 +80.210.63.101 +80.210.139.23 +80.210.141.56 +80.210.160.201 +80.210.164.153 +80.210.183.27 +80.210.193.176 +80.210.196.185 +80.210.242.220 +80.210.245.84 +80.210.246.86 +80.244.11.2 +80.253.152.178 +80.253.152.180 +81.12.1.28 +81.12.5.227 +81.12.47.52 +81.12.70.146 +81.12.72.234 +81.12.74.5 +81.12.76.64 +81.12.76.65 +81.12.76.66 +81.12.76.67 +81.12.76.68 +81.12.76.69 +81.12.76.70 +81.12.76.71 +81.12.76.73 +81.12.76.74 +81.12.76.75 +81.12.76.77 +81.12.76.79 +81.12.84.247 +81.12.87.101 +81.12.99.218 +81.12.118.214 +81.12.124.117 +81.12.124.158 +81.12.127.50 +81.16.112.49 +81.16.112.111 +81.16.113.186 +81.16.114.44 +81.16.114.199 +81.16.117.49 +81.16.121.150 +81.16.121.241 +81.16.124.53 +81.16.124.73 +81.16.124.165 +81.16.125.185 +81.16.125.235 +81.16.126.254 +81.28.32.40 +81.28.33.58 +81.28.52.31 +81.28.61.73 +81.29.248.99 +81.29.248.100 +81.29.248.101 +81.29.248.102 +81.29.248.103 +81.29.248.104 +81.29.248.105 +81.29.248.107 +81.90.146.46 +81.91.136.210 +81.91.144.18 +81.91.146.22 +81.91.151.30 +81.91.152.222 +81.91.153.9 +81.91.155.78 +81.91.156.186 +81.91.157.178 +81.91.159.58 +81.163.6.45 +82.99.232.78 +82.99.253.49 +84.47.236.218 +84.47.236.242 +84.241.1.127 +84.241.2.181 +84.241.2.187 +84.241.3.56 +84.241.3.94 +84.241.3.202 +84.241.4.78 +84.241.5.178 +84.241.6.65 +84.241.6.132 +84.241.6.191 +84.241.6.244 +84.241.8.128 +84.241.8.189 +84.241.9.10 +84.241.12.44 +84.241.12.173 +84.241.12.174 +84.241.14.53 +84.241.14.215 +84.241.14.220 +84.241.14.228 +84.241.15.29 +84.241.15.191 +84.241.16.37 +84.241.16.46 +84.241.16.203 +84.241.16.229 +84.241.18.174 +84.241.18.236 +84.241.18.254 +84.241.19.114 +84.241.22.145 +84.241.23.155 +84.241.24.107 +84.241.24.201 +84.241.24.246 +84.241.25.93 +84.241.25.250 +84.241.26.234 +84.241.27.32 +84.241.27.173 +84.241.28.126 +84.241.28.170 +84.241.28.209 +84.241.29.33 +84.241.29.55 +84.241.29.213 +84.241.30.49 +84.241.30.140 +84.241.36.194 +84.241.38.77 +84.241.38.166 +84.241.41.13 +84.241.42.191 +84.241.44.145 +84.241.44.198 +84.241.45.83 +84.241.45.218 +84.241.49.40 +84.241.52.13 +84.241.52.15 +84.241.56.122 +84.241.60.91 +85.9.87.50 +85.9.87.51 +85.9.94.3 +85.9.94.229 +85.9.105.38 +85.9.105.41 +85.9.115.105 +85.9.120.237 +85.9.121.104 +85.9.122.69 +85.133.133.206 +85.133.136.202 +85.133.137.98 +85.133.173.138 +85.133.173.139 +85.133.173.141 +85.133.173.142 +85.133.176.48 +85.133.184.233 +85.133.186.106 +85.133.187.69 +85.133.190.170 +85.133.221.190 +85.133.221.191 +85.133.228.178 +85.133.229.121 +85.133.229.202 +85.185.3.100 +85.185.12.173 +85.185.13.60 +85.185.13.148 +85.185.14.43 +85.185.43.176 +85.185.43.224 +85.185.49.27 +85.185.54.100 +85.185.62.32 +85.185.75.218 +85.185.78.181 +85.185.81.226 +85.185.81.227 +85.185.85.6 +85.185.87.105 +85.185.88.134 +85.185.98.57 +85.185.111.80 +85.185.123.177 +85.185.133.193 +85.185.138.250 +85.185.139.34 +85.185.139.42 +85.185.185.172 +85.185.187.2 +85.185.190.109 +85.185.201.197 +85.185.201.198 +85.185.201.227 +85.185.201.230 +85.185.206.161 +85.185.208.82 +85.185.212.150 +85.185.235.18 +85.185.238.111 +85.185.239.154 +85.185.239.155 +85.185.239.157 +85.198.26.3 +85.198.28.2 +85.198.29.80 +85.198.30.54 +85.198.30.223 +85.198.31.15 +85.198.31.107 +85.198.31.113 +85.204.88.255 +85.204.208.159 +86.104.38.94 +86.104.83.86 +86.104.88.202 +86.104.96.231 +86.104.98.191 +86.104.98.195 +86.104.98.239 +86.104.100.245 +86.104.101.72 +86.104.105.166 +86.104.106.34 +86.104.107.4 +86.104.109.105 +86.104.111.197 +86.105.43.13 +86.105.44.251 +86.105.45.105 +86.105.46.122 +86.105.47.176 +86.105.134.249 +86.105.138.65 +86.107.93.69 +86.107.145.246 +86.107.148.66 +86.107.149.221 +86.107.152.218 +86.107.155.195 +86.107.155.228 +86.107.156.242 +86.107.157.82 +87.107.17.22 +87.107.18.26 +87.107.18.178 +87.107.18.180 +87.107.48.10 +87.107.55.66 +87.107.87.8 +87.107.87.9 +87.107.87.10 +87.107.87.11 +87.107.105.43 +87.107.105.145 +87.107.136.130 +87.107.141.35 +87.107.141.136 +87.236.38.97 +87.236.38.99 +87.236.38.226 +87.236.214.214 +87.236.215.179 +87.248.145.213 +87.248.150.229 +87.248.151.36 +87.251.141.125 +88.218.16.4 +89.32.197.10 +89.33.129.156 +89.37.152.90 +89.37.153.30 +89.37.153.57 +89.37.155.10 +89.37.155.119 +89.37.219.25 +89.38.103.224 +89.38.244.110 +89.38.246.130 +89.39.9.15 +89.39.9.200 +89.39.11.125 +89.39.11.190 +89.40.78.167 +89.40.79.39 +89.40.79.93 +89.40.79.94 +89.40.246.181 +89.40.247.32 +89.41.40.219 +89.41.41.95 +89.42.69.188 +89.42.98.141 +89.43.7.182 +89.43.144.209 +89.43.220.123 +89.43.222.29 +89.43.224.58 +89.43.224.123 +89.43.226.208 +89.43.227.25 +89.43.227.182 +89.43.230.205 +89.43.231.153 +89.44.132.254 +89.44.176.90 +89.46.95.145 +89.46.216.48 +89.46.216.49 +89.46.216.50 +89.46.216.51 +89.46.216.53 +89.46.218.208 +89.46.218.209 +89.46.218.210 +89.46.218.211 +89.46.218.212 +89.46.218.213 +89.46.218.214 +89.46.218.215 +89.46.219.39 +89.46.219.200 +89.46.219.201 +89.46.219.202 +89.46.219.203 +89.46.219.204 +89.46.219.205 +89.46.219.206 +89.46.219.207 +89.144.136.152 +89.144.148.155 +89.144.157.144 +89.144.157.219 +89.144.167.45 +89.219.64.165 +89.219.66.125 +89.219.68.58 +89.219.69.122 +89.219.69.188 +89.219.71.91 +89.219.71.206 +89.219.74.95 +89.219.74.246 +89.219.76.55 +89.219.76.168 +89.219.76.250 +89.219.76.253 +89.219.77.179 +89.219.78.137 +89.219.78.248 +89.219.79.28 +89.219.79.34 +89.219.81.20 +89.219.82.36 +89.219.82.192 +89.219.84.16 +89.219.88.172 +89.219.92.79 +89.219.95.236 +89.219.98.186 +89.219.102.81 +89.219.102.145 +89.219.102.228 +89.219.104.103 +89.219.105.36 +89.219.105.147 +89.219.105.208 +89.219.106.180 +89.219.106.245 +89.219.107.242 +89.219.108.30 +89.219.108.87 +89.219.108.119 +89.219.108.162 +89.219.114.19 +89.219.114.67 +89.219.116.81 +89.219.117.55 +89.219.118.124 +89.219.120.196 +89.219.121.97 +89.219.121.202 +89.219.122.163 +89.219.124.77 +89.219.124.182 +89.219.124.190 +89.219.125.219 +89.219.125.241 +89.219.195.28 +89.219.197.140 +89.219.198.174 +89.219.200.215 +89.219.202.170 +89.219.204.225 +89.219.205.242 +89.219.206.64 +89.219.206.217 +89.219.208.93 +89.219.209.192 +89.219.210.23 +89.219.210.59 +89.219.210.101 +89.219.211.41 +89.219.211.114 +89.219.211.122 +89.219.213.9 +89.219.215.18 +89.219.216.120 +89.219.219.5 +89.219.220.133 +89.219.224.139 +89.219.224.145 +89.219.225.30 +89.219.226.171 +89.219.227.18 +89.219.227.30 +89.219.229.128 +89.219.230.93 +89.219.235.240 +89.219.238.107 +89.219.240.85 +89.219.240.218 +89.219.241.130 +89.219.241.141 +89.219.244.22 +89.219.244.236 +89.219.246.68 +89.219.246.152 +89.219.246.183 +89.219.249.214 +89.219.251.90 +89.219.251.219 +89.219.251.248 +89.219.253.157 +89.219.254.24 +89.219.254.58 +89.221.84.53 +89.221.84.111 +89.221.85.46 +89.221.85.61 +89.221.85.149 +89.221.85.194 +89.221.85.206 +89.221.85.225 +89.221.85.235 +89.221.85.241 +89.221.85.242 +89.221.85.253 +89.221.86.62 +89.221.86.159 +89.221.86.186 +89.235.70.70 +89.235.102.11 +89.235.109.20 +91.92.121.161 +91.92.121.180 +91.92.121.214 +91.92.122.157 +91.92.125.104 +91.92.125.118 +91.92.125.195 +91.92.129.240 +91.92.130.175 +91.92.131.18 +91.92.131.52 +91.92.131.133 +91.92.133.152 +91.92.149.40 +91.92.150.170 +91.92.157.114 +91.92.157.168 +91.92.164.143 +91.92.180.196 +91.92.181.171 +91.92.181.193 +91.92.182.158 +91.92.185.37 +91.92.186.76 +91.92.186.135 +91.92.188.251 +91.92.191.47 +91.92.191.125 +91.92.205.45 +91.92.207.143 +91.92.207.179 +91.92.209.91 +91.92.212.107 +91.92.212.182 +91.92.214.181 +91.92.215.243 +91.106.67.30 +91.106.80.65 +91.106.80.127 +91.106.83.24 +91.106.83.29 +91.106.90.177 +91.106.94.136 +91.108.138.179 +91.108.150.42 +91.185.129.168 +91.185.130.76 +91.185.140.53 +91.185.141.74 +91.185.144.212 +91.185.147.40 +91.186.193.26 +91.186.193.57 +91.199.18.60 +91.222.206.170 +91.222.206.206 +91.232.72.92 +91.232.72.108 +91.232.72.116 +91.232.72.118 +91.232.72.126 +91.232.74.194 +91.234.39.83 +91.242.44.34 +91.243.165.178 +91.243.172.186 +92.114.17.4 +92.119.58.85 +92.119.58.126 +92.242.193.66 +92.242.206.6 +92.242.219.186 +92.246.144.29 +93.114.111.108 +93.114.111.175 +93.115.121.162 +93.115.125.255 +93.115.127.45 +93.115.144.17 +93.115.144.80 +93.115.145.41 +93.115.145.221 +93.115.145.237 +93.115.146.70 +93.115.146.140 +93.115.146.168 +93.115.146.217 +93.115.147.35 +93.115.147.60 +93.115.147.79 +93.115.147.92 +93.115.147.105 +93.115.147.149 +93.115.147.162 +93.115.148.33 +93.115.148.245 +93.115.149.187 +93.115.149.243 +93.115.219.124 +93.117.1.197 +93.117.32.98 +93.117.38.186 +93.117.41.59 +93.117.42.205 +93.117.127.64 +93.117.127.230 +93.118.96.209 +93.118.107.27 +93.118.107.167 +93.118.108.1 +93.118.108.20 +93.118.108.97 +93.118.108.201 +93.118.110.78 +93.118.114.252 +93.118.115.17 +93.118.116.160 +93.118.119.64 +93.118.119.238 +93.118.120.51 +93.118.120.135 +93.118.123.87 +93.118.124.218 +93.118.126.1 +93.118.127.126 +93.118.127.147 +93.118.127.234 +93.118.128.93 +93.118.128.173 +93.118.132.228 +93.118.136.52 +93.118.139.177 +93.118.139.247 +93.118.140.25 +93.118.140.163 +93.118.141.13 +93.118.141.183 +93.118.142.99 +93.118.142.181 +93.118.143.242 +93.118.145.14 +93.118.145.65 +93.118.146.80 +93.118.146.143 +93.118.146.178 +93.118.147.1 +93.118.148.211 +93.118.148.214 +93.118.150.2 +93.118.151.69 +93.118.151.170 +93.118.152.54 +93.118.152.147 +93.118.152.220 +93.118.153.212 +93.118.154.127 +93.118.156.8 +93.118.159.179 +93.118.160.4 +93.118.160.31 +93.118.161.97 +93.118.161.126 +93.118.162.200 +93.118.163.83 +93.118.163.99 +93.118.163.126 +93.118.163.166 +93.118.164.241 +93.118.166.232 +93.118.180.65 +93.126.7.16 +93.126.9.38 +93.126.10.1 +93.126.11.129 +93.126.12.160 +93.126.18.28 +93.126.18.95 +93.126.18.133 +93.126.19.141 +93.126.19.239 +93.126.25.34 +93.126.56.30 +94.101.133.19 +94.139.183.164 +94.182.2.70 +94.182.2.132 +94.182.2.222 +94.182.4.172 +94.182.5.199 +94.182.18.14 +94.182.18.209 +94.182.18.226 +94.182.23.57 +94.182.24.206 +94.182.27.105 +94.182.31.70 +94.182.31.129 +94.182.34.88 +94.182.35.165 +94.182.35.190 +94.182.35.249 +94.182.39.166 +94.182.48.5 +94.182.50.48 +94.182.53.166 +94.182.53.245 +94.182.56.16 +94.182.63.24 +94.182.94.190 +94.182.110.250 +94.182.189.29 +94.182.189.246 +94.182.192.210 +94.182.192.239 +94.182.193.14 +94.182.193.62 +94.182.193.89 +94.182.193.142 +94.182.193.197 +94.182.193.211 +94.182.194.132 +94.182.196.113 +94.182.196.165 +94.182.197.49 +94.182.197.50 +94.182.200.10 +94.182.202.193 +94.182.211.97 +94.182.211.247 +94.182.214.205 +94.182.216.18 +94.182.216.205 +94.182.225.225 +94.183.0.137 +94.183.0.195 +94.183.1.160 +94.183.6.134 +94.183.7.152 +94.183.7.159 +94.183.19.200 +94.183.21.110 +94.183.23.251 +94.183.24.50 +94.183.27.70 +94.183.27.128 +94.183.27.173 +94.183.27.209 +94.183.28.216 +94.183.30.24 +94.183.30.72 +94.183.30.131 +94.183.30.132 +94.183.30.162 +94.183.30.197 +94.183.31.117 +94.183.32.24 +94.183.32.107 +94.183.46.99 +94.183.60.235 +94.183.62.94 +94.183.62.120 +94.183.67.63 +94.183.67.107 +94.183.71.93 +94.183.72.37 +94.183.72.133 +94.183.75.164 +94.183.83.164 +94.183.85.12 +94.183.89.83 +94.183.90.176 +94.183.91.88 +94.183.91.145 +94.183.92.218 +94.183.93.40 +94.183.97.3 +94.183.97.61 +94.183.99.183 +94.183.102.135 +94.183.103.185 +94.183.109.61 +94.183.116.21 +94.183.118.4 +94.183.118.45 +94.183.121.12 +94.183.123.138 +94.183.124.229 +94.183.126.175 +94.183.127.101 +94.183.127.111 +94.183.127.124 +94.183.131.36 +94.183.133.129 +94.183.145.252 +94.183.149.144 +94.183.160.229 +94.183.172.67 +94.184.21.255 +94.184.178.254 +94.232.173.145 +94.232.175.245 +95.38.11.5 +95.38.33.72 +95.38.33.190 +95.38.34.61 +95.38.46.178 +95.38.48.195 +95.38.48.247 +95.38.49.44 +95.38.49.132 +95.38.49.150 +95.38.49.158 +95.38.52.10 +95.38.52.44 +95.38.52.224 +95.38.66.32 +95.38.66.42 +95.38.67.74 +95.38.67.163 +95.38.78.14 +95.38.132.238 +95.38.135.134 +95.38.143.22 +95.38.148.64 +95.38.149.46 +95.38.149.51 +95.38.149.96 +95.38.169.186 +95.38.192.58 +95.38.192.133 +95.38.192.135 +95.38.192.178 +95.38.196.157 +95.38.196.169 +95.38.196.202 +95.38.196.243 +95.38.199.230 +95.80.141.50 +95.80.168.237 +95.80.171.57 +95.80.175.41 +95.130.56.177 +95.130.59.34 +95.130.225.119 +95.130.225.124 +95.130.225.186 +95.130.225.229 +95.130.225.239 +95.130.225.241 +95.156.252.19 +95.215.160.179 +103.215.220.90 +109.72.197.58 +109.95.71.36 +109.109.44.37 +109.109.44.172 +109.109.52.175 +109.122.235.120 +109.122.237.78 +109.125.129.234 +109.125.130.85 +109.125.132.133 +109.125.132.208 +109.125.132.231 +109.125.134.92 +109.125.135.16 +109.125.140.186 +109.125.147.46 +109.125.160.46 +109.125.168.20 +109.201.13.188 +109.201.15.9 +109.230.82.114 +109.230.83.233 +109.230.89.157 +109.230.90.106 +109.230.90.174 +109.230.93.162 +109.230.94.222 +109.230.94.246 +109.230.204.66 +109.230.204.91 +109.230.204.154 +109.230.204.174 +109.230.204.175 +109.232.2.63 +109.238.189.4 +109.238.189.50 +128.65.176.22 +128.65.179.91 +128.65.180.193 +128.65.183.63 +128.65.184.21 +128.65.184.26 +128.65.184.184 +128.65.188.19 +128.65.188.30 +128.65.189.79 +151.232.3.161 +151.232.48.49 +151.232.60.225 +151.232.101.57 +151.232.207.122 +151.233.2.157 +151.233.18.251 +151.233.47.165 +151.233.50.228 +151.233.53.38 +151.233.58.9 +151.234.0.199 +151.234.1.48 +151.234.2.254 +151.234.3.120 +151.234.23.25 +151.234.28.19 +151.234.31.125 +151.234.31.243 +151.234.72.243 +151.234.87.99 +151.234.125.227 +151.234.187.125 +151.234.211.218 +151.234.245.230 +151.235.1.157 +151.235.6.95 +151.235.7.132 +151.235.8.27 +151.235.8.47 +151.235.23.185 +151.235.32.61 +151.235.35.236 +151.235.38.166 +151.235.91.218 +151.235.95.23 +151.235.98.215 +151.235.99.142 +151.235.99.169 +151.235.101.1 +151.235.101.23 +151.235.102.98 +151.235.106.149 +151.235.106.162 +151.235.107.85 +151.235.108.124 +151.235.109.61 +151.235.109.246 +151.235.110.118 +151.235.110.157 +151.235.111.147 +151.235.112.216 +151.235.114.9 +151.235.115.208 +151.235.118.241 +151.235.120.110 +151.235.121.214 +151.235.123.9 +151.235.126.249 +151.235.129.206 +151.235.134.175 +151.235.185.215 +151.235.187.44 +151.235.190.76 +151.235.217.72 +151.235.240.128 +151.235.246.224 +151.235.250.125 +159.20.96.249 +159.20.103.83 +159.20.111.37 +171.22.25.51 +172.16.13.26 +172.17.0.6 +172.18.0.10 +172.18.10.2 +172.19.20.158 +172.19.190.190 +172.19.216.36 +172.20.1.206 +172.20.30.2 +172.21.113.129 +172.22.0.38 +172.22.11.2 +172.22.22.30 +172.24.206.228 +172.24.209.36 +172.30.7.170 +172.30.8.30 +172.31.100.245 +176.65.184.254 +176.65.224.62 +176.65.226.105 +176.65.226.111 +176.65.227.214 +176.65.228.94 +176.65.231.45 +176.65.241.43 +176.65.241.44 +176.65.241.45 +176.65.241.46 +176.65.252.69 +176.65.252.81 +176.65.252.227 +176.65.253.46 +176.65.253.55 +176.65.253.106 +176.65.253.200 +176.65.254.61 +176.65.254.112 +176.65.254.160 +176.122.210.113 +176.223.80.171 +176.223.83.46 +178.131.66.247 +178.131.72.214 +178.131.101.153 +178.131.113.81 +178.131.134.93 +178.131.135.230 +178.173.131.62 +178.173.131.163 +178.173.131.188 +178.173.137.44 +178.173.137.132 +178.173.137.249 +178.173.137.251 +178.173.138.33 +178.173.138.59 +178.173.138.157 +178.173.138.171 +178.173.138.255 +178.173.139.95 +178.173.140.171 +178.173.141.90 +178.173.143.103 +178.173.143.116 +178.173.143.161 +178.173.146.129 +178.173.147.73 +178.173.147.227 +178.173.161.10 +178.173.163.7 +178.173.163.158 +178.173.163.202 +178.173.169.16 +178.173.169.199 +178.173.169.228 +178.173.170.142 +178.173.173.88 +178.173.173.255 +178.173.174.48 +178.173.174.136 +178.173.174.229 +178.173.175.6 +178.173.175.56 +178.173.175.110 +178.173.175.152 +178.173.175.209 +178.173.182.45 +178.173.182.72 +178.173.182.87 +178.173.182.148 +178.173.182.177 +178.173.184.37 +178.173.184.112 +178.173.184.122 +178.173.185.11 +178.173.185.49 +178.173.185.86 +178.173.185.222 +178.173.185.241 +178.173.192.30 +178.173.193.32 +178.173.193.54 +178.173.193.77 +178.173.193.82 +178.173.195.75 +178.173.195.203 +178.173.196.39 +178.173.196.161 +178.173.197.84 +178.173.197.192 +178.173.197.228 +178.173.198.82 +178.173.198.112 +178.173.198.238 +178.173.199.32 +178.173.199.53 +178.173.199.105 +178.173.199.109 +178.173.199.113 +178.173.199.200 +178.173.200.68 +178.173.210.19 +178.173.210.29 +178.173.210.152 +178.173.210.176 +178.173.211.216 +178.173.212.43 +178.173.212.126 +178.173.212.209 +178.173.213.152 +178.173.213.211 +178.173.213.226 +178.173.214.151 +178.173.214.164 +178.173.215.117 +178.173.215.158 +178.173.215.208 +178.173.215.210 +178.173.216.5 +178.173.216.75 +178.173.216.112 +178.173.217.59 +178.173.218.46 +178.173.219.53 +178.173.219.114 +178.173.220.21 +178.173.220.47 +178.173.220.206 +178.173.221.227 +178.173.222.165 +178.236.98.110 +178.236.99.12 +178.236.99.239 +178.236.100.37 +178.236.103.15 +178.236.103.18 +178.236.103.127 +178.236.104.163 +178.236.105.85 +178.236.105.154 +178.236.106.237 +178.236.107.152 +178.236.108.124 +178.236.111.3 +178.236.111.114 +178.238.195.238 +178.239.144.40 +178.239.156.36 +178.239.156.113 +178.239.156.130 +178.239.156.133 +178.239.157.98 +178.239.157.100 +178.239.157.101 +178.239.157.102 +178.239.157.103 +178.239.157.104 +178.252.141.229 +178.252.147.125 +178.252.151.158 +178.252.152.72 +178.252.153.250 +178.252.171.227 +178.252.171.230 +178.252.177.32 +178.252.177.33 +178.252.177.35 +178.252.177.36 +178.252.177.37 +178.252.177.38 +178.252.177.39 +178.252.177.40 +178.252.177.42 +178.252.177.43 +178.252.177.44 +178.252.177.45 +185.3.201.0 +185.3.201.1 +185.3.201.2 +185.3.201.3 +185.3.201.4 +185.3.201.5 +185.3.201.6 +185.3.201.7 +185.3.201.8 +185.3.201.9 +185.3.201.10 +185.3.201.11 +185.3.201.12 +185.3.201.13 +185.3.201.14 +185.3.201.15 +185.3.201.16 +185.3.201.17 +185.3.201.18 +185.3.201.19 +185.3.201.20 +185.3.201.21 +185.3.201.22 +185.3.201.23 +185.3.201.24 +185.3.201.25 +185.3.201.26 +185.3.201.27 +185.3.201.28 +185.3.201.29 +185.3.201.31 +185.3.201.32 +185.3.201.33 +185.3.201.35 +185.3.201.36 +185.3.201.38 +185.3.201.40 +185.3.201.41 +185.3.201.42 +185.3.201.43 +185.3.201.44 +185.3.201.45 +185.3.201.46 +185.3.201.47 +185.3.201.48 +185.3.201.49 +185.3.201.51 +185.3.201.52 +185.3.201.53 +185.3.201.55 +185.3.201.56 +185.3.201.57 +185.3.201.58 +185.3.201.59 +185.3.201.60 +185.3.201.61 +185.3.201.62 +185.3.201.63 +185.3.201.64 +185.3.201.65 +185.3.201.66 +185.3.201.67 +185.3.201.68 +185.3.201.69 +185.3.201.70 +185.3.201.71 +185.3.201.72 +185.3.201.73 +185.3.201.74 +185.3.201.75 +185.3.201.76 +185.3.201.77 +185.3.201.78 +185.3.201.79 +185.3.201.80 +185.3.201.81 +185.3.201.82 +185.3.201.83 +185.3.201.84 +185.3.201.85 +185.3.201.86 +185.3.201.87 +185.3.201.88 +185.3.201.89 +185.3.201.90 +185.3.201.91 +185.3.201.92 +185.3.201.93 +185.3.201.94 +185.3.201.95 +185.3.201.96 +185.3.201.97 +185.3.201.98 +185.3.201.99 +185.3.201.100 +185.3.201.101 +185.3.201.102 +185.3.201.103 +185.3.201.104 +185.3.201.105 +185.3.201.106 +185.3.201.107 +185.3.201.109 +185.3.201.112 +185.3.201.114 +185.3.201.115 +185.3.201.116 +185.3.201.117 +185.3.201.118 +185.3.201.120 +185.3.201.121 +185.3.201.122 +185.3.201.123 +185.3.201.124 +185.3.201.125 +185.3.201.126 +185.3.201.127 +185.3.201.129 +185.3.201.130 +185.3.201.131 +185.3.201.132 +185.3.201.133 +185.3.201.134 +185.3.201.135 +185.3.202.0 +185.3.202.1 +185.3.202.2 +185.3.202.8 +185.3.202.10 +185.3.202.11 +185.3.202.12 +185.3.202.13 +185.3.202.15 +185.3.202.16 +185.3.202.17 +185.3.202.18 +185.3.202.19 +185.3.202.20 +185.3.202.21 +185.3.202.22 +185.3.202.23 +185.3.202.24 +185.3.202.27 +185.3.202.28 +185.3.202.29 +185.3.202.30 +185.3.202.31 +185.3.202.32 +185.3.202.33 +185.3.202.34 +185.3.202.35 +185.3.202.37 +185.3.202.38 +185.3.202.39 +185.3.202.40 +185.3.202.41 +185.3.202.43 +185.3.202.44 +185.3.202.45 +185.3.202.47 +185.3.202.48 +185.3.202.49 +185.3.202.50 +185.3.202.51 +185.3.202.52 +185.3.202.53 +185.3.202.54 +185.3.202.55 +185.3.202.56 +185.3.202.58 +185.3.202.60 +185.3.202.61 +185.3.202.62 +185.3.202.64 +185.3.202.65 +185.3.202.66 +185.3.202.67 +185.3.202.68 +185.3.202.69 +185.3.202.70 +185.3.202.71 +185.3.202.73 +185.3.202.75 +185.3.202.76 +185.3.202.77 +185.3.202.78 +185.3.202.79 +185.3.202.81 +185.3.202.82 +185.3.202.83 +185.3.202.84 +185.3.202.85 +185.3.202.86 +185.3.202.88 +185.3.202.89 +185.3.202.90 +185.3.202.91 +185.3.202.92 +185.3.202.93 +185.3.202.94 +185.3.202.95 +185.3.202.97 +185.3.202.98 +185.3.202.101 +185.3.202.102 +185.3.202.103 +185.3.202.104 +185.3.202.105 +185.3.202.106 +185.3.202.107 +185.3.202.109 +185.3.202.110 +185.3.202.111 +185.3.202.112 +185.3.202.113 +185.3.202.114 +185.3.202.117 +185.3.202.118 +185.3.202.119 +185.3.202.120 +185.3.202.121 +185.3.202.122 +185.3.202.123 +185.3.202.124 +185.3.202.126 +185.3.202.127 +185.3.202.128 +185.3.202.130 +185.3.202.131 +185.3.202.132 +185.3.202.133 +185.3.202.134 +185.3.202.135 +185.3.202.136 +185.3.202.137 +185.3.202.138 +185.3.202.139 +185.3.202.140 +185.3.202.141 +185.3.202.142 +185.3.202.145 +185.3.202.146 +185.3.202.147 +185.3.202.149 +185.3.202.150 +185.3.202.151 +185.3.202.152 +185.3.202.153 +185.3.202.155 +185.3.202.156 +185.3.202.157 +185.3.202.161 +185.3.202.163 +185.3.202.164 +185.3.202.165 +185.3.202.166 +185.3.202.167 +185.3.202.168 +185.3.202.169 +185.3.202.170 +185.3.202.171 +185.3.202.173 +185.3.202.175 +185.3.202.176 +185.3.202.177 +185.3.202.178 +185.3.202.179 +185.3.202.181 +185.3.202.182 +185.3.202.183 +185.3.202.185 +185.3.202.186 +185.3.202.189 +185.3.202.190 +185.3.202.191 +185.3.202.192 +185.3.202.194 +185.3.202.195 +185.3.202.196 +185.3.202.197 +185.3.202.198 +185.3.202.199 +185.3.202.200 +185.3.202.201 +185.3.202.202 +185.3.202.203 +185.3.202.204 +185.3.202.205 +185.3.202.206 +185.3.202.207 +185.3.202.208 +185.3.202.209 +185.3.202.210 +185.3.202.211 +185.3.202.212 +185.3.202.213 +185.3.202.214 +185.3.202.215 +185.3.202.217 +185.3.202.218 +185.3.202.219 +185.3.202.220 +185.3.202.222 +185.3.202.224 +185.3.202.225 +185.3.202.226 +185.3.202.227 +185.3.202.228 +185.3.202.229 +185.3.202.230 +185.3.202.231 +185.3.202.232 +185.3.202.233 +185.3.202.234 +185.3.202.235 +185.3.202.236 +185.3.202.237 +185.3.202.238 +185.3.202.239 +185.3.202.240 +185.3.202.241 +185.3.202.242 +185.3.202.243 +185.3.202.244 +185.3.202.245 +185.3.202.246 +185.3.202.247 +185.3.202.248 +185.3.202.249 +185.3.202.250 +185.3.202.252 +185.3.202.253 +185.3.202.254 +185.3.203.8 +185.3.203.9 +185.3.203.11 +185.3.203.13 +185.3.203.15 +185.3.203.36 +185.3.203.37 +185.3.203.38 +185.3.203.39 +185.3.203.40 +185.3.203.41 +185.3.203.42 +185.3.203.43 +185.3.203.44 +185.3.203.45 +185.3.203.46 +185.3.203.48 +185.3.203.50 +185.3.203.51 +185.3.203.52 +185.3.203.53 +185.3.203.54 +185.3.203.57 +185.3.203.60 +185.3.203.61 +185.3.203.62 +185.3.203.64 +185.3.203.65 +185.3.203.66 +185.3.203.67 +185.3.203.69 +185.3.203.70 +185.3.203.71 +185.3.203.72 +185.3.203.73 +185.3.203.74 +185.3.203.75 +185.3.203.76 +185.3.203.78 +185.3.203.79 +185.3.203.80 +185.3.203.81 +185.3.203.82 +185.3.203.83 +185.3.203.86 +185.3.203.87 +185.3.203.88 +185.3.203.89 +185.3.203.90 +185.3.203.94 +185.3.203.95 +185.3.203.96 +185.3.203.97 +185.3.203.98 +185.3.203.99 +185.3.203.101 +185.3.203.103 +185.3.203.104 +185.3.203.105 +185.3.203.106 +185.3.203.107 +185.3.203.108 +185.3.203.109 +185.3.203.110 +185.3.203.111 +185.3.203.113 +185.3.203.114 +185.3.203.115 +185.3.203.116 +185.3.203.119 +185.3.203.120 +185.3.203.121 +185.3.203.122 +185.3.203.123 +185.3.203.124 +185.3.203.125 +185.3.203.127 +185.14.80.238 +185.18.213.184 +185.20.163.2 +185.21.68.25 +185.22.31.186 +185.42.225.202 +185.42.225.203 +185.42.226.217 +185.44.113.194 +185.46.108.90 +185.46.108.100 +185.46.110.117 +185.46.218.30 +185.46.218.233 +185.49.96.145 +185.49.97.109 +185.49.97.238 +185.53.141.35 +185.53.143.185 +185.56.92.177 +185.56.93.73 +185.56.93.121 +185.66.226.12 +185.66.228.17 +185.71.195.160 +185.72.27.113 +185.72.27.114 +185.72.27.182 +185.72.80.60 +185.72.81.119 +185.83.88.119 +185.83.88.208 +185.83.182.89 +185.83.196.247 +185.86.183.33 +185.86.183.217 +185.92.41.251 +185.100.45.101 +185.100.47.205 +185.103.87.182 +185.103.129.198 +185.103.131.129 +185.106.137.91 +185.106.138.219 +185.106.146.146 +185.109.60.222 +185.109.63.58 +185.109.63.59 +185.109.63.60 +185.109.63.61 +185.109.63.146 +185.109.80.243 +185.109.251.165 +185.110.219.78 +185.110.219.88 +185.110.253.9 +185.111.81.2 +185.112.36.123 +185.112.36.172 +185.112.36.255 +185.112.37.174 +185.112.38.213 +185.112.39.14 +185.112.39.30 +185.112.39.181 +185.115.150.52 +185.115.169.71 +185.115.169.95 +185.115.169.101 +185.115.169.104 +185.115.169.193 +185.115.169.220 +185.117.139.165 +185.117.139.168 +185.117.139.169 +185.117.139.173 +185.119.164.1 +185.120.219.38 +185.120.230.33 +185.120.237.47 +185.120.238.5 +185.121.129.166 +185.126.2.134 +185.126.4.33 +185.126.6.3 +185.126.10.9 +185.126.10.33 +185.126.11.241 +185.128.138.138 +185.129.199.5 +185.129.199.129 +185.129.199.201 +185.129.216.1 +185.129.216.115 +185.129.216.151 +185.129.217.142 +185.131.30.5 +185.131.30.145 +185.131.30.163 +185.131.30.246 +185.131.103.238 +185.132.80.141 +185.132.80.188 +185.134.99.46 +185.136.174.247 +185.136.192.65 +185.136.194.35 +185.136.194.37 +185.136.194.40 +185.136.194.41 +185.136.194.234 +185.137.25.75 +185.137.25.208 +185.137.108.87 +185.141.36.130 +185.141.39.45 +185.142.95.10 +185.145.184.159 +185.147.40.12 +185.147.41.75 +185.147.41.88 +185.147.161.27 +185.147.161.30 +185.155.10.129 +185.155.10.178 +185.155.10.202 +185.155.11.23 +185.155.14.246 +185.155.15.164 +185.161.39.174 +185.161.112.1 +185.161.112.5 +185.161.112.8 +185.161.112.38 +185.161.112.41 +185.161.114.190 +185.162.42.113 +185.164.73.155 +185.169.20.113 +185.170.239.185 +185.172.2.162 +185.172.68.18 +185.172.68.66 +185.172.68.98 +185.172.68.100 +185.172.68.101 +185.172.68.103 +185.172.68.246 +185.172.71.115 +185.172.213.123 +185.174.132.180 +185.174.132.243 +185.176.59.227 +185.177.158.84 +185.177.159.231 +185.179.168.242 +185.179.171.216 +185.180.130.229 +185.180.131.223 +185.191.76.116 +185.191.76.119 +185.191.78.184 +185.193.209.34 +185.193.209.35 +185.193.209.36 +185.193.209.39 +185.193.209.40 +185.193.209.41 +185.193.209.42 +185.193.209.43 +185.193.209.46 +185.208.148.19 +185.213.165.116 +185.221.194.141 +185.221.194.214 +185.224.177.117 +185.224.179.59 +185.225.80.200 +185.225.83.200 +185.225.243.82 +185.225.243.85 +185.225.243.92 +185.225.243.93 +185.229.31.60 +185.235.197.122 +185.235.197.210 +185.235.197.218 +185.237.84.18 +185.237.87.97 +185.237.87.228 +185.255.208.31 +185.255.210.156 +185.255.210.160 +185.255.210.168 +185.255.210.194 +188.0.252.93 +188.75.64.118 +188.75.65.218 +188.75.65.219 +188.75.65.220 +188.75.65.221 +188.121.128.138 +188.121.129.219 +188.121.144.154 +188.121.145.94 +188.121.147.246 +188.121.150.39 +188.121.157.50 +188.121.159.178 +188.136.133.81 +188.136.133.82 +188.136.133.83 +188.136.142.185 +188.136.143.166 +188.136.144.98 +188.136.144.109 +188.136.152.9 +188.136.152.15 +188.136.152.100 +188.136.156.1 +188.136.156.2 +188.136.156.3 +188.136.156.4 +188.136.156.6 +188.136.156.9 +188.136.156.15 +188.136.156.18 +188.136.156.20 +188.136.156.21 +188.136.156.22 +188.136.156.23 +188.136.156.24 +188.136.156.26 +188.136.156.27 +188.136.156.28 +188.136.156.29 +188.136.162.118 +188.136.172.48 +188.136.174.223 +188.136.194.198 +188.136.208.249 +188.208.57.96 +188.208.57.189 +188.208.62.218 +188.209.9.21 +188.209.9.46 +188.209.14.56 +188.209.14.143 +188.209.14.176 +188.209.24.18 +188.209.24.179 +188.209.36.183 +188.209.39.46 +188.209.41.118 +188.209.43.141 +188.209.44.22 +188.209.44.25 +188.209.64.53 +188.209.64.97 +188.209.65.5 +188.209.65.16 +188.209.66.78 +188.209.70.28 +188.209.71.132 +188.209.75.85 +188.209.77.106 +188.209.79.35 +188.209.79.214 +188.210.80.12 +188.210.80.133 +188.210.82.71 +188.210.83.33 +188.210.85.91 +188.210.85.158 +188.210.85.198 +188.210.85.235 +188.210.86.7 +188.210.86.167 +188.210.87.171 +188.210.87.228 +188.210.97.73 +188.210.102.225 +188.210.103.74 +188.210.104.175 +188.210.110.19 +188.210.112.48 +188.210.112.62 +188.210.112.148 +188.210.113.85 +188.210.118.27 +188.210.120.214 +188.210.121.198 +188.210.123.34 +188.210.153.243 +188.210.160.92 +188.210.173.224 +188.211.32.154 +188.211.36.157 +188.211.48.173 +188.211.53.136 +188.211.53.150 +188.211.57.184 +188.211.59.63 +188.211.61.93 +188.211.61.110 +188.211.61.148 +188.211.62.19 +188.211.63.80 +188.211.66.239 +188.211.69.229 +188.211.74.122 +188.211.74.173 +188.211.74.230 +188.211.77.214 +188.211.78.196 +188.211.80.11 +188.211.81.41 +188.211.81.72 +188.211.82.229 +188.211.84.82 +188.211.84.110 +188.211.86.57 +188.211.86.140 +188.211.86.142 +188.211.87.47 +188.211.92.127 +188.211.95.111 +188.211.100.21 +188.211.100.255 +188.211.104.251 +188.211.106.164 +188.211.108.44 +188.211.109.83 +188.211.111.253 +188.211.114.45 +188.211.118.152 +188.211.119.147 +188.211.121.217 +188.211.123.8 +188.211.123.252 +188.211.124.104 +188.211.124.169 +188.211.124.189 +188.211.125.201 +188.211.127.59 +188.212.73.15 +188.212.81.13 +188.212.82.127 +188.212.85.27 +188.212.90.17 +188.212.144.128 +188.212.144.224 +188.212.145.97 +188.212.145.143 +188.212.148.5 +188.212.149.44 +188.212.245.103 +188.213.147.209 +188.213.148.89 +188.213.150.39 +188.213.155.38 +188.213.157.159 +188.213.158.155 +188.213.159.229 +188.213.177.216 +188.213.181.60 +188.213.183.209 +188.213.185.56 +188.213.188.237 +188.213.209.185 +188.214.187.38 +188.240.212.0 +188.240.212.1 +188.240.212.2 +188.240.212.3 +188.240.212.48 +188.240.212.49 +188.240.212.50 +188.240.212.51 +188.240.212.52 +188.240.212.53 +188.240.212.54 +188.240.212.55 +188.240.254.107 +192.168.100.2 +192.168.114.38 +192.168.160.66 +192.168.230.180 +193.24.120.212 +193.56.61.180 +193.134.100.5 +193.134.100.6 +193.134.100.7 +193.134.100.8 +193.134.100.9 +193.134.100.10 +193.134.100.11 +193.134.100.26 +193.134.100.27 +193.134.100.116 +193.134.100.117 +193.134.100.118 +193.151.157.201 +193.200.148.28 +193.200.148.79 +193.228.91.116 +193.228.168.209 +193.228.169.115 +193.228.169.116 +193.228.169.165 +194.9.56.64 +194.9.57.12 +194.9.57.139 +194.33.105.89 +194.36.174.129 +194.60.231.237 +194.147.164.139 +194.147.165.188 +194.150.68.122 +194.150.70.218 +194.150.70.219 +194.150.70.221 +194.150.71.157 +194.225.101.4 +194.225.101.8 +194.225.101.10 +194.225.101.23 +194.225.101.25 +194.225.101.28 +194.225.101.29 +194.225.101.33 +194.225.136.233 +194.225.149.241 +195.8.114.144 +195.88.189.60 +195.146.59.174 +195.146.59.175 +195.181.1.32 +195.181.3.10 +195.181.11.166 +195.181.11.227 +195.181.13.168 +195.181.26.210 +195.181.27.26 +195.181.27.67 +195.181.30.106 +195.181.30.206 +195.181.37.54 +195.181.39.210 +195.181.40.4 +195.181.40.5 +195.181.58.162 +195.181.86.242 +195.181.114.229 +212.16.64.129 +212.16.86.176 +212.33.210.119 +212.33.218.42 +212.80.19.122 +212.80.19.137 +212.86.73.128 +212.86.75.15 +212.86.94.173 +212.120.194.186 +212.120.196.162 +212.120.198.180 +212.120.199.78 +212.120.200.252 +212.120.215.52 +212.120.216.120 +212.120.220.62 +212.120.222.90 +212.120.223.134 +213.109.199.99 +213.109.248.89 +213.109.248.198 +213.109.250.70 +213.109.251.121 +213.109.251.128 +213.176.122.52 +213.176.122.93 +213.195.56.65 +213.207.204.105 +213.207.204.106 +217.11.27.141 +217.11.27.142 +217.11.29.254 +217.24.144.199 +217.24.148.89 +217.24.152.78 +217.24.155.243 +217.24.157.45 +217.60.241.77 +217.66.213.130 +217.146.222.230 +217.170.246.241 +217.170.251.124 +217.170.254.224 +217.170.254.225 +217.170.254.239 +217.170.255.2 +217.172.104.107 +217.172.104.155 +217.172.105.44 +217.172.105.112 +217.172.107.99 +217.172.110.58 +217.172.124.7 +217.218.33.138 +217.218.33.139 +217.218.44.62 +217.218.56.114 +217.218.60.89 +217.218.60.95 +217.218.65.22 +217.218.68.93 +217.218.72.159 +217.218.77.64 +217.218.87.32 +217.218.88.43 +217.218.118.210 +217.218.121.141 +217.218.121.183 +217.218.125.178 +217.218.126.239 +217.218.129.118 +217.218.137.200 +217.218.138.96 +217.218.164.81 +217.218.194.163 +217.218.195.27 +217.218.219.157 +217.218.223.149 +217.218.224.38 +217.218.224.212 +217.218.229.234 +217.218.229.236 +217.218.231.95 +217.218.235.107 +217.218.242.10 +217.218.248.213 +217.218.253.81 +217.219.27.59 +217.219.31.73 +217.219.34.42 +217.219.34.49 +217.219.35.99 +217.219.39.119 +217.219.45.4 +217.219.45.18 +217.219.45.19 +217.219.66.192 +217.219.66.194 +217.219.66.195 +217.219.66.196 +217.219.66.231 +217.219.66.233 +217.219.66.234 +217.219.66.235 +217.219.66.236 +217.219.66.237 +217.219.66.238 +217.219.66.239 +217.219.71.186 +217.219.71.190 +217.219.72.11 +217.219.75.225 +217.219.81.66 +217.219.82.195 +217.219.113.141 +217.219.131.139 +217.219.146.65 +217.219.146.71 +217.219.146.72 +217.219.146.74 +217.219.146.117 +217.219.156.146 +217.219.163.2 +217.219.163.12 +217.219.163.20 +217.219.163.22 +217.219.163.29 +217.219.163.58 +217.219.163.151 +217.219.163.162 +217.219.163.173 +217.219.173.237 +217.219.199.51 +217.219.213.28 +217.219.213.125 +217.219.221.147 +217.219.222.194 +217.219.224.130 +217.219.236.215 +217.219.237.17 +217.219.238.116 +217.219.239.69 +217.219.242.142 +217.219.242.195 +217.219.245.124 +217.219.245.171 \ No newline at end of file diff --git a/cmd/internal/app_store_connect/main.go b/cmd/internal/app_store_connect/main.go new file mode 100644 index 0000000000..d415abd695 --- /dev/null +++ b/cmd/internal/app_store_connect/main.go @@ -0,0 +1,452 @@ +package main + +import ( + "context" + "net/http" + "os" + "strconv" + "strings" + "time" + + "github.com/sagernet/asc-go/asc" + "github.com/sagernet/sing-box/cmd/internal/build_shared" + "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" +) + +func main() { + ctx := context.Background() + switch os.Args[1] { + case "next_macos_project_version": + err := fetchMacOSVersion(ctx) + if err != nil { + log.Fatal(err) + } + case "publish_testflight": + err := publishTestflight(ctx) + if err != nil { + log.Fatal(err) + } + case "cancel_app_store": + err := cancelAppStore(ctx, os.Args[2]) + if err != nil { + log.Fatal(err) + } + case "prepare_app_store": + err := prepareAppStore(ctx) + if err != nil { + log.Fatal(err) + } + case "publish_app_store": + err := publishAppStore(ctx) + if err != nil { + log.Fatal(err) + } + default: + log.Fatal("unknown action: ", os.Args[1]) + } +} + +const ( + appID = "6673731168" + groupID = "5c5f3b78-b7a0-40c0-bcad-e6ef87bbefda" +) + +func createClient(expireDuration time.Duration) *asc.Client { + privateKey, err := os.ReadFile(os.Getenv("ASC_KEY_PATH")) + if err != nil { + log.Fatal(err) + } + tokenConfig, err := asc.NewTokenConfig(os.Getenv("ASC_KEY_ID"), os.Getenv("ASC_KEY_ISSUER_ID"), expireDuration, privateKey) + if err != nil { + log.Fatal(err) + } + return asc.NewClient(tokenConfig.Client()) +} + +func fetchMacOSVersion(ctx context.Context) error { + client := createClient(time.Minute) + versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{ + FilterPlatform: []string{"MAC_OS"}, + }) + if err != nil { + return err + } + var versionID string +findVersion: + for _, version := range versions.Data { + switch *version.Attributes.AppStoreState { + case asc.AppStoreVersionStateReadyForSale, + asc.AppStoreVersionStatePendingDeveloperRelease: + versionID = version.ID + break findVersion + } + } + if versionID == "" { + return E.New("no version found") + } + latestBuild, _, err := client.Builds.GetBuildForAppStoreVersion(ctx, versionID, &asc.GetBuildForAppStoreVersionQuery{}) + if err != nil { + return err + } + versionInt, err := strconv.Atoi(*latestBuild.Data.Attributes.Version) + if err != nil { + return E.Cause(err, "parse version code") + } + os.Stdout.WriteString(F.ToString(versionInt+1, "\n")) + return nil +} + +func publishTestflight(ctx context.Context) error { + if len(os.Args) < 3 { + return E.New("platform required: ios, macos, or tvos") + } + var platform asc.Platform + switch os.Args[2] { + case "ios": + platform = asc.PlatformIOS + case "macos": + platform = asc.PlatformMACOS + case "tvos": + platform = asc.PlatformTVOS + default: + return E.New("unknown platform: ", os.Args[2]) + } + + tagVersion, err := build_shared.ReadTagVersion() + if err != nil { + return err + } + tag := tagVersion.VersionString() + + releaseNotes := F.ToString("sing-box ", tagVersion.String()) + if len(os.Args) >= 4 { + releaseNotes = strings.Join(os.Args[3:], " ") + } + + client := createClient(20 * time.Minute) + + log.Info(tag, " list build IDs") + buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil) + if err != nil { + return err + } + buildIDs := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string { + return it.ID + }) + + waitingForProcess := false + log.Info(string(platform), " list builds") + for { + builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{ + FilterApp: []string{appID}, + FilterPreReleaseVersionPlatform: []string{string(platform)}, + }) + if err != nil { + return err + } + build := builds.Data[0] + log.Info(string(platform), " ", tag, " found build: ", build.ID, " (", *build.Attributes.Version, ")") + if !waitingForProcess && (common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute) { + log.Info(string(platform), " ", tag, " waiting for process") + time.Sleep(15 * time.Second) + continue + } + if *build.Attributes.ProcessingState != "VALID" { + waitingForProcess = true + log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState) + time.Sleep(15 * time.Second) + continue + } + log.Info(string(platform), " ", tag, " list localizations") + localizations, _, err := client.TestFlight.ListBetaBuildLocalizationsForBuild(ctx, build.ID, nil) + if err != nil { + return err + } + localization := common.Find(localizations.Data, func(it asc.BetaBuildLocalization) bool { + return *it.Attributes.Locale == "en-US" + }) + if localization.ID == "" { + log.Fatal(string(platform), " ", tag, " no en-US localization found") + } + if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" { + log.Info(string(platform), " ", tag, " update localization") + _, _, err = client.TestFlight.UpdateBetaBuildLocalization(ctx, localization.ID, common.Ptr(releaseNotes)) + if err != nil { + return err + } + } + log.Info(string(platform), " ", tag, " publish") + response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID}) + if response != nil && (response.StatusCode == http.StatusUnprocessableEntity || response.StatusCode == http.StatusNotFound) { + log.Info("waiting for process") + time.Sleep(15 * time.Second) + continue + } else if err != nil { + return err + } + log.Info(string(platform), " ", tag, " list submissions") + betaSubmissions, _, err := client.TestFlight.ListBetaAppReviewSubmissions(ctx, &asc.ListBetaAppReviewSubmissionsQuery{ + FilterBuild: []string{build.ID}, + }) + if err != nil { + return err + } + if len(betaSubmissions.Data) == 0 { + log.Info(string(platform), " ", tag, " create submission") + _, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID) + if err != nil { + if strings.Contains(err.Error(), "ANOTHER_BUILD_IN_REVIEW") { + log.Error(err) + break + } + return err + } + } + break + } + return nil +} + +func cancelAppStore(ctx context.Context, platform string) error { + switch platform { + case "ios": + platform = string(asc.PlatformIOS) + case "macos": + platform = string(asc.PlatformMACOS) + case "tvos": + platform = string(asc.PlatformTVOS) + } + tag, err := build_shared.ReadTag() + if err != nil { + return err + } + client := createClient(time.Minute) + for { + log.Info(platform, " list versions") + versions, response, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{ + FilterPlatform: []string{string(platform)}, + }) + if isRetryable(response) { + continue + } else if err != nil { + return err + } + version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool { + return *it.Attributes.VersionString == tag + }) + if version.ID == "" { + return nil + } + log.Info(platform, " ", tag, " get submission") + submission, response, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil) + if response != nil && response.StatusCode == http.StatusNotFound { + return nil + } + if isRetryable(response) { + continue + } else if err != nil { + return err + } + log.Info(platform, " ", tag, " delete submission") + _, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID) + if err != nil { + return err + } + return nil + } +} + +func prepareAppStore(ctx context.Context) error { + tag, err := build_shared.ReadTag() + if err != nil { + return err + } + client := createClient(time.Minute) + for _, platform := range []asc.Platform{ + asc.PlatformIOS, + asc.PlatformMACOS, + asc.PlatformTVOS, + } { + log.Info(string(platform), " list versions") + versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{ + FilterPlatform: []string{string(platform)}, + }) + if err != nil { + return err + } + version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool { + return *it.Attributes.VersionString == tag + }) + log.Info(string(platform), " ", tag, " list builds") + builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{ + FilterApp: []string{appID}, + FilterPreReleaseVersionPlatform: []string{string(platform)}, + }) + if err != nil { + return err + } + if len(builds.Data) == 0 { + log.Fatal(platform, " ", tag, " no build found") + } + buildID := common.Ptr(builds.Data[0].ID) + if version.ID == "" { + log.Info(string(platform), " ", tag, " create version") + newVersion, _, err := client.Apps.CreateAppStoreVersion(ctx, asc.AppStoreVersionCreateRequestAttributes{ + Platform: platform, + VersionString: tag, + }, appID, buildID) + if err != nil { + return err + } + version = newVersion.Data + + } else { + log.Info(string(platform), " ", tag, " check build") + currentBuild, response, err := client.Apps.GetBuildIDForAppStoreVersion(ctx, version.ID) + if err != nil { + return err + } + if response.StatusCode != http.StatusOK || currentBuild.Data.ID != *buildID { + switch *version.Attributes.AppStoreState { + case asc.AppStoreVersionStatePrepareForSubmission, + asc.AppStoreVersionStateRejected, + asc.AppStoreVersionStateDeveloperRejected: + case asc.AppStoreVersionStateWaitingForReview, + asc.AppStoreVersionStateInReview, + asc.AppStoreVersionStatePendingDeveloperRelease: + submission, _, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil) + if err != nil { + return err + } + if submission != nil { + log.Info(string(platform), " ", tag, " delete submission") + _, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID) + if err != nil { + return err + } + time.Sleep(5 * time.Second) + } + default: + log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState)) + } + log.Info(string(platform), " ", tag, " update build") + response, err = client.Apps.UpdateBuildForAppStoreVersion(ctx, version.ID, buildID) + if err != nil { + return err + } + if response.StatusCode != http.StatusNoContent { + response.Write(os.Stderr) + log.Fatal(string(platform), " ", tag, " unexpected response: ", response.Status) + } + } else { + switch *version.Attributes.AppStoreState { + case asc.AppStoreVersionStatePrepareForSubmission, + asc.AppStoreVersionStateRejected, + asc.AppStoreVersionStateDeveloperRejected: + case asc.AppStoreVersionStateWaitingForReview, + asc.AppStoreVersionStateInReview, + asc.AppStoreVersionStatePendingDeveloperRelease: + continue + default: + log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState)) + } + } + } + log.Info(string(platform), " ", tag, " list localization") + localizations, _, err := client.Apps.ListLocalizationsForAppStoreVersion(ctx, version.ID, nil) + if err != nil { + return err + } + localization := common.Find(localizations.Data, func(it asc.AppStoreVersionLocalization) bool { + return *it.Attributes.Locale == "en-US" + }) + if localization.ID == "" { + log.Info(string(platform), " ", tag, " no en-US localization found") + } + if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" { + log.Info(string(platform), " ", tag, " update localization") + _, _, err = client.Apps.UpdateAppStoreVersionLocalization(ctx, localization.ID, &asc.AppStoreVersionLocalizationUpdateRequestAttributes{ + PromotionalText: common.Ptr("Yet another distribution for sing-box, the universal proxy platform."), + WhatsNew: common.Ptr(F.ToString("sing-box ", tag, ": Fixes and improvements.")), + }) + if err != nil { + return err + } + } + log.Info(string(platform), " ", tag, " create submission") + fixSubmit: + for { + _, response, err := client.Submission.CreateSubmission(ctx, version.ID) + if err != nil { + switch response.StatusCode { + case http.StatusInternalServerError: + continue + default: + return err + } + } + switch response.StatusCode { + case http.StatusCreated: + break fixSubmit + default: + return err + } + } + } + return nil +} + +func publishAppStore(ctx context.Context) error { + tag, err := build_shared.ReadTag() + if err != nil { + return err + } + client := createClient(time.Minute) + for _, platform := range []asc.Platform{ + asc.PlatformIOS, + asc.PlatformMACOS, + asc.PlatformTVOS, + } { + log.Info(string(platform), " list versions") + versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{ + FilterPlatform: []string{string(platform)}, + }) + if err != nil { + return err + } + version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool { + return *it.Attributes.VersionString == tag + }) + switch *version.Attributes.AppStoreState { + case asc.AppStoreVersionStatePrepareForSubmission, asc.AppStoreVersionStateDeveloperRejected: + log.Fatal(string(platform), " ", tag, " not submitted") + case asc.AppStoreVersionStateWaitingForReview, + asc.AppStoreVersionStateInReview: + log.Warn(string(platform), " ", tag, " waiting for review") + continue + case asc.AppStoreVersionStatePendingDeveloperRelease: + default: + log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState)) + } + _, _, err = client.Publishing.CreatePhasedRelease(ctx, common.Ptr(asc.PhasedReleaseStateComplete), version.ID) + if err != nil { + return err + } + } + return nil +} + +func isRetryable(response *asc.Response) bool { + if response == nil { + return false + } + switch response.StatusCode { + case http.StatusInternalServerError, http.StatusUnprocessableEntity: + return true + default: + return false + } +} diff --git a/cmd/internal/build_libbox/main.go b/cmd/internal/build_libbox/main.go index ae0fe34a6b..0f914499e0 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -5,22 +5,29 @@ import ( "os" "os/exec" "path/filepath" + "strconv" "strings" _ "github.com/sagernet/gomobile" "github.com/sagernet/sing-box/cmd/internal/build_shared" "github.com/sagernet/sing-box/log" + E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/rw" + "github.com/sagernet/sing/common/shell" ) var ( debugEnabled bool target string + platform string + // withTailscale bool ) func init() { flag.BoolVar(&debugEnabled, "debug", false, "enable debug") flag.StringVar(&target, "target", "android", "target platform") + flag.StringVar(&platform, "platform", "", "specify platform") + // flag.BoolVar(&withTailscale, "with-tailscale", false, "build tailscale for iOS and tvOS") } func main() { @@ -31,8 +38,8 @@ func main() { switch target { case "android": buildAndroid() - case "ios": - buildiOS() + case "apple": + buildApple() } } @@ -40,7 +47,9 @@ var ( sharedFlags []string debugFlags []string sharedTags []string - iosTags []string + darwinTags []string + // memcTags []string + notMemcTags []string debugTags []string ) @@ -51,36 +60,82 @@ func init() { if err != nil { currentTag = "unknown" } - sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=") - debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag) - - sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_ech", "with_utls", "with_clash_api") - iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack") + sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0") + debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0") + + sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_naive_outbound", "with_clash_api", "badlinkname", "tfogo_checklinkname0") + darwinTags = append(darwinTags, "with_dhcp", "grpcnotrace") + // memcTags = append(memcTags, "with_tailscale") + sharedTags = append(sharedTags, "with_tailscale", "ts_omit_logtail", "ts_omit_ssh", "ts_omit_drive", "ts_omit_taildrop", "ts_omit_webclient", "ts_omit_doctor", "ts_omit_capture", "ts_omit_kube", "ts_omit_aws", "ts_omit_synology", "ts_omit_bird") + notMemcTags = append(notMemcTags, "with_low_memory") debugTags = append(debugTags, "debug") } -func buildAndroid() { - build_shared.FindSDK() +type AndroidBuildConfig struct { + AndroidAPI int + OutputName string + Tags []string +} + +func filterTags(tags []string, exclude ...string) []string { + excludeMap := make(map[string]bool) + for _, tag := range exclude { + excludeMap[tag] = true + } + var result []string + for _, tag := range tags { + if !excludeMap[tag] { + result = append(result, tag) + } + } + return result +} + +func checkJavaVersion() { + var javaPath string + javaHome := os.Getenv("JAVA_HOME") + if javaHome == "" { + javaPath = "java" + } else { + javaPath = filepath.Join(javaHome, "bin", "java") + } + + javaVersion, err := shell.Exec(javaPath, "--version").ReadOutput() + if err != nil { + log.Fatal(E.Cause(err, "check java version")) + } + if !strings.Contains(javaVersion, "openjdk 17") { + log.Fatal("java version should be openjdk 17") + } +} + +func getAndroidBindTarget() string { + if platform != "" { + return platform + } else if debugEnabled { + return "android/arm64" + } + return "android" +} +func buildAndroidVariant(config AndroidBuildConfig, bindTarget string) { args := []string{ "bind", "-v", - "-androidapi", "21", + "-o", config.OutputName, + "-target", bindTarget, + "-androidapi", strconv.Itoa(config.AndroidAPI), "-javapkg=io.nekohasekai", "-libname=box", } + if !debugEnabled { args = append(args, sharedFlags...) } else { args = append(args, debugFlags...) } - args = append(args, "-tags") - if !debugEnabled { - args = append(args, strings.Join(sharedTags, ",")) - } else { - args = append(args, strings.Join(append(sharedTags, debugTags...), ",")) - } + args = append(args, "-tags", strings.Join(config.Tags, ",")) args = append(args, "./experimental/libbox") command := exec.Command(build_shared.GoBinPath+"/gomobile", args...) @@ -91,38 +146,87 @@ func buildAndroid() { log.Fatal(err) } - const name = "libbox.aar" copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs") - if rw.FileExists(copyPath) { + if rw.IsDir(copyPath) { copyPath, _ = filepath.Abs(copyPath) - err = rw.CopyFile(name, filepath.Join(copyPath, name)) + err = rw.CopyFile(config.OutputName, filepath.Join(copyPath, config.OutputName)) if err != nil { log.Fatal(err) } - log.Info("copied to ", copyPath) + log.Info("copied ", config.OutputName, " to ", copyPath) } } -func buildiOS() { +func buildAndroid() { + build_shared.FindSDK() + checkJavaVersion() + + bindTarget := getAndroidBindTarget() + + // Build main variant (SDK 23) + mainTags := append([]string{}, sharedTags...) + // mainTags = append(mainTags, memcTags...) + if debugEnabled { + mainTags = append(mainTags, debugTags...) + } + buildAndroidVariant(AndroidBuildConfig{ + AndroidAPI: 23, + OutputName: "libbox.aar", + Tags: mainTags, + }, bindTarget) + + // Build legacy variant (SDK 21, no naive outbound) + legacyTags := filterTags(sharedTags, "with_naive_outbound") + // legacyTags = append(legacyTags, memcTags...) + if debugEnabled { + legacyTags = append(legacyTags, debugTags...) + } + buildAndroidVariant(AndroidBuildConfig{ + AndroidAPI: 21, + OutputName: "libbox-legacy.aar", + Tags: legacyTags, + }, bindTarget) +} + +func buildApple() { + var bindTarget string + if platform != "" { + bindTarget = platform + } else if debugEnabled { + bindTarget = "ios" + } else { + bindTarget = "ios,iossimulator,tvos,tvossimulator,macos" + } + args := []string{ "bind", "-v", - "-target", "ios,iossimulator,tvos,tvossimulator,macos", + "-target", bindTarget, "-libname=box", + "-tags-not-macos=with_low_memory", + "-iosversion=15.0", + "-macosversion=13.0", + "-tvosversion=17.0", } + //if !withTailscale { + // args = append(args, "-tags-macos="+strings.Join(memcTags, ",")) + //} + if !debugEnabled { args = append(args, sharedFlags...) } else { args = append(args, debugFlags...) } - tags := append(sharedTags, iosTags...) - args = append(args, "-tags") - if !debugEnabled { - args = append(args, strings.Join(tags, ",")) - } else { - args = append(args, strings.Join(append(tags, debugTags...), ",")) + tags := append(sharedTags, darwinTags...) + //if withTailscale { + // tags = append(tags, memcTags...) + //} + if debugEnabled { + tags = append(tags, debugTags...) } + + args = append(args, "-tags", strings.Join(tags, ",")) args = append(args, "./experimental/libbox") command := exec.Command(build_shared.GoBinPath+"/gomobile", args...) @@ -134,7 +238,7 @@ func buildiOS() { } copyPath := filepath.Join("..", "sing-box-for-apple") - if rw.FileExists(copyPath) { + if rw.IsDir(copyPath) { targetDir := filepath.Join(copyPath, "Libbox.xcframework") targetDir, _ = filepath.Abs(targetDir) os.RemoveAll(targetDir) diff --git a/cmd/internal/build_shared/sdk.go b/cmd/internal/build_shared/sdk.go index ce7f0c86d0..5061c32177 100644 --- a/cmd/internal/build_shared/sdk.go +++ b/cmd/internal/build_shared/sdk.go @@ -11,9 +11,7 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/rw" - "github.com/sagernet/sing/common/shell" ) var ( @@ -30,7 +28,7 @@ func FindSDK() { } for _, path := range searchPath { path = os.ExpandEnv(path) - if rw.FileExists(filepath.Join(path, "licenses", "android-sdk-license")) { + if rw.IsFile(filepath.Join(path, "licenses", "android-sdk-license")) { androidSDKPath = path break } @@ -42,14 +40,6 @@ func FindSDK() { log.Fatal("android NDK not found") } - javaVersion, err := shell.Exec("java", "--version").ReadOutput() - if err != nil { - log.Fatal(E.Cause(err, "check java version")) - } - if !strings.Contains(javaVersion, "openjdk 17") { - log.Fatal("java version should be openjdk 17") - } - os.Setenv("ANDROID_HOME", androidSDKPath) os.Setenv("ANDROID_SDK_HOME", androidSDKPath) os.Setenv("ANDROID_NDK_HOME", androidNDKPath) @@ -58,12 +48,16 @@ func FindSDK() { } func findNDK() bool { - const fixedVersion = "26.2.11394342" + const fixedVersion = "28.0.13004108" const versionFile = "source.properties" - if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.FileExists(filepath.Join(fixedPath, versionFile)) { + if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) { androidNDKPath = fixedPath return true } + if ndkHomeEnv := os.Getenv("ANDROID_NDK_HOME"); rw.IsFile(filepath.Join(ndkHomeEnv, versionFile)) { + androidNDKPath = ndkHomeEnv + return true + } ndkVersions, err := os.ReadDir(filepath.Join(androidSDKPath, "ndk")) if err != nil { return false @@ -86,7 +80,7 @@ func findNDK() bool { }) for _, versionName := range versionNames { currentNDKPath := filepath.Join(androidSDKPath, "ndk", versionName) - if rw.FileExists(filepath.Join(androidSDKPath, versionFile)) { + if rw.IsFile(filepath.Join(currentNDKPath, versionFile)) { androidNDKPath = currentNDKPath log.Warn("reproducibility warning: using NDK version " + versionName + " instead of " + fixedVersion) return true @@ -100,11 +94,11 @@ var GoBinPath string func FindMobile() { goBin := filepath.Join(build.Default.GOPATH, "bin") if runtime.GOOS == "windows" { - if !rw.FileExists(filepath.Join(goBin, "gobind.exe")) { + if !rw.IsFile(filepath.Join(goBin, "gobind.exe")) { log.Fatal("missing gomobile installation") } } else { - if !rw.FileExists(filepath.Join(goBin, "gobind")) { + if !rw.IsFile(filepath.Join(goBin, "gobind")) { log.Fatal("missing gomobile installation") } } diff --git a/cmd/internal/build_shared/tag.go b/cmd/internal/build_shared/tag.go index 608b5877db..15f050907d 100644 --- a/cmd/internal/build_shared/tag.go +++ b/cmd/internal/build_shared/tag.go @@ -20,6 +20,11 @@ func ReadTag() (string, error) { return version.String() + "-" + shortCommit, nil } +func ReadTagVersionRev() (badversion.Version, error) { + currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput()) + return badversion.Parse(currentTagRev[1:]), nil +} + func ReadTagVersion() (badversion.Version, error) { currentTag := common.Must1(shell.Exec("git", "describe", "--tags").ReadOutput()) currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput()) diff --git a/cmd/internal/format_docs/main.go b/cmd/internal/format_docs/main.go new file mode 100644 index 0000000000..061b212102 --- /dev/null +++ b/cmd/internal/format_docs/main.go @@ -0,0 +1,117 @@ +package main + +import ( + "bytes" + "os" + "path/filepath" + "strings" + + "github.com/sagernet/sing-box/log" +) + +func main() { + err := filepath.Walk("docs", func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + if !strings.HasSuffix(path, ".md") { + return nil + } + return processFile(path) + }) + if err != nil { + log.Fatal(err) + } +} + +func processFile(path string) error { + content, err := os.ReadFile(path) + if err != nil { + return err + } + + lines := strings.Split(string(content), "\n") + modified := false + result := make([]string, 0, len(lines)) + + inQuoteBlock := false + materialLines := []int{} // indices of :material- lines in the block + + for _, line := range lines { + // Check for quote block start + if strings.HasPrefix(line, "!!! quote \"") && strings.Contains(line, "sing-box") { + inQuoteBlock = true + materialLines = nil + result = append(result, line) + continue + } + + // Inside a quote block + if inQuoteBlock { + trimmed := strings.TrimPrefix(line, " ") + isMaterialLine := strings.HasPrefix(trimmed, ":material-") + isEmpty := strings.TrimSpace(line) == "" + isIndented := strings.HasPrefix(line, " ") + + if isMaterialLine { + materialLines = append(materialLines, len(result)) + result = append(result, line) + continue + } + + // Block ends when: + // - Empty line AFTER we've seen material lines, OR + // - Non-indented, non-empty line + blockEnds := (isEmpty && len(materialLines) > 0) || (!isEmpty && !isIndented) + if blockEnds { + // Process collected material lines + if len(materialLines) > 0 { + for j, idx := range materialLines { + isLast := j == len(materialLines)-1 + resultLine := strings.TrimRight(result[idx], " ") + if !isLast { + // Add trailing two spaces for non-last lines + resultLine += " " + } + if result[idx] != resultLine { + modified = true + result[idx] = resultLine + } + } + } + inQuoteBlock = false + materialLines = nil + } + } + + result = append(result, line) + } + + // Handle case where file ends while still in a block + if inQuoteBlock && len(materialLines) > 0 { + for j, idx := range materialLines { + isLast := j == len(materialLines)-1 + resultLine := strings.TrimRight(result[idx], " ") + if !isLast { + resultLine += " " + } + if result[idx] != resultLine { + modified = true + result[idx] = resultLine + } + } + } + + if modified { + newContent := strings.Join(result, "\n") + if !bytes.Equal(content, []byte(newContent)) { + log.Info("formatted: ", path) + return os.WriteFile(path, []byte(newContent), 0o644) + } + } + + return nil +} diff --git a/cmd/internal/protogen/main.go b/cmd/internal/protogen/main.go index 4d5023f7b5..1a5d59b06c 100644 --- a/cmd/internal/protogen/main.go +++ b/cmd/internal/protogen/main.go @@ -48,8 +48,8 @@ func GetRuntimeEnv(key string) (string, error) { if readErr != nil { return "", readErr } - envStrings := strings.Split(string(data), "\n") - for _, envItem := range envStrings { + envStrings := strings.SplitSeq(string(data), "\n") + for envItem := range envStrings { envItem = strings.TrimSuffix(envItem, "\r") envKeyValue := strings.Split(envItem, "=") if strings.EqualFold(strings.TrimSpace(envKeyValue[0]), key) { diff --git a/cmd/internal/read_tag/main.go b/cmd/internal/read_tag/main.go index d319f771a0..c4da1de594 100644 --- a/cmd/internal/read_tag/main.go +++ b/cmd/internal/read_tag/main.go @@ -1,21 +1,71 @@ package main import ( + "flag" "os" "github.com/sagernet/sing-box/cmd/internal/build_shared" + "github.com/sagernet/sing-box/common/badversion" "github.com/sagernet/sing-box/log" ) +var ( + flagRunInCI bool + flagRunNightly bool +) + +func init() { + flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI") + flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly") +} + func main() { - currentTag, err := build_shared.ReadTag() - if err != nil { - log.Error(err) - _, err = os.Stdout.WriteString("unknown\n") + flag.Parse() + var ( + versionStr string + err error + ) + if flagRunNightly { + var version badversion.Version + version, err = build_shared.ReadTagVersion() + if err == nil { + versionStr = version.String() + } + } else { + versionStr, err = build_shared.ReadTag() + } + if flagRunInCI { + if err != nil { + log.Fatal(err) + } + err = setGitHubEnv("version", versionStr) + if err != nil { + log.Fatal(err) + } } else { - _, err = os.Stdout.WriteString(currentTag + "\n") + if err != nil { + log.Error(err) + os.Stdout.WriteString("unknown\n") + } else { + os.Stdout.WriteString(versionStr + "\n") + } + } +} + +func setGitHubEnv(name string, value string) error { + outputFile, err := os.OpenFile(os.Getenv("GITHUB_ENV"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644) + if err != nil { + return err + } + _, err = outputFile.WriteString(name + "=" + value + "\n") + if err != nil { + outputFile.Close() + return err } + err = outputFile.Close() if err != nil { - log.Error(err) + return err } + os.Stderr.WriteString(name + "=" + value + "\n") + return nil } diff --git a/cmd/internal/tun_bench/main.go b/cmd/internal/tun_bench/main.go new file mode 100644 index 0000000000..e62841dc9e --- /dev/null +++ b/cmd/internal/tun_bench/main.go @@ -0,0 +1,284 @@ +package main + +import ( + "context" + "fmt" + "io" + "net/netip" + "os" + "os/exec" + "strings" + "syscall" + "time" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/include" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/shell" +) + +var iperf3Path string + +func main() { + err := main0() + if err != nil { + log.Fatal(err) + } +} + +func main0() error { + err := shell.Exec("sudo", "ls").Run() + if err != nil { + return err + } + results, err := runTests() + if err != nil { + return err + } + encoder := json.NewEncoder(os.Stdout) + encoder.SetIndent("", " ") + return encoder.Encode(results) +} + +func runTests() ([]TestResult, error) { + boxPaths := []string{ + os.ExpandEnv("$HOME/Downloads/sing-box-1.11.15-darwin-arm64/sing-box"), + //"/Users/sekai/Downloads/sing-box-1.11.15-linux-arm64/sing-box", + "./sing-box", + } + stacks := []string{ + "gvisor", + "system", + } + mtus := []int{ + 1500, + 4064, + // 16384, + // 32768, + // 49152, + 65535, + } + flagList := [][]string{ + {}, + } + var results []TestResult + for _, boxPath := range boxPaths { + for _, stack := range stacks { + for _, mtu := range mtus { + if strings.HasPrefix(boxPath, ".") { + for _, flags := range flagList { + result, err := testOnce(boxPath, stack, mtu, false, flags) + if err != nil { + return nil, err + } + results = append(results, *result) + } + } else { + result, err := testOnce(boxPath, stack, mtu, false, nil) + if err != nil { + return nil, err + } + results = append(results, *result) + } + } + } + } + return results, nil +} + +type TestResult struct { + BoxPath string `json:"box_path"` + Stack string `json:"stack"` + MTU int `json:"mtu"` + Flags []string `json:"flags"` + MultiThread bool `json:"multi_thread"` + UploadSpeed string `json:"upload_speed"` + DownloadSpeed string `json:"download_speed"` +} + +func testOnce(boxPath string, stackName string, mtu int, multiThread bool, flags []string) (result *TestResult, err error) { + testAddress := netip.MustParseAddr("1.1.1.1") + testConfig := option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeTun, + Options: &option.TunInboundOptions{ + Address: []netip.Prefix{netip.MustParsePrefix("172.18.0.1/30")}, + AutoRoute: true, + MTU: uint32(mtu), + Stack: stackName, + RouteAddress: []netip.Prefix{netip.PrefixFrom(testAddress, testAddress.BitLen())}, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + IPCIDR: []string{testAddress.String()}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRouteOptions, + RouteOptionsOptions: option.RouteOptionsActionOptions{ + OverrideAddress: "127.0.0.1", + }, + }, + }, + }, + }, + AutoDetectInterface: true, + }, + } + ctx := include.Context(context.Background()) + tempConfig, err := os.CreateTemp("", "tun-bench-*.json") + if err != nil { + return + } + defer os.Remove(tempConfig.Name()) + encoder := json.NewEncoderContext(ctx, tempConfig) + encoder.SetIndent("", " ") + err = encoder.Encode(testConfig) + if err != nil { + return nil, E.Cause(err, "encode test config") + } + tempConfig.Close() + var sudoArgs []string + if len(flags) > 0 { + sudoArgs = append(sudoArgs, "env") + sudoArgs = append(sudoArgs, flags...) + } + sudoArgs = append(sudoArgs, boxPath, "run", "-c", tempConfig.Name()) + boxProcess := shell.Exec("sudo", sudoArgs...) + boxProcess.Stdout = &stderrWriter{} + boxProcess.Stderr = io.Discard + err = boxProcess.Start() + if err != nil { + return + } + + if C.IsDarwin { + iperf3Path, err = exec.LookPath("iperf3-darwin") + } else { + iperf3Path, err = exec.LookPath("iperf3") + } + if err != nil { + return + } + serverProcess := shell.Exec(iperf3Path, "-s") + serverProcess.Stdout = io.Discard + serverProcess.Stderr = io.Discard + err = serverProcess.Start() + if err != nil { + return nil, E.Cause(err, "start iperf3 server") + } + + time.Sleep(time.Second) + + args := []string{"-c", testAddress.String()} + if multiThread { + args = append(args, "-P", "10") + } + + uploadProcess := shell.Exec(iperf3Path, args...) + output, err := uploadProcess.Read() + if err != nil { + boxProcess.Process.Signal(syscall.SIGKILL) + serverProcess.Process.Signal(syscall.SIGKILL) + println(output) + return + } + + uploadResult := common.SubstringBeforeLast(output, "iperf Done.") + uploadResult = common.SubstringBeforeLast(uploadResult, "sender") + uploadResult = common.SubstringBeforeLast(uploadResult, "bits/sec") + uploadResult = common.SubstringAfterLast(uploadResult, "Bytes") + uploadResult = strings.ReplaceAll(uploadResult, " ", "") + + result = &TestResult{ + BoxPath: boxPath, + Stack: stackName, + MTU: mtu, + Flags: flags, + MultiThread: multiThread, + UploadSpeed: uploadResult, + } + + downloadProcess := shell.Exec(iperf3Path, append(args, "-R")...) + output, err = downloadProcess.Read() + if err != nil { + boxProcess.Process.Signal(syscall.SIGKILL) + serverProcess.Process.Signal(syscall.SIGKILL) + println(output) + return + } + + downloadResult := common.SubstringBeforeLast(output, "iperf Done.") + downloadResult = common.SubstringBeforeLast(downloadResult, "receiver") + downloadResult = common.SubstringBeforeLast(downloadResult, "bits/sec") + downloadResult = common.SubstringAfterLast(downloadResult, "Bytes") + downloadResult = strings.ReplaceAll(downloadResult, " ", "") + + result.DownloadSpeed = downloadResult + + printArgs := []any{boxPath, stackName, mtu, "upload", uploadResult, "download", downloadResult} + if len(flags) > 0 { + printArgs = append(printArgs, "flags", strings.Join(flags, " ")) + } + if multiThread { + printArgs = append(printArgs, "(-P 10)") + } + fmt.Println(printArgs...) + err = boxProcess.Process.Signal(syscall.SIGTERM) + if err != nil { + return + } + + err = serverProcess.Process.Signal(syscall.SIGTERM) + if err != nil { + return + } + + boxDone := make(chan struct{}) + go func() { + boxProcess.Cmd.Wait() + close(boxDone) + }() + + serverDone := make(chan struct{}) + go func() { + serverProcess.Process.Wait() + close(serverDone) + }() + + select { + case <-boxDone: + case <-time.After(2 * time.Second): + boxProcess.Process.Kill() + case <-time.After(4 * time.Second): + println("box process did not close!") + os.Exit(1) + } + + select { + case <-serverDone: + case <-time.After(2 * time.Second): + serverProcess.Process.Kill() + case <-time.After(4 * time.Second): + println("server process did not close!") + os.Exit(1) + } + + return +} + +type stderrWriter struct{} + +func (w *stderrWriter) Write(p []byte) (n int, err error) { + return os.Stderr.Write(p) +} diff --git a/cmd/internal/update_android_version/main.go b/cmd/internal/update_android_version/main.go index 040ba5d2d3..2278eeac5d 100644 --- a/cmd/internal/update_android_version/main.go +++ b/cmd/internal/update_android_version/main.go @@ -1,6 +1,7 @@ package main import ( + "flag" "os" "path/filepath" "runtime" @@ -12,16 +13,33 @@ import ( "github.com/sagernet/sing/common" ) +var ( + flagRunInCI bool + flagRunNightly bool +) + +func init() { + flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI") + flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly") +} + func main() { - newVersion := common.Must1(build_shared.ReadTagVersion()) - androidPath, err := filepath.Abs("../sing-box-for-android") + flag.Parse() + newVersion := common.Must1(build_shared.ReadTag()) + var androidPath string + if flagRunInCI { + androidPath = "clients/android" + } else { + androidPath = "../sing-box-for-android" + } + androidPath, err := filepath.Abs(androidPath) if err != nil { log.Fatal(err) } common.Must(os.Chdir(androidPath)) localProps := common.Must1(os.ReadFile("version.properties")) var propsList [][]string - for _, propLine := range strings.Split(string(localProps), "\n") { + for propLine := range strings.SplitSeq(string(localProps), "\n") { propsList = append(propsList, strings.Split(propLine, "=")) } var ( @@ -31,22 +49,24 @@ func main() { for _, propPair := range propsList { switch propPair[0] { case "VERSION_NAME": - if propPair[1] != newVersion.String() { + if propPair[1] != newVersion { + log.Info("updated version from ", propPair[1], " to ", newVersion) versionUpdated = true - propPair[1] = newVersion.String() - log.Info("updated version to ", newVersion.String()) + propPair[1] = newVersion } case "GO_VERSION": if propPair[1] != runtime.Version() { + log.Info("updated Go version from ", propPair[1], " to ", runtime.Version()) goVersionUpdated = true propPair[1] = runtime.Version() - log.Info("updated Go version to ", runtime.Version()) } } } if !(versionUpdated || goVersionUpdated) { log.Info("version not changed") return + } else if flagRunInCI && !flagRunNightly { + log.Fatal("version changed, commit changes first.") } for _, propPair := range propsList { switch propPair[0] { diff --git a/cmd/internal/update_apple_version/main.go b/cmd/internal/update_apple_version/main.go index 6a20da6125..1b2d0db52b 100644 --- a/cmd/internal/update_apple_version/main.go +++ b/cmd/internal/update_apple_version/main.go @@ -1,6 +1,7 @@ package main import ( + "flag" "os" "path/filepath" "regexp" @@ -13,9 +14,22 @@ import ( "howett.net/plist" ) +var flagRunInCI bool + +func init() { + flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI") +} + func main() { + flag.Parse() newVersion := common.Must1(build_shared.ReadTagVersion()) - applePath, err := filepath.Abs("../sing-box-for-apple") + var applePath string + if flagRunInCI { + applePath = "clients/apple" + } else { + applePath = "../sing-box-for-apple" + } + applePath, err := filepath.Abs(applePath) if err != nil { log.Fatal(err) } @@ -26,8 +40,8 @@ func main() { common.Must(decoder.Decode(&project)) objectsMap := project["objects"].(map[string]any) projectContent := string(common.Must1(os.ReadFile("sing-box.xcodeproj/project.pbxproj"))) - newContent, updated0 := findAndReplace(objectsMap, projectContent, []string{"io.nekohasekai.sfa"}, newVersion.VersionString()) - newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfa.independent", "io.nekohasekai.sfa.system"}, newVersion.String()) + newContent, updated0 := findAndReplace(objectsMap, projectContent, []string{"io.nekohasekai.sfavt"}, newVersion.VersionString()) + newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfavt.standalone", "io.nekohasekai.sfavt.system"}, newVersion.String()) if updated0 || updated1 { log.Info("updated version to ", newVersion.VersionString(), " (", newVersion.String(), ")") } @@ -57,12 +71,12 @@ func findAndReplace(objectsMap map[string]any, projectContent string, bundleIDLi indexEnd := indexStart + strings.Index(projectContent[indexStart:], "}") versionStart := indexStart + strings.Index(projectContent[indexStart:indexEnd], "MARKETING_VERSION = ") + 20 versionEnd := versionStart + strings.Index(projectContent[versionStart:indexEnd], ";") - version := projectContent[versionStart:versionEnd] + version := strings.Trim(projectContent[versionStart:versionEnd], "\"") if version == newVersion { continue } updated = true - projectContent = projectContent[:versionStart] + newVersion + projectContent[versionEnd:] + projectContent = projectContent[:versionStart] + "\"" + newVersion + "\"" + projectContent[versionEnd:] } return projectContent, updated } diff --git a/cmd/internal/update_certificates/main.go b/cmd/internal/update_certificates/main.go new file mode 100644 index 0000000000..d19eb7f1cb --- /dev/null +++ b/cmd/internal/update_certificates/main.go @@ -0,0 +1,148 @@ +package main + +import ( + "encoding/csv" + "io" + "net/http" + "os" + "strings" + + "github.com/sagernet/sing-box/log" + + "golang.org/x/exp/slices" +) + +func main() { + err := updateMozillaIncludedRootCAs() + if err != nil { + log.Error(err) + } + err = updateChromeIncludedRootCAs() + if err != nil { + log.Error(err) + } +} + +func updateMozillaIncludedRootCAs() error { + response, err := http.Get("https://ccadb.my.salesforce-sites.com/mozilla/IncludedCACertificateReportPEMCSV") + if err != nil { + return err + } + defer response.Body.Close() + reader := csv.NewReader(response.Body) + header, err := reader.Read() + if err != nil { + return err + } + geoIndex := slices.Index(header, "Geographic Focus") + nameIndex := slices.Index(header, "Common Name or Certificate Name") + certIndex := slices.Index(header, "PEM Info") + + generated := strings.Builder{} + generated.WriteString(`// Code generated by 'make update_certificates'. DO NOT EDIT. + +package certificate + +func mozillaIncludedPEM() string { + return ` + "`") + for { + record, err := reader.Read() + if err == io.EOF { + break + } else if err != nil { + return err + } + if record[geoIndex] == "China" { + continue + } + cert := strings.Trim(record[certIndex], "'") + generated.WriteString("\n// ") + generated.WriteString(record[nameIndex]) + generated.WriteString("\n") + generated.WriteString(cert) + generated.WriteString("\n") + } + generated.WriteString("`\n}\n") + return os.WriteFile("common/certificate/mozilla.go", []byte(generated.String()), 0o644) +} + +func fetchChinaFingerprints() (map[string]bool, error) { + response, err := http.Get("https://ccadb.my.salesforce-sites.com/ccadb/AllCertificateRecordsCSVFormatv4") + if err != nil { + return nil, err + } + defer response.Body.Close() + reader := csv.NewReader(response.Body) + header, err := reader.Read() + if err != nil { + return nil, err + } + countryIndex := slices.Index(header, "Country") + fingerprintIndex := slices.Index(header, "SHA-256 Fingerprint") + + chinaFingerprints := make(map[string]bool) + for { + record, err := reader.Read() + if err == io.EOF { + break + } else if err != nil { + return nil, err + } + if record[countryIndex] == "China" { + chinaFingerprints[record[fingerprintIndex]] = true + } + } + return chinaFingerprints, nil +} + +func updateChromeIncludedRootCAs() error { + chinaFingerprints, err := fetchChinaFingerprints() + if err != nil { + return err + } + + response, err := http.Get("https://ccadb.my.salesforce-sites.com/ccadb/RootCACertificatesIncludedByRSReportCSV") + if err != nil { + return err + } + defer response.Body.Close() + reader := csv.NewReader(response.Body) + header, err := reader.Read() + if err != nil { + return err + } + subjectIndex := slices.Index(header, "Subject") + statusIndex := slices.Index(header, "Google Chrome Status") + certIndex := slices.Index(header, "X.509 Certificate (PEM)") + fingerprintIndex := slices.Index(header, "SHA-256 Fingerprint") + + generated := strings.Builder{} + generated.WriteString(`// Code generated by 'make update_certificates'. DO NOT EDIT. + +package certificate + +func chromeIncludedPEM() string { + return ` + "`") + for { + record, err := reader.Read() + if err == io.EOF { + break + } else if err != nil { + return err + } + if record[statusIndex] != "Included" { + continue + } + if chinaFingerprints[record[fingerprintIndex]] { + continue + } + cert := strings.Trim(record[certIndex], "'") + generated.WriteString("\n// ") + generated.WriteString(record[subjectIndex]) + generated.WriteString("\n") + generated.WriteString(cert) + generated.WriteString("\n") + } + generated.WriteString("`\n}\n") + return os.WriteFile("common/certificate/chrome.go", []byte(generated.String()), 0o644) +} diff --git a/cmd/sing-box/cmd.go b/cmd/sing-box/cmd.go new file mode 100644 index 0000000000..575cb7a09d --- /dev/null +++ b/cmd/sing-box/cmd.go @@ -0,0 +1,71 @@ +package main + +import ( + "context" + "os" + "os/user" + "strconv" + "time" + + "github.com/sagernet/sing-box/experimental/deprecated" + "github.com/sagernet/sing-box/include" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/service" + "github.com/sagernet/sing/service/filemanager" + + "github.com/spf13/cobra" +) + +var ( + globalCtx context.Context + configPaths []string + configDirectories []string + workingDir string + disableColor bool +) + +var mainCommand = &cobra.Command{ + Use: "sing-box", + PersistentPreRun: preRun, +} + +func init() { + mainCommand.PersistentFlags().StringArrayVarP(&configPaths, "config", "c", nil, "set configuration file path") + mainCommand.PersistentFlags().StringArrayVarP(&configDirectories, "config-directory", "C", nil, "set configuration directory path") + mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory") + mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output") +} + +func preRun(cmd *cobra.Command, args []string) { + globalCtx = context.Background() + sudoUser := os.Getenv("SUDO_USER") + sudoUID, _ := strconv.Atoi(os.Getenv("SUDO_UID")) + sudoGID, _ := strconv.Atoi(os.Getenv("SUDO_GID")) + if sudoUID == 0 && sudoGID == 0 && sudoUser != "" { + sudoUserObject, _ := user.Lookup(sudoUser) + if sudoUserObject != nil { + sudoUID, _ = strconv.Atoi(sudoUserObject.Uid) + sudoGID, _ = strconv.Atoi(sudoUserObject.Gid) + } + } + if sudoUID > 0 && sudoGID > 0 { + globalCtx = filemanager.WithDefault(globalCtx, "", "", sudoUID, sudoGID) + } + if disableColor { + log.SetStdLogger(log.NewDefaultFactory(context.Background(), log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, "", nil, false).Logger()) + } + if workingDir != "" { + _, err := os.Stat(workingDir) + if err != nil { + filemanager.MkdirAll(globalCtx, workingDir, 0o777) + } + err = os.Chdir(workingDir) + if err != nil { + log.Fatal(err) + } + } + if len(configPaths) == 0 && len(configDirectories) == 0 { + configPaths = append(configPaths, "config.json") + } + globalCtx = include.Context(service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))) +} diff --git a/cmd/sing-box/cmd_check.go b/cmd/sing-box/cmd_check.go index 1beab9548d..29a3908168 100644 --- a/cmd/sing-box/cmd_check.go +++ b/cmd/sing-box/cmd_check.go @@ -30,7 +30,7 @@ func check() error { if err != nil { return err } - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(globalCtx) instance, err := box.New(box.Options{ Context: ctx, Options: options, diff --git a/cmd/sing-box/cmd_format.go b/cmd/sing-box/cmd_format.go index fc47c5a8dd..a8ecb6ab72 100644 --- a/cmd/sing-box/cmd_format.go +++ b/cmd/sing-box/cmd_format.go @@ -38,10 +38,12 @@ func format() error { return err } for _, optionsEntry := range optionsList { - optionsEntry.options, err = badjson.Omitempty(optionsEntry.options) + comments := optionsEntry.options.Comments() + optionsEntry.options, err = badjson.Omitempty(globalCtx, optionsEntry.options) if err != nil { return err } + optionsEntry.options.SetComments(comments) buffer := new(bytes.Buffer) encoder := json.NewEncoder(buffer) encoder.SetIndent("", " ") diff --git a/cmd/sing-box/cmd_generate_ech.go b/cmd/sing-box/cmd_generate_ech.go index 4b5b7be32d..7942d588bd 100644 --- a/cmd/sing-box/cmd_generate_ech.go +++ b/cmd/sing-box/cmd_generate_ech.go @@ -9,8 +9,6 @@ import ( "github.com/spf13/cobra" ) -var pqSignatureSchemesEnabled bool - var commandGenerateECHKeyPair = &cobra.Command{ Use: "ech-keypair ", Short: "Generate TLS ECH key pair", @@ -24,12 +22,11 @@ var commandGenerateECHKeyPair = &cobra.Command{ } func init() { - commandGenerateECHKeyPair.Flags().BoolVar(&pqSignatureSchemesEnabled, "pq-signature-schemes-enabled", false, "Enable PQ signature schemes") commandGenerate.AddCommand(commandGenerateECHKeyPair) } func generateECHKeyPair(serverName string) error { - configPem, keyPem, err := tls.ECHKeygenDefault(serverName, pqSignatureSchemesEnabled) + configPem, keyPem, err := tls.ECHKeygenDefault(serverName) if err != nil { return err } diff --git a/cmd/sing-box/cmd_generate_tls.go b/cmd/sing-box/cmd_generate_tls.go index d871566fac..4cd4060d1d 100644 --- a/cmd/sing-box/cmd_generate_tls.go +++ b/cmd/sing-box/cmd_generate_tls.go @@ -30,7 +30,7 @@ func init() { } func generateTLSKeyPair(serverName string) error { - privateKeyPem, publicKeyPem, err := tls.GenerateKeyPair(time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0)) + privateKeyPem, publicKeyPem, err := tls.GenerateCertificate(nil, nil, time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0)) if err != nil { return err } diff --git a/cmd/sing-box/cmd_geoip_export.go b/cmd/sing-box/cmd_geoip_export.go index 5787d2e5ad..6f59b4d53c 100644 --- a/cmd/sing-box/cmd_geoip_export.go +++ b/cmd/sing-box/cmd_geoip_export.go @@ -61,16 +61,17 @@ func geoipExport(countryCode string) error { outputFile *os.File outputWriter io.Writer ) - if flagGeoipExportOutput == "stdout" { + switch flagGeoipExportOutput { + case "stdout": outputWriter = os.Stdout - } else if flagGeoipExportOutput == flagGeoipExportDefaultOutput { + case flagGeoipExportDefaultOutput: outputFile, err = os.Create("geoip-" + countryCode + ".json") if err != nil { return err } defer outputFile.Close() outputWriter = outputFile - } else { + default: outputFile, err = os.Create(flagGeoipExportOutput) if err != nil { return err @@ -87,7 +88,7 @@ func geoipExport(countryCode string) error { headlessRule.IPCIDR = append(headlessRule.IPCIDR, cidr.String()) } var plainRuleSet option.PlainRuleSetCompat - plainRuleSet.Version = C.RuleSetVersion1 + plainRuleSet.Version = C.RuleSetVersion2 plainRuleSet.Options.Rules = []option.HeadlessRule{ { Type: C.RuleTypeDefault, diff --git a/cmd/sing-box/cmd_geosite_export.go b/cmd/sing-box/cmd_geosite_export.go index 2a6c27a0ed..573cc1dfea 100644 --- a/cmd/sing-box/cmd_geosite_export.go +++ b/cmd/sing-box/cmd_geosite_export.go @@ -43,16 +43,17 @@ func geositeExport(category string) error { outputFile *os.File outputWriter io.Writer ) - if commandGeositeExportOutput == "stdout" { + switch commandGeositeExportOutput { + case "stdout": outputWriter = os.Stdout - } else if commandGeositeExportOutput == commandGeositeExportDefaultOutput { + case commandGeositeExportDefaultOutput: outputFile, err = os.Create("geosite-" + category + ".json") if err != nil { return err } defer outputFile.Close() outputWriter = outputFile - } else { + default: outputFile, err = os.Create(commandGeositeExportOutput) if err != nil { return err @@ -70,7 +71,7 @@ func geositeExport(category string) error { headlessRule.DomainKeyword = defaultRule.DomainKeyword headlessRule.DomainRegex = defaultRule.DomainRegex var plainRuleSet option.PlainRuleSetCompat - plainRuleSet.Version = C.RuleSetVersion1 + plainRuleSet.Version = C.RuleSetVersion2 plainRuleSet.Options.Rules = []option.HeadlessRule{ { Type: C.RuleTypeDefault, diff --git a/cmd/sing-box/cmd_merge.go b/cmd/sing-box/cmd_merge.go index 1d19ff17e1..6ca9a15a10 100644 --- a/cmd/sing-box/cmd_merge.go +++ b/cmd/sing-box/cmd_merge.go @@ -18,7 +18,7 @@ import ( ) var commandMerge = &cobra.Command{ - Use: "merge ", + Use: "merge ", Short: "Merge configurations", Run: func(cmd *cobra.Command, args []string) { err := merge(args[0]) @@ -54,7 +54,11 @@ func merge(outputPath string) error { return nil } } - err = rw.WriteFile(outputPath, buffer.Bytes()) + err = rw.MkdirParent(outputPath) + if err != nil { + return err + } + err = os.WriteFile(outputPath, buffer.Bytes(), 0o644) if err != nil { return err } @@ -64,29 +68,19 @@ func merge(outputPath string) error { } func mergePathResources(options *option.Options) error { - for index, inbound := range options.Inbounds { - rawOptions, err := inbound.RawOptions() - if err != nil { - return err - } - if tlsOptions, containsTLSOptions := rawOptions.(option.InboundTLSOptionsWrapper); containsTLSOptions { + for _, inbound := range options.Inbounds { + if tlsOptions, containsTLSOptions := inbound.Options.(option.InboundTLSOptionsWrapper); containsTLSOptions { tlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions())) } - options.Inbounds[index] = inbound } - for index, outbound := range options.Outbounds { - rawOptions, err := outbound.RawOptions() - if err != nil { - return err - } + for _, outbound := range options.Outbounds { switch outbound.Type { case C.TypeSSH: - outbound.SSHOptions = mergeSSHOutboundOptions(outbound.SSHOptions) + mergeSSHOutboundOptions(outbound.Options.(*option.SSHOutboundOptions)) } - if tlsOptions, containsTLSOptions := rawOptions.(option.OutboundTLSOptionsWrapper); containsTLSOptions { + if tlsOptions, containsTLSOptions := outbound.Options.(option.OutboundTLSOptionsWrapper); containsTLSOptions { tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions())) } - options.Outbounds[index] = outbound } return nil } @@ -134,13 +128,12 @@ func mergeTLSOutboundOptions(options *option.OutboundTLSOptions) *option.Outboun return options } -func mergeSSHOutboundOptions(options option.SSHOutboundOptions) option.SSHOutboundOptions { +func mergeSSHOutboundOptions(options *option.SSHOutboundOptions) { if options.PrivateKeyPath != "" { if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil { options.PrivateKey = trimStringArray(strings.Split(string(content), "\n")) } } - return options } func trimStringArray(array []string) []string { diff --git a/cmd/sing-box/cmd_rule_set.go b/cmd/sing-box/cmd_rule_set.go index f4112a087b..242ea8b666 100644 --- a/cmd/sing-box/cmd_rule_set.go +++ b/cmd/sing-box/cmd_rule_set.go @@ -6,7 +6,7 @@ import ( var commandRuleSet = &cobra.Command{ Use: "rule-set", - Short: "Manage rule sets", + Short: "Manage rule-sets", } func init() { diff --git a/cmd/sing-box/cmd_rule_set_compile.go b/cmd/sing-box/cmd_rule_set_compile.go index 6e065101a8..e2cbefc7bf 100644 --- a/cmd/sing-box/cmd_rule_set_compile.go +++ b/cmd/sing-box/cmd_rule_set_compile.go @@ -6,8 +6,10 @@ import ( "strings" "github.com/sagernet/sing-box/common/srs" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing/common/json" "github.com/spf13/cobra" @@ -55,10 +57,6 @@ func compileRuleSet(sourcePath string) error { if err != nil { return err } - if err != nil { - return err - } - ruleSet := plainRuleSet.Upgrade() var outputPath string if flagRuleSetCompileOutput == flagRuleSetCompileDefaultOutput { if strings.HasSuffix(sourcePath, ".json") { @@ -73,7 +71,7 @@ func compileRuleSet(sourcePath string) error { if err != nil { return err } - err = srs.Write(outputFile, ruleSet) + err = srs.Write(outputFile, plainRuleSet.Options, downgradeRuleSetVersion(plainRuleSet.Version, plainRuleSet.Options)) if err != nil { outputFile.Close() os.Remove(outputPath) @@ -82,3 +80,23 @@ func compileRuleSet(sourcePath string) error { outputFile.Close() return nil } + +func downgradeRuleSetVersion(version uint8, options option.PlainRuleSet) uint8 { + if version == C.RuleSetVersion5 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool { + return len(rule.PackageNameRegex) > 0 + }) { + version = C.RuleSetVersion4 + } + if version == C.RuleSetVersion4 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool { + return rule.NetworkInterfaceAddress != nil && rule.NetworkInterfaceAddress.Size() > 0 || + len(rule.DefaultInterfaceAddress) > 0 + }) { + version = C.RuleSetVersion3 + } + if version == C.RuleSetVersion3 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool { + return len(rule.NetworkType) > 0 || rule.NetworkIsExpensive || rule.NetworkIsConstrained + }) { + version = C.RuleSetVersion2 + } + return version +} diff --git a/cmd/sing-box/cmd_rule_set_convert.go b/cmd/sing-box/cmd_rule_set_convert.go new file mode 100644 index 0000000000..f4f8d2caca --- /dev/null +++ b/cmd/sing-box/cmd_rule_set_convert.go @@ -0,0 +1,89 @@ +package main + +import ( + "io" + "os" + "strings" + + "github.com/sagernet/sing-box/common/convertor/adguard" + "github.com/sagernet/sing-box/common/srs" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + + "github.com/spf13/cobra" +) + +var ( + flagRuleSetConvertType string + flagRuleSetConvertOutput string +) + +var commandRuleSetConvert = &cobra.Command{ + Use: "convert [source-path]", + Short: "Convert adguard DNS filter to rule-set", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + err := convertRuleSet(args[0]) + if err != nil { + log.Fatal(err) + } + }, +} + +func init() { + commandRuleSet.AddCommand(commandRuleSetConvert) + commandRuleSetConvert.Flags().StringVarP(&flagRuleSetConvertType, "type", "t", "", "Source type, available: adguard") + commandRuleSetConvert.Flags().StringVarP(&flagRuleSetConvertOutput, "output", "o", flagRuleSetCompileDefaultOutput, "Output file") +} + +func convertRuleSet(sourcePath string) error { + var ( + reader io.Reader + err error + ) + if sourcePath == "stdin" { + reader = os.Stdin + } else { + reader, err = os.Open(sourcePath) + if err != nil { + return err + } + } + var rules []option.HeadlessRule + switch flagRuleSetConvertType { + case "adguard": + rules, err = adguard.ToOptions(reader, log.StdLogger()) + case "": + return E.New("source type is required") + default: + return E.New("unsupported source type: ", flagRuleSetConvertType) + } + if err != nil { + return err + } + var outputPath string + if flagRuleSetConvertOutput == flagRuleSetCompileDefaultOutput { + if strings.HasSuffix(sourcePath, ".txt") { + outputPath = sourcePath[:len(sourcePath)-4] + ".srs" + } else { + outputPath = sourcePath + ".srs" + } + } else { + outputPath = flagRuleSetConvertOutput + } + outputFile, err := os.Create(outputPath) + if err != nil { + return err + } + defer outputFile.Close() + err = srs.Write(outputFile, option.PlainRuleSet{Rules: rules}, C.RuleSetVersion2) + if err != nil { + outputFile.Close() + os.Remove(outputPath) + return err + } + outputFile.Close() + return nil +} diff --git a/cmd/sing-box/cmd_rule_set_decompile.go b/cmd/sing-box/cmd_rule_set_decompile.go new file mode 100644 index 0000000000..6fc43d42ac --- /dev/null +++ b/cmd/sing-box/cmd_rule_set_decompile.go @@ -0,0 +1,101 @@ +package main + +import ( + "io" + "os" + "strings" + + "github.com/sagernet/sing-box/common/srs" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + + "github.com/spf13/cobra" +) + +var flagRuleSetDecompileOutput string + +const flagRuleSetDecompileDefaultOutput = ".json" + +var commandRuleSetDecompile = &cobra.Command{ + Use: "decompile [binary-path]", + Short: "Decompile rule-set binary to json", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + err := decompileRuleSet(args[0]) + if err != nil { + log.Fatal(err) + } + }, +} + +func init() { + commandRuleSet.AddCommand(commandRuleSetDecompile) + commandRuleSetDecompile.Flags().StringVarP(&flagRuleSetDecompileOutput, "output", "o", flagRuleSetDecompileDefaultOutput, "Output file") +} + +func decompileRuleSet(sourcePath string) error { + var ( + reader io.Reader + err error + ) + if sourcePath == "stdin" { + reader = os.Stdin + } else { + reader, err = os.Open(sourcePath) + if err != nil { + return err + } + } + ruleSet, err := srs.Read(reader, true) + if err != nil { + return err + } + if hasRule(ruleSet.Options.Rules, func(rule option.DefaultHeadlessRule) bool { + return len(rule.AdGuardDomain) > 0 + }) { + return E.New("unable to decompile binary AdGuard rules to rule-set.") + } + var outputPath string + if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput { + if strings.HasSuffix(sourcePath, ".srs") { + outputPath = sourcePath[:len(sourcePath)-4] + ".json" + } else { + outputPath = sourcePath + ".json" + } + } else { + outputPath = flagRuleSetDecompileOutput + } + outputFile, err := os.Create(outputPath) + if err != nil { + return err + } + encoder := json.NewEncoder(outputFile) + encoder.SetIndent("", " ") + err = encoder.Encode(ruleSet) + if err != nil { + outputFile.Close() + os.Remove(outputPath) + return err + } + outputFile.Close() + return nil +} + +func hasRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool { + for _, rule := range rules { + switch rule.Type { + case C.RuleTypeDefault: + if cond(rule.DefaultOptions) { + return true + } + case C.RuleTypeLogical: + if hasRule(rule.LogicalOptions.Rules, cond) { + return true + } + } + } + return false +} diff --git a/cmd/sing-box/cmd_rule_set_match.go b/cmd/sing-box/cmd_rule_set_match.go index 08784caf11..e3dc19b8f8 100644 --- a/cmd/sing-box/cmd_rule_set_match.go +++ b/cmd/sing-box/cmd_rule_set_match.go @@ -2,18 +2,21 @@ package main import ( "bytes" + "context" "io" "os" + "path/filepath" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/srs" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-box/route" + "github.com/sagernet/sing-box/route/rule" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" + M "github.com/sagernet/sing/common/metadata" "github.com/spf13/cobra" ) @@ -21,8 +24,8 @@ import ( var flagRuleSetMatchFormat string var commandRuleSetMatch = &cobra.Command{ - Use: "match ", - Short: "Check if a domain matches the rule set", + Use: "match ", + Short: "Check if an IP address or a domain matches the rule-set", Args: cobra.ExactArgs(2), Run: func(cmd *cobra.Command, args []string) { err := ruleSetMatch(args[0], args[1]) @@ -54,32 +57,47 @@ func ruleSetMatch(sourcePath string, domain string) error { if err != nil { return E.Cause(err, "read rule-set") } - var plainRuleSet option.PlainRuleSet + if flagRuleSetMatchFormat == "" { + switch filepath.Ext(sourcePath) { + case ".json": + flagRuleSetMatchFormat = C.RuleSetFormatSource + case ".srs": + flagRuleSetMatchFormat = C.RuleSetFormatBinary + } + } + var ruleSet option.PlainRuleSetCompat switch flagRuleSetMatchFormat { case C.RuleSetFormatSource: - var compat option.PlainRuleSetCompat - compat, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content) + ruleSet, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content) if err != nil { return err } - plainRuleSet = compat.Upgrade() case C.RuleSetFormatBinary: - plainRuleSet, err = srs.Read(bytes.NewReader(content), false) + ruleSet, err = srs.Read(bytes.NewReader(content), false) if err != nil { return err } default: - return E.New("unknown rule set format: ", flagRuleSetMatchFormat) + return E.New("unknown rule-set format: ", flagRuleSetMatchFormat) + } + plainRuleSet, err := ruleSet.Upgrade() + if err != nil { + return err + } + ipAddress := M.ParseAddr(domain) + var metadata adapter.InboundContext + if ipAddress.IsValid() { + metadata.Destination = M.SocksaddrFrom(ipAddress, 0) + } else { + metadata.Domain = domain } for i, ruleOptions := range plainRuleSet.Rules { var currentRule adapter.HeadlessRule - currentRule, err = route.NewHeadlessRule(nil, ruleOptions) + currentRule, err = rule.NewHeadlessRule(context.Background(), ruleOptions) if err != nil { return E.Cause(err, "parse rule_set.rules.[", i, "]") } - if currentRule.Match(&adapter.InboundContext{ - Domain: domain, - }) { + if currentRule.Match(&metadata) { println(F.ToString("match rules.[", i, "]: ", currentRule)) } } diff --git a/cmd/sing-box/cmd_rule_set_merge.go b/cmd/sing-box/cmd_rule_set_merge.go new file mode 100644 index 0000000000..7c8f7a5353 --- /dev/null +++ b/cmd/sing-box/cmd_rule_set_merge.go @@ -0,0 +1,162 @@ +package main + +import ( + "bytes" + "io" + "os" + "path/filepath" + "sort" + "strings" + + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/rw" + + "github.com/spf13/cobra" +) + +var ( + ruleSetPaths []string + ruleSetDirectories []string +) + +var commandRuleSetMerge = &cobra.Command{ + Use: "merge ", + Short: "Merge rule-set source files", + Run: func(cmd *cobra.Command, args []string) { + err := mergeRuleSet(args[0]) + if err != nil { + log.Fatal(err) + } + }, + Args: cobra.ExactArgs(1), +} + +func init() { + commandRuleSetMerge.Flags().StringArrayVarP(&ruleSetPaths, "config", "c", nil, "set input rule-set file path") + commandRuleSetMerge.Flags().StringArrayVarP(&ruleSetDirectories, "config-directory", "C", nil, "set input rule-set directory path") + commandRuleSet.AddCommand(commandRuleSetMerge) +} + +type RuleSetEntry struct { + content []byte + path string + options option.PlainRuleSetCompat +} + +func readRuleSetAt(path string) (*RuleSetEntry, error) { + var ( + configContent []byte + err error + ) + if path == "stdin" { + configContent, err = io.ReadAll(os.Stdin) + } else { + configContent, err = os.ReadFile(path) + } + if err != nil { + return nil, E.Cause(err, "read config at ", path) + } + options, err := json.UnmarshalExtendedContext[option.PlainRuleSetCompat](globalCtx, configContent) + if err != nil { + return nil, E.Cause(err, "decode config at ", path) + } + return &RuleSetEntry{ + content: configContent, + path: path, + options: options, + }, nil +} + +func readRuleSet() ([]*RuleSetEntry, error) { + var optionsList []*RuleSetEntry + for _, path := range ruleSetPaths { + optionsEntry, err := readRuleSetAt(path) + if err != nil { + return nil, err + } + optionsList = append(optionsList, optionsEntry) + } + for _, directory := range ruleSetDirectories { + entries, err := os.ReadDir(directory) + if err != nil { + return nil, E.Cause(err, "read rule-set directory at ", directory) + } + for _, entry := range entries { + if !strings.HasSuffix(entry.Name(), ".json") || entry.IsDir() { + continue + } + optionsEntry, err := readRuleSetAt(filepath.Join(directory, entry.Name())) + if err != nil { + return nil, err + } + optionsList = append(optionsList, optionsEntry) + } + } + sort.Slice(optionsList, func(i, j int) bool { + return optionsList[i].path < optionsList[j].path + }) + return optionsList, nil +} + +func readRuleSetAndMerge() (option.PlainRuleSetCompat, error) { + optionsList, err := readRuleSet() + if err != nil { + return option.PlainRuleSetCompat{}, err + } + if len(optionsList) == 1 { + return optionsList[0].options, nil + } + var optionVersion uint8 + for _, options := range optionsList { + if optionVersion < options.options.Version { + optionVersion = options.options.Version + } + } + var mergedMessage json.RawMessage + for _, options := range optionsList { + mergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false) + if err != nil { + return option.PlainRuleSetCompat{}, E.Cause(err, "merge config at ", options.path) + } + } + mergedOptions, err := json.UnmarshalExtendedContext[option.PlainRuleSetCompat](globalCtx, mergedMessage) + if err != nil { + return option.PlainRuleSetCompat{}, E.Cause(err, "unmarshal merged config") + } + mergedOptions.Version = optionVersion + return mergedOptions, nil +} + +func mergeRuleSet(outputPath string) error { + mergedOptions, err := readRuleSetAndMerge() + if err != nil { + return err + } + buffer := new(bytes.Buffer) + encoder := json.NewEncoder(buffer) + encoder.SetIndent("", " ") + err = encoder.Encode(mergedOptions) + if err != nil { + return E.Cause(err, "encode config") + } + if existsContent, err := os.ReadFile(outputPath); err != nil { + if string(existsContent) == buffer.String() { + return nil + } + } + err = rw.MkdirParent(outputPath) + if err != nil { + return err + } + err = os.WriteFile(outputPath, buffer.Bytes(), 0o644) + if err != nil { + return err + } + outputPath, _ = filepath.Abs(outputPath) + os.Stderr.WriteString(outputPath + "\n") + return nil +} diff --git a/cmd/sing-box/cmd_rule_set_upgrade.go b/cmd/sing-box/cmd_rule_set_upgrade.go new file mode 100644 index 0000000000..3d77a1d2d4 --- /dev/null +++ b/cmd/sing-box/cmd_rule_set_upgrade.go @@ -0,0 +1,95 @@ +package main + +import ( + "bytes" + "io" + "os" + "path/filepath" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + + "github.com/spf13/cobra" +) + +var commandRuleSetUpgradeFlagWrite bool + +var commandRuleSetUpgrade = &cobra.Command{ + Use: "upgrade ", + Short: "Upgrade rule-set json", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + err := upgradeRuleSet(args[0]) + if err != nil { + log.Fatal(err) + } + }, +} + +func init() { + commandRuleSetUpgrade.Flags().BoolVarP(&commandRuleSetUpgradeFlagWrite, "write", "w", false, "write result to (source) file instead of stdout") + commandRuleSet.AddCommand(commandRuleSetUpgrade) +} + +func upgradeRuleSet(sourcePath string) error { + var ( + reader io.Reader + err error + ) + if sourcePath == "stdin" { + reader = os.Stdin + } else { + reader, err = os.Open(sourcePath) + if err != nil { + return err + } + } + content, err := io.ReadAll(reader) + if err != nil { + return err + } + plainRuleSetCompat, err := json.UnmarshalExtended[option.PlainRuleSetCompat](content) + if err != nil { + return err + } + switch plainRuleSetCompat.Version { + case C.RuleSetVersion1: + default: + log.Info("already up-to-date") + return nil + } + plainRuleSetCompat.Options, err = plainRuleSetCompat.Upgrade() + if err != nil { + return err + } + plainRuleSetCompat.Version = C.RuleSetVersionCurrent + buffer := new(bytes.Buffer) + encoder := json.NewEncoder(buffer) + encoder.SetIndent("", " ") + err = encoder.Encode(plainRuleSetCompat) + if err != nil { + return E.Cause(err, "encode config") + } + outputPath, _ := filepath.Abs(sourcePath) + if !commandRuleSetUpgradeFlagWrite || sourcePath == "stdin" { + os.Stdout.WriteString(buffer.String() + "\n") + return nil + } + if bytes.Equal(content, buffer.Bytes()) { + return nil + } + output, err := os.Create(sourcePath) + if err != nil { + return E.Cause(err, "open output") + } + _, err = output.Write(buffer.Bytes()) + output.Close() + if err != nil { + return E.Cause(err, "write output") + } + os.Stderr.WriteString(outputPath + "\n") + return nil +} diff --git a/cmd/sing-box/cmd_run.go b/cmd/sing-box/cmd_run.go index 3c4dd0d910..f31db9dc82 100644 --- a/cmd/sing-box/cmd_run.go +++ b/cmd/sing-box/cmd_run.go @@ -57,7 +57,7 @@ func readConfigAt(path string) (*OptionsEntry, error) { if err != nil { return nil, E.Cause(err, "read config at ", path) } - options, err := json.UnmarshalExtended[option.Options](configContent) + options, err := json.UnmarshalExtendedContext[option.Options](globalCtx, configContent) if err != nil { return nil, E.Cause(err, "decode config at ", path) } @@ -109,13 +109,13 @@ func readConfigAndMerge() (option.Options, error) { } var mergedMessage json.RawMessage for _, options := range optionsList { - mergedMessage, err = badjson.MergeJSON(options.options.RawMessage, mergedMessage) + mergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false) if err != nil { return option.Options{}, E.Cause(err, "merge config at ", options.path) } } var mergedOptions option.Options - err = mergedOptions.UnmarshalJSON(mergedMessage) + err = mergedOptions.UnmarshalJSONContext(globalCtx, mergedMessage) if err != nil { return option.Options{}, E.Cause(err, "unmarshal merged config") } @@ -188,9 +188,12 @@ func run() error { cancel() closeCtx, closed := context.WithCancel(context.Background()) go closeMonitor(closeCtx) - instance.Close() + err = instance.Close() closed() if osSignal != syscall.SIGHUP { + if err != nil { + log.Error(E.Cause(err, "sing-box did not closed properly")) + } return nil } break diff --git a/cmd/sing-box/cmd_tools.go b/cmd/sing-box/cmd_tools.go index c45f585576..55e5b45873 100644 --- a/cmd/sing-box/cmd_tools.go +++ b/cmd/sing-box/cmd_tools.go @@ -1,6 +1,9 @@ package main import ( + "errors" + "os" + "github.com/sagernet/sing-box" E "github.com/sagernet/sing/common/exceptions" N "github.com/sagernet/sing/common/network" @@ -23,9 +26,11 @@ func init() { func createPreStartedClient() (*box.Box, error) { options, err := readConfigAndMerge() if err != nil { - return nil, err + if !(errors.Is(err, os.ErrNotExist) && len(configDirectories) == 0 && len(configPaths) == 1) || configPaths[0] != "config.json" { + return nil, err + } } - instance, err := box.New(box.Options{Options: options}) + instance, err := box.New(box.Options{Context: globalCtx, Options: options}) if err != nil { return nil, E.Cause(err, "create service") } @@ -36,11 +41,11 @@ func createPreStartedClient() (*box.Box, error) { return instance, nil } -func createDialer(instance *box.Box, network string, outboundTag string) (N.Dialer, error) { +func createDialer(instance *box.Box, outboundTag string) (N.Dialer, error) { if outboundTag == "" { - return instance.Router().DefaultOutbound(N.NetworkName(network)) + return instance.Outbound().Default(), nil } else { - outbound, loaded := instance.Router().Outbound(outboundTag) + outbound, loaded := instance.Outbound().Outbound(outboundTag) if !loaded { return nil, E.New("outbound not found: ", outboundTag) } diff --git a/cmd/sing-box/cmd_tools_connect.go b/cmd/sing-box/cmd_tools_connect.go index 3ea04bcd40..d352d533e7 100644 --- a/cmd/sing-box/cmd_tools_connect.go +++ b/cmd/sing-box/cmd_tools_connect.go @@ -45,7 +45,7 @@ func connect(address string) error { return err } defer instance.Close() - dialer, err := createDialer(instance, commandConnectFlagNetwork, commandToolsFlagOutbound) + dialer, err := createDialer(instance, commandToolsFlagOutbound) if err != nil { return err } diff --git a/cmd/sing-box/cmd_tools_fetch.go b/cmd/sing-box/cmd_tools_fetch.go index 256c3f42b6..5ee3b87510 100644 --- a/cmd/sing-box/cmd_tools_fetch.go +++ b/cmd/sing-box/cmd_tools_fetch.go @@ -9,8 +9,10 @@ import ( "net/url" "os" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" "github.com/spf13/cobra" @@ -32,7 +34,10 @@ func init() { commandTools.AddCommand(commandFetch) } -var httpClient *http.Client +var ( + httpClient *http.Client + http3Client *http.Client +) func fetch(args []string) error { instance, err := createPreStartedClient() @@ -43,7 +48,7 @@ func fetch(args []string) error { httpClient = &http.Client{ Transport: &http.Transport{ DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - dialer, err := createDialer(instance, network, commandToolsFlagOutbound) + dialer, err := createDialer(instance, commandToolsFlagOutbound) if err != nil { return nil, err } @@ -53,8 +58,16 @@ func fetch(args []string) error { }, } defer httpClient.CloseIdleConnections() + if C.WithQUIC { + err = initializeHTTP3Client(instance) + if err != nil { + return err + } + defer http3Client.CloseIdleConnections() + } for _, urlString := range args { - parsedURL, err := url.Parse(urlString) + var parsedURL *url.URL + parsedURL, err = url.Parse(urlString) if err != nil { return err } @@ -63,16 +76,27 @@ func fetch(args []string) error { parsedURL.Scheme = "http" fallthrough case "http", "https": - err = fetchHTTP(parsedURL) + err = fetchHTTP(httpClient, parsedURL) + if err != nil { + return err + } + case "http3": + if !C.WithQUIC { + return C.ErrQUICNotIncluded + } + parsedURL.Scheme = "https" + err = fetchHTTP(http3Client, parsedURL) if err != nil { return err } + default: + return E.New("unsupported scheme: ", parsedURL.Scheme) } } return nil } -func fetchHTTP(parsedURL *url.URL) error { +func fetchHTTP(httpClient *http.Client, parsedURL *url.URL) error { request, err := http.NewRequest("GET", parsedURL.String(), nil) if err != nil { return err diff --git a/cmd/sing-box/cmd_tools_fetch_http3.go b/cmd/sing-box/cmd_tools_fetch_http3.go new file mode 100644 index 0000000000..3caa1e88bd --- /dev/null +++ b/cmd/sing-box/cmd_tools_fetch_http3.go @@ -0,0 +1,36 @@ +//go:build with_quic + +package main + +import ( + "context" + "crypto/tls" + "net/http" + + "github.com/sagernet/quic-go" + "github.com/sagernet/quic-go/http3" + box "github.com/sagernet/sing-box" + "github.com/sagernet/sing/common/bufio" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func initializeHTTP3Client(instance *box.Box) error { + dialer, err := createDialer(instance, commandToolsFlagOutbound) + if err != nil { + return err + } + http3Client = &http.Client{ + Transport: &http3.Transport{ + Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) { + destination := M.ParseSocksaddr(addr) + udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination) + if dErr != nil { + return nil, dErr + } + return quic.DialEarly(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), tlsCfg, cfg) + }, + }, + } + return nil +} diff --git a/cmd/sing-box/cmd_tools_fetch_http3_stub.go b/cmd/sing-box/cmd_tools_fetch_http3_stub.go new file mode 100644 index 0000000000..ae13f54c42 --- /dev/null +++ b/cmd/sing-box/cmd_tools_fetch_http3_stub.go @@ -0,0 +1,18 @@ +//go:build !with_quic + +package main + +import ( + "net/url" + "os" + + box "github.com/sagernet/sing-box" +) + +func initializeHTTP3Client(instance *box.Box) error { + return os.ErrInvalid +} + +func fetchHTTP3(parsedURL *url.URL) error { + return os.ErrInvalid +} diff --git a/cmd/sing-box/cmd_tools_networkquality.go b/cmd/sing-box/cmd_tools_networkquality.go new file mode 100644 index 0000000000..5f63571de7 --- /dev/null +++ b/cmd/sing-box/cmd_tools_networkquality.go @@ -0,0 +1,121 @@ +package main + +import ( + "fmt" + "os" + "strings" + "time" + + "github.com/sagernet/sing-box/common/networkquality" + "github.com/sagernet/sing-box/log" + + "github.com/spf13/cobra" +) + +var ( + commandNetworkQualityFlagConfigURL string + commandNetworkQualityFlagSerial bool + commandNetworkQualityFlagMaxRuntime int + commandNetworkQualityFlagHTTP3 bool +) + +var commandNetworkQuality = &cobra.Command{ + Use: "networkquality", + Short: "Run a network quality test", + Run: func(cmd *cobra.Command, args []string) { + err := runNetworkQuality() + if err != nil { + log.Fatal(err) + } + }, +} + +func init() { + commandNetworkQuality.Flags().StringVar( + &commandNetworkQualityFlagConfigURL, + "config-url", "", + "Network quality test config URL (default: Apple mensura)", + ) + commandNetworkQuality.Flags().BoolVar( + &commandNetworkQualityFlagSerial, + "serial", false, + "Run download and upload tests sequentially instead of in parallel", + ) + commandNetworkQuality.Flags().IntVar( + &commandNetworkQualityFlagMaxRuntime, + "max-runtime", int(networkquality.DefaultMaxRuntime/time.Second), + "Network quality maximum runtime in seconds", + ) + commandNetworkQuality.Flags().BoolVar( + &commandNetworkQualityFlagHTTP3, + "http3", false, + "Use HTTP/3 (QUIC) for measurement traffic", + ) + commandTools.AddCommand(commandNetworkQuality) +} + +func runNetworkQuality() error { + instance, err := createPreStartedClient() + if err != nil { + return err + } + defer instance.Close() + + dialer, err := createDialer(instance, commandToolsFlagOutbound) + if err != nil { + return err + } + + httpClient := networkquality.NewHTTPClient(dialer) + defer httpClient.CloseIdleConnections() + + measurementClientFactory, err := networkquality.NewOptionalHTTP3Factory(dialer, commandNetworkQualityFlagHTTP3) + if err != nil { + return err + } + + fmt.Fprintln(os.Stderr, "==== NETWORK QUALITY TEST ====") + + result, err := networkquality.Run(networkquality.Options{ + ConfigURL: commandNetworkQualityFlagConfigURL, + HTTPClient: httpClient, + NewMeasurementClient: measurementClientFactory, + Serial: commandNetworkQualityFlagSerial, + MaxRuntime: time.Duration(commandNetworkQualityFlagMaxRuntime) * time.Second, + Context: globalCtx, + OnProgress: func(p networkquality.Progress) { + if !commandNetworkQualityFlagSerial && p.Phase != networkquality.PhaseIdle { + fmt.Fprintf(os.Stderr, "\rDownload: %s RPM: %d Upload: %s RPM: %d", + networkquality.FormatBitrate(p.DownloadCapacity), p.DownloadRPM, + networkquality.FormatBitrate(p.UploadCapacity), p.UploadRPM) + return + } + switch networkquality.Phase(p.Phase) { + case networkquality.PhaseIdle: + if p.IdleLatencyMs > 0 { + fmt.Fprintf(os.Stderr, "\rIdle Latency: %d ms", p.IdleLatencyMs) + } else { + fmt.Fprint(os.Stderr, "\rMeasuring idle latency...") + } + case networkquality.PhaseDownload: + fmt.Fprintf(os.Stderr, "\rDownload: %s RPM: %d", + networkquality.FormatBitrate(p.DownloadCapacity), p.DownloadRPM) + case networkquality.PhaseUpload: + fmt.Fprintf(os.Stderr, "\rUpload: %s RPM: %d", + networkquality.FormatBitrate(p.UploadCapacity), p.UploadRPM) + } + }, + }) + if err != nil { + return err + } + + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, strings.Repeat("-", 40)) + fmt.Fprintf(os.Stderr, "Idle Latency: %d ms\n", result.IdleLatencyMs) + fmt.Fprintf(os.Stderr, "Download Capacity: %-20s Accuracy: %s\n", networkquality.FormatBitrate(result.DownloadCapacity), result.DownloadCapacityAccuracy) + fmt.Fprintf(os.Stderr, "Upload Capacity: %-20s Accuracy: %s\n", networkquality.FormatBitrate(result.UploadCapacity), result.UploadCapacityAccuracy) + fmt.Fprintf(os.Stderr, "Download Responsiveness: %-20s Accuracy: %s\n", fmt.Sprintf("%d RPM", result.DownloadRPM), result.DownloadRPMAccuracy) + fmt.Fprintf(os.Stderr, "Upload Responsiveness: %-20s Accuracy: %s\n", fmt.Sprintf("%d RPM", result.UploadRPM), result.UploadRPMAccuracy) + return nil +} diff --git a/cmd/sing-box/cmd_tools_stun.go b/cmd/sing-box/cmd_tools_stun.go new file mode 100644 index 0000000000..f13086caaa --- /dev/null +++ b/cmd/sing-box/cmd_tools_stun.go @@ -0,0 +1,79 @@ +package main + +import ( + "fmt" + "os" + + "github.com/sagernet/sing-box/common/stun" + "github.com/sagernet/sing-box/log" + + "github.com/spf13/cobra" +) + +var commandSTUNFlagServer string + +var commandSTUN = &cobra.Command{ + Use: "stun", + Short: "Run a STUN test", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + err := runSTUN() + if err != nil { + log.Fatal(err) + } + }, +} + +func init() { + commandSTUN.Flags().StringVarP(&commandSTUNFlagServer, "server", "s", stun.DefaultServer, "STUN server address") + commandTools.AddCommand(commandSTUN) +} + +func runSTUN() error { + instance, err := createPreStartedClient() + if err != nil { + return err + } + defer instance.Close() + + dialer, err := createDialer(instance, commandToolsFlagOutbound) + if err != nil { + return err + } + + fmt.Fprintln(os.Stderr, "==== STUN TEST ====") + + result, err := stun.Run(stun.Options{ + Server: commandSTUNFlagServer, + Dialer: dialer, + Context: globalCtx, + OnProgress: func(p stun.Progress) { + switch p.Phase { + case stun.PhaseBinding: + if p.ExternalAddr != "" { + fmt.Fprintf(os.Stderr, "\rExternal Address: %s (%d ms)", p.ExternalAddr, p.LatencyMs) + } else { + fmt.Fprint(os.Stderr, "\rSending binding request...") + } + case stun.PhaseNATMapping: + fmt.Fprint(os.Stderr, "\rDetecting NAT mapping behavior...") + case stun.PhaseNATFiltering: + fmt.Fprint(os.Stderr, "\rDetecting NAT filtering behavior...") + } + }, + }) + if err != nil { + return err + } + + fmt.Fprintln(os.Stderr) + fmt.Fprintf(os.Stderr, "External Address: %s\n", result.ExternalAddr) + fmt.Fprintf(os.Stderr, "Latency: %d ms\n", result.LatencyMs) + if result.NATTypeSupported { + fmt.Fprintf(os.Stderr, "NAT Mapping: %s\n", result.NATMapping) + fmt.Fprintf(os.Stderr, "NAT Filtering: %s\n", result.NATFiltering) + } else { + fmt.Fprintln(os.Stderr, "NAT Type Detection: not supported by server") + } + return nil +} diff --git a/cmd/sing-box/cmd_tools_synctime.go b/cmd/sing-box/cmd_tools_synctime.go index 20d73a6d67..09d487ef95 100644 --- a/cmd/sing-box/cmd_tools_synctime.go +++ b/cmd/sing-box/cmd_tools_synctime.go @@ -4,12 +4,10 @@ import ( "context" "os" - "github.com/sagernet/sing-box/common/settings" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ntp" "github.com/spf13/cobra" @@ -45,7 +43,7 @@ func syncTime() error { if err != nil { return err } - dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound) + dialer, err := createDialer(instance, commandToolsFlagOutbound) if err != nil { return err } @@ -59,7 +57,7 @@ func syncTime() error { return err } if commandSyncTimeWrite { - err = settings.SetSystemTime(response.Time) + err = ntp.SetSystemTime(response.Time) if err != nil { return E.Cause(err, "write time to system") } diff --git a/cmd/sing-box/cmd_version.go b/cmd/sing-box/cmd_version.go index ab5ada2ed8..3ef103b567 100644 --- a/cmd/sing-box/cmd_version.go +++ b/cmd/sing-box/cmd_version.go @@ -29,7 +29,7 @@ func printVersion(cmd *cobra.Command, args []string) { os.Stdout.WriteString(C.Version + "\n") return } - version := "sing-box version " + C.Version + "\n\n" + version := "hiddify-sing-box version " + C.Version + "\n\n" version += "Environment: " + runtime.Version() + " " + runtime.GOOS + "/" + runtime.GOARCH + "\n" var tags string diff --git a/cmd/sing-box/generate_completions.go b/cmd/sing-box/generate_completions.go new file mode 100644 index 0000000000..6ab0cade9e --- /dev/null +++ b/cmd/sing-box/generate_completions.go @@ -0,0 +1,28 @@ +//go:build generate && generate_completions + +package main + +import "github.com/sagernet/sing-box/log" + +func main() { + err := generateCompletions() + if err != nil { + log.Fatal(err) + } +} + +func generateCompletions() error { + err := mainCommand.GenBashCompletionFile("release/completions/sing-box.bash") + if err != nil { + return err + } + err = mainCommand.GenFishCompletionFile("release/completions/sing-box.fish", true) + if err != nil { + return err + } + err = mainCommand.GenZshCompletionFile("release/completions/sing-box.zsh") + if err != nil { + return err + } + return nil +} diff --git a/cmd/sing-box/main.go b/cmd/sing-box/main.go index 66b7daa1d9..fd55c7d319 100644 --- a/cmd/sing-box/main.go +++ b/cmd/sing-box/main.go @@ -1,74 +1,11 @@ -package main - -import ( - "context" - "os" - "os/user" - "strconv" - "time" - - _ "github.com/sagernet/sing-box/include" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing/service/filemanager" - - "github.com/spf13/cobra" -) +//go:build !generate -var ( - globalCtx context.Context - configPaths []string - configDirectories []string - workingDir string - disableColor bool -) - -var mainCommand = &cobra.Command{ - Use: "sing-box", - PersistentPreRun: preRun, -} +package main -func init() { - mainCommand.PersistentFlags().StringArrayVarP(&configPaths, "config", "c", nil, "set configuration file path") - mainCommand.PersistentFlags().StringArrayVarP(&configDirectories, "config-directory", "C", nil, "set configuration directory path") - mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory") - mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output") -} +import "github.com/sagernet/sing-box/log" func main() { if err := mainCommand.Execute(); err != nil { log.Fatal(err) } } - -func preRun(cmd *cobra.Command, args []string) { - globalCtx = context.Background() - sudoUser := os.Getenv("SUDO_USER") - sudoUID, _ := strconv.Atoi(os.Getenv("SUDO_UID")) - sudoGID, _ := strconv.Atoi(os.Getenv("SUDO_GID")) - if sudoUID == 0 && sudoGID == 0 && sudoUser != "" { - sudoUserObject, _ := user.Lookup(sudoUser) - if sudoUserObject != nil { - sudoUID, _ = strconv.Atoi(sudoUserObject.Uid) - sudoGID, _ = strconv.Atoi(sudoUserObject.Gid) - } - } - if sudoUID > 0 && sudoGID > 0 { - globalCtx = filemanager.WithDefault(globalCtx, "", "", sudoUID, sudoGID) - } - if disableColor { - log.SetStdLogger(log.NewDefaultFactory(context.Background(), log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, "", nil, false).Logger()) - } - if workingDir != "" { - _, err := os.Stat(workingDir) - if err != nil { - filemanager.MkdirAll(globalCtx, workingDir, 0o777) - } - err = os.Chdir(workingDir) - if err != nil { - log.Fatal(err) - } - } - if len(configPaths) == 0 && len(configDirectories) == 0 { - configPaths = append(configPaths, "config.json") - } -} diff --git a/common/badtls/raw_conn.go b/common/badtls/raw_conn.go new file mode 100644 index 0000000000..774e39a580 --- /dev/null +++ b/common/badtls/raw_conn.go @@ -0,0 +1,176 @@ +//go:build go1.25 && badlinkname + +package badtls + +import ( + "bytes" + "os" + "reflect" + "sync/atomic" + "unsafe" + + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/tls" +) + +type RawConn struct { + pointer unsafe.Pointer + methods *Methods + + IsClient *bool + IsHandshakeComplete *atomic.Bool + Vers *uint16 + CipherSuite *uint16 + + RawInput *bytes.Buffer + Input *bytes.Reader + Hand *bytes.Buffer + + CloseNotifySent *bool + CloseNotifyErr *error + + In *RawHalfConn + Out *RawHalfConn + + BytesSent *int64 + PacketsSent *int64 + + ActiveCall *atomic.Int32 + Tmp *[16]byte +} + +func NewRawConn(rawTLSConn tls.Conn) (*RawConn, error) { + var ( + pointer unsafe.Pointer + methods *Methods + loaded bool + ) + for _, tlsCreator := range methodRegistry { + pointer, methods, loaded = tlsCreator(rawTLSConn) + if loaded { + break + } + } + if !loaded { + return nil, os.ErrInvalid + } + + conn := &RawConn{ + pointer: pointer, + methods: methods, + } + + rawConn := reflect.Indirect(reflect.ValueOf(rawTLSConn)) + + rawIsClient := rawConn.FieldByName("isClient") + if !rawIsClient.IsValid() || rawIsClient.Kind() != reflect.Bool { + return nil, E.New("invalid Conn.isClient") + } + conn.IsClient = (*bool)(unsafe.Pointer(rawIsClient.UnsafeAddr())) + + rawIsHandshakeComplete := rawConn.FieldByName("isHandshakeComplete") + if !rawIsHandshakeComplete.IsValid() || rawIsHandshakeComplete.Kind() != reflect.Struct { + return nil, E.New("invalid Conn.isHandshakeComplete") + } + conn.IsHandshakeComplete = (*atomic.Bool)(unsafe.Pointer(rawIsHandshakeComplete.UnsafeAddr())) + + rawVers := rawConn.FieldByName("vers") + if !rawVers.IsValid() || rawVers.Kind() != reflect.Uint16 { + return nil, E.New("invalid Conn.vers") + } + conn.Vers = (*uint16)(unsafe.Pointer(rawVers.UnsafeAddr())) + + rawCipherSuite := rawConn.FieldByName("cipherSuite") + if !rawCipherSuite.IsValid() || rawCipherSuite.Kind() != reflect.Uint16 { + return nil, E.New("invalid Conn.cipherSuite") + } + conn.CipherSuite = (*uint16)(unsafe.Pointer(rawCipherSuite.UnsafeAddr())) + + rawRawInput := rawConn.FieldByName("rawInput") + if !rawRawInput.IsValid() || rawRawInput.Kind() != reflect.Struct { + return nil, E.New("invalid Conn.rawInput") + } + conn.RawInput = (*bytes.Buffer)(unsafe.Pointer(rawRawInput.UnsafeAddr())) + + rawInput := rawConn.FieldByName("input") + if !rawInput.IsValid() || rawInput.Kind() != reflect.Struct { + return nil, E.New("invalid Conn.input") + } + conn.Input = (*bytes.Reader)(unsafe.Pointer(rawInput.UnsafeAddr())) + + rawHand := rawConn.FieldByName("hand") + if !rawHand.IsValid() || rawHand.Kind() != reflect.Struct { + return nil, E.New("invalid Conn.hand") + } + conn.Hand = (*bytes.Buffer)(unsafe.Pointer(rawHand.UnsafeAddr())) + + rawCloseNotifySent := rawConn.FieldByName("closeNotifySent") + if !rawCloseNotifySent.IsValid() || rawCloseNotifySent.Kind() != reflect.Bool { + return nil, E.New("invalid Conn.closeNotifySent") + } + conn.CloseNotifySent = (*bool)(unsafe.Pointer(rawCloseNotifySent.UnsafeAddr())) + + rawCloseNotifyErr := rawConn.FieldByName("closeNotifyErr") + if !rawCloseNotifyErr.IsValid() || rawCloseNotifyErr.Kind() != reflect.Interface { + return nil, E.New("invalid Conn.closeNotifyErr") + } + conn.CloseNotifyErr = (*error)(unsafe.Pointer(rawCloseNotifyErr.UnsafeAddr())) + + rawIn := rawConn.FieldByName("in") + if !rawIn.IsValid() || rawIn.Kind() != reflect.Struct { + return nil, E.New("invalid Conn.in") + } + halfIn, err := NewRawHalfConn(rawIn, methods) + if err != nil { + return nil, E.Cause(err, "invalid Conn.in") + } + conn.In = halfIn + + rawOut := rawConn.FieldByName("out") + if !rawOut.IsValid() || rawOut.Kind() != reflect.Struct { + return nil, E.New("invalid Conn.out") + } + halfOut, err := NewRawHalfConn(rawOut, methods) + if err != nil { + return nil, E.Cause(err, "invalid Conn.out") + } + conn.Out = halfOut + + rawBytesSent := rawConn.FieldByName("bytesSent") + if !rawBytesSent.IsValid() || rawBytesSent.Kind() != reflect.Int64 { + return nil, E.New("invalid Conn.bytesSent") + } + conn.BytesSent = (*int64)(unsafe.Pointer(rawBytesSent.UnsafeAddr())) + + rawPacketsSent := rawConn.FieldByName("packetsSent") + if !rawPacketsSent.IsValid() || rawPacketsSent.Kind() != reflect.Int64 { + return nil, E.New("invalid Conn.packetsSent") + } + conn.PacketsSent = (*int64)(unsafe.Pointer(rawPacketsSent.UnsafeAddr())) + + rawActiveCall := rawConn.FieldByName("activeCall") + if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Struct { + return nil, E.New("invalid Conn.activeCall") + } + conn.ActiveCall = (*atomic.Int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr())) + + rawTmp := rawConn.FieldByName("tmp") + if !rawTmp.IsValid() || rawTmp.Kind() != reflect.Array || rawTmp.Len() != 16 || rawTmp.Type().Elem().Kind() != reflect.Uint8 { + return nil, E.New("invalid Conn.tmp") + } + conn.Tmp = (*[16]byte)(unsafe.Pointer(rawTmp.UnsafeAddr())) + + return conn, nil +} + +func (c *RawConn) ReadRecord() error { + return c.methods.readRecord(c.pointer) +} + +func (c *RawConn) HandlePostHandshakeMessage() error { + return c.methods.handlePostHandshakeMessage(c.pointer) +} + +func (c *RawConn) WriteRecordLocked(typ uint16, data []byte) (int, error) { + return c.methods.writeRecordLocked(c.pointer, typ, data) +} diff --git a/common/badtls/raw_half_conn.go b/common/badtls/raw_half_conn.go new file mode 100644 index 0000000000..4d2c8b64a1 --- /dev/null +++ b/common/badtls/raw_half_conn.go @@ -0,0 +1,121 @@ +//go:build go1.25 && badlinkname + +package badtls + +import ( + "hash" + "reflect" + "sync" + "unsafe" + + E "github.com/sagernet/sing/common/exceptions" +) + +type RawHalfConn struct { + pointer unsafe.Pointer + methods *Methods + *sync.Mutex + Err *error + Version *uint16 + Cipher *any + Seq *[8]byte + ScratchBuf *[13]byte + TrafficSecret *[]byte + Mac *hash.Hash + RawKey *[]byte + RawIV *[]byte + RawMac *[]byte +} + +func NewRawHalfConn(rawHalfConn reflect.Value, methods *Methods) (*RawHalfConn, error) { + halfConn := &RawHalfConn{ + pointer: (unsafe.Pointer)(rawHalfConn.UnsafeAddr()), + methods: methods, + } + + rawMutex := rawHalfConn.FieldByName("Mutex") + if !rawMutex.IsValid() || rawMutex.Kind() != reflect.Struct { + return nil, E.New("badtls: invalid halfConn.Mutex") + } + halfConn.Mutex = (*sync.Mutex)(unsafe.Pointer(rawMutex.UnsafeAddr())) + + rawErr := rawHalfConn.FieldByName("err") + if !rawErr.IsValid() || rawErr.Kind() != reflect.Interface { + return nil, E.New("badtls: invalid halfConn.err") + } + halfConn.Err = (*error)(unsafe.Pointer(rawErr.UnsafeAddr())) + + rawVersion := rawHalfConn.FieldByName("version") + if !rawVersion.IsValid() || rawVersion.Kind() != reflect.Uint16 { + return nil, E.New("badtls: invalid halfConn.version") + } + halfConn.Version = (*uint16)(unsafe.Pointer(rawVersion.UnsafeAddr())) + + rawCipher := rawHalfConn.FieldByName("cipher") + if !rawCipher.IsValid() || rawCipher.Kind() != reflect.Interface { + return nil, E.New("badtls: invalid halfConn.cipher") + } + halfConn.Cipher = (*any)(unsafe.Pointer(rawCipher.UnsafeAddr())) + + rawSeq := rawHalfConn.FieldByName("seq") + if !rawSeq.IsValid() || rawSeq.Kind() != reflect.Array || rawSeq.Len() != 8 || rawSeq.Type().Elem().Kind() != reflect.Uint8 { + return nil, E.New("badtls: invalid halfConn.seq") + } + halfConn.Seq = (*[8]byte)(unsafe.Pointer(rawSeq.UnsafeAddr())) + + rawScratchBuf := rawHalfConn.FieldByName("scratchBuf") + if !rawScratchBuf.IsValid() || rawScratchBuf.Kind() != reflect.Array || rawScratchBuf.Len() != 13 || rawScratchBuf.Type().Elem().Kind() != reflect.Uint8 { + return nil, E.New("badtls: invalid halfConn.scratchBuf") + } + halfConn.ScratchBuf = (*[13]byte)(unsafe.Pointer(rawScratchBuf.UnsafeAddr())) + + rawTrafficSecret := rawHalfConn.FieldByName("trafficSecret") + if !rawTrafficSecret.IsValid() || rawTrafficSecret.Kind() != reflect.Slice || rawTrafficSecret.Type().Elem().Kind() != reflect.Uint8 { + return nil, E.New("badtls: invalid halfConn.trafficSecret") + } + halfConn.TrafficSecret = (*[]byte)(unsafe.Pointer(rawTrafficSecret.UnsafeAddr())) + + rawMac := rawHalfConn.FieldByName("mac") + if !rawMac.IsValid() || rawMac.Kind() != reflect.Interface { + return nil, E.New("badtls: invalid halfConn.mac") + } + halfConn.Mac = (*hash.Hash)(unsafe.Pointer(rawMac.UnsafeAddr())) + + rawKey := rawHalfConn.FieldByName("rawKey") + if rawKey.IsValid() { + if /*!rawKey.IsValid() || */ rawKey.Kind() != reflect.Slice || rawKey.Type().Elem().Kind() != reflect.Uint8 { + return nil, E.New("badtls: invalid halfConn.rawKey") + } + halfConn.RawKey = (*[]byte)(unsafe.Pointer(rawKey.UnsafeAddr())) + + rawIV := rawHalfConn.FieldByName("rawIV") + if !rawIV.IsValid() || rawIV.Kind() != reflect.Slice || rawIV.Type().Elem().Kind() != reflect.Uint8 { + return nil, E.New("badtls: invalid halfConn.rawIV") + } + halfConn.RawIV = (*[]byte)(unsafe.Pointer(rawIV.UnsafeAddr())) + + rawMAC := rawHalfConn.FieldByName("rawMac") + if !rawMAC.IsValid() || rawMAC.Kind() != reflect.Slice || rawMAC.Type().Elem().Kind() != reflect.Uint8 { + return nil, E.New("badtls: invalid halfConn.rawMac") + } + halfConn.RawMac = (*[]byte)(unsafe.Pointer(rawMAC.UnsafeAddr())) + } + + return halfConn, nil +} + +func (hc *RawHalfConn) Decrypt(record []byte) ([]byte, uint8, error) { + return hc.methods.decrypt(hc.pointer, record) +} + +func (hc *RawHalfConn) SetErrorLocked(err error) error { + return hc.methods.setErrorLocked(hc.pointer, err) +} + +func (hc *RawHalfConn) SetTrafficSecret(suite unsafe.Pointer, level int, secret []byte) { + hc.methods.setTrafficSecret(hc.pointer, suite, level, secret) +} + +func (hc *RawHalfConn) ExplicitNonceLen() int { + return hc.methods.explicitNonceLen(hc.pointer) +} diff --git a/common/badtls/read_wait.go b/common/badtls/read_wait.go index 334bcfa81e..8448b1a247 100644 --- a/common/badtls/read_wait.go +++ b/common/badtls/read_wait.go @@ -1,18 +1,9 @@ -//go:build go1.21 && !without_badtls +//go:build go1.25 && badlinkname package badtls import ( - "bytes" - "context" - "net" - "os" - "reflect" - "sync" - "unsafe" - "github.com/sagernet/sing/common/buf" - E "github.com/sagernet/sing/common/exceptions" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/tls" ) @@ -21,63 +12,21 @@ var _ N.ReadWaiter = (*ReadWaitConn)(nil) type ReadWaitConn struct { tls.Conn - halfAccess *sync.Mutex - rawInput *bytes.Buffer - input *bytes.Reader - hand *bytes.Buffer - readWaitOptions N.ReadWaitOptions - tlsReadRecord func() error - tlsHandlePostHandshakeMessage func() error + rawConn *RawConn + readWaitOptions N.ReadWaitOptions } func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) { - var ( - loaded bool - tlsReadRecord func() error - tlsHandlePostHandshakeMessage func() error - ) - for _, tlsCreator := range tlsRegistry { - loaded, tlsReadRecord, tlsHandlePostHandshakeMessage = tlsCreator(conn) - if loaded { - break - } - } - if !loaded { - return nil, os.ErrInvalid - } - rawConn := reflect.Indirect(reflect.ValueOf(conn)) - rawHalfConn := rawConn.FieldByName("in") - if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct { - return nil, E.New("badtls: invalid half conn") - } - rawHalfMutex := rawHalfConn.FieldByName("Mutex") - if !rawHalfMutex.IsValid() || rawHalfMutex.Kind() != reflect.Struct { - return nil, E.New("badtls: invalid half mutex") + if _, isReadWaitConn := conn.(N.ReadWaiter); isReadWaitConn { + return conn, nil } - halfAccess := (*sync.Mutex)(unsafe.Pointer(rawHalfMutex.UnsafeAddr())) - rawRawInput := rawConn.FieldByName("rawInput") - if !rawRawInput.IsValid() || rawRawInput.Kind() != reflect.Struct { - return nil, E.New("badtls: invalid raw input") - } - rawInput := (*bytes.Buffer)(unsafe.Pointer(rawRawInput.UnsafeAddr())) - rawInput0 := rawConn.FieldByName("input") - if !rawInput0.IsValid() || rawInput0.Kind() != reflect.Struct { - return nil, E.New("badtls: invalid input") - } - input := (*bytes.Reader)(unsafe.Pointer(rawInput0.UnsafeAddr())) - rawHand := rawConn.FieldByName("hand") - if !rawHand.IsValid() || rawHand.Kind() != reflect.Struct { - return nil, E.New("badtls: invalid hand") + rawConn, err := NewRawConn(conn) + if err != nil { + return nil, err } - hand := (*bytes.Buffer)(unsafe.Pointer(rawHand.UnsafeAddr())) return &ReadWaitConn{ - Conn: conn, - halfAccess: halfAccess, - rawInput: rawInput, - input: input, - hand: hand, - tlsReadRecord: tlsReadRecord, - tlsHandlePostHandshakeMessage: tlsHandlePostHandshakeMessage, + Conn: conn, + rawConn: rawConn, }, nil } @@ -87,36 +36,36 @@ func (c *ReadWaitConn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy } func (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) { - err = c.HandshakeContext(context.Background()) - if err != nil { - return - } - c.halfAccess.Lock() - defer c.halfAccess.Unlock() - for c.input.Len() == 0 { - err = c.tlsReadRecord() + //err = c.HandshakeContext(context.Background()) + //if err != nil { + // return + //} + c.rawConn.In.Lock() + defer c.rawConn.In.Unlock() + for c.rawConn.Input.Len() == 0 { + err = c.rawConn.ReadRecord() if err != nil { return } - for c.hand.Len() > 0 { - err = c.tlsHandlePostHandshakeMessage() + for c.rawConn.Hand.Len() > 0 { + err = c.rawConn.HandlePostHandshakeMessage() if err != nil { return } } } buffer = c.readWaitOptions.NewBuffer() - n, err := c.input.Read(buffer.FreeBytes()) + n, err := c.rawConn.Input.Read(buffer.FreeBytes()) if err != nil { buffer.Release() return } buffer.Truncate(n) - if n != 0 && c.input.Len() == 0 && c.rawInput.Len() > 0 && - // recordType(c.rawInput.Bytes()[0]) == recordTypeAlert { - c.rawInput.Bytes()[0] == 21 { - _ = c.tlsReadRecord() + if n != 0 && c.rawConn.Input.Len() == 0 && c.rawConn.Input.Len() > 0 && + // recordType(c.RawInput.Bytes()[0]) == recordTypeAlert { + c.rawConn.RawInput.Bytes()[0] == 21 { + _ = c.rawConn.ReadRecord() // return n, err // will be io.EOF on closeNotify } @@ -128,24 +77,6 @@ func (c *ReadWaitConn) Upstream() any { return c.Conn } -var tlsRegistry []func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) - -func init() { - tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) { - tlsConn, loaded := conn.(*tls.STDConn) - if !loaded { - return - } - return true, func() error { - return stdTLSReadRecord(tlsConn) - }, func() error { - return stdTLSHandlePostHandshakeMessage(tlsConn) - } - }) +func (c *ReadWaitConn) ReaderReplaceable() bool { + return true } - -//go:linkname stdTLSReadRecord crypto/tls.(*Conn).readRecord -func stdTLSReadRecord(c *tls.STDConn) error - -//go:linkname stdTLSHandlePostHandshakeMessage crypto/tls.(*Conn).handlePostHandshakeMessage -func stdTLSHandlePostHandshakeMessage(c *tls.STDConn) error diff --git a/common/badtls/read_wait_ech.go b/common/badtls/read_wait_ech.go deleted file mode 100644 index 6a0d5b5ff8..0000000000 --- a/common/badtls/read_wait_ech.go +++ /dev/null @@ -1,31 +0,0 @@ -//go:build go1.21 && !without_badtls && with_ech - -package badtls - -import ( - "net" - _ "unsafe" - - "github.com/sagernet/cloudflare-tls" - "github.com/sagernet/sing/common" -) - -func init() { - tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) { - tlsConn, loaded := common.Cast[*tls.Conn](conn) - if !loaded { - return - } - return true, func() error { - return echReadRecord(tlsConn) - }, func() error { - return echHandlePostHandshakeMessage(tlsConn) - } - }) -} - -//go:linkname echReadRecord github.com/sagernet/cloudflare-tls.(*Conn).readRecord -func echReadRecord(c *tls.Conn) error - -//go:linkname echHandlePostHandshakeMessage github.com/sagernet/cloudflare-tls.(*Conn).handlePostHandshakeMessage -func echHandlePostHandshakeMessage(c *tls.Conn) error diff --git a/common/badtls/read_wait_stub.go b/common/badtls/read_wait_stub.go index c5c9946f8b..9258a46e59 100644 --- a/common/badtls/read_wait_stub.go +++ b/common/badtls/read_wait_stub.go @@ -1,4 +1,4 @@ -//go:build !go1.21 || without_badtls +//go:build !go1.25 || !badlinkname package badtls diff --git a/common/badtls/read_wait_utls.go b/common/badtls/read_wait_utls.go deleted file mode 100644 index ebdb2251a9..0000000000 --- a/common/badtls/read_wait_utls.go +++ /dev/null @@ -1,31 +0,0 @@ -//go:build go1.21 && !without_badtls && with_utls - -package badtls - -import ( - "net" - _ "unsafe" - - "github.com/sagernet/sing/common" - "github.com/sagernet/utls" -) - -func init() { - tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) { - tlsConn, loaded := common.Cast[*tls.UConn](conn) - if !loaded { - return - } - return true, func() error { - return utlsReadRecord(tlsConn.Conn) - }, func() error { - return utlsHandlePostHandshakeMessage(tlsConn.Conn) - } - }) -} - -//go:linkname utlsReadRecord github.com/sagernet/utls.(*Conn).readRecord -func utlsReadRecord(c *tls.Conn) error - -//go:linkname utlsHandlePostHandshakeMessage github.com/sagernet/utls.(*Conn).handlePostHandshakeMessage -func utlsHandlePostHandshakeMessage(c *tls.Conn) error diff --git a/common/badtls/registry.go b/common/badtls/registry.go new file mode 100644 index 0000000000..34cfe9ecaf --- /dev/null +++ b/common/badtls/registry.go @@ -0,0 +1,62 @@ +//go:build go1.25 && badlinkname + +package badtls + +import ( + "crypto/tls" + "net" + "unsafe" +) + +type Methods struct { + readRecord func(c unsafe.Pointer) error + handlePostHandshakeMessage func(c unsafe.Pointer) error + writeRecordLocked func(c unsafe.Pointer, typ uint16, data []byte) (int, error) + + setErrorLocked func(hc unsafe.Pointer, err error) error + decrypt func(hc unsafe.Pointer, record []byte) ([]byte, uint8, error) + setTrafficSecret func(hc unsafe.Pointer, suite unsafe.Pointer, level int, secret []byte) + explicitNonceLen func(hc unsafe.Pointer) int +} + +var methodRegistry []func(conn net.Conn) (unsafe.Pointer, *Methods, bool) + +func init() { + methodRegistry = append(methodRegistry, func(conn net.Conn) (unsafe.Pointer, *Methods, bool) { + tlsConn, loaded := conn.(*tls.Conn) + if !loaded { + return nil, nil, false + } + return unsafe.Pointer(tlsConn), &Methods{ + readRecord: stdTLSReadRecord, + handlePostHandshakeMessage: stdTLSHandlePostHandshakeMessage, + writeRecordLocked: stdWriteRecordLocked, + + setErrorLocked: stdSetErrorLocked, + decrypt: stdDecrypt, + setTrafficSecret: stdSetTrafficSecret, + explicitNonceLen: stdExplicitNonceLen, + }, true + }) +} + +//go:linkname stdTLSReadRecord crypto/tls.(*Conn).readRecord +func stdTLSReadRecord(c unsafe.Pointer) error + +//go:linkname stdTLSHandlePostHandshakeMessage crypto/tls.(*Conn).handlePostHandshakeMessage +func stdTLSHandlePostHandshakeMessage(c unsafe.Pointer) error + +//go:linkname stdWriteRecordLocked crypto/tls.(*Conn).writeRecordLocked +func stdWriteRecordLocked(c unsafe.Pointer, typ uint16, data []byte) (int, error) + +//go:linkname stdSetErrorLocked crypto/tls.(*halfConn).setErrorLocked +func stdSetErrorLocked(hc unsafe.Pointer, err error) error + +//go:linkname stdDecrypt crypto/tls.(*halfConn).decrypt +func stdDecrypt(hc unsafe.Pointer, record []byte) ([]byte, uint8, error) + +//go:linkname stdSetTrafficSecret crypto/tls.(*halfConn).setTrafficSecret +func stdSetTrafficSecret(hc unsafe.Pointer, suite unsafe.Pointer, level int, secret []byte) + +//go:linkname stdExplicitNonceLen crypto/tls.(*halfConn).explicitNonceLen +func stdExplicitNonceLen(hc unsafe.Pointer) int diff --git a/common/badtls/registry_utls.go b/common/badtls/registry_utls.go new file mode 100644 index 0000000000..330f64f58e --- /dev/null +++ b/common/badtls/registry_utls.go @@ -0,0 +1,56 @@ +//go:build go1.25 && badlinkname + +package badtls + +import ( + "net" + "unsafe" + + N "github.com/sagernet/sing/common/network" + + "github.com/metacubex/utls" +) + +func init() { + methodRegistry = append(methodRegistry, func(conn net.Conn) (unsafe.Pointer, *Methods, bool) { + var pointer unsafe.Pointer + if uConn, loaded := N.CastReader[*tls.Conn](conn); loaded { + pointer = unsafe.Pointer(uConn) + } else if uConn, loaded := N.CastReader[*tls.UConn](conn); loaded { + pointer = unsafe.Pointer(uConn.Conn) + } else { + return nil, nil, false + } + return pointer, &Methods{ + readRecord: utlsReadRecord, + handlePostHandshakeMessage: utlsHandlePostHandshakeMessage, + writeRecordLocked: utlsWriteRecordLocked, + + setErrorLocked: utlsSetErrorLocked, + decrypt: utlsDecrypt, + setTrafficSecret: utlsSetTrafficSecret, + explicitNonceLen: utlsExplicitNonceLen, + }, true + }) +} + +//go:linkname utlsReadRecord github.com/metacubex/utls.(*Conn).readRecord +func utlsReadRecord(c unsafe.Pointer) error + +//go:linkname utlsHandlePostHandshakeMessage github.com/metacubex/utls.(*Conn).handlePostHandshakeMessage +func utlsHandlePostHandshakeMessage(c unsafe.Pointer) error + +//go:linkname utlsWriteRecordLocked github.com/metacubex/utls.(*Conn).writeRecordLocked +func utlsWriteRecordLocked(hc unsafe.Pointer, typ uint16, data []byte) (int, error) + +//go:linkname utlsSetErrorLocked github.com/metacubex/utls.(*halfConn).setErrorLocked +func utlsSetErrorLocked(hc unsafe.Pointer, err error) error + +//go:linkname utlsDecrypt github.com/metacubex/utls.(*halfConn).decrypt +func utlsDecrypt(hc unsafe.Pointer, record []byte) ([]byte, uint8, error) + +//go:linkname utlsSetTrafficSecret github.com/metacubex/utls.(*halfConn).setTrafficSecret +func utlsSetTrafficSecret(hc unsafe.Pointer, suite unsafe.Pointer, level int, secret []byte) + +//go:linkname utlsExplicitNonceLen github.com/metacubex/utls.(*halfConn).explicitNonceLen +func utlsExplicitNonceLen(hc unsafe.Pointer) int diff --git a/common/badversion/version.go b/common/badversion/version.go index ccff02a696..3da6766c64 100644 --- a/common/badversion/version.go +++ b/common/badversion/version.go @@ -5,6 +5,8 @@ import ( "strings" F "github.com/sagernet/sing/common/format" + + "golang.org/x/mod/semver" ) type Version struct { @@ -16,7 +18,19 @@ type Version struct { PreReleaseVersion int } -func (v Version) After(anotherVersion Version) bool { +func (v Version) LessThan(anotherVersion Version) bool { + return !v.GreaterThanOrEqual(anotherVersion) +} + +func (v Version) LessThanOrEqual(anotherVersion Version) bool { + return v == anotherVersion || anotherVersion.GreaterThan(v) +} + +func (v Version) GreaterThanOrEqual(anotherVersion Version) bool { + return v == anotherVersion || v.GreaterThan(anotherVersion) +} + +func (v Version) GreaterThan(anotherVersion Version) bool { if v.Major > anotherVersion.Major { return true } else if v.Major < anotherVersion.Major { @@ -44,19 +58,29 @@ func (v Version) After(anotherVersion Version) bool { } else if v.PreReleaseVersion < anotherVersion.PreReleaseVersion { return false } - } else if v.PreReleaseIdentifier == "rc" && anotherVersion.PreReleaseIdentifier == "beta" { - return true - } else if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "rc" { - return false - } else if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "alpha" { + } + preReleaseIdentifier := parsePreReleaseIdentifier(v.PreReleaseIdentifier) + anotherPreReleaseIdentifier := parsePreReleaseIdentifier(anotherVersion.PreReleaseIdentifier) + if preReleaseIdentifier < anotherPreReleaseIdentifier { return true - } else if v.PreReleaseIdentifier == "alpha" && anotherVersion.PreReleaseIdentifier == "beta" { + } else if preReleaseIdentifier > anotherPreReleaseIdentifier { return false } } return false } +func parsePreReleaseIdentifier(identifier string) int { + if strings.HasPrefix(identifier, "rc") { + return 1 + } else if strings.HasPrefix(identifier, "beta") { + return 2 + } else if strings.HasPrefix(identifier, "alpha") { + return 3 + } + return 0 +} + func (v Version) VersionString() string { return F.ToString(v.Major, ".", v.Minor, ".", v.Patch) } @@ -83,10 +107,12 @@ func (v Version) BadString() string { return version } +func IsValid(versionName string) bool { + return semver.IsValid("v" + versionName) +} + func Parse(versionName string) (version Version) { - if strings.HasPrefix(versionName, "v") { - versionName = versionName[1:] - } + versionName = strings.TrimPrefix(versionName, "v") if strings.Contains(versionName, "-") { parts := strings.Split(versionName, "-") versionName = parts[0] diff --git a/common/badversion/version_test.go b/common/badversion/version_test.go index 9d6e8a7c21..d6d5a73c81 100644 --- a/common/badversion/version_test.go +++ b/common/badversion/version_test.go @@ -10,9 +10,9 @@ func TestCompareVersion(t *testing.T) { t.Parallel() require.Equal(t, "1.3.0-beta.1", Parse("v1.3.0-beta1").String()) require.Equal(t, "1.3-beta1", Parse("v1.3.0-beta.1").BadString()) - require.True(t, Parse("1.3.0").After(Parse("1.3-beta1"))) - require.True(t, Parse("1.3.0").After(Parse("1.3.0-beta1"))) - require.True(t, Parse("1.3.0-beta1").After(Parse("1.3.0-alpha1"))) - require.True(t, Parse("1.3.1").After(Parse("1.3.0"))) - require.True(t, Parse("1.4").After(Parse("1.3"))) + require.True(t, Parse("1.3.0").GreaterThan(Parse("1.3-beta1"))) + require.True(t, Parse("1.3.0").GreaterThan(Parse("1.3.0-beta1"))) + require.True(t, Parse("1.3.0-beta1").GreaterThan(Parse("1.3.0-alpha1"))) + require.True(t, Parse("1.3.1").GreaterThan(Parse("1.3.0"))) + require.True(t, Parse("1.4").GreaterThan(Parse("1.3"))) } diff --git a/common/certificate/anchors_darwin.h b/common/certificate/anchors_darwin.h new file mode 100644 index 0000000000..f535f5ca83 --- /dev/null +++ b/common/certificate/anchors_darwin.h @@ -0,0 +1,17 @@ +#ifndef BOX_CERTIFICATE_ANCHORS_DARWIN_H +#define BOX_CERTIFICATE_ANCHORS_DARWIN_H + +#include +#include + +// box_certificate_anchors_from_der wraps an array of DER-encoded certificate +// blobs into a retained CFArrayRef of SecCertificateRef, returned as an opaque +// pointer. The caller owns the returned reference and must call +// box_certificate_release_anchors. Returns NULL when no blobs were accepted. +void *box_certificate_anchors_from_der(const uint8_t *const *ders, const size_t *lens, size_t count); + +// box_certificate_release_anchors drops one reference from a CFArray handle +// previously returned by box_certificate_anchors_from_der. No-op on NULL. +void box_certificate_release_anchors(void *anchors); + +#endif diff --git a/common/certificate/anchors_darwin.m b/common/certificate/anchors_darwin.m new file mode 100644 index 0000000000..e9f471c67c --- /dev/null +++ b/common/certificate/anchors_darwin.m @@ -0,0 +1,42 @@ +#import "anchors_darwin.h" + +#import +#import + +void *box_certificate_anchors_from_der(const uint8_t *const *ders, const size_t *lens, size_t count) { + if (count == 0 || ders == NULL || lens == NULL) { + return NULL; + } + CFMutableArrayRef certificates = CFArrayCreateMutable(NULL, (CFIndex)count, &kCFTypeArrayCallBacks); + if (certificates == NULL) { + return NULL; + } + for (size_t index = 0; index < count; index++) { + if (ders[index] == NULL || lens[index] == 0) { + continue; + } + CFDataRef data = CFDataCreate(NULL, ders[index], (CFIndex)lens[index]); + if (data == NULL) { + continue; + } + SecCertificateRef certificate = SecCertificateCreateWithData(NULL, data); + CFRelease(data); + if (certificate == NULL) { + continue; + } + CFArrayAppendValue(certificates, certificate); + CFRelease(certificate); + } + if (CFArrayGetCount(certificates) == 0) { + CFRelease(certificates); + return NULL; + } + return certificates; +} + +void box_certificate_release_anchors(void *anchors) { + if (anchors == NULL) { + return; + } + CFRelease((CFTypeRef)anchors); +} diff --git a/common/certificate/chrome.go b/common/certificate/chrome.go new file mode 100644 index 0000000000..220462561b --- /dev/null +++ b/common/certificate/chrome.go @@ -0,0 +1,2574 @@ +// Code generated by 'make update_certificates'. DO NOT EDIT. + +package certificate + +func chromeIncludedPEM() string { + return ` +// CN=Actalis Authentication Root CA; O=Actalis S.p.A./03358520967; L=Milan; C=IT +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE +BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w +MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC +SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1 +ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv +UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX +4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9 +KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/ +gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb +rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ +51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F +be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe +KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F +v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn +fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7 +jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz +ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL +e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70 +jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz +WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V +SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j +pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX +X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok +fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R +K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU +ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU +LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT +LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +// CN=TunTrust Root CA; O=Agence Nationale de Certification Electronique; C=TN +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL +BQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg +Q2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv +b3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQwNDI2MDg1NzU2WjBhMQswCQYDVQQG +EwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBDZXJ0aWZpY2F0aW9u +IEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZ +n56eY+hz2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd +2JQDoOw05TDENX37Jk0bbjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgF +VwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZ +GoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAdgjH8KcwAWJeRTIAAHDOF +li/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViWVSHbhlnU +r8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2 +eY8fTpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIb +MlEsPvLfe/ZdeikZjuXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISg +jwBUFfyRbVinljvrS5YnzWuioYasDXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB +7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwSVXAkPcvCFDVDXSdOvsC9qnyW +5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI04Y+oXNZtPdE +ITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0 +90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+z +xiD2BkewhpMl0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYu +QEkHDVneixCwSQXi/5E/S7fdAo74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4 +FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRYYdZ2vyJ/0Adqp2RT8JeNnYA/u8EH +22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJpadbGNjHh/PqAulxP +xOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65xxBzn +dFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5 +Xc0yGYuPjCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7b +nV2UqL1g52KAdoGDDIzMMEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQ +CvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9zZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZH +u/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3rAZ3r2OvEhJn7wAzMMujj +d9qDRIueVSjAi1jTkD5OGwDxFa2DK5o= +-----END CERTIFICATE----- + +// CN=Amazon Root CA 2; O=Amazon; C=US +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK +gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ +W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg +1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K +8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r +2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me +z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR +8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj +mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz +7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6 ++XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI +0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm +UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2 +LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS +k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl +7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm +btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl +urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+ +fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63 +n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE +76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H +9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT +4PsJYGw= +-----END CERTIFICATE----- + +// CN=Amazon Root CA 4; O=Amazon; C=US +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi +9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk +M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB +MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw +CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW +1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- + +// CN=Amazon Root CA 1; O=Amazon; C=US +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj +ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM +9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw +IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 +VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L +93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm +jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA +A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI +U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs +N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv +o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU +5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy +rqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- + +// CN=Amazon Root CA 3; O=Amazon; C=US +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl +ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr +ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr +BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM +YyRIHN8wfdVoOw== +-----END CERTIFICATE----- + +// CN=Certum Trusted Network CA; OU=Certum Certification Authority; O=Unizeto Technologies S.A.; C=PL +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM +MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D +ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU +cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3 +WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg +Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw +IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH +UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM +TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU +BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM +kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x +AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV +HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y +sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL +I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 +J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY +VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +// CN=Certum EC-384 CA; OU=Certum Certification Authority; O=Asseco Data Systems S.A.; C=PL +-----BEGIN CERTIFICATE----- +MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw +CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw +JQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT +EENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2MDcyNDU0WhcNNDMwMzI2MDcyNDU0 +WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBT +LkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAX +BgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATE +KI6rGFtqvm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7Tm +Fy8as10CW4kjPMIRBSqniBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68Kj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI0GZnQkdjrzife81r1HfS+8 +EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjADVS2m5hjEfO/J +UG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0QoSZ/6vn +nvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= +-----END CERTIFICATE----- + +// CN=Certum Trusted Root CA; OU=Certum Certification Authority; O=Asseco Data Systems S.A.; C=PL +-----BEGIN CERTIFICATE----- +MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6 +MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu +MScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNV +BAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwHhcNMTgwMzE2MTIxMDEzWhcNNDMw +MzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEg +U3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZ +n0EGze2jusDbCSzBfN8pfktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/q +p1x4EaTByIVcJdPTsuclzxFUl6s1wB52HO8AU5853BSlLCIls3Jy/I2z5T4IHhQq +NwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2fJmItdUDmj0VDT06qKhF +8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGtg/BKEiJ3 +HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGa +mqi4NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi +7VdNIuJGmj8PkTQkfVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSF +ytKAQd8FqKPVhJBPC/PgP5sZ0jeJP/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0P +qafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSYnjYJdmZm/Bo/6khUHL4wvYBQ +v3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHKHRzQ+8S1h9E6 +Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 +vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQAD +ggIBAEii1QALLtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4 +WxmB82M+w85bj/UvXgF2Ez8sALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvo +zMrnadyHncI013nR03e4qllY/p0m+jiGPp2Kh2RX5Rc64vmNueMzeMGQ2Ljdt4NR +5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8CYyqOhNf6DR5UMEQ +GfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA4kZf +5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq +0Uc9NneoWWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7D +P78v3DSk+yshzWePS/Tj6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTM +qJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmTOPQD8rv7gmsHINFSH5pkAnuYZttcTVoP +0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZckbxJF0WddCajJFdr60qZf +E2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb +-----END CERTIFICATE----- + +// CN=Certum Trusted Network CA 2; OU=Certum Certification Authority; O=Unizeto Technologies S.A.; C=PL +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB +gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu +QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG +A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz +OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ +VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3 +b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA +DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn +0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB +OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE +fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E +Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m +o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i +sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW +OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez +Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS +adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n +3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ +F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf +CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29 +XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm +djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/ +WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb +AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq +P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko +b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj +XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P +5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi +DrW5viSP +-----END CERTIFICATE----- + +// CN=Autoridad de Certificacion Firmaprofesional CIF A62634068; C=ES +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE +BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h +cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1 +MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg +Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9 +thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM +cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG +L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i +NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h +X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b +m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy +Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja +EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T +KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF +6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh +OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1UdDgQWBBRlzeurNR4APn7VdMAc +tHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4wgZswgZgGBFUd +IAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j +b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABC +AG8AbgBhAG4AbwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAw +ADEANzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9m +iWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL4QjbEwj4KKE1soCzC1HA01aajTNF +Sa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDbLIpgD7dvlAceHabJ +hfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1ilI45P +Vf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZE +EAEeiGaPcjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV +1aUsIC+nmCjuRfzxuIgALI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2t +CsvMo2ebKHTEm9caPARYpoKdrcd7b/+Alun4jWq9GJAd/0kakFI3ky88Al2CdgtR +5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH9IBk9W6VULgRfhVwOEqw +f9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpfNIbnYrX9 +ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNK +GbqEZycPvEJdvSRUDewdcAZfpLz6IHxV +-----END CERTIFICATE----- + +// CN=ANF Secure Server Root CA; OU=ANF CA Raiz; O=ANF Autoridad de Certificacion; C=ES; SerialNumber=G63287510 +-----BEGIN CERTIFICATE----- +MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNV +BAUTCUc2MzI4NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlk +YWQgZGUgQ2VydGlmaWNhY2lvbjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNV +BAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3QgQ0EwHhcNMTkwOTA0MTAwMDM4WhcN +MzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEwMQswCQYDVQQGEwJF +UzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQwEgYD +VQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9v +dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCj +cqQZAZ2cC4Ffc0m6p6zzBE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9q +yGFOtibBTI3/TO80sh9l2Ll49a2pcbnvT1gdpd50IJeh7WhM3pIXS7yr/2WanvtH +2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcvB2VSAKduyK9o7PQUlrZX +H1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXsezx76W0OL +zc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyR +p1RMVwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQz +W7i1o0TJrH93PB0j7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/ +SiOL9V8BY9KHcyi1Swr1+KuCLH5zJTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJn +LNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe8TZBAQIvfXOn3kLMTOmJDVb3 +n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVOHj1tyRRM4y5B +u8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj +o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC +AgEATh65isagmD9uw2nAalxJUqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L +9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzxj6ptBZNscsdW699QIyjlRRA96Gej +rw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDtdD+4E5UGUcjohybK +pFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM5gf0 +vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjq +OknkJjCb5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ +/zo1PqVUSlJZS2Db7v54EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ9 +2zg/LFis6ELhDtjTO0wugumDLmsx2d1Hhk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI ++PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGyg77FGr8H6lnco4g175x2 +MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3r5+qPeoo +tt7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw= +-----END CERTIFICATE----- + +// CN=Buypass Class 2 Root CA; O=Buypass AS-983163327; C=NO +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr +6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV +L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91 +1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx +MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ +QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB +arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr +Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi +FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS +P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN +9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz +uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h +9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t +OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo ++fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7 +KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2 +DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us +H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ +I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 +5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h +3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz +Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= +-----END CERTIFICATE----- + +// CN=Buypass Class 3 Root CA; O=Buypass AS-983163327; C=NO +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y +ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E +N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9 +tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX +0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c +/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X +KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY +zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS +O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D +34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP +K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3 +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv +Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj +QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS +IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2 +HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa +O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv +033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u +dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE +kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41 +3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD +u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq +4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= +-----END CERTIFICATE----- + +// CN=Certainly Root R1; O=Certainly; C=US +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw +PTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy +dGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9 +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0 +YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANA2 +1B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O5MQT +vqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbed +aFySpvXl8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b0 +1C7jcvk2xusVtyWMOvwlDbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5 +r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGIXsXwClTNSaa/ApzSRKft43jvRl5tcdF5 +cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkNKPl6I7ENPT2a/Z2B7yyQ +wHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQAjeZjOVJ +6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA +2CnbrlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyH +Wyf5QBGenDPBt+U1VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMR +eiFPCyEQtkA6qyI6BJyLm4SGcprSp6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB +/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTgqj8ljZ9EXME66C6u +d0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAszHQNTVfSVcOQr +PbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d +8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi +1wrykXprOQ4vMMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrd +rRT90+7iIgXr0PK3aBLXWopBGsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9di +taY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+gjwN/KUD+nsa2UUeYNrEjvn8K8l7 +lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgHJBu6haEaBQmAupVj +yTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7fpYn +Kx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLy +yCwzk5Iwx06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5n +wXARPbv0+Em34yaXOp/SX3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6 +OV+KmalBWQewLK8= +-----END CERTIFICATE----- + +// CN=Certainly Root E1; O=Certainly; C=US +-----BEGIN CERTIFICATE----- +MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw +CQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu +bHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ +BgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxGjAYBgNVBAMTEUNlcnRhaW5s +eSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4fxzf7flHh4axpMCK ++IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9YBk2 +QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4 +hevIIgcwCgYIKoZIzj0EAwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozm +ut6Dacpps6kFtZaSF4fC0urQe87YQVt8rgIwRt7qy12a7DLCZRawTDBcMPPaTnOG +BtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR +-----END CERTIFICATE----- + +// CN=Certigna Root CA; OU=0002 48146308100036; O=Dhimyotis; C=FR +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw +WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw +MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x +MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD +VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX +BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO +ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M +CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu +I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm +TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh +C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf +ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz +IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT +Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k +JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5 +hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB +GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov +L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo +dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr +aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq +hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L +6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG +HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6 +0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB +lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi +o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1 +gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v +faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63 +Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh +jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw +3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE----- + +// OU=certSIGN ROOT CA G2; O=CERTSIGN SA; C=RO +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV +BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g +Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ +BgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJ +R04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDF +dRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05N0Iw +vlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZ +uIt4ImfkabBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhp +n+Sc8CnTXPnGFiWeI8MgwT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKs +cpc/I1mbySKEwQdPzH/iV8oScLumZfNpdWO9lfsbl83kqK/20U6o2YpxJM02PbyW +xPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91QqhngLjYl/rNUssuHLoPj1P +rCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732jcZZroiF +DsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fx +DTvf95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgy +LcsUDFDYg2WD7rlcz8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6C +eWRgKRM+o/1Pcmqr4tTluCRVLERLiohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSCIS1mxteg4BXrzkwJ +d8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOBywaK8SJJ6ejq +kX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQl +qiCA2ClV9+BB/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0 +OJD7uNGzcgbJceaBxXntC6Z58hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+c +NywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5BiKDUyUM/FHE5r7iOZULJK2v0ZXk +ltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklWatKcsWMy5WHgUyIO +pwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj +03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZk +PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE +1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX +QRBdJ3NghVdJIgc= +-----END CERTIFICATE----- + +// CN=HiPKI Root CA - G1; O=Chunghwa Telecom Co., Ltd.; C=TW +-----BEGIN CERTIFICATE----- +MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBP +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xGzAZBgNVBAMMEkhpUEtJIFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRa +Fw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3 +YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kgUm9vdCBDQSAtIEcx +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0o9Qw +qNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twv +Vcg3Px+kwJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6 +lZgRZq2XNdZ1AYDgr/SEYYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnz +Qs7ZngyzsHeXZJzA9KMuH5UHsBffMNsAGJZMoYFL3QRtU6M9/Aes1MU3guvklQgZ +KILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfdhSi8MEyr48KxRURHH+CK +FgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj1jOXTyFj +HluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDr +y+K49a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ +/W3c1pzAtH2lsN0/Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgM +a/aOEmem8rJY5AIJEzypuxC00jBF8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6 +fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQDAgGGMA0GCSqG +SIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi +7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqc +SE5XCV0vrPSltJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6Fza +ZsT0pPBWGTMpWmWSBUdGSquEwx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9Tc +XzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07QJNBAsNB1CI69aO4I1258EHBGG3zg +iLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv5wiZqAxeJoBF1Pho +L5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+GpzjLrF +Ne85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wr +kkVbbiVghUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+ +vhV4nYWBSipX3tUZQ9rbyltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQU +YDksswBVLuT1sw5XxJFBAJw/6KXf6vb/yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ== +-----END CERTIFICATE----- + +// OU=ePKI Root Certification Authority; O=Chunghwa Telecom Co., Ltd.; C=TW +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw +IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL +SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH +SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh +ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X +DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1 +TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ +fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA +sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU +WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS +nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH +dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip +NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC +AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF +MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB +uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl +PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP +JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/ +gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2 +j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6 +5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB +o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS +/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z +Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE +W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D +hNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- + +// CN=D-TRUST Root Class 3 CA 2 2009; O=D-Trust GmbH; C=DE +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha +ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM +HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03 +UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42 +tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R +ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM +lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp +/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G +A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy +MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl +cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js +L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL +BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni +acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K +zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8 +PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y +Johw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +// CN=D-TRUST EV Root CA 2 2023; O=D-Trust GmbH; C=DE +-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQaSYJfoBLTKCnjHhiU19abzANBgkqhkiG9w0BAQ0FADBI +MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlE +LVRSVVNUIEVWIFJvb3QgQ0EgMiAyMDIzMB4XDTIzMDUwOTA5MTAzM1oXDTM4MDUw +OTA5MTAzMlowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEi +MCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDIgMjAyMzCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBANiOo4mAC7JXUtypU0w3uX9jFxPvp1sjW2l1sJkK +F8GLxNuo4MwxusLyzV3pt/gdr2rElYfXR8mV2IIEUD2BCP/kPbOx1sWy/YgJ25yE +7CUXFId/MHibaljJtnMoPDT3mfd/06b4HEV8rSyMlD/YZxBTfiLNTiVR8CUkNRFe +EMbsh2aJgWi6zCudR3Mfvc2RpHJqnKIbGKBv7FD0fUDCqDDPvXPIEysQEx6Lmqg6 +lHPTGGkKSv/BAQP/eX+1SH977ugpbzZMlWGG2Pmic4ruri+W7mjNPU0oQvlFKzIb +RlUWaqZLKfm7lVa/Rh3sHZMdwGWyH6FDrlaeoLGPaxK3YG14C8qKXO0elg6DpkiV +jTujIcSuWMYAsoS0I6SWhjW42J7YrDRJmGOVxcttSEfi8i4YHtAxq9107PncjLgc +jmgjutDzUNzPZY9zOjLHfP7KgiJPvo5iR2blzYfi6NUPGJ/lBHJLRjwQ8kTCZFZx +TnXonMkmdMV9WdEKWw9t/p51HBjGGjp82A0EzM23RWV6sY+4roRIPrN6TagD4uJ+ +ARZZaBhDM7DS3LAaQzXupdqpRlyuhoFBAUp0JuyfBr/CBTdkdXgpaP3F9ev+R/nk +hbDhezGdpn9yo7nELC7MmVcOIQxFAZRl62UJxmMiCzNJkkg8/M3OsD6Onov4/knF +NXJHAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUqvyREBuH +kV8Wub9PS5FeAByxMoAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRCMEAwPqA8oDqG +OGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfZXZfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQCTy6UfmRHsmg1fLBWTxj++EI14 +QvBukEdHjqOSMo1wj/Zbjb6JzkcBahsgIIlbyIIQbODnmaprxiqgYzWRaoUlrRc4 +pZt+UPJ26oUFKidBK7GB0aL2QHWpDsvxVUjY7NHss+jOFKE17MJeNRqrphYBBo7q +3C+jisosketSjl8MmxfPy3MHGcRqwnNU73xDUmPBEcrCRbH0O1P1aa4846XerOhU +t7KR/aypH/KH5BfGSah82ApB9PI+53c0BFLd6IHyTS9URZ0V4U/M5d40VxDJI3IX +cI1QcB9WbMy5/zpaT2N6w25lBx2Eof+pDGOJbbJAiDnXH3dotfyc1dZnaVuodNv8 +ifYbMvekJKZ2t0dT741Jj6m2g1qllpBFYfXeA08mD6iL8AOWsKwV0HFaanuU5nCT +2vFp4LJiTZ6P/4mdm13NRemUAiKN4DV/6PEEeXFsVIP4M7kFMhtYVRFP0OUnR3Hs +7dpn1mKmS00PaaLJvOwiS5THaJQXfuKOKD62xur1NGyfN4gHONuGcfrNlUhDbqNP +gofXNJhuS5N5YHVpD/Aa1VP6IQzCP+k/HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAst +Nl7F6cTVg8uGF5csbBNvh1qvSaYd2804BC5f4ko1Di1L+KIkBI3Y4WNeApI02phh +XBxvWHZks/wCuPWdCg== +-----END CERTIFICATE----- + +// CN=D-TRUST Root Class 3 CA 2 EV 2009; O=D-Trust GmbH; C=DE +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw +NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV +BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn +ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0 +3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z +qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR +p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8 +HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw +ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea +HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw +Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh +c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E +RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt +dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku +Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp +3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF +CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na +xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX +KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +// CN=D-TRUST BR Root CA 1 2020; O=D-Trust GmbH; C=DE +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQsw +CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS +VVNUIEJSIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5 +NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG +A1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB +BAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7dPYS +zuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0 +QVK5buXuQqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/ +VbNafAkl1bK6CKBrqx9tMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g +PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2JyX3Jvb3Rf +Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l +dC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1 +c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO +PQQDAwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFW +wKrY7RjEsK70PvomAjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHV +dWNbFJWcHwHP2NVypw87 +-----END CERTIFICATE----- + +// CN=D-TRUST EV Root CA 1 2020; O=D-Trust GmbH; C=DE +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQsw +CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS +VVNUIEVWIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5 +NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG +A1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB +BAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8ZRCC +/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rD +wpdhQntJraOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3 +OqQo5FD4pPfsazK2/umLMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g +PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2V2X3Jvb3Rf +Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l +dC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1 +c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO +PQQDAwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CA +y/m0sRtW9XLS/BnRAjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJb +gfM0agPnIjhQW+0ZT0MW +-----END CERTIFICATE----- + +// CN=D-TRUST BR Root CA 2 2023; O=D-Trust GmbH; C=DE +-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQczswBEhb2U14LnNLyaHcZjANBgkqhkiG9w0BAQ0FADBI +MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlE +LVRSVVNUIEJSIFJvb3QgQ0EgMiAyMDIzMB4XDTIzMDUwOTA4NTYzMVoXDTM4MDUw +OTA4NTYzMFowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEi +MCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDIgMjAyMzCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAK7/CVmRgApKaOYkP7in5Mg6CjoWzckjYaCTcfKr +i3OPoGdlYNJUa2NRb0kz4HIHE304zQaSBylSa053bATTlfrdTIzZXcFhfUvnKLNE +gXtRr90zsWh81k5M/itoucpmacTsXld/9w3HnDY25QdgrMBM6ghs7wZ8T1soegj8 +k12b9py0i4a6Ibn08OhZWiihNIQaJZG2tY/vsvmA+vk9PBFy2OMvhnbFeSzBqZCT +Rphny4NqoFAjpzv2gTng7fC5v2Xx2Mt6++9zA84A9H3X4F07ZrjcjrqDy4d2A/wl +2ecjbwb9Z/Pg/4S8R7+1FhhGaRTMBffb00msa8yr5LULQyReS2tNZ9/WtT5PeB+U +cSTq3nD88ZP+npNa5JRal1QMNXtfbO4AHyTsA7oC9Xb0n9Sa7YUsOCIvx9gvdhFP +/Wxc6PWOJ4d/GUohR5AdeY0cW/jPSoXk7bNbjb7EZChdQcRurDhaTyN0dKkSw/bS +uREVMweR2Ds3OmMwBtHFIjYoYiMQ4EbMl6zWK11kJNXuHA7e+whadSr2Y23OC0K+ +0bpwHJwh5Q8xaRfX/Aq03u2AnMuStIv13lmiWAmlY0cL4UEyNEHZmrHZqLAbWt4N +DfTisl01gLmB1IRpkQLLddCNxbU9CZEJjxShFHR5PtbJFR2kWVki3PaKRT08EtY+ +XTIvAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUZ5Dw1t61 +GNVGKX5cq/ieCLxklRAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRCMEAwPqA8oDqG +OGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfYnJfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA097N3U9swFrktpSHxQCF16+tI +FoE9c+CeJyrrd6kTpGoKWloUMz1oH4Guaf2Mn2VsNELZLdB/eBaxOqwjMa1ef67n +riv6uvw8l5VAk1/DLQOj7aRvU9f6QA4w9QAgLABMjDu0ox+2v5Eyq6+SmNMW5tTR +VFxDWy6u71cqqLRvpO8NVhTaIasgdp4D/Ca4nj8+AybmTNudX0KEPUUDAxxZiMrc +LmEkWqTqJwtzEr5SswrPMhfiHocaFpVIbVrg0M8JkiZmkdijYQ6qgYF/6FKC0ULn +4B0Y+qSFNueG4A3rvNTJ1jxD8V1Jbn6Bm2m1iWKPiFLY1/4nwSPFyysCu7Ff/vtD +hQNGvl3GyiEm/9cCnnRK3PgTFbGBVzbLZVzRHTF36SXDw7IyN9XxmAnkbWOACKsG +koHU6XCPpz+y7YaMgmo1yEJagtFSGkUPFaUA8JR7ZSdXOUPPfH/mvTWze/EZTN46 +ls/pdu4D58JDUjxqgejBWoC9EV2Ta/vH5mQ/u2kc6d0li690yVRAysuTEwrt+2aS +Ecr1wPrYg1UDfNPFIkZ1cGt5SAYqgpq/5usWDiJFAbzdNpQ0qTUmiteXue4Icr80 +knCDgKs4qllo3UCkGJCy89UDyibK79XH4I9TjvAA46jtn/mtd+ArY0+ew+43u3gJ +hJ65bvspmZDogNOfJA== +-----END CERTIFICATE----- + +// CN=T-TeleSec GlobalRoot Class 2; OU=T-Systems Trust Center; O=T-Systems Enterprise Services GmbH; C=DE +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd +AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC +FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi +1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq +jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ +wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ +WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy +NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC +uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw +IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 +g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP +BSeOE6Fuwg== +-----END CERTIFICATE----- + +// CN=Telekom Security TLS ECC Root 2020; O=Deutsche Telekom Security GmbH; C=DE +-----BEGIN CERTIFICATE----- +MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQsw +CQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBH +bWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIw +MB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIzNTk1OVowYzELMAkGA1UEBhMCREUx +JzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkGA1UE +AwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/O +tdKPD/M12kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDP +f8iAC8GXs7s1J8nCG6NCMEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6f +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA +MGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZMo7k+5Dck2TOrbRBR2Di +z6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdUga/sf+Rn +27iQ7t0l +-----END CERTIFICATE----- + +// CN=Telekom Security TLS RSA Root 2023; O=Deutsche Telekom Security GmbH; C=DE +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBj +MQswCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0 +eSBHbWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAy +MDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMyNzIzNTk1OVowYzELMAkGA1UEBhMC +REUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkG +A1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9 +cUD/h3VCKSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHV +cp6R+SPWcHu79ZvB7JPPGeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMA +U6DksquDOFczJZSfvkgdmOGjup5czQRxUX11eKvzWarE4GC+j4NSuHUaQTXtvPM6 +Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWol8hHD/BeEIvnHRz+sTug +BTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9FIS3R/qy +8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73J +co4vzLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg +8qKrBC7m8kwOFjQgrIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8 +rFEz0ciD0cmfHdRHNCk+y7AO+oMLKFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12 +mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7SWWO/gLCMk3PLNaaZlSJhZQNg ++y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtqeX +gj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2 +p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQ +pGv7qHBFfLp+sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm +9S3ul0A8Yute1hTWjOKWi0FpkzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErw +M807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy/SKE8YXJN3nptT+/XOR0so8RYgDd +GGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4mZqTuXNnQkYRIer+ +CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtzaL1t +xKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+ +w6jv/naaoqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aK +L4x35bcF7DvB7L6Gs4a8wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+lj +X273CXE2whJdV/LItM3z7gLfEdxquVeEHVlNjM7IDiPCtyaaEBRx/pOyiriA8A4Q +ntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0o82bNSQ3+pCTE4FCxpgm +dTdmQRCsu/WU48IxK63nI1bMNSWSs1A= +-----END CERTIFICATE----- + +// CN=T-TeleSec GlobalRoot Class 3; OU=T-Systems Trust Center; O=T-Systems Enterprise Services GmbH; C=DE +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN +8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/ +RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4 +hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5 +ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM +EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1 +A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy +WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ +1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30 +6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT +91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p +TpPDpFQUWw== +-----END CERTIFICATE----- + +// CN=DigiCert TLS RSA4096 Root G5; O=DigiCert, Inc.; C=US +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN +MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT +HERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN +NDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs +IEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS87IE+ +ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG0 +2C+JFvuUAT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgp +wgscONyfMXdcvyej/Cestyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZM +pG2T6T867jp8nVid9E6P/DsjyG244gXazOvswzH016cpVIDPRFtMbzCe88zdH5RD +nU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnVDdXifBBiqmvwPXbzP6Po +sMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9qTXeXAaDx +Zre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cd +Lvvyz6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvX +KyY//SovcfXWJL5/MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNe +XoVPzthwiHvOAbWWl9fNff2C+MIkwcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPL +tgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4EFgQUUTMc7TZArxfTJc1paPKv +TiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN +AQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw +GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7H +PNtQOa27PShNlnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLF +O4uJ+DQtpBflF+aZfTCIITfNMBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQ +REtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/u4cnYiWB39yhL/btp/96j1EuMPik +AdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9GOUrYU9DzLjtxpdRv +/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh47a+ +p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilw +MUc/dNAUFvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WF +qUITVuwhd4GTWgzqltlJyqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCK +ovfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+ +-----END CERTIFICATE----- + +// CN=DigiCert TLS ECC P384 Root G5; O=DigiCert, Inc.; C=US +-----BEGIN CERTIFICATE----- +MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp +Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2 +MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ +bmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS +7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp +0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS +B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49 +BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ +LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4 +DXZDjC5Ty3zfDBeWUA== +-----END CERTIFICATE----- + +// CN=QuoVadis Root CA 3 G3; O=QuoVadis Limited; C=BM +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 +MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR +/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu +FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR +U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c +ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR +FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k +A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw +eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl +sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp +VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q +A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ +ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD +ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI +FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv +oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg +u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP +0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf +3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl +8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ +DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN +PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ +ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +// CN=QuoVadis Root CA 2 G3; O=QuoVadis Limited; C=BM +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +// CN=DigiCert Assured ID Root G2; OU=www.digicert.com; O=DigiCert Inc; C=US +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA +n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc +biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp +EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA +bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu +YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW +BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI +QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I +0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni +lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 +B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv +ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +// CN=DigiCert Assured ID Root G3; OU=www.digicert.com; O=DigiCert Inc; C=US +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg +RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf +Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q +RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD +AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY +JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv +6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +// CN=DigiCert Global Root G2; OU=www.digicert.com; O=DigiCert Inc; C=US +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +// CN=DigiCert Global Root G3; OU=www.digicert.com; O=DigiCert Inc; C=US +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe +Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw +EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x +IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG +fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO +Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx +AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ +oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 +sycX +-----END CERTIFICATE----- + +// CN=DigiCert Trusted Root G4; OU=www.digicert.com; O=DigiCert Inc; C=US +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg +RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y +ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If +xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV +ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO +DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ +jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ +CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi +EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM +fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY +uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK +chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t +9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 +SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd ++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc +fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa +sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N +cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N +0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie +4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI +r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 +/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm +gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ +-----END CERTIFICATE----- + +// CN=CA Disig Root R2; O=Disig a.s.; L=Bratislava; C=SK +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV +BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu +MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy +MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx +EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe +NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH +PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I +x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe +QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR +yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO +QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912 +H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ +QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD +i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs +nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1 +rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI +hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf +GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb +lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka ++elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal +TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i +nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3 +gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr +G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os +zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x +L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +// CN=emSign Root CA - G1; OU=emSign PKI; O=eMudhra Technologies Limited; C=IN +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD +VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU +ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH +MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO +MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv +Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz +f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO +8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq +d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM +tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt +Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB +o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD +AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x +PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM +wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d +GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH +6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby +RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE----- + +// CN=emSign ECC Root CA - G3; OU=emSign PKI; O=eMudhra Technologies Limited; C=IN +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG +EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo +bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ +TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s +b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0 +WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS +fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB +zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq +hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB +CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD ++JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- + +// CN=Atos TrustedRoot Root CA ECC TLS 2021; O=Atos; C=DE +-----BEGIN CERTIFICATE----- +MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4w +LAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0w +CwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0 +MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBF +Q0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMHYwEAYHKoZI +zj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6KDP/X +tXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4 +AjJn8ZQSb+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2 +KCXWfeBmmnoJsmo7jjPXNtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMD +aAAwZQIwW5kp85wxtolrbNa9d+F851F+uDrNozZffPc8dz7kUK2o59JZDCaOMDtu +CCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGYa3cpetskz2VAv9LcjBHo +9H1/IISpQuQo +-----END CERTIFICATE----- + +// CN=Atos TrustedRoot 2011; O=Atos; C=DE +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE +AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG +EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM +FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC +REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp +Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM +VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ +SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ +4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L +cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi +eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG +A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 +DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j +vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP +DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc +maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D +lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv +KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +// CN=Atos TrustedRoot Root CA RSA TLS 2021; O=Atos; C=DE +-----BEGIN CERTIFICATE----- +MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBM +MS4wLAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIx +MQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00 +MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBD +QSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BBl01Z +4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYv +Ye+W/CBGvevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZ +kmGbzSoXfduP9LVq6hdKZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDs +GY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt0xU6kGpn8bRrZtkh68rZYnxGEFzedUln +nkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVKPNe0OwANwI8f4UDErmwh +3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMYsluMWuPD +0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzy +geBYBr3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8 +ANSbhqRAvNncTFd+rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezB +c6eUWsuSZIKmAMFwoW4sKeFYV+xafJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lI +pw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +dEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +DAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS +4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPs +o0UvFJ/1TCplQ3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJ +qM7F78PRreBrAwA0JrRUITWXAdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuyw +xfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9GslA9hGCZcbUztVdF5kJHdWoOsAgM +rr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2VktafcxBPTy+av5EzH4 +AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9qTFsR +0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuY +o7Ey7Nmj1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5 +dDTedk+SKlOxJTnbPP/lPqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcE +oji2jbDwN/zIIX8/syQbPYtuzE2wFg2WHYMfRsCbvUOZ58SWLs5fyQ== +-----END CERTIFICATE----- + +// CN=GlobalSign Root R46; O=GlobalSign nv-sa; C=BE +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA +MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD +VQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy +MDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYt +c2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08EsCVeJ +OaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQG +vGIFAha/r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud +316HCkD7rRlr+/fKYIje2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo +0q3v84RLHIf8E6M6cqJaESvWJ3En7YEtbWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSE +y132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvjK8Cd+RTyG/FWaha/LIWF +zXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD412lPFzYE ++cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCN +I/onccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzs +x2sZy/N78CsHpdlseVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqa +ByFrgY/bxFn63iLABJzjqls2k+g9vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC +4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEMBQADggIBAHx4 +7PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti +2kM3S+LGteWygxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIk +pnnpHs6i58FZFZ8d4kuaPp92CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRF +FRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZmOUdkLG5NrmJ7v2B0GbhWrJKsFjLt +rWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qqJZ4d16GLuc1CLgSk +ZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwyeqiv5 +u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP +4vkYxboznxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6 +N3ec592kD3ZDZopD8p/7DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3 +vouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6 +-----END CERTIFICATE----- + +// CN=GlobalSign Root E46; O=GlobalSign nv-sa; C=BE +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx +CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD +ExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw +MDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2Ex +HDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkBjtjq +R+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGdd +yXqBPCCjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ +7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZkvLtoURMMA/cVi4RguYv/Uo7njLwcAjA8 ++RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE----- + +// CN=GlobalSign; OU=GlobalSign ECC Root CA - R5; O=GlobalSign +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc +8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke +hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI +KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg +515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO +xwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +// CN=GlobalSign; OU=GlobalSign Root CA - R3; O=GlobalSign +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 +MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 +RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT +gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm +KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd +QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ +XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o +LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU +RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp +jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK +6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX +mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs +Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH +WD9f +-----END CERTIFICATE----- + +// CN=GlobalSign; OU=GlobalSign Root CA - R6; O=GlobalSign +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg +MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx +MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET +MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI +xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k +ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD +aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw +LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw +1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX +k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2 +SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h +bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n +WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY +rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce +MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu +bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt +Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61 +55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj +vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf +cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz +oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp +nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs +pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v +JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R +8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4 +5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- + +// CN=Starfield Root Certificate Authority - G2; O=Starfield Technologies, Inc.; L=Scottsdale; ST=Arizona; C=US +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs +ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw +MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj +aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp +Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg +nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 +HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N +Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN +dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 +HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G +CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU +sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 +4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg +8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 +mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +// CN=Go Daddy Root Certificate Authority - G2; O=GoDaddy.com, Inc.; L=Scottsdale; ST=Arizona; C=US +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT +EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp +ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz +NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH +EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE +AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD +E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH +/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy +DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh +GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR +tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA +AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX +WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu +9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr +gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo +2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI +4uJEvlz36hz1 +-----END CERTIFICATE----- + +// CN=GTS Root R4; O=Google Trust Services LLC; C=US +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD +VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG +A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw +WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz +IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi +QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR +HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D +9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8 +p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD +-----END CERTIFICATE----- + +// CN=GTS Root R2; O=Google Trust Services LLC; C=US +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw +CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +MBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw +MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp +Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3LvCvpt +nfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY +6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAu +MC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7k +RXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWg +f9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1mKPV ++3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K8Yzo +dDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW +Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKa +G73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCq +gc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwID +AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBAB/Kzt3H +vqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8 +0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyC +B19m3H0Q/gxhswWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2u +NmSRXbBoGOqKYcl3qJfEycel/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMg +yALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVnjWQye+mew4K6Ki3pHrTgSAai/Gev +HyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y59PYjJbigapordwj6 +xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M7YNR +TOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924Sg +JPFI/2R80L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV +7LXTWtiBmelDGDfrs7vRWGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl +6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjWHYbL +-----END CERTIFICATE----- + +// CN=GTS Root R1; O=Google Trust Services LLC; C=US +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw +CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw +MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp +Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo +27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w +Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw +TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl +qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH +szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8 +Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk +MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92 +wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p +aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN +VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID +AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb +C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe +QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy +h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4 +7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J +ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef +MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/ +Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT +6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ +0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm +2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb +bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c +-----END CERTIFICATE----- + +// CN=GTS Root R3; O=Google Trust Services LLC; C=US +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD +VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG +A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw +WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz +IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout736G +jOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL2 +4CejQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7 +VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azTL818+FsuVbu/3ZL3pAzcMeGiAjEA/Jdm +ZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X +-----END CERTIFICATE----- + +// CN=GlobalSign; OU=GlobalSign ECC Root CA - R4; O=GlobalSign +-----BEGIN CERTIFICATE----- +MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD +VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw +MTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0g +UjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wWTAT +BgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkWymOx +uYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNV +HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/ ++wpu+74zyTyjhNUwCgYIKoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147 +bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm +-----END CERTIFICATE----- + +// CN=ACCVRAIZ1; OU=PKIACCV; O=ACCV; C=ES +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE +AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw +CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ +BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND +VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb +qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY +HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo +G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA +lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr +IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/ +0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH +k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47 +4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO +m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa +cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl +uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI +KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls +ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG +AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT +VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG +CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA +cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA +QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA +7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA +cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA +QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA +czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu +aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt +aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud +DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF +BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp +D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU +JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m +AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD +vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms +tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH +7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA +h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF +d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H +pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +// CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS; OU=Ceres; O=FNMT-RCM; C=ES; OrganizationIdentifier=VATES-Q2826004J +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw +CQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw +FgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S +Q00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4MTIyMDA5MzczM1oXDTQzMTIyMDA5 +MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQtUkNNMQ4wDAYDVQQL +DAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNBQyBS +QUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LH +sbI6GA60XYyzZl2hNPk2LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oK +Um8BA06Oi6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqGSM49BAMDA2kAMGYCMQCu +SuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoDzBOQn5IC +MQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJy +v+c= +-----END CERTIFICATE----- + +// OU=AC RAIZ FNMT-RCM; O=FNMT-RCM; C=ES +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx +CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ +WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ +BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG +Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/ +yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf +BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz +WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF +tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z +374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC +IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL +mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7 +wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS +MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2 +ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet +UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H +YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3 +LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1 +RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM +LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf +77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N +JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm +fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp +6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp +1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B +9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok +RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv +uu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----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 +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx +GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp +bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w +KwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0 +BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy +dW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG +EwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll +IEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU +QUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT +TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg +LSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7 +a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr +LqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr +N3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X +YacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/ +iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f +AJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH +V8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf +IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4 +lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c +8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf +lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- + +// CN=HARICA TLS RSA Root CA 2021; O=Hellenic Academic and Research Institutions CA; C=GR +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs +MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg +Um9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUzOFoXDTQ1MDIxMzEwNTUzN1owbDEL +MAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNBIFJv +b3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569l +mwVnlskNJLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE +4VGC/6zStGndLuwRo0Xua2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uv +a9of08WRiFukiZLRgeaMOVig1mlDqa2YUlhu2wr7a89o+uOkXjpFc5gH6l8Cct4M +pbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K5FrZx40d/JiZ+yykgmvw +Kh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEvdmn8kN3b +LW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcY +AuUR0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqB +AGMUuTNe3QvboEUHGjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYq +E613TBoYm5EPWNgGVMWX+Ko/IIqmhaZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHr +W2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQCPxrvrNQKlr9qEgYRtaQQJKQ +CoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAU +X15QvWiWkKQUEapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3 +f5Z2EMVGpdAgS1D0NTsY9FVqQRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxaja +H6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxDQpSbIPDRzbLrLFPCU3hKTwSUQZqP +JzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcRj88YxeMn/ibvBZ3P +zzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5vZSt +jBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0 +/L5H9MG0qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pT +BGIBnfHAT+7hOtSLIBD6Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79 +aPib8qXPMThcFarmlwDB31qlpzmq6YR/PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YW +xw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnnkf3/W9b3raYvAwtt41dU +63ZTGI0RmLo= +-----END CERTIFICATE----- + +// CN=HARICA TLS ECC Root CA 2021; O=Hellenic Academic and Research Institutions CA; C=GR +-----BEGIN CERTIFICATE----- +MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw +CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh +cmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v +dCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoXDTQ1MDIxMzExMDEwOVowbDELMAkG +A1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj +aCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJvb3Qg +Q0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7 +KKrxcm1lAEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9Y +STHMmE5gEYd103KUkE+bECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQD +AgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAircJRQO9gcS3ujwLEXQNw +SaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/QwCZ61IygN +nxS2PFOiTAZpffpskcYqSUXm7LcT4Tps +-----END CERTIFICATE----- + +// CN=IdenTrust Commercial Root CA 1; O=IdenTrust; C=US +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu +VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw +MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw +JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT +3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU ++ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp +S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1 +bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi +T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL +vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK +Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK +dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT +c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv +l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N +iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD +ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt +LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93 +nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3 ++wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK +W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT +AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq +l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG +4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ +mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A +7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +// CN=ISRG Root X1; O=Internet Security Research Group; C=US +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- + +// CN=ISRG Root X2; O=Internet Security Research Group; C=US +-----BEGIN CERTIFICATE----- +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw +CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg +R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00 +MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT +ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW ++1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9 +ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI +zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW +tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1 +/q4AaOeMSQ+2b1tbFfLn +-----END CERTIFICATE----- + +// CN=Izenpe.com; O=IZENPE S.A.; C=ES +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4 +MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6 +ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD +VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j +b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq +scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO +xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H +LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX +uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD +yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+ +JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q +rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN +BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L +hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB +QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+ +HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu +Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg +QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB +BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA +A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb +laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56 +awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo +JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw +LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT +VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk +LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb +UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/ +QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+ +naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls +QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE----- + +// CN=SZAFIR ROOT CA2; O=Krajowa Izba Rozliczeniowa S.A.; C=PL +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6 +ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw +NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L +cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg +Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN +QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT +3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw +3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6 +3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5 +BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN +XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF +AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw +8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG +nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP +oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy +d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg +LvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- + +// CN=e-Szigno TLS Root CA 2023; O=Microsec Ltd.; L=Budapest; C=HU; OrganizationIdentifier=VATHU-23584497 +-----BEGIN CERTIFICATE----- +MIICzzCCAjGgAwIBAgINAOhvGHvWOWuYSkmYCjAKBggqhkjOPQQDBDB1MQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xFzAVBgNVBGEMDlZBVEhVLTIzNTg0NDk3MSIwIAYDVQQDDBllLVN6aWdubyBU +TFMgUm9vdCBDQSAyMDIzMB4XDTIzMDcxNzE0MDAwMFoXDTM4MDcxNzE0MDAwMFow +dTELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRYwFAYDVQQKDA1NaWNy +b3NlYyBMdGQuMRcwFQYDVQRhDA5WQVRIVS0yMzU4NDQ5NzEiMCAGA1UEAwwZZS1T +emlnbm8gVExTIFJvb3QgQ0EgMjAyMzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAE +AGgP36J8PKp0iGEKjcJMpQEiFNT3YHdCnAo4YKGMZz6zY+n6kbCLS+Y53wLCMAFS +AL/fjO1ZrTJlqwlZULUZwmgcAOAFX9pQJhzDrAQixTpN7+lXWDajwRlTEArRzT/v +SzUaQ49CE0y5LBqcvjC2xN7cS53kpDzLLtmt3999Cd8ukv+ho2MwYTAPBgNVHRMB +Af8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUWYQCYlpGePVd3I8K +ECgj3NXW+0UwHwYDVR0jBBgwFoAUWYQCYlpGePVd3I8KECgj3NXW+0UwCgYIKoZI +zj0EAwQDgYsAMIGHAkIBLdqu9S54tma4n7Zwf2Z0z+yOfP7AAXmazlIC58PRDHpt +y7Ve7hekm9sEdu4pKeiv+62sUvTXK9Z3hBC9xdIoaDQCQTV2WnXzkoYI9bIeCvZl +C9p2x1L/Cx6AcCIwwzPbGO2E14vs7dOoY4G1VnxHx1YwlGhza9IuqbnZLBwpvQy6 +uWWL +-----END CERTIFICATE----- + +// CN=Microsec e-Szigno Root CA 2009; O=Microsec Ltd.; L=Budapest; C=HU; EmailAddress=info@e-szigno.hu +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G +CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y +OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx +FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp +Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP +kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc +cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U +fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7 +N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC +xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1 ++rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM +Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG +SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h +mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk +ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c +2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t +HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +// CN=e-Szigno Root CA 2017; O=Microsec Ltd.; L=Budapest; C=HU; OrganizationIdentifier=VATHU-23584497 +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV +BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk +LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv +b3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ +BgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg +THRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v +IFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv +xie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H +Wyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB +eAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo +jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ ++efcMQ== +-----END CERTIFICATE----- + +// CN=Microsoft ECC Root Certificate Authority 2017; O=Microsoft Corporation; C=US +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD +VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw +MTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4MjMxNjA0WjBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNy +b3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZR +ogPZnZH6thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYb +hGBKia/teQ87zvH2RPUBeMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3 +FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV +L8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB +iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- + +// CN=Microsoft RSA Root Certificate Authority 2017; O=Microsoft Corporation; C=US +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl +MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N +aWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ +Nt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0 +ZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1 +HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm +gGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ +jEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc +aDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG +YaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6 +W6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K +UGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH ++FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q +W5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC +LgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC +gMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6 +tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh +SnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2 +TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3 +pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR +xpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp +GWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9 +dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN +AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB +RA+GsCyRxj3qrg+E +-----END CERTIFICATE----- + +// CN=NAVER Global Root Certification Authority; O=NAVER BUSINESS PLATFORM Corp.; C=KR +-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM +BQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG +T1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4NDJaFw0zNzA4MTgyMzU5NTlaMGkx +CzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVTUyBQTEFURk9STSBD +b3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVA +iQqrDZBbUGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH +38dq6SZeWYp34+hInDEW+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lE +HoSTGEq0n+USZGnQJoViAbbJAh2+g1G7XNr4rRVqmfeSVPc0W+m/6imBEtRTkZaz +kVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2aacp+yPOiNgSnABIqKYP +szuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4Yb8Obtoq +vC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHf +nZ3zVHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaG +YQ5fG8Ir4ozVu53BA0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo +0es+nPxdGoMuK8u180SdOqcXYZaicdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3a +CJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejyYhbLgGvtPe31HzClrkvJE+2K +AQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNVHQ4EFgQU0p+I +36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoN +qo0hV4/GPnrK21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatj +cu3cvuzHV+YwIHHW1xDBE1UBjCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm ++LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bxhYTeodoS76TiEJd6eN4MUZeoIUCL +hr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTgE34h5prCy8VCZLQe +lHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTHD8z7 +p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8 +piKCk5XQA76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLR +LBT/DShycpWbXgnbiUSYqqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX +5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oGI/hGoiLtk/bdmuYqh7GYVPEi92tF4+KO +dh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul +9XXeifdy +-----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 +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG +EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 +MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl +cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR +dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB +pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM +b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm +aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz +IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT +lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz +AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5 +VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG +ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2 +BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG +AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M +U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh +bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C ++C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F +uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 +XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +// CN=OISTE WISeKey Global Root GC CA; OU=OISTE Foundation Endorsed; O=WISeKey; C=CH +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw +CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91 +bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg +Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ +BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu +ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS +b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni +eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W +p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T +rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV +57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg +Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE----- + +// CN=OISTE WISeKey Global Root GB CA; OU=OISTE Foundation Endorsed; O=WISeKey; C=CH +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt +MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg +Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i +YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x +CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG +b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3 +HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx +WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX +1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk +u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P +99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r +M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB +BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh +cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5 +gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO +ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf +aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- + +// CN=Security Communication ECC RootCA1; O=SECOM Trust Systems CO.,LTD.; C=JP +-----BEGIN CERTIFICATE----- +MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYT +AkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYD +VQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYx +NjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTELMAkGA1UEBhMCSlAxJTAjBgNVBAoT +HFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNVBAMTIlNlY3VyaXR5 +IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+Cnnfdl +dB9sELLo5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpK +ULGjQjBAMB0GA1UdDgQWBBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu +9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3LsnNdo4gIxwwCMQDAqy0O +be0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70eN9k= +-----END CERTIFICATE----- + +// OU=Security Communication RootCA2; O=SECOM Trust Systems CO.,LTD.; C=JP +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl +MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe +U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX +DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy +dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj +YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV +OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr +zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM +VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ +hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO +ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw +awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs +OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF +coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc +okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8 +t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy +1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/ +SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +// CN=COMODO Certification Authority; O=COMODO CA Limited; L=Salford; ST=Greater Manchester; C=GB +-----BEGIN CERTIFICATE----- +MIID0DCCArigAwIBAgIQIKTEf93f4cdTYwcTiHdgEjANBgkqhkiG9w0BAQUFADCB +gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV +BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xMTAxMDEwMDAw +MDBaFw0zMDEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl +YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P +RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3 +UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI +2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8 +Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp ++2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+ +DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O +nKVIrLsm9wIDAQABo0IwQDAdBgNVHQ4EFgQUC1jli8ZMFTekQKkwqSG+RzZaVv8w +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBAC/JxBwHO89hAgCx2SFRdXIDMLDEFh9sAIsQrK/xR9SuEDwMGvjUk2ysEDd8 +t6aDZK3N3w6HM503sMZ7OHKx8xoOo/lVem0DZgMXlUrxsXrfViEGQo+x06iF3u6X +HWLrp+cxEmbDD6ZLLkGC9/3JG6gbr+48zuOcrigHoSybJMIPIyaDMouGDx8rEkYl +Fo92kANr3ryqImhrjKGsKxE5pttwwn1y6TPn/CbxdFqR5p2ErPioBhlG5qfpqjQi +pKGfeq23sqSaM4hxAjwu1nqyH6LKwN0vEJT9s4yEIHlG1QXUEOTS22RPuFvuG8Ug +R1uUq27UlTMdphVx8fiUylQ5PsE= +-----END CERTIFICATE----- + +// CN=COMODO RSA Certification Authority; O=COMODO CA Limited; L=Salford; ST=Greater Manchester; C=GB +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB +hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV +BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT +EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR +6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X +pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC +9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV +/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf +Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z ++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w +qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah +SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC +u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf +Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq +crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl +wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM +4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV +2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna +FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ +CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK +boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke +jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL +S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb +QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl +0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB +NVOFBkpdn627G190 +-----END CERTIFICATE----- + +// CN=USERTrust RSA Certification Authority; O=The USERTRUST Network; L=Jersey City; ST=New Jersey; C=US +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +// CN=USERTrust ECC Certification Authority; O=The USERTRUST Network; L=Jersey City; ST=New Jersey; C=US +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl +eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT +JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg +VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo +I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng +o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G +A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB +zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW +RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +// CN=COMODO ECC Certification Authority; O=COMODO CA Limited; L=Salford; ST=Greater Manchester; C=GB +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT +IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw +MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy +ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N +T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR +FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J +cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW +BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm +fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv +GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +// CN=Sectigo Public Server Authentication Root E46; O=Sectigo Limited; C=GB +-----BEGIN CERTIFICATE----- +MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQsw +CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T +ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcN +MjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYG +A1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT +ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccC +WvkEN/U0NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+ +6xnOQ6OjQjBAMB0GA1UdDgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8B +Af8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNnADBkAjAn7qRa +qCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RHlAFWovgzJQxC36oCMB3q +4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21USAGKcw== +-----END CERTIFICATE----- + +// CN=Sectigo Public Server Authentication Root R46; O=Sectigo Limited; C=GB +-----BEGIN CERTIFICATE----- +MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBf +MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD +Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw +HhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEY +MBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1Ymxp +YyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDa +ef0rty2k1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnz +SDBh+oF8HqcIStw+KxwfGExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xf +iOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMPFF1bFOdLvt30yNoDN9HWOaEhUTCDsG3X +ME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vuZDCQOc2TZYEhMbUjUDM3 +IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5QazYw6A3OAS +VYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgE +SJ/AwSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu ++Zd4KKTIRJLpfSYFplhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt +8uaZFURww3y8nDnAtOFr94MlI1fZEoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+L +HaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW6aWWrL3DkJiy4Pmi1KZHQ3xt +zwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWIIUkwDgYDVR0P +AQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c +mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQ +YKlJfp/imTYpE0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52 +gDY9hAaLMyZlbcp+nv4fjFg4exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZA +Fv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M0ejf5lG5Nkc/kLnHvALcWxxPDkjB +JYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI84HxZmduTILA7rpX +DhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9mpFui +TdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5 +dHn5HrwdVw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65 +LvKRRFHQV80MNNVIIb/bE/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp +0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmmJ1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAY +QqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL +-----END CERTIFICATE----- + +// CN=SSL.com TLS ECC Root CA 2022; O=SSL Corporation; C=US +-----BEGIN CERTIFICATE----- +MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQsw +CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxT +U0wuY29tIFRMUyBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2 +MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3Jh +dGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3QgQ0EgMjAyMjB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWyJGYm +acCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFN +SeR7T5v15wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME +GDAWgBSJjy+j6CugFFR781a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NW +uCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp +15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w7deedWo1dlJF4AIxAMeN +b0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5Zn6g6g== +-----END CERTIFICATE----- + +// CN=SSL.com EV Root Certification Authority ECC; O=SSL Corporation; L=Houston; ST=Texas; C=US +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx +NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv +bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA +VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku +WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP +MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX +5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ +ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg +h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- + +// CN=SSL.com TLS RSA Root CA 2022; O=SSL Corporation; C=US +-----BEGIN CERTIFICATE----- +MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBO +MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQD +DBxTU0wuY29tIFRMUyBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloX +DTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jw +b3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJvb3QgQ0EgMjAyMjCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u9nTP +L3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OY +t6/wNr/y7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0ins +S657Lb85/bRi3pZ7QcacoOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3 +PnxEX4MN8/HdIGkWCVDi1FW24IBydm5MR7d1VVm0U3TZlMZBrViKMWYPHqIbKUBO +L9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDGD6C1vBdOSHtRwvzpXGk3 +R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEWTO6Af77w +dr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS ++YCk8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYS +d66UNHsef8JmAOSqg+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoG +AtUjHBPW6dvbxrB6y3snm/vg1UYk7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2f +gTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j +BBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsuN+7jhHonLs0Z +NbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt +hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsM +QtfhWsSWTVTNj8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvf +R4iyrT7gJ4eLSYwfqUdYe5byiB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJ +DPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjUo3KUQyxi4U5cMj29TH0ZR6LDSeeW +P4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqoENjwuSfr98t67wVy +lrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7EgkaibMOlq +bLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2w +AgDHbICivRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3q +r5nsLFR+jM4uElZI7xc7P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sji +Mho6/4UIyYOf8kpIEFR3N+2ivEC+5BB09+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU +98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA= +-----END CERTIFICATE----- + +// CN=SSL.com Root Certification Authority ECC; O=SSL Corporation; L=Houston; ST=Texas; C=US +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz +WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0 +b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS +b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI +7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg +CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD +VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T +kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+ +gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- + +// CN=SSL.com EV Root Certification Authority RSA R2; O=SSL Corporation; L=Houston; ST=Texas; C=US +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV +BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE +CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy +MDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G +A1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD +DC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq +M0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf +OePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa +4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9 +HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR +aZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA +b9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ +Gp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV +PWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO +pgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu +UDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY +MBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4 +9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW +s47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5 +Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg +cLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM +79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz +/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt +ll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm +Kf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK +QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ +w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi +S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07 +mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- + +// CN=SSL.com Root Certification Authority RSA; O=SSL Corporation; L=Houston; ST=Texas; C=US +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE +BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK +DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz +OTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R +xFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX +qhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC +C52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3 +6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh +/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF +YD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E +JNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc +US4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8 +ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm ++Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi +M+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G +A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV +cpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc +Hadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs +PgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/ +q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0 +cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr +a6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I +H37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y +K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu +nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf +oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY +Ic2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- + +// CN=SwissSign RSA TLS Root CA 2022 - 1; O=SwissSign AG; C=CH +-----BEGIN CERTIFICATE----- +MIIFkzCCA3ugAwIBAgIUQ/oMX04bgBhE79G0TzUfRPSA7cswDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzErMCkGA1UE +AxMiU3dpc3NTaWduIFJTQSBUTFMgUm9vdCBDQSAyMDIyIC0gMTAeFw0yMjA2MDgx +MTA4MjJaFw00NzA2MDgxMTA4MjJaMFExCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxT +d2lzc1NpZ24gQUcxKzApBgNVBAMTIlN3aXNzU2lnbiBSU0EgVExTIFJvb3QgQ0Eg +MjAyMiAtIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDLKmjiC8NX +vDVjvHClO/OMPE5Xlm7DTjak9gLKHqquuN6orx122ro10JFwB9+zBvKK8i5VUXu7 +LCTLf5ImgKO0lPaCoaTo+nUdWfMHamFk4saMla+ju45vVs9xzF6BYQ1t8qsCLqSX +5XH8irCRIFucdFJtrhUnWXjyCcplDn/L9Ovn3KlMd/YrFgSVrpxxpT8q2kFC5zyE +EPThPYxr4iuRR1VPuFa+Rd4iUU1OKNlfGUEGjw5NBuBwQCMBauTLE5tzrE0USJIt +/m2n+IdreXXhvhCxqohAWVTXz8TQm0SzOGlkjIHRI36qOTw7D59Ke4LKa2/KIj4x +0LDQKhySio/YGZxH5D4MucLNvkEM+KRHBdvBFzA4OmnczcNpI/2aDwLOEGrOyvi5 +KaM2iYauC8BPY7kGWUleDsFpswrzd34unYyzJ5jSmY0lpx+Gs6ZUcDj8fV3oT4MM +0ZPlEuRU2j7yrTrePjxF8CgPBrnh25d7mUWe3f6VWQQvdT/TromZhqwUtKiE+shd +OxtYk8EXlFXIC+OCeYSf8wCENO7cMdWP8vpPlkwGqnj73mSiI80fPsWMvDdUDrta +clXvyFu1cvh43zcgTFeRc5JzrBh3Q4IgaezprClG5QtO+DdziZaKHG29777YtvTK +wP1H8K4LWCDFyB02rpeNUIMmJCn3nTsPBQIDAQABo2MwYTAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBRvjmKLk0Ow4UD2p8P98Q+4 +DxU4pTAdBgNVHQ4EFgQUb45ii5NDsOFA9qfD/fEPuA8VOKUwDQYJKoZIhvcNAQEL +BQADggIBAKwsKUF9+lz1GpUYvyypiqkkVHX1uECry6gkUSsYP2OprphWKwVDIqO3 +10aewCoSPY6WlkDfDDOLazeROpW7OSltwAJsipQLBwJNGD77+3v1dj2b9l4wBlgz +Hqp41eZUBDqyggmNzhYzWUUo8aWjlw5DI/0LIICQ/+Mmz7hkkeUFjxOgdg3XNwwQ +iJb0Pr6VvfHDffCjw3lHC1ySFWPtUnWK50Zpy1FVCypM9fJkT6lc/2cyjlUtMoIc +gC9qkfjLvH4YoiaoLqNTKIftV+Vlek4ASltOU8liNr3CjlvrzG4ngRhZi0Rjn9UM +ZfQpZX+RLOV/fuiJz48gy20HQhFRJjKKLjpHE7iNvUcNCfAWpO2Whi4Z2L6MOuhF +LhG6rlrnub+xzI/goP+4s9GFe3lmozm1O2bYQL7Pt2eLSMkZJVX8vY3PXtpOpvJp +zv1/THfQwUY1mFwjmwJFQ5Ra3bxHrSL+ul4vkSkphnsh3m5kt8sNjzdbowhq6/Td +Ao9QAwKxuDdollDruF/UKIqlIgyKhPBZLtU30WHlQnNYKoH3dtvi4k0NX/a3vgW0 +rk4N3hY9A4GzJl5LuEsAz/+MF7psYC0nhzck5npgL7XTgwSqT0N1osGDsieYK7EO +gLrAhV5Cud+xYJHT6xh+cHiudoO+cVrQkOPKwRYlZ0rwtnu64ZzZ +-----END CERTIFICATE----- + +// CN=TWCA Global Root CA; OU=Root CA; O=TAIWAN-CA; C=TW +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx +EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT +VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5 +NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT +B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF +10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz +0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh +MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH +zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc +46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2 +yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi +laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP +oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA +BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE +qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm +4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL +1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF +H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo +RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+ +nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh +15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW +6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW +nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j +wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz +aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy +KwbQBM0= +-----END CERTIFICATE----- + +// CN=TWCA CYBER Root CA; OU=Root CA; O=TAIWAN-CA; C=TW +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQ +MQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290 +IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5 +WhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FO +LUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1sTs6P +40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxF +avcokPFhV8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/ +34bKS1PE2Y2yHer43CdTo0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684i +JkXXYJndzk834H/nY62wuFm40AZoNWDTNq5xQwTxaWV4fPMf88oon1oglWa0zbfu +j3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK/c/WMw+f+5eesRycnupf +Xtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkHIuNZW0CP +2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDA +S9TMfAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDA +oS/xUgXJP+92ZuJF2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzC +kHDXShi8fgGwsOsVHkQGzaRP6AzRwyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW +5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83QOGt4A1WNzAd +BgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB +AGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0t +tGlTITVX1olNc79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn +68xDiBaiA9a5F/gZbG0jAn/xX9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNn +TKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDRIG4kqIQnoVesqlVYL9zZyvpoBJ7t +RCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq/p1hvIbZv97Tujqx +f36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0RFxbI +Qh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz +8ppy6rBePm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4 +NxKfKjLji7gh7MMrZQzvIt6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzX +xeSDwWrruoBa3lwtcHb4yOWHh8qgnaHlIhInD0Q9HWzq1MKLL295q39QpsQZp6F6 +t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X +-----END CERTIFICATE----- + +// CN=Telia Root CA v2; O=Telia Finland Oyj; C=FI +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQx +CzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UE +AwwQVGVsaWEgUm9vdCBDQSB2MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1 +NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZ +MBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ76zBq +AMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9 +vVYiQJ3q9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9 +lRdU2HhE8Qx3FZLgmEKnpNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTOD +n3WhUidhOPFZPY5Q4L15POdslv5e2QJltI5c0BE0312/UqeBAMN/mUWZFdUXyApT +7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW5olWK8jjfN7j/4nlNW4o +6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNrRBH0pUPC +TEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6 +WT0EBXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63R +DolUK5X6wK0dmBR4M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZI +pEYslOqodmJHixBTB0hXbOKSTbauBcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGj +YzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7Wxy+G2CQ5MB0GA1UdDgQWBBRy +rOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ +8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi +0f6X+J8wfBj5tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMM +A8iZGok1GTzTyVR8qPAs5m4HeW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBS +SRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+Cy748fdHif64W1lZYudogsYMVoe+K +TTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygCQMez2P2ccGrGKMOF +6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15h2Er +3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMt +Ty3EHD70sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pT +VmBds9hCG1xLEooc6+t9xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAW +ysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQraVplI/owd8k+BsHMYeB2F326CjYSlKA +rBPuUBQemMc= +-----END CERTIFICATE----- +` +} diff --git a/common/certificate/chrome.pem b/common/certificate/chrome.pem new file mode 100644 index 0000000000..0d6ac2370b --- /dev/null +++ b/common/certificate/chrome.pem @@ -0,0 +1,2650 @@ +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE +BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w +MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC +SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1 +ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv +UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX +4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9 +KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/ +gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb +rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ +51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F +be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe +KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F +v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn +fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7 +jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz +ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL +e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70 +jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz +WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V +SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j +pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX +X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok +fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R +K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU +ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU +LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT +LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL +BQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg +Q2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv +b3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQwNDI2MDg1NzU2WjBhMQswCQYDVQQG +EwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBDZXJ0aWZpY2F0aW9u +IEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZ +n56eY+hz2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd +2JQDoOw05TDENX37Jk0bbjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgF +VwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZ +GoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAdgjH8KcwAWJeRTIAAHDOF +li/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViWVSHbhlnU +r8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2 +eY8fTpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIb +MlEsPvLfe/ZdeikZjuXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISg +jwBUFfyRbVinljvrS5YnzWuioYasDXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB +7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwSVXAkPcvCFDVDXSdOvsC9qnyW +5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI04Y+oXNZtPdE +ITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0 +90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+z +xiD2BkewhpMl0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYu +QEkHDVneixCwSQXi/5E/S7fdAo74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4 +FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRYYdZ2vyJ/0Adqp2RT8JeNnYA/u8EH +22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJpadbGNjHh/PqAulxP +xOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65xxBzn +dFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5 +Xc0yGYuPjCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7b +nV2UqL1g52KAdoGDDIzMMEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQ +CvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9zZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZH +u/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3rAZ3r2OvEhJn7wAzMMujj +d9qDRIueVSjAi1jTkD5OGwDxFa2DK5o= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj +ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM +9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw +IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 +VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L +93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm +jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA +A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI +U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs +N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv +o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU +5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy +rqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi +9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk +M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB +MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw +CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW +1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK +gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ +W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg +1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K +8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r +2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me +z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR +8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj +mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz +7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6 ++XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI +0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm +UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2 +LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS +k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl +7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm +btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl +urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+ +fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63 +n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE +76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H +9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT +4PsJYGw= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl +ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr +ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr +BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM +YyRIHN8wfdVoOw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6 +MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu +MScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNV +BAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwHhcNMTgwMzE2MTIxMDEzWhcNNDMw +MzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEg +U3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZ +n0EGze2jusDbCSzBfN8pfktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/q +p1x4EaTByIVcJdPTsuclzxFUl6s1wB52HO8AU5853BSlLCIls3Jy/I2z5T4IHhQq +NwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2fJmItdUDmj0VDT06qKhF +8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGtg/BKEiJ3 +HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGa +mqi4NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi +7VdNIuJGmj8PkTQkfVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSF +ytKAQd8FqKPVhJBPC/PgP5sZ0jeJP/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0P +qafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSYnjYJdmZm/Bo/6khUHL4wvYBQ +v3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHKHRzQ+8S1h9E6 +Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 +vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQAD +ggIBAEii1QALLtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4 +WxmB82M+w85bj/UvXgF2Ez8sALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvo +zMrnadyHncI013nR03e4qllY/p0m+jiGPp2Kh2RX5Rc64vmNueMzeMGQ2Ljdt4NR +5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8CYyqOhNf6DR5UMEQ +GfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA4kZf +5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq +0Uc9NneoWWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7D +P78v3DSk+yshzWePS/Tj6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTM +qJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmTOPQD8rv7gmsHINFSH5pkAnuYZttcTVoP +0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZckbxJF0WddCajJFdr60qZf +E2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM +MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D +ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU +cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3 +WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg +Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw +IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH +UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM +TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU +BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM +kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x +AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV +HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y +sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL +I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 +J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY +VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw +CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw +JQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT +EENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2MDcyNDU0WhcNNDMwMzI2MDcyNDU0 +WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBT +LkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAX +BgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATE +KI6rGFtqvm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7Tm +Fy8as10CW4kjPMIRBSqniBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68Kj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI0GZnQkdjrzife81r1HfS+8 +EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjADVS2m5hjEfO/J +UG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0QoSZ/6vn +nvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB +gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu +QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG +A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz +OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ +VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3 +b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA +DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn +0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB +OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE +fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E +Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m +o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i +sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW +OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez +Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS +adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n +3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ +F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf +CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29 +XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm +djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/ +WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb +AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq +P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko +b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj +XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P +5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi +DrW5viSP +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE +BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h +cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1 +MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg +Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9 +thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM +cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG +L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i +NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h +X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b +m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy +Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja +EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T +KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF +6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh +OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1UdDgQWBBRlzeurNR4APn7VdMAc +tHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4wgZswgZgGBFUd +IAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j +b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABC +AG8AbgBhAG4AbwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAw +ADEANzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9m +iWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL4QjbEwj4KKE1soCzC1HA01aajTNF +Sa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDbLIpgD7dvlAceHabJ +hfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1ilI45P +Vf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZE +EAEeiGaPcjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV +1aUsIC+nmCjuRfzxuIgALI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2t +CsvMo2ebKHTEm9caPARYpoKdrcd7b/+Alun4jWq9GJAd/0kakFI3ky88Al2CdgtR +5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH9IBk9W6VULgRfhVwOEqw +f9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpfNIbnYrX9 +ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNK +GbqEZycPvEJdvSRUDewdcAZfpLz6IHxV +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNV +BAUTCUc2MzI4NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlk +YWQgZGUgQ2VydGlmaWNhY2lvbjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNV +BAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3QgQ0EwHhcNMTkwOTA0MTAwMDM4WhcN +MzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEwMQswCQYDVQQGEwJF +UzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQwEgYD +VQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9v +dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCj +cqQZAZ2cC4Ffc0m6p6zzBE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9q +yGFOtibBTI3/TO80sh9l2Ll49a2pcbnvT1gdpd50IJeh7WhM3pIXS7yr/2WanvtH +2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcvB2VSAKduyK9o7PQUlrZX +H1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXsezx76W0OL +zc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyR +p1RMVwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQz +W7i1o0TJrH93PB0j7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/ +SiOL9V8BY9KHcyi1Swr1+KuCLH5zJTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJn +LNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe8TZBAQIvfXOn3kLMTOmJDVb3 +n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVOHj1tyRRM4y5B +u8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj +o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC +AgEATh65isagmD9uw2nAalxJUqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L +9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzxj6ptBZNscsdW699QIyjlRRA96Gej +rw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDtdD+4E5UGUcjohybK +pFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM5gf0 +vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjq +OknkJjCb5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ +/zo1PqVUSlJZS2Db7v54EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ9 +2zg/LFis6ELhDtjTO0wugumDLmsx2d1Hhk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI ++PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGyg77FGr8H6lnco4g175x2 +MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3r5+qPeoo +tt7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr +6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV +L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91 +1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx +MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ +QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB +arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr +Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi +FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS +P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN +9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz +uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h +9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t +OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo ++fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7 +KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2 +DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us +H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ +I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 +5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h +3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz +Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y +ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E +N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9 +tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX +0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c +/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X +KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY +zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS +O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D +34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP +K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3 +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv +Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj +QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS +IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2 +HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa +O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv +033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u +dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE +kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41 +3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD +u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq +4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw +PTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy +dGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9 +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0 +YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANA2 +1B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O5MQT +vqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbed +aFySpvXl8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b0 +1C7jcvk2xusVtyWMOvwlDbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5 +r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGIXsXwClTNSaa/ApzSRKft43jvRl5tcdF5 +cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkNKPl6I7ENPT2a/Z2B7yyQ +wHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQAjeZjOVJ +6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA +2CnbrlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyH +Wyf5QBGenDPBt+U1VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMR +eiFPCyEQtkA6qyI6BJyLm4SGcprSp6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB +/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTgqj8ljZ9EXME66C6u +d0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAszHQNTVfSVcOQr +PbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d +8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi +1wrykXprOQ4vMMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrd +rRT90+7iIgXr0PK3aBLXWopBGsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9di +taY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+gjwN/KUD+nsa2UUeYNrEjvn8K8l7 +lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgHJBu6haEaBQmAupVj +yTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7fpYn +Kx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLy +yCwzk5Iwx06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5n +wXARPbv0+Em34yaXOp/SX3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6 +OV+KmalBWQewLK8= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw +CQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu +bHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ +BgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxGjAYBgNVBAMTEUNlcnRhaW5s +eSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4fxzf7flHh4axpMCK ++IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9YBk2 +QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4 +hevIIgcwCgYIKoZIzj0EAwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozm +ut6Dacpps6kFtZaSF4fC0urQe87YQVt8rgIwRt7qy12a7DLCZRawTDBcMPPaTnOG +BtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw +WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw +MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x +MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD +VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX +BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO +ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M +CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu +I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm +TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh +C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf +ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz +IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT +Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k +JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5 +hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB +GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov +L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo +dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr +aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq +hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L +6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG +HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6 +0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB +lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi +o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1 +gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v +faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63 +Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh +jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw +3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV +BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X +DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ +BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4 +QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny +gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw +zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q +130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2 +JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw +ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT +AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj +AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG +9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h +bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc +fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu +HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w +t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT +AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD +QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP +MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do +0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ +UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d +RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ +OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv +JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C +AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O +BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ +LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY +MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ +44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I +Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw +i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN +9u6wWk5JRFRYX0KD +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV +BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g +Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ +BgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJ +R04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDF +dRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05N0Iw +vlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZ +uIt4ImfkabBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhp +n+Sc8CnTXPnGFiWeI8MgwT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKs +cpc/I1mbySKEwQdPzH/iV8oScLumZfNpdWO9lfsbl83kqK/20U6o2YpxJM02PbyW +xPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91QqhngLjYl/rNUssuHLoPj1P +rCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732jcZZroiF +DsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fx +DTvf95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgy +LcsUDFDYg2WD7rlcz8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6C +eWRgKRM+o/1Pcmqr4tTluCRVLERLiohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSCIS1mxteg4BXrzkwJ +d8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOBywaK8SJJ6ejq +kX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQl +qiCA2ClV9+BB/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0 +OJD7uNGzcgbJceaBxXntC6Z58hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+c +NywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5BiKDUyUM/FHE5r7iOZULJK2v0ZXk +ltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklWatKcsWMy5WHgUyIO +pwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj +03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZk +PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE +1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX +QRBdJ3NghVdJIgc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw +IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL +SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH +SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh +ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X +DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1 +TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ +fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA +sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU +WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS +nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH +dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip +NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC +AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF +MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB +uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl +PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP +JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/ +gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2 +j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6 +5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB +o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS +/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z +Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE +W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D +hNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBP +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xGzAZBgNVBAMMEkhpUEtJIFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRa +Fw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3 +YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kgUm9vdCBDQSAtIEcx +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0o9Qw +qNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twv +Vcg3Px+kwJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6 +lZgRZq2XNdZ1AYDgr/SEYYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnz +Qs7ZngyzsHeXZJzA9KMuH5UHsBffMNsAGJZMoYFL3QRtU6M9/Aes1MU3guvklQgZ +KILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfdhSi8MEyr48KxRURHH+CK +FgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj1jOXTyFj +HluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDr +y+K49a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ +/W3c1pzAtH2lsN0/Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgM +a/aOEmem8rJY5AIJEzypuxC00jBF8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6 +fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQDAgGGMA0GCSqG +SIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi +7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqc +SE5XCV0vrPSltJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6Fza +ZsT0pPBWGTMpWmWSBUdGSquEwx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9Tc +XzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07QJNBAsNB1CI69aO4I1258EHBGG3zg +iLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv5wiZqAxeJoBF1Pho +L5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+GpzjLrF +Ne85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wr +kkVbbiVghUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+ +vhV4nYWBSipX3tUZQ9rbyltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQU +YDksswBVLuT1sw5XxJFBAJw/6KXf6vb/yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQczswBEhb2U14LnNLyaHcZjANBgkqhkiG9w0BAQ0FADBI +MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlE +LVRSVVNUIEJSIFJvb3QgQ0EgMiAyMDIzMB4XDTIzMDUwOTA4NTYzMVoXDTM4MDUw +OTA4NTYzMFowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEi +MCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDIgMjAyMzCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAK7/CVmRgApKaOYkP7in5Mg6CjoWzckjYaCTcfKr +i3OPoGdlYNJUa2NRb0kz4HIHE304zQaSBylSa053bATTlfrdTIzZXcFhfUvnKLNE +gXtRr90zsWh81k5M/itoucpmacTsXld/9w3HnDY25QdgrMBM6ghs7wZ8T1soegj8 +k12b9py0i4a6Ibn08OhZWiihNIQaJZG2tY/vsvmA+vk9PBFy2OMvhnbFeSzBqZCT +Rphny4NqoFAjpzv2gTng7fC5v2Xx2Mt6++9zA84A9H3X4F07ZrjcjrqDy4d2A/wl +2ecjbwb9Z/Pg/4S8R7+1FhhGaRTMBffb00msa8yr5LULQyReS2tNZ9/WtT5PeB+U +cSTq3nD88ZP+npNa5JRal1QMNXtfbO4AHyTsA7oC9Xb0n9Sa7YUsOCIvx9gvdhFP +/Wxc6PWOJ4d/GUohR5AdeY0cW/jPSoXk7bNbjb7EZChdQcRurDhaTyN0dKkSw/bS +uREVMweR2Ds3OmMwBtHFIjYoYiMQ4EbMl6zWK11kJNXuHA7e+whadSr2Y23OC0K+ +0bpwHJwh5Q8xaRfX/Aq03u2AnMuStIv13lmiWAmlY0cL4UEyNEHZmrHZqLAbWt4N +DfTisl01gLmB1IRpkQLLddCNxbU9CZEJjxShFHR5PtbJFR2kWVki3PaKRT08EtY+ +XTIvAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUZ5Dw1t61 +GNVGKX5cq/ieCLxklRAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRCMEAwPqA8oDqG +OGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfYnJfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA097N3U9swFrktpSHxQCF16+tI +FoE9c+CeJyrrd6kTpGoKWloUMz1oH4Guaf2Mn2VsNELZLdB/eBaxOqwjMa1ef67n +riv6uvw8l5VAk1/DLQOj7aRvU9f6QA4w9QAgLABMjDu0ox+2v5Eyq6+SmNMW5tTR +VFxDWy6u71cqqLRvpO8NVhTaIasgdp4D/Ca4nj8+AybmTNudX0KEPUUDAxxZiMrc +LmEkWqTqJwtzEr5SswrPMhfiHocaFpVIbVrg0M8JkiZmkdijYQ6qgYF/6FKC0ULn +4B0Y+qSFNueG4A3rvNTJ1jxD8V1Jbn6Bm2m1iWKPiFLY1/4nwSPFyysCu7Ff/vtD +hQNGvl3GyiEm/9cCnnRK3PgTFbGBVzbLZVzRHTF36SXDw7IyN9XxmAnkbWOACKsG +koHU6XCPpz+y7YaMgmo1yEJagtFSGkUPFaUA8JR7ZSdXOUPPfH/mvTWze/EZTN46 +ls/pdu4D58JDUjxqgejBWoC9EV2Ta/vH5mQ/u2kc6d0li690yVRAysuTEwrt+2aS +Ecr1wPrYg1UDfNPFIkZ1cGt5SAYqgpq/5usWDiJFAbzdNpQ0qTUmiteXue4Icr80 +knCDgKs4qllo3UCkGJCy89UDyibK79XH4I9TjvAA46jtn/mtd+ArY0+ew+43u3gJ +hJ65bvspmZDogNOfJA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQaSYJfoBLTKCnjHhiU19abzANBgkqhkiG9w0BAQ0FADBI +MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlE +LVRSVVNUIEVWIFJvb3QgQ0EgMiAyMDIzMB4XDTIzMDUwOTA5MTAzM1oXDTM4MDUw +OTA5MTAzMlowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEi +MCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDIgMjAyMzCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBANiOo4mAC7JXUtypU0w3uX9jFxPvp1sjW2l1sJkK +F8GLxNuo4MwxusLyzV3pt/gdr2rElYfXR8mV2IIEUD2BCP/kPbOx1sWy/YgJ25yE +7CUXFId/MHibaljJtnMoPDT3mfd/06b4HEV8rSyMlD/YZxBTfiLNTiVR8CUkNRFe +EMbsh2aJgWi6zCudR3Mfvc2RpHJqnKIbGKBv7FD0fUDCqDDPvXPIEysQEx6Lmqg6 +lHPTGGkKSv/BAQP/eX+1SH977ugpbzZMlWGG2Pmic4ruri+W7mjNPU0oQvlFKzIb +RlUWaqZLKfm7lVa/Rh3sHZMdwGWyH6FDrlaeoLGPaxK3YG14C8qKXO0elg6DpkiV +jTujIcSuWMYAsoS0I6SWhjW42J7YrDRJmGOVxcttSEfi8i4YHtAxq9107PncjLgc +jmgjutDzUNzPZY9zOjLHfP7KgiJPvo5iR2blzYfi6NUPGJ/lBHJLRjwQ8kTCZFZx +TnXonMkmdMV9WdEKWw9t/p51HBjGGjp82A0EzM23RWV6sY+4roRIPrN6TagD4uJ+ +ARZZaBhDM7DS3LAaQzXupdqpRlyuhoFBAUp0JuyfBr/CBTdkdXgpaP3F9ev+R/nk +hbDhezGdpn9yo7nELC7MmVcOIQxFAZRl62UJxmMiCzNJkkg8/M3OsD6Onov4/knF +NXJHAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUqvyREBuH +kV8Wub9PS5FeAByxMoAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRCMEAwPqA8oDqG +OGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfZXZfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQCTy6UfmRHsmg1fLBWTxj++EI14 +QvBukEdHjqOSMo1wj/Zbjb6JzkcBahsgIIlbyIIQbODnmaprxiqgYzWRaoUlrRc4 +pZt+UPJ26oUFKidBK7GB0aL2QHWpDsvxVUjY7NHss+jOFKE17MJeNRqrphYBBo7q +3C+jisosketSjl8MmxfPy3MHGcRqwnNU73xDUmPBEcrCRbH0O1P1aa4846XerOhU +t7KR/aypH/KH5BfGSah82ApB9PI+53c0BFLd6IHyTS9URZ0V4U/M5d40VxDJI3IX +cI1QcB9WbMy5/zpaT2N6w25lBx2Eof+pDGOJbbJAiDnXH3dotfyc1dZnaVuodNv8 +ifYbMvekJKZ2t0dT741Jj6m2g1qllpBFYfXeA08mD6iL8AOWsKwV0HFaanuU5nCT +2vFp4LJiTZ6P/4mdm13NRemUAiKN4DV/6PEEeXFsVIP4M7kFMhtYVRFP0OUnR3Hs +7dpn1mKmS00PaaLJvOwiS5THaJQXfuKOKD62xur1NGyfN4gHONuGcfrNlUhDbqNP +gofXNJhuS5N5YHVpD/Aa1VP6IQzCP+k/HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAst +Nl7F6cTVg8uGF5csbBNvh1qvSaYd2804BC5f4ko1Di1L+KIkBI3Y4WNeApI02phh +XBxvWHZks/wCuPWdCg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQsw +CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS +VVNUIEJSIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5 +NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG +A1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB +BAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7dPYS +zuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0 +QVK5buXuQqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/ +VbNafAkl1bK6CKBrqx9tMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g +PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2JyX3Jvb3Rf +Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l +dC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1 +c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO +PQQDAwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFW +wKrY7RjEsK70PvomAjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHV +dWNbFJWcHwHP2NVypw87 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQsw +CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS +VVNUIEVWIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5 +NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG +A1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB +BAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8ZRCC +/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rD +wpdhQntJraOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3 +OqQo5FD4pPfsazK2/umLMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g +PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2V2X3Jvb3Rf +Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l +dC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1 +c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO +PQQDAwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CA +y/m0sRtW9XLS/BnRAjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJb +gfM0agPnIjhQW+0ZT0MW +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw +NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV +BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn +ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0 +3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z +qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR +p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8 +HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw +ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea +HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw +Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh +c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E +RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt +dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku +Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp +3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF +CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na +xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX +KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha +ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM +HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03 +UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42 +tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R +ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM +lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp +/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G +A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy +MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl +cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js +L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL +BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni +acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K +zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8 +PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y +Johw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd +AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC +FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi +1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq +jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ +wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ +WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy +NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC +uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw +IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 +g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP +BSeOE6Fuwg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBj +MQswCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0 +eSBHbWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAy +MDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMyNzIzNTk1OVowYzELMAkGA1UEBhMC +REUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkG +A1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9 +cUD/h3VCKSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHV +cp6R+SPWcHu79ZvB7JPPGeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMA +U6DksquDOFczJZSfvkgdmOGjup5czQRxUX11eKvzWarE4GC+j4NSuHUaQTXtvPM6 +Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWol8hHD/BeEIvnHRz+sTug +BTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9FIS3R/qy +8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73J +co4vzLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg +8qKrBC7m8kwOFjQgrIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8 +rFEz0ciD0cmfHdRHNCk+y7AO+oMLKFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12 +mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7SWWO/gLCMk3PLNaaZlSJhZQNg ++y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtqeX +gj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2 +p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQ +pGv7qHBFfLp+sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm +9S3ul0A8Yute1hTWjOKWi0FpkzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErw +M807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy/SKE8YXJN3nptT+/XOR0so8RYgDd +GGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4mZqTuXNnQkYRIer+ +CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtzaL1t +xKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+ +w6jv/naaoqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aK +L4x35bcF7DvB7L6Gs4a8wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+lj +X273CXE2whJdV/LItM3z7gLfEdxquVeEHVlNjM7IDiPCtyaaEBRx/pOyiriA8A4Q +ntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0o82bNSQ3+pCTE4FCxpgm +dTdmQRCsu/WU48IxK63nI1bMNSWSs1A= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN +8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/ +RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4 +hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5 +ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM +EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1 +A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy +WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ +1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30 +6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT +91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p +TpPDpFQUWw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQsw +CQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBH +bWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIw +MB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIzNTk1OVowYzELMAkGA1UEBhMCREUx +JzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkGA1UE +AwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/O +tdKPD/M12kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDP +f8iAC8GXs7s1J8nCG6NCMEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6f +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA +MGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZMo7k+5Dck2TOrbRBR2Di +z6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdUga/sf+Rn +27iQ7t0l +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp +Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2 +MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ +bmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS +7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp +0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS +B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49 +BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ +LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4 +DXZDjC5Ty3zfDBeWUA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN +MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT +HERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN +NDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs +IEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS87IE+ +ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG0 +2C+JFvuUAT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgp +wgscONyfMXdcvyej/Cestyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZM +pG2T6T867jp8nVid9E6P/DsjyG244gXazOvswzH016cpVIDPRFtMbzCe88zdH5RD +nU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnVDdXifBBiqmvwPXbzP6Po +sMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9qTXeXAaDx +Zre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cd +Lvvyz6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvX +KyY//SovcfXWJL5/MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNe +XoVPzthwiHvOAbWWl9fNff2C+MIkwcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPL +tgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4EFgQUUTMc7TZArxfTJc1paPKv +TiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN +AQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw +GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7H +PNtQOa27PShNlnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLF +O4uJ+DQtpBflF+aZfTCIITfNMBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQ +REtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/u4cnYiWB39yhL/btp/96j1EuMPik +AdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9GOUrYU9DzLjtxpdRv +/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh47a+ +p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilw +MUc/dNAUFvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WF +qUITVuwhd4GTWgzqltlJyqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCK +ovfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c +JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP +mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ +wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 +VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ +AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB +AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun +pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC +dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf +fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm +NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx +H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA +n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc +biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp +EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA +bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu +YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW +BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI +QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I +0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni +lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 +B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv +ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg +RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf +Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q +RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD +AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY +JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv +6pZjamVFkpUBtA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB +CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 +nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt +43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P +T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 +gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR +TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw +DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr +hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg +06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF +PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls +YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe +Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw +EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x +IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG +fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO +Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx +AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ +oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 +sycX +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep ++OkuE6N36B9K +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg +RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y +ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If +xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV +ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO +DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ +jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ +CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi +EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM +fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY +uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK +chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t +9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 +SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd ++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc +fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa +sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N +cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N +0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie +4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI +r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 +/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm +gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa +GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg +Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J +WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB +rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp ++ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1 +ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i +Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz +PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og +/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH +oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI +yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud +EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2 +A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL +MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f +BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn +g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl +fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K +WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha +B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc +hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR +TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD +mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z +ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y +4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza +8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 +MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR +/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu +FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR +U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c +ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR +FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k +A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw +eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl +sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp +VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q +A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ +ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD +ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI +FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv +oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg +u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP +0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf +3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl +8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ +DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN +PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ +ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV +BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu +MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy +MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx +EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe +NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH +PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I +x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe +QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR +yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO +QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912 +H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ +QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD +i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs +nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1 +rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI +hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf +GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb +lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka ++elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal +TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i +nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3 +gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr +G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os +zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x +L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG +EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo +bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ +TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s +b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0 +WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS +fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB +zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq +hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB +CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD ++JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD +VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU +ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH +MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO +MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv +Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz +f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO +8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq +d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM +tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt +Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB +o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD +AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x +PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM +wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d +GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH +6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby +RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE +AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG +EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM +FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC +REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp +Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM +VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ +SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ +4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L +cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi +eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG +A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 +DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j +vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP +DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc +maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D +lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv +KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4w +LAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0w +CwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0 +MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBF +Q0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMHYwEAYHKoZI +zj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6KDP/X +tXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4 +AjJn8ZQSb+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2 +KCXWfeBmmnoJsmo7jjPXNtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMD +aAAwZQIwW5kp85wxtolrbNa9d+F851F+uDrNozZffPc8dz7kUK2o59JZDCaOMDtu +CCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGYa3cpetskz2VAv9LcjBHo +9H1/IISpQuQo +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBM +MS4wLAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIx +MQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00 +MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBD +QSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BBl01Z +4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYv +Ye+W/CBGvevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZ +kmGbzSoXfduP9LVq6hdKZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDs +GY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt0xU6kGpn8bRrZtkh68rZYnxGEFzedUln +nkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVKPNe0OwANwI8f4UDErmwh +3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMYsluMWuPD +0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzy +geBYBr3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8 +ANSbhqRAvNncTFd+rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezB +c6eUWsuSZIKmAMFwoW4sKeFYV+xafJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lI +pw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +dEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +DAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS +4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPs +o0UvFJ/1TCplQ3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJ +qM7F78PRreBrAwA0JrRUITWXAdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuyw +xfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9GslA9hGCZcbUztVdF5kJHdWoOsAgM +rr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2VktafcxBPTy+av5EzH4 +AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9qTFsR +0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuY +o7Ey7Nmj1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5 +dDTedk+SKlOxJTnbPP/lPqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcE +oji2jbDwN/zIIX8/syQbPYtuzE2wFg2WHYMfRsCbvUOZ58SWLs5fyQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx +CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD +ExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw +MDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2Ex +HDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkBjtjq +R+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGdd +yXqBPCCjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ +7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZkvLtoURMMA/cVi4RguYv/Uo7njLwcAjA8 ++RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc +8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke +hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI +KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg +515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO +xwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 +MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 +RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT +gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm +KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd +QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ +XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o +LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU +RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp +jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK +6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX +mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs +Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH +WD9f +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg +MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx +MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET +MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI +xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k +ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD +aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw +LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw +1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX +k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2 +SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h +bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n +WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY +rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce +MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu +bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt +Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61 +55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj +vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf +cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz +oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp +nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs +pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v +JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R +8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4 +5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA +MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD +VQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy +MDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYt +c2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08EsCVeJ +OaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQG +vGIFAha/r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud +316HCkD7rRlr+/fKYIje2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo +0q3v84RLHIf8E6M6cqJaESvWJ3En7YEtbWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSE +y132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvjK8Cd+RTyG/FWaha/LIWF +zXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD412lPFzYE ++cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCN +I/onccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzs +x2sZy/N78CsHpdlseVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqa +ByFrgY/bxFn63iLABJzjqls2k+g9vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC +4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEMBQADggIBAHx4 +7PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti +2kM3S+LGteWygxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIk +pnnpHs6i58FZFZ8d4kuaPp92CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRF +FRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZmOUdkLG5NrmJ7v2B0GbhWrJKsFjLt +rWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qqJZ4d16GLuc1CLgSk +ZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwyeqiv5 +u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP +4vkYxboznxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6 +N3ec592kD3ZDZopD8p/7DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3 +vouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT +EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp +ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz +NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH +EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE +AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD +E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH +/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy +DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh +GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR +tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA +AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX +WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu +9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr +gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo +2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI +4uJEvlz36hz1 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs +ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw +MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj +aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp +Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg +nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 +HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N +Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN +dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 +HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G +CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU +sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 +4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg +8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 +mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD +VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG +A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw +WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz +IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout736G +jOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL2 +4CejQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7 +VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azTL818+FsuVbu/3ZL3pAzcMeGiAjEA/Jdm +ZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD +VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG +A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw +WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz +IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi +QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR +HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D +9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8 +p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw +CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw +MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp +Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo +27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w +Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw +TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl +qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH +szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8 +Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk +MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92 +wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p +aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN +VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID +AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb +C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe +QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy +h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4 +7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J +ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef +MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/ +Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT +6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ +0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm +2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb +bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw +CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +MBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw +MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp +Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3LvCvpt +nfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY +6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAu +MC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7k +RXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWg +f9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1mKPV ++3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K8Yzo +dDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW +Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKa +G73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCq +gc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwID +AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBAB/Kzt3H +vqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8 +0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyC +B19m3H0Q/gxhswWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2u +NmSRXbBoGOqKYcl3qJfEycel/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMg +yALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVnjWQye+mew4K6Ki3pHrTgSAai/Gev +HyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y59PYjJbigapordwj6 +xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M7YNR +TOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924Sg +JPFI/2R80L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV +7LXTWtiBmelDGDfrs7vRWGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl +6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjWHYbL +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD +VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw +MTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0g +UjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wWTAT +BgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkWymOx +uYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNV +HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/ ++wpu+74zyTyjhNUwCgYIKoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147 +bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE +AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw +CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ +BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND +VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb +qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY +HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo +G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA +lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr +IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/ +0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH +k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47 +4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO +m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa +cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl +uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI +KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls +ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG +AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT +VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG +CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA +cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA +QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA +7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA +cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA +QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA +czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu +aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt +aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud +DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF +BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp +D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU +JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m +AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD +vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms +tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH +7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA +h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF +d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H +pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx +CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ +WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ +BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG +Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/ +yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf +BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz +WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF +tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z +374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC +IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL +mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7 +wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS +MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2 +ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet +UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H +YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3 +LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1 +RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM +LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf +77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N +JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm +fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp +6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp +1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B +9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok +RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv +uu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw +CQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw +FgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S +Q00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4MTIyMDA5MzczM1oXDTQzMTIyMDA5 +MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQtUkNNMQ4wDAYDVQQL +DAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNBQyBS +QUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LH +sbI6GA60XYyzZl2hNPk2LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oK +Um8BA06Oi6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqGSM49BAMDA2kAMGYCMQCu +SuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoDzBOQn5IC +MQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJy +v+c= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx +GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp +bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w +KwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0 +BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy +dW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG +EwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll +IEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU +QUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT +TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg +LSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7 +a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr +LqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr +N3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X +YacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/ +iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f +AJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH +V8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf +IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4 +lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c +8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf +lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs +MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg +Um9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUzOFoXDTQ1MDIxMzEwNTUzN1owbDEL +MAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNBIFJv +b3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569l +mwVnlskNJLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE +4VGC/6zStGndLuwRo0Xua2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uv +a9of08WRiFukiZLRgeaMOVig1mlDqa2YUlhu2wr7a89o+uOkXjpFc5gH6l8Cct4M +pbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K5FrZx40d/JiZ+yykgmvw +Kh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEvdmn8kN3b +LW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcY +AuUR0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqB +AGMUuTNe3QvboEUHGjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYq +E613TBoYm5EPWNgGVMWX+Ko/IIqmhaZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHr +W2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQCPxrvrNQKlr9qEgYRtaQQJKQ +CoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAU +X15QvWiWkKQUEapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3 +f5Z2EMVGpdAgS1D0NTsY9FVqQRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxaja +H6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxDQpSbIPDRzbLrLFPCU3hKTwSUQZqP +JzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcRj88YxeMn/ibvBZ3P +zzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5vZSt +jBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0 +/L5H9MG0qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pT +BGIBnfHAT+7hOtSLIBD6Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79 +aPib8qXPMThcFarmlwDB31qlpzmq6YR/PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YW +xw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnnkf3/W9b3raYvAwtt41dU +63ZTGI0RmLo= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw +CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh +cmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v +dCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoXDTQ1MDIxMzExMDEwOVowbDELMAkG +A1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj +aCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJvb3Qg +Q0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7 +KKrxcm1lAEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9Y +STHMmE5gEYd103KUkE+bECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQD +AgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAircJRQO9gcS3ujwLEXQNw +SaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/QwCZ61IygN +nxS2PFOiTAZpffpskcYqSUXm7LcT4Tps +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu +VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw +MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw +JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT +3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU ++ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp +S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1 +bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi +T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL +vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK +Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK +dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT +c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv +l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N +iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD +ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt +LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93 +nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3 ++wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK +W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT +AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq +l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG +4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ +mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A +7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw +CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg +R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00 +MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT +ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW ++1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9 +ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI +zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW +tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1 +/q4AaOeMSQ+2b1tbFfLn +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4 +MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6 +ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD +VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j +b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq +scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO +xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H +LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX +uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD +yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+ +JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q +rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN +BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L +hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB +QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+ +HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu +Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg +QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB +BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA +A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb +laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56 +awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo +JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw +LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT +VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk +LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb +UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/ +QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+ +naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls +QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6 +ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw +NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L +cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg +Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN +QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT +3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw +3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6 +3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5 +BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN +XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF +AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw +8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG +nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP +oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy +d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg +LvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G +CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y +OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx +FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp +Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP +kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc +cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U +fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7 +N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC +xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1 ++rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM +Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG +SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h +mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk +ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c +2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t +HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV +BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk +LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv +b3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ +BgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg +THRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v +IFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv +xie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H +Wyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB +eAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo +jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ ++efcMQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD +VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw +MTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4MjMxNjA0WjBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNy +b3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZR +ogPZnZH6thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYb +hGBKia/teQ87zvH2RPUBeMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3 +FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV +L8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB +iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl +MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N +aWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ +Nt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0 +ZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1 +HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm +gGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ +jEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc +aDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG +YaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6 +W6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K +UGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH ++FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q +W5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC +LgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC +gMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6 +tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh +SnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2 +TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3 +pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR +xpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp +GWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9 +dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN +AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB +RA+GsCyRxj3qrg+E +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM +BQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG +T1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4NDJaFw0zNzA4MTgyMzU5NTlaMGkx +CzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVTUyBQTEFURk9STSBD +b3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVA +iQqrDZBbUGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH +38dq6SZeWYp34+hInDEW+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lE +HoSTGEq0n+USZGnQJoViAbbJAh2+g1G7XNr4rRVqmfeSVPc0W+m/6imBEtRTkZaz +kVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2aacp+yPOiNgSnABIqKYP +szuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4Yb8Obtoq +vC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHf +nZ3zVHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaG +YQ5fG8Ir4ozVu53BA0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo +0es+nPxdGoMuK8u180SdOqcXYZaicdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3a +CJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejyYhbLgGvtPe31HzClrkvJE+2K +AQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNVHQ4EFgQU0p+I +36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoN +qo0hV4/GPnrK21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatj +cu3cvuzHV+YwIHHW1xDBE1UBjCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm ++LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bxhYTeodoS76TiEJd6eN4MUZeoIUCL +hr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTgE34h5prCy8VCZLQe +lHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTHD8z7 +p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8 +piKCk5XQA76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLR +LBT/DShycpWbXgnbiUSYqqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX +5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oGI/hGoiLtk/bdmuYqh7GYVPEi92tF4+KO +dh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul +9XXeifdy +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG +EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 +MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl +cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR +dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB +pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM +b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm +aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz +IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT +lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz +AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5 +VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG +ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2 +BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG +AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M +U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh +bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C ++C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F +uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 +XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw +CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91 +bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg +Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ +BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu +ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS +b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni +eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W +p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T +rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV +57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg +Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt +MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg +Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i +YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x +CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG +b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3 +HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx +WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX +1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk +u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P +99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r +M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB +BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh +cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5 +gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO +ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf +aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYT +AkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYD +VQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYx +NjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTELMAkGA1UEBhMCSlAxJTAjBgNVBAoT +HFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNVBAMTIlNlY3VyaXR5 +IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+Cnnfdl +dB9sELLo5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpK +ULGjQjBAMB0GA1UdDgQWBBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu +9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3LsnNdo4gIxwwCMQDAqy0O +be0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70eN9k= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl +MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe +U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX +DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy +dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj +YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV +OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr +zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM +VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ +hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO +ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw +awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs +OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF +coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc +okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8 +t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy +1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/ +SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQsw +CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T +ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcN +MjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYG +A1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT +ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccC +WvkEN/U0NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+ +6xnOQ6OjQjBAMB0GA1UdDgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8B +Af8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNnADBkAjAn7qRa +qCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RHlAFWovgzJQxC36oCMB3q +4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21USAGKcw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT +IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw +MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy +ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N +T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR +FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J +cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW +BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm +fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv +GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID0DCCArigAwIBAgIQIKTEf93f4cdTYwcTiHdgEjANBgkqhkiG9w0BAQUFADCB +gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV +BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xMTAxMDEwMDAw +MDBaFw0zMDEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl +YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P +RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3 +UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI +2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8 +Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp ++2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+ +DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O +nKVIrLsm9wIDAQABo0IwQDAdBgNVHQ4EFgQUC1jli8ZMFTekQKkwqSG+RzZaVv8w +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBAC/JxBwHO89hAgCx2SFRdXIDMLDEFh9sAIsQrK/xR9SuEDwMGvjUk2ysEDd8 +t6aDZK3N3w6HM503sMZ7OHKx8xoOo/lVem0DZgMXlUrxsXrfViEGQo+x06iF3u6X +HWLrp+cxEmbDD6ZLLkGC9/3JG6gbr+48zuOcrigHoSybJMIPIyaDMouGDx8rEkYl +Fo92kANr3ryqImhrjKGsKxE5pttwwn1y6TPn/CbxdFqR5p2ErPioBhlG5qfpqjQi +pKGfeq23sqSaM4hxAjwu1nqyH6LKwN0vEJT9s4yEIHlG1QXUEOTS22RPuFvuG8Ug +R1uUq27UlTMdphVx8fiUylQ5PsE= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB +hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV +BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT +EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR +6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X +pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC +9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV +/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf +Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z ++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w +qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah +SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC +u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf +Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq +crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl +wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM +4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV +2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna +FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ +CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK +boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke +jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL +S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb +QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl +0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB +NVOFBkpdn627G190 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl +eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT +JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg +VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo +I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng +o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G +A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB +zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW +RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBf +MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD +Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw +HhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEY +MBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1Ymxp +YyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDa +ef0rty2k1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnz +SDBh+oF8HqcIStw+KxwfGExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xf +iOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMPFF1bFOdLvt30yNoDN9HWOaEhUTCDsG3X +ME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vuZDCQOc2TZYEhMbUjUDM3 +IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5QazYw6A3OAS +VYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgE +SJ/AwSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu ++Zd4KKTIRJLpfSYFplhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt +8uaZFURww3y8nDnAtOFr94MlI1fZEoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+L +HaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW6aWWrL3DkJiy4Pmi1KZHQ3xt +zwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWIIUkwDgYDVR0P +AQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c +mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQ +YKlJfp/imTYpE0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52 +gDY9hAaLMyZlbcp+nv4fjFg4exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZA +Fv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M0ejf5lG5Nkc/kLnHvALcWxxPDkjB +JYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI84HxZmduTILA7rpX +DhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9mpFui +TdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5 +dHn5HrwdVw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65 +LvKRRFHQV80MNNVIIb/bE/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp +0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmmJ1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAY +QqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz +MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv +cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz +Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO +0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao +wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj +7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS +8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT +BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg +JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3 +6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/ +3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm +D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS +CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf +BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 +YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x +NzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYDVQQGEwJVUzERMA8G +A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 +d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF +Q0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqG +SM49AwEHA0IABH77bOYj43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoN +FWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqmP62jQzBBMA8GA1UdEwEB/wQFMAMBAf8w +DwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt0UrrdaVKEJmzsaGLSvcw +CgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjzRM4q3wgh +DDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBO +MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQD +DBxTU0wuY29tIFRMUyBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloX +DTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jw +b3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJvb3QgQ0EgMjAyMjCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u9nTP +L3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OY +t6/wNr/y7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0ins +S657Lb85/bRi3pZ7QcacoOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3 +PnxEX4MN8/HdIGkWCVDi1FW24IBydm5MR7d1VVm0U3TZlMZBrViKMWYPHqIbKUBO +L9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDGD6C1vBdOSHtRwvzpXGk3 +R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEWTO6Af77w +dr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS ++YCk8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYS +d66UNHsef8JmAOSqg+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoG +AtUjHBPW6dvbxrB6y3snm/vg1UYk7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2f +gTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j +BBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsuN+7jhHonLs0Z +NbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt +hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsM +QtfhWsSWTVTNj8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvf +R4iyrT7gJ4eLSYwfqUdYe5byiB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJ +DPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjUo3KUQyxi4U5cMj29TH0ZR6LDSeeW +P4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqoENjwuSfr98t67wVy +lrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7EgkaibMOlq +bLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2w +AgDHbICivRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3q +r5nsLFR+jM4uElZI7xc7P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sji +Mho6/4UIyYOf8kpIEFR3N+2ivEC+5BB09+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU +98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQsw +CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxT +U0wuY29tIFRMUyBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2 +MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3Jh +dGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3QgQ0EgMjAyMjB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWyJGYm +acCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFN +SeR7T5v15wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME +GDAWgBSJjy+j6CugFFR781a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NW +uCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp +15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w7deedWo1dlJF4AIxAMeN +b0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5Zn6g6g== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV +BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE +CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy +MDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G +A1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD +DC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq +M0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf +OePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa +4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9 +HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR +aZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA +b9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ +Gp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV +PWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO +pgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu +UDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY +MBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4 +9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW +s47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5 +Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg +cLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM +79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz +/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt +ll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm +Kf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK +QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ +w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi +S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07 +mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQsw +CQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28x +ITAfBgNVBAoMGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1 +c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMx +OTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJVUzERMA8GA1UECAwI +SWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +ALldUShLPDeS0YLOvR29zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0Xzn +swuvCAAJWX/NKSqIk4cXGIDtiLK0thAfLdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu +7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4BqstTnoApTAbqOl5F2brz8 +1Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9oWN0EACyW +80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotP +JqX+OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1l +RtzuzWniTY+HKE40Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfw +hI0Vcnyh78zyiGG69Gm7DIwLdVcEuE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10 +coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm+9jaJXLE9gCxInm943xZYkqc +BW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqjifLJS3tBEW1n +twiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1Ud +DwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W +0OhUKDtkLSGm+J1WE2pIPU/HPinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfe +uyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0HZJDmHvUqoai7PF35owgLEQzxPy0Q +lG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla4gt5kNdXElE1GYhB +aCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5RvbbE +sLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPT +MaCm/zjdzyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qe +qu5AvzSxnI9O4fKSTx+O856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxh +VicGaeVyQYHTtgGJoC86cnn+OjC/QezHYj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8 +h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu3R3y4G5OBVixwJAWKqQ9 +EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP29FpHOTK +yeC2nOnOcXHebD8WpHk= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE +BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK +DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz +OTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R +xFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX +qhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC +C52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3 +6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh +/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF +YD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E +JNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc +US4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8 +ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm ++Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi +M+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G +A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV +cpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc +Hadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs +PgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/ +q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0 +cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr +a6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I +H37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y +K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu +nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf +oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY +Ic2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf +BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 +YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x +NzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYDVQQGEwJVUzERMA8G +A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 +d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF +Q0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuB +BAAiA2IABGvaDXU1CDFHBa5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJ +j9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr/TklZvFe/oyujUF5nQlgziip04pt89ZF +1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwYAMB0G +A1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNnADBkAjA3 +AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsC +MGclCrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVu +Sw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz +WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0 +b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS +b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI +7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg +CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD +VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T +kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+ +gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx +NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv +bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA +VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku +WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP +MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX +5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ +ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg +h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV +BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln +biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF +MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT +d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8 +76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+ +bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c +6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE +emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd +MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt +MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y +MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y +FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi +aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM +gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB +qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7 +lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn +8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov +L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6 +45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO +UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5 +O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC +bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv +GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a +77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC +hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3 +92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp +Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w +ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt +Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFkzCCA3ugAwIBAgIUQ/oMX04bgBhE79G0TzUfRPSA7cswDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzErMCkGA1UE +AxMiU3dpc3NTaWduIFJTQSBUTFMgUm9vdCBDQSAyMDIyIC0gMTAeFw0yMjA2MDgx +MTA4MjJaFw00NzA2MDgxMTA4MjJaMFExCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxT +d2lzc1NpZ24gQUcxKzApBgNVBAMTIlN3aXNzU2lnbiBSU0EgVExTIFJvb3QgQ0Eg +MjAyMiAtIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDLKmjiC8NX +vDVjvHClO/OMPE5Xlm7DTjak9gLKHqquuN6orx122ro10JFwB9+zBvKK8i5VUXu7 +LCTLf5ImgKO0lPaCoaTo+nUdWfMHamFk4saMla+ju45vVs9xzF6BYQ1t8qsCLqSX +5XH8irCRIFucdFJtrhUnWXjyCcplDn/L9Ovn3KlMd/YrFgSVrpxxpT8q2kFC5zyE +EPThPYxr4iuRR1VPuFa+Rd4iUU1OKNlfGUEGjw5NBuBwQCMBauTLE5tzrE0USJIt +/m2n+IdreXXhvhCxqohAWVTXz8TQm0SzOGlkjIHRI36qOTw7D59Ke4LKa2/KIj4x +0LDQKhySio/YGZxH5D4MucLNvkEM+KRHBdvBFzA4OmnczcNpI/2aDwLOEGrOyvi5 +KaM2iYauC8BPY7kGWUleDsFpswrzd34unYyzJ5jSmY0lpx+Gs6ZUcDj8fV3oT4MM +0ZPlEuRU2j7yrTrePjxF8CgPBrnh25d7mUWe3f6VWQQvdT/TromZhqwUtKiE+shd +OxtYk8EXlFXIC+OCeYSf8wCENO7cMdWP8vpPlkwGqnj73mSiI80fPsWMvDdUDrta +clXvyFu1cvh43zcgTFeRc5JzrBh3Q4IgaezprClG5QtO+DdziZaKHG29777YtvTK +wP1H8K4LWCDFyB02rpeNUIMmJCn3nTsPBQIDAQABo2MwYTAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBRvjmKLk0Ow4UD2p8P98Q+4 +DxU4pTAdBgNVHQ4EFgQUb45ii5NDsOFA9qfD/fEPuA8VOKUwDQYJKoZIhvcNAQEL +BQADggIBAKwsKUF9+lz1GpUYvyypiqkkVHX1uECry6gkUSsYP2OprphWKwVDIqO3 +10aewCoSPY6WlkDfDDOLazeROpW7OSltwAJsipQLBwJNGD77+3v1dj2b9l4wBlgz +Hqp41eZUBDqyggmNzhYzWUUo8aWjlw5DI/0LIICQ/+Mmz7hkkeUFjxOgdg3XNwwQ +iJb0Pr6VvfHDffCjw3lHC1ySFWPtUnWK50Zpy1FVCypM9fJkT6lc/2cyjlUtMoIc +gC9qkfjLvH4YoiaoLqNTKIftV+Vlek4ASltOU8liNr3CjlvrzG4ngRhZi0Rjn9UM +ZfQpZX+RLOV/fuiJz48gy20HQhFRJjKKLjpHE7iNvUcNCfAWpO2Whi4Z2L6MOuhF +LhG6rlrnub+xzI/goP+4s9GFe3lmozm1O2bYQL7Pt2eLSMkZJVX8vY3PXtpOpvJp +zv1/THfQwUY1mFwjmwJFQ5Ra3bxHrSL+ul4vkSkphnsh3m5kt8sNjzdbowhq6/Td +Ao9QAwKxuDdollDruF/UKIqlIgyKhPBZLtU30WHlQnNYKoH3dtvi4k0NX/a3vgW0 +rk4N3hY9A4GzJl5LuEsAz/+MF7psYC0nhzck5npgL7XTgwSqT0N1osGDsieYK7EO +gLrAhV5Cud+xYJHT6xh+cHiudoO+cVrQkOPKwRYlZ0rwtnu64ZzZ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx +EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT +VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5 +NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT +B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF +10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz +0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh +MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH +zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc +46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2 +yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi +laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP +oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA +BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE +qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm +4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL +1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF +H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo +RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+ +nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh +15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW +6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW +nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j +wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz +aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy +KwbQBM0= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQ +MQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290 +IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5 +WhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FO +LUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1sTs6P +40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxF +avcokPFhV8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/ +34bKS1PE2Y2yHer43CdTo0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684i +JkXXYJndzk834H/nY62wuFm40AZoNWDTNq5xQwTxaWV4fPMf88oon1oglWa0zbfu +j3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK/c/WMw+f+5eesRycnupf +Xtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkHIuNZW0CP +2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDA +S9TMfAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDA +oS/xUgXJP+92ZuJF2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzC +kHDXShi8fgGwsOsVHkQGzaRP6AzRwyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW +5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83QOGt4A1WNzAd +BgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB +AGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0t +tGlTITVX1olNc79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn +68xDiBaiA9a5F/gZbG0jAn/xX9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNn +TKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDRIG4kqIQnoVesqlVYL9zZyvpoBJ7t +RCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq/p1hvIbZv97Tujqx +f36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0RFxbI +Qh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz +8ppy6rBePm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4 +NxKfKjLji7gh7MMrZQzvIt6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzX +xeSDwWrruoBa3lwtcHb4yOWHh8qgnaHlIhInD0Q9HWzq1MKLL295q39QpsQZp6F6 +t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQx +CzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UE +AwwQVGVsaWEgUm9vdCBDQSB2MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1 +NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZ +MBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ76zBq +AMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9 +vVYiQJ3q9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9 +lRdU2HhE8Qx3FZLgmEKnpNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTOD +n3WhUidhOPFZPY5Q4L15POdslv5e2QJltI5c0BE0312/UqeBAMN/mUWZFdUXyApT +7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW5olWK8jjfN7j/4nlNW4o +6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNrRBH0pUPC +TEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6 +WT0EBXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63R +DolUK5X6wK0dmBR4M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZI +pEYslOqodmJHixBTB0hXbOKSTbauBcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGj +YzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7Wxy+G2CQ5MB0GA1UdDgQWBBRy +rOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ +8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi +0f6X+J8wfBj5tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMM +A8iZGok1GTzTyVR8qPAs5m4HeW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBS +SRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+Cy748fdHif64W1lZYudogsYMVoe+K +TTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygCQMez2P2ccGrGKMOF +6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15h2Er +3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMt +Ty3EHD70sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pT +VmBds9hCG1xLEooc6+t9xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAW +ysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQraVplI/owd8k+BsHMYeB2F326CjYSlKA +rBPuUBQemMc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw +NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv +b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD +VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2 +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F +VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1 +7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X +Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+ +/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs +81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm +dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe +Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu +sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4 +pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs +slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ +arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD +VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG +9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl +dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj +TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed +Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7 +Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI +OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7 +vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW +t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn +HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx +SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- diff --git a/common/certificate/mozilla.go b/common/certificate/mozilla.go new file mode 100644 index 0000000000..551b00c447 --- /dev/null +++ b/common/certificate/mozilla.go @@ -0,0 +1,4264 @@ +// Code generated by 'make update_certificates'. DO NOT EDIT. + +package certificate + +func mozillaIncludedPEM() string { + return ` +// Actalis Authentication Root CA +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE +BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w +MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC +SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1 +ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv +UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX +4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9 +KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/ +gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb +rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ +51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F +be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe +KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F +v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn +fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7 +jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz +ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL +e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70 +jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz +WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V +SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j +pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX +X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok +fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R +K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU +ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU +LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT +LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +// TunTrust Root CA +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL +BQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg +Q2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv +b3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQwNDI2MDg1NzU2WjBhMQswCQYDVQQG +EwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBDZXJ0aWZpY2F0aW9u +IEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZ +n56eY+hz2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd +2JQDoOw05TDENX37Jk0bbjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgF +VwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZ +GoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAdgjH8KcwAWJeRTIAAHDOF +li/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViWVSHbhlnU +r8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2 +eY8fTpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIb +MlEsPvLfe/ZdeikZjuXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISg +jwBUFfyRbVinljvrS5YnzWuioYasDXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB +7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwSVXAkPcvCFDVDXSdOvsC9qnyW +5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI04Y+oXNZtPdE +ITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0 +90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+z +xiD2BkewhpMl0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYu +QEkHDVneixCwSQXi/5E/S7fdAo74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4 +FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRYYdZ2vyJ/0Adqp2RT8JeNnYA/u8EH +22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJpadbGNjHh/PqAulxP +xOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65xxBzn +dFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5 +Xc0yGYuPjCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7b +nV2UqL1g52KAdoGDDIzMMEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQ +CvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9zZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZH +u/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3rAZ3r2OvEhJn7wAzMMujj +d9qDRIueVSjAi1jTkD5OGwDxFa2DK5o= +-----END CERTIFICATE----- + +// Amazon Root CA 1 +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj +ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM +9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw +IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 +VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L +93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm +jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA +A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI +U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs +N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv +o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU +5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy +rqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- + +// Amazon Root CA 2 +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK +gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ +W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg +1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K +8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r +2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me +z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR +8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj +mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz +7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6 ++XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI +0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm +UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2 +LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS +k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl +7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm +btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl +urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+ +fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63 +n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE +76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H +9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT +4PsJYGw= +-----END CERTIFICATE----- + +// Amazon Root CA 3 +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl +ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr +ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr +BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM +YyRIHN8wfdVoOw== +-----END CERTIFICATE----- + +// Amazon Root CA 4 +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi +9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk +M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB +MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw +CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW +1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- + +// Starfield Services Root Certificate Authority - G2 +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs +ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD +VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy +ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy +dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p +OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2 +8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K +Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe +hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk +6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q +AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI +bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB +ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z +qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn +0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN +sSi6 +-----END CERTIFICATE----- + +// Certum CA +-----BEGIN CERTIFICATE----- +MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBM +MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD +QTAeFw0wMjA2MTExMDQ2MzlaFw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBM +MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD +QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6xwS7TT3zNJc4YPk/E +jG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdLkKWo +ePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GI +ULdtlkIJ89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapu +Ob7kky/ZR6By6/qmW6/KUz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUg +AKpoC6EahQGcxEZjgoi2IrHu/qpGWX7PNSzVttpd90gzFFS269lvzs2I1qsb2pY7 +HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA +uI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+GXYkHAQa +TOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTg +xSvgGrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1q +CjqTE5s7FCMTY5w/0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5x +O/fIR/RpbxXyEV6DHpx8Uq79AtoSqFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs +6GAqm4VKQPNriiTsBhYscw== +-----END CERTIFICATE----- + +// Certum EC-384 CA +-----BEGIN CERTIFICATE----- +MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw +CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw +JQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT +EENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2MDcyNDU0WhcNNDMwMzI2MDcyNDU0 +WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBT +LkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAX +BgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATE +KI6rGFtqvm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7Tm +Fy8as10CW4kjPMIRBSqniBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68Kj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI0GZnQkdjrzife81r1HfS+8 +EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjADVS2m5hjEfO/J +UG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0QoSZ/6vn +nvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= +-----END CERTIFICATE----- + +// Certum Trusted Network CA +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM +MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D +ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU +cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3 +WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg +Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw +IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH +UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM +TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU +BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM +kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x +AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV +HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y +sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL +I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 +J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY +VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +// Certum Trusted Network CA 2 +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB +gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu +QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG +A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz +OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ +VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3 +b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA +DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn +0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB +OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE +fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E +Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m +o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i +sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW +OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez +Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS +adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n +3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ +F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf +CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29 +XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm +djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/ +WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb +AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq +P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko +b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj +XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P +5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi +DrW5viSP +-----END CERTIFICATE----- + +// Certum Trusted Root CA +-----BEGIN CERTIFICATE----- +MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6 +MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu +MScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNV +BAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwHhcNMTgwMzE2MTIxMDEzWhcNNDMw +MzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEg +U3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZ +n0EGze2jusDbCSzBfN8pfktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/q +p1x4EaTByIVcJdPTsuclzxFUl6s1wB52HO8AU5853BSlLCIls3Jy/I2z5T4IHhQq +NwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2fJmItdUDmj0VDT06qKhF +8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGtg/BKEiJ3 +HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGa +mqi4NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi +7VdNIuJGmj8PkTQkfVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSF +ytKAQd8FqKPVhJBPC/PgP5sZ0jeJP/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0P +qafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSYnjYJdmZm/Bo/6khUHL4wvYBQ +v3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHKHRzQ+8S1h9E6 +Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 +vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQAD +ggIBAEii1QALLtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4 +WxmB82M+w85bj/UvXgF2Ez8sALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvo +zMrnadyHncI013nR03e4qllY/p0m+jiGPp2Kh2RX5Rc64vmNueMzeMGQ2Ljdt4NR +5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8CYyqOhNf6DR5UMEQ +GfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA4kZf +5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq +0Uc9NneoWWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7D +P78v3DSk+yshzWePS/Tj6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTM +qJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmTOPQD8rv7gmsHINFSH5pkAnuYZttcTVoP +0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZckbxJF0WddCajJFdr60qZf +E2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb +-----END CERTIFICATE----- + +// Autoridad de Certificacion Firmaprofesional CIF A62634068 +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE +BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h +cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1 +MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg +Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9 +thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM +cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG +L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i +NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h +X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b +m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy +Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja +EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T +KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF +6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh +OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1UdDgQWBBRlzeurNR4APn7VdMAc +tHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4wgZswgZgGBFUd +IAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j +b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABC +AG8AbgBhAG4AbwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAw +ADEANzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9m +iWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL4QjbEwj4KKE1soCzC1HA01aajTNF +Sa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDbLIpgD7dvlAceHabJ +hfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1ilI45P +Vf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZE +EAEeiGaPcjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV +1aUsIC+nmCjuRfzxuIgALI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2t +CsvMo2ebKHTEm9caPARYpoKdrcd7b/+Alun4jWq9GJAd/0kakFI3ky88Al2CdgtR +5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH9IBk9W6VULgRfhVwOEqw +f9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpfNIbnYrX9 +ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNK +GbqEZycPvEJdvSRUDewdcAZfpLz6IHxV +-----END CERTIFICATE----- + +// ANF Secure Server Root CA +-----BEGIN CERTIFICATE----- +MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNV +BAUTCUc2MzI4NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlk +YWQgZGUgQ2VydGlmaWNhY2lvbjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNV +BAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3QgQ0EwHhcNMTkwOTA0MTAwMDM4WhcN +MzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEwMQswCQYDVQQGEwJF +UzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQwEgYD +VQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9v +dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCj +cqQZAZ2cC4Ffc0m6p6zzBE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9q +yGFOtibBTI3/TO80sh9l2Ll49a2pcbnvT1gdpd50IJeh7WhM3pIXS7yr/2WanvtH +2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcvB2VSAKduyK9o7PQUlrZX +H1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXsezx76W0OL +zc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyR +p1RMVwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQz +W7i1o0TJrH93PB0j7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/ +SiOL9V8BY9KHcyi1Swr1+KuCLH5zJTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJn +LNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe8TZBAQIvfXOn3kLMTOmJDVb3 +n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVOHj1tyRRM4y5B +u8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj +o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC +AgEATh65isagmD9uw2nAalxJUqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L +9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzxj6ptBZNscsdW699QIyjlRRA96Gej +rw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDtdD+4E5UGUcjohybK +pFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM5gf0 +vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjq +OknkJjCb5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ +/zo1PqVUSlJZS2Db7v54EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ9 +2zg/LFis6ELhDtjTO0wugumDLmsx2d1Hhk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI ++PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGyg77FGr8H6lnco4g175x2 +MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3r5+qPeoo +tt7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw= +-----END CERTIFICATE----- + +// Buypass Class 2 Root CA +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr +6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV +L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91 +1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx +MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ +QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB +arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr +Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi +FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS +P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN +9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz +uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h +9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t +OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo ++fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7 +KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2 +DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us +H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ +I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 +5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h +3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz +Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= +-----END CERTIFICATE----- + +// Buypass Class 3 Root CA +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y +ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E +N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9 +tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX +0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c +/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X +KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY +zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS +O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D +34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP +K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3 +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv +Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj +QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS +IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2 +HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa +O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv +033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u +dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE +kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41 +3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD +u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq +4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= +-----END CERTIFICATE----- + +// Certainly Root E1 +-----BEGIN CERTIFICATE----- +MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw +CQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu +bHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ +BgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxGjAYBgNVBAMTEUNlcnRhaW5s +eSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4fxzf7flHh4axpMCK ++IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9YBk2 +QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4 +hevIIgcwCgYIKoZIzj0EAwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozm +ut6Dacpps6kFtZaSF4fC0urQe87YQVt8rgIwRt7qy12a7DLCZRawTDBcMPPaTnOG +BtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR +-----END CERTIFICATE----- + +// Certainly Root R1 +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw +PTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy +dGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9 +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0 +YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANA2 +1B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O5MQT +vqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbed +aFySpvXl8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b0 +1C7jcvk2xusVtyWMOvwlDbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5 +r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGIXsXwClTNSaa/ApzSRKft43jvRl5tcdF5 +cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkNKPl6I7ENPT2a/Z2B7yyQ +wHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQAjeZjOVJ +6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA +2CnbrlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyH +Wyf5QBGenDPBt+U1VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMR +eiFPCyEQtkA6qyI6BJyLm4SGcprSp6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB +/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTgqj8ljZ9EXME66C6u +d0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAszHQNTVfSVcOQr +PbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d +8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi +1wrykXprOQ4vMMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrd +rRT90+7iIgXr0PK3aBLXWopBGsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9di +taY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+gjwN/KUD+nsa2UUeYNrEjvn8K8l7 +lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgHJBu6haEaBQmAupVj +yTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7fpYn +Kx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLy +yCwzk5Iwx06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5n +wXARPbv0+Em34yaXOp/SX3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6 +OV+KmalBWQewLK8= +-----END CERTIFICATE----- + +// Certigna +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV +BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X +DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ +BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4 +QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny +gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw +zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q +130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2 +JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw +ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT +AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj +AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG +9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h +bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc +fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu +HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w +t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- + +// Certigna Root CA +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw +WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw +MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x +MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD +VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX +BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO +ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M +CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu +I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm +TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh +C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf +ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz +IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT +Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k +JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5 +hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB +GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov +L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo +dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr +aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq +hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L +6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG +HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6 +0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB +lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi +o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1 +gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v +faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63 +Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh +jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw +3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE----- + +// certSIGN ROOT CA G2 +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV +BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g +Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ +BgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJ +R04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDF +dRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05N0Iw +vlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZ +uIt4ImfkabBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhp +n+Sc8CnTXPnGFiWeI8MgwT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKs +cpc/I1mbySKEwQdPzH/iV8oScLumZfNpdWO9lfsbl83kqK/20U6o2YpxJM02PbyW +xPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91QqhngLjYl/rNUssuHLoPj1P +rCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732jcZZroiF +DsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fx +DTvf95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgy +LcsUDFDYg2WD7rlcz8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6C +eWRgKRM+o/1Pcmqr4tTluCRVLERLiohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSCIS1mxteg4BXrzkwJ +d8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOBywaK8SJJ6ejq +kX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQl +qiCA2ClV9+BB/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0 +OJD7uNGzcgbJceaBxXntC6Z58hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+c +NywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5BiKDUyUM/FHE5r7iOZULJK2v0ZXk +ltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklWatKcsWMy5WHgUyIO +pwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj +03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZk +PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE +1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX +QRBdJ3NghVdJIgc= +-----END CERTIFICATE----- + +// ePKI Root Certification Authority +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw +IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL +SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH +SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh +ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X +DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1 +TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ +fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA +sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU +WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS +nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH +dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip +NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC +AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF +MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB +uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl +PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP +JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/ +gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2 +j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6 +5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB +o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS +/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z +Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE +W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D +hNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- + +// HiPKI Root CA - G1 +-----BEGIN CERTIFICATE----- +MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBP +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xGzAZBgNVBAMMEkhpUEtJIFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRa +Fw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3 +YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kgUm9vdCBDQSAtIEcx +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0o9Qw +qNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twv +Vcg3Px+kwJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6 +lZgRZq2XNdZ1AYDgr/SEYYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnz +Qs7ZngyzsHeXZJzA9KMuH5UHsBffMNsAGJZMoYFL3QRtU6M9/Aes1MU3guvklQgZ +KILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfdhSi8MEyr48KxRURHH+CK +FgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj1jOXTyFj +HluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDr +y+K49a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ +/W3c1pzAtH2lsN0/Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgM +a/aOEmem8rJY5AIJEzypuxC00jBF8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6 +fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQDAgGGMA0GCSqG +SIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi +7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqc +SE5XCV0vrPSltJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6Fza +ZsT0pPBWGTMpWmWSBUdGSquEwx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9Tc +XzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07QJNBAsNB1CI69aO4I1258EHBGG3zg +iLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv5wiZqAxeJoBF1Pho +L5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+GpzjLrF +Ne85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wr +kkVbbiVghUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+ +vhV4nYWBSipX3tUZQ9rbyltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQU +YDksswBVLuT1sw5XxJFBAJw/6KXf6vb/yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ== +-----END CERTIFICATE----- + +// SecureSign Root CA12 +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUZvnHwa/swlG07VOX5uaCwysckBYwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u +LCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExMjAeFw0yMDA0MDgw +NTM2NDZaFw00MDA0MDgwNTM2NDZaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpD +eWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBS +b290IENBMTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6OcE3emhF +KxS06+QT61d1I02PJC0W6K6OyX2kVzsqdiUzg2zqMoqUm048luT9Ub+ZyZN+v/mt +p7JIKwccJ/VMvHASd6SFVLX9kHrko+RRWAPNEHl57muTH2SOa2SroxPjcf59q5zd +J1M3s6oYwlkm7Fsf0uZlfO+TvdhYXAvA42VvPMfKWeP+bl+sg779XSVOKik71gur +FzJ4pOE+lEa+Ym6b3kaosRbnhW70CEBFEaCeVESE99g2zvVQR9wsMJvuwPWW0v4J +hscGWa5Pro4RmHvzC1KqYiaqId+OJTN5lxZJjfU+1UefNzFJM3IFTQy2VYzxV4+K +h9GtxRESOaCtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBRXNPN0zwRL1SXm8UC2LEzZLemgrTANBgkqhkiG9w0BAQsF +AAOCAQEAPrvbFxbS8hQBICw4g0utvsqFepq2m2um4fylOqyttCg6r9cBg0krY6Ld +mmQOmFxv3Y67ilQiLUoT865AQ9tPkbeGGuwAtEGBpE/6aouIs3YIcipJQMPTw4WJ +mBClnW8Zt7vPemVV2zfrPIpyMpcemik+rY3moxtt9XUa5rBouVui7mlHJzWhhpmA +8zNL4WukJsPvdFlseqJkth5Ew1DgDzk9qTPxpfPSvWKErI4cqc1avTc7bgoitPQV +55FYxTpE05Uo2cBl6XLK0A+9H7MV2anjpEcJnuDLN/v9vZfVvhgaaaI5gdka9at/ +yOPiZwud9AzqVN/Ssq+xIvEg37xEHA== +-----END CERTIFICATE----- + +// SecureSign Root CA14 +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIUZNtaDCBO6Ncpd8hQJ6JaJ90t8sswDQYJKoZIhvcNAQEM +BQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u +LCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNDAeFw0yMDA0MDgw +NzA2MTlaFw00NTA0MDgwNzA2MTlaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpD +eWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBS +b290IENBMTQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDF0nqh1oq/ +FjHQmNE6lPxauG4iwWL3pwon71D2LrGeaBLwbCRjOfHw3xDG3rdSINVSW0KZnvOg +vlIfX8xnbacuUKLBl422+JX1sLrcneC+y9/3OPJH9aaakpUqYllQC6KxNedlsmGy +6pJxaeQp8E+BgQQ8sqVb1MWoWWd7VRxJq3qdwudzTe/NCcLEVxLbAQ4jeQkHO6Lo +/IrPj8BGJJw4J+CDnRugv3gVEOuGTgpa/d/aLIJ+7sr2KeH6caH3iGicnPCNvg9J +kdjqOvn90Ghx2+m1K06Ckm9mH+Dw3EzsytHqunQG+bOEkJTRX45zGRBdAuVwpcAQ +0BB8b8VYSbSwbprafZX1zNoCr7gsfXmPvkPx+SgojQlD+Ajda8iLLCSxjVIHvXib +y8posqTdDEx5YMaZ0ZPxMBoH064iwurO8YQJzOAUbn8/ftKChazcqRZOhaBgy/ac +18izju3Gm5h1DVXoX+WViwKkrkMpKBGk5hIwAUt1ax5mnXkvpXYvHUC0bcl9eQjs +0Wq2XSqypWa9a4X0dFbD9ed1Uigspf9mR6XU/v6eVL9lfgHWMI+lNpyiUBzuOIAB +SMbHdPTGrMNASRZhdCyvjG817XsYAFs2PJxQDcqSMxDxJklt33UkN4Ii1+iW/RVL +ApY+B3KVfqs9TC7XyvDf4Fg/LS8EmjijAQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUBpOjCl4oaTeqYR3r6/wtbyPk +86AwDQYJKoZIhvcNAQEMBQADggIBAJaAcgkGfpzMkwQWu6A6jZJOtxEaCnFxEM0E +rX+lRVAQZk5KQaID2RFPeje5S+LGjzJmdSX7684/AykmjbgWHfYfM25I5uj4V7Ib +ed87hwriZLoAymzvftAj63iP/2SbNDefNWWipAA9EiOWWF3KY4fGoweITedpdopT +zfFP7ELyk+OZpDc8h7hi2/DsHzc/N19DzFGdtfCXwreFamgLRB7lUe6TzktuhsHS +DCRZNhqfLJGP4xjblJUK7ZGqDpncllPjYYPGFrojutzdfhrGe0K22VoF3Jpf1d+4 +2kd92jjbrDnVHmtsKheMYc2xbXIBw8MgAGJoFjHVdqqGuw6qnsb58Nn4DSEC5MUo +FlkRudlpcyqSeLiSV5sI8jrlL5WwWLdrIBRtFO8KvH7YVdiI2i/6GaX7i+B/OfVy +K4XELKzvGUWSTLNhB9xNH27SgRNcmvMSZ4PPmz+Ln52kuaiWA3rF7iDeM9ovnhp6 +dB7h7sxaOgTdsxoEqBRjrLdHEoOabPXm6RUVkRqEGQ6UROcSjiVbgGcZ3GOTEAtl +Lor6CZpO2oYofaphNdgOpygau1LgePhsumywbrmHXumZNTfxPWQrqaA0k89jL9WB +365jJ6UeTo3cKXhZ+PmhIIynJkBugnLNeLLIjzwec+fBH7/PzqUqm9tEZDKgu39c +JRNItX+S +-----END CERTIFICATE----- + +// SecureSign Root CA15 +-----BEGIN CERTIFICATE----- +MIICIzCCAamgAwIBAgIUFhXHw9hJp75pDIqI7fBw+d23PocwCgYIKoZIzj0EAwMw +UTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBM +dGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNTAeFw0yMDA0MDgwODMy +NTZaFw00NTA0MDgwODMyNTZaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpDeWJl +cnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBSb290 +IENBMTUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQLUHSNZDKZmbPSYAi4Io5GdCx4 +wCtELW1fHcmuS1Iggz24FG1Th2CeX2yF2wYUleDHKP+dX+Sq8bOLbe1PL0vJSpSR +ZHX+AezB2Ot6lHhWGENfa4HL9rzatAy2KZMIaY+jQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT +9DAKBggqhkjOPQQDAwNoADBlAjEA2S6Jfl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp +4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJSwdLZrWeqrqgHkHZAXQ6 +bkU6iYAZezKYVWOr62Nuk22rGwlgMU4= +-----END CERTIFICATE----- + +// D-TRUST BR Root CA 1 2020 +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQsw +CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS +VVNUIEJSIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5 +NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG +A1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB +BAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7dPYS +zuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0 +QVK5buXuQqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/ +VbNafAkl1bK6CKBrqx9tMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g +PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2JyX3Jvb3Rf +Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l +dC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1 +c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO +PQQDAwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFW +wKrY7RjEsK70PvomAjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHV +dWNbFJWcHwHP2NVypw87 +-----END CERTIFICATE----- + +// D-TRUST BR Root CA 2 2023 +-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQczswBEhb2U14LnNLyaHcZjANBgkqhkiG9w0BAQ0FADBI +MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlE +LVRSVVNUIEJSIFJvb3QgQ0EgMiAyMDIzMB4XDTIzMDUwOTA4NTYzMVoXDTM4MDUw +OTA4NTYzMFowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEi +MCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDIgMjAyMzCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAK7/CVmRgApKaOYkP7in5Mg6CjoWzckjYaCTcfKr +i3OPoGdlYNJUa2NRb0kz4HIHE304zQaSBylSa053bATTlfrdTIzZXcFhfUvnKLNE +gXtRr90zsWh81k5M/itoucpmacTsXld/9w3HnDY25QdgrMBM6ghs7wZ8T1soegj8 +k12b9py0i4a6Ibn08OhZWiihNIQaJZG2tY/vsvmA+vk9PBFy2OMvhnbFeSzBqZCT +Rphny4NqoFAjpzv2gTng7fC5v2Xx2Mt6++9zA84A9H3X4F07ZrjcjrqDy4d2A/wl +2ecjbwb9Z/Pg/4S8R7+1FhhGaRTMBffb00msa8yr5LULQyReS2tNZ9/WtT5PeB+U +cSTq3nD88ZP+npNa5JRal1QMNXtfbO4AHyTsA7oC9Xb0n9Sa7YUsOCIvx9gvdhFP +/Wxc6PWOJ4d/GUohR5AdeY0cW/jPSoXk7bNbjb7EZChdQcRurDhaTyN0dKkSw/bS +uREVMweR2Ds3OmMwBtHFIjYoYiMQ4EbMl6zWK11kJNXuHA7e+whadSr2Y23OC0K+ +0bpwHJwh5Q8xaRfX/Aq03u2AnMuStIv13lmiWAmlY0cL4UEyNEHZmrHZqLAbWt4N +DfTisl01gLmB1IRpkQLLddCNxbU9CZEJjxShFHR5PtbJFR2kWVki3PaKRT08EtY+ +XTIvAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUZ5Dw1t61 +GNVGKX5cq/ieCLxklRAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRCMEAwPqA8oDqG +OGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfYnJfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA097N3U9swFrktpSHxQCF16+tI +FoE9c+CeJyrrd6kTpGoKWloUMz1oH4Guaf2Mn2VsNELZLdB/eBaxOqwjMa1ef67n +riv6uvw8l5VAk1/DLQOj7aRvU9f6QA4w9QAgLABMjDu0ox+2v5Eyq6+SmNMW5tTR +VFxDWy6u71cqqLRvpO8NVhTaIasgdp4D/Ca4nj8+AybmTNudX0KEPUUDAxxZiMrc +LmEkWqTqJwtzEr5SswrPMhfiHocaFpVIbVrg0M8JkiZmkdijYQ6qgYF/6FKC0ULn +4B0Y+qSFNueG4A3rvNTJ1jxD8V1Jbn6Bm2m1iWKPiFLY1/4nwSPFyysCu7Ff/vtD +hQNGvl3GyiEm/9cCnnRK3PgTFbGBVzbLZVzRHTF36SXDw7IyN9XxmAnkbWOACKsG +koHU6XCPpz+y7YaMgmo1yEJagtFSGkUPFaUA8JR7ZSdXOUPPfH/mvTWze/EZTN46 +ls/pdu4D58JDUjxqgejBWoC9EV2Ta/vH5mQ/u2kc6d0li690yVRAysuTEwrt+2aS +Ecr1wPrYg1UDfNPFIkZ1cGt5SAYqgpq/5usWDiJFAbzdNpQ0qTUmiteXue4Icr80 +knCDgKs4qllo3UCkGJCy89UDyibK79XH4I9TjvAA46jtn/mtd+ArY0+ew+43u3gJ +hJ65bvspmZDogNOfJA== +-----END CERTIFICATE----- + +// D-TRUST EV Root CA 1 2020 +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQsw +CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS +VVNUIEVWIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5 +NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG +A1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB +BAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8ZRCC +/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rD +wpdhQntJraOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3 +OqQo5FD4pPfsazK2/umLMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g +PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2V2X3Jvb3Rf +Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l +dC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1 +c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO +PQQDAwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CA +y/m0sRtW9XLS/BnRAjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJb +gfM0agPnIjhQW+0ZT0MW +-----END CERTIFICATE----- + +// D-TRUST EV Root CA 2 2023 +-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQaSYJfoBLTKCnjHhiU19abzANBgkqhkiG9w0BAQ0FADBI +MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlE +LVRSVVNUIEVWIFJvb3QgQ0EgMiAyMDIzMB4XDTIzMDUwOTA5MTAzM1oXDTM4MDUw +OTA5MTAzMlowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEi +MCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDIgMjAyMzCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBANiOo4mAC7JXUtypU0w3uX9jFxPvp1sjW2l1sJkK +F8GLxNuo4MwxusLyzV3pt/gdr2rElYfXR8mV2IIEUD2BCP/kPbOx1sWy/YgJ25yE +7CUXFId/MHibaljJtnMoPDT3mfd/06b4HEV8rSyMlD/YZxBTfiLNTiVR8CUkNRFe +EMbsh2aJgWi6zCudR3Mfvc2RpHJqnKIbGKBv7FD0fUDCqDDPvXPIEysQEx6Lmqg6 +lHPTGGkKSv/BAQP/eX+1SH977ugpbzZMlWGG2Pmic4ruri+W7mjNPU0oQvlFKzIb +RlUWaqZLKfm7lVa/Rh3sHZMdwGWyH6FDrlaeoLGPaxK3YG14C8qKXO0elg6DpkiV +jTujIcSuWMYAsoS0I6SWhjW42J7YrDRJmGOVxcttSEfi8i4YHtAxq9107PncjLgc +jmgjutDzUNzPZY9zOjLHfP7KgiJPvo5iR2blzYfi6NUPGJ/lBHJLRjwQ8kTCZFZx +TnXonMkmdMV9WdEKWw9t/p51HBjGGjp82A0EzM23RWV6sY+4roRIPrN6TagD4uJ+ +ARZZaBhDM7DS3LAaQzXupdqpRlyuhoFBAUp0JuyfBr/CBTdkdXgpaP3F9ev+R/nk +hbDhezGdpn9yo7nELC7MmVcOIQxFAZRl62UJxmMiCzNJkkg8/M3OsD6Onov4/knF +NXJHAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUqvyREBuH +kV8Wub9PS5FeAByxMoAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRCMEAwPqA8oDqG +OGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfZXZfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQCTy6UfmRHsmg1fLBWTxj++EI14 +QvBukEdHjqOSMo1wj/Zbjb6JzkcBahsgIIlbyIIQbODnmaprxiqgYzWRaoUlrRc4 +pZt+UPJ26oUFKidBK7GB0aL2QHWpDsvxVUjY7NHss+jOFKE17MJeNRqrphYBBo7q +3C+jisosketSjl8MmxfPy3MHGcRqwnNU73xDUmPBEcrCRbH0O1P1aa4846XerOhU +t7KR/aypH/KH5BfGSah82ApB9PI+53c0BFLd6IHyTS9URZ0V4U/M5d40VxDJI3IX +cI1QcB9WbMy5/zpaT2N6w25lBx2Eof+pDGOJbbJAiDnXH3dotfyc1dZnaVuodNv8 +ifYbMvekJKZ2t0dT741Jj6m2g1qllpBFYfXeA08mD6iL8AOWsKwV0HFaanuU5nCT +2vFp4LJiTZ6P/4mdm13NRemUAiKN4DV/6PEEeXFsVIP4M7kFMhtYVRFP0OUnR3Hs +7dpn1mKmS00PaaLJvOwiS5THaJQXfuKOKD62xur1NGyfN4gHONuGcfrNlUhDbqNP +gofXNJhuS5N5YHVpD/Aa1VP6IQzCP+k/HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAst +Nl7F6cTVg8uGF5csbBNvh1qvSaYd2804BC5f4ko1Di1L+KIkBI3Y4WNeApI02phh +XBxvWHZks/wCuPWdCg== +-----END CERTIFICATE----- + +// D-TRUST Root CA 3 2013 +-----BEGIN CERTIFICATE----- +MIIEDjCCAvagAwIBAgIDD92sMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxHzAdBgNVBAMMFkQtVFJVU1QgUm9vdCBD +QSAzIDIwMTMwHhcNMTMwOTIwMDgyNTUxWhcNMjgwOTIwMDgyNTUxWjBFMQswCQYD +VQQGEwJERTEVMBMGA1UECgwMRC1UcnVzdCBHbWJIMR8wHQYDVQQDDBZELVRSVVNU +IFJvb3QgQ0EgMyAyMDEzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +xHtCkoIf7O1UmI4SwMoJ35NuOpNcG+QQd55OaYhs9uFp8vabomGxvQcgdJhl8Ywm +CM2oNcqANtFjbehEeoLDbF7eu+g20sRoNoyfMr2EIuDcwu4QRjltr5M5rofmw7wJ +ySxrZ1vZm3Z1TAvgu8XXvD558l++0ZBX+a72Zl8xv9Ntj6e6SvMjZbu376Ml1wrq +WLbviPr6ebJSWNXwrIyhUXQplapRO5AyA58ccnSQ3j3tYdLl4/1kR+W5t0qp9x+u +loYErC/jpIF3t1oW/9gPP/a3eMykr/pbPBJbqFKJcu+I89VEgYaVI5973bzZNO98 +lDyqwEHC451QGsDkGSL8swIDAQABo4IBBTCCAQEwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUP5DIfccVb/Mkj6nDL0uiDyGyL+cwDgYDVR0PAQH/BAQDAgEGMIG+ +BgNVHR8EgbYwgbMwdKByoHCGbmxkYXA6Ly9kaXJlY3RvcnkuZC10cnVzdC5uZXQv +Q049RC1UUlVTVCUyMFJvb3QlMjBDQSUyMDMlMjAyMDEzLE89RC1UcnVzdCUyMEdt +YkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MDugOaA3hjVodHRwOi8v +Y3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2FfM18yMDEzLmNybDAN +BgkqhkiG9w0BAQsFAAOCAQEADlkOWOR0SCNEzzQhtZwUGq2aS7eziG1cqRdw8Cqf +jXv5e4X6xznoEAiwNStfzwLS05zICx7uBVSuN5MECX1sj8J0vPgclL4xAUAt8yQg +t4RVLFzI9XRKEBmLo8ftNdYJSNMOwLo5qLBGArDbxohZwr78e7Erz35ih1WWzAFv +m2chlTWL+BD8cRu3SzdppjvW7IvuwbDzJcmPkn2h6sPKRL8mpXSSnON065102ctN +h9j8tGlsi6BDB2B4l+nZk3zCRrybN1Kj7Yo8E6l7U0tJmhEFLAtuVqwfLoJs4Gln +tQ5tLdnkwBXxP/oYcuEVbSdbLTAoK59ImmQrme/ydUlfXA== +-----END CERTIFICATE----- + +// D-TRUST Root Class 3 CA 2 2009 +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha +ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM +HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03 +UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42 +tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R +ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM +lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp +/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G +A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy +MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl +cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js +L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL +BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni +acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K +zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8 +PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y +Johw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +// D-TRUST Root Class 3 CA 2 EV 2009 +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw +NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV +BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn +ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0 +3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z +qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR +p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8 +HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw +ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea +HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw +Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh +c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E +RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt +dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku +Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp +3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF +CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na +xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX +KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +// D-Trust SBR Root CA 1 2022 +-----BEGIN CERTIFICATE----- +MIICXjCCAeOgAwIBAgIQUs/kjG2gSvc/gpcMgAmMlTAKBggqhkjOPQQDAzBJMQsw +CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSMwIQYDVQQDExpELVRy +dXN0IFNCUiBSb290IENBIDEgMjAyMjAeFw0yMjA3MDYxMTMwMDBaFw0zNzA3MDYx +MTI5NTlaMEkxCzAJBgNVBAYTAkRFMRUwEwYDVQQKEwxELVRydXN0IEdtYkgxIzAh +BgNVBAMTGkQtVHJ1c3QgU0JSIFJvb3QgQ0EgMSAyMDIyMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAEWZM59oxJZijXYQzIq38Moy3foqR8kito1S5+HkDLtGhJfxKhq39X +nxkuYy5b/mZxDDMPud5rxIjDse/sOUDjlqvb5XuuH9z5r0aaakYGL8c3ZIsXYv6W +w6LuhOCwlzm8o4GPMIGMMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFPEpox4B +Eh09dVZNx1B8xRmqDxi3MA4GA1UdDwEB/wQEAwIBBjBKBgNVHR8EQzBBMD+gPaA7 +hjlodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Nicl9yb290X2Nh +XzFfMjAyMi5jcmwwCgYIKoZIzj0EAwMDaQAwZgIxAJf53q5Lj5i1HkB/Mn1NVEPa +ic3CqpI80YIec8/6TJIg+2MnxfVzPQk996dhhozzagIxAOcvfLj1JYw7OR82q431 +hqIu4Xpk2mc5Av7+Mz/Zc7ZYWzr8sqTZYHh3zHmnpq5VvQ== +-----END CERTIFICATE----- + +// D-Trust SBR Root CA 2 2022 +-----BEGIN CERTIFICATE----- +MIIFrDCCA5SgAwIBAgIQVNWjlR49lbpyG5rQMSFKujANBgkqhkiG9w0BAQ0FADBJ +MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSMwIQYDVQQDExpE +LVRydXN0IFNCUiBSb290IENBIDIgMjAyMjAeFw0yMjA3MDcwNzMwMDBaFw0zNzA3 +MDcwNzI5NTlaMEkxCzAJBgNVBAYTAkRFMRUwEwYDVQQKEwxELVRydXN0IEdtYkgx +IzAhBgNVBAMTGkQtVHJ1c3QgU0JSIFJvb3QgQ0EgMiAyMDIyMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAryy8jjaM62SvUWrWbjxekTrqmsPKbPuqJ55k +IqlA37koRVrsU2EWKJjCiqR1eFCE3fogSJIHZUE1ZlESdGGdBwaFOTFXeyg/1Zyl +7FrpHEsnn84nBvM39VLYETMWQTof9WN4ZWOGyb/IAQQfbu7i7KwM7oKS4vYaDT85 ++Z1lk634uQXBPfg3gVbDoP4F7OCUFjojFgTapgqThXJtYTuhjUXW43++Fb02hAj2 +C4NrJqqiveCw56rgrmfE04KlDKmk8DN5DVA/8O+QPSS5f9IgbOqX87+c3EfeCWG9 +lHmVWgJ2NWDERyIN93ZjA9PG+4PGXaut7WklKwNbTSUAQeOMhxdSqOAFK0NNFBPK +5z9DIrw3pHXx9r867zIeru5YhpByugSsQEjvXMR4p6mPJ1rLeuxY8sIIWJBtTQOF +eXEVBQ5OPvnfDwX3XxRIViENM5KxrIzlGP6/D+7gBKq9IfJYtlyJCosYCSIaszXG +ZsL1MxWZgOAI+ZYvE4zu2reIxOk3tddq1zqETatwjNNOFFWgohD8ZNpn6PHLM93J +moqPli9Ygdn4mgBDzJD7VXb7huM3ASgMb/TpWU0Vd1FCSsw0uIBDUIHvV6UT26eU +eQ9Lyn4Xfa+jIWTocVVWjwawR+xZD11wWywWQvCGnnXea01ImITiVxi2nIKZZTqL +gHhXDEkCAwEAAaOBjzCBjDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRds4CU +G+WGv2i6FDSk9u5t8t3f5zAOBgNVHQ8BAf8EBAMCAQYwSgYDVR0fBEMwQTA/oD2g +O4Y5aHR0cDovL2NybC5kLXRydXN0Lm5ldC9jcmwvZC10cnVzdF9zYnJfcm9vdF9j +YV8yXzIwMjIuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA0VC5YGFbNSr2X0/V9K9yv +D1HhTbwhS5P0AEQTBxALJRg+SFmW96Hhk5B4Zho9I+siqwGmjgxRM+ZtjDHurKQB +cDlI3sdmLGsNy3Ofh5LpPkcfuO8v7rdWjEiJ8DinFTmy7sA/F6RzAgicvAaKpMK3 +YWH5w9vE0Hp8Yd6xWJH13WVMLwv46z217Yq+dxy6WQISZnHlmCfODj2vUaJF+YL7 +WqWUcPeLhMNMZSWbe+IfMHCzQI467r3052jFnckpR3EOk8i1SE71ZrsHiHFpa3tI +jm/wEcS0yXAUmCC97afqAdpupZsS/j5EMLPw63VSwPTD+ncmpHeCLW/zKB5OlfAw +94n4LKJQW/K+Mn5sVNtyySpa4By2C9hSmlmh47ABJ8WgFlBm3OuubfSbWz2EbVuH +56mJu2644JtTicD/LkAaiUQuGENnOOR8cl/ZoyklQUE9HHcbZKjDVe5jcWZig/R/ +JpmgVDuhEm1wYs7T+bi9IvzUmtS74jgWL7d9OcKwqQPpnM9+GI123F8Ru+tC7FAJ +PlzskDHYGnK6P2kH7pg0wjSk1toT1qmE8gCGwFS6HhGw4rnEB7SR56rmMVZvsUTE +KmK8ybBlnDT8DBpT3yEXu8JtoQrm8bCqRAlQSTh6XXHiMS4ZsN+VQgR9hIjOCiNn +azidFt4G/ihwOKVarvyD7Q== +-----END CERTIFICATE----- + +// T-TeleSec GlobalRoot Class 2 +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd +AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC +FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi +1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq +jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ +wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ +WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy +NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC +uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw +IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 +g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP +BSeOE6Fuwg== +-----END CERTIFICATE----- + +// T-TeleSec GlobalRoot Class 3 +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN +8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/ +RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4 +hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5 +ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM +EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1 +A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy +WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ +1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30 +6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT +91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p +TpPDpFQUWw== +-----END CERTIFICATE----- + +// Telekom Security SMIME ECC Root 2021 +-----BEGIN CERTIFICATE----- +MIICRzCCAc2gAwIBAgIQFSrdFMkY0aRWQIamJa8HXzAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBH +bWJIMS0wKwYDVQQDDCRUZWxla29tIFNlY3VyaXR5IFNNSU1FIEVDQyBSb290IDIw +MjEwHhcNMjEwMzE4MTEwODMwWhcNNDYwMzE3MjM1OTU5WjBlMQswCQYDVQQGEwJE +RTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMS0wKwYD +VQQDDCRUZWxla29tIFNlY3VyaXR5IFNNSU1FIEVDQyBSb290IDIwMjEwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAASwGY+ia7XHzQ8wmTcMw2Bb8fEnIFU9wJKLq1ehb3OD +IcJDEwxeiarHBTV5k2KQ1l0TH9F6oLyeEKdmfEYKsFdsv+ZUOTghbBJccczTWl9t +t6eG37Pf7sLniUGWNfYvSrWjQjBAMB0GA1UdDgQWBBQrywEMY8NTEqWoV6/QnIP7 +vZA6SzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQD +AwNoADBlAjEA1rxIkodHA8dwOyW2H65GZ3N0ACdL5KUEogPfXiitbl4DyN1onLa/ +lBBIlS8P/xiLAjABQDOel5dNBfJ0VAzNOf1qawnBJD9hjjiht+jXRBURYv8OYTdH +S0B/Sl+yZ1pzdcI= +-----END CERTIFICATE----- + +// Telekom Security SMIME RSA Root 2023 +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgIQDH5i9XlzO51Djotj7ZGVuDANBgkqhkiG9w0BAQwFADBl +MQswCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0 +eSBHbWJIMS0wKwYDVQQDDCRUZWxla29tIFNlY3VyaXR5IFNNSU1FIFJTQSBSb290 +IDIwMjMwHhcNMjMwMzI4MTIwOTIyWhcNNDgwMzI3MjM1OTU5WjBlMQswCQYDVQQG +EwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMS0w +KwYDVQQDDCRUZWxla29tIFNlY3VyaXR5IFNNSU1FIFJTQSBSb290IDIwMjMwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDvxQ6LvjLSZ0f/Ckxnsyq/yMPF +keu1xx6R4WaoiItVIIAfUV53l54ZClzHazchfAM2AfSIJdmoLkGq/Ngm4JZAYnmu +V54DOBocsncUPumhctDk4DfRF0btUFx6WMX4K/d1L8+BnlostzqsoFmYBFEM/0nF +UP0e00eFSzNPoje1rwSaJzKdVtU/VWHji2+uUf6X/mkH+mJbJuYUeRWlEziuXze+ +lErWDYAWaaSRsjpJmHWdRhCKXHp/hKXorx7Hq7NaRrWjS/WmIzYARrHbBbYbzp56 +Mlya1XLDnYZNK4TTHrWI2hB4nCLDOyO16xMHvW9T7Jvsm9Nl9QcJ412nmbV+ho7V +Av+3hQnjRxTdlmYYNN4I1d/LGJliCyvsAF1SRNPGlvwyViWRz80ZO5U5PgKHmWO2 +1T40eg8RdYG8fQTKYLQoddcCUd1SAC7H/YnxXPPLpCcSOI+7+4nw5MQ4LL6CoHFh +YpGPSAwvK6mw8csQBOd0vzeQ708qQzWXEsYqcA3eLFVHeWMp9cofagZSHK4tJCKD +Iq/QqjC3Kh//ZSNYZZPIjn1AEDGGeNlVyzww8N5RKgA20idFX9jooSE9fkZWOylF +8R0FCc62QzDcRZAQMEyka4aLPz0vMZFx7ya59r6dsGzfEe5YP0N5hjmA8SYXB5jw +maowLENZFM7t4kAThQIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FJrOrCrsAfplcN6XnfHSAIylo2S7MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgw +FoAUms6sKuwB+mVw3ped8dIAjKWjZLswDQYJKoZIhvcNAQEMBQADggIBAONQ/fVA +FiIJljoNqe+B5y4y8KHxSV57iA0Ecte+Z6i6He5Qu3JuetG7DHIwRsjV1wISFplO +Ht9alu6Pkb6uhvgQd6XEbkdhwPIm2U9haAVIdQgVpaF71biziXnm7fHzYQCGey4x +/qNc+Hk9tFuIe+Ajuw2hF/rLaA2Yd3EI4m1DdGvENsWUQaQA1lctmYqLIBIVAjIO +0knsgUjFaidS17JzVVOWPJ5PTLWg0E9X0GcoSGS+xri67GTPyHvFaucq5llXttbU +1sBnXNmeKAlAv/OpNTFlYAPLGWyClQMeXz/hvepJceVbtwtHFhsgiW2UmQx+iGwd +DfS3IRpZl6zL6L4XH5V8U5uvUFKqjQsur1rXYPIqaSq57lRwGKq99aE/0t2hYxkA ++KcM66N58nBZo/iiEgPsE//kAoY218HDpLXUpMI3RbaUcD3FveujFR3jNnoVaSpW +NDnPpZo2qsjtebzP9s4EUwvaslAjfLw+Jq3wDkO7JsuuwkDeNx8KoFHNY522T9jG +R3y82LTtnovzEeKotT7srnA+fiK7NUgXYGIUkTCjdj2mUTaLHw3dajEcpe3dlqNu +cg8TTaqnqVx4+QMSGJM3RRKJPfi+yr3ZvgzZGGSnyEE+dYIhOH1l9KDUE0sHeCn5 +nX7Mhz/E2i6I3eML3FpRWunZEk+eAtv3BSVR +-----END CERTIFICATE----- + +// Telekom Security TLS ECC Root 2020 +-----BEGIN CERTIFICATE----- +MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQsw +CQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBH +bWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIw +MB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIzNTk1OVowYzELMAkGA1UEBhMCREUx +JzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkGA1UE +AwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/O +tdKPD/M12kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDP +f8iAC8GXs7s1J8nCG6NCMEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6f +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA +MGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZMo7k+5Dck2TOrbRBR2Di +z6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdUga/sf+Rn +27iQ7t0l +-----END CERTIFICATE----- + +// Telekom Security TLS RSA Root 2023 +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBj +MQswCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0 +eSBHbWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAy +MDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMyNzIzNTk1OVowYzELMAkGA1UEBhMC +REUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkG +A1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9 +cUD/h3VCKSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHV +cp6R+SPWcHu79ZvB7JPPGeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMA +U6DksquDOFczJZSfvkgdmOGjup5czQRxUX11eKvzWarE4GC+j4NSuHUaQTXtvPM6 +Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWol8hHD/BeEIvnHRz+sTug +BTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9FIS3R/qy +8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73J +co4vzLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg +8qKrBC7m8kwOFjQgrIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8 +rFEz0ciD0cmfHdRHNCk+y7AO+oMLKFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12 +mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7SWWO/gLCMk3PLNaaZlSJhZQNg ++y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtqeX +gj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2 +p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQ +pGv7qHBFfLp+sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm +9S3ul0A8Yute1hTWjOKWi0FpkzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErw +M807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy/SKE8YXJN3nptT+/XOR0so8RYgDd +GGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4mZqTuXNnQkYRIer+ +CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtzaL1t +xKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+ +w6jv/naaoqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aK +L4x35bcF7DvB7L6Gs4a8wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+lj +X273CXE2whJdV/LItM3z7gLfEdxquVeEHVlNjM7IDiPCtyaaEBRx/pOyiriA8A4Q +ntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0o82bNSQ3+pCTE4FCxpgm +dTdmQRCsu/WU48IxK63nI1bMNSWSs1A= +-----END CERTIFICATE----- + +// DigiCert Assured ID Root CA +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c +JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP +mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ +wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 +VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ +AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB +AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun +pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC +dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf +fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm +NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx +H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- + +// DigiCert Assured ID Root G2 +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA +n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc +biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp +EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA +bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu +YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW +BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI +QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I +0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni +lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 +B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv +ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +// DigiCert Assured ID Root G3 +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg +RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf +Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q +RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD +AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY +JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv +6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +// DigiCert Global Root CA +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB +CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 +nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt +43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P +T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 +gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR +TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw +DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr +hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg +06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF +PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls +YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + +// DigiCert Global Root G2 +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +// DigiCert Global Root G3 +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe +Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw +EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x +IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG +fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO +Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx +AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ +oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 +sycX +-----END CERTIFICATE----- + +// DigiCert High Assurance EV Root CA +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep ++OkuE6N36B9K +-----END CERTIFICATE----- + +// DigiCert SMIME ECC P384 Root G5 +-----BEGIN CERTIFICATE----- +MIICHDCCAaOgAwIBAgIQBT9uoAYBcn3tP8OjtqPW7zAKBggqhkjOPQQDAzBQMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xKDAmBgNVBAMTH0Rp +Z2lDZXJ0IFNNSU1FIEVDQyBQMzg0IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN +NDYwMTE0MjM1OTU5WjBQMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs +IEluYy4xKDAmBgNVBAMTH0RpZ2lDZXJ0IFNNSU1FIEVDQyBQMzg0IFJvb3QgRzUw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQWnVXlttT7+2drGtShqtJ3lT6I5QeftnBm +ICikiOxwNa+zMv83E0qevAED3oTBuMbmZUeJ8hNVv82lHghgf61/6GGSKc8JR14L +HMAfpL/yW7yY75lMzHBrtrrQKB2/vgSjQjBAMB0GA1UdDgQWBBRzemuW20IHi1Jm +wmQyF/7gZ5AurTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAKBggq +hkjOPQQDAwNnADBkAjA3RPUygONx6/Rtz3zMkZrDbnHY0iNdkk2CQm1cYZX2kfWn +CPZql+mclC2YcP0ztgkCMAc8L7lYgl4Po2Kok2fwIMNpvwMsO1CnO69BOMlSSJHW +Dvu8YDB8ZD8SHkV/UT70pg== +-----END CERTIFICATE----- + +// DigiCert SMIME RSA4096 Root G5 +-----BEGIN CERTIFICATE----- +MIIFajCCA1KgAwIBAgIQBfa6BCODRst9XOa5W7ocVTANBgkqhkiG9w0BAQwFADBP +MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJzAlBgNVBAMT +HkRpZ2lDZXJ0IFNNSU1FIFJTQTQwOTYgUm9vdCBHNTAeFw0yMTAxMTUwMDAwMDBa +Fw00NjAxMTQyMzU5NTlaME8xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2Vy +dCwgSW5jLjEnMCUGA1UEAxMeRGlnaUNlcnQgU01JTUUgUlNBNDA5NiBSb290IEc1 +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4Gpb2fj5fey1e+9f3Vw0 +2Npd0ctldashfFsA1IJvRYVBiqkSAnIy8BT1A3W7Y5dJD0CZCxoeVqfS0OGr3eUE +G+MfFBICiPWggAn2J5pQ8LrjouCsahSRtWs4EHqiMeGRG7e58CtbyHcJdrdRxDYK +mVNURCW3CTWGFwVWkz1BtwLXYh+KkhGH6hFt6ggR3LF4SEmS9rRRgHgj2P7hVho6 +kBNWNInV4pWLX96yzPs/OLeF9+qevy6hLi9NfWoRLjag/xEIBJVV4Bs7Z5OplFXq +Mu0GOn/Cf+OtEyfRNEGzMMO/tIj4A4Kk3z6reHegWZNx593rAAR7zEg5KOAeoxVp +yDayoQuX31XW75GcpPYW91EK7gMjkdwE/+DdOPYiAwDCB3EaEsnXRiqUG83Wuxvu +v75NUFiwC80wdin1z+W2ai92sLBpatBtZRg1fpO8chfBVULNL8Ilu/T9HaFkIlRd +4p5yQYRucZbqRQe2XnpKhp1zZHc4A9IPU6VVIMRN/2hvVanq3XHkT9mFo3xOKQKe +CwnyGlPMAKbd0TT2DcEwsZwCZKw17aWwKbHSlTMP0iAzvewjS/IZ+dqYZOQsMR8u +4Y0cBJUoTYxYzUvlc4KGjOyo1nlc+2S73AxMKPYXr+Jo1haGmNv8AdwxuvicDvko +Rkrh/ZYGRXkRaBdlXIsmh1sCAwEAAaNCMEAwHQYDVR0OBBYEFNGj1FcdT1XbdUxc +Qp5jFs60xjsfMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG +SIb3DQEBDAUAA4ICAQAHpwreU7ua63C/sjaQzeSnuPEM5F1aHXhl/Mm4HiMRV3xp +NW0B/1NQvwcOuscBP1gqlHUDqxwLI9wbih43PR1Yj3PZsypv3xCgWwynyrB/uSSi +ATUy5V5GQevYf3PnQumkUSZ3gQqo6w8KUJ1+iiBn/AuOOhHTxYxgGNlLsfzU8bRJ +Tq6H4dH7dqFf8wbPl5YM6Z51gVxTDSL8NuZJbnTbAIWNfCKgjvsQTNRiE1vvS3Im +i/xOio/+lxBTxXiLQmQbX+CJ/bsJf1DgVIUmEWodZflJKdx8Nt/7PffSrO4yjW6m +fTmcRcTKDfU7tHlTpS9Wx1HFikxkXZBDI45rTBd4zOi/9TvkqEjPrZsM3zJK09kS +jiN4DS2vn6+ePAnClwDtOmkccT8539OPxGb17zaUD/PdkraWX5Cm3XOqpiCUlCVq +CQxy5BMjYEyjyhcue2cA29DN6nofOSZXiTB3y07llUVPX/s2XD35ILU6ECVPkzJa +7sGW6OlWBLBJYU3seKidGMH/2OovVu+VK3sEXmfjVUDtOQT5C3n1aoxcD4makMfN +i97bJjWhbs2zQvKiDzsMjpP/FM/895P35EEIbhlSEQ9TGXN4DM/YhYH4rVXIsJ5G +Y6+cUu5cv/DAWzceCSDSPiPGoRVKDjZ+MMV5arwiiNkMUkAf3U4PZyYW0q0XHA== +-----END CERTIFICATE----- + +// DigiCert TLS ECC P384 Root G5 +-----BEGIN CERTIFICATE----- +MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp +Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2 +MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ +bmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS +7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp +0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS +B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49 +BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ +LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4 +DXZDjC5Ty3zfDBeWUA== +-----END CERTIFICATE----- + +// DigiCert TLS RSA4096 Root G5 +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN +MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT +HERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN +NDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs +IEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS87IE+ +ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG0 +2C+JFvuUAT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgp +wgscONyfMXdcvyej/Cestyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZM +pG2T6T867jp8nVid9E6P/DsjyG244gXazOvswzH016cpVIDPRFtMbzCe88zdH5RD +nU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnVDdXifBBiqmvwPXbzP6Po +sMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9qTXeXAaDx +Zre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cd +Lvvyz6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvX +KyY//SovcfXWJL5/MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNe +XoVPzthwiHvOAbWWl9fNff2C+MIkwcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPL +tgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4EFgQUUTMc7TZArxfTJc1paPKv +TiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN +AQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw +GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7H +PNtQOa27PShNlnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLF +O4uJ+DQtpBflF+aZfTCIITfNMBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQ +REtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/u4cnYiWB39yhL/btp/96j1EuMPik +AdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9GOUrYU9DzLjtxpdRv +/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh47a+ +p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilw +MUc/dNAUFvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WF +qUITVuwhd4GTWgzqltlJyqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCK +ovfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+ +-----END CERTIFICATE----- + +// DigiCert Trusted Root G4 +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg +RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y +ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If +xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV +ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO +DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ +jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ +CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi +EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM +fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY +uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK +chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t +9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 +SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd ++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc +fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa +sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N +cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N +0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie +4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI +r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 +/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm +gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ +-----END CERTIFICATE----- + +// QuoVadis Root CA 1 G3 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 +MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV +wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe +rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341 +68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh +4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp +UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o +abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc +3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G +KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt +hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO +Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt +zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD +ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2 +cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN +qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5 +YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv +b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2 +8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k +NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj +ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp +q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt +nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +// QuoVadis Root CA 2 +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa +GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg +Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J +WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB +rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp ++ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1 +ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i +Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz +PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og +/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH +oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI +yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud +EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2 +A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL +MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f +BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn +g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl +fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K +WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha +B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc +hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR +TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD +mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z +ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y +4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza +8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- + +// QuoVadis Root CA 2 G3 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +// QuoVadis Root CA 3 +-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM +V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB +4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr +H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd +8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv +vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT +mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe +btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc +T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt +WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ +c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A +4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD +VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG +CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0 +aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu +dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw +czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G +A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC +TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg +Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0 +7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem +d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd ++LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B +4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN +t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x +DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57 +k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s +zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j +Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT +mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK +4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE----- + +// QuoVadis Root CA 3 G3 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 +MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR +/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu +FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR +U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c +ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR +FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k +A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw +eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl +sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp +VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q +A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ +ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD +ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI +FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv +oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg +u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP +0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf +3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl +8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ +DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN +PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ +ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +// DIGITALSIGN GLOBAL ROOT ECDSA CA +-----BEGIN CERTIFICATE----- +MIICajCCAfCgAwIBAgIUNi2PcoiiKCfkAP8kxi3k6/qdtuEwCgYIKoZIzj0EAwMw +ZDELMAkGA1UEBhMCUFQxKjAoBgNVBAoMIURpZ2l0YWxTaWduIENlcnRpZmljYWRv +cmEgRGlnaXRhbDEpMCcGA1UEAwwgRElHSVRBTFNJR04gR0xPQkFMIFJPT1QgRUNE +U0EgQ0EwHhcNMjEwMTIxMTEwNzUwWhcNNDYwMTE1MTEwNzUwWjBkMQswCQYDVQQG +EwJQVDEqMCgGA1UECgwhRGlnaXRhbFNpZ24gQ2VydGlmaWNhZG9yYSBEaWdpdGFs +MSkwJwYDVQQDDCBESUdJVEFMU0lHTiBHTE9CQUwgUk9PVCBFQ0RTQSBDQTB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABG4Lo6szTRzqSuj8BI0UoH3wCCxfg6uT0dJ7utdJ +fY/sElBf1LnL5fD5M2MfyVfsQNgRC5foUhbMKY70BoYeONw9V8Tuqr3IVAQmWicT +UUc9Hx8ajqiVpDPQzEfMbbj8SKNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME +GDAWgBTOr0qLGnXi8TjnAvAWrV7qZNV7tDAdBgNVHQ4EFgQUzq9Kixp14vE45wLw +Fq1e6mTVe7QwDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMAqIxHGc +RANNjbTHvKiu2TAnNWprFmPX/OdZ4aeJG0wxmiNVRObzQyHVRydvbVcBqgIxAPuy +6uKXf1G1n0jrvG81iahkcKtXds3AxhRgyn/iggBz98w16o4km+UIWccEjHN4/g== +-----END CERTIFICATE----- + +// DIGITALSIGN GLOBAL ROOT RSA CA +-----BEGIN CERTIFICATE----- +MIIFtTCCA52gAwIBAgIUXVnIyqsJV/XmtdoplARq/8XUlYcwDQYJKoZIhvcNAQEN +BQAwYjELMAkGA1UEBhMCUFQxKjAoBgNVBAoMIURpZ2l0YWxTaWduIENlcnRpZmlj +YWRvcmEgRGlnaXRhbDEnMCUGA1UEAwweRElHSVRBTFNJR04gR0xPQkFMIFJPT1Qg +UlNBIENBMB4XDTIxMDEyMTEwNTAzNFoXDTQ2MDExNTEwNTAzNFowYjELMAkGA1UE +BhMCUFQxKjAoBgNVBAoMIURpZ2l0YWxTaWduIENlcnRpZmljYWRvcmEgRGlnaXRh +bDEnMCUGA1UEAwweRElHSVRBTFNJR04gR0xPQkFMIFJPT1QgUlNBIENBMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyIe2ONMc8N4S+IPHxIriibi0Inp4 ++AxmUWh2NwrVT8JaCLgWXPdyAQk3hIEqVGvXktBs+qinQxI06w7bNw8p/ooxUULo +S5yQqMgsEdP9oCl+zt6U9oLgWLRORSXxIvI90w97VBrcMrbWUU5+QbRXuCzGuQ4u +ylfx1cjTWOel6UIRrtMgJZRp14/Kog3D058HaD8V0mcuU/12gpsLc6kpDZ4RkxQI +mOyeVBJKVqIGFexrbC6SYC6GDa6CH1FN47IH1xAZVyL2qWlEhPPZPaAGv8yIfn/1 +zlulwipqdELqb6b/+Wix0F+9kdJVbzNXTB6d5OKLwYVloOBqnAAAiJLdWAgW8nAx +qBzh3r1OcenWvn61oVrDTfe/m72UpP31qlOTRskmAQRwxKBxus4lZvuRflVw7kkK +TWJ/wlCacvIYZ53pRag0hOj4gfbRWiIeB087s3/dEaVz3L6pGTppqW0bMuKJqqUn +C1p+dOIPZDldfly5wRf8x41eyewk7dLyP3qERTcCvj5rWcTmWxZtwKqeqrVZLixw +VZzMmZaYJFTRjtrKtBG0t3BDH2+QCyCgqHYTZdvbI1p1S6ELMXcK7n1oYRoTjOpR +flxWo1dMXaHrE2W/VBTM8+7c1+w8l/J4Vrjfclxw/M4G3Z/SBzHv51KRns2618AY +RAcxZUkyaRNK648CAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAW +gBS1Nrw8jBqrLPZZGS2DFNqTJRXWhjAdBgNVHQ4EFgQUtTa8PIwaqyz2WRktgxTa +kyUV1oYwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDQUAA4ICAQAU+zElODH4 +ygiyI3Y4rfjTWfXMtFcl4US+fvwW7K76Jp9PZxZKVvD97ccZATSOkFot1oBc7HHS +gSWCHgBx35rR1R0iu9Gl82IPtOvcJHP+plbNmhTFBDUWMaIH66UA4rb4X3L9P2FJ +jt5+TTjXeh50N2xR3L4ABLg4FPMgwe2bpyP9DUKEHX/yc8PQeGPxn+zXW+nxvmyg +SwOejWnhFNqIEIEjU//aVCsLxrmWlQQYRvN7qJfYW2ik5DgcDkXlmNMJrppe7LN5 +DTly8vSUnQ6eYCLmqPZMhc0HgjpoOc09X+M49LavO2tKn2BRRaJAAuWqDOM+0XjU +onScJroFmihwSj6mC9AdSfC6+K5BEH6kBxK9qM8pPVe7x/FDRwA+rnAYWiB7Ccs6 +OnCA5UxgmMEVwR1K98jwm+FyreddaFgLBLGMvJ+3+26LWwRV++sjVdd4UNoly74n +NrskGnkcUdH+E7v/eCzcpL4v9sVLU8+nTJlecKxZiASuZAS/e6Z6TdPod72hflAV +8+9JMIVNIVeq2yx1l62BAYeisXCdHgZaA2CxP6ZtgizUFLGBpeg9iB20cixYN4qO +OJS4c92p4Lj2d6KzfFjermk6tYulGrvy2HQGnP1icyAhdrF+cJ4Z1OsXYhk4mc02 +K0f+McvfueSsCNPYpuvUnn5LZKRVXSsXyQ== +-----END CERTIFICATE----- + +// CA Disig Root R2 +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV +BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu +MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy +MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx +EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe +NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH +PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I +x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe +QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR +yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO +QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912 +H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ +QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD +i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs +nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1 +rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI +hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf +GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb +lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka ++elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal +TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i +nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3 +gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr +G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os +zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x +L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +// emSign ECC Root CA - C3 +-----BEGIN CERTIFICATE----- +MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG +EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx +IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw +MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln +biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND +IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci +MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti +sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O +BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB +Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c +3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J +0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== +-----END CERTIFICATE----- + +// emSign ECC Root CA - G3 +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG +EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo +bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ +TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s +b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0 +WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS +fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB +zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq +hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB +CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD ++JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- + +// emSign Root CA - C1 +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG +A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg +SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw +MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln +biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v +dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ +BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ +HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH +3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH +GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c +xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1 +aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq +TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87 +/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4 +kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG +YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT ++xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo +WXzhriKi4gp6D/piq1JM4fHfyr6DDUI= +-----END CERTIFICATE----- + +// emSign Root CA - G1 +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD +VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU +ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH +MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO +MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv +Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz +f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO +8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq +d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM +tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt +Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB +o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD +AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x +PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM +wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d +GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH +6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby +RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE----- + +// Atos TrustedRoot 2011 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE +AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG +EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM +FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC +REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp +Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM +VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ +SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ +4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L +cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi +eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG +A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 +DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j +vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP +DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc +maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D +lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv +KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +// Atos TrustedRoot Root CA ECC G2 2020 +-----BEGIN CERTIFICATE----- +MIICMTCCAbagAwIBAgIMC3MoERh0MBzvbwiEMAoGCCqGSM49BAMDMEsxCzAJBgNV +BAYTAkRFMQ0wCwYDVQQKDARBdG9zMS0wKwYDVQQDDCRBdG9zIFRydXN0ZWRSb290 +IFJvb3QgQ0EgRUNDIEcyIDIwMjAwHhcNMjAxMjE1MDgzOTEwWhcNNDAxMjEwMDgz +OTA5WjBLMQswCQYDVQQGEwJERTENMAsGA1UECgwEQXRvczEtMCsGA1UEAwwkQXRv +cyBUcnVzdGVkUm9vdCBSb290IENBIEVDQyBHMiAyMDIwMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAEyFyAyk7CKB9XvzjmYSP80KlblhYWwwxeFaWQCf84KLR6HgrWUyrB +u5BAdDfpgeiNL2gBNXxSLtj0WLMRHFvZhxiTkS3sndpsnm2ESPzCiQXrmBMCAWxT +Hg5JY1hHsa/Co2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFFsfxHFs +shufvlwfjP2ztvuzDgmHMB0GA1UdDgQWBBRbH8RxbLIbn75cH4z9s7b7sw4JhzAO +BgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAOzgmf3d5FTByx/oPijX +FVlKgspTMOzrNqW5yM6TR1bIYabhbZJTlY/241VT8N165wIxALCH1RuzYPyRjYDK +ohtRSzhUy6oee9flRJUWLzxEeC4luuqQ5OxS7lfsA4TzXtsWDQ== +-----END CERTIFICATE----- + +// Atos TrustedRoot Root CA ECC TLS 2021 +-----BEGIN CERTIFICATE----- +MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4w +LAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0w +CwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0 +MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBF +Q0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMHYwEAYHKoZI +zj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6KDP/X +tXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4 +AjJn8ZQSb+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2 +KCXWfeBmmnoJsmo7jjPXNtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMD +aAAwZQIwW5kp85wxtolrbNa9d+F851F+uDrNozZffPc8dz7kUK2o59JZDCaOMDtu +CCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGYa3cpetskz2VAv9LcjBHo +9H1/IISpQuQo +-----END CERTIFICATE----- + +// Atos TrustedRoot Root CA RSA G2 2020 +-----BEGIN CERTIFICATE----- +MIIFfzCCA2egAwIBAgIMR7opRlU+FpKXsKtAMA0GCSqGSIb3DQEBDAUAMEsxCzAJ +BgNVBAYTAkRFMQ0wCwYDVQQKDARBdG9zMS0wKwYDVQQDDCRBdG9zIFRydXN0ZWRS +b290IFJvb3QgQ0EgUlNBIEcyIDIwMjAwHhcNMjAxMjE1MDg0MTIzWhcNNDAxMjEw +MDg0MTIyWjBLMQswCQYDVQQGEwJERTENMAsGA1UECgwEQXRvczEtMCsGA1UEAwwk +QXRvcyBUcnVzdGVkUm9vdCBSb290IENBIFJTQSBHMiAyMDIwMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAljGFSqoPMv554UOHnPsjt45/DVS9x2KTd+Qc +NQR2owOLIu7EhN2lk25uso4JA+tRFjEXqmkVGA5ndCNe6pp9tTk+PYKpa+H+qRyw +rVpNTHiDQYvP8h1impgEnGPpq2X+SB0kZQdHPrmRLumdm38aNak0sLflcDPvSnJR +tge/YD8qn51U3/PXlElRA1pAqWjdEVlc+HamvFBSEO2s7JXg1INrSdoKT5mD3jKD +SINnlbJ+54GFPc2C98oC7W2IXQiNuDW/KmkwmbtL0UHbRaCTmVGBkDYIqoq26I+z +y+7lRg1ydfVJbOGify+87YSmN+7ewk85Tvae8MnRmzCdSW3h2v8SEIzW5Zl7BbZ9 +sAnHpPiyHDmVOTP0Nc4lYnuwXyDzy234bFIUZESP08ipdgflr3GZLS0EJUh2r8Pn +zEPyB7xKJCQ33fpulAlvTF4BtP5U7COWpV7dhv/pRirx6NzspT2vb6oOD7R1+j4I +uSZFT2aGTLwZuOHVNe6ChMjTqxLnzXMzYnf0F8u9NHYqBc6V5Xh5S56wjfk8WDiR +6l6HOMC3Qv2qTIcjrQQgsX52Qtq7tha6V8iOE/p11QhMrziRqu+P+p9JLlR8Clax +evrETi/Uo/oWitCV5Zem/8P8fA5HWPN/B3sS3Fc/LeOhTVtSTDOHmagJe2x+DvLP +VkKe6wUCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBQgJfMH +/adv8ZbukRBpzJrvfchoeDAdBgNVHQ4EFgQUICXzB/2nb/GW7pEQacya733IaHgw +DgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQAkK06Y8h0X7dl2JrYw +M+hpRaFRS1LYejowtuQS6r+fTOAEpPY1xv6hMPdThZKtVAVXX5LlKt42J557E0fJ +anWv/PM35wz1PQFztWlR+L1Z0boL+Lq6ZCdDs3yDlYrnnhOW129KlkFJiw4grRbG +96aHW4gSiYuJyhLSVq8iASFG6auYP6eI3uTLKpp1Gfo5XgkF1wMyGrgXUQjHAEB9 +9L74DFn0aXZu06RYW14mc+RCVQZeeEAP0zif7yZRcHSR8XdiAejZy+uh3zkyHbtr +/XH+68+l5hT9AIATxpoASLCZBemugEj7CT9RFLW552BNTcovgSHuUgxletz1iUlM +MJI0WIAyWbEN/yRhD+cKQtB7vPiOJ0c/cJ0n2bYGPaW7y16Prg5Tx5xqbztMD6NA +cKiaB87UblsHotLiVLa9bzNyY61RmOGPdvFqBzgl/vZizl/bY8Jume8G3LneGRro +VD190nZ12V4+MkinjPKecgz4uFi4FyOlFId1WHoAgQciOWpMlKC1otunLMGw8aOb +wEz3bXDqMZ/xrn0+cyjZod/6k/CbsPDizSUgde/ifTIFyZt27su9MR75lJhLJFhW +SMDeBky9pjRd7RZhY3P7GeL6W9iXddRtnmA5XpSLAizrmc5gKm4bjKdLvP025pgf +ZfJ/8eOPTIBGNli2oWXLzhxEdQ== +-----END CERTIFICATE----- + +// Atos TrustedRoot Root CA RSA TLS 2021 +-----BEGIN CERTIFICATE----- +MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBM +MS4wLAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIx +MQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00 +MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBD +QSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BBl01Z +4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYv +Ye+W/CBGvevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZ +kmGbzSoXfduP9LVq6hdKZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDs +GY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt0xU6kGpn8bRrZtkh68rZYnxGEFzedUln +nkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVKPNe0OwANwI8f4UDErmwh +3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMYsluMWuPD +0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzy +geBYBr3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8 +ANSbhqRAvNncTFd+rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezB +c6eUWsuSZIKmAMFwoW4sKeFYV+xafJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lI +pw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +dEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +DAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS +4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPs +o0UvFJ/1TCplQ3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJ +qM7F78PRreBrAwA0JrRUITWXAdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuyw +xfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9GslA9hGCZcbUztVdF5kJHdWoOsAgM +rr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2VktafcxBPTy+av5EzH4 +AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9qTFsR +0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuY +o7Ey7Nmj1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5 +dDTedk+SKlOxJTnbPP/lPqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcE +oji2jbDwN/zIIX8/syQbPYtuzE2wFg2WHYMfRsCbvUOZ58SWLs5fyQ== +-----END CERTIFICATE----- + +// GlobalSign +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg +MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx +MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET +MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI +xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k +ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD +aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw +LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw +1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX +k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2 +SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h +bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n +WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY +rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce +MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu +bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt +Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61 +55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj +vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf +cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz +oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp +nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs +pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v +JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R +8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4 +5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- + +// GlobalSign +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc +8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke +hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI +KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg +515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO +xwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +// GlobalSign +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 +MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 +RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT +gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm +KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd +QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ +XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o +LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU +RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp +jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK +6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX +mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs +Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH +WD9f +-----END CERTIFICATE----- + +// GlobalSign Root CA +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG +A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv +b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw +MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i +YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT +aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ +jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp +xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp +1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG +snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ +U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 +9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B +AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz +yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE +38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP +AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad +DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME +HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- + +// GlobalSign Root E46 +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx +CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD +ExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw +MDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2Ex +HDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkBjtjq +R+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGdd +yXqBPCCjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ +7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZkvLtoURMMA/cVi4RguYv/Uo7njLwcAjA8 ++RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE----- + +// GlobalSign Root R46 +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA +MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD +VQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy +MDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYt +c2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08EsCVeJ +OaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQG +vGIFAha/r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud +316HCkD7rRlr+/fKYIje2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo +0q3v84RLHIf8E6M6cqJaESvWJ3En7YEtbWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSE +y132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvjK8Cd+RTyG/FWaha/LIWF +zXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD412lPFzYE ++cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCN +I/onccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzs +x2sZy/N78CsHpdlseVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqa +ByFrgY/bxFn63iLABJzjqls2k+g9vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC +4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEMBQADggIBAHx4 +7PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti +2kM3S+LGteWygxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIk +pnnpHs6i58FZFZ8d4kuaPp92CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRF +FRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZmOUdkLG5NrmJ7v2B0GbhWrJKsFjLt +rWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qqJZ4d16GLuc1CLgSk +ZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwyeqiv5 +u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP +4vkYxboznxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6 +N3ec592kD3ZDZopD8p/7DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3 +vouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6 +-----END CERTIFICATE----- + +// GlobalSign Secure Mail Root E45 +-----BEGIN CERTIFICATE----- +MIICITCCAaegAwIBAgIQdlP+qicdlUZd1vGe5biQCjAKBggqhkjOPQQDAzBSMQsw +CQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEoMCYGA1UEAxMf +R2xvYmFsU2lnbiBTZWN1cmUgTWFpbCBSb290IEU0NTAeFw0yMDAzMTgwMDAwMDBa +Fw00NTAzMTgwMDAwMDBaMFIxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxT +aWduIG52LXNhMSgwJgYDVQQDEx9HbG9iYWxTaWduIFNlY3VyZSBNYWlsIFJvb3Qg +RTQ1MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE+XmLgUc3iZY/RUlQfxomC5Myfi7A +wKcImsNuj5s+CyLsN1O3b4qwvCc3S22pRjvZH/+loUS7LXO/nkEHXFObUQg6Wrtv +OMcWkXjCShNpHYLfWi8AiJaiLhx0+Z1+ZjeKo0IwQDAOBgNVHQ8BAf8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU3xNei1/CQAL9VreUTLYe1aaxFJYw +CgYIKoZIzj0EAwMDaAAwZQIwE7C+13EgPuSrnM42En1fTB8qtWlFM1/TLVqy5IjH +3go2QjJ5naZruuH5RCp7isMSAjEAoGYcToedh8ntmUwbCu4tYMM3xx3NtXKw2cbv +vPL/P/BS3QjnqmR5w+RpV5EvpMt8 +-----END CERTIFICATE----- + +// GlobalSign Secure Mail Root R45 +-----BEGIN CERTIFICATE----- +MIIFcDCCA1igAwIBAgIQdlP+qExQq5+NMrUdA49X3DANBgkqhkiG9w0BAQwFADBS +MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEoMCYGA1UE +AxMfR2xvYmFsU2lnbiBTZWN1cmUgTWFpbCBSb290IFI0NTAeFw0yMDAzMTgwMDAw +MDBaFw00NTAzMTgwMDAwMDBaMFIxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i +YWxTaWduIG52LXNhMSgwJgYDVQQDEx9HbG9iYWxTaWduIFNlY3VyZSBNYWlsIFJv +b3QgUjQ1MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA3HnMbQb5bbvg +VgRsf+B1zC0FSehL3FTsW3eVcr9/Yp2FqYokUF9T5dt0b6QpWxMqCa2axS/C93Y7 +oUVGqkPmJP4rsG8ycBlGWnkmL/w9fV9ky1fMYWGo2ZVu45Wgbn9HEhjW7wPJ+4r6 +mr2CFalVd0sRT1nga8Nx8wzYVNWBaD4TuRUuh4o8RCc2YiRu+CwFcjBhvUKRI8Sd +JafZVJoUozGtgHkMp2NsmKOsV0czH2WW4dDSNdr5cfehpiW1QV3fPmDY0fafpfK4 +zBOqj/mybuGDLZPdPoUa3eixXCYBy0mF/PzS1H+FYoZ0+cvsNSKiDDCPO6t561by ++kLz7fkfRYlAKa3qknTqUv1WtCvaou11wm6rzlKQS/be8EmPmkjUiBltRebMjLnd +ZGBgAkD4uc+8WOs9hbnGCtOcB2aPxxg5I0bhPB6jL1Bhkgs9K2zxo0c4V5GrDY/G +nU0E0iZSXOWl/SotFioBaeepfeE2t7Eqxdmxjb25i87Mi6E+C0jNUJU0xNgIWdhr +JvS+9dQiFwBXya6bBDAznwv731aiyW5Udtqxl2InWQ8RiiIbZJY/qPG3JEqNPFN8 +bYN2PbImSHP1RBYBLQkqjhaWUNBzBl27IkiCTApGWj+A/1zy8pqsLAjg1urwEjiB +T6YQ7UarzBacC89kppkChURnRq39TecCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgGG +MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKCTFShu7o8IsjXGnmJ5dKexDit7 +MA0GCSqGSIb3DQEBDAUAA4ICAQBFCvjRXKxigdAE17b/V1GJCwzL3iRlN/urnu1m +9OoMGWmJuBmxMFa02fb3vsaul8tF9hGMOjBkTMGfWcBGQggGR2QXeOCVBwbWjKKs +qdk/03tWT/zEhyjftisWI8CfH1vj1kReIk8jBIw1FrV5B4ZcL5fi9ghkptzbqIrj +pHt3DdEpkyggtFOjS05f3sH2dSP8Hzx4T3AxeC+iNVRxBKzIxG3D9pGx/s3uRG6B +9kDFPioBv6tMsQM/DRHkD9Ik4yKIm59fRz1RSeAJN34XITF2t2dxSChLJdcQ6J9h +WRbFPjJOHwzOo8wP5McRByIvOAjdW5frQmxZmpruetCd38XbCUMuCqoZPWvoajB6 +V+a/s2o5qY/j8U9laLa9nyiPoRZaCVA6Mi4dL0QRQqYA5jGY/y2hD+akYFbPedey +Ttew+m4MVyPHzh+lsUxtGUmeDn9wj3E/WCifdd1h4Dq3Obbul9Q1UfuLSWDIPGau +l+6NJllXu3jwelAwCbBgqp9O3Mk+HjrcYpMzsDpUdG8sMUXRaxEyamh29j32ahNe +JJjn6h2az3iCB2D3TRDTgZpFjZ6vm9yAx0OylWikww7oCkcVv1Qz3AHn1aYec9h6 +sr8vreNVMJ7fDkG84BH1oQyoIuHjAKNOcHyS4wTRekKKdZBZ45vRTKJkvXN5m2/y +s8H2PA== +-----END CERTIFICATE----- + +// Go Daddy Class 2 Certification Authority +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh +MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE +YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 +MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo +ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg +MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN +ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA +PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w +wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi +EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY +avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ +YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE +sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h +/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 +IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy +OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P +TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER +dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf +ReYNnyicsbkqWletNw+vHX/bvZ8= +-----END CERTIFICATE----- + +// Go Daddy Root Certificate Authority - G2 +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT +EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp +ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz +NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH +EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE +AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD +E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH +/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy +DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh +GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR +tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA +AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX +WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu +9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr +gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo +2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI +4uJEvlz36hz1 +-----END CERTIFICATE----- + +// Starfield Class 2 Certification Authority +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl +MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp +U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw +NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE +ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp +ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3 +DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf +8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN ++lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0 +X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa +K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA +1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G +A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR +zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0 +YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD +bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3 +L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D +eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp +VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY +WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- + +// Starfield Root Certificate Authority - G2 +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs +ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw +MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj +aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp +Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg +nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 +HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N +Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN +dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 +HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G +CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU +sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 +4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg +8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 +mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +// GlobalSign +-----BEGIN CERTIFICATE----- +MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD +VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw +MTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0g +UjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wWTAT +BgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkWymOx +uYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNV +HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/ ++wpu+74zyTyjhNUwCgYIKoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147 +bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm +-----END CERTIFICATE----- + +// GTS Root R1 +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw +CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw +MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp +Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo +27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w +Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw +TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl +qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH +szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8 +Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk +MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92 +wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p +aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN +VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID +AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb +C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe +QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy +h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4 +7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J +ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef +MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/ +Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT +6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ +0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm +2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb +bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c +-----END CERTIFICATE----- + +// GTS Root R2 +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw +CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +MBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw +MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp +Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3LvCvpt +nfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY +6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAu +MC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7k +RXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWg +f9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1mKPV ++3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K8Yzo +dDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW +Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKa +G73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCq +gc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwID +AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBAB/Kzt3H +vqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8 +0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyC +B19m3H0Q/gxhswWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2u +NmSRXbBoGOqKYcl3qJfEycel/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMg +yALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVnjWQye+mew4K6Ki3pHrTgSAai/Gev +HyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y59PYjJbigapordwj6 +xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M7YNR +TOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924Sg +JPFI/2R80L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV +7LXTWtiBmelDGDfrs7vRWGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl +6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjWHYbL +-----END CERTIFICATE----- + +// GTS Root R3 +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD +VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG +A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw +WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz +IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout736G +jOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL2 +4CejQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7 +VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azTL818+FsuVbu/3ZL3pAzcMeGiAjEA/Jdm +ZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X +-----END CERTIFICATE----- + +// GTS Root R4 +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD +VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG +A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw +WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz +IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi +QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR +HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D +9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8 +p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD +-----END CERTIFICATE----- + +// Hongkong Post Root CA 3 +-----BEGIN CERTIFICATE----- +MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL +BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ +SG9uZyBLb25nMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25n +a29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2MDMwMjI5NDZaFw00MjA2MDMwMjI5 +NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtvbmcxEjAQBgNVBAcT +CUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMXSG9u +Z2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCziNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFO +dem1p+/l6TWZ5Mwc50tfjTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mI +VoBc+L0sPOFMV4i707mV78vH9toxdCim5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV +9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOesL4jpNrcyCse2m5FHomY +2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj0mRiikKY +vLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+Tt +bNe/JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZb +x39ri1UbSsUgYT2uy1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+ +l2oBlKN8W4UdKjk60FSh0Tlxnf0h+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YK +TE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsGxVd7GYYKecsAyVKvQv83j+Gj +Hno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwIDAQABo2MwYTAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e +i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEw +DQYJKoZIhvcNAQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG +7BJ8dNVI0lkUmcDrudHr9EgwW62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCk +MpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWldy8joRTnU+kLBEUx3XZL7av9YROXr +gZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov+BS5gLNdTaqX4fnk +GMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDceqFS +3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJm +Ozj/2ZQw9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+ +l6mc1X5VTMbeRRAc6uk7nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6c +JfTzPV4e0hz5sy229zdcxsshTrD3mUcYhcErulWuBurQB7Lcq9CClnXO0lD+mefP +L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa +LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG +mpv0 +-----END CERTIFICATE----- + +// ACCVRAIZ1 +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE +AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw +CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ +BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND +VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb +qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY +HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo +G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA +lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr +IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/ +0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH +k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47 +4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO +m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa +cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl +uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI +KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls +ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG +AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT +VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG +CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA +cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA +QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA +7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA +cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA +QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA +czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu +aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt +aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud +DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF +BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp +D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU +JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m +AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD +vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms +tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH +7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA +h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF +d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H +pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +// AC RAIZ FNMT-RCM +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx +CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ +WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ +BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG +Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/ +yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf +BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz +WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF +tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z +374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC +IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL +mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7 +wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS +MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2 +ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet +UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H +YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3 +LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1 +RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM +LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf +77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N +JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm +fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp +6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp +1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B +9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok +RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv +uu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- + +// AC RAIZ FNMT-RCM SERVIDORES SEGUROS +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw +CQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw +FgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S +Q00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4MTIyMDA5MzczM1oXDTQzMTIyMDA5 +MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQtUkNNMQ4wDAYDVQQL +DAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNBQyBS +QUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LH +sbI6GA60XYyzZl2hNPk2LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oK +Um8BA06Oi6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqGSM49BAMDA2kAMGYCMQCu +SuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoDzBOQn5IC +MQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJy +v+c= +-----END CERTIFICATE----- + +// Staat der Nederlanden Root CA - G3 +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX +DTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl +ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv +b3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP +cPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW +IkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX +xz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy +KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR +9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az +5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8 +6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7 +Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP +bMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt +BznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt +XUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd +INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD +U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp +LiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8 +Ipf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp +gZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh +/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw +0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A +fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq +4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR +1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/ +QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM +94B7IWcnMFk= +-----END CERTIFICATE----- + +// TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx +GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp +bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w +KwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0 +BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy +dW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG +EwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll +IEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU +QUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT +TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg +LSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7 +a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr +LqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr +N3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X +YacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/ +iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f +AJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH +V8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf +IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4 +lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c +8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf +lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- + +// HARICA Client ECC Root CA 2021 +-----BEGIN CERTIFICATE----- +MIICWjCCAeGgAwIBAgIQMWjZ2OFiVx7SGUSI5hB98DAKBggqhkjOPQQDAzBvMQsw +CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh +cmNoIEluc3RpdHV0aW9ucyBDQTEnMCUGA1UEAwweSEFSSUNBIENsaWVudCBFQ0Mg +Um9vdCBDQSAyMDIxMB4XDTIxMDIxOTExMDMzNFoXDTQ1MDIxMzExMDMzM1owbzEL +MAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgQ0ExJzAlBgNVBAMMHkhBUklDQSBDbGllbnQgRUND +IFJvb3QgQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABAcYrZWWlNBcD4L3 +KkD6AsnJPTamowRqwW2VAYhgElRsXKIrbhM6iJUMHCaGNkqJGbcY3jvoqFAfyt9b +v0mAFdvjMOEdWscqigEH/m0sNO8oKJe8wflXhpWLNc+eWtFolaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUUgjSvjKBJf31GpfsTl8au1PNkK0wDgYDVR0P +AQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMEwxRUZPqOa+w3eyGhhLLYh7WOar +lGtEA7AX/9+Cc0RRLP2THQZ7FNKJ7EAM7yEBLgIwL8kuWmwsHdmV4J6wuVxSfPb4 +OMou8dQd8qJJopX4wVheT/5zCu8xsKsjWBOMi947 +-----END CERTIFICATE----- + +// HARICA Client RSA Root CA 2021 +-----BEGIN CERTIFICATE----- +MIIFqjCCA5KgAwIBAgIQVVL4HtsbJCyeu5YYzQIoPjANBgkqhkiG9w0BAQsFADBv +MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEnMCUGA1UEAwweSEFSSUNBIENsaWVudCBS +U0EgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTg0NloXDTQ1MDIxMzEwNTg0NVow +bzELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBS +ZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJzAlBgNVBAMMHkhBUklDQSBDbGllbnQg +UlNBIFJvb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +AIHbV0KQLHQ19Pi4dBlNqwlad0WBc2KwNZ/40LczAIcTtparDlQSMAe8m7dI19EZ +g66O2KnxqQCEsIxenugMj1Rpv/bUCE8mcP4YQWMaszKLQPgHq1cx8MYWdmeatN0v +8tFrxdCShJFxbg8uY+kfU6TdUhPMCYMpgQzFU3VEsQ5nUxjQwx+IS5+UJLQpvLvo +Tv1v0hUdSdyNcPIRGiBRVRG6iG/E91B51qox4oQ9XjLIdypQceULL+m26u+rCjM5 +Dv2PpWdDgo6YaQkJG0DNOGdH6snsl3ES3iT1cjzR90NMJveQsonpRUtVPTEFekHi +lbpDwBfFtoU9GY1kcPNbrM2f0yl1h0uVZ2qm+NHdvJCGiUMpqTdb9V2wJlpTQnaQ +K8+eVmwrVM9cmmXfW4tIYDh8+8ULz3YEYwIzKn31g2fn+sZD/SsP1CYvd6QywSTq +ZJ2/szhxMUTyR7iiZkGh+5t7vMdGanW/WqKM6GpEwbiWtcAyCC17dDVzssrG/q8R +chj258jCz6Uq6nvWWeh8oLJqQAlpDqWW29EAufGIbjbwiLKd8VLyw3y/MIk8Cmn5 +IqRl4ZvgdMaxhZeWLK6Uj1CmORIfvkfygXjTdTaefVogl+JSrpmfxnybZvP+2M/u +vZcGHS2F3D42U5Z7ILroyOGtlmI+EXyzAISep0xxq0o3AgMBAAGjQjBAMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFKDWBz1eJPd7oEQuJFINGaorBJGnMA4GA1Ud +DwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEADUf5CWYxUux57sKo8mg+7ZZF +yzqmmGM/6itNTgPQHILhy9Pl1qtbZyi8nf4MmQqAVafOGyNhDbBX8P7gyr7mkNuD +LL6DjvR5tv7QDUKnWB9p6oH1BaX+RmjrbHjJ4Orn5t4xxdLVLIJjKJ1dqBp+iObn +K/Es1dAFntwtvTdm1ASip62/OsKoO63/jZ0z4LmahKGHH3b0gnTXDvkwSD5biD6q +XGvWLwzojnPCGJGDObZmWtAfYCddTeP2Og1mUJx4e6vzExCuDy+r6GSzGCCdRjVk +JXPqmxBcWDWJsUZIp/Ss1B2eW8yppRoTTyRQqtkbbbFA+53dWHTEwm8UcuzbNZ+4 +VHVFw6bIGig1Oq5l8qmYzq9byTiMMTt/zNyW/eJb1tBZ9Ha6C8tPgxDHQNAdYOkq +5UhYdwxFab4ZcQQk4uMkH0rIwT6Z9ZaYOEgloRWwG9fihBhb9nE1mmh7QMwYXAwk +ndSV9ZmqRuqurL/0FBkk6Izs4/W8BmiKKgwFXwqXdafcfsD913oY3zDROEsfsJhw +v8x8c/BuxDGlpJcdrL/ObCFKvicjZ/MGVoEKkY624QMFMyzaNAhNTlAjrR+lxdR6 +/uoJ7KcoYItGfLXqm91P+edrFcaIz0Pb5SfcBFZub0YV8VYt6FwMc8MjgTggy8kM +ac8sqzuEYDMZUv1pFDM= +-----END CERTIFICATE----- + +// HARICA TLS ECC Root CA 2021 +-----BEGIN CERTIFICATE----- +MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw +CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh +cmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v +dCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoXDTQ1MDIxMzExMDEwOVowbDELMAkG +A1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj +aCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJvb3Qg +Q0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7 +KKrxcm1lAEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9Y +STHMmE5gEYd103KUkE+bECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQD +AgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAircJRQO9gcS3ujwLEXQNw +SaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/QwCZ61IygN +nxS2PFOiTAZpffpskcYqSUXm7LcT4Tps +-----END CERTIFICATE----- + +// HARICA TLS RSA Root CA 2021 +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs +MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg +Um9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUzOFoXDTQ1MDIxMzEwNTUzN1owbDEL +MAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNBIFJv +b3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569l +mwVnlskNJLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE +4VGC/6zStGndLuwRo0Xua2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uv +a9of08WRiFukiZLRgeaMOVig1mlDqa2YUlhu2wr7a89o+uOkXjpFc5gH6l8Cct4M +pbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K5FrZx40d/JiZ+yykgmvw +Kh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEvdmn8kN3b +LW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcY +AuUR0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqB +AGMUuTNe3QvboEUHGjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYq +E613TBoYm5EPWNgGVMWX+Ko/IIqmhaZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHr +W2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQCPxrvrNQKlr9qEgYRtaQQJKQ +CoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAU +X15QvWiWkKQUEapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3 +f5Z2EMVGpdAgS1D0NTsY9FVqQRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxaja +H6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxDQpSbIPDRzbLrLFPCU3hKTwSUQZqP +JzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcRj88YxeMn/ibvBZ3P +zzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5vZSt +jBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0 +/L5H9MG0qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pT +BGIBnfHAT+7hOtSLIBD6Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79 +aPib8qXPMThcFarmlwDB31qlpzmq6YR/PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YW +xw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnnkf3/W9b3raYvAwtt41dU +63ZTGI0RmLo= +-----END CERTIFICATE----- + +// Hellenic Academic and Research Institutions ECC RootCA 2015 +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN +BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl +bGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv +b3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ +BgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj +YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5 +MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0 +dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg +QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa +jq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi +C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep +lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof +TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- + +// Hellenic Academic and Research Institutions RootCA 2015 +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix +DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k +IFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT +N0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9v +dENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAxMTIxWjCBpjELMAkG +A1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNh +ZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkx +QDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 +dGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA +4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0 +AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe10 +4S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06C +ojXdFPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV +9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrD +gfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6 +Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZYeCsargq +NhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko +LfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVd +ctA4GGqd83EkVAswDQYJKoZIhvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0I +XtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMI +M4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrMd/K4kPFox/la/vot +9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+yd+2V +Z5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/ea +j8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh +X9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQ +l033DlZdwJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdf +bzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4 +pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK +e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0 +vm9qp/UsQu0yrbYhnr68 +-----END CERTIFICATE----- + +// IdenTrust Commercial Root CA 1 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu +VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw +MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw +JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT +3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU ++ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp +S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1 +bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi +T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL +vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK +Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK +dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT +c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv +l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N +iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD +ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt +LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93 +nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3 ++wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK +W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT +AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq +l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG +4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ +mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A +7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +// IdenTrust Public Sector Root CA 1 +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu +VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN +MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0 +MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7 +ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy +RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS +bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF +/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R +3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw +EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy +9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V +GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ +2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV +WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD +W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN +AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV +DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9 +TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G +lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW +mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df +WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5 ++bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ +tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA +GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv +8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +// ISRG Root X1 +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- + +// ISRG Root X2 +-----BEGIN CERTIFICATE----- +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw +CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg +R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00 +MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT +ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW ++1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9 +ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI +zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW +tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1 +/q4AaOeMSQ+2b1tbFfLn +-----END CERTIFICATE----- + +// Izenpe.com +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4 +MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6 +ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD +VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j +b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq +scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO +xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H +LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX +uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD +yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+ +JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q +rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN +BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L +hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB +QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+ +HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu +Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg +QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB +BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA +A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb +laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56 +awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo +JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw +LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT +VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk +LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb +UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/ +QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+ +naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls +QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE----- + +// SZAFIR ROOT CA2 +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6 +ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw +NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L +cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg +Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN +QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT +3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw +3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6 +3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5 +BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN +XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF +AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw +8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG +nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP +oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy +d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg +LvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- + +// LAWtrust Root CA2 (4096) +-----BEGIN CERTIFICATE----- +MIIFmDCCA4CgAwIBAgIEVRpusTANBgkqhkiG9w0BAQsFADBDMQswCQYDVQQGEwJa +QTERMA8GA1UEChMITEFXdHJ1c3QxITAfBgNVBAMTGExBV3RydXN0IFJvb3QgQ0Ey +ICg0MDk2KTAgFw0yMzAyMTQwOTE5MzhaGA8yMDUzMDIxNDA5NDkzOFowQzELMAkG +A1UEBhMCWkExETAPBgNVBAoTCExBV3RydXN0MSEwHwYDVQQDExhMQVd0cnVzdCBS +b290IENBMiAoNDA5NikwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM +F8srQ7ps+cmTimUNEkzsJxS3E3ng1NUtGFbx+eoqEBZObETHamVG85qJNdGH+DOJ +L4gJGpIQkZDBa58Obn8mihNdGKxoAQ0QeGVw2I6PhFqXMBjQEQ5KjVIQpYErUSj1 +Y8S27ECzAeWtd73lOO+8jbPdGaB7DY2022r7JTNa+pGvxHFFMPiIKXvLv9W6JwSO +3bIA98pcmTUU6v11BhUIu8pXaPs/+7Q0c2PR1ePIOFppfWp6RAwNik7tkh0Qjzsi +LLbf7cXG8Il5VGVeXxu9j33fubft6+TFB9FnPJU7kf5CelJAgATSOVdL9JJ9/5vv +5Z3JCbKREjimKQg7ruvKzO1N504hAQf8bzLOaYyEUsZ36icwCt6lrzAraB+s1Owh +rSJJds4PwvIHKvlqEoOaOwSuGXr+oYYk+kFeJXxArCe24yk2bzXiV9AZWN//ZPbD +AUl22yu+vLlPFArVG1gh9hwuAHz4lLXLNxoU5DK5FtRg7AWqXzL6aiMSrNQQu9Ki +grRLDotwJ6rWB8FniPqEwwjJioTI0jdygQ+NFkrk1zVRpTgPjIRLlTbA9ded4F2P +q5HuAAi5nVIf7PiZu3lWsUna0uXYYYtbr/CrN8V7Go6Gvn7FexUeYWjoC4eLc0mh +F3N+KXiOyuBBL3VzdKKXOn/3LnQJuExgi0Y2GRAtnQIDAQABo4GRMIGOMA8GA1Ud +EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMCsGA1UdEAQkMCKADzIwMjMwMjE0 +MDkxOTM4WoEPMjA1MzAyMTQwOTQ5MzhaMB8GA1UdIwQYMBaAFNfWVmJcPxeB5nNE +KfVRBe8LYDesMB0GA1UdDgQWBBTX1lZiXD8XgeZzRCn1UQXvC2A3rDANBgkqhkiG +9w0BAQsFAAOCAgEASZwp/j3snkV/qz48/iNvNz53p1P/eJ/8SUSAV2acbtp5/81F +rUyTv7VZxukQt+X4jPuHxR6L2LM/ApYKu4qO79e0wIMgOJdZRWT89ncT8gnXocg4 +dAjq+UhM+h8EnLT/7G5WNnKTbJU+LF/eDwurycwVPhaPZvyyELih0bTewGMZzO9T +qnU2IoslH7+byNfBX+ymNwmqe2K89iIt8dZY3Yy7UvQLp3apensajdytmoFiLoYF +kHJHL6HJZ4SwDWywuJsWt9CZFC+cEpsjqI2mQx7p5S3leKcfZJRktneyqFz7Casp +6x5tddH20MWlwx2fHvMaLbLIH+UoCm7zX/3a5iOhdpBcS5gBgizuRy0CGl9/NMVp +tXKtPvPPnm34KegRJyvgWQsbYetKymmlpNXNURuUjnnN3/audF2xLBuGU/7RMAZB +NAdigkz0fseHdA6wIR4JIIDBsxU9Rm3T8QaSP++glYocbncxtut4KQx77oKlT36k +KV6eqi34jsDz/A0GhZtO3PfiCXzQFFEeerMjr/rRYSpltQHZuOMHyiR20vBKvu+G +BIBCFXARaH7Xx7v+506bnJWlHEqkydAJjKrOSNIekpfXEentZsw33PXXG3SbpupC +rF0y4Fj0gUf/0hLifhzcSXaWwx2fS8pcKjdbPYrROJsh2uO/RUPT4Fh3Hyg= +-----END CERTIFICATE----- + +// e-Szigno Root CA 2017 +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV +BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk +LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv +b3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ +BgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg +THRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v +IFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv +xie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H +Wyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB +eAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo +jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ ++efcMQ== +-----END CERTIFICATE----- + +// e-Szigno TLS Root CA 2023 +-----BEGIN CERTIFICATE----- +MIICzzCCAjGgAwIBAgINAOhvGHvWOWuYSkmYCjAKBggqhkjOPQQDBDB1MQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xFzAVBgNVBGEMDlZBVEhVLTIzNTg0NDk3MSIwIAYDVQQDDBllLVN6aWdubyBU +TFMgUm9vdCBDQSAyMDIzMB4XDTIzMDcxNzE0MDAwMFoXDTM4MDcxNzE0MDAwMFow +dTELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRYwFAYDVQQKDA1NaWNy +b3NlYyBMdGQuMRcwFQYDVQRhDA5WQVRIVS0yMzU4NDQ5NzEiMCAGA1UEAwwZZS1T +emlnbm8gVExTIFJvb3QgQ0EgMjAyMzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAE +AGgP36J8PKp0iGEKjcJMpQEiFNT3YHdCnAo4YKGMZz6zY+n6kbCLS+Y53wLCMAFS +AL/fjO1ZrTJlqwlZULUZwmgcAOAFX9pQJhzDrAQixTpN7+lXWDajwRlTEArRzT/v +SzUaQ49CE0y5LBqcvjC2xN7cS53kpDzLLtmt3999Cd8ukv+ho2MwYTAPBgNVHRMB +Af8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUWYQCYlpGePVd3I8K +ECgj3NXW+0UwHwYDVR0jBBgwFoAUWYQCYlpGePVd3I8KECgj3NXW+0UwCgYIKoZI +zj0EAwQDgYsAMIGHAkIBLdqu9S54tma4n7Zwf2Z0z+yOfP7AAXmazlIC58PRDHpt +y7Ve7hekm9sEdu4pKeiv+62sUvTXK9Z3hBC9xdIoaDQCQTV2WnXzkoYI9bIeCvZl +C9p2x1L/Cx6AcCIwwzPbGO2E14vs7dOoY4G1VnxHx1YwlGhza9IuqbnZLBwpvQy6 +uWWL +-----END CERTIFICATE----- + +// Microsec e-Szigno Root CA 2009 +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G +CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y +OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx +FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp +Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP +kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc +cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U +fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7 +N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC +xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1 ++rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM +Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG +SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h +mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk +ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c +2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t +HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +// Microsoft ECC Root Certificate Authority 2017 +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD +VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw +MTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4MjMxNjA0WjBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNy +b3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZR +ogPZnZH6thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYb +hGBKia/teQ87zvH2RPUBeMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3 +FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV +L8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB +iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- + +// Microsoft RSA Root Certificate Authority 2017 +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl +MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N +aWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ +Nt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0 +ZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1 +HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm +gGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ +jEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc +aDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG +YaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6 +W6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K +UGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH ++FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q +W5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC +LgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC +gMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6 +tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh +SnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2 +TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3 +pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR +xpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp +GWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9 +dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN +AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB +RA+GsCyRxj3qrg+E +-----END CERTIFICATE----- + +// NAVER Global Root Certification Authority +-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM +BQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG +T1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4NDJaFw0zNzA4MTgyMzU5NTlaMGkx +CzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVTUyBQTEFURk9STSBD +b3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVA +iQqrDZBbUGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH +38dq6SZeWYp34+hInDEW+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lE +HoSTGEq0n+USZGnQJoViAbbJAh2+g1G7XNr4rRVqmfeSVPc0W+m/6imBEtRTkZaz +kVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2aacp+yPOiNgSnABIqKYP +szuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4Yb8Obtoq +vC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHf +nZ3zVHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaG +YQ5fG8Ir4ozVu53BA0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo +0es+nPxdGoMuK8u180SdOqcXYZaicdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3a +CJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejyYhbLgGvtPe31HzClrkvJE+2K +AQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNVHQ4EFgQU0p+I +36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoN +qo0hV4/GPnrK21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatj +cu3cvuzHV+YwIHHW1xDBE1UBjCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm ++LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bxhYTeodoS76TiEJd6eN4MUZeoIUCL +hr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTgE34h5prCy8VCZLQe +lHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTHD8z7 +p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8 +piKCk5XQA76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLR +LBT/DShycpWbXgnbiUSYqqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX +5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oGI/hGoiLtk/bdmuYqh7GYVPEi92tF4+KO +dh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul +9XXeifdy +-----END CERTIFICATE----- + +// NetLock Arany (Class Gold) Főtanúsítvány +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG +EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 +MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl +cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR +dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB +pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM +b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm +aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz +IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT +lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz +AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5 +VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG +ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2 +BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG +AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M +U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh +bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C ++C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F +uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 +XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +// OISTE Client Root ECC G1 +-----BEGIN CERTIFICATE----- +MIICNDCCAbqgAwIBAgIQVOyX1ou0xAshbg6y0FPIejAKBggqhkjOPQQDAzBLMQsw +CQYDVQQGEwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UEAwwY +T0lTVEUgQ2xpZW50IFJvb3QgRUNDIEcxMB4XDTIzMDUzMTE0MzE0MFoXDTQ4MDUy +NDE0MzEzOVowSzELMAkGA1UEBhMCQ0gxGTAXBgNVBAoMEE9JU1RFIEZvdW5kYXRp +b24xITAfBgNVBAMMGE9JU1RFIENsaWVudCBSb290IEVDQyBHMTB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABIhOaB/Jnr46BFsVwzX0zFDFCK04bqg80gK6zKsl/XVA/WcZ +nxsKXfbLFnv5XB6C3BVE1Jw8bWGTRfRPz2K53z5TjZrUSt6Iqgum8dRh1h501Riy +xU1M74B77A3rgzlUlqNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSZ +Vzs5sS0AjCFmjJVpnG117Iw/+jAdBgNVHQ4EFgQUmVc7ObEtAIwhZoyVaZxtdeyM +P/owDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMQCW/+SCThYiW6CF +GDw9Oo8gBggl5/WRNhmte7TfW2YSN3Nw7c0FKAdeCM4NQl8ZkQICMGdJh64GQR0g +0zGmqiY38SeKYQ3+mgZDpy6eJkejMhiL6F5QBfGwekh23tuhYkq6dw== +-----END CERTIFICATE----- + +// OISTE Client Root RSA G1 +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIQNBdvWQGIG6ql3chIu7Q7czANBgkqhkiG9w0BAQwFADBL +MQswCQYDVQQGEwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UE +AwwYT0lTVEUgQ2xpZW50IFJvb3QgUlNBIEcxMB4XDTIzMDUzMTE0MjMyOVoXDTQ4 +MDUyNDE0MjMyOFowSzELMAkGA1UEBhMCQ0gxGTAXBgNVBAoMEE9JU1RFIEZvdW5k +YXRpb24xITAfBgNVBAMMGE9JU1RFIENsaWVudCBSb290IFJTQSBHMTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBALpP/v5UE7WEPLzg0zHxHW7cxFNx+uQ5 +UUN2fZIfgX8Aa0HC5trcGE1sF1lwCTNi7GmILbDdWflhYGBW8ba07+uH0BP+w89v +j345WFGziQKOVJUeIl+rKAVDJ/hF9AlCJpT+vRN4u5HyEBCcDWd82mQg63owGrpI +DXhUKpkxNKvLpmrnDGc5ZqQmqCco5/PmPHPkK8xvMS4TdGHLaObSM85SvH5lJFoh +gTFDqrKc0RjnYTxSr4CJ6TRG3vlNmVptHb3GJdGTVY74J5JDOoyVRUDjiRinhsFZ +mMrbJhwTwIyBuZiwrWmtbhjje2JB9a02/gu0eyBfn6lu+ZmCElLSisRUeLR890Gb +A+cHXrPCuUlkZ5IWxGCQDrCCfTOt0Dbq0XZrfIhHmKwb+bRQjGGBadgx8436PvL1 +S6/Owx3vXygb6xjWoFhSMr5Cb81JlyLBcLnT42BP3oOCoE4wvXNTwr0X/aDAmI/q +DhcH5kOVIE7bEaj549O4J0cMJ9sS64FVzHXbn9MXQ8T764oobemvRFBaQ/vxOeKT +UM+Y/ESWWDilpe1Fw1JCBafv5TykrD3n1qlWBaqww6cZ5OU911dEbZQRH8pwyPy5 +TMxBWoN0U5B4z9bULk+xqk0u9dEIWzpk78inqHph7Oym1YhOtlTUWJHCJWSRvAoU +PZIUmrULBukvAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU +KYIlNQo6vpIr5AkD5OyPjThyOcswHQYDVR0OBBYEFCmCJTUKOr6SK+QJA+Tsj404 +cjnLMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQwFAAOCAgEAbSOGwv/14MjA +VYpgMcyXQ0dwQ9Pj7FL608Ke+4kyGspGk08Elyvb0JyEDZUHQlT+72kh35IDLo83 +ISN3qXc3bKDErpynWDlKFZdiRoNRIO0/wqPxw2In0KwTHv48Uh2Q1WPxqV7qf+fn +65ZaUezUqRvjDJRmrMuIkkm+c1yK4Gq8poHNs1zUI5LITfkgjHCUS2ht8o8ebDX3 +6F/U170gN1Jm/yu7SWa3cagsX3MPB5LnTl+lBtvJijyXxULqfQ+BG1frngwP/6Mn +IElTprM6TMttMDXa8vCa/lDfbVwkPU13an2GX0zQ4aa0rgQTAZDxgGiEB5SCB4Pr +keWTDnWRrqMjIElk1Lo5lldw7lU0KHzWr8qpnubJAckHwdBEsYC0UVCqj/ac5Wdz +0BvqgzUXL1DG3lbHu6MDy+KhGOj4zlEGo9IDQGEap2dXg/zRErkoqtpOa9Wc2IU3 +2r0i1zRZnBqmznjWlHgHBg+xkyGgSccQngquUXca+XGQw62YD4opamABqk+tIAMt +ao6jC2rW/ZMMimHLvSjxX3H9uDM51krx9rJoUj5lj0OdgSQk9ihMNaf9MwqleMEE +H+xJasSu1UQWpqeNf9ohlj6ouhZn1Kmh58Ka+BDZO5ruaPYvAO7Lu2aNIjiG9L9f +eKnIoB1au3VQ+VILDx0CLBQa84dqd/M= +-----END CERTIFICATE----- + +// OISTE Server Root ECC G1 +-----BEGIN CERTIFICATE----- +MIICNTCCAbqgAwIBAgIQI/nD1jWvjyhLH/BU6n6XnTAKBggqhkjOPQQDAzBLMQsw +CQYDVQQGEwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UEAwwY +T0lTVEUgU2VydmVyIFJvb3QgRUNDIEcxMB4XDTIzMDUzMTE0NDIyOFoXDTQ4MDUy +NDE0NDIyN1owSzELMAkGA1UEBhMCQ0gxGTAXBgNVBAoMEE9JU1RFIEZvdW5kYXRp +b24xITAfBgNVBAMMGE9JU1RFIFNlcnZlciBSb290IEVDQyBHMTB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABBcv+hK8rBjzCvRE1nZCnrPoH7d5qVi2+GXROiFPqOujvqQy +cvO2Ackr/XeFblPdreqqLiWStukhEaivtUwL85Zgmjvn6hp4LrQ95SjeHIC6XG4N +2xml4z+cKrhAS93mT6NjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBQ3 +TYhlz/w9itWj8UnATgwQb0K0nDAdBgNVHQ4EFgQUN02IZc/8PYrVo/FJwE4MEG9C +tJwwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2kAMGYCMQCpKjAd0MKfkFFR +QD6VVCHNFmb3U2wIFjnQEnx/Yxvf4zgAOdktUyBFCxxgZzFDJe0CMQCSia7pXGKD +YmH5LVerVrkR3SW+ak5KGoJr3M/TvEqzPNcum9v4KGm8ay3sMaE641c= +-----END CERTIFICATE----- + +// OISTE Server Root RSA G1 +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIQVaXZZ5Qoxu0M+ifdWwFNGDANBgkqhkiG9w0BAQwFADBL +MQswCQYDVQQGEwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UE +AwwYT0lTVEUgU2VydmVyIFJvb3QgUlNBIEcxMB4XDTIzMDUzMTE0MzcxNloXDTQ4 +MDUyNDE0MzcxNVowSzELMAkGA1UEBhMCQ0gxGTAXBgNVBAoMEE9JU1RFIEZvdW5k +YXRpb24xITAfBgNVBAMMGE9JU1RFIFNlcnZlciBSb290IFJTQSBHMTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAKqu9KuCz/vlNwvn1ZatkOhLKdxVYOPM +vLO8LZK55KN68YG0nnJyQ98/qwsmtO57Gmn7KNByXEptaZnwYx4M0rH/1ow00O7b +rEi56rAUjtgHqSSY3ekJvqgiG1k50SeH3BzN+Puz6+mTeO0Pzjd8JnduodgsIUzk +ik/HEzxux9UTl7Ko2yRpg1bTacuCErudG/L4NPKYKyqOBGf244ehHa1uzjZ0Dl4z +O8vbUZeUapU8zhhabkvG/AePLhq5SvdkNCncpo1Q4Y2LS+VIG24ugBA/5J8bZT8R +tOpXaZ+0AOuFJJkk9SGdl6r7NH8CaxWQrbueWhl/pIzY+m0o/DjH40ytas7ZTpOS +jswMZ78LS5bOZmdTaMsXEY5Z96ycG7mOaES3GK/m5Q9l3JUJsJMStR8+lKXHiHUh +sd4JJCpM4rzsTGdHwimIuQq6+cF0zowYJmXa92/GjHtoXAvuY8BeS/FOzJ8vD+Ho +mnqT8eDI278n5mUpezbgMxVz8p1rhAhoKzYHKyfMeNhqhw5HdPSqoBNdZH702xSu ++zrkL8Fl47l6QGzwBrd7KJvX4V84c5Ss2XCTLdyEr0YconosP4EmQufU2MVshGYR +i3drVByjtdgQ8K4p92cIiBdcuJd5z+orKu5YM+Vt6SmqZQENghPsJQtdLEByFSnT +kCz3GkPVavBpAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU +8snBDw1jALvsRQ5KH7WxszbNDo0wHQYDVR0OBBYEFPLJwQ8NYwC77EUOSh+1sbM2 +zQ6NMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQwFAAOCAgEANGd5sjrG5T33 +I3K5Ce+SrScfoE4KsvXaFwyihdJ+klH9FWXXXGtkFu6KRcoMQzZENdl//nk6HOjG +5D1rd9QhEOP28yBOqb6J8xycqd+8MDoX0TJD0KqKchxRKEzdNsjkLWd9kYccnbz8 +qyiWXmFcuCIzGEgWUOrKL+mlSdx/PKQZvDatkuK59EvV6wit53j+F8Bdh3foZ3dP +AGav9LEDOr4SfEE15fSmG0eLy3n31r8Xbk5l8PjaV8GUgeV6Vg27Rn9vkf195hfk +gSe7BYhW3SCl95gtkRlpMV+bMPKZrXJAlszYd2abtNUOshD+FKrDgHGdPY3ofRRs +YWSGRqbXVMW215AWRqWFyp464+YTFrYVI8ypKVL9AMb2kI5Wj4kI3Zaq5tNqqYY1 +9tVFeEJKRvwDyF7YZvZFZSS0vod7VSCd9521Kvy5YhnLbDuv0204bKt7ph6N/Ome +/msVuduCmsuY33OhkKCgxeDoAaijFJzIwZqsFVAzje18KotzlUBDJvyBpCpfOZC3 +J8tRd/iWkx7P8nd9H0aTolkelUTFLXVksNb54Dxp6gS1HAviRkRNQzuXSXERvSS2 +wq1yVAb+axj5d9spLFKebXd7Yv0PTY6YMjAwcRLWJTXjn/hvnLXrahut6hDTlhZy +BiElxky8j3C7DOReIoMt0r7+hVu05L0= +-----END CERTIFICATE----- + +// OISTE WISeKey Global Root GA CA +-----BEGIN CERTIFICATE----- +MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB +ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly +aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl +ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w +NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G +A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD +VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX +SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR +VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2 +w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF +mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg +4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9 +4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw +EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx +SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2 +ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8 +vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa +hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi +Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ +/L7fCg0= +-----END CERTIFICATE----- + +// OISTE WISeKey Global Root GB CA +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt +MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg +Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i +YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x +CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG +b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3 +HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx +WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX +1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk +u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P +99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r +M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB +BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh +cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5 +gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO +ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf +aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- + +// OISTE WISeKey Global Root GC CA +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw +CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91 +bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg +Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ +BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu +ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS +b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni +eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W +p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T +rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV +57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg +Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE----- + +// Security Communication ECC RootCA1 +-----BEGIN CERTIFICATE----- +MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYT +AkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYD +VQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYx +NjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTELMAkGA1UEBhMCSlAxJTAjBgNVBAoT +HFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNVBAMTIlNlY3VyaXR5 +IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+Cnnfdl +dB9sELLo5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpK +ULGjQjBAMB0GA1UdDgQWBBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu +9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3LsnNdo4gIxwwCMQDAqy0O +be0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70eN9k= +-----END CERTIFICATE----- + +// Security Communication RootCA2 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl +MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe +U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX +DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy +dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj +YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV +OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr +zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM +VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ +hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO +ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw +awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs +OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF +coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc +okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8 +t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy +1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/ +SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +// AAA Certificate Services +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb +MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow +GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj +YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM +GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua +BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe +3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 +YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR +rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm +ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU +oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v +QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t +b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF +AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q +GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 +G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi +l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 +smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- + +// COMODO Certification Authority +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB +gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV +BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw +MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl +YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P +RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3 +UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI +2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8 +Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp ++2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+ +DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O +nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW +/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g +PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u +QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY +SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv +IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4 +zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd +BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB +ZQ== +-----END CERTIFICATE----- + +// COMODO ECC Certification Authority +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT +IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw +MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy +ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N +T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR +FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J +cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW +BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm +fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv +GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +// COMODO RSA Certification Authority +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB +hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV +BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT +EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR +6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X +pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC +9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV +/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf +Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z ++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w +qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah +SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC +u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf +Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq +crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl +wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM +4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV +2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna +FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ +CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK +boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke +jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL +S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb +QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl +0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB +NVOFBkpdn627G190 +-----END CERTIFICATE----- + +// Entrust Root Certification Authority +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0 +Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW +KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw +NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw +NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy +ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV +BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo +Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4 +4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9 +KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI +rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi +94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB +sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi +gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo +kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE +vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t +O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua +AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP +9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/ +eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m +0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- + +// Entrust Root Certification Authority - EC1 +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG +A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3 +d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu +dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq +RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy +MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD +VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 +L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g +Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi +A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt +ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH +Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC +R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX +hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +// Entrust Root Certification Authority - G2 +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50 +cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs +IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz +dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy +NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu +dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt +dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0 +aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T +RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN +cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW +wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1 +U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0 +jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN +BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/ +jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v +1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R +nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH +VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g== +-----END CERTIFICATE----- + +// Entrust Root Certification Authority - G4 +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAw +gb4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQL +Ex9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykg +MjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAw +BgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0 +MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYTAlVT +MRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1 +c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJ +bmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3Qg +Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3DumSXbcr3DbVZwbPLqGgZ +2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV3imz/f3E +T+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j +5pds8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAM +C1rlLAHGVK/XqsEQe9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73T +DtTUXm6Hnmo9RR3RXRv06QqsYJn7ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNX +wbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5XxNMhIWNlUpEbsZmOeX7m640A +2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV7rtNOzK+mndm +nqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8 +dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwl +N4y6mACXi0mWHv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNj +c0kCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9nMA0GCSqGSIb3DQEBCwUAA4ICAQAS +5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4QjbRaZIxowLByQzTS +Gwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht7LGr +hFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/ +B7NTeLUKYvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uI +AeV8KEsD+UmDfLJ/fOPtjqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbw +H5Lk6rWS02FREAutp9lfx1/cH6NcjKF+m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+ +b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKWRGhXxNUzzxkvFMSUHHuk +2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjAJOgc47Ol +IQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk +5F6G+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuY +n/PIjhs4ViFqUZPTkcpG2om3PVODLAgfi49T3f+sHw== +-----END CERTIFICATE----- + +// Entrust.net Certification Authority (2048) +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML +RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp +bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 +IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3 +MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 +LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp +YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG +A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq +K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe +sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX +MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT +XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/ +HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH +4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub +j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo +U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b +u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+ +bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er +fF6adulZkMV8gzURZVE= +-----END CERTIFICATE----- + +// Sectigo Public Email Protection Root E46 +-----BEGIN CERTIFICATE----- +MIICMTCCAbegAwIBAgIQbvXTp0GOoFlApzBr0kBlVjAKBggqhkjOPQQDAzBaMQsw +CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTEwLwYDVQQDEyhT +ZWN0aWdvIFB1YmxpYyBFbWFpbCBQcm90ZWN0aW9uIFJvb3QgRTQ2MB4XDTIxMDMy +MjAwMDAwMFoXDTQ2MDMyMTIzNTk1OVowWjELMAkGA1UEBhMCR0IxGDAWBgNVBAoT +D1NlY3RpZ28gTGltaXRlZDExMC8GA1UEAxMoU2VjdGlnbyBQdWJsaWMgRW1haWwg +UHJvdGVjdGlvbiBSb290IEU0NjB2MBAGByqGSM49AgEGBSuBBAAiA2IABLinUpT1 +PgWwG/YfsdN+ueQFZlSAzmylaH3kU1LbgvrEht9DePfIrRa8P3gyy2vTSdZE5bN+ +n3umxizy4rbTibCaPEvOiUvGxss6SWAPRrxtTnqcyZuFewq2sEfCiOPU0aNCMEAw +HQYDVR0OBBYEFC1OjKfCI7JXqQZrPmsrifPDXkfOMA4GA1UdDwEB/wQEAwIBhjAP +BgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQCSnRpZY0VYjhsW5H16 +bDZIMB8rcueQMzT9JKLGBoxvOzJXWvj+xkkSU5rZELKZUXICMAUlKjMh/JPmIqLM +cFUoNVaiB8QhhCMaTEyZUJmSFMtK3Fb79dOPaiz1cTr4izsDng== +-----END CERTIFICATE----- + +// Sectigo Public Email Protection Root R46 +-----BEGIN CERTIFICATE----- +MIIFgDCCA2igAwIBAgIQHUSeuQ2DkXSu3fLriLemozANBgkqhkiG9w0BAQwFADBa +MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTEwLwYDVQQD +EyhTZWN0aWdvIFB1YmxpYyBFbWFpbCBQcm90ZWN0aW9uIFJvb3QgUjQ2MB4XDTIx +MDMyMjAwMDAwMFoXDTQ2MDMyMTIzNTk1OVowWjELMAkGA1UEBhMCR0IxGDAWBgNV +BAoTD1NlY3RpZ28gTGltaXRlZDExMC8GA1UEAxMoU2VjdGlnbyBQdWJsaWMgRW1h +aWwgUHJvdGVjdGlvbiBSb290IFI0NjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC +AgoCggIBAJHlG/qqbTcrdccuXxSl2yyXtixGj2nZ7JYt8x1avtMdI+ZoCf9KEXMa +rmefdprS5+y42V8r+SZWUa92nan8F+8yCtAjPLosT0eD7J0FaEJeBuDV6CtoSJey ++vOkcTV9NJsXi39NDdvcTwVMlGK/NfovyKccZtlxX+XmWlXKq/S4dxlFUEVOSqvb +nmbBGbc3QshWpUAS+TPoOEU6xoSjAo4vJLDDQYUHSZzP3NHyJm/tMxwzZypFN9mF +ZSIasbUQUglrA8YfcD2RxH2QPe1m+JD/JeDtkqKLMSmtnBJmeGOdV+z7C96O3IvL +Oql39Lrl7DiMi+YTZqdpWMOCGhrN8Z/YU5JOSX2pRefxQyFatz5AzWOJz9m/x1AL +4bzniJatntQX2l3P4JH9phDUuQOBm2ms+4SogTXrG+tobHxgPsPfybSudB1Ird1u +EYbhKmo2Fq7IzrzbWPxAk0DYjlOXwqwiOOWIMbMuoe/s4EIN6v+TVkoGpJtMAmhk +j1ZQwYEF/cvbxdcV8mu1dsOj+TLOyrVKqRt9Gdx/x2p+ley2uI39lUqcoytti/Fw +5UcrAFzkuZ7U+NlYKdDL4ChibK6cYuLMvDaTQfXv/kZilbBXSnQsR1Ipnd2ioU9C +wpLOLVBSXowKoffYncX4/TaHTlf9aKFfmYMc8LXd6JLTZUBVypaFAgMBAAGjQjBA +MB0GA1UdDgQWBBSn15V360rDJ82TvjdMJoQhFH1dmDAOBgNVHQ8BAf8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQwFAAOCAgEANNLxFfOTAdRyi/Cr +CB8TPHO0sKvoeNlsupqvJuwQgOUNUzHd4/qMUSIkMze4GH46+ljoNOWM4KEfCUHS +Nz/Mywk1Qojp/BHXz0KqpHC2ccFTvcV0r8QiJGPPYoJ9yctRwYiQbVtcvvuZqLq2 +hrDpZgvlG2uv6iuGp9+oI0yWP09XQhgVg0Pxhia3KgPOC53opWgejG+9heMbUY/n +Fy8r0NZ4wi3dcojUZZ76mdR+55cKkgGapamEOgwqdD0zGMiH9+ik9YZCOf1rdSn8 +AAasoqUaVI7pUEkXZq9LBC2blIClVKuMVxdEnw/WaGRytEseAcfZm5TZg5mvEgUR +o5gi0vJXyiT5ujgVEki6Yzv8i5V41nIHVszN/J0c0MVkO2M0zwSZircweXq28sbV +2VR6hwt+TveE7BTziBYS8dWuChoJ7oat5av9rsMpeXTDAV8Rm991mcZK95uPbEns +IS+0AlmzLdBykLoLFHR4S8/BX1VyjlQrE876WAzTuyzZqZFh+PjxtnvevKnMkgTM +S2tfc4C2Ie1QT9d2h27O39K3vWKhfVhiaEVStj/eEtvtBGmedoiqAW3ahsdgG8NS +rDfsUHGAciohRQpTRzwZ643SWQTeJbDrHzVvYH3Xtca7CyeN4E1U5c8dJgFuOzXI +IBKJg/DS7Vg7NJ27MfUy/THzVho= +-----END CERTIFICATE----- + +// Sectigo Public Server Authentication Root E46 +-----BEGIN CERTIFICATE----- +MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQsw +CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T +ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcN +MjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYG +A1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT +ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccC +WvkEN/U0NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+ +6xnOQ6OjQjBAMB0GA1UdDgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8B +Af8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNnADBkAjAn7qRa +qCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RHlAFWovgzJQxC36oCMB3q +4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21USAGKcw== +-----END CERTIFICATE----- + +// Sectigo Public Server Authentication Root R46 +-----BEGIN CERTIFICATE----- +MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBf +MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD +Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw +HhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEY +MBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1Ymxp +YyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDa +ef0rty2k1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnz +SDBh+oF8HqcIStw+KxwfGExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xf +iOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMPFF1bFOdLvt30yNoDN9HWOaEhUTCDsG3X +ME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vuZDCQOc2TZYEhMbUjUDM3 +IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5QazYw6A3OAS +VYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgE +SJ/AwSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu ++Zd4KKTIRJLpfSYFplhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt +8uaZFURww3y8nDnAtOFr94MlI1fZEoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+L +HaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW6aWWrL3DkJiy4Pmi1KZHQ3xt +zwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWIIUkwDgYDVR0P +AQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c +mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQ +YKlJfp/imTYpE0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52 +gDY9hAaLMyZlbcp+nv4fjFg4exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZA +Fv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M0ejf5lG5Nkc/kLnHvALcWxxPDkjB +JYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI84HxZmduTILA7rpX +DhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9mpFui +TdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5 +dHn5HrwdVw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65 +LvKRRFHQV80MNNVIIb/bE/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp +0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmmJ1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAY +QqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL +-----END CERTIFICATE----- + +// USERTrust ECC Certification Authority +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl +eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT +JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg +VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo +I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng +o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G +A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB +zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW +RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +// USERTrust RSA Certification Authority +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +// SSL.com Client ECC Root CA 2022 +-----BEGIN CERTIFICATE----- +MIICQDCCAcagAwIBAgIQdvhIHq7wPHAf4D8lVAGD1TAKBggqhkjOPQQDAzBRMQsw +CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSgwJgYDVQQDDB9T +U0wuY29tIENsaWVudCBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzAzMloX +DTQ2MDgxOTE2MzAzMVowUTELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jw +b3JhdGlvbjEoMCYGA1UEAwwfU1NMLmNvbSBDbGllbnQgRUNDIFJvb3QgQ0EgMjAy +MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABC1Tfp+LPrM2ulDizOvcuiaK04wGP2cP +7/UX5dSumkYqQQEHaedncfHCAzbG8CtSjs8UkmikPnBREmmNeKKCyikUwOSUIrJE +kmBvyASkZ9Wi0PPQ1+qOPA+60kBHkDTufaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAf +BgNVHSMEGDAWgBS3/i1ixYFTzVIaL11goMNd+7IcHDAdBgNVHQ4EFgQUt/4tYsWB +U81SGi9dYKDDXfuyHBwwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUC +ME0HES0R+7kmwyHdcuEX/MHPFOpJznGHjtZT3BHNXVSKr9kt9IxR6rxmR+J/lYNg +ZQIxAIwhTE+75bBQ35BiSebMkdv4P11xkQiOT5LJf6Zc6hN+7W3E6MMqb1wR4aXz +alqaTQ== +-----END CERTIFICATE----- + +// SSL.com Client RSA Root CA 2022 +-----BEGIN CERTIFICATE----- +MIIFjzCCA3egAwIBAgIQdq/uiJMVRbZQU5uAnKTfmjANBgkqhkiG9w0BAQsFADBR +MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSgwJgYDVQQD +DB9TU0wuY29tIENsaWVudCBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzEw +N1oXDTQ2MDgxOTE2MzEwNlowUTELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBD +b3Jwb3JhdGlvbjEoMCYGA1UEAwwfU1NMLmNvbSBDbGllbnQgUlNBIFJvb3QgQ0Eg +MjAyMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALhY20Yw+8k/48jw +ATM04tpIqBjpIG6a1wHh1SmPMLQjauTLYrC+4p8gvT5UoDlox4Y3ZnQGBu90K9rc +n4SpUi+Q0u5+fPulIq1vcEZnlj0p1KO7VnsUBFnBIWNEHrIfElyQh2UNiPYeiCLi +Y1S78zb41n/c2v8pNanGbg5pWz/YvoKHFXBdsMdcEg9jpjjNz3O5ww6JJjcbP2Ic +MmnRm9n/VZAx3rFj3c/FdHf874ghU78AMRomLAAwpV9s4+T2AIrKmIecdAN6i2bs +fv2jjzUlXHils6T7PW2pivBsiIKL/UrQb+TXo7SONEk4vs5F5dIcyl7CNxSLzWZW +Mzed5WvsQ5JkoELadW/AFez5ab00uYp7+hb7Vf5SIOgEBFZWZfU3RJjIikbpt6y4 +6L5ijlQ2W/c7cL9d7i26X95CGYbwf4vrCMvYvuoOQkKgNnNXF+0y6tCN6Acbm5no +xJpiBA5I9zwSuvdYwZqM6cewIzZWNB3LbNq6B4Qd/dGsn+bCie/DuWwYs2mHV1+1 +DDhbpyEkKjunNJGetFTqKE/TwaOL5OYr1fKdv5thACLd1ktEHz9dVv7enHjMmVuq +5L2620NLrUwmTKNNNIpsdDYT22L8m7IFgf+uPwzN9hui9DnnyvVMXPtUdzWAWsAS +oRMBM2c9nYGhqfWFJFiIeOf042hVAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8w +HwYDVR0jBBgwFoAU8DhClDSpPAB/Uu45pfdLDbxqfSMwHQYDVR0OBBYEFPA4QpQ0 +qTwAf1LuOaX3Sw28an0jMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AgEAmU/b8OrWEfoq/cirbeQOc2LSQp8V/nxwUj9kh4IxP0VALuEinwZmKfyW0y2N +tjjH2fMnwVkpoIz2cyQPKCLXTmHdE93bnzJSk/tPzOo4PJhqA6sWryHRQq59RSvq +xM+KWZ+CcHY6+GImyRCXWEAkpC25LymAJ+GJa3LKSQhxN1MF8YDO00IC0vzC0ZQG +7gfi9oPif5/nu1bDW7/dlZMJHiTBzybNraSuwrRp56q17TeU6d3RY4VrmnpKVnbc +GYUo1OTGpNi4lkF30LRZ8UYFh4cCH2m5ghjQQ9km2hpnqNZ1durybQ5C/4gmom6E +/n5iG/DGPe3AHGrHkda4ADdJm7mEBaHNbjHWROpTi7pTmB2hkIrphfgb8pNYw8jc +miZPPiDPT0PzEIx/EGF6NsqqC33Mn0dEWa6llcaZU+MHaz1JELAY/10OhUMUS+dr +00q1smBh3GlJAiNd6JJxw5yfRWd5HtwyhrqqVTxkbzK1EEAV3nJAeOBucLtu6wno +OdmsupJ13UPKugGVrRqBKzrw48UvDBhNEMauwO3+BVJ/GQXLqa81CAw4IuT+VuVT +Pr/k1rPZCMM91TMygSTFqeFlEbgyMzBxGEkdGkXGmhSKWDkobvPLUblJJmR4A8eR +EYOpuZA0tm+qBZ6FKFeZvn8nBkliTaH8CeErRglMFJtWj0U= +-----END CERTIFICATE----- + +// SSL.com EV Root Certification Authority ECC +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx +NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv +bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA +VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku +WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP +MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX +5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ +ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg +h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- + +// SSL.com EV Root Certification Authority RSA R2 +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV +BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE +CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy +MDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G +A1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD +DC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq +M0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf +OePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa +4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9 +HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR +aZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA +b9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ +Gp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV +PWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO +pgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu +UDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY +MBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4 +9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW +s47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5 +Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg +cLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM +79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz +/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt +ll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm +Kf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK +QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ +w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi +S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07 +mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- + +// SSL.com Root Certification Authority ECC +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz +WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0 +b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS +b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI +7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg +CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD +VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T +kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+ +gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- + +// SSL.com Root Certification Authority RSA +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE +BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK +DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz +OTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R +xFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX +qhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC +C52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3 +6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh +/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF +YD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E +JNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc +US4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8 +ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm ++Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi +M+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G +A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV +cpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc +Hadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs +PgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/ +q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0 +cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr +a6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I +H37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y +K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu +nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf +oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY +Ic2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- + +// SSL.com TLS ECC Root CA 2022 +-----BEGIN CERTIFICATE----- +MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQsw +CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxT +U0wuY29tIFRMUyBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2 +MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3Jh +dGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3QgQ0EgMjAyMjB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWyJGYm +acCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFN +SeR7T5v15wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME +GDAWgBSJjy+j6CugFFR781a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NW +uCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp +15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w7deedWo1dlJF4AIxAMeN +b0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5Zn6g6g== +-----END CERTIFICATE----- + +// SSL.com TLS RSA Root CA 2022 +-----BEGIN CERTIFICATE----- +MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBO +MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQD +DBxTU0wuY29tIFRMUyBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloX +DTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jw +b3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJvb3QgQ0EgMjAyMjCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u9nTP +L3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OY +t6/wNr/y7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0ins +S657Lb85/bRi3pZ7QcacoOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3 +PnxEX4MN8/HdIGkWCVDi1FW24IBydm5MR7d1VVm0U3TZlMZBrViKMWYPHqIbKUBO +L9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDGD6C1vBdOSHtRwvzpXGk3 +R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEWTO6Af77w +dr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS ++YCk8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYS +d66UNHsef8JmAOSqg+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoG +AtUjHBPW6dvbxrB6y3snm/vg1UYk7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2f +gTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j +BBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsuN+7jhHonLs0Z +NbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt +hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsM +QtfhWsSWTVTNj8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvf +R4iyrT7gJ4eLSYwfqUdYe5byiB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJ +DPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjUo3KUQyxi4U5cMj29TH0ZR6LDSeeW +P4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqoENjwuSfr98t67wVy +lrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7EgkaibMOlq +bLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2w +AgDHbICivRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3q +r5nsLFR+jM4uElZI7xc7P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sji +Mho6/4UIyYOf8kpIEFR3N+2ivEC+5BB09+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU +98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA= +-----END CERTIFICATE----- + +// SwissSign Gold CA - G2 +-----BEGIN CERTIFICATE----- +MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV +BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln +biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF +MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT +d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8 +76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+ +bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c +6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE +emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd +MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt +MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y +MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y +FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi +aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM +gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB +qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7 +lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn +8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov +L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6 +45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO +UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5 +O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC +bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv +GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a +77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC +hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3 +92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp +Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w +ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt +Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ +-----END CERTIFICATE----- + +// SwissSign RSA SMIME Root CA 2022 - 1 +-----BEGIN CERTIFICATE----- +MIIFlzCCA3+gAwIBAgIURg7UAXGQoBqDLEpCECgV0mEbrTIwDQYJKoZIhvcNAQEL +BQAwUzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEtMCsGA1UE +AxMkU3dpc3NTaWduIFJTQSBTTUlNRSBSb290IENBIDIwMjIgLSAxMB4XDTIyMDYw +ODEwNTMxM1oXDTQ3MDYwODEwNTMxM1owUzELMAkGA1UEBhMCQ0gxFTATBgNVBAoT +DFN3aXNzU2lnbiBBRzEtMCsGA1UEAxMkU3dpc3NTaWduIFJTQSBTTUlNRSBSb290 +IENBIDIwMjIgLSAxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1Pv6 +P4aimXAJOsnWoU4Bzka1LSRIDUXprMka1zKApObTytbyKTfsmizWgc7mG52xD0Hf +WNNfqqB5WQuMrfnF+Rz7w+k1QHTDwQzLZ/ucXgwj+dAv+kyCRRy19R/4GW7ak7dO +aIN+Yi0djJUfcNnOWowhXai+CKlWbdn3uZCZxzvXvZ4uyWdXLiHO8DKD+wQB+beC +RA2yy3oJoUg+T8ALahsb7M8dnn8GkKwoBQuo5lQ7oqcsOROZqPs06/XwvQHYiBHI +rroZAkkC3IostL1hYOydeFxqiy8Xhl7yT5MAa13FsqmlGOrmbX5XBfsH/Lx8oUOx +ZhyoZ/urN/aqqrh6Qfc51YyfrnI2J+RixkOZ8aFB6f+Jnw9Jr8kUBhcnZDkNpbQq +W+w8+5/FX8Y7XSYZ8oQpuJVECVL9bDDQYo8opYGWK5QvJnXkCYwK3zjzfl04joKa +jNyers4SQjoi8jWNT9IayEkzC/o2P/8sa2ogcUzNrRA/aTKEjlzuU4hE4t3MAzCS +hnmQKkt1+1JixPRvTffbI6EY3UVTF5pjJEiJIs1+mwEcgCgDj1sr+h/jfBm95o+x +QHag8sc3sjKUEDLNpxOX8TssejQie3Q6QOKvgvjBwXj8X+Q1f8D0TPBMsuqHA3Il +WYMqCKRR3s/uqOfoQD+I8DarCU7YoKh/8+EJ27kCAwEAAaNjMGEwDwYDVR0TAQH/ +BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHwYDVR0jBBgwFoAUzC6tiYyD40CjJWml +6pJ90jc6x8YwHQYDVR0OBBYEFMwurYmMg+NAoyVppeqSfdI3OsfGMA0GCSqGSIb3 +DQEBCwUAA4ICAQAAB2YWpe3Hub+8yJGtWO1eEgWz9kabe+SEEC8HsVpeMm5tAPBe +x5piOYdN5Dzzvva6alNshG0H1GHKZ2a+mz5FMJ1R0tdaQq6dkg4jq9AVlD6omsqb +7cHCXyGjmYD8uaZhDlCAgCfH6H2g1JR6mAPn7kKL81JQXO++sHZaHAmhv4PAHnZl +0CVBW2mRk3f5jEvwLNubBgAXg/palLSGie+8CgsS+AZN0nPikThduWpLT6ev2iYl +kiMafB8nDZGE7xdy9kbrazs3qdTVmmO6XnmMKrWbojS1zJYn+XkIPH9t4P983MUm +r8OhemkW3Yc1c8ZrMWtWAS1PmdnuyuHQg962hecW+NGuM0j7Gs9dX4qEYXQHbxmw +USGyoQSxe1OP76JFrR+Y3flqBGyqNsWvjOopSUrn/1ezxjwRSRgX5maF4egj8osO +PJPEP3ZOfmKiKcsWMN4saa+Rp+JX5TNMv9iOB6J/oTVGaUqoICn/694glVmxrk0w +a9iatAMfwjjkINUO1howTGicjODtoQ+OQl3rgCoSeaYXF7SVKo40kae90ayoGkMh +i97v4KxGJWUKxiuhmz4i6Bg4tSb2LMoIIN4w0a1U/dxIFZ/Np0HXNziFME8SiEM0 +g9cqTdQAV1zlyvDd4ZIoKxh1vUekQhPpVlqNSl7ODnU1gHMZDywpi7uVuA== +-----END CERTIFICATE----- + +// SwissSign RSA TLS Root CA 2022 - 1 +-----BEGIN CERTIFICATE----- +MIIFkzCCA3ugAwIBAgIUQ/oMX04bgBhE79G0TzUfRPSA7cswDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzErMCkGA1UE +AxMiU3dpc3NTaWduIFJTQSBUTFMgUm9vdCBDQSAyMDIyIC0gMTAeFw0yMjA2MDgx +MTA4MjJaFw00NzA2MDgxMTA4MjJaMFExCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxT +d2lzc1NpZ24gQUcxKzApBgNVBAMTIlN3aXNzU2lnbiBSU0EgVExTIFJvb3QgQ0Eg +MjAyMiAtIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDLKmjiC8NX +vDVjvHClO/OMPE5Xlm7DTjak9gLKHqquuN6orx122ro10JFwB9+zBvKK8i5VUXu7 +LCTLf5ImgKO0lPaCoaTo+nUdWfMHamFk4saMla+ju45vVs9xzF6BYQ1t8qsCLqSX +5XH8irCRIFucdFJtrhUnWXjyCcplDn/L9Ovn3KlMd/YrFgSVrpxxpT8q2kFC5zyE +EPThPYxr4iuRR1VPuFa+Rd4iUU1OKNlfGUEGjw5NBuBwQCMBauTLE5tzrE0USJIt +/m2n+IdreXXhvhCxqohAWVTXz8TQm0SzOGlkjIHRI36qOTw7D59Ke4LKa2/KIj4x +0LDQKhySio/YGZxH5D4MucLNvkEM+KRHBdvBFzA4OmnczcNpI/2aDwLOEGrOyvi5 +KaM2iYauC8BPY7kGWUleDsFpswrzd34unYyzJ5jSmY0lpx+Gs6ZUcDj8fV3oT4MM +0ZPlEuRU2j7yrTrePjxF8CgPBrnh25d7mUWe3f6VWQQvdT/TromZhqwUtKiE+shd +OxtYk8EXlFXIC+OCeYSf8wCENO7cMdWP8vpPlkwGqnj73mSiI80fPsWMvDdUDrta +clXvyFu1cvh43zcgTFeRc5JzrBh3Q4IgaezprClG5QtO+DdziZaKHG29777YtvTK +wP1H8K4LWCDFyB02rpeNUIMmJCn3nTsPBQIDAQABo2MwYTAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBRvjmKLk0Ow4UD2p8P98Q+4 +DxU4pTAdBgNVHQ4EFgQUb45ii5NDsOFA9qfD/fEPuA8VOKUwDQYJKoZIhvcNAQEL +BQADggIBAKwsKUF9+lz1GpUYvyypiqkkVHX1uECry6gkUSsYP2OprphWKwVDIqO3 +10aewCoSPY6WlkDfDDOLazeROpW7OSltwAJsipQLBwJNGD77+3v1dj2b9l4wBlgz +Hqp41eZUBDqyggmNzhYzWUUo8aWjlw5DI/0LIICQ/+Mmz7hkkeUFjxOgdg3XNwwQ +iJb0Pr6VvfHDffCjw3lHC1ySFWPtUnWK50Zpy1FVCypM9fJkT6lc/2cyjlUtMoIc +gC9qkfjLvH4YoiaoLqNTKIftV+Vlek4ASltOU8liNr3CjlvrzG4ngRhZi0Rjn9UM +ZfQpZX+RLOV/fuiJz48gy20HQhFRJjKKLjpHE7iNvUcNCfAWpO2Whi4Z2L6MOuhF +LhG6rlrnub+xzI/goP+4s9GFe3lmozm1O2bYQL7Pt2eLSMkZJVX8vY3PXtpOpvJp +zv1/THfQwUY1mFwjmwJFQ5Ra3bxHrSL+ul4vkSkphnsh3m5kt8sNjzdbowhq6/Td +Ao9QAwKxuDdollDruF/UKIqlIgyKhPBZLtU30WHlQnNYKoH3dtvi4k0NX/a3vgW0 +rk4N3hY9A4GzJl5LuEsAz/+MF7psYC0nhzck5npgL7XTgwSqT0N1osGDsieYK7EO +gLrAhV5Cud+xYJHT6xh+cHiudoO+cVrQkOPKwRYlZ0rwtnu64ZzZ +-----END CERTIFICATE----- + +// TWCA CYBER Root CA +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQ +MQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290 +IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5 +WhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FO +LUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1sTs6P +40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxF +avcokPFhV8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/ +34bKS1PE2Y2yHer43CdTo0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684i +JkXXYJndzk834H/nY62wuFm40AZoNWDTNq5xQwTxaWV4fPMf88oon1oglWa0zbfu +j3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK/c/WMw+f+5eesRycnupf +Xtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkHIuNZW0CP +2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDA +S9TMfAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDA +oS/xUgXJP+92ZuJF2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzC +kHDXShi8fgGwsOsVHkQGzaRP6AzRwyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW +5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83QOGt4A1WNzAd +BgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB +AGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0t +tGlTITVX1olNc79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn +68xDiBaiA9a5F/gZbG0jAn/xX9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNn +TKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDRIG4kqIQnoVesqlVYL9zZyvpoBJ7t +RCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq/p1hvIbZv97Tujqx +f36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0RFxbI +Qh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz +8ppy6rBePm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4 +NxKfKjLji7gh7MMrZQzvIt6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzX +xeSDwWrruoBa3lwtcHb4yOWHh8qgnaHlIhInD0Q9HWzq1MKLL295q39QpsQZp6F6 +t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X +-----END CERTIFICATE----- + +// TWCA Global Root CA +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx +EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT +VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5 +NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT +B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF +10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz +0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh +MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH +zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc +46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2 +yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi +laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP +oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA +BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE +qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm +4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL +1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF +H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo +RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+ +nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh +15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW +6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW +nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j +wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz +aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy +KwbQBM0= +-----END CERTIFICATE----- + +// TWCA Global Root CA G2 +-----BEGIN CERTIFICATE----- +MIIFlTCCA32gAwIBAgIQQAE0jMIAAAAAAAAAAZdY9DANBgkqhkiG9w0BAQwFADBU +MQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290 +IENBMR8wHQYDVQQDExZUV0NBIEdsb2JhbCBSb290IENBIEcyMB4XDTIyMTEyMjA2 +NDIyMVoXDTQ3MTEyMjE1NTk1OVowVDELMAkGA1UEBhMCVFcxEjAQBgNVBAoTCVRB +SVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEfMB0GA1UEAxMWVFdDQSBHbG9iYWwg +Um9vdCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKoO1SCS +Aa2C+QwIkTRrihbQRhb/A7jYjeqTNPv/K739bqrcm/KGgVX1iRzEjXVqWHiREx4C +E3A9774K5wCPuDHldMUwvv991pnlwkKjzyHWswh/kdVh5qKVEA3vXpcLSTjVIrDX +i1lvnzWbf9KRzHp/u6Cf3lUz9kuNCup9CcB53L1E4v4c52QhKM8ESuK0v4Z5KrsO +k8mPXqwwOVKQB7nqnCZCFMRnRv7RGmihPlAZoyYKJymQwva063OaeB7hmPRlDDUh +BvgL3mLlTcGzXdm5+mGXKuPqx0RVJJL+Eqc/xHfgLQKBB9X7feYQnjq0qO/s+1Dq +Nc/MfrtCuURsUum/KnIfP96bcOncWsU7u7/wWYWvL8GwFHkFrHWfJfURJwZgIcdt +Zb6oiZzlrEbf+F1EA41gvfexDcwv70FUL+5rlblOfDTfO/l3nX3NBz0cBjMSgOxy +nPItgtrVO8TH+QTDZAJ89TVgp7RGKS4b76VYgC56iVE4Njz9oXe4gDDQit6NpzQm +7CO7GFUYNkXu7QEGqk2/ZAzKmJcaMQJm+HhoW4jfCajnm/o0bXAcIa0Ii/Khtqx2 +ar/xgCUAvjweTa65PLaVY71rfkcSkFVFEY3sFx/BvieBk1djaQAmd4vDWeV70Q1E +8qjw94WaBffCLnCak4XYlZAxkFSm7AufN0UPAgMBAAGjYzBhMA4GA1UdDwEB/wQE +AwIBBjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFJKM1DbRW0dTxHENhN1k +KvU2ZEDnMB0GA1UdDgQWBBSSjNQ20VtHU8RxDYTdZCr1NmRA5zANBgkqhkiG9w0B +AQwFAAOCAgEAJfxL2pC02nXnQTqB0ab+oGrzGHFiaiQIi6l6TclVzs8QKC4EGZYF +z10CICo7s1U/Ac1CzbJ37f9183x325alz4xnBvSkm3L2IUkJmKMyXndaYwnvYkOX +Aji16jwYUGj8WVvZedTx5FZIE1bY03ELXniUOBFF+gUX9Q51HmJSYUa6LhmthrSI +D7FQ5kAANBqVnZPgUfnUVUbplTwlhi6X1wExGETsHGDpfWmvMviXQCUkto0aVTzF +t/e8BlI7cTBwPnEXfvFmBF5dvIoxQ6aSHXtU0qU2i2+N1l7a1MMuHd85VWCCMJ4n +/46A3WNMplU12NAzqYBtPl6dzKhngGb6mVcMUsoZdbA4NVUqgcWMHlbXX5DyINja +4GZx6bJ4q2e5JG5rNnL8b439f3I5KGdSkQUfV2XSo6cNYfqh59U1RpXJBof2MOwy +UamsVsAhTqMUdAU6vOO/bT1OP16lpG0pv4RRdVOOhhr1UXAqDRxOQOH9o+OlK2eQ +ksdsroW/OpsXFcqcKpPUTTkNvCAIo42IbAkNjK5EIU3JcezYJtcXni0RGDyjIn24 +J1S/aMg7QsyPXk7n3MLF+mpED41WiHrfiYRsoLM+PfFlAAmI6irrQM6zXawyF67B +m+nQwfVJlN2nznxaB+uuIJwXMJJpk3Lzmltxm/5q33owaY6zLtsPLN0= +-----END CERTIFICATE----- + +// TWCA Root Certification Authority +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES +MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU +V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz +WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO +LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE +AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH +K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX +RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z +rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx +3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq +hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC +MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls +XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D +lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn +aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ +YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +// Telia Root CA v2 +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQx +CzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UE +AwwQVGVsaWEgUm9vdCBDQSB2MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1 +NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZ +MBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ76zBq +AMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9 +vVYiQJ3q9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9 +lRdU2HhE8Qx3FZLgmEKnpNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTOD +n3WhUidhOPFZPY5Q4L15POdslv5e2QJltI5c0BE0312/UqeBAMN/mUWZFdUXyApT +7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW5olWK8jjfN7j/4nlNW4o +6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNrRBH0pUPC +TEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6 +WT0EBXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63R +DolUK5X6wK0dmBR4M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZI +pEYslOqodmJHixBTB0hXbOKSTbauBcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGj +YzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7Wxy+G2CQ5MB0GA1UdDgQWBBRy +rOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ +8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi +0f6X+J8wfBj5tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMM +A8iZGok1GTzTyVR8qPAs5m4HeW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBS +SRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+Cy748fdHif64W1lZYudogsYMVoe+K +TTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygCQMez2P2ccGrGKMOF +6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15h2Er +3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMt +Ty3EHD70sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pT +VmBds9hCG1xLEooc6+t9xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAW +ysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQraVplI/owd8k+BsHMYeB2F326CjYSlKA +rBPuUBQemMc= +-----END CERTIFICATE----- + +// TrustAsia Global Root CA G3 +-----BEGIN CERTIFICATE----- +MIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEM +BQAwWjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dp +ZXMsIEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAe +Fw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEwMTlaMFoxCzAJBgNVBAYTAkNOMSUw +IwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtU +cnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNS +T1QY4SxzlZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqK +AtCWHwDNBSHvBm3dIZwZQ0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1 +nyDvP+uLRx+PjsXUjrYsyUQE49RDdT/VP68czH5GX6zfZBCK70bwkPAPLfSIC7Ep +qq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1AgdB4SQXMeJNnKziyhWTXA +yB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm9WAPzJMs +hH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gX +zhqcD0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAv +kV34PmVACxmZySYgWmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msT +f9FkPz2ccEblooV7WIQn3MSAPmeamseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jA +uPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCFTIcQcf+eQxuulXUtgQIDAQAB +o2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj7zjKsK5Xf/Ih +MBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E +BAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4 +wM8zAQLpw6o1D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2 +XFNFV1pF1AWZLy4jVe5jaN/TG3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1 +JKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNjduMNhXJEIlU/HHzp/LgV6FL6qj6j +ITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstlcHboCoWASzY9M/eV +VHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys+TIx +xHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1on +AX1daBli2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d +7XB4tmBZrOFdRWOPyN9yaFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2Ntjj +gKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsASZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV ++Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFRJQJ6+N1rZdVtTTDIZbpo +FGWsJwt0ivKH +-----END CERTIFICATE----- + +// TrustAsia Global Root CA G4 +-----BEGIN CERTIFICATE----- +MIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMw +WjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs +IEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0y +MTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJaMFoxCzAJBgNVBAYTAkNOMSUwIwYD +VQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtUcnVz +dEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATx +s8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbw +LxYI+hW8m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJij +YzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mD +pm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/pDHel4NZg6ZvccveMA4GA1UdDwEB/wQE +AwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AAbbd+NvBNEU/zy4k6LHiR +UKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xkdUfFVZDj +/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA== +-----END CERTIFICATE----- + +// TrustAsia SMIME ECC Root CA +-----BEGIN CERTIFICATE----- +MIICNjCCAbugAwIBAgIUWsL4KU/jfcVeHRhvO5MgH/97ui0wCgYIKoZIzj0EAwMw +WjELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs +IEluYy4xJDAiBgNVBAMTG1RydXN0QXNpYSBTTUlNRSBFQ0MgUm9vdCBDQTAeFw0y +NDA1MTUwNTQxNTlaFw00NDA1MTUwNTQxNThaMFoxCzAJBgNVBAYTAkNOMSUwIwYD +VQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDExtUcnVz +dEFzaWEgU01JTUUgRUNDIFJvb3QgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATN +2fsnvWnshsmQQ7FwF5SnyXcjOj8jZdMcox0eQlQg69BCu1m5i6zyho1Ljh2qliIj +OXZtkpvrIst6Q6Jz/XNLwiUPKrFpxv9F36k8lYC7qR5Kky/sHB2I9BGSN583mHKj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFDFn5nKyDeYioKzPfiKnWTLj +ZiOlMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNpADBmAjEA3TpMjaTGf+29 +pcZPPv0xSyjWilbfZRZ3h037ujIIgeCeM0iLn5SG7wErlOaM1tSOAjEAn4GcsCb9 +K9by9XGEnqjHiozWWBFStbgEy8xxdWPixhk42W1sGXGkFhkhk7oGRChs +-----END CERTIFICATE----- + +// TrustAsia SMIME RSA Root CA +-----BEGIN CERTIFICATE----- +MIIFhDCCA2ygAwIBAgIUWu5x394MV4W1uzYi17h2RgJzyv8wDQYJKoZIhvcNAQEM +BQAwWjELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dp +ZXMsIEluYy4xJDAiBgNVBAMTG1RydXN0QXNpYSBTTUlNRSBSU0EgUm9vdCBDQTAe +Fw0yNDA1MTUwNTQyMDFaFw00NDA1MTUwNTQyMDBaMFoxCzAJBgNVBAYTAkNOMSUw +IwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDExtU +cnVzdEFzaWEgU01JTUUgUlNBIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCYlZytPFlz05N2pkhUphyIckxN4YL/GhMfUN6M2ZBC0byZ0zej +13E6yt1eG5BhQm6PQAFzfR8xutQdbgTSqpCESjMKRJ9aGR+0bi1o/K/An0oQEr8+ +gsKCsC/nkG+QZBCD7Ow2lAx8T+ACDT2HeUJNAOUwrnAfFf36z1IlNk15ILvxEJjg +YIfJ9XgMIu0C5hFs8ZtakRF0htD+eJKWBMOY78Zwr6mQqhb2Iu3Y+kYoceLJCMBQ +vHajui2W8hH5pL0QVvgnbStLZIjcF13PAAiKkq4azBLX3/AQKPPNOuo6Eowb52EJ +Q2rkOOn+dDnbzQo7w09T1q5x1TiDhx/O50zzEVWH37ev9+sahhBtqO1I3TLQ26oq +C3J3KXf9eug/eCAqaL7ebwjmtYVHgDf5cZaLpZhWl3wRZRaO1M7YJ9T5WsWnjbvR +Nw2lq2Vd2nSTiF7bdfZ/m8KasW0IAgyYSrvNMK92NQKFViNRCUAJBffwPR7CyHoa +usVBFbkNdrS0pLhF/Y2jOz0DKs2zlX80e92hT9k6/yf1DcIBnP9ZdVoayefS/X9P +D7X+DTzmoNb7tXZctDBNED/+4utaDrFPT1B+CDMCkVcySYmnQBBQF2ufY7qyslaY +dvT/cukEnNSnTE/2Oh9aVDFvy7oyrfhtr0XHe2NE38L9eOhKirB0dRbejwIDAQAB +o0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSAGqpDwcl/ixqWRbw9u2tI +UmxbqzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACp1gaGCIOp/ +Vq4JMJcQePTZQBRSpO5qf/AJKNYQY+BOe8kxxwilF+uvhuKXB0+pDqKFzO2kgIEd +WlMGPEwaqbeEhs989YUKcJnQ7TaRjed3Ls6EnCiGLSU1jEwB5n3bYV3id4TTAdFi +3QyiCmSk/PDtOkjyOew11qF6F3Hs09LsuCb7rRVwVkrPZMC5YFv35s2gwgMr+bLl +2rqlNxzYjdp5dCpn5KJ6xyyNpcFqgWzM9ak5aiJ9ouIIzemT27rLH3V3nveYrxTk +O6BMp3LntV5TScz/klfxWSsJuulSk8APRQth1mxZcwvY+QEv2gNPNxz034NeC0Gg +sXw5AKFs0Ni0kXIrGz+imtHE3yvVyJV9hM12G9zkJMY/FSI9hadCK+1+cVlhSMI9 +kWNAfCmzgBYKJfwYYA5TrQ4qzvxBOs2x5GprzDltyE1luKqTiHhuDwKL4JaOdB/Q +fuF0t/aBauQjrI79jzUdmnEKTypVL/4YwQD3e0iKZa9vCB1D51q4H6ToA+v9TLW0 +k6gx3kOdEr3n6aTS32/8b0aj7zFOjRerG6ng+Kk0VqEO53TsqIeF2Hc1S40+bnJ8 +SMwfcrNxdNQkhrzIwON5FAHO2fqBxlyz+V0MOL7O8o6NXz0l4VE5I6jqAI4Es79y +oMK6g/vNpJd1IJq/p1Di3a0sH/Q/o8gx +-----END CERTIFICATE----- + +// TrustAsia TLS ECC Root CA +-----BEGIN CERTIFICATE----- +MIICMTCCAbegAwIBAgIUNnThTXxlE8msg1UloD5Sfi9QaMcwCgYIKoZIzj0EAwMw +WDELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs +IEluYy4xIjAgBgNVBAMTGVRydXN0QXNpYSBUTFMgRUNDIFJvb3QgQ0EwHhcNMjQw +NTE1MDU0MTU2WhcNNDQwNTE1MDU0MTU1WjBYMQswCQYDVQQGEwJDTjElMCMGA1UE +ChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAGA1UEAxMZVHJ1c3RB +c2lhIFRMUyBFQ0MgUm9vdCBDQTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLh/pVs/ +AT598IhtrimY4ZtcU5nb9wj/1WrgjstEpvDBjL1P1M7UiFPoXlfXTr4sP/MSpwDp +guMqWzJ8S5sUKZ74LYO1644xST0mYekdcouJtgq7nDM1D9rs3qlKH8kzsaNCMEAw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQULIVTu7FDzTLqnqOH/qKYqKaT6RAw +DgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMFRH18MtYYZI9HlaVQ01 +L18N9mdsd0AaRuf4aFtOJx24mH1/k78ITcTaRTChD15KeAIxAKORh/IRM4PDwYqR +OkwrULG9IpRdNYlzg8WbGf60oenUoWa2AaU2+dhoYSi3dOGiMQ== +-----END CERTIFICATE----- + +// TrustAsia TLS RSA Root CA +-----BEGIN CERTIFICATE----- +MIIFgDCCA2igAwIBAgIUHBjYz+VTPyI1RlNUJDxsR9FcSpwwDQYJKoZIhvcNAQEM +BQAwWDELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dp +ZXMsIEluYy4xIjAgBgNVBAMTGVRydXN0QXNpYSBUTFMgUlNBIFJvb3QgQ0EwHhcN +MjQwNTE1MDU0MTU3WhcNNDQwNTE1MDU0MTU2WjBYMQswCQYDVQQGEwJDTjElMCMG +A1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAGA1UEAxMZVHJ1 +c3RBc2lhIFRMUyBSU0EgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC +AgoCggIBAMMWuBtqpERz5dZO9LnPWwvB0ZqB9WOwj0PBuwhaGnrhB3YmH49pVr7+ +NmDQDIPNlOrnxS1cLwUWAp4KqC/lYCZUlviYQB2srp10Zy9U+5RjmOMmSoPGlbYJ +Q1DNDX3eRA5gEk9bNb2/mThtfWza4mhzH/kxpRkQcwUqwzIZheo0qt1CHjCNP561 +HmHVb70AcnKtEj+qpklz8oYVlQwQX1Fkzv93uMltrOXVmPGZLmzjyUT5tUMnCE32 +ft5EebuyjBza00tsLtbDeLdM1aTk2tyKjg7/D8OmYCYozza/+lcK7Fs/6TAWe8Tb +xNRkoDD75f0dcZLdKY9BWN4ArTr9PXwaqLEX8E40eFgl1oUh63kd0Nyrz2I8sMeX +i9bQn9P+PN7F4/w6g3CEIR0JwqH8uyghZVNgepBtljhb//HXeltt08lwSUq6HTrQ +UNoyIBnkiz/r1RYmNzz7dZ6wB3C4FGB33PYPXFIKvF1tjVEK2sUYyJtt3LCDs3+j +TnhMmCWr8n4uIF6CFabW2I+s5c0yhsj55NqJ4js+k8UTav/H9xj8Z7XvGCxUq0DT +bE3txci3OE9kxJRMT6DNrqXGJyV1J23G2pyOsAWZ1SgRxSHUuPzHlqtKZFlhaxP8 +S8ySpg+kUb8OWJDZgoM5pl+z+m6Ss80zDoWo8SnTq1mt1tve1CuBAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLgHkXlcBvRG/XtZylomkadFK/hT +MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQwFAAOCAgEAIZtqBSBdGBanEqT3 +Rz/NyjuujsCCztxIJXgXbODgcMTWltnZ9r96nBO7U5WS/8+S4PPFJzVXqDuiGev4 +iqME3mmL5Dw8veWv0BIb5Ylrc5tvJQJLkIKvQMKtuppgJFqBTQUYo+IzeXoLH5Pt +7DlK9RME7I10nYEKqG/odv6LTytpEoYKNDbdgptvT+Bz3Ul/KD7JO6NXBNiT2Twp +2xIQaOHEibgGIOcberyxk2GaGUARtWqFVwHxtlotJnMnlvm5P1vQiJ3koP26TpUJ +g3933FEFlJ0gcXax7PqJtZwuhfG5WyRasQmr2soaB82G39tp27RIGAAtvKLEiUUj +pQ7hRGU+isFqMB3iYPg6qocJQrmBktwliJiJ8Xw18WLK7nn4GS/+X/jbh87qqA8M +pugLoDzga5SYnH+tBuYc6kIQX+ImFTw3OffXvO645e8D7r0i+yiGNFjEWn9hongP +XvPKnbwbPKfILfanIhHKA9jnZwqKDss1jjQ52MjqjZ9k4DewbNfFj8GQYSbbJIwe +SsCI3zWQzj8C9GRh3sfIB5XeMhg6j6JCQCTl1jNdfK7vsU1P1FeQNWrcrgSXSYk0 +ly4wBOeY99sLAZDBHwo/+ML+TvrbmnNzFrwFuHnYWa8G5z9nODmxfKuU4CkUpijy +323imttUQ/hHWKNddBWcwauwxzQ= +-----END CERTIFICATE----- +` +} diff --git a/common/certificate/mozilla.pem b/common/certificate/mozilla.pem new file mode 100644 index 0000000000..96f941b07e --- /dev/null +++ b/common/certificate/mozilla.pem @@ -0,0 +1,4256 @@ +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE +BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w +MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC +SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1 +ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv +UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX +4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9 +KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/ +gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb +rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ +51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F +be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe +KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F +v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn +fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7 +jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz +ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL +e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70 +jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz +WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V +SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j +pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX +X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok +fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R +K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU +ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU +LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT +LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL +BQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg +Q2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv +b3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQwNDI2MDg1NzU2WjBhMQswCQYDVQQG +EwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBDZXJ0aWZpY2F0aW9u +IEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZ +n56eY+hz2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd +2JQDoOw05TDENX37Jk0bbjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgF +VwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZ +GoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAdgjH8KcwAWJeRTIAAHDOF +li/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViWVSHbhlnU +r8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2 +eY8fTpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIb +MlEsPvLfe/ZdeikZjuXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISg +jwBUFfyRbVinljvrS5YnzWuioYasDXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB +7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwSVXAkPcvCFDVDXSdOvsC9qnyW +5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI04Y+oXNZtPdE +ITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0 +90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+z +xiD2BkewhpMl0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYu +QEkHDVneixCwSQXi/5E/S7fdAo74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4 +FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRYYdZ2vyJ/0Adqp2RT8JeNnYA/u8EH +22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJpadbGNjHh/PqAulxP +xOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65xxBzn +dFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5 +Xc0yGYuPjCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7b +nV2UqL1g52KAdoGDDIzMMEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQ +CvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9zZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZH +u/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3rAZ3r2OvEhJn7wAzMMujj +d9qDRIueVSjAi1jTkD5OGwDxFa2DK5o= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj +ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM +9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw +IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 +VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L +93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm +jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA +A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI +U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs +N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv +o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU +5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy +rqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK +gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ +W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg +1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K +8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r +2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me +z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR +8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj +mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz +7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6 ++XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI +0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm +UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2 +LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS +k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl +7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm +btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl +urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+ +fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63 +n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE +76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H +9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT +4PsJYGw= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl +ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr +ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr +BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM +YyRIHN8wfdVoOw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi +9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk +M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB +MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw +CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW +1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs +ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD +VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy +ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy +dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p +OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2 +8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K +Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe +hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk +6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q +AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI +bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB +ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z +qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn +0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN +sSi6 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBM +MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD +QTAeFw0wMjA2MTExMDQ2MzlaFw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBM +MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD +QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6xwS7TT3zNJc4YPk/E +jG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdLkKWo +ePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GI +ULdtlkIJ89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapu +Ob7kky/ZR6By6/qmW6/KUz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUg +AKpoC6EahQGcxEZjgoi2IrHu/qpGWX7PNSzVttpd90gzFFS269lvzs2I1qsb2pY7 +HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA +uI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+GXYkHAQa +TOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTg +xSvgGrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1q +CjqTE5s7FCMTY5w/0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5x +O/fIR/RpbxXyEV6DHpx8Uq79AtoSqFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs +6GAqm4VKQPNriiTsBhYscw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw +CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw +JQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT +EENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2MDcyNDU0WhcNNDMwMzI2MDcyNDU0 +WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBT +LkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAX +BgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATE +KI6rGFtqvm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7Tm +Fy8as10CW4kjPMIRBSqniBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68Kj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI0GZnQkdjrzife81r1HfS+8 +EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjADVS2m5hjEfO/J +UG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0QoSZ/6vn +nvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM +MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D +ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU +cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3 +WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg +Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw +IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH +UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM +TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU +BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM +kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x +AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV +HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y +sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL +I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 +J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY +VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB +gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu +QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG +A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz +OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ +VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3 +b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA +DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn +0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB +OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE +fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E +Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m +o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i +sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW +OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez +Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS +adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n +3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ +F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf +CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29 +XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm +djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/ +WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb +AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq +P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko +b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj +XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P +5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi +DrW5viSP +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6 +MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu +MScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNV +BAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwHhcNMTgwMzE2MTIxMDEzWhcNNDMw +MzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEg +U3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZ +n0EGze2jusDbCSzBfN8pfktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/q +p1x4EaTByIVcJdPTsuclzxFUl6s1wB52HO8AU5853BSlLCIls3Jy/I2z5T4IHhQq +NwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2fJmItdUDmj0VDT06qKhF +8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGtg/BKEiJ3 +HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGa +mqi4NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi +7VdNIuJGmj8PkTQkfVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSF +ytKAQd8FqKPVhJBPC/PgP5sZ0jeJP/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0P +qafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSYnjYJdmZm/Bo/6khUHL4wvYBQ +v3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHKHRzQ+8S1h9E6 +Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 +vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQAD +ggIBAEii1QALLtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4 +WxmB82M+w85bj/UvXgF2Ez8sALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvo +zMrnadyHncI013nR03e4qllY/p0m+jiGPp2Kh2RX5Rc64vmNueMzeMGQ2Ljdt4NR +5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8CYyqOhNf6DR5UMEQ +GfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA4kZf +5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq +0Uc9NneoWWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7D +P78v3DSk+yshzWePS/Tj6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTM +qJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmTOPQD8rv7gmsHINFSH5pkAnuYZttcTVoP +0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZckbxJF0WddCajJFdr60qZf +E2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE +BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h +cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1 +MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg +Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9 +thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM +cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG +L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i +NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h +X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b +m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy +Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja +EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T +KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF +6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh +OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1UdDgQWBBRlzeurNR4APn7VdMAc +tHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4wgZswgZgGBFUd +IAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j +b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABC +AG8AbgBhAG4AbwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAw +ADEANzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9m +iWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL4QjbEwj4KKE1soCzC1HA01aajTNF +Sa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDbLIpgD7dvlAceHabJ +hfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1ilI45P +Vf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZE +EAEeiGaPcjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV +1aUsIC+nmCjuRfzxuIgALI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2t +CsvMo2ebKHTEm9caPARYpoKdrcd7b/+Alun4jWq9GJAd/0kakFI3ky88Al2CdgtR +5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH9IBk9W6VULgRfhVwOEqw +f9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpfNIbnYrX9 +ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNK +GbqEZycPvEJdvSRUDewdcAZfpLz6IHxV +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICejCCAgCgAwIBAgIQMZch7a+JQn81QYehZ1ZMbTAKBggqhkjOPQQDAzBuMQsw +CQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UE +YQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENB +IFJPT1QtQSBXRUIwHhcNMjIwNDA2MDkwMTM2WhcNNDcwMzMxMDkwMTM2WjBuMQsw +CQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UE +YQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENB +IFJPT1QtQSBXRUIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARHU+osEaR3xyrq89Zf +e9MEkVz6iMYiuYMQYneEMy3pA4jU4DP37XcsSmDq5G+tbbT4TIqk5B/K6k84Si6C +cyvHZpsKjECcfIr28jlgst7L7Ljkb+qbXbdTkBgyVcUgt5SjYzBhMA8GA1UdEwEB +/wQFMAMBAf8wHwYDVR0jBBgwFoAUk+FDY1w8ndYn81LsF7Kpryz3dvgwHQYDVR0O +BBYEFJPhQ2NcPJ3WJ/NS7Beyqa8s93b4MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjO +PQQDAwNoADBlAjAdfKR7w4l1M+E7qUW/Runpod3JIha3RxEL2Jq68cgLcFBTApFw +hVmpHqTm6iMxoAACMQD94vizrxa5HnPEluPBMBnYfubDl94cT7iJLzPrSA8Z94dG +XSaQpYXFuXqUPoeovQA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNV +BAUTCUc2MzI4NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlk +YWQgZGUgQ2VydGlmaWNhY2lvbjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNV +BAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3QgQ0EwHhcNMTkwOTA0MTAwMDM4WhcN +MzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEwMQswCQYDVQQGEwJF +UzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQwEgYD +VQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9v +dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCj +cqQZAZ2cC4Ffc0m6p6zzBE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9q +yGFOtibBTI3/TO80sh9l2Ll49a2pcbnvT1gdpd50IJeh7WhM3pIXS7yr/2WanvtH +2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcvB2VSAKduyK9o7PQUlrZX +H1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXsezx76W0OL +zc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyR +p1RMVwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQz +W7i1o0TJrH93PB0j7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/ +SiOL9V8BY9KHcyi1Swr1+KuCLH5zJTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJn +LNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe8TZBAQIvfXOn3kLMTOmJDVb3 +n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVOHj1tyRRM4y5B +u8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj +o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC +AgEATh65isagmD9uw2nAalxJUqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L +9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzxj6ptBZNscsdW699QIyjlRRA96Gej +rw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDtdD+4E5UGUcjohybK +pFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM5gf0 +vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjq +OknkJjCb5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ +/zo1PqVUSlJZS2Db7v54EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ9 +2zg/LFis6ELhDtjTO0wugumDLmsx2d1Hhk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI ++PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGyg77FGr8H6lnco4g175x2 +MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3r5+qPeoo +tt7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr +6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV +L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91 +1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx +MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ +QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB +arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr +Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi +FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS +P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN +9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz +uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h +9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t +OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo ++fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7 +KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2 +DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us +H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ +I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 +5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h +3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz +Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y +ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E +N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9 +tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX +0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c +/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X +KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY +zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS +O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D +34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP +K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3 +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv +Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj +QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS +IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2 +HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa +O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv +033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u +dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE +kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41 +3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD +u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq +4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw +CQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu +bHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ +BgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxGjAYBgNVBAMTEUNlcnRhaW5s +eSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4fxzf7flHh4axpMCK ++IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9YBk2 +QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4 +hevIIgcwCgYIKoZIzj0EAwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozm +ut6Dacpps6kFtZaSF4fC0urQe87YQVt8rgIwRt7qy12a7DLCZRawTDBcMPPaTnOG +BtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw +PTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy +dGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9 +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0 +YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANA2 +1B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O5MQT +vqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbed +aFySpvXl8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b0 +1C7jcvk2xusVtyWMOvwlDbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5 +r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGIXsXwClTNSaa/ApzSRKft43jvRl5tcdF5 +cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkNKPl6I7ENPT2a/Z2B7yyQ +wHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQAjeZjOVJ +6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA +2CnbrlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyH +Wyf5QBGenDPBt+U1VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMR +eiFPCyEQtkA6qyI6BJyLm4SGcprSp6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB +/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTgqj8ljZ9EXME66C6u +d0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAszHQNTVfSVcOQr +PbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d +8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi +1wrykXprOQ4vMMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrd +rRT90+7iIgXr0PK3aBLXWopBGsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9di +taY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+gjwN/KUD+nsa2UUeYNrEjvn8K8l7 +lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgHJBu6haEaBQmAupVj +yTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7fpYn +Kx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLy +yCwzk5Iwx06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5n +wXARPbv0+Em34yaXOp/SX3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6 +OV+KmalBWQewLK8= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV +BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X +DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ +BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4 +QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny +gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw +zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q +130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2 +JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw +ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT +AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj +AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG +9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h +bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc +fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu +HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w +t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw +WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw +MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x +MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD +VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX +BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO +ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M +CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu +I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm +TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh +C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf +ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz +IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT +Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k +JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5 +hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB +GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov +L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo +dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr +aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq +hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L +6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG +HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6 +0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB +lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi +o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1 +gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v +faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63 +Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh +jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw +3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT +AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD +QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP +MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do +0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ +UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d +RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ +OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv +JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C +AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O +BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ +LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY +MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ +44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I +Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw +i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN +9u6wWk5JRFRYX0KD +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV +BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g +Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ +BgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJ +R04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDF +dRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05N0Iw +vlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZ +uIt4ImfkabBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhp +n+Sc8CnTXPnGFiWeI8MgwT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKs +cpc/I1mbySKEwQdPzH/iV8oScLumZfNpdWO9lfsbl83kqK/20U6o2YpxJM02PbyW +xPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91QqhngLjYl/rNUssuHLoPj1P +rCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732jcZZroiF +DsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fx +DTvf95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgy +LcsUDFDYg2WD7rlcz8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6C +eWRgKRM+o/1Pcmqr4tTluCRVLERLiohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSCIS1mxteg4BXrzkwJ +d8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOBywaK8SJJ6ejq +kX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQl +qiCA2ClV9+BB/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0 +OJD7uNGzcgbJceaBxXntC6Z58hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+c +NywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5BiKDUyUM/FHE5r7iOZULJK2v0ZXk +ltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklWatKcsWMy5WHgUyIO +pwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj +03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZk +PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE +1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX +QRBdJ3NghVdJIgc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw +IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL +SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH +SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh +ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X +DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1 +TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ +fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA +sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU +WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS +nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH +dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip +NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC +AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF +MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB +uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl +PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP +JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/ +gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2 +j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6 +5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB +o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS +/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z +Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE +W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D +hNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBP +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xGzAZBgNVBAMMEkhpUEtJIFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRa +Fw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3 +YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kgUm9vdCBDQSAtIEcx +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0o9Qw +qNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twv +Vcg3Px+kwJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6 +lZgRZq2XNdZ1AYDgr/SEYYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnz +Qs7ZngyzsHeXZJzA9KMuH5UHsBffMNsAGJZMoYFL3QRtU6M9/Aes1MU3guvklQgZ +KILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfdhSi8MEyr48KxRURHH+CK +FgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj1jOXTyFj +HluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDr +y+K49a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ +/W3c1pzAtH2lsN0/Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgM +a/aOEmem8rJY5AIJEzypuxC00jBF8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6 +fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQDAgGGMA0GCSqG +SIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi +7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqc +SE5XCV0vrPSltJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6Fza +ZsT0pPBWGTMpWmWSBUdGSquEwx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9Tc +XzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07QJNBAsNB1CI69aO4I1258EHBGG3zg +iLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv5wiZqAxeJoBF1Pho +L5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+GpzjLrF +Ne85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wr +kkVbbiVghUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+ +vhV4nYWBSipX3tUZQ9rbyltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQU +YDksswBVLuT1sw5XxJFBAJw/6KXf6vb/yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUZvnHwa/swlG07VOX5uaCwysckBYwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u +LCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExMjAeFw0yMDA0MDgw +NTM2NDZaFw00MDA0MDgwNTM2NDZaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpD +eWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBS +b290IENBMTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6OcE3emhF +KxS06+QT61d1I02PJC0W6K6OyX2kVzsqdiUzg2zqMoqUm048luT9Ub+ZyZN+v/mt +p7JIKwccJ/VMvHASd6SFVLX9kHrko+RRWAPNEHl57muTH2SOa2SroxPjcf59q5zd +J1M3s6oYwlkm7Fsf0uZlfO+TvdhYXAvA42VvPMfKWeP+bl+sg779XSVOKik71gur +FzJ4pOE+lEa+Ym6b3kaosRbnhW70CEBFEaCeVESE99g2zvVQR9wsMJvuwPWW0v4J +hscGWa5Pro4RmHvzC1KqYiaqId+OJTN5lxZJjfU+1UefNzFJM3IFTQy2VYzxV4+K +h9GtxRESOaCtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBRXNPN0zwRL1SXm8UC2LEzZLemgrTANBgkqhkiG9w0BAQsF +AAOCAQEAPrvbFxbS8hQBICw4g0utvsqFepq2m2um4fylOqyttCg6r9cBg0krY6Ld +mmQOmFxv3Y67ilQiLUoT865AQ9tPkbeGGuwAtEGBpE/6aouIs3YIcipJQMPTw4WJ +mBClnW8Zt7vPemVV2zfrPIpyMpcemik+rY3moxtt9XUa5rBouVui7mlHJzWhhpmA +8zNL4WukJsPvdFlseqJkth5Ew1DgDzk9qTPxpfPSvWKErI4cqc1avTc7bgoitPQV +55FYxTpE05Uo2cBl6XLK0A+9H7MV2anjpEcJnuDLN/v9vZfVvhgaaaI5gdka9at/ +yOPiZwud9AzqVN/Ssq+xIvEg37xEHA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIUZNtaDCBO6Ncpd8hQJ6JaJ90t8sswDQYJKoZIhvcNAQEM +BQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u +LCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNDAeFw0yMDA0MDgw +NzA2MTlaFw00NTA0MDgwNzA2MTlaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpD +eWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBS +b290IENBMTQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDF0nqh1oq/ +FjHQmNE6lPxauG4iwWL3pwon71D2LrGeaBLwbCRjOfHw3xDG3rdSINVSW0KZnvOg +vlIfX8xnbacuUKLBl422+JX1sLrcneC+y9/3OPJH9aaakpUqYllQC6KxNedlsmGy +6pJxaeQp8E+BgQQ8sqVb1MWoWWd7VRxJq3qdwudzTe/NCcLEVxLbAQ4jeQkHO6Lo +/IrPj8BGJJw4J+CDnRugv3gVEOuGTgpa/d/aLIJ+7sr2KeH6caH3iGicnPCNvg9J +kdjqOvn90Ghx2+m1K06Ckm9mH+Dw3EzsytHqunQG+bOEkJTRX45zGRBdAuVwpcAQ +0BB8b8VYSbSwbprafZX1zNoCr7gsfXmPvkPx+SgojQlD+Ajda8iLLCSxjVIHvXib +y8posqTdDEx5YMaZ0ZPxMBoH064iwurO8YQJzOAUbn8/ftKChazcqRZOhaBgy/ac +18izju3Gm5h1DVXoX+WViwKkrkMpKBGk5hIwAUt1ax5mnXkvpXYvHUC0bcl9eQjs +0Wq2XSqypWa9a4X0dFbD9ed1Uigspf9mR6XU/v6eVL9lfgHWMI+lNpyiUBzuOIAB +SMbHdPTGrMNASRZhdCyvjG817XsYAFs2PJxQDcqSMxDxJklt33UkN4Ii1+iW/RVL +ApY+B3KVfqs9TC7XyvDf4Fg/LS8EmjijAQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUBpOjCl4oaTeqYR3r6/wtbyPk +86AwDQYJKoZIhvcNAQEMBQADggIBAJaAcgkGfpzMkwQWu6A6jZJOtxEaCnFxEM0E +rX+lRVAQZk5KQaID2RFPeje5S+LGjzJmdSX7684/AykmjbgWHfYfM25I5uj4V7Ib +ed87hwriZLoAymzvftAj63iP/2SbNDefNWWipAA9EiOWWF3KY4fGoweITedpdopT +zfFP7ELyk+OZpDc8h7hi2/DsHzc/N19DzFGdtfCXwreFamgLRB7lUe6TzktuhsHS +DCRZNhqfLJGP4xjblJUK7ZGqDpncllPjYYPGFrojutzdfhrGe0K22VoF3Jpf1d+4 +2kd92jjbrDnVHmtsKheMYc2xbXIBw8MgAGJoFjHVdqqGuw6qnsb58Nn4DSEC5MUo +FlkRudlpcyqSeLiSV5sI8jrlL5WwWLdrIBRtFO8KvH7YVdiI2i/6GaX7i+B/OfVy +K4XELKzvGUWSTLNhB9xNH27SgRNcmvMSZ4PPmz+Ln52kuaiWA3rF7iDeM9ovnhp6 +dB7h7sxaOgTdsxoEqBRjrLdHEoOabPXm6RUVkRqEGQ6UROcSjiVbgGcZ3GOTEAtl +Lor6CZpO2oYofaphNdgOpygau1LgePhsumywbrmHXumZNTfxPWQrqaA0k89jL9WB +365jJ6UeTo3cKXhZ+PmhIIynJkBugnLNeLLIjzwec+fBH7/PzqUqm9tEZDKgu39c +JRNItX+S +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICIzCCAamgAwIBAgIUFhXHw9hJp75pDIqI7fBw+d23PocwCgYIKoZIzj0EAwMw +UTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBM +dGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNTAeFw0yMDA0MDgwODMy +NTZaFw00NTA0MDgwODMyNTZaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpDeWJl +cnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBSb290 +IENBMTUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQLUHSNZDKZmbPSYAi4Io5GdCx4 +wCtELW1fHcmuS1Iggz24FG1Th2CeX2yF2wYUleDHKP+dX+Sq8bOLbe1PL0vJSpSR +ZHX+AezB2Ot6lHhWGENfa4HL9rzatAy2KZMIaY+jQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT +9DAKBggqhkjOPQQDAwNoADBlAjEA2S6Jfl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp +4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJSwdLZrWeqrqgHkHZAXQ6 +bkU6iYAZezKYVWOr62Nuk22rGwlgMU4= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQsw +CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS +VVNUIEJSIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5 +NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG +A1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB +BAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7dPYS +zuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0 +QVK5buXuQqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/ +VbNafAkl1bK6CKBrqx9tMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g +PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2JyX3Jvb3Rf +Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l +dC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1 +c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO +PQQDAwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFW +wKrY7RjEsK70PvomAjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHV +dWNbFJWcHwHP2NVypw87 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQczswBEhb2U14LnNLyaHcZjANBgkqhkiG9w0BAQ0FADBI +MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlE +LVRSVVNUIEJSIFJvb3QgQ0EgMiAyMDIzMB4XDTIzMDUwOTA4NTYzMVoXDTM4MDUw +OTA4NTYzMFowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEi +MCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDIgMjAyMzCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAK7/CVmRgApKaOYkP7in5Mg6CjoWzckjYaCTcfKr +i3OPoGdlYNJUa2NRb0kz4HIHE304zQaSBylSa053bATTlfrdTIzZXcFhfUvnKLNE +gXtRr90zsWh81k5M/itoucpmacTsXld/9w3HnDY25QdgrMBM6ghs7wZ8T1soegj8 +k12b9py0i4a6Ibn08OhZWiihNIQaJZG2tY/vsvmA+vk9PBFy2OMvhnbFeSzBqZCT +Rphny4NqoFAjpzv2gTng7fC5v2Xx2Mt6++9zA84A9H3X4F07ZrjcjrqDy4d2A/wl +2ecjbwb9Z/Pg/4S8R7+1FhhGaRTMBffb00msa8yr5LULQyReS2tNZ9/WtT5PeB+U +cSTq3nD88ZP+npNa5JRal1QMNXtfbO4AHyTsA7oC9Xb0n9Sa7YUsOCIvx9gvdhFP +/Wxc6PWOJ4d/GUohR5AdeY0cW/jPSoXk7bNbjb7EZChdQcRurDhaTyN0dKkSw/bS +uREVMweR2Ds3OmMwBtHFIjYoYiMQ4EbMl6zWK11kJNXuHA7e+whadSr2Y23OC0K+ +0bpwHJwh5Q8xaRfX/Aq03u2AnMuStIv13lmiWAmlY0cL4UEyNEHZmrHZqLAbWt4N +DfTisl01gLmB1IRpkQLLddCNxbU9CZEJjxShFHR5PtbJFR2kWVki3PaKRT08EtY+ +XTIvAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUZ5Dw1t61 +GNVGKX5cq/ieCLxklRAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRCMEAwPqA8oDqG +OGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfYnJfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA097N3U9swFrktpSHxQCF16+tI +FoE9c+CeJyrrd6kTpGoKWloUMz1oH4Guaf2Mn2VsNELZLdB/eBaxOqwjMa1ef67n +riv6uvw8l5VAk1/DLQOj7aRvU9f6QA4w9QAgLABMjDu0ox+2v5Eyq6+SmNMW5tTR +VFxDWy6u71cqqLRvpO8NVhTaIasgdp4D/Ca4nj8+AybmTNudX0KEPUUDAxxZiMrc +LmEkWqTqJwtzEr5SswrPMhfiHocaFpVIbVrg0M8JkiZmkdijYQ6qgYF/6FKC0ULn +4B0Y+qSFNueG4A3rvNTJ1jxD8V1Jbn6Bm2m1iWKPiFLY1/4nwSPFyysCu7Ff/vtD +hQNGvl3GyiEm/9cCnnRK3PgTFbGBVzbLZVzRHTF36SXDw7IyN9XxmAnkbWOACKsG +koHU6XCPpz+y7YaMgmo1yEJagtFSGkUPFaUA8JR7ZSdXOUPPfH/mvTWze/EZTN46 +ls/pdu4D58JDUjxqgejBWoC9EV2Ta/vH5mQ/u2kc6d0li690yVRAysuTEwrt+2aS +Ecr1wPrYg1UDfNPFIkZ1cGt5SAYqgpq/5usWDiJFAbzdNpQ0qTUmiteXue4Icr80 +knCDgKs4qllo3UCkGJCy89UDyibK79XH4I9TjvAA46jtn/mtd+ArY0+ew+43u3gJ +hJ65bvspmZDogNOfJA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQsw +CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS +VVNUIEVWIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5 +NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG +A1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB +BAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8ZRCC +/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rD +wpdhQntJraOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3 +OqQo5FD4pPfsazK2/umLMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g +PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2V2X3Jvb3Rf +Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l +dC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1 +c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO +PQQDAwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CA +y/m0sRtW9XLS/BnRAjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJb +gfM0agPnIjhQW+0ZT0MW +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQaSYJfoBLTKCnjHhiU19abzANBgkqhkiG9w0BAQ0FADBI +MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlE +LVRSVVNUIEVWIFJvb3QgQ0EgMiAyMDIzMB4XDTIzMDUwOTA5MTAzM1oXDTM4MDUw +OTA5MTAzMlowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEi +MCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDIgMjAyMzCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBANiOo4mAC7JXUtypU0w3uX9jFxPvp1sjW2l1sJkK +F8GLxNuo4MwxusLyzV3pt/gdr2rElYfXR8mV2IIEUD2BCP/kPbOx1sWy/YgJ25yE +7CUXFId/MHibaljJtnMoPDT3mfd/06b4HEV8rSyMlD/YZxBTfiLNTiVR8CUkNRFe +EMbsh2aJgWi6zCudR3Mfvc2RpHJqnKIbGKBv7FD0fUDCqDDPvXPIEysQEx6Lmqg6 +lHPTGGkKSv/BAQP/eX+1SH977ugpbzZMlWGG2Pmic4ruri+W7mjNPU0oQvlFKzIb +RlUWaqZLKfm7lVa/Rh3sHZMdwGWyH6FDrlaeoLGPaxK3YG14C8qKXO0elg6DpkiV +jTujIcSuWMYAsoS0I6SWhjW42J7YrDRJmGOVxcttSEfi8i4YHtAxq9107PncjLgc +jmgjutDzUNzPZY9zOjLHfP7KgiJPvo5iR2blzYfi6NUPGJ/lBHJLRjwQ8kTCZFZx +TnXonMkmdMV9WdEKWw9t/p51HBjGGjp82A0EzM23RWV6sY+4roRIPrN6TagD4uJ+ +ARZZaBhDM7DS3LAaQzXupdqpRlyuhoFBAUp0JuyfBr/CBTdkdXgpaP3F9ev+R/nk +hbDhezGdpn9yo7nELC7MmVcOIQxFAZRl62UJxmMiCzNJkkg8/M3OsD6Onov4/knF +NXJHAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUqvyREBuH +kV8Wub9PS5FeAByxMoAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRCMEAwPqA8oDqG +OGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfZXZfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQCTy6UfmRHsmg1fLBWTxj++EI14 +QvBukEdHjqOSMo1wj/Zbjb6JzkcBahsgIIlbyIIQbODnmaprxiqgYzWRaoUlrRc4 +pZt+UPJ26oUFKidBK7GB0aL2QHWpDsvxVUjY7NHss+jOFKE17MJeNRqrphYBBo7q +3C+jisosketSjl8MmxfPy3MHGcRqwnNU73xDUmPBEcrCRbH0O1P1aa4846XerOhU +t7KR/aypH/KH5BfGSah82ApB9PI+53c0BFLd6IHyTS9URZ0V4U/M5d40VxDJI3IX +cI1QcB9WbMy5/zpaT2N6w25lBx2Eof+pDGOJbbJAiDnXH3dotfyc1dZnaVuodNv8 +ifYbMvekJKZ2t0dT741Jj6m2g1qllpBFYfXeA08mD6iL8AOWsKwV0HFaanuU5nCT +2vFp4LJiTZ6P/4mdm13NRemUAiKN4DV/6PEEeXFsVIP4M7kFMhtYVRFP0OUnR3Hs +7dpn1mKmS00PaaLJvOwiS5THaJQXfuKOKD62xur1NGyfN4gHONuGcfrNlUhDbqNP +gofXNJhuS5N5YHVpD/Aa1VP6IQzCP+k/HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAst +Nl7F6cTVg8uGF5csbBNvh1qvSaYd2804BC5f4ko1Di1L+KIkBI3Y4WNeApI02phh +XBxvWHZks/wCuPWdCg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEDjCCAvagAwIBAgIDD92sMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxHzAdBgNVBAMMFkQtVFJVU1QgUm9vdCBD +QSAzIDIwMTMwHhcNMTMwOTIwMDgyNTUxWhcNMjgwOTIwMDgyNTUxWjBFMQswCQYD +VQQGEwJERTEVMBMGA1UECgwMRC1UcnVzdCBHbWJIMR8wHQYDVQQDDBZELVRSVVNU +IFJvb3QgQ0EgMyAyMDEzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +xHtCkoIf7O1UmI4SwMoJ35NuOpNcG+QQd55OaYhs9uFp8vabomGxvQcgdJhl8Ywm +CM2oNcqANtFjbehEeoLDbF7eu+g20sRoNoyfMr2EIuDcwu4QRjltr5M5rofmw7wJ +ySxrZ1vZm3Z1TAvgu8XXvD558l++0ZBX+a72Zl8xv9Ntj6e6SvMjZbu376Ml1wrq +WLbviPr6ebJSWNXwrIyhUXQplapRO5AyA58ccnSQ3j3tYdLl4/1kR+W5t0qp9x+u +loYErC/jpIF3t1oW/9gPP/a3eMykr/pbPBJbqFKJcu+I89VEgYaVI5973bzZNO98 +lDyqwEHC451QGsDkGSL8swIDAQABo4IBBTCCAQEwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUP5DIfccVb/Mkj6nDL0uiDyGyL+cwDgYDVR0PAQH/BAQDAgEGMIG+ +BgNVHR8EgbYwgbMwdKByoHCGbmxkYXA6Ly9kaXJlY3RvcnkuZC10cnVzdC5uZXQv +Q049RC1UUlVTVCUyMFJvb3QlMjBDQSUyMDMlMjAyMDEzLE89RC1UcnVzdCUyMEdt +YkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MDugOaA3hjVodHRwOi8v +Y3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2FfM18yMDEzLmNybDAN +BgkqhkiG9w0BAQsFAAOCAQEADlkOWOR0SCNEzzQhtZwUGq2aS7eziG1cqRdw8Cqf +jXv5e4X6xznoEAiwNStfzwLS05zICx7uBVSuN5MECX1sj8J0vPgclL4xAUAt8yQg +t4RVLFzI9XRKEBmLo8ftNdYJSNMOwLo5qLBGArDbxohZwr78e7Erz35ih1WWzAFv +m2chlTWL+BD8cRu3SzdppjvW7IvuwbDzJcmPkn2h6sPKRL8mpXSSnON065102ctN +h9j8tGlsi6BDB2B4l+nZk3zCRrybN1Kj7Yo8E6l7U0tJmhEFLAtuVqwfLoJs4Gln +tQ5tLdnkwBXxP/oYcuEVbSdbLTAoK59ImmQrme/ydUlfXA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha +ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM +HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03 +UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42 +tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R +ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM +lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp +/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G +A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy +MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl +cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js +L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL +BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni +acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K +zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8 +PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y +Johw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw +NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV +BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn +ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0 +3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z +qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR +p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8 +HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw +ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea +HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw +Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh +c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E +RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt +dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku +Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp +3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF +CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na +xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX +KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICXjCCAeOgAwIBAgIQUs/kjG2gSvc/gpcMgAmMlTAKBggqhkjOPQQDAzBJMQsw +CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSMwIQYDVQQDExpELVRy +dXN0IFNCUiBSb290IENBIDEgMjAyMjAeFw0yMjA3MDYxMTMwMDBaFw0zNzA3MDYx +MTI5NTlaMEkxCzAJBgNVBAYTAkRFMRUwEwYDVQQKEwxELVRydXN0IEdtYkgxIzAh +BgNVBAMTGkQtVHJ1c3QgU0JSIFJvb3QgQ0EgMSAyMDIyMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAEWZM59oxJZijXYQzIq38Moy3foqR8kito1S5+HkDLtGhJfxKhq39X +nxkuYy5b/mZxDDMPud5rxIjDse/sOUDjlqvb5XuuH9z5r0aaakYGL8c3ZIsXYv6W +w6LuhOCwlzm8o4GPMIGMMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFPEpox4B +Eh09dVZNx1B8xRmqDxi3MA4GA1UdDwEB/wQEAwIBBjBKBgNVHR8EQzBBMD+gPaA7 +hjlodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Nicl9yb290X2Nh +XzFfMjAyMi5jcmwwCgYIKoZIzj0EAwMDaQAwZgIxAJf53q5Lj5i1HkB/Mn1NVEPa +ic3CqpI80YIec8/6TJIg+2MnxfVzPQk996dhhozzagIxAOcvfLj1JYw7OR82q431 +hqIu4Xpk2mc5Av7+Mz/Zc7ZYWzr8sqTZYHh3zHmnpq5VvQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFrDCCA5SgAwIBAgIQVNWjlR49lbpyG5rQMSFKujANBgkqhkiG9w0BAQ0FADBJ +MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSMwIQYDVQQDExpE +LVRydXN0IFNCUiBSb290IENBIDIgMjAyMjAeFw0yMjA3MDcwNzMwMDBaFw0zNzA3 +MDcwNzI5NTlaMEkxCzAJBgNVBAYTAkRFMRUwEwYDVQQKEwxELVRydXN0IEdtYkgx +IzAhBgNVBAMTGkQtVHJ1c3QgU0JSIFJvb3QgQ0EgMiAyMDIyMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAryy8jjaM62SvUWrWbjxekTrqmsPKbPuqJ55k +IqlA37koRVrsU2EWKJjCiqR1eFCE3fogSJIHZUE1ZlESdGGdBwaFOTFXeyg/1Zyl +7FrpHEsnn84nBvM39VLYETMWQTof9WN4ZWOGyb/IAQQfbu7i7KwM7oKS4vYaDT85 ++Z1lk634uQXBPfg3gVbDoP4F7OCUFjojFgTapgqThXJtYTuhjUXW43++Fb02hAj2 +C4NrJqqiveCw56rgrmfE04KlDKmk8DN5DVA/8O+QPSS5f9IgbOqX87+c3EfeCWG9 +lHmVWgJ2NWDERyIN93ZjA9PG+4PGXaut7WklKwNbTSUAQeOMhxdSqOAFK0NNFBPK +5z9DIrw3pHXx9r867zIeru5YhpByugSsQEjvXMR4p6mPJ1rLeuxY8sIIWJBtTQOF +eXEVBQ5OPvnfDwX3XxRIViENM5KxrIzlGP6/D+7gBKq9IfJYtlyJCosYCSIaszXG +ZsL1MxWZgOAI+ZYvE4zu2reIxOk3tddq1zqETatwjNNOFFWgohD8ZNpn6PHLM93J +moqPli9Ygdn4mgBDzJD7VXb7huM3ASgMb/TpWU0Vd1FCSsw0uIBDUIHvV6UT26eU +eQ9Lyn4Xfa+jIWTocVVWjwawR+xZD11wWywWQvCGnnXea01ImITiVxi2nIKZZTqL +gHhXDEkCAwEAAaOBjzCBjDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRds4CU +G+WGv2i6FDSk9u5t8t3f5zAOBgNVHQ8BAf8EBAMCAQYwSgYDVR0fBEMwQTA/oD2g +O4Y5aHR0cDovL2NybC5kLXRydXN0Lm5ldC9jcmwvZC10cnVzdF9zYnJfcm9vdF9j +YV8yXzIwMjIuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA0VC5YGFbNSr2X0/V9K9yv +D1HhTbwhS5P0AEQTBxALJRg+SFmW96Hhk5B4Zho9I+siqwGmjgxRM+ZtjDHurKQB +cDlI3sdmLGsNy3Ofh5LpPkcfuO8v7rdWjEiJ8DinFTmy7sA/F6RzAgicvAaKpMK3 +YWH5w9vE0Hp8Yd6xWJH13WVMLwv46z217Yq+dxy6WQISZnHlmCfODj2vUaJF+YL7 +WqWUcPeLhMNMZSWbe+IfMHCzQI467r3052jFnckpR3EOk8i1SE71ZrsHiHFpa3tI +jm/wEcS0yXAUmCC97afqAdpupZsS/j5EMLPw63VSwPTD+ncmpHeCLW/zKB5OlfAw +94n4LKJQW/K+Mn5sVNtyySpa4By2C9hSmlmh47ABJ8WgFlBm3OuubfSbWz2EbVuH +56mJu2644JtTicD/LkAaiUQuGENnOOR8cl/ZoyklQUE9HHcbZKjDVe5jcWZig/R/ +JpmgVDuhEm1wYs7T+bi9IvzUmtS74jgWL7d9OcKwqQPpnM9+GI123F8Ru+tC7FAJ +PlzskDHYGnK6P2kH7pg0wjSk1toT1qmE8gCGwFS6HhGw4rnEB7SR56rmMVZvsUTE +KmK8ybBlnDT8DBpT3yEXu8JtoQrm8bCqRAlQSTh6XXHiMS4ZsN+VQgR9hIjOCiNn +azidFt4G/ihwOKVarvyD7Q== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd +AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC +FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi +1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq +jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ +wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ +WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy +NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC +uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw +IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 +g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP +BSeOE6Fuwg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN +8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/ +RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4 +hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5 +ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM +EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1 +A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy +WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ +1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30 +6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT +91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p +TpPDpFQUWw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICRzCCAc2gAwIBAgIQFSrdFMkY0aRWQIamJa8HXzAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBH +bWJIMS0wKwYDVQQDDCRUZWxla29tIFNlY3VyaXR5IFNNSU1FIEVDQyBSb290IDIw +MjEwHhcNMjEwMzE4MTEwODMwWhcNNDYwMzE3MjM1OTU5WjBlMQswCQYDVQQGEwJE +RTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMS0wKwYD +VQQDDCRUZWxla29tIFNlY3VyaXR5IFNNSU1FIEVDQyBSb290IDIwMjEwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAASwGY+ia7XHzQ8wmTcMw2Bb8fEnIFU9wJKLq1ehb3OD +IcJDEwxeiarHBTV5k2KQ1l0TH9F6oLyeEKdmfEYKsFdsv+ZUOTghbBJccczTWl9t +t6eG37Pf7sLniUGWNfYvSrWjQjBAMB0GA1UdDgQWBBQrywEMY8NTEqWoV6/QnIP7 +vZA6SzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQD +AwNoADBlAjEA1rxIkodHA8dwOyW2H65GZ3N0ACdL5KUEogPfXiitbl4DyN1onLa/ +lBBIlS8P/xiLAjABQDOel5dNBfJ0VAzNOf1qawnBJD9hjjiht+jXRBURYv8OYTdH +S0B/Sl+yZ1pzdcI= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgIQDH5i9XlzO51Djotj7ZGVuDANBgkqhkiG9w0BAQwFADBl +MQswCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0 +eSBHbWJIMS0wKwYDVQQDDCRUZWxla29tIFNlY3VyaXR5IFNNSU1FIFJTQSBSb290 +IDIwMjMwHhcNMjMwMzI4MTIwOTIyWhcNNDgwMzI3MjM1OTU5WjBlMQswCQYDVQQG +EwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMS0w +KwYDVQQDDCRUZWxla29tIFNlY3VyaXR5IFNNSU1FIFJTQSBSb290IDIwMjMwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDvxQ6LvjLSZ0f/Ckxnsyq/yMPF +keu1xx6R4WaoiItVIIAfUV53l54ZClzHazchfAM2AfSIJdmoLkGq/Ngm4JZAYnmu +V54DOBocsncUPumhctDk4DfRF0btUFx6WMX4K/d1L8+BnlostzqsoFmYBFEM/0nF +UP0e00eFSzNPoje1rwSaJzKdVtU/VWHji2+uUf6X/mkH+mJbJuYUeRWlEziuXze+ +lErWDYAWaaSRsjpJmHWdRhCKXHp/hKXorx7Hq7NaRrWjS/WmIzYARrHbBbYbzp56 +Mlya1XLDnYZNK4TTHrWI2hB4nCLDOyO16xMHvW9T7Jvsm9Nl9QcJ412nmbV+ho7V +Av+3hQnjRxTdlmYYNN4I1d/LGJliCyvsAF1SRNPGlvwyViWRz80ZO5U5PgKHmWO2 +1T40eg8RdYG8fQTKYLQoddcCUd1SAC7H/YnxXPPLpCcSOI+7+4nw5MQ4LL6CoHFh +YpGPSAwvK6mw8csQBOd0vzeQ708qQzWXEsYqcA3eLFVHeWMp9cofagZSHK4tJCKD +Iq/QqjC3Kh//ZSNYZZPIjn1AEDGGeNlVyzww8N5RKgA20idFX9jooSE9fkZWOylF +8R0FCc62QzDcRZAQMEyka4aLPz0vMZFx7ya59r6dsGzfEe5YP0N5hjmA8SYXB5jw +maowLENZFM7t4kAThQIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FJrOrCrsAfplcN6XnfHSAIylo2S7MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgw +FoAUms6sKuwB+mVw3ped8dIAjKWjZLswDQYJKoZIhvcNAQEMBQADggIBAONQ/fVA +FiIJljoNqe+B5y4y8KHxSV57iA0Ecte+Z6i6He5Qu3JuetG7DHIwRsjV1wISFplO +Ht9alu6Pkb6uhvgQd6XEbkdhwPIm2U9haAVIdQgVpaF71biziXnm7fHzYQCGey4x +/qNc+Hk9tFuIe+Ajuw2hF/rLaA2Yd3EI4m1DdGvENsWUQaQA1lctmYqLIBIVAjIO +0knsgUjFaidS17JzVVOWPJ5PTLWg0E9X0GcoSGS+xri67GTPyHvFaucq5llXttbU +1sBnXNmeKAlAv/OpNTFlYAPLGWyClQMeXz/hvepJceVbtwtHFhsgiW2UmQx+iGwd +DfS3IRpZl6zL6L4XH5V8U5uvUFKqjQsur1rXYPIqaSq57lRwGKq99aE/0t2hYxkA ++KcM66N58nBZo/iiEgPsE//kAoY218HDpLXUpMI3RbaUcD3FveujFR3jNnoVaSpW +NDnPpZo2qsjtebzP9s4EUwvaslAjfLw+Jq3wDkO7JsuuwkDeNx8KoFHNY522T9jG +R3y82LTtnovzEeKotT7srnA+fiK7NUgXYGIUkTCjdj2mUTaLHw3dajEcpe3dlqNu +cg8TTaqnqVx4+QMSGJM3RRKJPfi+yr3ZvgzZGGSnyEE+dYIhOH1l9KDUE0sHeCn5 +nX7Mhz/E2i6I3eML3FpRWunZEk+eAtv3BSVR +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQsw +CQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBH +bWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIw +MB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIzNTk1OVowYzELMAkGA1UEBhMCREUx +JzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkGA1UE +AwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/O +tdKPD/M12kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDP +f8iAC8GXs7s1J8nCG6NCMEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6f +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA +MGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZMo7k+5Dck2TOrbRBR2Di +z6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdUga/sf+Rn +27iQ7t0l +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBj +MQswCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0 +eSBHbWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAy +MDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMyNzIzNTk1OVowYzELMAkGA1UEBhMC +REUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkG +A1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9 +cUD/h3VCKSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHV +cp6R+SPWcHu79ZvB7JPPGeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMA +U6DksquDOFczJZSfvkgdmOGjup5czQRxUX11eKvzWarE4GC+j4NSuHUaQTXtvPM6 +Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWol8hHD/BeEIvnHRz+sTug +BTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9FIS3R/qy +8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73J +co4vzLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg +8qKrBC7m8kwOFjQgrIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8 +rFEz0ciD0cmfHdRHNCk+y7AO+oMLKFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12 +mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7SWWO/gLCMk3PLNaaZlSJhZQNg ++y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtqeX +gj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2 +p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQ +pGv7qHBFfLp+sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm +9S3ul0A8Yute1hTWjOKWi0FpkzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErw +M807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy/SKE8YXJN3nptT+/XOR0so8RYgDd +GGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4mZqTuXNnQkYRIer+ +CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtzaL1t +xKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+ +w6jv/naaoqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aK +L4x35bcF7DvB7L6Gs4a8wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+lj +X273CXE2whJdV/LItM3z7gLfEdxquVeEHVlNjM7IDiPCtyaaEBRx/pOyiriA8A4Q +ntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0o82bNSQ3+pCTE4FCxpgm +dTdmQRCsu/WU48IxK63nI1bMNSWSs1A= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c +JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP +mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ +wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 +VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ +AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB +AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun +pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC +dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf +fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm +NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx +H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA +n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc +biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp +EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA +bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu +YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW +BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI +QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I +0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni +lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 +B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv +ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg +RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf +Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q +RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD +AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY +JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv +6pZjamVFkpUBtA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB +CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 +nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt +43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P +T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 +gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR +TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw +DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr +hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg +06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF +PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls +YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe +Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw +EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x +IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG +fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO +Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx +AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ +oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 +sycX +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep ++OkuE6N36B9K +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICHDCCAaOgAwIBAgIQBT9uoAYBcn3tP8OjtqPW7zAKBggqhkjOPQQDAzBQMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xKDAmBgNVBAMTH0Rp +Z2lDZXJ0IFNNSU1FIEVDQyBQMzg0IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN +NDYwMTE0MjM1OTU5WjBQMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs +IEluYy4xKDAmBgNVBAMTH0RpZ2lDZXJ0IFNNSU1FIEVDQyBQMzg0IFJvb3QgRzUw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQWnVXlttT7+2drGtShqtJ3lT6I5QeftnBm +ICikiOxwNa+zMv83E0qevAED3oTBuMbmZUeJ8hNVv82lHghgf61/6GGSKc8JR14L +HMAfpL/yW7yY75lMzHBrtrrQKB2/vgSjQjBAMB0GA1UdDgQWBBRzemuW20IHi1Jm +wmQyF/7gZ5AurTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAKBggq +hkjOPQQDAwNnADBkAjA3RPUygONx6/Rtz3zMkZrDbnHY0iNdkk2CQm1cYZX2kfWn +CPZql+mclC2YcP0ztgkCMAc8L7lYgl4Po2Kok2fwIMNpvwMsO1CnO69BOMlSSJHW +Dvu8YDB8ZD8SHkV/UT70pg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFajCCA1KgAwIBAgIQBfa6BCODRst9XOa5W7ocVTANBgkqhkiG9w0BAQwFADBP +MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJzAlBgNVBAMT +HkRpZ2lDZXJ0IFNNSU1FIFJTQTQwOTYgUm9vdCBHNTAeFw0yMTAxMTUwMDAwMDBa +Fw00NjAxMTQyMzU5NTlaME8xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2Vy +dCwgSW5jLjEnMCUGA1UEAxMeRGlnaUNlcnQgU01JTUUgUlNBNDA5NiBSb290IEc1 +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4Gpb2fj5fey1e+9f3Vw0 +2Npd0ctldashfFsA1IJvRYVBiqkSAnIy8BT1A3W7Y5dJD0CZCxoeVqfS0OGr3eUE +G+MfFBICiPWggAn2J5pQ8LrjouCsahSRtWs4EHqiMeGRG7e58CtbyHcJdrdRxDYK +mVNURCW3CTWGFwVWkz1BtwLXYh+KkhGH6hFt6ggR3LF4SEmS9rRRgHgj2P7hVho6 +kBNWNInV4pWLX96yzPs/OLeF9+qevy6hLi9NfWoRLjag/xEIBJVV4Bs7Z5OplFXq +Mu0GOn/Cf+OtEyfRNEGzMMO/tIj4A4Kk3z6reHegWZNx593rAAR7zEg5KOAeoxVp +yDayoQuX31XW75GcpPYW91EK7gMjkdwE/+DdOPYiAwDCB3EaEsnXRiqUG83Wuxvu +v75NUFiwC80wdin1z+W2ai92sLBpatBtZRg1fpO8chfBVULNL8Ilu/T9HaFkIlRd +4p5yQYRucZbqRQe2XnpKhp1zZHc4A9IPU6VVIMRN/2hvVanq3XHkT9mFo3xOKQKe +CwnyGlPMAKbd0TT2DcEwsZwCZKw17aWwKbHSlTMP0iAzvewjS/IZ+dqYZOQsMR8u +4Y0cBJUoTYxYzUvlc4KGjOyo1nlc+2S73AxMKPYXr+Jo1haGmNv8AdwxuvicDvko +Rkrh/ZYGRXkRaBdlXIsmh1sCAwEAAaNCMEAwHQYDVR0OBBYEFNGj1FcdT1XbdUxc +Qp5jFs60xjsfMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG +SIb3DQEBDAUAA4ICAQAHpwreU7ua63C/sjaQzeSnuPEM5F1aHXhl/Mm4HiMRV3xp +NW0B/1NQvwcOuscBP1gqlHUDqxwLI9wbih43PR1Yj3PZsypv3xCgWwynyrB/uSSi +ATUy5V5GQevYf3PnQumkUSZ3gQqo6w8KUJ1+iiBn/AuOOhHTxYxgGNlLsfzU8bRJ +Tq6H4dH7dqFf8wbPl5YM6Z51gVxTDSL8NuZJbnTbAIWNfCKgjvsQTNRiE1vvS3Im +i/xOio/+lxBTxXiLQmQbX+CJ/bsJf1DgVIUmEWodZflJKdx8Nt/7PffSrO4yjW6m +fTmcRcTKDfU7tHlTpS9Wx1HFikxkXZBDI45rTBd4zOi/9TvkqEjPrZsM3zJK09kS +jiN4DS2vn6+ePAnClwDtOmkccT8539OPxGb17zaUD/PdkraWX5Cm3XOqpiCUlCVq +CQxy5BMjYEyjyhcue2cA29DN6nofOSZXiTB3y07llUVPX/s2XD35ILU6ECVPkzJa +7sGW6OlWBLBJYU3seKidGMH/2OovVu+VK3sEXmfjVUDtOQT5C3n1aoxcD4makMfN +i97bJjWhbs2zQvKiDzsMjpP/FM/895P35EEIbhlSEQ9TGXN4DM/YhYH4rVXIsJ5G +Y6+cUu5cv/DAWzceCSDSPiPGoRVKDjZ+MMV5arwiiNkMUkAf3U4PZyYW0q0XHA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp +Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2 +MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ +bmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS +7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp +0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS +B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49 +BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ +LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4 +DXZDjC5Ty3zfDBeWUA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN +MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT +HERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN +NDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs +IEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS87IE+ +ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG0 +2C+JFvuUAT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgp +wgscONyfMXdcvyej/Cestyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZM +pG2T6T867jp8nVid9E6P/DsjyG244gXazOvswzH016cpVIDPRFtMbzCe88zdH5RD +nU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnVDdXifBBiqmvwPXbzP6Po +sMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9qTXeXAaDx +Zre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cd +Lvvyz6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvX +KyY//SovcfXWJL5/MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNe +XoVPzthwiHvOAbWWl9fNff2C+MIkwcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPL +tgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4EFgQUUTMc7TZArxfTJc1paPKv +TiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN +AQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw +GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7H +PNtQOa27PShNlnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLF +O4uJ+DQtpBflF+aZfTCIITfNMBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQ +REtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/u4cnYiWB39yhL/btp/96j1EuMPik +AdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9GOUrYU9DzLjtxpdRv +/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh47a+ +p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilw +MUc/dNAUFvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WF +qUITVuwhd4GTWgzqltlJyqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCK +ovfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg +RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y +ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If +xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV +ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO +DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ +jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ +CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi +EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM +fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY +uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK +chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t +9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 +SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd ++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc +fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa +sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N +cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N +0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie +4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI +r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 +/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm +gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 +MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV +wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe +rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341 +68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh +4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp +UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o +abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc +3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G +KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt +hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO +Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt +zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD +ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2 +cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN +qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5 +YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv +b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2 +8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k +NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj +ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp +q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt +nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa +GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg +Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J +WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB +rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp ++ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1 +ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i +Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz +PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og +/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH +oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI +yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud +EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2 +A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL +MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f +BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn +g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl +fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K +WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha +B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc +hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR +TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD +mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z +ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y +4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza +8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM +V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB +4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr +H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd +8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv +vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT +mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe +btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc +T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt +WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ +c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A +4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD +VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG +CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0 +aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu +dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw +czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G +A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC +TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg +Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0 +7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem +d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd ++LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B +4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN +t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x +DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57 +k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s +zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j +Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT +mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK +4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 +MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR +/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu +FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR +U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c +ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR +FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k +A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw +eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl +sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp +VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q +A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ +ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD +ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI +FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv +oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg +u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP +0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf +3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl +8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ +DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN +PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ +ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICajCCAfCgAwIBAgIUNi2PcoiiKCfkAP8kxi3k6/qdtuEwCgYIKoZIzj0EAwMw +ZDELMAkGA1UEBhMCUFQxKjAoBgNVBAoMIURpZ2l0YWxTaWduIENlcnRpZmljYWRv +cmEgRGlnaXRhbDEpMCcGA1UEAwwgRElHSVRBTFNJR04gR0xPQkFMIFJPT1QgRUNE +U0EgQ0EwHhcNMjEwMTIxMTEwNzUwWhcNNDYwMTE1MTEwNzUwWjBkMQswCQYDVQQG +EwJQVDEqMCgGA1UECgwhRGlnaXRhbFNpZ24gQ2VydGlmaWNhZG9yYSBEaWdpdGFs +MSkwJwYDVQQDDCBESUdJVEFMU0lHTiBHTE9CQUwgUk9PVCBFQ0RTQSBDQTB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABG4Lo6szTRzqSuj8BI0UoH3wCCxfg6uT0dJ7utdJ +fY/sElBf1LnL5fD5M2MfyVfsQNgRC5foUhbMKY70BoYeONw9V8Tuqr3IVAQmWicT +UUc9Hx8ajqiVpDPQzEfMbbj8SKNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME +GDAWgBTOr0qLGnXi8TjnAvAWrV7qZNV7tDAdBgNVHQ4EFgQUzq9Kixp14vE45wLw +Fq1e6mTVe7QwDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMAqIxHGc +RANNjbTHvKiu2TAnNWprFmPX/OdZ4aeJG0wxmiNVRObzQyHVRydvbVcBqgIxAPuy +6uKXf1G1n0jrvG81iahkcKtXds3AxhRgyn/iggBz98w16o4km+UIWccEjHN4/g== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFtTCCA52gAwIBAgIUXVnIyqsJV/XmtdoplARq/8XUlYcwDQYJKoZIhvcNAQEN +BQAwYjELMAkGA1UEBhMCUFQxKjAoBgNVBAoMIURpZ2l0YWxTaWduIENlcnRpZmlj +YWRvcmEgRGlnaXRhbDEnMCUGA1UEAwweRElHSVRBTFNJR04gR0xPQkFMIFJPT1Qg +UlNBIENBMB4XDTIxMDEyMTEwNTAzNFoXDTQ2MDExNTEwNTAzNFowYjELMAkGA1UE +BhMCUFQxKjAoBgNVBAoMIURpZ2l0YWxTaWduIENlcnRpZmljYWRvcmEgRGlnaXRh +bDEnMCUGA1UEAwweRElHSVRBTFNJR04gR0xPQkFMIFJPT1QgUlNBIENBMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyIe2ONMc8N4S+IPHxIriibi0Inp4 ++AxmUWh2NwrVT8JaCLgWXPdyAQk3hIEqVGvXktBs+qinQxI06w7bNw8p/ooxUULo +S5yQqMgsEdP9oCl+zt6U9oLgWLRORSXxIvI90w97VBrcMrbWUU5+QbRXuCzGuQ4u +ylfx1cjTWOel6UIRrtMgJZRp14/Kog3D058HaD8V0mcuU/12gpsLc6kpDZ4RkxQI +mOyeVBJKVqIGFexrbC6SYC6GDa6CH1FN47IH1xAZVyL2qWlEhPPZPaAGv8yIfn/1 +zlulwipqdELqb6b/+Wix0F+9kdJVbzNXTB6d5OKLwYVloOBqnAAAiJLdWAgW8nAx +qBzh3r1OcenWvn61oVrDTfe/m72UpP31qlOTRskmAQRwxKBxus4lZvuRflVw7kkK +TWJ/wlCacvIYZ53pRag0hOj4gfbRWiIeB087s3/dEaVz3L6pGTppqW0bMuKJqqUn +C1p+dOIPZDldfly5wRf8x41eyewk7dLyP3qERTcCvj5rWcTmWxZtwKqeqrVZLixw +VZzMmZaYJFTRjtrKtBG0t3BDH2+QCyCgqHYTZdvbI1p1S6ELMXcK7n1oYRoTjOpR +flxWo1dMXaHrE2W/VBTM8+7c1+w8l/J4Vrjfclxw/M4G3Z/SBzHv51KRns2618AY +RAcxZUkyaRNK648CAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAW +gBS1Nrw8jBqrLPZZGS2DFNqTJRXWhjAdBgNVHQ4EFgQUtTa8PIwaqyz2WRktgxTa +kyUV1oYwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDQUAA4ICAQAU+zElODH4 +ygiyI3Y4rfjTWfXMtFcl4US+fvwW7K76Jp9PZxZKVvD97ccZATSOkFot1oBc7HHS +gSWCHgBx35rR1R0iu9Gl82IPtOvcJHP+plbNmhTFBDUWMaIH66UA4rb4X3L9P2FJ +jt5+TTjXeh50N2xR3L4ABLg4FPMgwe2bpyP9DUKEHX/yc8PQeGPxn+zXW+nxvmyg +SwOejWnhFNqIEIEjU//aVCsLxrmWlQQYRvN7qJfYW2ik5DgcDkXlmNMJrppe7LN5 +DTly8vSUnQ6eYCLmqPZMhc0HgjpoOc09X+M49LavO2tKn2BRRaJAAuWqDOM+0XjU +onScJroFmihwSj6mC9AdSfC6+K5BEH6kBxK9qM8pPVe7x/FDRwA+rnAYWiB7Ccs6 +OnCA5UxgmMEVwR1K98jwm+FyreddaFgLBLGMvJ+3+26LWwRV++sjVdd4UNoly74n +NrskGnkcUdH+E7v/eCzcpL4v9sVLU8+nTJlecKxZiASuZAS/e6Z6TdPod72hflAV +8+9JMIVNIVeq2yx1l62BAYeisXCdHgZaA2CxP6ZtgizUFLGBpeg9iB20cixYN4qO +OJS4c92p4Lj2d6KzfFjermk6tYulGrvy2HQGnP1icyAhdrF+cJ4Z1OsXYhk4mc02 +K0f+McvfueSsCNPYpuvUnn5LZKRVXSsXyQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV +BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu +MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy +MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx +EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe +NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH +PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I +x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe +QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR +yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO +QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912 +H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ +QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD +i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs +nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1 +rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI +hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf +GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb +lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka ++elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal +TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i +nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3 +gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr +G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os +zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x +L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkG +A1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkw +FwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYx +MDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9u +aXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMIICIjANBgkq +hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWiD59b +RatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9Z +YybNpyrOVPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3 +QWPKzv9pj2gOlTblzLmMCcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPw +yJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCmfecqQjuCgGOlYx8ZzHyyZqjC0203b+J+ +BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKAA1GqtH6qRNdDYfOiaxaJ +SaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9ORJitHHmkH +r96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj0 +4KlGDfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9Me +dKZssCz3AwyIDMvUclOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIw +q7ejMZdnrY8XD2zHc+0klGvIg5rQmjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2 +nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1UdIwQYMBaAFNwu +H9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA +VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJC +XtzoRlgHNQIw4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd +6IwPS3BD0IL/qMy/pJTAvoe9iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf ++I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS8cE54+X1+NZK3TTN+2/BT+MAi1bi +kvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2HcqtbepBEX4tdJP7 +wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxSvTOB +TI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6C +MUO+1918oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn +4rnvyOL2NSl6dPrFf4IFYqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+I +aFvowdlxfv1k7/9nR4hYJS8+hge9+6jlgqispdNpQ80xiEmEU5LAsTkbOYMBMMTy +qfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG +EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx +IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw +MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln +biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND +IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci +MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti +sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O +BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB +Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c +3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J +0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG +EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo +bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ +TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s +b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0 +WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS +fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB +zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq +hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB +CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD ++JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG +A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg +SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw +MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln +biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v +dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ +BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ +HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH +3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH +GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c +xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1 +aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq +TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87 +/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4 +kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG +YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT ++xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo +WXzhriKi4gp6D/piq1JM4fHfyr6DDUI= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD +VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU +ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH +MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO +MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv +Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz +f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO +8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq +d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM +tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt +Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB +o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD +AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x +PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM +wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d +GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH +6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby +RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz +dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL +MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp +cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP +Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr +ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL +MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1 +yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr +VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/ +nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ +KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG +XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj +vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt +Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g +N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC +nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz +dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL +MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp +cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y +YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua +kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL +QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp +6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG +yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i +QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ +KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO +tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu +QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ +Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u +olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48 +x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz +dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG +A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U +cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf +qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ +JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ ++jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS +s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5 +HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7 +70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG +V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S +qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S +5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia +C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX +OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE +FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2 +KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg +Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B +8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ +MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc +0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ +u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF +u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH +YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8 +GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO +RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e +KeC2uAloGRwYQw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC +VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ +cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ +BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt +VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D +0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9 +ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G +A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs +aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I +flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE +AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG +EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM +FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC +REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp +Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM +VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ +SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ +4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L +cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi +eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG +A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 +DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j +vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP +DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc +maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D +lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv +KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICMTCCAbagAwIBAgIMC3MoERh0MBzvbwiEMAoGCCqGSM49BAMDMEsxCzAJBgNV +BAYTAkRFMQ0wCwYDVQQKDARBdG9zMS0wKwYDVQQDDCRBdG9zIFRydXN0ZWRSb290 +IFJvb3QgQ0EgRUNDIEcyIDIwMjAwHhcNMjAxMjE1MDgzOTEwWhcNNDAxMjEwMDgz +OTA5WjBLMQswCQYDVQQGEwJERTENMAsGA1UECgwEQXRvczEtMCsGA1UEAwwkQXRv +cyBUcnVzdGVkUm9vdCBSb290IENBIEVDQyBHMiAyMDIwMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAEyFyAyk7CKB9XvzjmYSP80KlblhYWwwxeFaWQCf84KLR6HgrWUyrB +u5BAdDfpgeiNL2gBNXxSLtj0WLMRHFvZhxiTkS3sndpsnm2ESPzCiQXrmBMCAWxT +Hg5JY1hHsa/Co2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFFsfxHFs +shufvlwfjP2ztvuzDgmHMB0GA1UdDgQWBBRbH8RxbLIbn75cH4z9s7b7sw4JhzAO +BgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAOzgmf3d5FTByx/oPijX +FVlKgspTMOzrNqW5yM6TR1bIYabhbZJTlY/241VT8N165wIxALCH1RuzYPyRjYDK +ohtRSzhUy6oee9flRJUWLzxEeC4luuqQ5OxS7lfsA4TzXtsWDQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4w +LAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0w +CwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0 +MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBF +Q0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMHYwEAYHKoZI +zj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6KDP/X +tXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4 +AjJn8ZQSb+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2 +KCXWfeBmmnoJsmo7jjPXNtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMD +aAAwZQIwW5kp85wxtolrbNa9d+F851F+uDrNozZffPc8dz7kUK2o59JZDCaOMDtu +CCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGYa3cpetskz2VAv9LcjBHo +9H1/IISpQuQo +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFfzCCA2egAwIBAgIMR7opRlU+FpKXsKtAMA0GCSqGSIb3DQEBDAUAMEsxCzAJ +BgNVBAYTAkRFMQ0wCwYDVQQKDARBdG9zMS0wKwYDVQQDDCRBdG9zIFRydXN0ZWRS +b290IFJvb3QgQ0EgUlNBIEcyIDIwMjAwHhcNMjAxMjE1MDg0MTIzWhcNNDAxMjEw +MDg0MTIyWjBLMQswCQYDVQQGEwJERTENMAsGA1UECgwEQXRvczEtMCsGA1UEAwwk +QXRvcyBUcnVzdGVkUm9vdCBSb290IENBIFJTQSBHMiAyMDIwMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAljGFSqoPMv554UOHnPsjt45/DVS9x2KTd+Qc +NQR2owOLIu7EhN2lk25uso4JA+tRFjEXqmkVGA5ndCNe6pp9tTk+PYKpa+H+qRyw +rVpNTHiDQYvP8h1impgEnGPpq2X+SB0kZQdHPrmRLumdm38aNak0sLflcDPvSnJR +tge/YD8qn51U3/PXlElRA1pAqWjdEVlc+HamvFBSEO2s7JXg1INrSdoKT5mD3jKD +SINnlbJ+54GFPc2C98oC7W2IXQiNuDW/KmkwmbtL0UHbRaCTmVGBkDYIqoq26I+z +y+7lRg1ydfVJbOGify+87YSmN+7ewk85Tvae8MnRmzCdSW3h2v8SEIzW5Zl7BbZ9 +sAnHpPiyHDmVOTP0Nc4lYnuwXyDzy234bFIUZESP08ipdgflr3GZLS0EJUh2r8Pn +zEPyB7xKJCQ33fpulAlvTF4BtP5U7COWpV7dhv/pRirx6NzspT2vb6oOD7R1+j4I +uSZFT2aGTLwZuOHVNe6ChMjTqxLnzXMzYnf0F8u9NHYqBc6V5Xh5S56wjfk8WDiR +6l6HOMC3Qv2qTIcjrQQgsX52Qtq7tha6V8iOE/p11QhMrziRqu+P+p9JLlR8Clax +evrETi/Uo/oWitCV5Zem/8P8fA5HWPN/B3sS3Fc/LeOhTVtSTDOHmagJe2x+DvLP +VkKe6wUCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBQgJfMH +/adv8ZbukRBpzJrvfchoeDAdBgNVHQ4EFgQUICXzB/2nb/GW7pEQacya733IaHgw +DgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQAkK06Y8h0X7dl2JrYw +M+hpRaFRS1LYejowtuQS6r+fTOAEpPY1xv6hMPdThZKtVAVXX5LlKt42J557E0fJ +anWv/PM35wz1PQFztWlR+L1Z0boL+Lq6ZCdDs3yDlYrnnhOW129KlkFJiw4grRbG +96aHW4gSiYuJyhLSVq8iASFG6auYP6eI3uTLKpp1Gfo5XgkF1wMyGrgXUQjHAEB9 +9L74DFn0aXZu06RYW14mc+RCVQZeeEAP0zif7yZRcHSR8XdiAejZy+uh3zkyHbtr +/XH+68+l5hT9AIATxpoASLCZBemugEj7CT9RFLW552BNTcovgSHuUgxletz1iUlM +MJI0WIAyWbEN/yRhD+cKQtB7vPiOJ0c/cJ0n2bYGPaW7y16Prg5Tx5xqbztMD6NA +cKiaB87UblsHotLiVLa9bzNyY61RmOGPdvFqBzgl/vZizl/bY8Jume8G3LneGRro +VD190nZ12V4+MkinjPKecgz4uFi4FyOlFId1WHoAgQciOWpMlKC1otunLMGw8aOb +wEz3bXDqMZ/xrn0+cyjZod/6k/CbsPDizSUgde/ifTIFyZt27su9MR75lJhLJFhW +SMDeBky9pjRd7RZhY3P7GeL6W9iXddRtnmA5XpSLAizrmc5gKm4bjKdLvP025pgf +ZfJ/8eOPTIBGNli2oWXLzhxEdQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBM +MS4wLAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIx +MQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00 +MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBD +QSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BBl01Z +4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYv +Ye+W/CBGvevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZ +kmGbzSoXfduP9LVq6hdKZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDs +GY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt0xU6kGpn8bRrZtkh68rZYnxGEFzedUln +nkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVKPNe0OwANwI8f4UDErmwh +3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMYsluMWuPD +0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzy +geBYBr3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8 +ANSbhqRAvNncTFd+rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezB +c6eUWsuSZIKmAMFwoW4sKeFYV+xafJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lI +pw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +dEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +DAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS +4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPs +o0UvFJ/1TCplQ3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJ +qM7F78PRreBrAwA0JrRUITWXAdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuyw +xfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9GslA9hGCZcbUztVdF5kJHdWoOsAgM +rr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2VktafcxBPTy+av5EzH4 +AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9qTFsR +0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuY +o7Ey7Nmj1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5 +dDTedk+SKlOxJTnbPP/lPqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcE +oji2jbDwN/zIIX8/syQbPYtuzE2wFg2WHYMfRsCbvUOZ58SWLs5fyQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg +MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx +MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET +MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI +xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k +ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD +aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw +LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw +1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX +k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2 +SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h +bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n +WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY +rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce +MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu +bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt +Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61 +55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj +vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf +cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz +oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp +nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs +pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v +JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R +8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4 +5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 +MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 +RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT +gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm +KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd +QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ +XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o +LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU +RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp +jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK +6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX +mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs +Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH +WD9f +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc +8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke +hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI +KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg +515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO +xwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG +A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv +b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw +MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i +YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT +aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ +jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp +xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp +1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG +snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ +U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 +9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B +AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz +yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE +38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP +AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad +DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME +HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx +CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD +ExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw +MDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2Ex +HDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkBjtjq +R+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGdd +yXqBPCCjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ +7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZkvLtoURMMA/cVi4RguYv/Uo7njLwcAjA8 ++RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA +MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD +VQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy +MDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYt +c2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08EsCVeJ +OaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQG +vGIFAha/r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud +316HCkD7rRlr+/fKYIje2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo +0q3v84RLHIf8E6M6cqJaESvWJ3En7YEtbWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSE +y132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvjK8Cd+RTyG/FWaha/LIWF +zXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD412lPFzYE ++cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCN +I/onccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzs +x2sZy/N78CsHpdlseVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqa +ByFrgY/bxFn63iLABJzjqls2k+g9vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC +4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEMBQADggIBAHx4 +7PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti +2kM3S+LGteWygxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIk +pnnpHs6i58FZFZ8d4kuaPp92CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRF +FRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZmOUdkLG5NrmJ7v2B0GbhWrJKsFjLt +rWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qqJZ4d16GLuc1CLgSk +ZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwyeqiv5 +u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP +4vkYxboznxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6 +N3ec592kD3ZDZopD8p/7DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3 +vouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICITCCAaegAwIBAgIQdlP+qicdlUZd1vGe5biQCjAKBggqhkjOPQQDAzBSMQsw +CQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEoMCYGA1UEAxMf +R2xvYmFsU2lnbiBTZWN1cmUgTWFpbCBSb290IEU0NTAeFw0yMDAzMTgwMDAwMDBa +Fw00NTAzMTgwMDAwMDBaMFIxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxT +aWduIG52LXNhMSgwJgYDVQQDEx9HbG9iYWxTaWduIFNlY3VyZSBNYWlsIFJvb3Qg +RTQ1MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE+XmLgUc3iZY/RUlQfxomC5Myfi7A +wKcImsNuj5s+CyLsN1O3b4qwvCc3S22pRjvZH/+loUS7LXO/nkEHXFObUQg6Wrtv +OMcWkXjCShNpHYLfWi8AiJaiLhx0+Z1+ZjeKo0IwQDAOBgNVHQ8BAf8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU3xNei1/CQAL9VreUTLYe1aaxFJYw +CgYIKoZIzj0EAwMDaAAwZQIwE7C+13EgPuSrnM42En1fTB8qtWlFM1/TLVqy5IjH +3go2QjJ5naZruuH5RCp7isMSAjEAoGYcToedh8ntmUwbCu4tYMM3xx3NtXKw2cbv +vPL/P/BS3QjnqmR5w+RpV5EvpMt8 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFcDCCA1igAwIBAgIQdlP+qExQq5+NMrUdA49X3DANBgkqhkiG9w0BAQwFADBS +MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEoMCYGA1UE +AxMfR2xvYmFsU2lnbiBTZWN1cmUgTWFpbCBSb290IFI0NTAeFw0yMDAzMTgwMDAw +MDBaFw00NTAzMTgwMDAwMDBaMFIxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i +YWxTaWduIG52LXNhMSgwJgYDVQQDEx9HbG9iYWxTaWduIFNlY3VyZSBNYWlsIFJv +b3QgUjQ1MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA3HnMbQb5bbvg +VgRsf+B1zC0FSehL3FTsW3eVcr9/Yp2FqYokUF9T5dt0b6QpWxMqCa2axS/C93Y7 +oUVGqkPmJP4rsG8ycBlGWnkmL/w9fV9ky1fMYWGo2ZVu45Wgbn9HEhjW7wPJ+4r6 +mr2CFalVd0sRT1nga8Nx8wzYVNWBaD4TuRUuh4o8RCc2YiRu+CwFcjBhvUKRI8Sd +JafZVJoUozGtgHkMp2NsmKOsV0czH2WW4dDSNdr5cfehpiW1QV3fPmDY0fafpfK4 +zBOqj/mybuGDLZPdPoUa3eixXCYBy0mF/PzS1H+FYoZ0+cvsNSKiDDCPO6t561by ++kLz7fkfRYlAKa3qknTqUv1WtCvaou11wm6rzlKQS/be8EmPmkjUiBltRebMjLnd +ZGBgAkD4uc+8WOs9hbnGCtOcB2aPxxg5I0bhPB6jL1Bhkgs9K2zxo0c4V5GrDY/G +nU0E0iZSXOWl/SotFioBaeepfeE2t7Eqxdmxjb25i87Mi6E+C0jNUJU0xNgIWdhr +JvS+9dQiFwBXya6bBDAznwv731aiyW5Udtqxl2InWQ8RiiIbZJY/qPG3JEqNPFN8 +bYN2PbImSHP1RBYBLQkqjhaWUNBzBl27IkiCTApGWj+A/1zy8pqsLAjg1urwEjiB +T6YQ7UarzBacC89kppkChURnRq39TecCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgGG +MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKCTFShu7o8IsjXGnmJ5dKexDit7 +MA0GCSqGSIb3DQEBDAUAA4ICAQBFCvjRXKxigdAE17b/V1GJCwzL3iRlN/urnu1m +9OoMGWmJuBmxMFa02fb3vsaul8tF9hGMOjBkTMGfWcBGQggGR2QXeOCVBwbWjKKs +qdk/03tWT/zEhyjftisWI8CfH1vj1kReIk8jBIw1FrV5B4ZcL5fi9ghkptzbqIrj +pHt3DdEpkyggtFOjS05f3sH2dSP8Hzx4T3AxeC+iNVRxBKzIxG3D9pGx/s3uRG6B +9kDFPioBv6tMsQM/DRHkD9Ik4yKIm59fRz1RSeAJN34XITF2t2dxSChLJdcQ6J9h +WRbFPjJOHwzOo8wP5McRByIvOAjdW5frQmxZmpruetCd38XbCUMuCqoZPWvoajB6 +V+a/s2o5qY/j8U9laLa9nyiPoRZaCVA6Mi4dL0QRQqYA5jGY/y2hD+akYFbPedey +Ttew+m4MVyPHzh+lsUxtGUmeDn9wj3E/WCifdd1h4Dq3Obbul9Q1UfuLSWDIPGau +l+6NJllXu3jwelAwCbBgqp9O3Mk+HjrcYpMzsDpUdG8sMUXRaxEyamh29j32ahNe +JJjn6h2az3iCB2D3TRDTgZpFjZ6vm9yAx0OylWikww7oCkcVv1Qz3AHn1aYec9h6 +sr8vreNVMJ7fDkG84BH1oQyoIuHjAKNOcHyS4wTRekKKdZBZ45vRTKJkvXN5m2/y +s8H2PA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh +MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE +YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 +MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo +ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg +MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN +ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA +PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w +wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi +EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY +avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ +YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE +sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h +/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 +IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy +OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P +TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER +dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf +ReYNnyicsbkqWletNw+vHX/bvZ8= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT +EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp +ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz +NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH +EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE +AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD +E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH +/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy +DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh +GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR +tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA +AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX +WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu +9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr +gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo +2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI +4uJEvlz36hz1 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl +MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp +U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw +NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE +ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp +ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3 +DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf +8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN ++lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0 +X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa +K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA +1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G +A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR +zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0 +YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD +bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3 +L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D +eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp +VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY +WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs +ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw +MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj +aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp +Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg +nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 +HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N +Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN +dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 +HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G +CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU +sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 +4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg +8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 +mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD +VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw +MTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0g +UjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wWTAT +BgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkWymOx +uYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNV +HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/ ++wpu+74zyTyjhNUwCgYIKoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147 +bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw +CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw +MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp +Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo +27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w +Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw +TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl +qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH +szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8 +Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk +MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92 +wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p +aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN +VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID +AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb +C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe +QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy +h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4 +7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J +ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef +MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/ +Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT +6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ +0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm +2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb +bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw +CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +MBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw +MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp +Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3LvCvpt +nfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY +6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAu +MC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7k +RXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWg +f9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1mKPV ++3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K8Yzo +dDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW +Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKa +G73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCq +gc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwID +AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBAB/Kzt3H +vqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8 +0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyC +B19m3H0Q/gxhswWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2u +NmSRXbBoGOqKYcl3qJfEycel/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMg +yALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVnjWQye+mew4K6Ki3pHrTgSAai/Gev +HyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y59PYjJbigapordwj6 +xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M7YNR +TOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924Sg +JPFI/2R80L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV +7LXTWtiBmelDGDfrs7vRWGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl +6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjWHYbL +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD +VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG +A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw +WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz +IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout736G +jOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL2 +4CejQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7 +VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azTL818+FsuVbu/3ZL3pAzcMeGiAjEA/Jdm +ZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD +VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG +A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw +WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz +IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi +QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR +HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D +9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8 +p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL +BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ +SG9uZyBLb25nMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25n +a29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2MDMwMjI5NDZaFw00MjA2MDMwMjI5 +NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtvbmcxEjAQBgNVBAcT +CUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMXSG9u +Z2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCziNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFO +dem1p+/l6TWZ5Mwc50tfjTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mI +VoBc+L0sPOFMV4i707mV78vH9toxdCim5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV +9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOesL4jpNrcyCse2m5FHomY +2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj0mRiikKY +vLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+Tt +bNe/JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZb +x39ri1UbSsUgYT2uy1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+ +l2oBlKN8W4UdKjk60FSh0Tlxnf0h+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YK +TE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsGxVd7GYYKecsAyVKvQv83j+Gj +Hno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwIDAQABo2MwYTAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e +i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEw +DQYJKoZIhvcNAQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG +7BJ8dNVI0lkUmcDrudHr9EgwW62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCk +MpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWldy8joRTnU+kLBEUx3XZL7av9YROXr +gZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov+BS5gLNdTaqX4fnk +GMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDceqFS +3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJm +Ozj/2ZQw9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+ +l6mc1X5VTMbeRRAc6uk7nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6c +JfTzPV4e0hz5sy229zdcxsshTrD3mUcYhcErulWuBurQB7Lcq9CClnXO0lD+mefP +L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa +LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG +mpv0 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE +AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw +CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ +BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND +VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb +qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY +HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo +G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA +lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr +IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/ +0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH +k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47 +4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO +m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa +cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl +uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI +KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls +ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG +AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT +VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG +CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA +cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA +QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA +7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA +cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA +QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA +czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu +aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt +aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud +DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF +BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp +D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU +JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m +AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD +vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms +tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH +7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA +h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF +d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H +pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx +CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ +WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ +BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG +Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/ +yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf +BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz +WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF +tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z +374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC +IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL +mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7 +wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS +MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2 +ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet +UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H +YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3 +LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1 +RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM +LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf +77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N +JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm +fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp +6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp +1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B +9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok +RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv +uu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw +CQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw +FgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S +Q00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4MTIyMDA5MzczM1oXDTQzMTIyMDA5 +MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQtUkNNMQ4wDAYDVQQL +DAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNBQyBS +QUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LH +sbI6GA60XYyzZl2hNPk2LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oK +Um8BA06Oi6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqGSM49BAMDA2kAMGYCMQCu +SuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoDzBOQn5IC +MQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJy +v+c= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX +DTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl +ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv +b3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP +cPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW +IkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX +xz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy +KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR +9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az +5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8 +6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7 +Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP +bMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt +BznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt +XUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd +INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD +U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp +LiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8 +Ipf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp +gZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh +/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw +0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A +fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq +4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR +1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/ +QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM +94B7IWcnMFk= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx +GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp +bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w +KwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0 +BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy +dW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG +EwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll +IEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU +QUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT +TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg +LSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7 +a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr +LqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr +N3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X +YacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/ +iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f +AJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH +V8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf +IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4 +lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c +8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf +lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICWjCCAeGgAwIBAgIQMWjZ2OFiVx7SGUSI5hB98DAKBggqhkjOPQQDAzBvMQsw +CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh +cmNoIEluc3RpdHV0aW9ucyBDQTEnMCUGA1UEAwweSEFSSUNBIENsaWVudCBFQ0Mg +Um9vdCBDQSAyMDIxMB4XDTIxMDIxOTExMDMzNFoXDTQ1MDIxMzExMDMzM1owbzEL +MAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgQ0ExJzAlBgNVBAMMHkhBUklDQSBDbGllbnQgRUND +IFJvb3QgQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABAcYrZWWlNBcD4L3 +KkD6AsnJPTamowRqwW2VAYhgElRsXKIrbhM6iJUMHCaGNkqJGbcY3jvoqFAfyt9b +v0mAFdvjMOEdWscqigEH/m0sNO8oKJe8wflXhpWLNc+eWtFolaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUUgjSvjKBJf31GpfsTl8au1PNkK0wDgYDVR0P +AQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMEwxRUZPqOa+w3eyGhhLLYh7WOar +lGtEA7AX/9+Cc0RRLP2THQZ7FNKJ7EAM7yEBLgIwL8kuWmwsHdmV4J6wuVxSfPb4 +OMou8dQd8qJJopX4wVheT/5zCu8xsKsjWBOMi947 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFqjCCA5KgAwIBAgIQVVL4HtsbJCyeu5YYzQIoPjANBgkqhkiG9w0BAQsFADBv +MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEnMCUGA1UEAwweSEFSSUNBIENsaWVudCBS +U0EgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTg0NloXDTQ1MDIxMzEwNTg0NVow +bzELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBS +ZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJzAlBgNVBAMMHkhBUklDQSBDbGllbnQg +UlNBIFJvb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +AIHbV0KQLHQ19Pi4dBlNqwlad0WBc2KwNZ/40LczAIcTtparDlQSMAe8m7dI19EZ +g66O2KnxqQCEsIxenugMj1Rpv/bUCE8mcP4YQWMaszKLQPgHq1cx8MYWdmeatN0v +8tFrxdCShJFxbg8uY+kfU6TdUhPMCYMpgQzFU3VEsQ5nUxjQwx+IS5+UJLQpvLvo +Tv1v0hUdSdyNcPIRGiBRVRG6iG/E91B51qox4oQ9XjLIdypQceULL+m26u+rCjM5 +Dv2PpWdDgo6YaQkJG0DNOGdH6snsl3ES3iT1cjzR90NMJveQsonpRUtVPTEFekHi +lbpDwBfFtoU9GY1kcPNbrM2f0yl1h0uVZ2qm+NHdvJCGiUMpqTdb9V2wJlpTQnaQ +K8+eVmwrVM9cmmXfW4tIYDh8+8ULz3YEYwIzKn31g2fn+sZD/SsP1CYvd6QywSTq +ZJ2/szhxMUTyR7iiZkGh+5t7vMdGanW/WqKM6GpEwbiWtcAyCC17dDVzssrG/q8R +chj258jCz6Uq6nvWWeh8oLJqQAlpDqWW29EAufGIbjbwiLKd8VLyw3y/MIk8Cmn5 +IqRl4ZvgdMaxhZeWLK6Uj1CmORIfvkfygXjTdTaefVogl+JSrpmfxnybZvP+2M/u +vZcGHS2F3D42U5Z7ILroyOGtlmI+EXyzAISep0xxq0o3AgMBAAGjQjBAMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFKDWBz1eJPd7oEQuJFINGaorBJGnMA4GA1Ud +DwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEADUf5CWYxUux57sKo8mg+7ZZF +yzqmmGM/6itNTgPQHILhy9Pl1qtbZyi8nf4MmQqAVafOGyNhDbBX8P7gyr7mkNuD +LL6DjvR5tv7QDUKnWB9p6oH1BaX+RmjrbHjJ4Orn5t4xxdLVLIJjKJ1dqBp+iObn +K/Es1dAFntwtvTdm1ASip62/OsKoO63/jZ0z4LmahKGHH3b0gnTXDvkwSD5biD6q +XGvWLwzojnPCGJGDObZmWtAfYCddTeP2Og1mUJx4e6vzExCuDy+r6GSzGCCdRjVk +JXPqmxBcWDWJsUZIp/Ss1B2eW8yppRoTTyRQqtkbbbFA+53dWHTEwm8UcuzbNZ+4 +VHVFw6bIGig1Oq5l8qmYzq9byTiMMTt/zNyW/eJb1tBZ9Ha6C8tPgxDHQNAdYOkq +5UhYdwxFab4ZcQQk4uMkH0rIwT6Z9ZaYOEgloRWwG9fihBhb9nE1mmh7QMwYXAwk +ndSV9ZmqRuqurL/0FBkk6Izs4/W8BmiKKgwFXwqXdafcfsD913oY3zDROEsfsJhw +v8x8c/BuxDGlpJcdrL/ObCFKvicjZ/MGVoEKkY624QMFMyzaNAhNTlAjrR+lxdR6 +/uoJ7KcoYItGfLXqm91P+edrFcaIz0Pb5SfcBFZub0YV8VYt6FwMc8MjgTggy8kM +ac8sqzuEYDMZUv1pFDM= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw +CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh +cmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v +dCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoXDTQ1MDIxMzExMDEwOVowbDELMAkG +A1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj +aCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJvb3Qg +Q0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7 +KKrxcm1lAEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9Y +STHMmE5gEYd103KUkE+bECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQD +AgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAircJRQO9gcS3ujwLEXQNw +SaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/QwCZ61IygN +nxS2PFOiTAZpffpskcYqSUXm7LcT4Tps +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs +MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg +Um9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUzOFoXDTQ1MDIxMzEwNTUzN1owbDEL +MAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNBIFJv +b3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569l +mwVnlskNJLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE +4VGC/6zStGndLuwRo0Xua2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uv +a9of08WRiFukiZLRgeaMOVig1mlDqa2YUlhu2wr7a89o+uOkXjpFc5gH6l8Cct4M +pbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K5FrZx40d/JiZ+yykgmvw +Kh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEvdmn8kN3b +LW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcY +AuUR0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqB +AGMUuTNe3QvboEUHGjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYq +E613TBoYm5EPWNgGVMWX+Ko/IIqmhaZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHr +W2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQCPxrvrNQKlr9qEgYRtaQQJKQ +CoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAU +X15QvWiWkKQUEapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3 +f5Z2EMVGpdAgS1D0NTsY9FVqQRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxaja +H6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxDQpSbIPDRzbLrLFPCU3hKTwSUQZqP +JzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcRj88YxeMn/ibvBZ3P +zzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5vZSt +jBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0 +/L5H9MG0qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pT +BGIBnfHAT+7hOtSLIBD6Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79 +aPib8qXPMThcFarmlwDB31qlpzmq6YR/PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YW +xw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnnkf3/W9b3raYvAwtt41dU +63ZTGI0RmLo= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN +BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl +bGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv +b3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ +BgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj +YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5 +MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0 +dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg +QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa +jq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi +C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep +lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof +TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix +DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k +IFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT +N0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9v +dENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAxMTIxWjCBpjELMAkG +A1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNh +ZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkx +QDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 +dGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA +4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0 +AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe10 +4S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06C +ojXdFPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV +9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrD +gfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6 +Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZYeCsargq +NhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko +LfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVd +ctA4GGqd83EkVAswDQYJKoZIhvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0I +XtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMI +M4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrMd/K4kPFox/la/vot +9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+yd+2V +Z5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/ea +j8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh +X9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQ +l033DlZdwJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdf +bzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4 +pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK +e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0 +vm9qp/UsQu0yrbYhnr68 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu +VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw +MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw +JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT +3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU ++ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp +S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1 +bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi +T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL +vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK +Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK +dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT +c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv +l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N +iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD +ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt +LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93 +nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3 ++wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK +W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT +AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq +l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG +4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ +mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A +7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu +VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN +MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0 +MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7 +ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy +RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS +bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF +/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R +3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw +EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy +9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V +GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ +2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV +WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD +W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN +AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV +DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9 +TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G +lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW +mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df +WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5 ++bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ +tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA +GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv +8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw +CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg +R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00 +MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT +ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW ++1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9 +ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI +zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW +tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1 +/q4AaOeMSQ+2b1tbFfLn +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4 +MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6 +ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD +VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j +b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq +scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO +xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H +LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX +uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD +yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+ +JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q +rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN +BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L +hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB +QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+ +HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu +Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg +QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB +BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA +A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb +laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56 +awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo +JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw +LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT +VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk +LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb +UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/ +QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+ +naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls +QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6 +ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw +NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L +cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg +Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN +QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT +3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw +3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6 +3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5 +BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN +XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF +AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw +8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG +nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP +oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy +d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg +LvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFmDCCA4CgAwIBAgIEVRpusTANBgkqhkiG9w0BAQsFADBDMQswCQYDVQQGEwJa +QTERMA8GA1UEChMITEFXdHJ1c3QxITAfBgNVBAMTGExBV3RydXN0IFJvb3QgQ0Ey +ICg0MDk2KTAgFw0yMzAyMTQwOTE5MzhaGA8yMDUzMDIxNDA5NDkzOFowQzELMAkG +A1UEBhMCWkExETAPBgNVBAoTCExBV3RydXN0MSEwHwYDVQQDExhMQVd0cnVzdCBS +b290IENBMiAoNDA5NikwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM +F8srQ7ps+cmTimUNEkzsJxS3E3ng1NUtGFbx+eoqEBZObETHamVG85qJNdGH+DOJ +L4gJGpIQkZDBa58Obn8mihNdGKxoAQ0QeGVw2I6PhFqXMBjQEQ5KjVIQpYErUSj1 +Y8S27ECzAeWtd73lOO+8jbPdGaB7DY2022r7JTNa+pGvxHFFMPiIKXvLv9W6JwSO +3bIA98pcmTUU6v11BhUIu8pXaPs/+7Q0c2PR1ePIOFppfWp6RAwNik7tkh0Qjzsi +LLbf7cXG8Il5VGVeXxu9j33fubft6+TFB9FnPJU7kf5CelJAgATSOVdL9JJ9/5vv +5Z3JCbKREjimKQg7ruvKzO1N504hAQf8bzLOaYyEUsZ36icwCt6lrzAraB+s1Owh +rSJJds4PwvIHKvlqEoOaOwSuGXr+oYYk+kFeJXxArCe24yk2bzXiV9AZWN//ZPbD +AUl22yu+vLlPFArVG1gh9hwuAHz4lLXLNxoU5DK5FtRg7AWqXzL6aiMSrNQQu9Ki +grRLDotwJ6rWB8FniPqEwwjJioTI0jdygQ+NFkrk1zVRpTgPjIRLlTbA9ded4F2P +q5HuAAi5nVIf7PiZu3lWsUna0uXYYYtbr/CrN8V7Go6Gvn7FexUeYWjoC4eLc0mh +F3N+KXiOyuBBL3VzdKKXOn/3LnQJuExgi0Y2GRAtnQIDAQABo4GRMIGOMA8GA1Ud +EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMCsGA1UdEAQkMCKADzIwMjMwMjE0 +MDkxOTM4WoEPMjA1MzAyMTQwOTQ5MzhaMB8GA1UdIwQYMBaAFNfWVmJcPxeB5nNE +KfVRBe8LYDesMB0GA1UdDgQWBBTX1lZiXD8XgeZzRCn1UQXvC2A3rDANBgkqhkiG +9w0BAQsFAAOCAgEASZwp/j3snkV/qz48/iNvNz53p1P/eJ/8SUSAV2acbtp5/81F +rUyTv7VZxukQt+X4jPuHxR6L2LM/ApYKu4qO79e0wIMgOJdZRWT89ncT8gnXocg4 +dAjq+UhM+h8EnLT/7G5WNnKTbJU+LF/eDwurycwVPhaPZvyyELih0bTewGMZzO9T +qnU2IoslH7+byNfBX+ymNwmqe2K89iIt8dZY3Yy7UvQLp3apensajdytmoFiLoYF +kHJHL6HJZ4SwDWywuJsWt9CZFC+cEpsjqI2mQx7p5S3leKcfZJRktneyqFz7Casp +6x5tddH20MWlwx2fHvMaLbLIH+UoCm7zX/3a5iOhdpBcS5gBgizuRy0CGl9/NMVp +tXKtPvPPnm34KegRJyvgWQsbYetKymmlpNXNURuUjnnN3/audF2xLBuGU/7RMAZB +NAdigkz0fseHdA6wIR4JIIDBsxU9Rm3T8QaSP++glYocbncxtut4KQx77oKlT36k +KV6eqi34jsDz/A0GhZtO3PfiCXzQFFEeerMjr/rRYSpltQHZuOMHyiR20vBKvu+G +BIBCFXARaH7Xx7v+506bnJWlHEqkydAJjKrOSNIekpfXEentZsw33PXXG3SbpupC +rF0y4Fj0gUf/0hLifhzcSXaWwx2fS8pcKjdbPYrROJsh2uO/RUPT4Fh3Hyg= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV +BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk +LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv +b3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ +BgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg +THRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v +IFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv +xie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H +Wyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB +eAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo +jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ ++efcMQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICzzCCAjGgAwIBAgINAOhvGHvWOWuYSkmYCjAKBggqhkjOPQQDBDB1MQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xFzAVBgNVBGEMDlZBVEhVLTIzNTg0NDk3MSIwIAYDVQQDDBllLVN6aWdubyBU +TFMgUm9vdCBDQSAyMDIzMB4XDTIzMDcxNzE0MDAwMFoXDTM4MDcxNzE0MDAwMFow +dTELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRYwFAYDVQQKDA1NaWNy +b3NlYyBMdGQuMRcwFQYDVQRhDA5WQVRIVS0yMzU4NDQ5NzEiMCAGA1UEAwwZZS1T +emlnbm8gVExTIFJvb3QgQ0EgMjAyMzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAE +AGgP36J8PKp0iGEKjcJMpQEiFNT3YHdCnAo4YKGMZz6zY+n6kbCLS+Y53wLCMAFS +AL/fjO1ZrTJlqwlZULUZwmgcAOAFX9pQJhzDrAQixTpN7+lXWDajwRlTEArRzT/v +SzUaQ49CE0y5LBqcvjC2xN7cS53kpDzLLtmt3999Cd8ukv+ho2MwYTAPBgNVHRMB +Af8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUWYQCYlpGePVd3I8K +ECgj3NXW+0UwHwYDVR0jBBgwFoAUWYQCYlpGePVd3I8KECgj3NXW+0UwCgYIKoZI +zj0EAwQDgYsAMIGHAkIBLdqu9S54tma4n7Zwf2Z0z+yOfP7AAXmazlIC58PRDHpt +y7Ve7hekm9sEdu4pKeiv+62sUvTXK9Z3hBC9xdIoaDQCQTV2WnXzkoYI9bIeCvZl +C9p2x1L/Cx6AcCIwwzPbGO2E14vs7dOoY4G1VnxHx1YwlGhza9IuqbnZLBwpvQy6 +uWWL +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G +CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y +OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx +FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp +Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP +kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc +cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U +fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7 +N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC +xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1 ++rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM +Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG +SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h +mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk +ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c +2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t +HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD +VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw +MTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4MjMxNjA0WjBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNy +b3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZR +ogPZnZH6thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYb +hGBKia/teQ87zvH2RPUBeMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3 +FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV +L8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB +iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl +MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N +aWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ +Nt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0 +ZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1 +HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm +gGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ +jEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc +aDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG +YaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6 +W6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K +UGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH ++FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q +W5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC +LgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC +gMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6 +tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh +SnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2 +TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3 +pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR +xpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp +GWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9 +dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN +AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB +RA+GsCyRxj3qrg+E +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM +BQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG +T1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4NDJaFw0zNzA4MTgyMzU5NTlaMGkx +CzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVTUyBQTEFURk9STSBD +b3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVA +iQqrDZBbUGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH +38dq6SZeWYp34+hInDEW+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lE +HoSTGEq0n+USZGnQJoViAbbJAh2+g1G7XNr4rRVqmfeSVPc0W+m/6imBEtRTkZaz +kVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2aacp+yPOiNgSnABIqKYP +szuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4Yb8Obtoq +vC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHf +nZ3zVHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaG +YQ5fG8Ir4ozVu53BA0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo +0es+nPxdGoMuK8u180SdOqcXYZaicdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3a +CJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejyYhbLgGvtPe31HzClrkvJE+2K +AQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNVHQ4EFgQU0p+I +36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoN +qo0hV4/GPnrK21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatj +cu3cvuzHV+YwIHHW1xDBE1UBjCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm ++LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bxhYTeodoS76TiEJd6eN4MUZeoIUCL +hr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTgE34h5prCy8VCZLQe +lHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTHD8z7 +p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8 +piKCk5XQA76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLR +LBT/DShycpWbXgnbiUSYqqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX +5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oGI/hGoiLtk/bdmuYqh7GYVPEi92tF4+KO +dh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul +9XXeifdy +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG +EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 +MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl +cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR +dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB +pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM +b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm +aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz +IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT +lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz +AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5 +VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG +ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2 +BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG +AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M +U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh +bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C ++C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F +uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 +XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICNDCCAbqgAwIBAgIQVOyX1ou0xAshbg6y0FPIejAKBggqhkjOPQQDAzBLMQsw +CQYDVQQGEwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UEAwwY +T0lTVEUgQ2xpZW50IFJvb3QgRUNDIEcxMB4XDTIzMDUzMTE0MzE0MFoXDTQ4MDUy +NDE0MzEzOVowSzELMAkGA1UEBhMCQ0gxGTAXBgNVBAoMEE9JU1RFIEZvdW5kYXRp +b24xITAfBgNVBAMMGE9JU1RFIENsaWVudCBSb290IEVDQyBHMTB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABIhOaB/Jnr46BFsVwzX0zFDFCK04bqg80gK6zKsl/XVA/WcZ +nxsKXfbLFnv5XB6C3BVE1Jw8bWGTRfRPz2K53z5TjZrUSt6Iqgum8dRh1h501Riy +xU1M74B77A3rgzlUlqNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSZ +Vzs5sS0AjCFmjJVpnG117Iw/+jAdBgNVHQ4EFgQUmVc7ObEtAIwhZoyVaZxtdeyM +P/owDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMQCW/+SCThYiW6CF +GDw9Oo8gBggl5/WRNhmte7TfW2YSN3Nw7c0FKAdeCM4NQl8ZkQICMGdJh64GQR0g +0zGmqiY38SeKYQ3+mgZDpy6eJkejMhiL6F5QBfGwekh23tuhYkq6dw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIQNBdvWQGIG6ql3chIu7Q7czANBgkqhkiG9w0BAQwFADBL +MQswCQYDVQQGEwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UE +AwwYT0lTVEUgQ2xpZW50IFJvb3QgUlNBIEcxMB4XDTIzMDUzMTE0MjMyOVoXDTQ4 +MDUyNDE0MjMyOFowSzELMAkGA1UEBhMCQ0gxGTAXBgNVBAoMEE9JU1RFIEZvdW5k +YXRpb24xITAfBgNVBAMMGE9JU1RFIENsaWVudCBSb290IFJTQSBHMTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBALpP/v5UE7WEPLzg0zHxHW7cxFNx+uQ5 +UUN2fZIfgX8Aa0HC5trcGE1sF1lwCTNi7GmILbDdWflhYGBW8ba07+uH0BP+w89v +j345WFGziQKOVJUeIl+rKAVDJ/hF9AlCJpT+vRN4u5HyEBCcDWd82mQg63owGrpI +DXhUKpkxNKvLpmrnDGc5ZqQmqCco5/PmPHPkK8xvMS4TdGHLaObSM85SvH5lJFoh +gTFDqrKc0RjnYTxSr4CJ6TRG3vlNmVptHb3GJdGTVY74J5JDOoyVRUDjiRinhsFZ +mMrbJhwTwIyBuZiwrWmtbhjje2JB9a02/gu0eyBfn6lu+ZmCElLSisRUeLR890Gb +A+cHXrPCuUlkZ5IWxGCQDrCCfTOt0Dbq0XZrfIhHmKwb+bRQjGGBadgx8436PvL1 +S6/Owx3vXygb6xjWoFhSMr5Cb81JlyLBcLnT42BP3oOCoE4wvXNTwr0X/aDAmI/q +DhcH5kOVIE7bEaj549O4J0cMJ9sS64FVzHXbn9MXQ8T764oobemvRFBaQ/vxOeKT +UM+Y/ESWWDilpe1Fw1JCBafv5TykrD3n1qlWBaqww6cZ5OU911dEbZQRH8pwyPy5 +TMxBWoN0U5B4z9bULk+xqk0u9dEIWzpk78inqHph7Oym1YhOtlTUWJHCJWSRvAoU +PZIUmrULBukvAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU +KYIlNQo6vpIr5AkD5OyPjThyOcswHQYDVR0OBBYEFCmCJTUKOr6SK+QJA+Tsj404 +cjnLMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQwFAAOCAgEAbSOGwv/14MjA +VYpgMcyXQ0dwQ9Pj7FL608Ke+4kyGspGk08Elyvb0JyEDZUHQlT+72kh35IDLo83 +ISN3qXc3bKDErpynWDlKFZdiRoNRIO0/wqPxw2In0KwTHv48Uh2Q1WPxqV7qf+fn +65ZaUezUqRvjDJRmrMuIkkm+c1yK4Gq8poHNs1zUI5LITfkgjHCUS2ht8o8ebDX3 +6F/U170gN1Jm/yu7SWa3cagsX3MPB5LnTl+lBtvJijyXxULqfQ+BG1frngwP/6Mn +IElTprM6TMttMDXa8vCa/lDfbVwkPU13an2GX0zQ4aa0rgQTAZDxgGiEB5SCB4Pr +keWTDnWRrqMjIElk1Lo5lldw7lU0KHzWr8qpnubJAckHwdBEsYC0UVCqj/ac5Wdz +0BvqgzUXL1DG3lbHu6MDy+KhGOj4zlEGo9IDQGEap2dXg/zRErkoqtpOa9Wc2IU3 +2r0i1zRZnBqmznjWlHgHBg+xkyGgSccQngquUXca+XGQw62YD4opamABqk+tIAMt +ao6jC2rW/ZMMimHLvSjxX3H9uDM51krx9rJoUj5lj0OdgSQk9ihMNaf9MwqleMEE +H+xJasSu1UQWpqeNf9ohlj6ouhZn1Kmh58Ka+BDZO5ruaPYvAO7Lu2aNIjiG9L9f +eKnIoB1au3VQ+VILDx0CLBQa84dqd/M= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICNTCCAbqgAwIBAgIQI/nD1jWvjyhLH/BU6n6XnTAKBggqhkjOPQQDAzBLMQsw +CQYDVQQGEwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UEAwwY +T0lTVEUgU2VydmVyIFJvb3QgRUNDIEcxMB4XDTIzMDUzMTE0NDIyOFoXDTQ4MDUy +NDE0NDIyN1owSzELMAkGA1UEBhMCQ0gxGTAXBgNVBAoMEE9JU1RFIEZvdW5kYXRp +b24xITAfBgNVBAMMGE9JU1RFIFNlcnZlciBSb290IEVDQyBHMTB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABBcv+hK8rBjzCvRE1nZCnrPoH7d5qVi2+GXROiFPqOujvqQy +cvO2Ackr/XeFblPdreqqLiWStukhEaivtUwL85Zgmjvn6hp4LrQ95SjeHIC6XG4N +2xml4z+cKrhAS93mT6NjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBQ3 +TYhlz/w9itWj8UnATgwQb0K0nDAdBgNVHQ4EFgQUN02IZc/8PYrVo/FJwE4MEG9C +tJwwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2kAMGYCMQCpKjAd0MKfkFFR +QD6VVCHNFmb3U2wIFjnQEnx/Yxvf4zgAOdktUyBFCxxgZzFDJe0CMQCSia7pXGKD +YmH5LVerVrkR3SW+ak5KGoJr3M/TvEqzPNcum9v4KGm8ay3sMaE641c= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIQVaXZZ5Qoxu0M+ifdWwFNGDANBgkqhkiG9w0BAQwFADBL +MQswCQYDVQQGEwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UE +AwwYT0lTVEUgU2VydmVyIFJvb3QgUlNBIEcxMB4XDTIzMDUzMTE0MzcxNloXDTQ4 +MDUyNDE0MzcxNVowSzELMAkGA1UEBhMCQ0gxGTAXBgNVBAoMEE9JU1RFIEZvdW5k +YXRpb24xITAfBgNVBAMMGE9JU1RFIFNlcnZlciBSb290IFJTQSBHMTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAKqu9KuCz/vlNwvn1ZatkOhLKdxVYOPM +vLO8LZK55KN68YG0nnJyQ98/qwsmtO57Gmn7KNByXEptaZnwYx4M0rH/1ow00O7b +rEi56rAUjtgHqSSY3ekJvqgiG1k50SeH3BzN+Puz6+mTeO0Pzjd8JnduodgsIUzk +ik/HEzxux9UTl7Ko2yRpg1bTacuCErudG/L4NPKYKyqOBGf244ehHa1uzjZ0Dl4z +O8vbUZeUapU8zhhabkvG/AePLhq5SvdkNCncpo1Q4Y2LS+VIG24ugBA/5J8bZT8R +tOpXaZ+0AOuFJJkk9SGdl6r7NH8CaxWQrbueWhl/pIzY+m0o/DjH40ytas7ZTpOS +jswMZ78LS5bOZmdTaMsXEY5Z96ycG7mOaES3GK/m5Q9l3JUJsJMStR8+lKXHiHUh +sd4JJCpM4rzsTGdHwimIuQq6+cF0zowYJmXa92/GjHtoXAvuY8BeS/FOzJ8vD+Ho +mnqT8eDI278n5mUpezbgMxVz8p1rhAhoKzYHKyfMeNhqhw5HdPSqoBNdZH702xSu ++zrkL8Fl47l6QGzwBrd7KJvX4V84c5Ss2XCTLdyEr0YconosP4EmQufU2MVshGYR +i3drVByjtdgQ8K4p92cIiBdcuJd5z+orKu5YM+Vt6SmqZQENghPsJQtdLEByFSnT +kCz3GkPVavBpAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU +8snBDw1jALvsRQ5KH7WxszbNDo0wHQYDVR0OBBYEFPLJwQ8NYwC77EUOSh+1sbM2 +zQ6NMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQwFAAOCAgEANGd5sjrG5T33 +I3K5Ce+SrScfoE4KsvXaFwyihdJ+klH9FWXXXGtkFu6KRcoMQzZENdl//nk6HOjG +5D1rd9QhEOP28yBOqb6J8xycqd+8MDoX0TJD0KqKchxRKEzdNsjkLWd9kYccnbz8 +qyiWXmFcuCIzGEgWUOrKL+mlSdx/PKQZvDatkuK59EvV6wit53j+F8Bdh3foZ3dP +AGav9LEDOr4SfEE15fSmG0eLy3n31r8Xbk5l8PjaV8GUgeV6Vg27Rn9vkf195hfk +gSe7BYhW3SCl95gtkRlpMV+bMPKZrXJAlszYd2abtNUOshD+FKrDgHGdPY3ofRRs +YWSGRqbXVMW215AWRqWFyp464+YTFrYVI8ypKVL9AMb2kI5Wj4kI3Zaq5tNqqYY1 +9tVFeEJKRvwDyF7YZvZFZSS0vod7VSCd9521Kvy5YhnLbDuv0204bKt7ph6N/Ome +/msVuduCmsuY33OhkKCgxeDoAaijFJzIwZqsFVAzje18KotzlUBDJvyBpCpfOZC3 +J8tRd/iWkx7P8nd9H0aTolkelUTFLXVksNb54Dxp6gS1HAviRkRNQzuXSXERvSS2 +wq1yVAb+axj5d9spLFKebXd7Yv0PTY6YMjAwcRLWJTXjn/hvnLXrahut6hDTlhZy +BiElxky8j3C7DOReIoMt0r7+hVu05L0= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB +ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly +aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl +ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w +NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G +A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD +VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX +SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR +VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2 +w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF +mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg +4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9 +4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw +EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx +SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2 +ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8 +vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa +hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi +Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ +/L7fCg0= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt +MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg +Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i +YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x +CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG +b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3 +HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx +WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX +1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk +u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P +99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r +M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB +BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh +cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5 +gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO +ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf +aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw +CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91 +bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg +Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ +BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu +ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS +b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni +eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W +p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T +rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV +57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg +Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYT +AkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYD +VQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYx +NjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTELMAkGA1UEBhMCSlAxJTAjBgNVBAoT +HFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNVBAMTIlNlY3VyaXR5 +IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+Cnnfdl +dB9sELLo5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpK +ULGjQjBAMB0GA1UdDgQWBBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu +9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3LsnNdo4gIxwwCMQDAqy0O +be0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70eN9k= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl +MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe +U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX +DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy +dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj +YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV +OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr +zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM +VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ +hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO +ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw +awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs +OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF +coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc +okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8 +t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy +1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/ +SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb +MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow +GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj +YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM +GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua +BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe +3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 +YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR +rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm +ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU +oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v +QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t +b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF +AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q +GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 +G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi +l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 +smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB +gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV +BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw +MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl +YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P +RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3 +UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI +2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8 +Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp ++2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+ +DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O +nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW +/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g +PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u +QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY +SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv +IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4 +zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd +BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB +ZQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT +IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw +MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy +ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N +T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR +FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J +cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW +BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm +fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv +GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB +hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV +BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT +EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR +6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X +pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC +9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV +/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf +Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z ++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w +qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah +SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC +u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf +Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq +crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl +wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM +4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV +2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna +FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ +CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK +boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke +jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL +S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb +QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl +0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB +NVOFBkpdn627G190 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0 +Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW +KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw +NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw +NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy +ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV +BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo +Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4 +4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9 +KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI +rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi +94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB +sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi +gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo +kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE +vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t +O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua +AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP +9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/ +eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m +0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG +A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3 +d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu +dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq +RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy +MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD +VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 +L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g +Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi +A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt +ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH +Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC +R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX +hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50 +cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs +IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz +dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy +NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu +dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt +dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0 +aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T +RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN +cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW +wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1 +U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0 +jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN +BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/ +jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v +1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R +nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH +VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAw +gb4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQL +Ex9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykg +MjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAw +BgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0 +MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYTAlVT +MRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1 +c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJ +bmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3Qg +Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3DumSXbcr3DbVZwbPLqGgZ +2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV3imz/f3E +T+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j +5pds8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAM +C1rlLAHGVK/XqsEQe9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73T +DtTUXm6Hnmo9RR3RXRv06QqsYJn7ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNX +wbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5XxNMhIWNlUpEbsZmOeX7m640A +2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV7rtNOzK+mndm +nqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8 +dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwl +N4y6mACXi0mWHv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNj +c0kCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9nMA0GCSqGSIb3DQEBCwUAA4ICAQAS +5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4QjbRaZIxowLByQzTS +Gwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht7LGr +hFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/ +B7NTeLUKYvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uI +AeV8KEsD+UmDfLJ/fOPtjqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbw +H5Lk6rWS02FREAutp9lfx1/cH6NcjKF+m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+ +b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKWRGhXxNUzzxkvFMSUHHuk +2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjAJOgc47Ol +IQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk +5F6G+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuY +n/PIjhs4ViFqUZPTkcpG2om3PVODLAgfi49T3f+sHw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML +RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp +bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 +IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3 +MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 +LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp +YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG +A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq +K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe +sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX +MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT +XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/ +HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH +4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub +j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo +U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b +u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+ +bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er +fF6adulZkMV8gzURZVE= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICMTCCAbegAwIBAgIQbvXTp0GOoFlApzBr0kBlVjAKBggqhkjOPQQDAzBaMQsw +CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTEwLwYDVQQDEyhT +ZWN0aWdvIFB1YmxpYyBFbWFpbCBQcm90ZWN0aW9uIFJvb3QgRTQ2MB4XDTIxMDMy +MjAwMDAwMFoXDTQ2MDMyMTIzNTk1OVowWjELMAkGA1UEBhMCR0IxGDAWBgNVBAoT +D1NlY3RpZ28gTGltaXRlZDExMC8GA1UEAxMoU2VjdGlnbyBQdWJsaWMgRW1haWwg +UHJvdGVjdGlvbiBSb290IEU0NjB2MBAGByqGSM49AgEGBSuBBAAiA2IABLinUpT1 +PgWwG/YfsdN+ueQFZlSAzmylaH3kU1LbgvrEht9DePfIrRa8P3gyy2vTSdZE5bN+ +n3umxizy4rbTibCaPEvOiUvGxss6SWAPRrxtTnqcyZuFewq2sEfCiOPU0aNCMEAw +HQYDVR0OBBYEFC1OjKfCI7JXqQZrPmsrifPDXkfOMA4GA1UdDwEB/wQEAwIBhjAP +BgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQCSnRpZY0VYjhsW5H16 +bDZIMB8rcueQMzT9JKLGBoxvOzJXWvj+xkkSU5rZELKZUXICMAUlKjMh/JPmIqLM +cFUoNVaiB8QhhCMaTEyZUJmSFMtK3Fb79dOPaiz1cTr4izsDng== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFgDCCA2igAwIBAgIQHUSeuQ2DkXSu3fLriLemozANBgkqhkiG9w0BAQwFADBa +MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTEwLwYDVQQD +EyhTZWN0aWdvIFB1YmxpYyBFbWFpbCBQcm90ZWN0aW9uIFJvb3QgUjQ2MB4XDTIx +MDMyMjAwMDAwMFoXDTQ2MDMyMTIzNTk1OVowWjELMAkGA1UEBhMCR0IxGDAWBgNV +BAoTD1NlY3RpZ28gTGltaXRlZDExMC8GA1UEAxMoU2VjdGlnbyBQdWJsaWMgRW1h +aWwgUHJvdGVjdGlvbiBSb290IFI0NjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC +AgoCggIBAJHlG/qqbTcrdccuXxSl2yyXtixGj2nZ7JYt8x1avtMdI+ZoCf9KEXMa +rmefdprS5+y42V8r+SZWUa92nan8F+8yCtAjPLosT0eD7J0FaEJeBuDV6CtoSJey ++vOkcTV9NJsXi39NDdvcTwVMlGK/NfovyKccZtlxX+XmWlXKq/S4dxlFUEVOSqvb +nmbBGbc3QshWpUAS+TPoOEU6xoSjAo4vJLDDQYUHSZzP3NHyJm/tMxwzZypFN9mF +ZSIasbUQUglrA8YfcD2RxH2QPe1m+JD/JeDtkqKLMSmtnBJmeGOdV+z7C96O3IvL +Oql39Lrl7DiMi+YTZqdpWMOCGhrN8Z/YU5JOSX2pRefxQyFatz5AzWOJz9m/x1AL +4bzniJatntQX2l3P4JH9phDUuQOBm2ms+4SogTXrG+tobHxgPsPfybSudB1Ird1u +EYbhKmo2Fq7IzrzbWPxAk0DYjlOXwqwiOOWIMbMuoe/s4EIN6v+TVkoGpJtMAmhk +j1ZQwYEF/cvbxdcV8mu1dsOj+TLOyrVKqRt9Gdx/x2p+ley2uI39lUqcoytti/Fw +5UcrAFzkuZ7U+NlYKdDL4ChibK6cYuLMvDaTQfXv/kZilbBXSnQsR1Ipnd2ioU9C +wpLOLVBSXowKoffYncX4/TaHTlf9aKFfmYMc8LXd6JLTZUBVypaFAgMBAAGjQjBA +MB0GA1UdDgQWBBSn15V360rDJ82TvjdMJoQhFH1dmDAOBgNVHQ8BAf8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQwFAAOCAgEANNLxFfOTAdRyi/Cr +CB8TPHO0sKvoeNlsupqvJuwQgOUNUzHd4/qMUSIkMze4GH46+ljoNOWM4KEfCUHS +Nz/Mywk1Qojp/BHXz0KqpHC2ccFTvcV0r8QiJGPPYoJ9yctRwYiQbVtcvvuZqLq2 +hrDpZgvlG2uv6iuGp9+oI0yWP09XQhgVg0Pxhia3KgPOC53opWgejG+9heMbUY/n +Fy8r0NZ4wi3dcojUZZ76mdR+55cKkgGapamEOgwqdD0zGMiH9+ik9YZCOf1rdSn8 +AAasoqUaVI7pUEkXZq9LBC2blIClVKuMVxdEnw/WaGRytEseAcfZm5TZg5mvEgUR +o5gi0vJXyiT5ujgVEki6Yzv8i5V41nIHVszN/J0c0MVkO2M0zwSZircweXq28sbV +2VR6hwt+TveE7BTziBYS8dWuChoJ7oat5av9rsMpeXTDAV8Rm991mcZK95uPbEns +IS+0AlmzLdBykLoLFHR4S8/BX1VyjlQrE876WAzTuyzZqZFh+PjxtnvevKnMkgTM +S2tfc4C2Ie1QT9d2h27O39K3vWKhfVhiaEVStj/eEtvtBGmedoiqAW3ahsdgG8NS +rDfsUHGAciohRQpTRzwZ643SWQTeJbDrHzVvYH3Xtca7CyeN4E1U5c8dJgFuOzXI +IBKJg/DS7Vg7NJ27MfUy/THzVho= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQsw +CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T +ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcN +MjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYG +A1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT +ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccC +WvkEN/U0NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+ +6xnOQ6OjQjBAMB0GA1UdDgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8B +Af8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNnADBkAjAn7qRa +qCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RHlAFWovgzJQxC36oCMB3q +4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21USAGKcw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBf +MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD +Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw +HhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEY +MBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1Ymxp +YyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDa +ef0rty2k1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnz +SDBh+oF8HqcIStw+KxwfGExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xf +iOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMPFF1bFOdLvt30yNoDN9HWOaEhUTCDsG3X +ME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vuZDCQOc2TZYEhMbUjUDM3 +IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5QazYw6A3OAS +VYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgE +SJ/AwSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu ++Zd4KKTIRJLpfSYFplhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt +8uaZFURww3y8nDnAtOFr94MlI1fZEoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+L +HaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW6aWWrL3DkJiy4Pmi1KZHQ3xt +zwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWIIUkwDgYDVR0P +AQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c +mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQ +YKlJfp/imTYpE0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52 +gDY9hAaLMyZlbcp+nv4fjFg4exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZA +Fv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M0ejf5lG5Nkc/kLnHvALcWxxPDkjB +JYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI84HxZmduTILA7rpX +DhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9mpFui +TdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5 +dHn5HrwdVw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65 +LvKRRFHQV80MNNVIIb/bE/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp +0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmmJ1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAY +QqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl +eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT +JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg +VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo +I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng +o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G +A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB +zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW +RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx +MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg +Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ +iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa +/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ +jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI +HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7 +sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w +gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw +KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG +AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L +URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO +H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm +I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY +iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz +MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv +cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz +Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO +0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao +wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj +7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS +8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT +BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg +JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3 +6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/ +3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm +D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS +CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICQDCCAcagAwIBAgIQdvhIHq7wPHAf4D8lVAGD1TAKBggqhkjOPQQDAzBRMQsw +CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSgwJgYDVQQDDB9T +U0wuY29tIENsaWVudCBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzAzMloX +DTQ2MDgxOTE2MzAzMVowUTELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jw +b3JhdGlvbjEoMCYGA1UEAwwfU1NMLmNvbSBDbGllbnQgRUNDIFJvb3QgQ0EgMjAy +MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABC1Tfp+LPrM2ulDizOvcuiaK04wGP2cP +7/UX5dSumkYqQQEHaedncfHCAzbG8CtSjs8UkmikPnBREmmNeKKCyikUwOSUIrJE +kmBvyASkZ9Wi0PPQ1+qOPA+60kBHkDTufaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAf +BgNVHSMEGDAWgBS3/i1ixYFTzVIaL11goMNd+7IcHDAdBgNVHQ4EFgQUt/4tYsWB +U81SGi9dYKDDXfuyHBwwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUC +ME0HES0R+7kmwyHdcuEX/MHPFOpJznGHjtZT3BHNXVSKr9kt9IxR6rxmR+J/lYNg +ZQIxAIwhTE+75bBQ35BiSebMkdv4P11xkQiOT5LJf6Zc6hN+7W3E6MMqb1wR4aXz +alqaTQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFjzCCA3egAwIBAgIQdq/uiJMVRbZQU5uAnKTfmjANBgkqhkiG9w0BAQsFADBR +MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSgwJgYDVQQD +DB9TU0wuY29tIENsaWVudCBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzEw +N1oXDTQ2MDgxOTE2MzEwNlowUTELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBD +b3Jwb3JhdGlvbjEoMCYGA1UEAwwfU1NMLmNvbSBDbGllbnQgUlNBIFJvb3QgQ0Eg +MjAyMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALhY20Yw+8k/48jw +ATM04tpIqBjpIG6a1wHh1SmPMLQjauTLYrC+4p8gvT5UoDlox4Y3ZnQGBu90K9rc +n4SpUi+Q0u5+fPulIq1vcEZnlj0p1KO7VnsUBFnBIWNEHrIfElyQh2UNiPYeiCLi +Y1S78zb41n/c2v8pNanGbg5pWz/YvoKHFXBdsMdcEg9jpjjNz3O5ww6JJjcbP2Ic +MmnRm9n/VZAx3rFj3c/FdHf874ghU78AMRomLAAwpV9s4+T2AIrKmIecdAN6i2bs +fv2jjzUlXHils6T7PW2pivBsiIKL/UrQb+TXo7SONEk4vs5F5dIcyl7CNxSLzWZW +Mzed5WvsQ5JkoELadW/AFez5ab00uYp7+hb7Vf5SIOgEBFZWZfU3RJjIikbpt6y4 +6L5ijlQ2W/c7cL9d7i26X95CGYbwf4vrCMvYvuoOQkKgNnNXF+0y6tCN6Acbm5no +xJpiBA5I9zwSuvdYwZqM6cewIzZWNB3LbNq6B4Qd/dGsn+bCie/DuWwYs2mHV1+1 +DDhbpyEkKjunNJGetFTqKE/TwaOL5OYr1fKdv5thACLd1ktEHz9dVv7enHjMmVuq +5L2620NLrUwmTKNNNIpsdDYT22L8m7IFgf+uPwzN9hui9DnnyvVMXPtUdzWAWsAS +oRMBM2c9nYGhqfWFJFiIeOf042hVAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8w +HwYDVR0jBBgwFoAU8DhClDSpPAB/Uu45pfdLDbxqfSMwHQYDVR0OBBYEFPA4QpQ0 +qTwAf1LuOaX3Sw28an0jMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AgEAmU/b8OrWEfoq/cirbeQOc2LSQp8V/nxwUj9kh4IxP0VALuEinwZmKfyW0y2N +tjjH2fMnwVkpoIz2cyQPKCLXTmHdE93bnzJSk/tPzOo4PJhqA6sWryHRQq59RSvq +xM+KWZ+CcHY6+GImyRCXWEAkpC25LymAJ+GJa3LKSQhxN1MF8YDO00IC0vzC0ZQG +7gfi9oPif5/nu1bDW7/dlZMJHiTBzybNraSuwrRp56q17TeU6d3RY4VrmnpKVnbc +GYUo1OTGpNi4lkF30LRZ8UYFh4cCH2m5ghjQQ9km2hpnqNZ1durybQ5C/4gmom6E +/n5iG/DGPe3AHGrHkda4ADdJm7mEBaHNbjHWROpTi7pTmB2hkIrphfgb8pNYw8jc +miZPPiDPT0PzEIx/EGF6NsqqC33Mn0dEWa6llcaZU+MHaz1JELAY/10OhUMUS+dr +00q1smBh3GlJAiNd6JJxw5yfRWd5HtwyhrqqVTxkbzK1EEAV3nJAeOBucLtu6wno +OdmsupJ13UPKugGVrRqBKzrw48UvDBhNEMauwO3+BVJ/GQXLqa81CAw4IuT+VuVT +Pr/k1rPZCMM91TMygSTFqeFlEbgyMzBxGEkdGkXGmhSKWDkobvPLUblJJmR4A8eR +EYOpuZA0tm+qBZ6FKFeZvn8nBkliTaH8CeErRglMFJtWj0U= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx +NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv +bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA +VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku +WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP +MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX +5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ +ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg +h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV +BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE +CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy +MDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G +A1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD +DC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq +M0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf +OePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa +4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9 +HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR +aZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA +b9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ +Gp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV +PWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO +pgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu +UDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY +MBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4 +9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW +s47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5 +Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg +cLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM +79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz +/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt +ll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm +Kf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK +QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ +w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi +S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07 +mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz +WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0 +b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS +b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI +7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg +CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD +VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T +kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+ +gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE +BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK +DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz +OTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R +xFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX +qhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC +C52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3 +6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh +/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF +YD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E +JNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc +US4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8 +ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm ++Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi +M+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G +A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV +cpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc +Hadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs +PgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/ +q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0 +cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr +a6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I +H37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y +K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu +nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf +oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY +Ic2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQsw +CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxT +U0wuY29tIFRMUyBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2 +MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3Jh +dGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3QgQ0EgMjAyMjB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWyJGYm +acCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFN +SeR7T5v15wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME +GDAWgBSJjy+j6CugFFR781a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NW +uCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp +15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w7deedWo1dlJF4AIxAMeN +b0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5Zn6g6g== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBO +MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQD +DBxTU0wuY29tIFRMUyBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloX +DTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jw +b3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJvb3QgQ0EgMjAyMjCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u9nTP +L3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OY +t6/wNr/y7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0ins +S657Lb85/bRi3pZ7QcacoOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3 +PnxEX4MN8/HdIGkWCVDi1FW24IBydm5MR7d1VVm0U3TZlMZBrViKMWYPHqIbKUBO +L9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDGD6C1vBdOSHtRwvzpXGk3 +R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEWTO6Af77w +dr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS ++YCk8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYS +d66UNHsef8JmAOSqg+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoG +AtUjHBPW6dvbxrB6y3snm/vg1UYk7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2f +gTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j +BBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsuN+7jhHonLs0Z +NbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt +hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsM +QtfhWsSWTVTNj8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvf +R4iyrT7gJ4eLSYwfqUdYe5byiB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJ +DPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjUo3KUQyxi4U5cMj29TH0ZR6LDSeeW +P4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqoENjwuSfr98t67wVy +lrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7EgkaibMOlq +bLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2w +AgDHbICivRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3q +r5nsLFR+jM4uElZI7xc7P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sji +Mho6/4UIyYOf8kpIEFR3N+2ivEC+5BB09+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU +98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQsw +CQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28x +ITAfBgNVBAoMGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1 +c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMx +OTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJVUzERMA8GA1UECAwI +SWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +ALldUShLPDeS0YLOvR29zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0Xzn +swuvCAAJWX/NKSqIk4cXGIDtiLK0thAfLdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu +7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4BqstTnoApTAbqOl5F2brz8 +1Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9oWN0EACyW +80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotP +JqX+OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1l +RtzuzWniTY+HKE40Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfw +hI0Vcnyh78zyiGG69Gm7DIwLdVcEuE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10 +coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm+9jaJXLE9gCxInm943xZYkqc +BW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqjifLJS3tBEW1n +twiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1Ud +DwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W +0OhUKDtkLSGm+J1WE2pIPU/HPinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfe +uyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0HZJDmHvUqoai7PF35owgLEQzxPy0Q +lG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla4gt5kNdXElE1GYhB +aCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5RvbbE +sLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPT +MaCm/zjdzyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qe +qu5AvzSxnI9O4fKSTx+O856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxh +VicGaeVyQYHTtgGJoC86cnn+OjC/QezHYj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8 +h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu3R3y4G5OBVixwJAWKqQ9 +EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP29FpHOTK +yeC2nOnOcXHebD8WpHk= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf +BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 +YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x +NzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYDVQQGEwJVUzERMA8G +A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 +d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF +Q0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqG +SM49AwEHA0IABH77bOYj43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoN +FWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqmP62jQzBBMA8GA1UdEwEB/wQFMAMBAf8w +DwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt0UrrdaVKEJmzsaGLSvcw +CgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjzRM4q3wgh +DDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf +BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 +YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x +NzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYDVQQGEwJVUzERMA8G +A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 +d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF +Q0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuB +BAAiA2IABGvaDXU1CDFHBa5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJ +j9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr/TklZvFe/oyujUF5nQlgziip04pt89ZF +1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwYAMB0G +A1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNnADBkAjA3 +AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsC +MGclCrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVu +Sw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB +gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk +MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY +UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx +NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3 +dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy +dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6 +38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP +KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q +DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4 +qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa +JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi +PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P +BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs +jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0 +eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD +ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR +vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa +IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy +i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ +O+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV +BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln +biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF +MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT +d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8 +76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+ +bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c +6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE +emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd +MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt +MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y +MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y +FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi +aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM +gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB +qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7 +lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn +8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov +L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6 +45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO +UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5 +O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC +bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv +GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a +77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC +hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3 +92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp +Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w +ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt +Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFlzCCA3+gAwIBAgIURg7UAXGQoBqDLEpCECgV0mEbrTIwDQYJKoZIhvcNAQEL +BQAwUzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEtMCsGA1UE +AxMkU3dpc3NTaWduIFJTQSBTTUlNRSBSb290IENBIDIwMjIgLSAxMB4XDTIyMDYw +ODEwNTMxM1oXDTQ3MDYwODEwNTMxM1owUzELMAkGA1UEBhMCQ0gxFTATBgNVBAoT +DFN3aXNzU2lnbiBBRzEtMCsGA1UEAxMkU3dpc3NTaWduIFJTQSBTTUlNRSBSb290 +IENBIDIwMjIgLSAxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1Pv6 +P4aimXAJOsnWoU4Bzka1LSRIDUXprMka1zKApObTytbyKTfsmizWgc7mG52xD0Hf +WNNfqqB5WQuMrfnF+Rz7w+k1QHTDwQzLZ/ucXgwj+dAv+kyCRRy19R/4GW7ak7dO +aIN+Yi0djJUfcNnOWowhXai+CKlWbdn3uZCZxzvXvZ4uyWdXLiHO8DKD+wQB+beC +RA2yy3oJoUg+T8ALahsb7M8dnn8GkKwoBQuo5lQ7oqcsOROZqPs06/XwvQHYiBHI +rroZAkkC3IostL1hYOydeFxqiy8Xhl7yT5MAa13FsqmlGOrmbX5XBfsH/Lx8oUOx +ZhyoZ/urN/aqqrh6Qfc51YyfrnI2J+RixkOZ8aFB6f+Jnw9Jr8kUBhcnZDkNpbQq +W+w8+5/FX8Y7XSYZ8oQpuJVECVL9bDDQYo8opYGWK5QvJnXkCYwK3zjzfl04joKa +jNyers4SQjoi8jWNT9IayEkzC/o2P/8sa2ogcUzNrRA/aTKEjlzuU4hE4t3MAzCS +hnmQKkt1+1JixPRvTffbI6EY3UVTF5pjJEiJIs1+mwEcgCgDj1sr+h/jfBm95o+x +QHag8sc3sjKUEDLNpxOX8TssejQie3Q6QOKvgvjBwXj8X+Q1f8D0TPBMsuqHA3Il +WYMqCKRR3s/uqOfoQD+I8DarCU7YoKh/8+EJ27kCAwEAAaNjMGEwDwYDVR0TAQH/ +BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHwYDVR0jBBgwFoAUzC6tiYyD40CjJWml +6pJ90jc6x8YwHQYDVR0OBBYEFMwurYmMg+NAoyVppeqSfdI3OsfGMA0GCSqGSIb3 +DQEBCwUAA4ICAQAAB2YWpe3Hub+8yJGtWO1eEgWz9kabe+SEEC8HsVpeMm5tAPBe +x5piOYdN5Dzzvva6alNshG0H1GHKZ2a+mz5FMJ1R0tdaQq6dkg4jq9AVlD6omsqb +7cHCXyGjmYD8uaZhDlCAgCfH6H2g1JR6mAPn7kKL81JQXO++sHZaHAmhv4PAHnZl +0CVBW2mRk3f5jEvwLNubBgAXg/palLSGie+8CgsS+AZN0nPikThduWpLT6ev2iYl +kiMafB8nDZGE7xdy9kbrazs3qdTVmmO6XnmMKrWbojS1zJYn+XkIPH9t4P983MUm +r8OhemkW3Yc1c8ZrMWtWAS1PmdnuyuHQg962hecW+NGuM0j7Gs9dX4qEYXQHbxmw +USGyoQSxe1OP76JFrR+Y3flqBGyqNsWvjOopSUrn/1ezxjwRSRgX5maF4egj8osO +PJPEP3ZOfmKiKcsWMN4saa+Rp+JX5TNMv9iOB6J/oTVGaUqoICn/694glVmxrk0w +a9iatAMfwjjkINUO1howTGicjODtoQ+OQl3rgCoSeaYXF7SVKo40kae90ayoGkMh +i97v4KxGJWUKxiuhmz4i6Bg4tSb2LMoIIN4w0a1U/dxIFZ/Np0HXNziFME8SiEM0 +g9cqTdQAV1zlyvDd4ZIoKxh1vUekQhPpVlqNSl7ODnU1gHMZDywpi7uVuA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFkzCCA3ugAwIBAgIUQ/oMX04bgBhE79G0TzUfRPSA7cswDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzErMCkGA1UE +AxMiU3dpc3NTaWduIFJTQSBUTFMgUm9vdCBDQSAyMDIyIC0gMTAeFw0yMjA2MDgx +MTA4MjJaFw00NzA2MDgxMTA4MjJaMFExCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxT +d2lzc1NpZ24gQUcxKzApBgNVBAMTIlN3aXNzU2lnbiBSU0EgVExTIFJvb3QgQ0Eg +MjAyMiAtIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDLKmjiC8NX +vDVjvHClO/OMPE5Xlm7DTjak9gLKHqquuN6orx122ro10JFwB9+zBvKK8i5VUXu7 +LCTLf5ImgKO0lPaCoaTo+nUdWfMHamFk4saMla+ju45vVs9xzF6BYQ1t8qsCLqSX +5XH8irCRIFucdFJtrhUnWXjyCcplDn/L9Ovn3KlMd/YrFgSVrpxxpT8q2kFC5zyE +EPThPYxr4iuRR1VPuFa+Rd4iUU1OKNlfGUEGjw5NBuBwQCMBauTLE5tzrE0USJIt +/m2n+IdreXXhvhCxqohAWVTXz8TQm0SzOGlkjIHRI36qOTw7D59Ke4LKa2/KIj4x +0LDQKhySio/YGZxH5D4MucLNvkEM+KRHBdvBFzA4OmnczcNpI/2aDwLOEGrOyvi5 +KaM2iYauC8BPY7kGWUleDsFpswrzd34unYyzJ5jSmY0lpx+Gs6ZUcDj8fV3oT4MM +0ZPlEuRU2j7yrTrePjxF8CgPBrnh25d7mUWe3f6VWQQvdT/TromZhqwUtKiE+shd +OxtYk8EXlFXIC+OCeYSf8wCENO7cMdWP8vpPlkwGqnj73mSiI80fPsWMvDdUDrta +clXvyFu1cvh43zcgTFeRc5JzrBh3Q4IgaezprClG5QtO+DdziZaKHG29777YtvTK +wP1H8K4LWCDFyB02rpeNUIMmJCn3nTsPBQIDAQABo2MwYTAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBRvjmKLk0Ow4UD2p8P98Q+4 +DxU4pTAdBgNVHQ4EFgQUb45ii5NDsOFA9qfD/fEPuA8VOKUwDQYJKoZIhvcNAQEL +BQADggIBAKwsKUF9+lz1GpUYvyypiqkkVHX1uECry6gkUSsYP2OprphWKwVDIqO3 +10aewCoSPY6WlkDfDDOLazeROpW7OSltwAJsipQLBwJNGD77+3v1dj2b9l4wBlgz +Hqp41eZUBDqyggmNzhYzWUUo8aWjlw5DI/0LIICQ/+Mmz7hkkeUFjxOgdg3XNwwQ +iJb0Pr6VvfHDffCjw3lHC1ySFWPtUnWK50Zpy1FVCypM9fJkT6lc/2cyjlUtMoIc +gC9qkfjLvH4YoiaoLqNTKIftV+Vlek4ASltOU8liNr3CjlvrzG4ngRhZi0Rjn9UM +ZfQpZX+RLOV/fuiJz48gy20HQhFRJjKKLjpHE7iNvUcNCfAWpO2Whi4Z2L6MOuhF +LhG6rlrnub+xzI/goP+4s9GFe3lmozm1O2bYQL7Pt2eLSMkZJVX8vY3PXtpOpvJp +zv1/THfQwUY1mFwjmwJFQ5Ra3bxHrSL+ul4vkSkphnsh3m5kt8sNjzdbowhq6/Td +Ao9QAwKxuDdollDruF/UKIqlIgyKhPBZLtU30WHlQnNYKoH3dtvi4k0NX/a3vgW0 +rk4N3hY9A4GzJl5LuEsAz/+MF7psYC0nhzck5npgL7XTgwSqT0N1osGDsieYK7EO +gLrAhV5Cud+xYJHT6xh+cHiudoO+cVrQkOPKwRYlZ0rwtnu64ZzZ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQ +MQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290 +IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5 +WhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FO +LUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1sTs6P +40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxF +avcokPFhV8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/ +34bKS1PE2Y2yHer43CdTo0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684i +JkXXYJndzk834H/nY62wuFm40AZoNWDTNq5xQwTxaWV4fPMf88oon1oglWa0zbfu +j3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK/c/WMw+f+5eesRycnupf +Xtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkHIuNZW0CP +2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDA +S9TMfAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDA +oS/xUgXJP+92ZuJF2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzC +kHDXShi8fgGwsOsVHkQGzaRP6AzRwyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW +5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83QOGt4A1WNzAd +BgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB +AGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0t +tGlTITVX1olNc79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn +68xDiBaiA9a5F/gZbG0jAn/xX9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNn +TKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDRIG4kqIQnoVesqlVYL9zZyvpoBJ7t +RCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq/p1hvIbZv97Tujqx +f36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0RFxbI +Qh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz +8ppy6rBePm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4 +NxKfKjLji7gh7MMrZQzvIt6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzX +xeSDwWrruoBa3lwtcHb4yOWHh8qgnaHlIhInD0Q9HWzq1MKLL295q39QpsQZp6F6 +t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx +EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT +VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5 +NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT +B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF +10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz +0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh +MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH +zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc +46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2 +yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi +laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP +oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA +BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE +qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm +4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL +1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF +H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo +RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+ +nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh +15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW +6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW +nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j +wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz +aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy +KwbQBM0= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFlTCCA32gAwIBAgIQQAE0jMIAAAAAAAAAAZdY9DANBgkqhkiG9w0BAQwFADBU +MQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290 +IENBMR8wHQYDVQQDExZUV0NBIEdsb2JhbCBSb290IENBIEcyMB4XDTIyMTEyMjA2 +NDIyMVoXDTQ3MTEyMjE1NTk1OVowVDELMAkGA1UEBhMCVFcxEjAQBgNVBAoTCVRB +SVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEfMB0GA1UEAxMWVFdDQSBHbG9iYWwg +Um9vdCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKoO1SCS +Aa2C+QwIkTRrihbQRhb/A7jYjeqTNPv/K739bqrcm/KGgVX1iRzEjXVqWHiREx4C +E3A9774K5wCPuDHldMUwvv991pnlwkKjzyHWswh/kdVh5qKVEA3vXpcLSTjVIrDX +i1lvnzWbf9KRzHp/u6Cf3lUz9kuNCup9CcB53L1E4v4c52QhKM8ESuK0v4Z5KrsO +k8mPXqwwOVKQB7nqnCZCFMRnRv7RGmihPlAZoyYKJymQwva063OaeB7hmPRlDDUh +BvgL3mLlTcGzXdm5+mGXKuPqx0RVJJL+Eqc/xHfgLQKBB9X7feYQnjq0qO/s+1Dq +Nc/MfrtCuURsUum/KnIfP96bcOncWsU7u7/wWYWvL8GwFHkFrHWfJfURJwZgIcdt +Zb6oiZzlrEbf+F1EA41gvfexDcwv70FUL+5rlblOfDTfO/l3nX3NBz0cBjMSgOxy +nPItgtrVO8TH+QTDZAJ89TVgp7RGKS4b76VYgC56iVE4Njz9oXe4gDDQit6NpzQm +7CO7GFUYNkXu7QEGqk2/ZAzKmJcaMQJm+HhoW4jfCajnm/o0bXAcIa0Ii/Khtqx2 +ar/xgCUAvjweTa65PLaVY71rfkcSkFVFEY3sFx/BvieBk1djaQAmd4vDWeV70Q1E +8qjw94WaBffCLnCak4XYlZAxkFSm7AufN0UPAgMBAAGjYzBhMA4GA1UdDwEB/wQE +AwIBBjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFJKM1DbRW0dTxHENhN1k +KvU2ZEDnMB0GA1UdDgQWBBSSjNQ20VtHU8RxDYTdZCr1NmRA5zANBgkqhkiG9w0B +AQwFAAOCAgEAJfxL2pC02nXnQTqB0ab+oGrzGHFiaiQIi6l6TclVzs8QKC4EGZYF +z10CICo7s1U/Ac1CzbJ37f9183x325alz4xnBvSkm3L2IUkJmKMyXndaYwnvYkOX +Aji16jwYUGj8WVvZedTx5FZIE1bY03ELXniUOBFF+gUX9Q51HmJSYUa6LhmthrSI +D7FQ5kAANBqVnZPgUfnUVUbplTwlhi6X1wExGETsHGDpfWmvMviXQCUkto0aVTzF +t/e8BlI7cTBwPnEXfvFmBF5dvIoxQ6aSHXtU0qU2i2+N1l7a1MMuHd85VWCCMJ4n +/46A3WNMplU12NAzqYBtPl6dzKhngGb6mVcMUsoZdbA4NVUqgcWMHlbXX5DyINja +4GZx6bJ4q2e5JG5rNnL8b439f3I5KGdSkQUfV2XSo6cNYfqh59U1RpXJBof2MOwy +UamsVsAhTqMUdAU6vOO/bT1OP16lpG0pv4RRdVOOhhr1UXAqDRxOQOH9o+OlK2eQ +ksdsroW/OpsXFcqcKpPUTTkNvCAIo42IbAkNjK5EIU3JcezYJtcXni0RGDyjIn24 +J1S/aMg7QsyPXk7n3MLF+mpED41WiHrfiYRsoLM+PfFlAAmI6irrQM6zXawyF67B +m+nQwfVJlN2nznxaB+uuIJwXMJJpk3Lzmltxm/5q33owaY6zLtsPLN0= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES +MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU +V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz +WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO +LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE +AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH +K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX +RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z +rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx +3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq +hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC +MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls +XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D +lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn +aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ +YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQx +CzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UE +AwwQVGVsaWEgUm9vdCBDQSB2MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1 +NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZ +MBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ76zBq +AMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9 +vVYiQJ3q9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9 +lRdU2HhE8Qx3FZLgmEKnpNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTOD +n3WhUidhOPFZPY5Q4L15POdslv5e2QJltI5c0BE0312/UqeBAMN/mUWZFdUXyApT +7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW5olWK8jjfN7j/4nlNW4o +6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNrRBH0pUPC +TEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6 +WT0EBXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63R +DolUK5X6wK0dmBR4M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZI +pEYslOqodmJHixBTB0hXbOKSTbauBcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGj +YzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7Wxy+G2CQ5MB0GA1UdDgQWBBRy +rOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ +8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi +0f6X+J8wfBj5tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMM +A8iZGok1GTzTyVR8qPAs5m4HeW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBS +SRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+Cy748fdHif64W1lZYudogsYMVoe+K +TTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygCQMez2P2ccGrGKMOF +6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15h2Er +3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMt +Ty3EHD70sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pT +VmBds9hCG1xLEooc6+t9xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAW +ysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQraVplI/owd8k+BsHMYeB2F326CjYSlKA +rBPuUBQemMc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw +NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv +b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD +VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2 +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F +VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1 +7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X +Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+ +/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs +81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm +dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe +Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu +sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4 +pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs +slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ +arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD +VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG +9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl +dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj +TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed +Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7 +Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI +OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7 +vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW +t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn +HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx +SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEM +BQAwWjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dp +ZXMsIEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAe +Fw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEwMTlaMFoxCzAJBgNVBAYTAkNOMSUw +IwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtU +cnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNS +T1QY4SxzlZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqK +AtCWHwDNBSHvBm3dIZwZQ0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1 +nyDvP+uLRx+PjsXUjrYsyUQE49RDdT/VP68czH5GX6zfZBCK70bwkPAPLfSIC7Ep +qq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1AgdB4SQXMeJNnKziyhWTXA +yB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm9WAPzJMs +hH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gX +zhqcD0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAv +kV34PmVACxmZySYgWmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msT +f9FkPz2ccEblooV7WIQn3MSAPmeamseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jA +uPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCFTIcQcf+eQxuulXUtgQIDAQAB +o2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj7zjKsK5Xf/Ih +MBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E +BAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4 +wM8zAQLpw6o1D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2 +XFNFV1pF1AWZLy4jVe5jaN/TG3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1 +JKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNjduMNhXJEIlU/HHzp/LgV6FL6qj6j +ITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstlcHboCoWASzY9M/eV +VHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys+TIx +xHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1on +AX1daBli2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d +7XB4tmBZrOFdRWOPyN9yaFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2Ntjj +gKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsASZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV ++Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFRJQJ6+N1rZdVtTTDIZbpo +FGWsJwt0ivKH +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMw +WjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs +IEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0y +MTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJaMFoxCzAJBgNVBAYTAkNOMSUwIwYD +VQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtUcnVz +dEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATx +s8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbw +LxYI+hW8m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJij +YzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mD +pm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/pDHel4NZg6ZvccveMA4GA1UdDwEB/wQE +AwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AAbbd+NvBNEU/zy4k6LHiR +UKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xkdUfFVZDj +/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICNjCCAbugAwIBAgIUWsL4KU/jfcVeHRhvO5MgH/97ui0wCgYIKoZIzj0EAwMw +WjELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs +IEluYy4xJDAiBgNVBAMTG1RydXN0QXNpYSBTTUlNRSBFQ0MgUm9vdCBDQTAeFw0y +NDA1MTUwNTQxNTlaFw00NDA1MTUwNTQxNThaMFoxCzAJBgNVBAYTAkNOMSUwIwYD +VQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDExtUcnVz +dEFzaWEgU01JTUUgRUNDIFJvb3QgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATN +2fsnvWnshsmQQ7FwF5SnyXcjOj8jZdMcox0eQlQg69BCu1m5i6zyho1Ljh2qliIj +OXZtkpvrIst6Q6Jz/XNLwiUPKrFpxv9F36k8lYC7qR5Kky/sHB2I9BGSN583mHKj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFDFn5nKyDeYioKzPfiKnWTLj +ZiOlMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNpADBmAjEA3TpMjaTGf+29 +pcZPPv0xSyjWilbfZRZ3h037ujIIgeCeM0iLn5SG7wErlOaM1tSOAjEAn4GcsCb9 +K9by9XGEnqjHiozWWBFStbgEy8xxdWPixhk42W1sGXGkFhkhk7oGRChs +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFhDCCA2ygAwIBAgIUWu5x394MV4W1uzYi17h2RgJzyv8wDQYJKoZIhvcNAQEM +BQAwWjELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dp +ZXMsIEluYy4xJDAiBgNVBAMTG1RydXN0QXNpYSBTTUlNRSBSU0EgUm9vdCBDQTAe +Fw0yNDA1MTUwNTQyMDFaFw00NDA1MTUwNTQyMDBaMFoxCzAJBgNVBAYTAkNOMSUw +IwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDExtU +cnVzdEFzaWEgU01JTUUgUlNBIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCYlZytPFlz05N2pkhUphyIckxN4YL/GhMfUN6M2ZBC0byZ0zej +13E6yt1eG5BhQm6PQAFzfR8xutQdbgTSqpCESjMKRJ9aGR+0bi1o/K/An0oQEr8+ +gsKCsC/nkG+QZBCD7Ow2lAx8T+ACDT2HeUJNAOUwrnAfFf36z1IlNk15ILvxEJjg +YIfJ9XgMIu0C5hFs8ZtakRF0htD+eJKWBMOY78Zwr6mQqhb2Iu3Y+kYoceLJCMBQ +vHajui2W8hH5pL0QVvgnbStLZIjcF13PAAiKkq4azBLX3/AQKPPNOuo6Eowb52EJ +Q2rkOOn+dDnbzQo7w09T1q5x1TiDhx/O50zzEVWH37ev9+sahhBtqO1I3TLQ26oq +C3J3KXf9eug/eCAqaL7ebwjmtYVHgDf5cZaLpZhWl3wRZRaO1M7YJ9T5WsWnjbvR +Nw2lq2Vd2nSTiF7bdfZ/m8KasW0IAgyYSrvNMK92NQKFViNRCUAJBffwPR7CyHoa +usVBFbkNdrS0pLhF/Y2jOz0DKs2zlX80e92hT9k6/yf1DcIBnP9ZdVoayefS/X9P +D7X+DTzmoNb7tXZctDBNED/+4utaDrFPT1B+CDMCkVcySYmnQBBQF2ufY7qyslaY +dvT/cukEnNSnTE/2Oh9aVDFvy7oyrfhtr0XHe2NE38L9eOhKirB0dRbejwIDAQAB +o0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSAGqpDwcl/ixqWRbw9u2tI +UmxbqzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACp1gaGCIOp/ +Vq4JMJcQePTZQBRSpO5qf/AJKNYQY+BOe8kxxwilF+uvhuKXB0+pDqKFzO2kgIEd +WlMGPEwaqbeEhs989YUKcJnQ7TaRjed3Ls6EnCiGLSU1jEwB5n3bYV3id4TTAdFi +3QyiCmSk/PDtOkjyOew11qF6F3Hs09LsuCb7rRVwVkrPZMC5YFv35s2gwgMr+bLl +2rqlNxzYjdp5dCpn5KJ6xyyNpcFqgWzM9ak5aiJ9ouIIzemT27rLH3V3nveYrxTk +O6BMp3LntV5TScz/klfxWSsJuulSk8APRQth1mxZcwvY+QEv2gNPNxz034NeC0Gg +sXw5AKFs0Ni0kXIrGz+imtHE3yvVyJV9hM12G9zkJMY/FSI9hadCK+1+cVlhSMI9 +kWNAfCmzgBYKJfwYYA5TrQ4qzvxBOs2x5GprzDltyE1luKqTiHhuDwKL4JaOdB/Q +fuF0t/aBauQjrI79jzUdmnEKTypVL/4YwQD3e0iKZa9vCB1D51q4H6ToA+v9TLW0 +k6gx3kOdEr3n6aTS32/8b0aj7zFOjRerG6ng+Kk0VqEO53TsqIeF2Hc1S40+bnJ8 +SMwfcrNxdNQkhrzIwON5FAHO2fqBxlyz+V0MOL7O8o6NXz0l4VE5I6jqAI4Es79y +oMK6g/vNpJd1IJq/p1Di3a0sH/Q/o8gx +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICMTCCAbegAwIBAgIUNnThTXxlE8msg1UloD5Sfi9QaMcwCgYIKoZIzj0EAwMw +WDELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs +IEluYy4xIjAgBgNVBAMTGVRydXN0QXNpYSBUTFMgRUNDIFJvb3QgQ0EwHhcNMjQw +NTE1MDU0MTU2WhcNNDQwNTE1MDU0MTU1WjBYMQswCQYDVQQGEwJDTjElMCMGA1UE +ChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAGA1UEAxMZVHJ1c3RB +c2lhIFRMUyBFQ0MgUm9vdCBDQTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLh/pVs/ +AT598IhtrimY4ZtcU5nb9wj/1WrgjstEpvDBjL1P1M7UiFPoXlfXTr4sP/MSpwDp +guMqWzJ8S5sUKZ74LYO1644xST0mYekdcouJtgq7nDM1D9rs3qlKH8kzsaNCMEAw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQULIVTu7FDzTLqnqOH/qKYqKaT6RAw +DgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMFRH18MtYYZI9HlaVQ01 +L18N9mdsd0AaRuf4aFtOJx24mH1/k78ITcTaRTChD15KeAIxAKORh/IRM4PDwYqR +OkwrULG9IpRdNYlzg8WbGf60oenUoWa2AaU2+dhoYSi3dOGiMQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFgDCCA2igAwIBAgIUHBjYz+VTPyI1RlNUJDxsR9FcSpwwDQYJKoZIhvcNAQEM +BQAwWDELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dp +ZXMsIEluYy4xIjAgBgNVBAMTGVRydXN0QXNpYSBUTFMgUlNBIFJvb3QgQ0EwHhcN +MjQwNTE1MDU0MTU3WhcNNDQwNTE1MDU0MTU2WjBYMQswCQYDVQQGEwJDTjElMCMG +A1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAGA1UEAxMZVHJ1 +c3RBc2lhIFRMUyBSU0EgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC +AgoCggIBAMMWuBtqpERz5dZO9LnPWwvB0ZqB9WOwj0PBuwhaGnrhB3YmH49pVr7+ +NmDQDIPNlOrnxS1cLwUWAp4KqC/lYCZUlviYQB2srp10Zy9U+5RjmOMmSoPGlbYJ +Q1DNDX3eRA5gEk9bNb2/mThtfWza4mhzH/kxpRkQcwUqwzIZheo0qt1CHjCNP561 +HmHVb70AcnKtEj+qpklz8oYVlQwQX1Fkzv93uMltrOXVmPGZLmzjyUT5tUMnCE32 +ft5EebuyjBza00tsLtbDeLdM1aTk2tyKjg7/D8OmYCYozza/+lcK7Fs/6TAWe8Tb +xNRkoDD75f0dcZLdKY9BWN4ArTr9PXwaqLEX8E40eFgl1oUh63kd0Nyrz2I8sMeX +i9bQn9P+PN7F4/w6g3CEIR0JwqH8uyghZVNgepBtljhb//HXeltt08lwSUq6HTrQ +UNoyIBnkiz/r1RYmNzz7dZ6wB3C4FGB33PYPXFIKvF1tjVEK2sUYyJtt3LCDs3+j +TnhMmCWr8n4uIF6CFabW2I+s5c0yhsj55NqJ4js+k8UTav/H9xj8Z7XvGCxUq0DT +bE3txci3OE9kxJRMT6DNrqXGJyV1J23G2pyOsAWZ1SgRxSHUuPzHlqtKZFlhaxP8 +S8ySpg+kUb8OWJDZgoM5pl+z+m6Ss80zDoWo8SnTq1mt1tve1CuBAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLgHkXlcBvRG/XtZylomkadFK/hT +MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQwFAAOCAgEAIZtqBSBdGBanEqT3 +Rz/NyjuujsCCztxIJXgXbODgcMTWltnZ9r96nBO7U5WS/8+S4PPFJzVXqDuiGev4 +iqME3mmL5Dw8veWv0BIb5Ylrc5tvJQJLkIKvQMKtuppgJFqBTQUYo+IzeXoLH5Pt +7DlK9RME7I10nYEKqG/odv6LTytpEoYKNDbdgptvT+Bz3Ul/KD7JO6NXBNiT2Twp +2xIQaOHEibgGIOcberyxk2GaGUARtWqFVwHxtlotJnMnlvm5P1vQiJ3koP26TpUJ +g3933FEFlJ0gcXax7PqJtZwuhfG5WyRasQmr2soaB82G39tp27RIGAAtvKLEiUUj +pQ7hRGU+isFqMB3iYPg6qocJQrmBktwliJiJ8Xw18WLK7nn4GS/+X/jbh87qqA8M +pugLoDzga5SYnH+tBuYc6kIQX+ImFTw3OffXvO645e8D7r0i+yiGNFjEWn9hongP +XvPKnbwbPKfILfanIhHKA9jnZwqKDss1jjQ52MjqjZ9k4DewbNfFj8GQYSbbJIwe +SsCI3zWQzj8C9GRh3sfIB5XeMhg6j6JCQCTl1jNdfK7vsU1P1FeQNWrcrgSXSYk0 +ly4wBOeY99sLAZDBHwo/+ML+TvrbmnNzFrwFuHnYWa8G5z9nODmxfKuU4CkUpijy +323imttUQ/hHWKNddBWcwauwxzQ= +-----END CERTIFICATE----- diff --git a/common/certificate/store.go b/common/certificate/store.go new file mode 100644 index 0000000000..f489267d34 --- /dev/null +++ b/common/certificate/store.go @@ -0,0 +1,252 @@ +package certificate + +import ( + "bytes" + "context" + "crypto/x509" + "io/fs" + "os" + "path/filepath" + "strings" + "sync" + + "github.com/sagernet/fswatch" + "github.com/sagernet/sing-box/adapter" + 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" + "github.com/sagernet/sing/service" +) + +var _ adapter.CertificateStore = (*Store)(nil) + +type Store struct { + access sync.RWMutex + storeType string + systemPool *x509.CertPool + currentPool *x509.CertPool + certificate string + certificatePaths []string + certificateDirectoryPaths []string + watcher *fswatch.Watcher + //nolint:unused // populated only on darwin && cgo via the storePlatform embed. + platform storePlatform +} + +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: + systemPool = x509.NewCertPool() + platformInterface := service.FromContext[adapter.PlatformInterface](ctx) + var systemValid bool + if platformInterface != nil { + for _, cert := range platformInterface.SystemCertificates() { + if systemPool.AppendCertsFromPEM([]byte(cert)) { + systemValid = true + } + } + } + if !systemValid { + certPool, err := x509.SystemCertPool() + if err != nil { + return nil, err + } + systemPool = certPool + } + case C.CertificateStoreMozilla, C.CertificateStoreChrome: + case C.CertificateStoreNone: + 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, + certificateDirectoryPaths: options.CertificateDirectoryPath, + } + var watchPaths []string + for _, target := range options.CertificatePath { + watchPaths = append(watchPaths, target) + } + for _, target := range options.CertificateDirectoryPath { + watchPaths = append(watchPaths, target) + } + if len(watchPaths) > 0 { + watcher, err := fswatch.NewWatcher(fswatch.Options{ + Path: watchPaths, + Logger: logger, + Callback: func(_ string) { + err := store.update() + if err != nil { + logger.Error(E.Cause(err, "reload certificates")) + } + }, + }) + if err != nil { + return nil, E.Cause(err, "fswatch: create fsnotify watcher") + } + store.watcher = watcher + } + err := store.update() + if err != nil { + return nil, E.Cause(err, "initializing certificate store") + } + return store, nil +} + +func (s *Store) Name() string { + return "certificate" +} + +func (s *Store) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + if s.watcher != nil { + return s.watcher.Start() + } + return nil +} + +func (s *Store) Close() error { + watcher := s.watcher + s.watcher = nil + + var closeErr error + if watcher != nil { + closeErr = watcher.Close() + } + platformErr := s.closePlatform() + if platformErr != nil { + closeErr = platformErr + } + return closeErr +} + +func (s *Store) Pool() *x509.CertPool { + s.access.RLock() + defer s.access.RUnlock() + return s.currentPool +} + +func (s *Store) StoreKind() string { + return s.storeType +} + +func (s *Store) ExclusiveAnchors() bool { + return s.storeType != C.CertificateStoreSystem +} + +func (s *Store) update() error { + currentPool, err := s.newBasePool() + if err != nil { + return err + } + pemBuffer := new(bytes.Buffer) + switch s.storeType { + case C.CertificateStoreMozilla: + pemContent := mozillaIncludedPEM() + if !currentPool.AppendCertsFromPEM([]byte(pemContent)) { + return E.New("invalid Mozilla included certificate PEM") + } + appendPEMBlock(pemBuffer, string(pemContent)) + case C.CertificateStoreChrome: + pemContent := chromeIncludedPEM() + if !currentPool.AppendCertsFromPEM([]byte(pemContent)) { + return E.New("invalid Chrome included certificate PEM") + } + appendPEMBlock(pemBuffer, string(pemContent)) + } + if s.certificate != "" { + if !currentPool.AppendCertsFromPEM([]byte(s.certificate)) { + return E.New("invalid certificate PEM strings") + } + appendPEMBlock(pemBuffer, s.certificate) + } + for _, path := range s.certificatePaths { + pemContent, err := os.ReadFile(path) + if err != nil { + return err + } + if !currentPool.AppendCertsFromPEM(pemContent) { + return E.New("invalid certificate PEM file: ", path) + } + appendPEMBlock(pemBuffer, string(pemContent)) + } + var firstErr error + for _, directoryPath := range s.certificateDirectoryPaths { + directoryEntries, err := readUniqueDirectoryEntries(directoryPath) + if err != nil { + if firstErr == nil && !os.IsNotExist(err) { + firstErr = E.Cause(err, "invalid certificate directory: ", directoryPath) + } + continue + } + for _, directoryEntry := range directoryEntries { + pemContent, err := os.ReadFile(filepath.Join(directoryPath, directoryEntry.Name())) + if err == nil && currentPool.AppendCertsFromPEM(pemContent) { + appendPEMBlock(pemBuffer, string(pemContent)) + } + } + } + if firstErr != nil { + return firstErr + } + s.access.Lock() + defer s.access.Unlock() + s.currentPool = currentPool + return s.updatePlatformLocked(pemBuffer.Bytes()) +} + +func appendPEMBlock(buffer *bytes.Buffer, block string) { + existing := buffer.Bytes() + if len(existing) > 0 && existing[len(existing)-1] != '\n' { + buffer.WriteByte('\n') + } + buffer.WriteString(block) +} + +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, C.CertificateStoreChrome: + return x509.NewCertPool(), 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 { + return nil, err + } + uniq := files[:0] + for _, f := range files { + if !isSameDirSymlink(f, dir) { + uniq = append(uniq, f) + } + } + return uniq, nil +} + +func isSameDirSymlink(f fs.DirEntry, dir string) bool { + if f.Type()&fs.ModeSymlink == 0 { + return false + } + target, err := os.Readlink(filepath.Join(dir, f.Name())) + return err == nil && !strings.Contains(target, "/") +} diff --git a/common/certificate/store_darwin.go b/common/certificate/store_darwin.go new file mode 100644 index 0000000000..bbc8d4ba84 --- /dev/null +++ b/common/certificate/store_darwin.go @@ -0,0 +1,167 @@ +//go:build darwin && cgo + +package certificate + +/* +#cgo CFLAGS: -x objective-c -fobjc-arc +#cgo LDFLAGS: -framework Foundation -framework Security + +#include +#include "anchors_darwin.h" +*/ +import "C" + +import ( + "crypto/sha256" + "encoding/pem" + "runtime" + "sync/atomic" + "unsafe" + + "github.com/sagernet/sing-box/adapter" + E "github.com/sagernet/sing/common/exceptions" +) + +var ( + _ adapter.AppleCertificateStore = (*Store)(nil) + _ adapter.AppleAnchors = (*appleAnchors)(nil) +) + +type storePlatform struct { + anchors *appleAnchors + hash [sha256.Size]byte +} + +type appleAnchors struct { + cfArray unsafe.Pointer + refs atomic.Int32 +} + +func newAppleAnchors(pemBytes []byte) (*appleAnchors, error) { + anchors := &appleAnchors{} + anchors.refs.Store(1) + if len(pemBytes) == 0 { + return anchors, nil + } + derBlocks := decodeCertificatePEM(pemBytes) + if len(derBlocks) == 0 { + return nil, E.New("parse certificate PEM") + } + pointerSize := C.size_t(unsafe.Sizeof((*C.uint8_t)(nil))) + lenSize := C.size_t(unsafe.Sizeof(C.size_t(0))) + pointersC := (**C.uint8_t)(C.malloc(pointerSize * C.size_t(len(derBlocks)))) + defer C.free(unsafe.Pointer(pointersC)) + lensC := (*C.size_t)(C.malloc(lenSize * C.size_t(len(derBlocks)))) + defer C.free(unsafe.Pointer(lensC)) + pointersSlice := unsafe.Slice(pointersC, len(derBlocks)) + lensSlice := unsafe.Slice(lensC, len(derBlocks)) + var pinner runtime.Pinner + defer pinner.Unpin() + for index, der := range derBlocks { + pinner.Pin(&der[0]) + pointersSlice[index] = (*C.uint8_t)(unsafe.Pointer(&der[0])) + lensSlice[index] = C.size_t(len(der)) + } + cfArray := C.box_certificate_anchors_from_der(pointersC, lensC, C.size_t(len(derBlocks))) + if cfArray == nil { + return nil, E.New("parse certificate PEM") + } + anchors.cfArray = cfArray + return anchors, nil +} + +// NewAppleAnchors parses the given PEM and returns a ref-counted handle +// wrapping a CFArray of SecCertificateRef. The caller owns the returned +// reference and must call Release when finished. Returns an error when +// pemBytes is non-empty but contains no usable CERTIFICATE blocks. +func NewAppleAnchors(pemBytes []byte) (adapter.AppleAnchors, error) { + return newAppleAnchors(pemBytes) +} + +// AcquireAnchors returns a retained AppleAnchors handle, preferring the +// per-config userAnchors over the process-wide certificate store. Returns +// nil when neither source is available. Callers must Release the handle. +func AcquireAnchors(userAnchors adapter.AppleAnchors, store adapter.CertificateStore) adapter.AppleAnchors { + if userAnchors != nil { + return userAnchors.Retain() + } + if store == nil { + return nil + } + apple, loaded := store.(adapter.AppleCertificateStore) + if !loaded { + return nil + } + return apple.AppleAnchors() +} + +func (a *appleAnchors) Retain() adapter.AppleAnchors { + a.refs.Add(1) + return a +} + +func (a *appleAnchors) Release() { + if a.refs.Add(-1) != 0 { + return + } + if a.cfArray != nil { + C.box_certificate_release_anchors(a.cfArray) + } +} + +func (a *appleAnchors) Ref() unsafe.Pointer { + return a.cfArray +} + +func (s *Store) AppleAnchors() adapter.AppleAnchors { + s.access.RLock() + defer s.access.RUnlock() + if s.platform.anchors == nil { + return nil + } + return s.platform.anchors.Retain() +} + +func (s *Store) updatePlatformLocked(pemBytes []byte) error { + hash := sha256.Sum256(pemBytes) + if s.platform.anchors != nil && s.platform.hash == hash { + return nil + } + newAnchors, err := newAppleAnchors(pemBytes) + if err != nil { + return err + } + old := s.platform.anchors + s.platform.anchors = newAnchors + s.platform.hash = hash + if old != nil { + old.Release() + } + return nil +} + +func (s *Store) closePlatform() error { + s.access.Lock() + defer s.access.Unlock() + if s.platform.anchors != nil { + s.platform.anchors.Release() + s.platform.anchors = nil + } + return nil +} + +func decodeCertificatePEM(pemBytes []byte) [][]byte { + var blocks [][]byte + rest := pemBytes + for { + block, next := pem.Decode(rest) + if block == nil { + break + } + if block.Type == "CERTIFICATE" && len(block.Bytes) > 0 { + blocks = append(blocks, block.Bytes) + } + rest = next + } + return blocks +} diff --git a/common/certificate/store_other.go b/common/certificate/store_other.go new file mode 100644 index 0000000000..746520d141 --- /dev/null +++ b/common/certificate/store_other.go @@ -0,0 +1,14 @@ +//go:build !(darwin && cgo) + +package certificate + +//nolint:unused // referenced by Store.platform; populated only in store_darwin.go. +type storePlatform struct{} + +func (s *Store) updatePlatformLocked(_ []byte) error { + return nil +} + +func (s *Store) closePlatform() error { + return nil +} diff --git a/common/cloudflare/api.go b/common/cloudflare/api.go new file mode 100644 index 0000000000..d86f811c53 --- /dev/null +++ b/common/cloudflare/api.go @@ -0,0 +1,224 @@ +package cloudflare + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "strings" + "time" + + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/tidwall/gjson" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +type CloudflareApi struct { + client http.Client +} + +const baseUrl = "https://api.cloudflareclient.com/v0i1909051800/" + +func NewCloudflareApiDetour(detour N.Dialer) *CloudflareApi { + opts := make([]CloudflareApiOption, 0, 1) + if detour != nil { + opts = append(opts, WithDialContext(func(ctx context.Context, network, addr string) (net.Conn, error) { + return detour.DialContext(ctx, network, M.ParseSocksaddr(addr)) + })) + } + return NewCloudflareApi(opts...) +} +func NewCloudflareApi(opts ...CloudflareApiOption) *CloudflareApi { + api := &CloudflareApi{http.Client{Timeout: 30 * time.Second}} + for _, opt := range opts { + opt(api) + } + return api +} + +func (api *CloudflareApi) CreateProfile(ctx context.Context, publicKey string) (*CloudflareProfile, error) { + request, err := http.NewRequest("POST", baseUrl+"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 + } + response, err := api.client.Do(request.WithContext(ctx)) + if err != nil { + return nil, err + } + defer response.Body.Close() + 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(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", baseUrl+"reg/"+id, nil) + if err != nil { + return nil, err + } + 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 != 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(strings.NewReader(gjson.Get(string(content), "result").Raw)).Decode(profile) +} + +func (api *CloudflareApi) UpdateAccount(ctx context.Context, profile *CloudflareProfile, license string) (*CloudflareProfile, error) { + deviceId := profile.ID + authToken := profile.Token + request, err := http.NewRequest("POST", fmt.Sprint(baseUrl, "reg/", deviceId, "/account"), strings.NewReader( + fmt.Sprintf("{\"license\":\"%s\"}", license), + )) + request.Header.Set("Authorization", "Bearer "+authToken) + if err != nil { + return nil, err + } + response, err := api.client.Do(request.WithContext(ctx)) + if err != nil { + return nil, err + } + defer response.Body.Close() + 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 + } + ia := new(IdentityAccount) + if err := json.Unmarshal([]byte(content), ia); err != nil { + return nil, err + } + + profile.Account = *ia + return profile, nil +} + +func (api *CloudflareApi) CreateProfileLicense(ctx context.Context, privateKey string, license string) (*CloudflareProfile, error) { + var wgKey wgtypes.Key + var err error + if privateKey != "" { + wgKey, err = wgtypes.ParseKey(privateKey) + if err != nil { + + return nil, err + } + } else { + wgKey, err = wgtypes.GeneratePrivateKey() + if err != nil { + + return nil, err + } + } + profile, err := api.CreateProfile(ctx, wgKey.PublicKey().String()) + if err != nil { + return nil, err + } + profile.Config.PrivateKey = wgKey.String() + if license == "" { + return profile, nil + } + return api.UpdateAccount(ctx, profile, license) +} + +func (api *CloudflareApi) DeleteProfile(ctx context.Context, profile *CloudflareProfile) error { + request, err := http.NewRequest("DELETE", fmt.Sprint(baseUrl, "reg/", profile.ID), nil) + if err != nil { + return err + } + request.Header.Set("Authorization", "Bearer "+profile.Token) + response, err := api.client.Do(request.WithContext(ctx)) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode != 200 { + return fmt.Errorf("status code is not 200") + } + return nil +} + +type DeviceUpdate struct { + Name string `json:"name"` + Key string `json:"key"` + KeyType string `json:"key_type"` + TunType string `json:"tunnel_type"` +} + +func (api *CloudflareApi) GetProfile4471(ctx context.Context, authToken string, id string) (*CloudflareProfile, error) { + request, err := http.NewRequest("GET", ApiUrl+"/"+ApiVersion+"/reg/"+id, nil) + if err != nil { + return nil, err + } + for k, v := range Headers { + request.Header.Set(k, v) + } + request.Header.Set("Authorization", "Bearer "+authToken) + response, err := api.client.Do(request.WithContext(ctx)) + if err != nil { + return nil, err + } + defer response.Body.Close() + if response.StatusCode != http.StatusOK { + return nil, fmt.Errorf("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)) + 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) +} diff --git a/common/cloudflare/constant.go b/common/cloudflare/constant.go new file mode 100644 index 0000000000..47bd5cb982 --- /dev/null +++ b/common/cloudflare/constant.go @@ -0,0 +1,24 @@ +package cloudflare + +const ( + ApiUrl = "https://api.cloudflareclient.com" + ApiVersion = "v0a4471" + ConnectSNI = "consumer-masque.cloudflareclient.com" + 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", +} diff --git a/common/cloudflare/option.go b/common/cloudflare/option.go new file mode 100644 index 0000000000..929e91b7ea --- /dev/null +++ b/common/cloudflare/option.go @@ -0,0 +1,17 @@ +package cloudflare + +import ( + "context" + "net" + "net/http" +) + +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.Transport = &http.Transport{ + DialContext: dialContext, + } + } +} diff --git a/common/cloudflare/profile.go b/common/cloudflare/profile.go new file mode 100644 index 0000000000..9528774052 --- /dev/null +++ b/common/cloudflare/profile.go @@ -0,0 +1,45 @@ +package cloudflare + +import ( + "time" + + C "github.com/sagernet/sing-box/constant" +) + +type IdentityAccount struct { + Created time.Time `json:"created"` + Updated time.Time `json:"updated"` + License string `json:"license"` + PremiumData int64 `json:"premium_data"` + WarpPlus bool `json:"warp_plus"` + AccountType string `json:"account_type"` + ReferralRenewalCountdown int64 `json:"referral_renewal_countdown"` + Role string `json:"role"` + ID string `json:"id"` + Quota int64 `json:"quota"` + Usage int64 `json:"usage"` + ReferralCount int64 `json:"referral_count"` + TTL time.Time `json:"ttl"` +} +type CloudflareProfile struct { + ID string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + Key string `json:"key"` + Account IdentityAccount `json:"account"` + Config C.WARPConfig `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"` +} diff --git a/experimental/clashapi/compatible/map.go b/common/compatible/map.go similarity index 100% rename from experimental/clashapi/compatible/map.go rename to common/compatible/map.go diff --git a/common/conntrack/conn.go b/common/conntrack/conn.go deleted file mode 100644 index 4773d6a820..0000000000 --- a/common/conntrack/conn.go +++ /dev/null @@ -1,54 +0,0 @@ -package conntrack - -import ( - "io" - "net" - - "github.com/sagernet/sing/common/x/list" -) - -type Conn struct { - net.Conn - element *list.Element[io.Closer] -} - -func NewConn(conn net.Conn) (net.Conn, error) { - connAccess.Lock() - element := openConnection.PushBack(conn) - connAccess.Unlock() - if KillerEnabled { - err := KillerCheck() - if err != nil { - conn.Close() - return nil, err - } - } - return &Conn{ - Conn: conn, - element: element, - }, nil -} - -func (c *Conn) Close() error { - if c.element.Value != nil { - connAccess.Lock() - if c.element.Value != nil { - openConnection.Remove(c.element) - c.element.Value = nil - } - connAccess.Unlock() - } - return c.Conn.Close() -} - -func (c *Conn) Upstream() any { - return c.Conn -} - -func (c *Conn) ReaderReplaceable() bool { - return true -} - -func (c *Conn) WriterReplaceable() bool { - return true -} diff --git a/common/conntrack/killer.go b/common/conntrack/killer.go deleted file mode 100644 index e0a71e5cb6..0000000000 --- a/common/conntrack/killer.go +++ /dev/null @@ -1,35 +0,0 @@ -package conntrack - -import ( - runtimeDebug "runtime/debug" - "time" - - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/memory" -) - -var ( - KillerEnabled bool - MemoryLimit uint64 - killerLastCheck time.Time -) - -func KillerCheck() error { - if !KillerEnabled { - return nil - } - nowTime := time.Now() - if nowTime.Sub(killerLastCheck) < 3*time.Second { - return nil - } - killerLastCheck = nowTime - if memory.Total() > MemoryLimit { - Close() - go func() { - time.Sleep(time.Second) - runtimeDebug.FreeOSMemory() - }() - return E.New("out of memory") - } - return nil -} diff --git a/common/conntrack/packet_conn.go b/common/conntrack/packet_conn.go deleted file mode 100644 index c7274637e8..0000000000 --- a/common/conntrack/packet_conn.go +++ /dev/null @@ -1,55 +0,0 @@ -package conntrack - -import ( - "io" - "net" - - "github.com/sagernet/sing/common/bufio" - "github.com/sagernet/sing/common/x/list" -) - -type PacketConn struct { - net.PacketConn - element *list.Element[io.Closer] -} - -func NewPacketConn(conn net.PacketConn) (net.PacketConn, error) { - connAccess.Lock() - element := openConnection.PushBack(conn) - connAccess.Unlock() - if KillerEnabled { - err := KillerCheck() - if err != nil { - conn.Close() - return nil, err - } - } - return &PacketConn{ - PacketConn: conn, - element: element, - }, nil -} - -func (c *PacketConn) Close() error { - if c.element.Value != nil { - connAccess.Lock() - if c.element.Value != nil { - openConnection.Remove(c.element) - c.element.Value = nil - } - connAccess.Unlock() - } - return c.PacketConn.Close() -} - -func (c *PacketConn) Upstream() any { - return bufio.NewPacketConn(c.PacketConn) -} - -func (c *PacketConn) ReaderReplaceable() bool { - return true -} - -func (c *PacketConn) WriterReplaceable() bool { - return true -} diff --git a/common/conntrack/track.go b/common/conntrack/track.go deleted file mode 100644 index 2c3e328b34..0000000000 --- a/common/conntrack/track.go +++ /dev/null @@ -1,47 +0,0 @@ -package conntrack - -import ( - "io" - "sync" - - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/x/list" -) - -var ( - connAccess sync.RWMutex - openConnection list.List[io.Closer] -) - -func Count() int { - if !Enabled { - return 0 - } - return openConnection.Len() -} - -func List() []io.Closer { - if !Enabled { - return nil - } - connAccess.RLock() - defer connAccess.RUnlock() - connList := make([]io.Closer, 0, openConnection.Len()) - for element := openConnection.Front(); element != nil; element = element.Next() { - connList = append(connList, element.Value) - } - return connList -} - -func Close() { - if !Enabled { - return - } - connAccess.Lock() - defer connAccess.Unlock() - for element := openConnection.Front(); element != nil; element = element.Next() { - common.Close(element.Value) - element.Value = nil - } - openConnection.Init() -} diff --git a/common/conntrack/track_disable.go b/common/conntrack/track_disable.go deleted file mode 100644 index 174d8b6ebe..0000000000 --- a/common/conntrack/track_disable.go +++ /dev/null @@ -1,5 +0,0 @@ -//go:build !with_conntrack - -package conntrack - -const Enabled = false diff --git a/common/conntrack/track_enable.go b/common/conntrack/track_enable.go deleted file mode 100644 index a4bf9986a0..0000000000 --- a/common/conntrack/track_enable.go +++ /dev/null @@ -1,5 +0,0 @@ -//go:build with_conntrack - -package conntrack - -const Enabled = true diff --git a/common/convertor/adguard/convertor.go b/common/convertor/adguard/convertor.go new file mode 100644 index 0000000000..187c4f4d5a --- /dev/null +++ b/common/convertor/adguard/convertor.go @@ -0,0 +1,454 @@ +package adguard + +import ( + "bufio" + "bytes" + "io" + "net/netip" + "os" + "strconv" + "strings" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" +) + +type agdguardRuleLine struct { + ruleLine string + isRawDomain bool + isExclude bool + isSuffix bool + hasStart bool + hasEnd bool + isRegexp bool + isImportant bool +} + +func ToOptions(reader io.Reader, logger logger.Logger) ([]option.HeadlessRule, error) { + scanner := bufio.NewScanner(reader) + var ( + ruleLines []agdguardRuleLine + ignoredLines int + ) +parseLine: + for scanner.Scan() { + ruleLine := scanner.Text() + if ruleLine == "" { + continue + } + if strings.HasPrefix(ruleLine, "!") || strings.HasPrefix(ruleLine, "#") { + continue + } + originRuleLine := ruleLine + if M.IsDomainName(ruleLine) { + ruleLines = append(ruleLines, agdguardRuleLine{ + ruleLine: ruleLine, + isRawDomain: true, + }) + continue + } + hostLine, err := parseAdGuardHostLine(ruleLine) + if err == nil { + if hostLine != "" { + ruleLines = append(ruleLines, agdguardRuleLine{ + ruleLine: hostLine, + isRawDomain: true, + hasStart: true, + hasEnd: true, + }) + } + continue + } + ruleLine = strings.TrimSuffix(ruleLine, "|") + var ( + isExclude bool + isSuffix bool + hasStart bool + hasEnd bool + isRegexp bool + isImportant bool + ) + if !strings.HasPrefix(ruleLine, "/") && strings.Contains(ruleLine, "$") { + params := common.SubstringAfter(ruleLine, "$") + for param := range strings.SplitSeq(params, ",") { + paramParts := strings.Split(param, "=") + var ignored bool + if len(paramParts) > 0 && len(paramParts) <= 2 { + switch paramParts[0] { + case "app", "network": + // maybe support by package_name/process_name + case "dnstype": + // maybe support by query_type + case "important": + ignored = true + isImportant = true + case "dnsrewrite": + if len(paramParts) == 2 && M.ParseAddr(paramParts[1]).IsUnspecified() { + ignored = true + } + } + } + if !ignored { + ignoredLines++ + logger.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", originRuleLine) + continue parseLine + } + } + ruleLine = common.SubstringBefore(ruleLine, "$") + } + if strings.HasPrefix(ruleLine, "@@") { + ruleLine = ruleLine[2:] + isExclude = true + } + ruleLine = strings.TrimSuffix(ruleLine, "|") + if strings.HasPrefix(ruleLine, "||") { + ruleLine = ruleLine[2:] + isSuffix = true + } else if strings.HasPrefix(ruleLine, "|") { + ruleLine = ruleLine[1:] + hasStart = true + } + if strings.HasSuffix(ruleLine, "^") { + ruleLine = ruleLine[:len(ruleLine)-1] + hasEnd = true + } + if strings.HasPrefix(ruleLine, "/") && strings.HasSuffix(ruleLine, "/") { + ruleLine = ruleLine[1 : len(ruleLine)-1] + if ignoreIPCIDRRegexp(ruleLine) { + ignoredLines++ + logger.Debug("ignored unsupported rule with IPCIDR regexp: ", originRuleLine) + continue + } + isRegexp = true + } else { + if strings.Contains(ruleLine, "://") { + ruleLine = common.SubstringAfter(ruleLine, "://") + isSuffix = true + } + if strings.Contains(ruleLine, "/") { + ignoredLines++ + logger.Debug("ignored unsupported rule with path: ", originRuleLine) + continue + } + if strings.Contains(ruleLine, "?") || strings.Contains(ruleLine, "&") { + ignoredLines++ + logger.Debug("ignored unsupported rule with query: ", originRuleLine) + continue + } + if strings.Contains(ruleLine, "[") || strings.Contains(ruleLine, "]") || + strings.Contains(ruleLine, "(") || strings.Contains(ruleLine, ")") || + strings.Contains(ruleLine, "!") || strings.Contains(ruleLine, "#") { + ignoredLines++ + logger.Debug("ignored unsupported cosmetic filter: ", originRuleLine) + continue + } + if strings.Contains(ruleLine, "~") { + ignoredLines++ + logger.Debug("ignored unsupported rule modifier: ", originRuleLine) + continue + } + var domainCheck string + if strings.HasPrefix(ruleLine, ".") || strings.HasPrefix(ruleLine, "-") { + domainCheck = "r" + ruleLine + } else { + domainCheck = ruleLine + } + if ruleLine == "" { + ignoredLines++ + logger.Debug("ignored unsupported rule with empty domain", originRuleLine) + continue + } else { + domainCheck = strings.ReplaceAll(domainCheck, "*", "x") + if !M.IsDomainName(domainCheck) { + _, ipErr := parseADGuardIPCIDRLine(ruleLine) + if ipErr == nil { + ignoredLines++ + logger.Debug("ignored unsupported rule with IPCIDR: ", originRuleLine) + continue + } + if M.ParseSocksaddr(domainCheck).Port != 0 { + logger.Debug("ignored unsupported rule with port: ", originRuleLine) + } else { + logger.Debug("ignored unsupported rule with invalid domain: ", originRuleLine) + } + ignoredLines++ + continue + } + } + } + ruleLines = append(ruleLines, agdguardRuleLine{ + ruleLine: ruleLine, + isExclude: isExclude, + isSuffix: isSuffix, + hasStart: hasStart, + hasEnd: hasEnd, + isRegexp: isRegexp, + isImportant: isImportant, + }) + } + if len(ruleLines) == 0 { + return nil, E.New("AdGuard rule-set is empty or all rules are unsupported") + } + if common.All(ruleLines, func(it agdguardRuleLine) bool { + return it.isRawDomain + }) { + return []option.HeadlessRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultHeadlessRule{ + Domain: common.Map(ruleLines, func(it agdguardRuleLine) string { + return it.ruleLine + }), + }, + }, + }, nil + } + mapDomain := func(it agdguardRuleLine) string { + ruleLine := it.ruleLine + if it.isSuffix { + ruleLine = "||" + ruleLine + } else if it.hasStart { + ruleLine = "|" + ruleLine + } + if it.hasEnd { + ruleLine += "^" + } + return ruleLine + } + + importantDomain := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return it.isImportant && !it.isRegexp && !it.isExclude }), mapDomain) + importantDomainRegex := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return it.isImportant && it.isRegexp && !it.isExclude }), mapDomain) + importantExcludeDomain := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return it.isImportant && !it.isRegexp && it.isExclude }), mapDomain) + importantExcludeDomainRegex := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return it.isImportant && it.isRegexp && it.isExclude }), mapDomain) + domain := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return !it.isImportant && !it.isRegexp && !it.isExclude }), mapDomain) + domainRegex := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return !it.isImportant && it.isRegexp && !it.isExclude }), mapDomain) + excludeDomain := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return !it.isImportant && !it.isRegexp && it.isExclude }), mapDomain) + excludeDomainRegex := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return !it.isImportant && it.isRegexp && it.isExclude }), mapDomain) + currentRule := option.HeadlessRule{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultHeadlessRule{ + AdGuardDomain: domain, + DomainRegex: domainRegex, + }, + } + if len(excludeDomain) > 0 || len(excludeDomainRegex) > 0 { + currentRule = option.HeadlessRule{ + Type: C.RuleTypeLogical, + LogicalOptions: option.LogicalHeadlessRule{ + Mode: C.LogicalTypeAnd, + Rules: []option.HeadlessRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultHeadlessRule{ + AdGuardDomain: excludeDomain, + DomainRegex: excludeDomainRegex, + Invert: true, + }, + }, + currentRule, + }, + }, + } + } + if len(importantDomain) > 0 || len(importantDomainRegex) > 0 { + currentRule = option.HeadlessRule{ + Type: C.RuleTypeLogical, + LogicalOptions: option.LogicalHeadlessRule{ + Mode: C.LogicalTypeOr, + Rules: []option.HeadlessRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultHeadlessRule{ + AdGuardDomain: importantDomain, + DomainRegex: importantDomainRegex, + }, + }, + currentRule, + }, + }, + } + } + if len(importantExcludeDomain) > 0 || len(importantExcludeDomainRegex) > 0 { + currentRule = option.HeadlessRule{ + Type: C.RuleTypeLogical, + LogicalOptions: option.LogicalHeadlessRule{ + Mode: C.LogicalTypeAnd, + Rules: []option.HeadlessRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultHeadlessRule{ + AdGuardDomain: importantExcludeDomain, + DomainRegex: importantExcludeDomainRegex, + Invert: true, + }, + }, + currentRule, + }, + }, + } + } + if ignoredLines > 0 { + logger.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines) + } + return []option.HeadlessRule{currentRule}, nil +} + +var ErrInvalid = E.New("invalid binary AdGuard rule-set") + +func FromOptions(rules []option.HeadlessRule) ([]byte, error) { + if len(rules) != 1 { + return nil, ErrInvalid + } + rule := rules[0] + var ( + importantDomain []string + importantDomainRegex []string + importantExcludeDomain []string + importantExcludeDomainRegex []string + domain []string + domainRegex []string + excludeDomain []string + excludeDomainRegex []string + ) +parse: + for { + switch rule.Type { + case C.RuleTypeLogical: + if !(len(rule.LogicalOptions.Rules) == 2 && rule.LogicalOptions.Rules[0].Type == C.RuleTypeDefault) { + return nil, ErrInvalid + } + if rule.LogicalOptions.Mode == C.LogicalTypeAnd && rule.LogicalOptions.Rules[0].DefaultOptions.Invert { + if len(importantExcludeDomain) == 0 && len(importantExcludeDomainRegex) == 0 { + importantExcludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain + importantExcludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex + if len(importantExcludeDomain)+len(importantExcludeDomainRegex) == 0 { + return nil, ErrInvalid + } + } else { + excludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain + excludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex + if len(excludeDomain)+len(excludeDomainRegex) == 0 { + return nil, ErrInvalid + } + } + } else if rule.LogicalOptions.Mode == C.LogicalTypeOr && !rule.LogicalOptions.Rules[0].DefaultOptions.Invert { + importantDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain + importantDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex + if len(importantDomain)+len(importantDomainRegex) == 0 { + return nil, ErrInvalid + } + } else { + return nil, ErrInvalid + } + rule = rule.LogicalOptions.Rules[1] + case C.RuleTypeDefault: + domain = rule.DefaultOptions.AdGuardDomain + domainRegex = rule.DefaultOptions.DomainRegex + if len(domain)+len(domainRegex) == 0 { + return nil, ErrInvalid + } + break parse + } + } + var output bytes.Buffer + for _, ruleLine := range importantDomain { + output.WriteString(ruleLine) + output.WriteString("$important\n") + } + for _, ruleLine := range importantDomainRegex { + output.WriteString("/") + output.WriteString(ruleLine) + output.WriteString("/$important\n") + + } + for _, ruleLine := range importantExcludeDomain { + output.WriteString("@@") + output.WriteString(ruleLine) + output.WriteString("$important\n") + } + for _, ruleLine := range importantExcludeDomainRegex { + output.WriteString("@@/") + output.WriteString(ruleLine) + output.WriteString("/$important\n") + } + for _, ruleLine := range domain { + output.WriteString(ruleLine) + output.WriteString("\n") + } + for _, ruleLine := range domainRegex { + output.WriteString("/") + output.WriteString(ruleLine) + output.WriteString("/\n") + } + for _, ruleLine := range excludeDomain { + output.WriteString("@@") + output.WriteString(ruleLine) + output.WriteString("\n") + } + for _, ruleLine := range excludeDomainRegex { + output.WriteString("@@/") + output.WriteString(ruleLine) + output.WriteString("/\n") + } + return output.Bytes(), nil +} + +func ignoreIPCIDRRegexp(ruleLine string) bool { + if strings.HasPrefix(ruleLine, "(http?:\\/\\/)") { + ruleLine = ruleLine[12:] + } else if strings.HasPrefix(ruleLine, "(https?:\\/\\/)") { + ruleLine = ruleLine[13:] + } else if strings.HasPrefix(ruleLine, "^") { + ruleLine = ruleLine[1:] + } + return common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)) == nil || + common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "."), 10, 8)) == nil +} + +func parseAdGuardHostLine(ruleLine string) (string, error) { + before, after, ok := strings.Cut(ruleLine, " ") + if !ok { + return "", os.ErrInvalid + } + address, err := netip.ParseAddr(before) + if err != nil { + return "", err + } + if !address.IsUnspecified() { + return "", nil + } + domain := after + if !M.IsDomainName(domain) { + return "", E.New("invalid domain name: ", domain) + } + return domain, nil +} + +func parseADGuardIPCIDRLine(ruleLine string) (netip.Prefix, error) { + var isPrefix bool + if strings.HasSuffix(ruleLine, ".") { + isPrefix = true + ruleLine = ruleLine[:len(ruleLine)-1] + } + ruleStringParts := strings.Split(ruleLine, ".") + if len(ruleStringParts) > 4 || len(ruleStringParts) < 4 && !isPrefix { + return netip.Prefix{}, os.ErrInvalid + } + ruleParts := make([]uint8, 0, len(ruleStringParts)) + for _, part := range ruleStringParts { + rulePart, err := strconv.ParseUint(part, 10, 8) + if err != nil { + return netip.Prefix{}, err + } + ruleParts = append(ruleParts, uint8(rulePart)) + } + bitLen := len(ruleParts) * 8 + for len(ruleParts) < 4 { + ruleParts = append(ruleParts, 0) + } + return netip.PrefixFrom(netip.AddrFrom4([4]byte(ruleParts)), bitLen), nil +} diff --git a/common/convertor/adguard/convertor_test.go b/common/convertor/adguard/convertor_test.go new file mode 100644 index 0000000000..b91d9befa2 --- /dev/null +++ b/common/convertor/adguard/convertor_test.go @@ -0,0 +1,145 @@ +package adguard + +import ( + "context" + "strings" + "testing" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/route/rule" + "github.com/sagernet/sing/common/logger" + + "github.com/stretchr/testify/require" +) + +func TestConverter(t *testing.T) { + t.Parallel() + ruleString := `||sagernet.org^$important +@@|sing-box.sagernet.org^$important +||example.org^ +|example.com^ +example.net^ +||example.edu +||example.edu.tw^ +|example.gov +example.arpa +@@|sagernet.example.org^ +` + rules, err := ToOptions(strings.NewReader(ruleString), logger.NOP()) + require.NoError(t, err) + require.Len(t, rules, 1) + rule, err := rule.NewHeadlessRule(context.Background(), rules[0]) + require.NoError(t, err) + matchDomain := []string{ + "example.org", + "www.example.org", + "example.com", + "example.net", + "isexample.net", + "www.example.net", + "example.edu", + "example.edu.cn", + "example.edu.tw", + "www.example.edu", + "www.example.edu.cn", + "example.gov", + "example.gov.cn", + "example.arpa", + "www.example.arpa", + "isexample.arpa", + "example.arpa.cn", + "www.example.arpa.cn", + "isexample.arpa.cn", + "sagernet.org", + "www.sagernet.org", + } + notMatchDomain := []string{ + "example.org.cn", + "notexample.org", + "example.com.cn", + "www.example.com.cn", + "example.net.cn", + "notexample.edu", + "notexample.edu.cn", + "www.example.gov", + "notexample.gov", + "sagernet.example.org", + "sing-box.sagernet.org", + } + for _, domain := range matchDomain { + require.True(t, rule.Match(&adapter.InboundContext{ + Domain: domain, + }), domain) + } + for _, domain := range notMatchDomain { + require.False(t, rule.Match(&adapter.InboundContext{ + Domain: domain, + }), domain) + } + ruleFromOptions, err := FromOptions(rules) + require.NoError(t, err) + require.Equal(t, ruleString, string(ruleFromOptions)) +} + +func TestHosts(t *testing.T) { + t.Parallel() + rules, err := ToOptions(strings.NewReader(` +127.0.0.1 localhost +::1 localhost #[IPv6] +0.0.0.0 google.com +`), logger.NOP()) + require.NoError(t, err) + require.Len(t, rules, 1) + rule, err := rule.NewHeadlessRule(context.Background(), rules[0]) + require.NoError(t, err) + matchDomain := []string{ + "google.com", + } + notMatchDomain := []string{ + "www.google.com", + "notgoogle.com", + "localhost", + } + for _, domain := range matchDomain { + require.True(t, rule.Match(&adapter.InboundContext{ + Domain: domain, + }), domain) + } + for _, domain := range notMatchDomain { + require.False(t, rule.Match(&adapter.InboundContext{ + Domain: domain, + }), domain) + } +} + +func TestSimpleHosts(t *testing.T) { + t.Parallel() + rules, err := ToOptions(strings.NewReader(` +example.com +www.example.org +`), logger.NOP()) + require.NoError(t, err) + require.Len(t, rules, 1) + rule, err := rule.NewHeadlessRule(context.Background(), rules[0]) + require.NoError(t, err) + matchDomain := []string{ + "example.com", + "www.example.org", + } + notMatchDomain := []string{ + "example.com.cn", + "www.example.com", + "notexample.com", + "example.org", + } + for _, domain := range matchDomain { + require.True(t, rule.Match(&adapter.InboundContext{ + Domain: domain, + }), domain) + } + for _, domain := range notMatchDomain { + require.False(t, rule.Match(&adapter.InboundContext{ + Domain: domain, + }), domain) + } +} diff --git a/common/dialer/default.go b/common/dialer/default.go index 4fbad07deb..4ffe00c1a1 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -2,60 +2,134 @@ package dialer import ( "context" + "errors" "net" + "net/netip" + "syscall" "time" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/conntrack" + "github.com/sagernet/sing-box/common/listener" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" + + "github.com/database64128/tfo-go/v2" ) -var _ WireGuardListener = (*DefaultDialer)(nil) +var ( + _ ParallelInterfaceDialer = (*DefaultDialer)(nil) + _ WireGuardListener = (*DefaultDialer)(nil) +) type DefaultDialer struct { - dialer4 tcpDialer - dialer6 tcpDialer - udpDialer4 net.Dialer - udpDialer6 net.Dialer - udpListener net.ListenConfig - udpAddr4 string - udpAddr6 string - isWireGuardListener bool + dialer4 tfo.Dialer + dialer6 tfo.Dialer + udpDialer4 net.Dialer + udpDialer6 net.Dialer + udpListener net.ListenConfig + udpAddr4 string + udpAddr6 string + netns string + connectionManager adapter.ConnectionManager + networkManager adapter.NetworkManager + networkStrategy *C.NetworkStrategy + defaultNetworkStrategy bool + networkType []C.InterfaceType + fallbackNetworkType []C.InterfaceType + networkFallbackDelay time.Duration + networkLastFallback common.TypedValue[time.Time] } -func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDialer, error) { - var dialer net.Dialer - var listener net.ListenConfig +func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) { + connectionManager := service.FromContext[adapter.ConnectionManager](ctx) + networkManager := service.FromContext[adapter.NetworkManager](ctx) + platformInterface := service.FromContext[adapter.PlatformInterface](ctx) + + var ( + dialer net.Dialer + listener net.ListenConfig + interfaceFinder control.InterfaceFinder + networkStrategy *C.NetworkStrategy + defaultNetworkStrategy bool + networkType []C.InterfaceType + fallbackNetworkType []C.InterfaceType + networkFallbackDelay time.Duration + ) + if networkManager != nil { + interfaceFinder = networkManager.InterfaceFinder() + } else { + interfaceFinder = control.NewDefaultInterfaceFinder() + } if options.BindInterface != "" { - var interfaceFinder control.InterfaceFinder - if router != nil { - interfaceFinder = router.InterfaceFinder() - } else { - interfaceFinder = control.NewDefaultInterfaceFinder() + if !(C.IsLinux || C.IsDarwin || C.IsWindows) { + return nil, E.New("`bind_interface` is only supported on Linux, macOS and Windows") } bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1) dialer.Control = control.Append(dialer.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc) - } else if router != nil && router.AutoDetectInterface() { - bindFunc := router.AutoDetectInterfaceFunc() - dialer.Control = control.Append(dialer.Control, bindFunc) - listener.Control = control.Append(listener.Control, bindFunc) - } else if router != nil && router.DefaultInterface() != "" { - bindFunc := control.BindToInterface(router.InterfaceFinder(), router.DefaultInterface(), -1) - dialer.Control = control.Append(dialer.Control, bindFunc) - listener.Control = control.Append(listener.Control, bindFunc) } - if options.RoutingMark != 0 { - dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark)) - listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark)) - } else if router != nil && router.DefaultMark() != 0 { - dialer.Control = control.Append(dialer.Control, control.RoutingMark(router.DefaultMark())) - listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark())) + if options.RoutingMark > 0 { + if !C.IsLinux { + return nil, E.New("`routing_mark` is only supported on Linux") + } + dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false)) + listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false)) + } + disableDefaultBind := options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil + if disableDefaultBind || options.TCPFastOpen { + if options.NetworkStrategy != nil || len(options.NetworkType) > 0 && options.FallbackNetworkType == nil && options.FallbackDelay == 0 { + return nil, E.New("`network_strategy` is conflict with `bind_interface`, `inet4_bind_address`, `inet6_bind_address` and `tcp_fast_open`") + } + } + + if networkManager != nil { + defaultOptions := networkManager.DefaultOptions() + if defaultOptions.BindInterface != "" && !disableDefaultBind { + bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1) + dialer.Control = control.Append(dialer.Control, bindFunc) + listener.Control = control.Append(listener.Control, bindFunc) + } else if networkManager.AutoDetectInterface() && !disableDefaultBind { + if platformInterface != nil { + networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy) + networkType = common.Map(options.NetworkType, option.InterfaceType.Build) + fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build) + if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 { + networkStrategy = defaultOptions.NetworkStrategy + networkType = defaultOptions.NetworkType + fallbackNetworkType = defaultOptions.FallbackNetworkType + } + networkFallbackDelay = time.Duration(options.FallbackDelay) + if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 { + networkFallbackDelay = defaultOptions.FallbackDelay + } + if networkStrategy == nil { + networkStrategy = common.Ptr(C.NetworkStrategyDefault) + defaultNetworkStrategy = true + } + bindFunc := networkManager.ProtectFunc() + dialer.Control = control.Append(dialer.Control, bindFunc) + listener.Control = control.Append(listener.Control, bindFunc) + } else { + bindFunc := networkManager.AutoDetectInterfaceFunc() + dialer.Control = control.Append(dialer.Control, bindFunc) + listener.Control = control.Append(listener.Control, bindFunc) + } + } + if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 { + dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true)) + listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true)) + } + } + if networkManager != nil { + markFunc := networkManager.AutoRedirectOutputMarkFunc() + dialer.Control = control.Append(dialer.Control, markFunc) + listener.Control = control.Append(listener.Control, markFunc) } if options.ReuseAddr { listener.Control = control.Append(listener.Control, control.ReuseAddr()) @@ -64,14 +138,35 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi dialer.Control = control.Append(dialer.Control, control.ProtectPath(options.ProtectPath)) listener.Control = control.Append(listener.Control, control.ProtectPath(options.ProtectPath)) } + if options.BindAddressNoPort { + if !C.IsLinux { + return nil, E.New("`bind_address_no_port` is only supported on Linux") + } + dialer.Control = control.Append(dialer.Control, control.BindAddressNoPort()) + } if options.ConnectTimeout != 0 { dialer.Timeout = time.Duration(options.ConnectTimeout) } else { - dialer.Timeout = C.TCPTimeout + dialer.Timeout = C.TCPConnectTimeout + } + if options.DisableTCPKeepAlive { + dialer.KeepAlive = -1 + dialer.KeepAliveConfig.Enable = false + } else { + keepIdle := time.Duration(options.TCPKeepAlive) + if keepIdle == 0 { + keepIdle = C.TCPKeepAliveInitial + } + keepInterval := time.Duration(options.TCPKeepAliveInterval) + if keepInterval == 0 { + keepInterval = C.TCPKeepAliveInterval + } + dialer.KeepAliveConfig = net.KeepAliveConfig{ + Enable: true, + Idle: keepIdle, + Interval: keepInterval, + } } - // TODO: Add an option to customize the keep alive period - dialer.KeepAlive = C.TCPKeepAliveInitial - dialer.Control = control.Append(dialer.Control, control.SetKeepAlivePeriod(C.TCPKeepAliveInitial, C.TCPKeepAliveInterval)) var udpFragment bool if options.UDPFragment != nil { udpFragment = *options.UDPFragment @@ -88,7 +183,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi udpAddr4 string ) if options.Inet4BindAddress != nil { - bindAddr := options.Inet4BindAddress.Build() + bindAddr := options.Inet4BindAddress.Build(netip.IPv4Unspecified()) dialer4.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()} udpDialer4.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()} udpAddr4 = M.SocksaddrFrom(bindAddr, 0).String() @@ -99,85 +194,197 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi udpAddr6 string ) if options.Inet6BindAddress != nil { - bindAddr := options.Inet6BindAddress.Build() + bindAddr := options.Inet6BindAddress.Build(netip.IPv6Unspecified()) dialer6.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()} udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()} udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String() } if options.TCPMultiPath { - if !go121Available { - return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.") - } - setMultiPathTCP(&dialer4) - } - if options.IsWireGuardListener { - for _, controlFn := range wgControlFns { - listener.Control = control.Append(listener.Control, controlFn) - } - } - tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen) - if err != nil { - return nil, err - } - tcpDialer6, err := newTCPDialer(dialer6, options.TCPFastOpen) - if err != nil { - return nil, err + dialer4.SetMultipathTCP(true) } + tcpDialer4 := tfo.Dialer{Dialer: dialer4, DisableTFO: !options.TCPFastOpen} + tcpDialer6 := tfo.Dialer{Dialer: dialer6, DisableTFO: !options.TCPFastOpen} return &DefaultDialer{ - tcpDialer4, - tcpDialer6, - udpDialer4, - udpDialer6, - listener, - udpAddr4, - udpAddr6, - options.IsWireGuardListener, + dialer4: tcpDialer4, + dialer6: tcpDialer6, + udpDialer4: udpDialer4, + udpDialer6: udpDialer6, + udpListener: listener, + udpAddr4: udpAddr4, + udpAddr6: udpAddr6, + netns: options.NetNs, + connectionManager: connectionManager, + networkManager: networkManager, + networkStrategy: networkStrategy, + defaultNetworkStrategy: defaultNetworkStrategy, + networkType: networkType, + fallbackNetworkType: fallbackNetworkType, + networkFallbackDelay: networkFallbackDelay, }, nil } +func setMarkWrapper(networkManager adapter.NetworkManager, mark uint32, isDefault bool) control.Func { + if networkManager == nil { + return control.RoutingMark(mark) + } + return func(network, address string, conn syscall.RawConn) error { + if networkManager.AutoRedirectOutputMark() != 0 { + if isDefault { + return E.New("`route.default_mark` is conflict with `tun.auto_redirect`") + } else { + return E.New("`routing_mark` is conflict with `tun.auto_redirect`") + } + } + return control.RoutingMark(mark)(network, address, conn) + } +} + 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() { + return nil, E.New("domain not resolved") + } + if d.networkStrategy == nil { + return d.trackConn(listener.ListenNetworkNamespace[net.Conn](d.netns, func() (net.Conn, error) { + switch N.NetworkName(network) { + case N.NetworkUDP: + if !address.IsIPv6() { + return d.udpDialer4.DialContext(ctx, network, address.String()) + } else { + return d.udpDialer6.DialContext(ctx, network, address.String()) + } + } + if !address.IsIPv6() { + return DialSlowContext(&d.dialer4, ctx, network, address) + } else { + return DialSlowContext(&d.dialer6, ctx, network, address) + } + })) + } else { + return d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay) + } +} + +func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network string, address M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { + if strategy == nil { + strategy = d.networkStrategy + } + if strategy == nil { + return d.DialContext(ctx, network, address) + } + if len(interfaceType) == 0 { + interfaceType = d.networkType + } + if len(fallbackInterfaceType) == 0 { + fallbackInterfaceType = d.fallbackNetworkType + } + if fallbackDelay == 0 { + fallbackDelay = d.networkFallbackDelay + } + var dialer net.Dialer + if N.NetworkName(network) == N.NetworkTCP { + dialer = d.dialer4.Dialer + } else { + dialer = d.udpDialer4 + } + fastFallback := time.Since(d.networkLastFallback.Load()) < C.TCPTimeout + var ( + conn net.Conn + isPrimary bool + err error + ) + if !fastFallback { + conn, isPrimary, err = d.dialParallelInterface(ctx, dialer, network, address.String(), *strategy, interfaceType, fallbackInterfaceType, fallbackDelay) + } else { + conn, isPrimary, err = d.dialParallelInterfaceFastFallback(ctx, dialer, network, address.String(), *strategy, interfaceType, fallbackInterfaceType, fallbackDelay, d.networkLastFallback.Store) } - switch N.NetworkName(network) { - case N.NetworkUDP: - if !address.IsIPv6() { - return trackConn(d.udpDialer4.DialContext(ctx, network, address.String())) + if err != nil { + // bind interface failed on legacy xiaomi systems + if d.defaultNetworkStrategy && errors.Is(err, syscall.EPERM) { + d.networkStrategy = nil + return d.DialContext(ctx, network, address) } else { - return trackConn(d.udpDialer6.DialContext(ctx, network, address.String())) + return nil, err } } - if !address.IsIPv6() { - return trackConn(DialSlowContext(&d.dialer4, ctx, network, address)) - } else { - return trackConn(DialSlowContext(&d.dialer6, ctx, network, address)) + if !fastFallback && !isPrimary { + d.networkLastFallback.Store(time.Now()) } + return d.trackConn(conn, nil) } func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - if destination.IsIPv6() { - return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)) - } else if destination.IsIPv4() && !destination.Addr.IsUnspecified() { - return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4)) + if d.networkStrategy == nil { + return d.trackPacketConn(listener.ListenNetworkNamespace[net.PacketConn](d.netns, func() (net.PacketConn, error) { + if destination.IsIPv6() { + return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6) + } else if destination.IsIPv4() && !destination.Addr.IsUnspecified() { + return d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4) + } else { + return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4) + } + })) } else { - return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)) + return d.ListenSerialInterfacePacket(ctx, destination, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay) + } +} + +func (d *DefaultDialer) DialerForICMPDestination(destination netip.Addr) net.Dialer { + if !destination.Is6() { + return d.dialer4.Dialer + } else { + return d.dialer6.Dialer + } +} + +func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) { + if strategy == nil { + strategy = d.networkStrategy + } + if strategy == nil { + return d.ListenPacket(ctx, destination) + } + if len(interfaceType) == 0 { + interfaceType = d.networkType + } + if len(fallbackInterfaceType) == 0 { + fallbackInterfaceType = d.fallbackNetworkType + } + if fallbackDelay == 0 { + fallbackDelay = d.networkFallbackDelay + } + network := N.NetworkUDP + if destination.IsIPv4() && !destination.Addr.IsUnspecified() { + network += "4" + } + packetConn, err := d.listenSerialInterfacePacket(ctx, d.udpListener, network, "", *strategy, interfaceType, fallbackInterfaceType, fallbackDelay) + if err != nil { + // bind interface failed on legacy xiaomi systems + if d.defaultNetworkStrategy && errors.Is(err, syscall.EPERM) { + d.networkStrategy = nil + return d.ListenPacket(ctx, destination) + } else { + return nil, err + } } + return d.trackPacketConn(packetConn, nil) } -func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) { - return trackPacketConn(d.udpListener.ListenPacket(context.Background(), network, address)) +func (d *DefaultDialer) WireGuardControl() control.Func { + return d.udpListener.Control } -func trackConn(conn net.Conn, err error) (net.Conn, error) { - if !conntrack.Enabled || err != nil { +func (d *DefaultDialer) trackConn(conn net.Conn, err error) (net.Conn, error) { + if d.connectionManager == nil || err != nil { return conn, err } - return conntrack.NewConn(conn) + return d.connectionManager.TrackConn(conn), nil } -func trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) { - if !conntrack.Enabled || err != nil { +func (d *DefaultDialer) trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) { + if d.connectionManager == nil || err != nil { return conn, err } - return conntrack.NewPacketConn(conn) + return d.connectionManager.TrackPacketConn(conn), nil } diff --git a/common/dialer/default_go1.20.go b/common/dialer/default_go1.20.go deleted file mode 100644 index 8c3507c213..0000000000 --- a/common/dialer/default_go1.20.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build go1.20 - -package dialer - -import ( - "net" - - "github.com/sagernet/tfo-go" -) - -type tcpDialer = tfo.Dialer - -func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) { - return tfo.Dialer{Dialer: dialer, DisableTFO: !tfoEnabled}, nil -} diff --git a/common/dialer/default_go1.21.go b/common/dialer/default_go1.21.go deleted file mode 100644 index 6ecb5b2572..0000000000 --- a/common/dialer/default_go1.21.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build go1.21 - -package dialer - -import "net" - -const go121Available = true - -func setMultiPathTCP(dialer *net.Dialer) { - dialer.SetMultipathTCP(true) -} diff --git a/common/dialer/default_nongo1.20.go b/common/dialer/default_nongo1.20.go deleted file mode 100644 index 215024245a..0000000000 --- a/common/dialer/default_nongo1.20.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build !go1.20 - -package dialer - -import ( - "net" - - E "github.com/sagernet/sing/common/exceptions" -) - -type tcpDialer = net.Dialer - -func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) { - if tfoEnabled { - return dialer, E.New("TCP Fast Open requires go1.20, please recompile your binary.") - } - return dialer, nil -} diff --git a/common/dialer/default_nongo1.21.go b/common/dialer/default_nongo1.21.go deleted file mode 100644 index 386d50dd02..0000000000 --- a/common/dialer/default_nongo1.21.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build !go1.21 - -package dialer - -import ( - "net" -) - -const go121Available = false - -func setMultiPathTCP(dialer *net.Dialer) { -} diff --git a/common/dialer/default_parallel_interface.go b/common/dialer/default_parallel_interface.go new file mode 100644 index 0000000000..e91abc2898 --- /dev/null +++ b/common/dialer/default_parallel_interface.go @@ -0,0 +1,248 @@ +package dialer + +import ( + "context" + "net" + "time" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" + N "github.com/sagernet/sing/common/network" +) + +func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, bool, error) { + primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType) + if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { + return nil, false, E.New("no available network interface") + } + defaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface() + if fallbackDelay == 0 { + fallbackDelay = N.DefaultFallbackDelay + } + returned := make(chan struct{}) + defer close(returned) + type dialResult struct { + net.Conn + error + primary bool + } + results := make(chan dialResult) // unbuffered + startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) { + perNetDialer := dialer + if defaultInterface == nil || iif.Index != defaultInterface.Index { + perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index)) + } + conn, err := perNetDialer.DialContext(ctx, network, addr) + if err != nil { + select { + case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Index, ")"), primary: primary}: + case <-returned: + } + } else { + select { + case results <- dialResult{Conn: conn, primary: primary}: + case <-returned: + conn.Close() + } + } + } + primaryCtx, primaryCancel := context.WithCancel(ctx) + defer primaryCancel() + for _, iif := range primaryInterfaces { + go startRacer(primaryCtx, true, iif) + } + var ( + fallbackTimer *time.Timer + fallbackChan <-chan time.Time + ) + if len(fallbackInterfaces) > 0 { + fallbackTimer = time.NewTimer(fallbackDelay) + defer fallbackTimer.Stop() + fallbackChan = fallbackTimer.C + } + var errors []error + for { + select { + case <-fallbackChan: + fallbackCtx, fallbackCancel := context.WithCancel(ctx) + defer fallbackCancel() + for _, iif := range fallbackInterfaces { + go startRacer(fallbackCtx, false, iif) + } + 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...) + } + if res.primary && fallbackTimer != nil && fallbackTimer.Stop() { + fallbackTimer.Reset(0) + } + } + } +} + +func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration, resetFastFallback func(time.Time)) (net.Conn, bool, error) { + primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType) + if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { + return nil, false, E.New("no available network interface") + } + defaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface() + if fallbackDelay == 0 { + fallbackDelay = N.DefaultFallbackDelay + } + returned := make(chan struct{}) + defer close(returned) + type dialResult struct { + net.Conn + error + primary bool + } + startAt := time.Now() + results := make(chan dialResult) // unbuffered + startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) { + perNetDialer := dialer + if defaultInterface == nil || iif.Index != defaultInterface.Index { + perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index)) + } + conn, err := perNetDialer.DialContext(ctx, network, addr) + if err != nil { + select { + case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Index, ")"), primary: primary}: + case <-returned: + } + } else { + select { + case results <- dialResult{Conn: conn, primary: primary}: + case <-returned: + if primary && time.Since(startAt) <= fallbackDelay { + resetFastFallback(time.Time{}) + } + conn.Close() + } + } + } + for _, iif := range primaryInterfaces { + go startRacer(ctx, true, iif) + } + fallbackCtx, fallbackCancel := context.WithCancel(ctx) + defer fallbackCancel() + for _, iif := range fallbackInterfaces { + 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...) + } + } + 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) { + primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType) + if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { + return nil, E.New("no available network interface") + } + defaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface() + var errors []error + for _, primaryInterface := range primaryInterfaces { + perNetListener := listener + if defaultInterface == nil || primaryInterface.Index != defaultInterface.Index { + perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, primaryInterface.Name, primaryInterface.Index)) + } + conn, err := perNetListener.ListenPacket(ctx, network, addr) + if err == nil { + return conn, nil + } + errors = append(errors, E.Cause(err, "listen ", primaryInterface.Name, " (", primaryInterface.Index, ")")) + } + for _, fallbackInterface := range fallbackInterfaces { + perNetListener := listener + if defaultInterface == nil || fallbackInterface.Index != defaultInterface.Index { + perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, fallbackInterface.Name, fallbackInterface.Index)) + } + conn, err := perNetListener.ListenPacket(ctx, network, addr) + if err == nil { + return conn, nil + } + errors = append(errors, E.Cause(err, "listen ", fallbackInterface.Name, " (", fallbackInterface.Index, ")")) + } + return nil, E.Errors(errors...) +} + +func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) { + interfaces := networkManager.NetworkInterfaces() + myInterface := networkManager.InterfaceMonitor().MyInterface() + if myInterface != "" { + interfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool { + return it.Name != myInterface + }) + } + switch strategy { + case C.NetworkStrategyDefault: + if len(interfaceType) == 0 { + defaultIf := networkManager.InterfaceMonitor().DefaultInterface() + if defaultIf != nil { + for _, iif := range interfaces { + if iif.Index == defaultIf.Index { + primaryInterfaces = append(primaryInterfaces, iif) + } + } + } else { + primaryInterfaces = interfaces + } + } else { + primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool { + return common.Contains(interfaceType, it.Type) + }) + } + case C.NetworkStrategyHybrid: + if len(interfaceType) == 0 { + primaryInterfaces = interfaces + } else { + primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool { + return common.Contains(interfaceType, it.Type) + }) + } + case C.NetworkStrategyFallback: + if len(interfaceType) == 0 { + defaultIf := networkManager.InterfaceMonitor().DefaultInterface() + if defaultIf != nil { + for _, iif := range interfaces { + if iif.Index == defaultIf.Index { + primaryInterfaces = append(primaryInterfaces, iif) + break + } + } + } else { + primaryInterfaces = interfaces + } + } else { + primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool { + return common.Contains(interfaceType, it.Type) + }) + } + if len(fallbackInterfaceType) == 0 { + fallbackInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool { + return !common.Any(primaryInterfaces, func(iif adapter.NetworkInterface) bool { + return it.Index == iif.Index + }) + }) + } else { + fallbackInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool { + return common.Contains(fallbackInterfaceType, iif.Type) + }) + } + } + return primaryInterfaces, fallbackInterfaces +} diff --git a/common/dialer/default_parallel_network.go b/common/dialer/default_parallel_network.go new file mode 100644 index 0000000000..006e574759 --- /dev/null +++ b/common/dialer/default_parallel_network.go @@ -0,0 +1,161 @@ +package dialer + +import ( + "context" + "net" + "net/netip" + "time" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func DialSerialNetwork(ctx context.Context, dialer N.Dialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { + if len(destinationAddresses) == 0 { + if !destination.IsIP() { + panic("invalid usage") + } + destinationAddresses = []netip.Addr{destination.Addr} + } + if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel { + return parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) + } + var errors []error + if parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel { + for _, address := range destinationAddresses { + conn, err := parallelDialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay) + if err == nil { + return conn, nil + } + errors = append(errors, err) + } + } else { + for _, address := range destinationAddresses { + conn, err := dialer.DialContext(ctx, network, M.SocksaddrFrom(address, destination.Port)) + if err == nil { + return conn, nil + } + errors = append(errors, err) + } + } + return nil, E.Errors(errors...) +} + +func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, preferIPv6 bool, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { + if len(destinationAddresses) == 0 { + if !destination.IsIP() { + panic("invalid usage") + } + destinationAddresses = []netip.Addr{destination.Addr} + } + + if fallbackDelay == 0 { + fallbackDelay = N.DefaultFallbackDelay + } + + returned := make(chan struct{}) + defer close(returned) + + addresses4 := common.Filter(destinationAddresses, func(address netip.Addr) bool { + return address.Is4() || address.Is4In6() + }) + addresses6 := common.Filter(destinationAddresses, func(address netip.Addr) bool { + return address.Is6() && !address.Is4In6() + }) + if len(addresses4) == 0 || len(addresses6) == 0 { + return DialSerialNetwork(ctx, dialer, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) + } + var primaries, fallbacks []netip.Addr + if preferIPv6 { + primaries = addresses6 + fallbacks = addresses4 + } else { + primaries = addresses4 + fallbacks = addresses6 + } + type dialResult struct { + net.Conn + error + primary bool + done bool + } + results := make(chan dialResult) // unbuffered + startRacer := func(ctx context.Context, primary bool) { + ras := primaries + if !primary { + ras = fallbacks + } + c, err := DialSerialNetwork(ctx, dialer, network, destination, ras, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) + select { + case results <- dialResult{Conn: c, error: err, primary: primary, done: true}: + case <-returned: + if c != nil { + c.Close() + } + } + } + var primary, fallback dialResult + primaryCtx, primaryCancel := context.WithCancel(ctx) + defer primaryCancel() + go startRacer(primaryCtx, true) + fallbackTimer := time.NewTimer(fallbackDelay) + defer fallbackTimer.Stop() + for { + select { + case <-fallbackTimer.C: + fallbackCtx, fallbackCancel := context.WithCancel(ctx) + defer fallbackCancel() + go startRacer(fallbackCtx, false) + + case res := <-results: + if res.error == nil { + return res.Conn, nil + } + if res.primary { + primary = res + } else { + fallback = res + } + if primary.done && fallback.done { + return nil, primary.error + } + if res.primary && fallbackTimer.Stop() { + fallbackTimer.Reset(0) + } + } + } +} + +func ListenSerialNetworkPacket(ctx context.Context, dialer N.Dialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { + if len(destinationAddresses) == 0 { + if !destination.IsIP() { + panic("invalid usage") + } + destinationAddresses = []netip.Addr{destination.Addr} + } + if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel { + return parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) + } + var errors []error + if parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel { + for _, address := range destinationAddresses { + conn, err := parallelDialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay) + if err == nil { + return conn, address, nil + } + errors = append(errors, err) + } + } else { + for _, address := range destinationAddresses { + conn, err := dialer.ListenPacket(ctx, M.SocksaddrFrom(address, destination.Port)) + if err == nil { + return conn, address, nil + } + errors = append(errors, err) + } + } + return nil, netip.Addr{}, E.Errors(errors...) +} diff --git a/common/dialer/detour.go b/common/dialer/detour.go index 81600913d4..b2fc3efa0b 100644 --- a/common/dialer/detour.go +++ b/common/dialer/detour.go @@ -6,37 +6,75 @@ import ( "sync" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) +type DirectDialer interface { + IsEmpty() bool +} + type DetourDialer struct { - router adapter.Router - detour string - dialer N.Dialer - initOnce sync.Once - initErr error + outboundManager adapter.OutboundManager + detour string + defaultOutbound bool + disableEmptyDirectCheck bool + dialer N.Dialer + initOnce sync.Once + initErr error } -func NewDetour(router adapter.Router, detour string) N.Dialer { - return &DetourDialer{router: router, detour: detour} +func NewDetour(outboundManager adapter.OutboundManager, detour string, disableEmptyDirectCheck bool) N.Dialer { + return &DetourDialer{ + outboundManager: outboundManager, + detour: detour, + disableEmptyDirectCheck: disableEmptyDirectCheck, + } } -func (d *DetourDialer) Start() error { - _, err := d.Dialer() - return err +func NewDefaultOutboundDetour(outboundManager adapter.OutboundManager) N.Dialer { + return &DetourDialer{ + outboundManager: outboundManager, + defaultOutbound: true, + } +} + +func InitializeDetour(dialer N.Dialer) error { + detourDialer, isDetour := common.Cast[*DetourDialer](dialer) + if !isDetour { + return nil + } + return common.Error(detourDialer.Dialer()) } func (d *DetourDialer) Dialer() (N.Dialer, error) { - d.initOnce.Do(func() { + d.initOnce.Do(d.init) + return d.dialer, d.initErr +} + +func (d *DetourDialer) init() { + var dialer adapter.Outbound + if d.detour != "" { var loaded bool - d.dialer, loaded = d.router.Outbound(d.detour) + dialer, loaded = d.outboundManager.Outbound(d.detour) if !loaded { d.initErr = E.New("outbound detour not found: ", d.detour) + return } - }) - return d.dialer, d.initErr + } else { + dialer = d.outboundManager.Default() + } + if !d.defaultOutbound && !d.disableEmptyDirectCheck { + if directDialer, isDirect := dialer.(DirectDialer); isDirect { + if directDialer.IsEmpty() { + d.initErr = E.New("detour to an empty direct outbound makes no sense") + return + } + } + } + d.dialer = dialer } func (d *DetourDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index a1721b281f..f0e8043093 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -1,41 +1,161 @@ package dialer import ( + "context" + "net" + "net/netip" "time" "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-dns" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" ) -func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error) { - if options.IsWireGuardListener { - return NewDefault(router, options) - } - if router == nil { - return NewDefault(nil, options) - } +type Options struct { + Context context.Context + Options option.DialerOptions + RemoteIsDomain bool + DirectResolver bool + ResolverOnDetour bool + NewDialer bool + DisableEmptyDirectCheck bool + DirectOutbound bool + DefaultOutbound bool +} + +// TODO: merge with NewWithOptions +func New(ctx context.Context, options option.DialerOptions, remoteIsDomain bool) (N.Dialer, error) { + return NewWithOptions(Options{ + Context: ctx, + Options: options, + RemoteIsDomain: remoteIsDomain, + }) +} + +func NewWithOptions(options Options) (N.Dialer, error) { + dialOptions := options.Options var ( dialer N.Dialer err error ) - if options.Detour == "" { - dialer, err = NewDefault(router, options) + hasDetour := dialOptions.Detour != "" || options.DefaultOutbound + if dialOptions.Detour != "" { + outboundManager := service.FromContext[adapter.OutboundManager](options.Context) + if outboundManager == nil { + return nil, E.New("missing outbound manager") + } + dialer = NewDetour(outboundManager, dialOptions.Detour, options.DisableEmptyDirectCheck) + } else if options.DefaultOutbound { + outboundManager := service.FromContext[adapter.OutboundManager](options.Context) + if outboundManager == nil { + return nil, E.New("missing outbound manager") + } + dialer = NewDefaultOutboundDetour(outboundManager) + } else { + dialer, err = NewDefault(options.Context, dialOptions) if err != nil { return nil, err } - } else { - dialer = NewDetour(router, options.Detour) } - domainStrategy := dns.DomainStrategy(options.DomainStrategy) - if domainStrategy != dns.DomainStrategyAsIS || options.Detour == "" { + if options.RemoteIsDomain && (!hasDetour || options.ResolverOnDetour || dialOptions.DomainResolver != nil && dialOptions.DomainResolver.Server != "") { + networkManager := service.FromContext[adapter.NetworkManager](options.Context) + dnsTransport := service.FromContext[adapter.DNSTransportManager](options.Context) + var defaultOptions adapter.NetworkOptions + if networkManager != nil { + defaultOptions = networkManager.DefaultOptions() + } + var ( + server string + dnsQueryOptions adapter.DNSQueryOptions + resolveFallbackDelay time.Duration + ) + if dialOptions.DomainResolver != nil && dialOptions.DomainResolver.Server != "" { + var transport adapter.DNSTransport + if !options.DirectResolver { + var loaded bool + transport, loaded = dnsTransport.Transport(dialOptions.DomainResolver.Server) + if !loaded { + return nil, E.New("domain resolver not found: " + dialOptions.DomainResolver.Server) + } + } + var strategy C.DomainStrategy + if dialOptions.DomainResolver.Strategy != option.DomainStrategy(C.DomainStrategyAsIS) { + strategy = C.DomainStrategy(dialOptions.DomainResolver.Strategy) + } else if + //nolint:staticcheck + dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) { + //nolint:staticcheck + strategy = C.DomainStrategy(dialOptions.DomainStrategy) + deprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions) + } + server = dialOptions.DomainResolver.Server + dnsQueryOptions = adapter.DNSQueryOptions{ + Transport: transport, + Strategy: strategy, + Timeout: time.Duration(dialOptions.DomainResolver.Timeout), + DisableCache: dialOptions.DomainResolver.DisableCache, + DisableOptimisticCache: dialOptions.DomainResolver.DisableOptimisticCache, + RewriteTTL: dialOptions.DomainResolver.RewriteTTL, + ClientSubnet: dialOptions.DomainResolver.ClientSubnet.Build(netip.Prefix{}), + } + resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay) + } else if options.DirectResolver { + return nil, E.New("missing domain resolver for domain server address") + } else { + if defaultOptions.DomainResolver != "" { + dnsQueryOptions = defaultOptions.DomainResolveOptions + transport, loaded := dnsTransport.Transport(defaultOptions.DomainResolver) + if !loaded { + return nil, E.New("default domain resolver not found: " + defaultOptions.DomainResolver) + } + dnsQueryOptions.Transport = transport + resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay) + } else { + transports := dnsTransport.Transports() + if len(transports) < 2 { + dnsQueryOptions.Transport = dnsTransport.Default() + } else if options.NewDialer { + return nil, E.New("missing domain resolver for domain server address") + } else { + deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver) + } + } + if + //nolint:staticcheck + dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) { + //nolint:staticcheck + dnsQueryOptions.Strategy = C.DomainStrategy(dialOptions.DomainStrategy) + deprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions) + } + } dialer = NewResolveDialer( - router, + options.Context, dialer, - options.Detour == "" && !options.TCPFastOpen, - domainStrategy, - time.Duration(options.FallbackDelay)) + dialOptions.Detour == "" && !dialOptions.TCPFastOpen, + server, + dnsQueryOptions, + resolveFallbackDelay, + ) } return dialer, nil } + +type ParallelInterfaceDialer interface { + N.Dialer + DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) + ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) +} + +type ParallelNetworkDialer interface { + DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) + ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) +} + +type PacketDialerWithDestination interface { + ListenPacketWithDestination(ctx context.Context, destination M.Socksaddr) (net.PacketConn, netip.Addr, error) +} diff --git a/common/dialer/resolve.go b/common/dialer/resolve.go index f2ee50db5a..21fe38d5cf 100644 --- a/common/dialer/resolve.go +++ b/common/dialer/resolve.go @@ -3,85 +3,192 @@ package dialer import ( "context" "net" - "net/netip" + "sync" "time" "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" ) -type ResolveDialer struct { +var ( + _ N.Dialer = (*resolveDialer)(nil) + _ ParallelInterfaceDialer = (*resolveParallelNetworkDialer)(nil) +) + +type ResolveDialer interface { + N.Dialer + QueryOptions() adapter.DNSQueryOptions +} + +type ParallelInterfaceResolveDialer interface { + ParallelInterfaceDialer + QueryOptions() adapter.DNSQueryOptions +} + +type resolveDialer struct { + transport adapter.DNSTransportManager + router adapter.DNSRouter dialer N.Dialer parallel bool - router adapter.Router - strategy dns.DomainStrategy + server string + initOnce sync.Once + initErr error + queryOptions adapter.DNSQueryOptions fallbackDelay time.Duration } -func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) *ResolveDialer { - return &ResolveDialer{ - dialer, - parallel, - router, - strategy, - fallbackDelay, +func NewResolveDialer(ctx context.Context, dialer N.Dialer, parallel bool, server string, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ResolveDialer { + if parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel { + return &resolveParallelNetworkDialer{ + resolveDialer{ + transport: service.FromContext[adapter.DNSTransportManager](ctx), + router: service.FromContext[adapter.DNSRouter](ctx), + dialer: dialer, + parallel: parallel, + server: server, + queryOptions: queryOptions, + fallbackDelay: fallbackDelay, + }, + parallelDialer, + } + } + return &resolveDialer{ + transport: service.FromContext[adapter.DNSTransportManager](ctx), + router: service.FromContext[adapter.DNSRouter](ctx), + dialer: dialer, + parallel: parallel, + server: server, + queryOptions: queryOptions, + fallbackDelay: fallbackDelay, + } +} + +type resolveParallelNetworkDialer struct { + resolveDialer + dialer ParallelInterfaceDialer +} + +func (d *resolveDialer) initialize() error { + d.initOnce.Do(d.initServer) + return d.initErr +} + +func (d *resolveDialer) initServer() { + if d.server == "" { + return } + transport, loaded := d.transport.Transport(d.server) + if !loaded { + d.initErr = E.New("domain resolver not found: " + d.server) + return + } + d.queryOptions.Transport = transport } -func (d *ResolveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - if !destination.IsFqdn() { +func (d *resolveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + err := d.initialize() + if err != nil { + return nil, err + } + if !destination.IsDomain() { return d.dialer.DialContext(ctx, network, destination) } - ctx, metadata := adapter.ExtendContext(ctx) ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) - metadata.Destination = destination - metadata.Domain = "" - var addresses []netip.Addr - var err error - if d.strategy == dns.DomainStrategyAsIS { - addresses, err = d.router.LookupDefault(ctx, destination.Fqdn) - } else { - addresses, err = d.router.Lookup(ctx, destination.Fqdn, d.strategy) - } + addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions) if err != nil { return nil, err } if d.parallel { - return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, d.fallbackDelay) + return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.queryOptions.Strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay) } else { return N.DialSerial(ctx, d.dialer, network, destination, addresses) } } -func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - if !destination.IsFqdn() { +func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + err := d.initialize() + if err != nil { + return nil, err + } + if !destination.IsDomain() { return d.dialer.ListenPacket(ctx, destination) } - ctx, metadata := adapter.ExtendContext(ctx) ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) - metadata.Destination = destination - metadata.Domain = "" - var addresses []netip.Addr - var err error - if d.strategy == dns.DomainStrategyAsIS { - addresses, err = d.router.LookupDefault(ctx, destination.Fqdn) + addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions) + if err != nil { + return nil, err + } + conn, destinationAddress, err := N.ListenSerial(ctx, d.dialer, destination, addresses) + if err != nil { + return nil, err + } + return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil +} + +func (d *resolveDialer) QueryOptions() adapter.DNSQueryOptions { + return d.queryOptions +} + +func (d *resolveDialer) Upstream() any { + return d.dialer +} + +func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { + err := d.initialize() + if err != nil { + return nil, err + } + if !destination.IsDomain() { + return d.dialer.DialContext(ctx, network, destination) + } + ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) + addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions) + if err != nil { + return nil, err + } + if fallbackDelay == 0 { + fallbackDelay = d.fallbackDelay + } + if d.parallel { + return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.queryOptions.Strategy == C.DomainStrategyPreferIPv6, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } else { - addresses, err = d.router.Lookup(ctx, destination.Fqdn, d.strategy) + return DialSerialNetwork(ctx, d.dialer, network, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } +} + +func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) { + err := d.initialize() if err != nil { return nil, err } - conn, destinationAddress, err := N.ListenSerial(ctx, d.dialer, destination, addresses) + if !destination.IsDomain() { + return d.dialer.ListenPacket(ctx, destination) + } + ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) + addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions) + if err != nil { + return nil, err + } + if fallbackDelay == 0 { + fallbackDelay = d.fallbackDelay + } + conn, destinationAddress, err := ListenSerialNetworkPacket(ctx, d.dialer, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) if err != nil { return nil, err } return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil } -func (d *ResolveDialer) Upstream() any { +func (d *resolveParallelNetworkDialer) QueryOptions() adapter.DNSQueryOptions { + return d.queryOptions +} + +func (d *resolveParallelNetworkDialer) Upstream() any { return d.dialer } diff --git a/common/dialer/router.go b/common/dialer/router.go index 2531607753..801a36b1dc 100644 --- a/common/dialer/router.go +++ b/common/dialer/router.go @@ -7,32 +7,27 @@ import ( "github.com/sagernet/sing-box/adapter" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" ) -type RouterDialer struct { - router adapter.Router +type DefaultOutboundDialer struct { + outbound adapter.OutboundManager } -func NewRouter(router adapter.Router) N.Dialer { - return &RouterDialer{router: router} +func NewDefaultOutbound(ctx context.Context) N.Dialer { + return &DefaultOutboundDialer{ + outbound: service.FromContext[adapter.OutboundManager](ctx), + } } -func (d *RouterDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - dialer, err := d.router.DefaultOutbound(network) - if err != nil { - return nil, err - } - return dialer.DialContext(ctx, network, destination) +func (d *DefaultOutboundDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + return d.outbound.Default().DialContext(ctx, network, destination) } -func (d *RouterDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - dialer, err := d.router.DefaultOutbound(N.NetworkUDP) - if err != nil { - return nil, err - } - return dialer.ListenPacket(ctx, destination) +func (d *DefaultOutboundDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return d.outbound.Default().ListenPacket(ctx, destination) } -func (d *RouterDialer) Upstream() any { - return d.router +func (d *DefaultOutboundDialer) Upstream() any { + return d.outbound.Default() } diff --git a/common/dialer/tfo.go b/common/dialer/tfo.go index 82e5e9c119..e8e93083e6 100644 --- a/common/dialer/tfo.go +++ b/common/dialer/tfo.go @@ -1,5 +1,3 @@ -//go:build go1.20 - package dialer import ( @@ -8,14 +6,15 @@ import ( "net" "os" "sync" + "sync/atomic" "time" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" - E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" - "github.com/sagernet/tfo-go" + + "github.com/database64128/tfo-go/v2" ) type slowOpenConn struct { @@ -23,13 +22,15 @@ type slowOpenConn struct { ctx context.Context network string destination M.Socksaddr - conn net.Conn + conn atomic.Pointer[net.TCPConn] create chan struct{} + done chan struct{} access sync.Mutex + closeOnce sync.Once err error } -func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP { switch N.NetworkName(network) { case N.NetworkTCP, N.NetworkUDP: @@ -44,26 +45,30 @@ func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, des network: network, destination: destination, create: make(chan struct{}), + done: make(chan struct{}), }, nil } func (c *slowOpenConn) Read(b []byte) (n int, err error) { - if c.conn == nil { - select { - case <-c.create: - if c.err != nil { - return 0, c.err - } - case <-c.ctx.Done(): - return 0, c.ctx.Err() + conn := c.conn.Load() + if conn != nil { + return conn.Read(b) + } + select { + case <-c.create: + if c.err != nil { + return 0, c.err } + return c.conn.Load().Read(b) + case <-c.done: + return 0, os.ErrClosed } - return c.conn.Read(b) } func (c *slowOpenConn) Write(b []byte) (n int, err error) { - if c.conn != nil { - return c.conn.Write(b) + tcpConn := c.conn.Load() + if tcpConn != nil { + return tcpConn.Write(b) } c.access.Lock() defer c.access.Unlock() @@ -72,13 +77,16 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) { if c.err != nil { return 0, c.err } - return c.conn.Write(b) + return c.conn.Load().Write(b) + case <-c.done: + return 0, os.ErrClosed default: } - c.conn, err = c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b) + conn, err := c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b) if err != nil { - c.conn = nil - c.err = E.Cause(err, "dial tcp fast open") + c.err = err + } else { + c.conn.Store(conn.(*net.TCPConn)) } n = len(b) close(c.create) @@ -86,74 +94,87 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) { } func (c *slowOpenConn) Close() error { - return common.Close(c.conn) + c.closeOnce.Do(func() { + close(c.done) + conn := c.conn.Load() + if conn != nil { + conn.Close() + } + }) + return nil } func (c *slowOpenConn) LocalAddr() net.Addr { - if c.conn == nil { + conn := c.conn.Load() + if conn == nil { return M.Socksaddr{} } - return c.conn.LocalAddr() + return conn.LocalAddr() } func (c *slowOpenConn) RemoteAddr() net.Addr { - if c.conn == nil { + conn := c.conn.Load() + if conn == nil { return M.Socksaddr{} } - return c.conn.RemoteAddr() + return conn.RemoteAddr() } func (c *slowOpenConn) SetDeadline(t time.Time) error { - if c.conn == nil { + conn := c.conn.Load() + if conn == nil { return os.ErrInvalid } - return c.conn.SetDeadline(t) + return conn.SetDeadline(t) } func (c *slowOpenConn) SetReadDeadline(t time.Time) error { - if c.conn == nil { + conn := c.conn.Load() + if conn == nil { return os.ErrInvalid } - return c.conn.SetReadDeadline(t) + return conn.SetReadDeadline(t) } func (c *slowOpenConn) SetWriteDeadline(t time.Time) error { - if c.conn == nil { + conn := c.conn.Load() + if conn == nil { return os.ErrInvalid } - return c.conn.SetWriteDeadline(t) + return conn.SetWriteDeadline(t) } func (c *slowOpenConn) Upstream() any { - return c.conn + return common.PtrOrNil(c.conn.Load()) } func (c *slowOpenConn) ReaderReplaceable() bool { - return c.conn != nil + return c.conn.Load() != nil } func (c *slowOpenConn) WriterReplaceable() bool { - return c.conn != nil + return c.conn.Load() != nil } func (c *slowOpenConn) LazyHeadroom() bool { - return c.conn == nil + return c.conn.Load() == nil } func (c *slowOpenConn) NeedHandshake() bool { - return c.conn == nil + return c.conn.Load() == nil } func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) { - if c.conn == nil { + conn := c.conn.Load() + if conn == nil { select { case <-c.create: if c.err != nil { return 0, c.err } - case <-c.ctx.Done(): - return 0, c.ctx.Err() + case <-c.done: + return 0, c.err } } - return bufio.Copy(w, c.conn) + return bufio.Copy(w, c.conn.Load()) } diff --git a/common/dialer/tfo_stub.go b/common/dialer/tfo_stub.go deleted file mode 100644 index 144902e5be..0000000000 --- a/common/dialer/tfo_stub.go +++ /dev/null @@ -1,20 +0,0 @@ -//go:build !go1.20 - -package dialer - -import ( - "context" - "net" - - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - switch N.NetworkName(network) { - case N.NetworkTCP, N.NetworkUDP: - return dialer.DialContext(ctx, network, destination.String()) - default: - return dialer.DialContext(ctx, network, destination.AddrString()) - } -} diff --git a/common/dialer/wireguard.go b/common/dialer/wireguard.go index 195133c650..8a916a5916 100644 --- a/common/dialer/wireguard.go +++ b/common/dialer/wireguard.go @@ -1,9 +1,9 @@ package dialer import ( - "net" + "github.com/sagernet/sing/common/control" ) type WireGuardListener interface { - ListenPacketCompat(network, address string) (net.PacketConn, error) + WireGuardControl() control.Func } diff --git a/common/dialer/wireguard_control.go b/common/dialer/wireguard_control.go deleted file mode 100644 index def86411a5..0000000000 --- a/common/dialer/wireguard_control.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build with_wireguard - -package dialer - -import ( - "github.com/sagernet/wireguard-go/conn" -) - -var _ WireGuardListener = (conn.Listener)(nil) - -var wgControlFns = conn.ControlFns diff --git a/common/dialer/wiregurad_stub.go b/common/dialer/wiregurad_stub.go deleted file mode 100644 index d30c223a95..0000000000 --- a/common/dialer/wiregurad_stub.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !with_wireguard - -package dialer - -import ( - "github.com/sagernet/sing/common/control" -) - -var wgControlFns []control.Func diff --git a/common/geosite/compat_test.go b/common/geosite/compat_test.go new file mode 100644 index 0000000000..9c66aea34c --- /dev/null +++ b/common/geosite/compat_test.go @@ -0,0 +1,229 @@ +package geosite + +import ( + "bufio" + "bytes" + "encoding/binary" + "strings" + "testing" + + "github.com/sagernet/sing/common/varbin" + + "github.com/stretchr/testify/require" +) + +// Old implementation using varbin reflection-based serialization + +func oldWriteString(writer varbin.Writer, value string) error { + //nolint:staticcheck + return varbin.Write(writer, binary.BigEndian, value) +} + +func oldReadString(reader varbin.Reader) (string, error) { + //nolint:staticcheck + return varbin.ReadValue[string](reader, binary.BigEndian) +} + +func oldReadItem(reader varbin.Reader) (Item, error) { + //nolint:staticcheck + return varbin.ReadValue[Item](reader, binary.BigEndian) +} + +func TestStringCompat(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + input string + }{ + {"empty", ""}, + {"single_char", "a"}, + {"ascii", "example.com"}, + {"utf8", "测试域名.中国"}, + {"special_chars", "\x00\xff\n\t"}, + {"127_bytes", strings.Repeat("x", 127)}, + {"128_bytes", strings.Repeat("x", 128)}, + {"16383_bytes", strings.Repeat("x", 16383)}, + {"16384_bytes", strings.Repeat("x", 16384)}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Old write + var oldBuf bytes.Buffer + err := oldWriteString(&oldBuf, tc.input) + require.NoError(t, err) + + // New write + var newBuf bytes.Buffer + err = writeString(&newBuf, tc.input) + require.NoError(t, err) + + // Bytes must match + require.Equal(t, oldBuf.Bytes(), newBuf.Bytes(), + "mismatch for %q\nold: %x\nnew: %x", tc.name, oldBuf.Bytes(), newBuf.Bytes()) + + // New write -> old read + readBack, err := oldReadString(bufio.NewReader(bytes.NewReader(newBuf.Bytes()))) + require.NoError(t, err) + require.Equal(t, tc.input, readBack) + + // Old write -> new read + readBack2, err := readString(bufio.NewReader(bytes.NewReader(oldBuf.Bytes()))) + require.NoError(t, err) + require.Equal(t, tc.input, readBack2) + }) + } +} + +func TestItemCompat(t *testing.T) { + t.Parallel() + + // Note: varbin.Write has a bug where struct values (not pointers) don't write their fields + // because field.CanSet() returns false for non-addressable values. + // The old geosite code passed Item values to varbin.Write, which silently wrote nothing. + // The new code correctly writes Type + Value using manual serialization. + // This test verifies the new serialization format and round-trip correctness. + + cases := []struct { + name string + input Item + }{ + {"domain_empty", Item{Type: RuleTypeDomain, Value: ""}}, + {"domain_normal", Item{Type: RuleTypeDomain, Value: "example.com"}}, + {"domain_suffix", Item{Type: RuleTypeDomainSuffix, Value: ".example.com"}}, + {"domain_keyword", Item{Type: RuleTypeDomainKeyword, Value: "google"}}, + {"domain_regex", Item{Type: RuleTypeDomainRegex, Value: `^.*\.example\.com$`}}, + {"utf8_domain", Item{Type: RuleTypeDomain, Value: "测试.com"}}, + {"long_domain", Item{Type: RuleTypeDomainSuffix, Value: strings.Repeat("a", 200) + ".com"}}, + {"128_bytes_value", Item{Type: RuleTypeDomain, Value: strings.Repeat("x", 128)}}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // New write + var newBuf bytes.Buffer + err := newBuf.WriteByte(byte(tc.input.Type)) + require.NoError(t, err) + err = writeString(&newBuf, tc.input.Value) + require.NoError(t, err) + + // Verify format: Type (1 byte) + Value (uvarint len + bytes) + require.True(t, len(newBuf.Bytes()) >= 1, "output too short") + require.Equal(t, byte(tc.input.Type), newBuf.Bytes()[0], "type byte mismatch") + + // New write -> old read (varbin can read correctly when given addressable target) + readBack, err := oldReadItem(bufio.NewReader(bytes.NewReader(newBuf.Bytes()))) + require.NoError(t, err) + require.Equal(t, tc.input, readBack) + + // New write -> new read + reader := bufio.NewReader(bytes.NewReader(newBuf.Bytes())) + typeByte, err := reader.ReadByte() + require.NoError(t, err) + value, err := readString(reader) + require.NoError(t, err) + require.Equal(t, tc.input, Item{Type: ItemType(typeByte), Value: value}) + }) + } +} + +func TestGeositeWriteReadCompat(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + input map[string][]Item + }{ + { + "empty_map", + map[string][]Item{}, + }, + { + "single_code_empty_items", + map[string][]Item{"test": {}}, + }, + { + "single_code_single_item", + map[string][]Item{"test": {{Type: RuleTypeDomain, Value: "a.com"}}}, + }, + { + "single_code_multi_items", + map[string][]Item{ + "test": { + {Type: RuleTypeDomain, Value: "a.com"}, + {Type: RuleTypeDomainSuffix, Value: ".b.com"}, + {Type: RuleTypeDomainKeyword, Value: "keyword"}, + {Type: RuleTypeDomainRegex, Value: `^.*$`}, + }, + }, + }, + { + "multi_code", + map[string][]Item{ + "cn": {{Type: RuleTypeDomain, Value: "baidu.com"}, {Type: RuleTypeDomainSuffix, Value: ".cn"}}, + "us": {{Type: RuleTypeDomain, Value: "google.com"}}, + "jp": {{Type: RuleTypeDomainSuffix, Value: ".jp"}}, + }, + }, + { + "utf8_values", + map[string][]Item{ + "test": { + {Type: RuleTypeDomain, Value: "测试.中国"}, + {Type: RuleTypeDomainSuffix, Value: ".テスト"}, + }, + }, + }, + { + "large_items", + generateLargeItems(1000), + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Write using new implementation + var buf bytes.Buffer + err := Write(&buf, tc.input) + require.NoError(t, err) + + // Read back and verify + reader, codes, err := NewReader(bytes.NewReader(buf.Bytes())) + require.NoError(t, err) + + // Verify all codes exist + codeSet := make(map[string]bool) + for _, code := range codes { + codeSet[code] = true + } + for code := range tc.input { + require.True(t, codeSet[code], "missing code: %s", code) + } + + // Verify items match + for code, expectedItems := range tc.input { + items, err := reader.Read(code) + require.NoError(t, err) + require.Equal(t, expectedItems, items, "items mismatch for code: %s", code) + } + }) + } +} + +func generateLargeItems(count int) map[string][]Item { + items := make([]Item, count) + for i := range count { + items[i] = Item{ + Type: ItemType(i % 4), + Value: strings.Repeat("x", i%200) + ".com", + } + } + return map[string][]Item{"large": items} +} diff --git a/common/geosite/geosite_test.go b/common/geosite/geosite_test.go new file mode 100644 index 0000000000..bdcb7a7afd --- /dev/null +++ b/common/geosite/geosite_test.go @@ -0,0 +1,34 @@ +package geosite_test + +import ( + "bytes" + "testing" + + "github.com/sagernet/sing-box/common/geosite" + + "github.com/stretchr/testify/require" +) + +func TestGeosite(t *testing.T) { + t.Parallel() + + var buffer bytes.Buffer + err := geosite.Write(&buffer, map[string][]geosite.Item{ + "test": { + { + Type: geosite.RuleTypeDomain, + Value: "example.org", + }, + }, + }) + require.NoError(t, err) + reader, codes, err := geosite.NewReader(bytes.NewReader(buffer.Bytes())) + require.NoError(t, err) + require.Equal(t, []string{"test"}, codes) + items, err := reader.Read("test") + require.NoError(t, err) + require.Equal(t, []geosite.Item{{ + Type: geosite.RuleTypeDomain, + Value: "example.org", + }}, items) +} diff --git a/common/geosite/reader.go b/common/geosite/reader.go index a1b39f28b4..6cccee9ee3 100644 --- a/common/geosite/reader.go +++ b/common/geosite/reader.go @@ -1,17 +1,24 @@ package geosite import ( + "bufio" + "encoding/binary" "io" "os" + "sync" + "sync/atomic" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/rw" + "github.com/sagernet/sing/common/varbin" ) type Reader struct { - reader io.ReadSeeker - domainIndex map[string]int - domainLength map[string]int + access sync.Mutex + reader io.ReadSeeker + bufferedReader *bufio.Reader + metadataIndex int64 + domainIndex map[string]int + domainLength map[string]int } func Open(path string) (*Reader, []string, error) { @@ -19,12 +26,20 @@ func Open(path string) (*Reader, []string, error) { if err != nil { return nil, nil, err } + reader, codes, err := NewReader(content) + if err != nil { + content.Close() + return nil, nil, err + } + return reader, codes, nil +} + +func NewReader(readSeeker io.ReadSeeker) (*Reader, []string, error) { reader := &Reader{ - reader: content, + reader: readSeeker, } - err = reader.readMetadata() + err := reader.readMetadata() if err != nil { - content.Close() return nil, nil, err } codes := make([]string, 0, len(reader.domainIndex)) @@ -35,14 +50,16 @@ func Open(path string) (*Reader, []string, error) { } func (r *Reader) readMetadata() error { - version, err := rw.ReadByte(r.reader) + counter := &readCounter{Reader: r.reader} + reader := bufio.NewReader(counter) + version, err := reader.ReadByte() if err != nil { return err } if version != 0 { return E.New("unknown version") } - entryLength, err := rw.ReadUVariant(r.reader) + entryLength, err := binary.ReadUvarint(reader) if err != nil { return err } @@ -55,16 +72,16 @@ func (r *Reader) readMetadata() error { codeIndex uint64 codeLength uint64 ) - code, err = rw.ReadVString(r.reader) + code, err = varbin.ReadValue[string](reader, binary.BigEndian) if err != nil { return err } keys[i] = code - codeIndex, err = rw.ReadUVariant(r.reader) + codeIndex, err = binary.ReadUvarint(reader) if err != nil { return err } - codeLength, err = rw.ReadUVariant(r.reader) + codeLength, err = binary.ReadUvarint(reader) if err != nil { return err } @@ -73,39 +90,45 @@ func (r *Reader) readMetadata() error { } r.domainIndex = domainIndex r.domainLength = domainLength + r.metadataIndex = counter.count - int64(reader.Buffered()) + r.bufferedReader = reader return nil } 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!") } - _, err := r.reader.Seek(int64(index), io.SeekCurrent) + _, err := r.reader.Seek(r.metadataIndex+int64(index), io.SeekStart) if err != nil { return nil, err } - counter := &rw.ReadCounter{Reader: r.reader} - domain := make([]Item, r.domainLength[code]) - for i := range domain { - var ( - item Item - err error - ) - item.Type, err = rw.ReadByte(counter) - if err != nil { - return nil, err - } - item.Value, err = rw.ReadVString(counter) - if err != nil { - return nil, err - } - domain[i] = item + r.bufferedReader.Reset(r.reader) + itemList := make([]Item, r.domainLength[code]) + err = varbin.Read(r.bufferedReader, binary.BigEndian, &itemList) + if err != nil { + return nil, err } - _, err = r.reader.Seek(int64(-index)-counter.Count(), io.SeekCurrent) - return domain, err + return itemList, nil } func (r *Reader) Upstream() any { return r.reader } + +type readCounter struct { + io.Reader + count int64 +} + +func (r *readCounter) Read(p []byte) (n int, err error) { + n, err = r.Reader.Read(p) + if n > 0 { + atomic.AddInt64(&r.count, int64(n)) + } + return +} diff --git a/common/geosite/writer.go b/common/geosite/writer.go index 4e7ec514b1..1615fa3480 100644 --- a/common/geosite/writer.go +++ b/common/geosite/writer.go @@ -2,13 +2,13 @@ package geosite import ( "bytes" - "io" + "encoding/binary" "sort" - "github.com/sagernet/sing/common/rw" + "github.com/sagernet/sing/common/varbin" ) -func Write(writer io.Writer, domains map[string][]Item) error { +func Write(writer varbin.Writer, domains map[string][]Item) error { keys := make([]string, 0, len(domains)) for code := range domains { keys = append(keys, code) @@ -19,35 +19,34 @@ func Write(writer io.Writer, domains map[string][]Item) error { index := make(map[string]int) for _, code := range keys { index[code] = content.Len() - for _, domain := range domains[code] { - content.WriteByte(domain.Type) - err := rw.WriteVString(content, domain.Value) + for _, item := range domains[code] { + err := varbin.Write(content, binary.BigEndian, item) if err != nil { return err } } } - err := rw.WriteByte(writer, 0) + err := writer.WriteByte(0) if err != nil { return err } - err = rw.WriteUVariant(writer, uint64(len(keys))) + _, err = varbin.WriteUvarint(writer, uint64(len(keys))) if err != nil { return err } for _, code := range keys { - err = rw.WriteVString(writer, code) + err = varbin.Write(writer, binary.BigEndian, code) if err != nil { return err } - err = rw.WriteUVariant(writer, uint64(index[code])) + _, err = varbin.WriteUvarint(writer, uint64(index[code])) if err != nil { return err } - err = rw.WriteUVariant(writer, uint64(len(domains[code]))) + _, err = varbin.WriteUvarint(writer, uint64(len(domains[code]))) if err != nil { return err } diff --git a/common/httpclient/apple_transport_darwin.go b/common/httpclient/apple_transport_darwin.go new file mode 100644 index 0000000000..4619dc58de --- /dev/null +++ b/common/httpclient/apple_transport_darwin.go @@ -0,0 +1,461 @@ +//go:build darwin && cgo + +package httpclient + +/* +#cgo CFLAGS: -x objective-c -fobjc-arc +#cgo LDFLAGS: -framework Foundation -framework Security + +#include +#include "apple_transport_darwin.h" +*/ +import "C" + +import ( + "bytes" + "context" + "crypto/sha256" + "fmt" + "io" + "net" + "net/http" + "strings" + "sync" + "sync/atomic" + "time" + "unsafe" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/certificate" + "github.com/sagernet/sing-box/common/proxybridge" + boxTLS "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/ntp" +) + +const applePinnedHashSize = sha256.Size + +var ( + newAppleUserAnchors = certificate.NewAppleAnchors + newAppleProxyBridge = proxybridge.New + newAppleTransportSession = func(shared *appleTransportShared) (unsafe.Pointer, error) { + session, err := shared.newSession() + return unsafe.Pointer(session), err + } +) + +func verifyApplePinnedPublicKeySHA256(flatHashes []byte, leafCertificate []byte) error { + if len(flatHashes)%applePinnedHashSize != 0 { + return E.New("invalid pinned public key list") + } + knownHashes := make([][]byte, 0, len(flatHashes)/applePinnedHashSize) + for offset := 0; offset < len(flatHashes); offset += applePinnedHashSize { + knownHashes = append(knownHashes, append([]byte(nil), flatHashes[offset:offset+applePinnedHashSize]...)) + } + return boxTLS.VerifyPublicKeySHA256(knownHashes, [][]byte{leafCertificate}) +} + +//export box_apple_http_verify_public_key_sha256 +func box_apple_http_verify_public_key_sha256(knownHashValues *C.uint8_t, knownHashValuesLen C.size_t, leafCert *C.uint8_t, leafCertLen C.size_t) *C.char { + flatHashes := C.GoBytes(unsafe.Pointer(knownHashValues), C.int(knownHashValuesLen)) + leafCertificate := C.GoBytes(unsafe.Pointer(leafCert), C.int(leafCertLen)) + err := verifyApplePinnedPublicKeySHA256(flatHashes, leafCertificate) + if err == nil { + return nil + } + return C.CString(err.Error()) +} + +type appleSessionConfig struct { + serverName string + minVersion uint16 + maxVersion uint16 + insecure bool + anchorOnly bool + userAnchors adapter.AppleAnchors + store adapter.CertificateStore + pinnedPublicKeySHA256s []byte +} + +type appleTransportShared struct { + logger logger.ContextLogger + bridge *proxybridge.Bridge + config appleSessionConfig + timeFunc func() time.Time + refs atomic.Int32 +} + +type appleTransport struct { + shared *appleTransportShared + access sync.Mutex + session *C.box_apple_http_session_t + closed bool +} + +func newAppleTransport(ctx context.Context, logger logger.ContextLogger, rawDialer N.Dialer, options option.HTTPClientOptions) (innerTransport, error) { + sessionConfig, err := newAppleSessionConfig(ctx, options) + if err != nil { + return nil, err + } + releaseConfig := true + defer func() { + if releaseConfig { + sessionConfig.close() + } + }() + bridge, err := newAppleProxyBridge(ctx, logger, "apple http proxy", rawDialer) + if err != nil { + return nil, err + } + shared := &appleTransportShared{ + logger: logger, + bridge: bridge, + config: sessionConfig, + timeFunc: ntp.TimeFuncFromContext(ctx), + } + shared.refs.Store(1) + sessionRef, err := newAppleTransportSession(shared) + if err != nil { + bridge.Close() + return nil, err + } + session := (*C.box_apple_http_session_t)(sessionRef) + releaseConfig = false + return &appleTransport{ + shared: shared, + session: session, + }, nil +} + +func newAppleSessionConfig(ctx context.Context, options option.HTTPClientOptions) (appleSessionConfig, error) { + version := options.Version + if version == 0 { + version = 2 + } + switch version { + case 2: + case 1: + return appleSessionConfig{}, E.New("HTTP/1.1 is unsupported in Apple HTTP engine") + case 3: + return appleSessionConfig{}, E.New("HTTP/3 is unsupported in Apple HTTP engine") + default: + return appleSessionConfig{}, E.New("unknown HTTP version: ", version) + } + if options.DisableVersionFallback { + return appleSessionConfig{}, E.New("disable_version_fallback is unsupported in Apple HTTP engine") + } + if options.HTTP2Options != (option.HTTP2Options{}) { + return appleSessionConfig{}, E.New("HTTP/2 options are unsupported in Apple HTTP engine") + } + if options.HTTP3Options != (option.QUICOptions{}) { + return appleSessionConfig{}, E.New("QUIC options are unsupported in Apple HTTP engine") + } + + tlsOptions := common.PtrValueOrDefault(options.TLS) + if tlsOptions.Engine != "" { + return appleSessionConfig{}, E.New("tls.engine is unsupported in Apple HTTP engine") + } + if len(tlsOptions.ALPN) > 0 { + return appleSessionConfig{}, E.New("tls.alpn is unsupported in Apple HTTP engine") + } + validated, err := boxTLS.ValidateSystemTLSOptions(ctx, tlsOptions, "Apple HTTP engine") + if err != nil { + return appleSessionConfig{}, err + } + + config := appleSessionConfig{ + serverName: tlsOptions.ServerName, + minVersion: validated.MinVersion, + maxVersion: validated.MaxVersion, + insecure: tlsOptions.Insecure || len(tlsOptions.CertificatePublicKeySHA256) > 0, + anchorOnly: validated.Exclusive, + store: validated.Store, + } + if len(validated.UserPEM) > 0 { + userAnchors, anchorsErr := newAppleUserAnchors(validated.UserPEM) + if anchorsErr != nil { + return appleSessionConfig{}, anchorsErr + } + config.userAnchors = userAnchors + } + if len(tlsOptions.CertificatePublicKeySHA256) > 0 { + config.pinnedPublicKeySHA256s = make([]byte, 0, len(tlsOptions.CertificatePublicKeySHA256)*applePinnedHashSize) + for _, hashValue := range tlsOptions.CertificatePublicKeySHA256 { + if len(hashValue) != applePinnedHashSize { + if config.userAnchors != nil { + config.userAnchors.Release() + } + return appleSessionConfig{}, E.New("invalid certificate_public_key_sha256 length: ", len(hashValue)) + } + config.pinnedPublicKeySHA256s = append(config.pinnedPublicKeySHA256s, hashValue...) + } + } + return config, nil +} + +func (c *appleSessionConfig) close() { + if c.userAnchors != nil { + c.userAnchors.Release() + c.userAnchors = nil + } +} + +func (s *appleTransportShared) retain() { + s.refs.Add(1) +} + +func (s *appleTransportShared) release() error { + if s.refs.Add(-1) == 0 { + s.config.close() + return s.bridge.Close() + } + return nil +} + +func (s *appleTransportShared) newSession() (*C.box_apple_http_session_t, error) { + cProxyHost := C.CString("127.0.0.1") + defer C.free(unsafe.Pointer(cProxyHost)) + cProxyUsername := C.CString(s.bridge.Username()) + defer C.free(unsafe.Pointer(cProxyUsername)) + cProxyPassword := C.CString(s.bridge.Password()) + defer C.free(unsafe.Pointer(cProxyPassword)) + var pinnedPointer *C.uint8_t + if len(s.config.pinnedPublicKeySHA256s) > 0 { + pinnedPointer = (*C.uint8_t)(C.CBytes(s.config.pinnedPublicKeySHA256s)) + defer C.free(unsafe.Pointer(pinnedPointer)) + } + anchors := certificate.AcquireAnchors(s.config.userAnchors, s.config.store) + var anchorsRef unsafe.Pointer + if anchors != nil { + anchorsRef = anchors.Ref() + defer anchors.Release() + } + cConfig := C.box_apple_http_session_config_t{ + proxy_host: cProxyHost, + proxy_port: C.int(s.bridge.Port()), + proxy_username: cProxyUsername, + proxy_password: cProxyPassword, + min_tls_version: C.uint16_t(s.config.minVersion), + max_tls_version: C.uint16_t(s.config.maxVersion), + insecure: C.bool(s.config.insecure), + anchors_cf: anchorsRef, + anchor_only: C.bool(s.config.anchorOnly), + pinned_public_key_sha256: pinnedPointer, + pinned_public_key_sha256_len: C.size_t(len(s.config.pinnedPublicKeySHA256s)), + } + var cErr *C.char + session := C.box_apple_http_session_create(&cConfig, &cErr) + if session != nil { + return session, nil + } + return nil, appleCStringError(cErr, "create Apple HTTP session") +} + +func (t *appleTransport) RoundTrip(request *http.Request) (*http.Response, error) { + if requestRequiresHTTP1(request) { + return nil, E.New("HTTP upgrade requests are unsupported in Apple HTTP engine") + } + if request.URL == nil { + return nil, E.New("missing request URL") + } + switch request.URL.Scheme { + case "http", "https": + default: + return nil, E.New("unsupported URL scheme: ", request.URL.Scheme) + } + if request.URL.Scheme == "https" && t.shared.config.serverName != "" && !strings.EqualFold(t.shared.config.serverName, request.URL.Hostname()) { + return nil, E.New("tls.server_name is unsupported in Apple HTTP engine unless it matches request host") + } + var body []byte + if request.Body != nil && request.Body != http.NoBody { + defer request.Body.Close() + var err error + body, err = io.ReadAll(request.Body) + if err != nil { + return nil, err + } + } + headerKeys, headerValues := flattenRequestHeaders(request) + cMethod := C.CString(request.Method) + defer C.free(unsafe.Pointer(cMethod)) + cURL := C.CString(request.URL.String()) + defer C.free(unsafe.Pointer(cURL)) + cHeaderKeys := make([]*C.char, len(headerKeys)) + cHeaderValues := make([]*C.char, len(headerValues)) + defer func() { + for _, ptr := range cHeaderKeys { + C.free(unsafe.Pointer(ptr)) + } + for _, ptr := range cHeaderValues { + C.free(unsafe.Pointer(ptr)) + } + }() + for index, value := range headerKeys { + cHeaderKeys[index] = C.CString(value) + } + for index, value := range headerValues { + cHeaderValues[index] = C.CString(value) + } + var headerKeysPointer **C.char + var headerValuesPointer **C.char + if len(cHeaderKeys) > 0 { + pointerArraySize := C.size_t(len(cHeaderKeys)) * C.size_t(unsafe.Sizeof((*C.char)(nil))) + headerKeysPointer = (**C.char)(C.malloc(pointerArraySize)) + defer C.free(unsafe.Pointer(headerKeysPointer)) + headerValuesPointer = (**C.char)(C.malloc(pointerArraySize)) + defer C.free(unsafe.Pointer(headerValuesPointer)) + copy(unsafe.Slice(headerKeysPointer, len(cHeaderKeys)), cHeaderKeys) + copy(unsafe.Slice(headerValuesPointer, len(cHeaderValues)), cHeaderValues) + } + var bodyPointer *C.uint8_t + if len(body) > 0 { + bodyPointer = (*C.uint8_t)(C.CBytes(body)) + defer C.free(unsafe.Pointer(bodyPointer)) + } + var ( + hasVerifyTime bool + verifyTimeUnixMilli int64 + ) + if t.shared.timeFunc != nil { + hasVerifyTime = true + verifyTimeUnixMilli = t.shared.timeFunc().UnixMilli() + } + cRequest := C.box_apple_http_request_t{ + method: cMethod, + url: cURL, + header_keys: (**C.char)(headerKeysPointer), + header_values: (**C.char)(headerValuesPointer), + header_count: C.size_t(len(cHeaderKeys)), + body: bodyPointer, + body_len: C.size_t(len(body)), + has_verify_time: C.bool(hasVerifyTime), + verify_time_unix_millis: C.int64_t(verifyTimeUnixMilli), + } + var cErr *C.char + var task *C.box_apple_http_task_t + t.access.Lock() + if t.session == nil { + t.access.Unlock() + return nil, net.ErrClosed + } + // Keep the session attached until NSURLSession has created the task. + task = C.box_apple_http_session_send_async(t.session, &cRequest, &cErr) + t.access.Unlock() + if task == nil { + return nil, appleCStringError(cErr, "create Apple HTTP request") + } + cancelDone := make(chan struct{}) + cancelExit := make(chan struct{}) + go func() { + defer close(cancelExit) + select { + case <-request.Context().Done(): + C.box_apple_http_task_cancel(task) + case <-cancelDone: + } + }() + cResponse := C.box_apple_http_task_wait(task, &cErr) + close(cancelDone) + <-cancelExit + C.box_apple_http_task_close(task) + if cResponse == nil { + err := appleCStringError(cErr, "Apple HTTP request failed") + if request.Context().Err() != nil { + return nil, request.Context().Err() + } + return nil, err + } + defer C.box_apple_http_response_free(cResponse) + return parseAppleHTTPResponse(request, cResponse), nil +} + +func (t *appleTransport) CloseIdleConnections() { + t.access.Lock() + if t.closed { + t.access.Unlock() + return + } + t.access.Unlock() + newSession, err := t.shared.newSession() + if err != nil { + t.shared.logger.Error(E.Cause(err, "reset Apple HTTP session")) + return + } + t.access.Lock() + if t.closed { + t.access.Unlock() + C.box_apple_http_session_close(newSession) + return + } + oldSession := t.session + t.session = newSession + t.access.Unlock() + C.box_apple_http_session_retire(oldSession) +} + +func (t *appleTransport) Close() error { + t.access.Lock() + if t.closed { + t.access.Unlock() + return nil + } + t.closed = true + session := t.session + t.session = nil + t.access.Unlock() + C.box_apple_http_session_close(session) + return t.shared.release() +} + +func flattenRequestHeaders(request *http.Request) ([]string, []string) { + var ( + keys []string + values []string + ) + for key, headerValues := range request.Header { + for _, value := range headerValues { + keys = append(keys, key) + values = append(values, value) + } + } + if request.Host != "" { + keys = append(keys, "Host") + values = append(values, request.Host) + } + return keys, values +} + +func parseAppleHTTPResponse(request *http.Request, response *C.box_apple_http_response_t) *http.Response { + headers := make(http.Header) + headerKeys := unsafe.Slice(response.header_keys, int(response.header_count)) + headerValues := unsafe.Slice(response.header_values, int(response.header_count)) + for index := range headerKeys { + headers.Add(C.GoString(headerKeys[index]), C.GoString(headerValues[index])) + } + body := bytes.NewReader(C.GoBytes(unsafe.Pointer(response.body), C.int(response.body_len))) + // NSURLSession's completion-handler API does not expose the negotiated protocol; + // callers that read Response.Proto will see HTTP/1.1 even when the wire was HTTP/2. + return &http.Response{ + StatusCode: int(response.status_code), + Status: fmt.Sprintf("%d %s", int(response.status_code), http.StatusText(int(response.status_code))), + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Header: headers, + Body: io.NopCloser(body), + ContentLength: int64(body.Len()), + Request: request, + } +} + +func appleCStringError(cErr *C.char, message string) error { + if cErr == nil { + return E.New(message) + } + defer C.free(unsafe.Pointer(cErr)) + return E.New(message, ": ", C.GoString(cErr)) +} diff --git a/common/httpclient/apple_transport_darwin.h b/common/httpclient/apple_transport_darwin.h new file mode 100644 index 0000000000..4774a6b4de --- /dev/null +++ b/common/httpclient/apple_transport_darwin.h @@ -0,0 +1,70 @@ +#include +#include +#include + +typedef struct box_apple_http_session box_apple_http_session_t; +typedef struct box_apple_http_task box_apple_http_task_t; + +typedef struct box_apple_http_session_config { + const char *proxy_host; + int proxy_port; + const char *proxy_username; + const char *proxy_password; + uint16_t min_tls_version; + uint16_t max_tls_version; + bool insecure; + void *anchors_cf; + bool anchor_only; + const uint8_t *pinned_public_key_sha256; + size_t pinned_public_key_sha256_len; +} box_apple_http_session_config_t; + +typedef struct box_apple_http_request { + const char *method; + const char *url; + const char **header_keys; + const char **header_values; + size_t header_count; + const uint8_t *body; + size_t body_len; + bool has_verify_time; + int64_t verify_time_unix_millis; +} box_apple_http_request_t; + +typedef struct box_apple_http_response { + int status_code; + char **header_keys; + char **header_values; + size_t header_count; + uint8_t *body; + size_t body_len; + char *error; +} box_apple_http_response_t; + +box_apple_http_session_t *box_apple_http_session_create( + const box_apple_http_session_config_t *config, + char **error_out +); +void box_apple_http_session_retire(box_apple_http_session_t *session); +void box_apple_http_session_close(box_apple_http_session_t *session); + +box_apple_http_task_t *box_apple_http_session_send_async( + box_apple_http_session_t *session, + const box_apple_http_request_t *request, + char **error_out +); +box_apple_http_response_t *box_apple_http_task_wait( + box_apple_http_task_t *task, + char **error_out +); +void box_apple_http_task_cancel(box_apple_http_task_t *task); +void box_apple_http_task_close(box_apple_http_task_t *task); + +void box_apple_http_response_free(box_apple_http_response_t *response); + +char *box_apple_http_verify_public_key_sha256( + uint8_t *known_hash_values, + size_t known_hash_values_len, + uint8_t *leaf_cert, + size_t leaf_cert_len +); diff --git a/common/httpclient/apple_transport_darwin.m b/common/httpclient/apple_transport_darwin.m new file mode 100644 index 0000000000..1e72a3e0ab --- /dev/null +++ b/common/httpclient/apple_transport_darwin.m @@ -0,0 +1,364 @@ +#import "apple_transport_darwin.h" + +#import +#import +#import +#import +#import +#import + +typedef struct box_apple_http_session { + void *handle; +} box_apple_http_session_t; + +typedef struct box_apple_http_task { + void *task; + void *done_semaphore; + box_apple_http_response_t *response; + char *error; +} box_apple_http_task_t; + +static NSString *const box_apple_http_verify_time_key = @"sing-box.verify-time"; + +static void box_set_error_string(char **error_out, NSString *message) { + if (error_out == NULL || *error_out != NULL) { + return; + } + const char *utf8 = [message UTF8String]; + *error_out = strdup(utf8 != NULL ? utf8 : "unknown error"); +} + +static void box_set_error_from_nserror(char **error_out, NSError *error) { + if (error == nil) { + box_set_error_string(error_out, @"unknown error"); + return; + } + box_set_error_string(error_out, error.localizedDescription ?: error.description); +} + +static bool box_evaluate_trust(SecTrustRef trustRef, NSArray *anchors, bool anchor_only, NSDate *verifyDate) { + if (trustRef == NULL) { + return false; + } + if (verifyDate != nil && SecTrustSetVerifyDate(trustRef, (__bridge CFDateRef)verifyDate) != errSecSuccess) { + return false; + } + if (anchors.count > 0 || anchor_only) { + CFMutableArrayRef anchorArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + for (id certificate in anchors) { + CFArrayAppendValue(anchorArray, (__bridge const void *)certificate); + } + SecTrustSetAnchorCertificates(trustRef, anchorArray); + SecTrustSetAnchorCertificatesOnly(trustRef, anchor_only); + CFRelease(anchorArray); + } + CFErrorRef error = NULL; + bool result = SecTrustEvaluateWithError(trustRef, &error); + if (error != NULL) { + CFRelease(error); + } + return result; +} + +static NSDate *box_apple_http_verify_date_for_request(NSURLRequest *request) { + if (request == nil) { + return nil; + } + id value = [NSURLProtocol propertyForKey:box_apple_http_verify_time_key inRequest:request]; + if (![value isKindOfClass:[NSNumber class]]) { + return nil; + } + return [NSDate dateWithTimeIntervalSince1970:[(NSNumber *)value longLongValue] / 1000.0]; +} + +static box_apple_http_response_t *box_create_response(NSHTTPURLResponse *httpResponse, NSData *data) { + box_apple_http_response_t *response = calloc(1, sizeof(box_apple_http_response_t)); + response->status_code = (int)httpResponse.statusCode; + NSDictionary *headers = httpResponse.allHeaderFields; + response->header_count = headers.count; + if (response->header_count > 0) { + response->header_keys = calloc(response->header_count, sizeof(char *)); + response->header_values = calloc(response->header_count, sizeof(char *)); + NSUInteger index = 0; + for (id key in headers) { + NSString *keyString = [[key description] copy]; + NSString *valueString = [[headers[key] description] copy]; + response->header_keys[index] = strdup(keyString.UTF8String ?: ""); + response->header_values[index] = strdup(valueString.UTF8String ?: ""); + index++; + } + } + if (data.length > 0) { + response->body_len = data.length; + response->body = malloc(data.length); + memcpy(response->body, data.bytes, data.length); + } + return response; +} + +@interface BoxAppleHTTPSessionDelegate : NSObject +@property(nonatomic, assign) BOOL insecure; +@property(nonatomic, assign) BOOL anchorOnly; +@property(nonatomic, strong) NSArray *anchors; +@property(nonatomic, strong) NSData *pinnedPublicKeyHashes; +@end + +@implementation BoxAppleHTTPSessionDelegate + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +willPerformHTTPRedirection:(NSHTTPURLResponse *)response + newRequest:(NSURLRequest *)request + completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler { + completionHandler(nil); +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge + completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler { + if (![challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { + completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); + return; + } + SecTrustRef trustRef = challenge.protectionSpace.serverTrust; + if (trustRef == NULL) { + completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); + return; + } + NSDate *verifyDate = box_apple_http_verify_date_for_request(task.currentRequest ?: task.originalRequest); + BOOL needsCustomHandling = self.insecure || self.anchorOnly || self.anchors.count > 0 || self.pinnedPublicKeyHashes.length > 0 || verifyDate != nil; + if (!needsCustomHandling) { + completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); + return; + } + BOOL ok = YES; + if (!self.insecure) { + ok = box_evaluate_trust(trustRef, self.anchors, self.anchorOnly, verifyDate); + } + if (ok && self.pinnedPublicKeyHashes.length > 0) { + CFArrayRef certificateChain = SecTrustCopyCertificateChain(trustRef); + SecCertificateRef leafCertificate = NULL; + if (certificateChain != NULL && CFArrayGetCount(certificateChain) > 0) { + leafCertificate = (SecCertificateRef)CFArrayGetValueAtIndex(certificateChain, 0); + } + if (leafCertificate == NULL) { + ok = NO; + } else { + NSData *leafData = CFBridgingRelease(SecCertificateCopyData(leafCertificate)); + char *pinError = box_apple_http_verify_public_key_sha256( + (uint8_t *)self.pinnedPublicKeyHashes.bytes, + self.pinnedPublicKeyHashes.length, + (uint8_t *)leafData.bytes, + leafData.length + ); + if (pinError != NULL) { + free(pinError); + ok = NO; + } + } + if (certificateChain != NULL) { + CFRelease(certificateChain); + } + } + if (!ok) { + completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); + return; + } + completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:trustRef]); +} + +@end + +@interface BoxAppleHTTPSessionHandle : NSObject +@property(nonatomic, strong) NSURLSession *session; +@property(nonatomic, strong) BoxAppleHTTPSessionDelegate *delegate; +@end + +@implementation BoxAppleHTTPSessionHandle +@end + +box_apple_http_session_t *box_apple_http_session_create( + const box_apple_http_session_config_t *config, + char **error_out +) { + @autoreleasepool { + NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration ephemeralSessionConfiguration]; + sessionConfig.URLCache = nil; + sessionConfig.HTTPCookieStorage = nil; + sessionConfig.URLCredentialStorage = nil; + sessionConfig.HTTPShouldSetCookies = NO; + if (config != NULL && config->proxy_host != NULL && config->proxy_port > 0) { + NSMutableDictionary *proxyDictionary = [NSMutableDictionary dictionary]; + proxyDictionary[(__bridge NSString *)kCFStreamPropertySOCKSProxyHost] = [NSString stringWithUTF8String:config->proxy_host]; + proxyDictionary[(__bridge NSString *)kCFStreamPropertySOCKSProxyPort] = @(config->proxy_port); + proxyDictionary[(__bridge NSString *)kCFStreamPropertySOCKSVersion] = (__bridge NSString *)kCFStreamSocketSOCKSVersion5; + if (config->proxy_username != NULL) { + proxyDictionary[(__bridge NSString *)kCFStreamPropertySOCKSUser] = [NSString stringWithUTF8String:config->proxy_username]; + } + if (config->proxy_password != NULL) { + proxyDictionary[(__bridge NSString *)kCFStreamPropertySOCKSPassword] = [NSString stringWithUTF8String:config->proxy_password]; + } + sessionConfig.connectionProxyDictionary = proxyDictionary; + } + if (config != NULL && config->min_tls_version != 0) { + sessionConfig.TLSMinimumSupportedProtocolVersion = (tls_protocol_version_t)config->min_tls_version; + } + if (config != NULL && config->max_tls_version != 0) { + sessionConfig.TLSMaximumSupportedProtocolVersion = (tls_protocol_version_t)config->max_tls_version; + } + BoxAppleHTTPSessionDelegate *delegate = [[BoxAppleHTTPSessionDelegate alloc] init]; + if (config != NULL) { + delegate.insecure = config->insecure; + delegate.anchorOnly = config->anchor_only; + if (config->anchors_cf != NULL) { + delegate.anchors = (__bridge NSArray *)config->anchors_cf; + } else { + delegate.anchors = @[]; + } + if (config->pinned_public_key_sha256 != NULL && config->pinned_public_key_sha256_len > 0) { + delegate.pinnedPublicKeyHashes = [NSData dataWithBytes:config->pinned_public_key_sha256 length:config->pinned_public_key_sha256_len]; + } + } + NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:delegate delegateQueue:nil]; + if (session == nil) { + box_set_error_string(error_out, @"create URLSession"); + return NULL; + } + BoxAppleHTTPSessionHandle *handle = [[BoxAppleHTTPSessionHandle alloc] init]; + handle.session = session; + handle.delegate = delegate; + box_apple_http_session_t *sessionHandle = calloc(1, sizeof(box_apple_http_session_t)); + sessionHandle->handle = (__bridge_retained void *)handle; + return sessionHandle; + } +} + +void box_apple_http_session_retire(box_apple_http_session_t *session) { + if (session == NULL || session->handle == NULL) { + return; + } + BoxAppleHTTPSessionHandle *handle = (__bridge_transfer BoxAppleHTTPSessionHandle *)session->handle; + [handle.session finishTasksAndInvalidate]; + free(session); +} + +void box_apple_http_session_close(box_apple_http_session_t *session) { + if (session == NULL || session->handle == NULL) { + return; + } + BoxAppleHTTPSessionHandle *handle = (__bridge_transfer BoxAppleHTTPSessionHandle *)session->handle; + [handle.session invalidateAndCancel]; + free(session); +} + +box_apple_http_task_t *box_apple_http_session_send_async( + box_apple_http_session_t *session, + const box_apple_http_request_t *request, + char **error_out +) { + @autoreleasepool { + if (session == NULL || session->handle == NULL || request == NULL || request->method == NULL || request->url == NULL) { + box_set_error_string(error_out, @"invalid apple HTTP request"); + return NULL; + } + BoxAppleHTTPSessionHandle *handle = (__bridge BoxAppleHTTPSessionHandle *)session->handle; + NSURL *requestURL = [NSURL URLWithString:[NSString stringWithUTF8String:request->url]]; + if (requestURL == nil) { + box_set_error_string(error_out, @"invalid request URL"); + return NULL; + } + NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:requestURL]; + urlRequest.HTTPMethod = [NSString stringWithUTF8String:request->method]; + for (size_t index = 0; index < request->header_count; index++) { + const char *key = request->header_keys[index]; + const char *value = request->header_values[index]; + if (key == NULL || value == NULL) { + continue; + } + [urlRequest addValue:[NSString stringWithUTF8String:value] forHTTPHeaderField:[NSString stringWithUTF8String:key]]; + } + if (request->body != NULL && request->body_len > 0) { + urlRequest.HTTPBody = [NSData dataWithBytes:request->body length:request->body_len]; + } + if (request->has_verify_time) { + [NSURLProtocol setProperty:@(request->verify_time_unix_millis) forKey:box_apple_http_verify_time_key inRequest:urlRequest]; + } + box_apple_http_task_t *task = calloc(1, sizeof(box_apple_http_task_t)); + dispatch_semaphore_t doneSemaphore = dispatch_semaphore_create(0); + task->done_semaphore = (__bridge_retained void *)doneSemaphore; + NSURLSessionDataTask *dataTask = [handle.session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + if (error != nil) { + box_set_error_from_nserror(&task->error, error); + } else if (![response isKindOfClass:[NSHTTPURLResponse class]]) { + box_set_error_string(&task->error, @"unexpected HTTP response type"); + } else { + task->response = box_create_response((NSHTTPURLResponse *)response, data ?: [NSData data]); + } + dispatch_semaphore_signal((__bridge dispatch_semaphore_t)task->done_semaphore); + }]; + if (dataTask == nil) { + box_set_error_string(error_out, @"create data task"); + box_apple_http_task_close(task); + return NULL; + } + task->task = (__bridge_retained void *)dataTask; + [dataTask resume]; + return task; + } +} + +box_apple_http_response_t *box_apple_http_task_wait( + box_apple_http_task_t *task, + char **error_out +) { + if (task == NULL || task->done_semaphore == NULL) { + box_set_error_string(error_out, @"invalid apple HTTP task"); + return NULL; + } + dispatch_semaphore_wait((__bridge dispatch_semaphore_t)task->done_semaphore, DISPATCH_TIME_FOREVER); + if (task->error != NULL) { + box_set_error_string(error_out, [NSString stringWithUTF8String:task->error]); + return NULL; + } + return task->response; +} + +void box_apple_http_task_cancel(box_apple_http_task_t *task) { + if (task == NULL || task->task == NULL) { + return; + } + NSURLSessionTask *nsTask = (__bridge NSURLSessionTask *)task->task; + [nsTask cancel]; +} + +void box_apple_http_task_close(box_apple_http_task_t *task) { + if (task == NULL) { + return; + } + if (task->task != NULL) { + __unused NSURLSessionTask *nsTask = (__bridge_transfer NSURLSessionTask *)task->task; + task->task = NULL; + } + if (task->done_semaphore != NULL) { + __unused dispatch_semaphore_t doneSemaphore = (__bridge_transfer dispatch_semaphore_t)task->done_semaphore; + task->done_semaphore = NULL; + } + free(task->error); + free(task); +} + +void box_apple_http_response_free(box_apple_http_response_t *response) { + if (response == NULL) { + return; + } + for (size_t index = 0; index < response->header_count; index++) { + free(response->header_keys[index]); + free(response->header_values[index]); + } + free(response->header_keys); + free(response->header_values); + free(response->body); + free(response->error); + free(response); +} diff --git a/common/httpclient/apple_transport_darwin_test.go b/common/httpclient/apple_transport_darwin_test.go new file mode 100644 index 0000000000..3055c25f25 --- /dev/null +++ b/common/httpclient/apple_transport_darwin_test.go @@ -0,0 +1,952 @@ +//go:build darwin && cgo + +package httpclient + +import ( + "bytes" + "context" + "crypto/sha256" + stdtls "crypto/tls" + "crypto/x509" + "errors" + "io" + "net" + "net/http" + "net/http/httptest" + "net/url" + "slices" + "strconv" + "strings" + "testing" + "time" + "unsafe" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/proxybridge" + boxTLS "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/route" + "github.com/sagernet/sing/common/json/badoption" + commonLogger "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" +) + +const appleHTTPTestTimeout = 5 * time.Second + +const appleHTTPRecoveryLoops = 5 + +type appleHTTPTestDialer struct { + dialer net.Dialer + listener net.ListenConfig + hostMap map[string]string +} + +type appleHTTPObservedRequest struct { + method string + body string + host string + values []string + protoMajor int +} + +type appleHTTPTestServer struct { + server *httptest.Server + baseURL string + dialHost string + certificate stdtls.Certificate + certificatePEM string + publicKeyHash []byte +} + +type appleTestAnchors struct { + ref unsafe.Pointer + releases int +} + +func (a *appleTestAnchors) Retain() adapter.AppleAnchors { + return a +} + +func (a *appleTestAnchors) Release() { + a.releases++ +} + +func (a *appleTestAnchors) Ref() unsafe.Pointer { + return a.ref +} + +func TestNewAppleSessionConfig(t *testing.T) { + serverCertificate, serverCertificatePEM := newAppleHTTPTestCertificate(t, "localhost") + serverHash := certificatePublicKeySHA256(t, serverCertificate.Certificate[0]) + otherHash := bytes.Repeat([]byte{0x7f}, applePinnedHashSize) + + testCases := []struct { + name string + options option.HTTPClientOptions + check func(t *testing.T, config appleSessionConfig) + wantErr string + }{ + { + name: "success with certificate anchors", + options: option.HTTPClientOptions{ + Version: 2, + DialerOptions: option.DialerOptions{ + ConnectTimeout: badoption.Duration(2 * time.Second), + }, + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "localhost", + MinVersion: "1.2", + MaxVersion: "1.3", + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }, + }, + }, + check: func(t *testing.T, config appleSessionConfig) { + t.Helper() + if config.serverName != "localhost" { + t.Fatalf("unexpected server name: %q", config.serverName) + } + if config.minVersion != stdtls.VersionTLS12 { + t.Fatalf("unexpected min version: %x", config.minVersion) + } + if config.maxVersion != stdtls.VersionTLS13 { + t.Fatalf("unexpected max version: %x", config.maxVersion) + } + if config.insecure { + t.Fatal("unexpected insecure flag") + } + if !config.anchorOnly { + t.Fatal("expected anchor_only") + } + if config.userAnchors == nil { + t.Fatal("expected user anchors") + } + if config.userAnchors.Ref() == nil { + t.Fatal("expected non-empty user anchors") + } + if config.store != nil { + t.Fatal("unexpected store reference") + } + if len(config.pinnedPublicKeySHA256s) != 0 { + t.Fatalf("unexpected pinned hashes: %d", len(config.pinnedPublicKeySHA256s)) + } + }, + }, + { + name: "success with flattened pins", + options: option.HTTPClientOptions{ + Version: 2, + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Enabled: true, + Insecure: true, + CertificatePublicKeySHA256: badoption.Listable[[]byte]{serverHash, otherHash}, + }, + }, + }, + check: func(t *testing.T, config appleSessionConfig) { + t.Helper() + if !config.insecure { + t.Fatal("expected insecure flag") + } + if len(config.pinnedPublicKeySHA256s) != 2*applePinnedHashSize { + t.Fatalf("unexpected flattened pin length: %d", len(config.pinnedPublicKeySHA256s)) + } + if !bytes.Equal(config.pinnedPublicKeySHA256s[:applePinnedHashSize], serverHash) { + t.Fatal("unexpected first pin") + } + if !bytes.Equal(config.pinnedPublicKeySHA256s[applePinnedHashSize:], otherHash) { + t.Fatal("unexpected second pin") + } + if config.userAnchors != nil { + t.Fatal("unexpected user anchors") + } + if config.anchorOnly { + t.Fatal("unexpected anchor_only") + } + }, + }, + { + name: "http11 unsupported", + options: option.HTTPClientOptions{Version: 1}, + wantErr: "HTTP/1.1 is unsupported in Apple HTTP engine", + }, + { + name: "http3 unsupported", + options: option.HTTPClientOptions{Version: 3}, + wantErr: "HTTP/3 is unsupported in Apple HTTP engine", + }, + { + name: "unknown version", + options: option.HTTPClientOptions{Version: 9}, + wantErr: "unknown HTTP version: 9", + }, + { + name: "disable version fallback unsupported", + options: option.HTTPClientOptions{ + DisableVersionFallback: true, + }, + wantErr: "disable_version_fallback is unsupported in Apple HTTP engine", + }, + { + name: "http2 options unsupported", + options: option.HTTPClientOptions{ + HTTP2Options: option.HTTP2Options{ + IdleTimeout: badoption.Duration(time.Second), + }, + }, + wantErr: "HTTP/2 options are unsupported in Apple HTTP engine", + }, + { + name: "quic options unsupported", + options: option.HTTPClientOptions{ + HTTP3Options: option.QUICOptions{ + InitialPacketSize: 1200, + }, + }, + wantErr: "QUIC options are unsupported in Apple HTTP engine", + }, + { + name: "tls engine unsupported", + options: option.HTTPClientOptions{ + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{Engine: "go"}, + }, + }, + wantErr: "tls.engine is unsupported in Apple HTTP engine", + }, + { + name: "disable sni unsupported", + options: option.HTTPClientOptions{ + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{DisableSNI: true}, + }, + }, + wantErr: "disable_sni is unsupported in Apple HTTP engine", + }, + { + name: "alpn unsupported", + options: option.HTTPClientOptions{ + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + ALPN: badoption.Listable[string]{"h2"}, + }, + }, + }, + wantErr: "tls.alpn is unsupported in Apple HTTP engine", + }, + { + name: "cipher suites unsupported", + options: option.HTTPClientOptions{ + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + CipherSuites: badoption.Listable[string]{"TLS_AES_128_GCM_SHA256"}, + }, + }, + }, + wantErr: "cipher_suites is unsupported in Apple HTTP engine", + }, + { + name: "curve preferences unsupported", + options: option.HTTPClientOptions{ + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + CurvePreferences: badoption.Listable[option.CurvePreference]{option.CurvePreference(option.X25519)}, + }, + }, + }, + wantErr: "curve_preferences is unsupported in Apple HTTP engine", + }, + { + name: "client certificate unsupported", + options: option.HTTPClientOptions{ + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + ClientCertificate: badoption.Listable[string]{"client-certificate"}, + ClientKey: badoption.Listable[string]{"client-key"}, + }, + }, + }, + wantErr: "client certificate is unsupported in Apple HTTP engine", + }, + { + name: "tls fragment unsupported", + options: option.HTTPClientOptions{ + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{Fragment: true}, + }, + }, + wantErr: "tls fragment is unsupported in Apple HTTP engine", + }, + { + name: "ktls unsupported", + options: option.HTTPClientOptions{ + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{KernelTx: true}, + }, + }, + wantErr: "ktls is unsupported in Apple HTTP engine", + }, + { + name: "ech unsupported", + options: option.HTTPClientOptions{ + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + ECH: &option.OutboundECHOptions{Enabled: true}, + }, + }, + }, + wantErr: "ech is unsupported in Apple HTTP engine", + }, + { + name: "utls unsupported", + options: option.HTTPClientOptions{ + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + UTLS: &option.OutboundUTLSOptions{Enabled: true}, + }, + }, + }, + wantErr: "utls is unsupported in Apple HTTP engine", + }, + { + name: "reality unsupported", + options: option.HTTPClientOptions{ + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Reality: &option.OutboundRealityOptions{Enabled: true}, + }, + }, + }, + wantErr: "reality is unsupported in Apple HTTP engine", + }, + { + name: "pin and certificate conflict", + options: option.HTTPClientOptions{ + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Certificate: badoption.Listable[string]{serverCertificatePEM}, + CertificatePublicKeySHA256: badoption.Listable[[]byte]{serverHash}, + }, + }, + }, + wantErr: "certificate_public_key_sha256 is conflict with certificate or certificate_path", + }, + { + name: "invalid min version", + options: option.HTTPClientOptions{ + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{MinVersion: "bogus"}, + }, + }, + wantErr: "parse min_version", + }, + { + name: "invalid max version", + options: option.HTTPClientOptions{ + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{MaxVersion: "bogus"}, + }, + }, + wantErr: "parse max_version", + }, + { + name: "invalid pin length", + options: option.HTTPClientOptions{ + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + CertificatePublicKeySHA256: badoption.Listable[[]byte]{{0x01, 0x02}}, + }, + }, + }, + wantErr: "invalid certificate_public_key_sha256 length: 2", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + config, err := newAppleSessionConfig(context.Background(), testCase.options) + if testCase.wantErr != "" { + if err == nil { + t.Fatal("expected error") + } + if !strings.Contains(err.Error(), testCase.wantErr) { + t.Fatalf("unexpected error: %v", err) + } + return + } + if err != nil { + t.Fatal(err) + } + if testCase.check != nil { + testCase.check(t, config) + } + }) + } +} + +func TestAppleTransportVerifyPublicKeySHA256(t *testing.T) { + serverCertificate, _ := newAppleHTTPTestCertificate(t, "localhost") + goodHash := certificatePublicKeySHA256(t, serverCertificate.Certificate[0]) + badHash := append([]byte(nil), goodHash...) + badHash[0] ^= 0xff + + err := verifyApplePinnedPublicKeySHA256(goodHash, serverCertificate.Certificate[0]) + if err != nil { + t.Fatalf("expected correct pin to succeed: %v", err) + } + + err = verifyApplePinnedPublicKeySHA256(badHash, serverCertificate.Certificate[0]) + if err == nil { + t.Fatal("expected incorrect pin to fail") + } + if !strings.Contains(err.Error(), "unrecognized remote public key") { + t.Fatalf("unexpected pin mismatch error: %v", err) + } + + err = verifyApplePinnedPublicKeySHA256(goodHash[:applePinnedHashSize-1], serverCertificate.Certificate[0]) + if err == nil { + t.Fatal("expected malformed pin list to fail") + } + if !strings.Contains(err.Error(), "invalid pinned public key list") { + t.Fatalf("unexpected malformed pin error: %v", err) + } +} + +func TestNewAppleTransportClosesSessionConfigOnBridgeFailure(t *testing.T) { + _, serverCertificatePEM := newAppleHTTPTestCertificate(t, "localhost") + restoreAppleTransportFactories(t) + testAnchors := &appleTestAnchors{ref: unsafe.Pointer(new(int))} + newAppleUserAnchors = func([]byte) (adapter.AppleAnchors, error) { + return testAnchors, nil + } + newAppleProxyBridge = func(context.Context, commonLogger.ContextLogger, string, N.Dialer) (*proxybridge.Bridge, error) { + return nil, errors.New("bridge boom") + } + + _, err := newAppleTransport(newAppleHTTPTestContext(), log.NewNOPFactory().NewLogger("httpclient"), &appleHTTPTestDialer{}, appleTransportAnchorOptions(serverCertificatePEM)) + if err == nil || !strings.Contains(err.Error(), "bridge boom") { + t.Fatalf("unexpected error: %v", err) + } + if testAnchors.releases != 1 { + t.Fatalf("expected 1 anchor release, got %d", testAnchors.releases) + } +} + +func TestNewAppleTransportClosesSessionConfigOnSessionFailure(t *testing.T) { + _, serverCertificatePEM := newAppleHTTPTestCertificate(t, "localhost") + restoreAppleTransportFactories(t) + testAnchors := &appleTestAnchors{ref: unsafe.Pointer(new(int))} + newAppleUserAnchors = func([]byte) (adapter.AppleAnchors, error) { + return testAnchors, nil + } + newAppleTransportSession = func(*appleTransportShared) (unsafe.Pointer, error) { + return nil, errors.New("session boom") + } + + _, err := newAppleTransport(newAppleHTTPTestContext(), log.NewNOPFactory().NewLogger("httpclient"), &appleHTTPTestDialer{}, appleTransportAnchorOptions(serverCertificatePEM)) + if err == nil || !strings.Contains(err.Error(), "session boom") { + t.Fatalf("unexpected error: %v", err) + } + if testAnchors.releases != 1 { + t.Fatalf("expected 1 anchor release, got %d", testAnchors.releases) + } +} + +func TestAppleTransportRoundTripHTTPS(t *testing.T) { + requests := make(chan appleHTTPObservedRequest, 1) + server := startAppleHTTPTestServer(t, func(w http.ResponseWriter, r *http.Request) { + body, err := io.ReadAll(r.Body) + if err != nil { + t.Error(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + requests <- appleHTTPObservedRequest{ + method: r.Method, + body: string(body), + host: r.Host, + values: append([]string(nil), r.Header.Values("X-Test")...), + protoMajor: r.ProtoMajor, + } + w.Header().Set("X-Reply", "apple") + w.WriteHeader(http.StatusCreated) + _, _ = w.Write([]byte("response body")) + }) + + transport := newAppleHTTPTestTransport(t, server, option.HTTPClientOptions{ + Version: 2, + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: appleHTTPServerTLSOptions(server), + }, + }) + + request, err := http.NewRequest(http.MethodPost, server.URL("/roundtrip"), bytes.NewReader([]byte("request body"))) + if err != nil { + t.Fatal(err) + } + request.Header.Add("X-Test", "one") + request.Header.Add("X-Test", "two") + request.Host = "custom.example" + + response, err := transport.RoundTrip(request) + if err != nil { + t.Fatal(err) + } + defer response.Body.Close() + + responseBody := readResponseBody(t, response) + if response.StatusCode != http.StatusCreated { + t.Fatalf("unexpected status code: %d", response.StatusCode) + } + if response.Status != "201 Created" { + t.Fatalf("unexpected status: %q", response.Status) + } + if response.Header.Get("X-Reply") != "apple" { + t.Fatalf("unexpected response header: %q", response.Header.Get("X-Reply")) + } + if responseBody != "response body" { + t.Fatalf("unexpected response body: %q", responseBody) + } + if response.ContentLength != int64(len(responseBody)) { + t.Fatalf("unexpected content length: %d", response.ContentLength) + } + + observed := waitObservedRequest(t, requests) + if observed.method != http.MethodPost { + t.Fatalf("unexpected method: %q", observed.method) + } + if observed.body != "request body" { + t.Fatalf("unexpected request body: %q", observed.body) + } + if observed.host != "custom.example" { + t.Fatalf("unexpected host: %q", observed.host) + } + if observed.protoMajor != 2 { + t.Fatalf("expected HTTP/2 request, got HTTP/%d", observed.protoMajor) + } + var normalizedValues []string + for _, value := range observed.values { + for part := range strings.SplitSeq(value, ",") { + normalizedValues = append(normalizedValues, strings.TrimSpace(part)) + } + } + slices.Sort(normalizedValues) + if !slices.Equal(normalizedValues, []string{"one", "two"}) { + t.Fatalf("unexpected header values: %#v", observed.values) + } +} + +func TestAppleTransportPinnedPublicKey(t *testing.T) { + server := startAppleHTTPTestServer(t, func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("pinned")) + }) + + goodTransport := newAppleHTTPTestTransport(t, server, option.HTTPClientOptions{ + Version: 2, + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "localhost", + Insecure: true, + CertificatePublicKeySHA256: badoption.Listable[[]byte]{server.publicKeyHash}, + }, + }, + }) + + response, err := goodTransport.RoundTrip(newAppleHTTPRequest(t, http.MethodGet, server.URL("/good"), nil)) + if err != nil { + t.Fatalf("expected pinned request to succeed: %v", err) + } + response.Body.Close() + + badHash := append([]byte(nil), server.publicKeyHash...) + badHash[0] ^= 0xff + badTransport := newAppleHTTPTestTransport(t, server, option.HTTPClientOptions{ + Version: 2, + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "localhost", + Insecure: true, + CertificatePublicKeySHA256: badoption.Listable[[]byte]{badHash}, + }, + }, + }) + + response, err = badTransport.RoundTrip(newAppleHTTPRequest(t, http.MethodGet, server.URL("/bad"), nil)) + if err == nil { + response.Body.Close() + t.Fatal("expected incorrect pinned public key to fail") + } +} + +func TestAppleTransportGuardrails(t *testing.T) { + testCases := []struct { + name string + options option.HTTPClientOptions + buildRequest func(t *testing.T) *http.Request + wantErrSubstr string + }{ + { + name: "websocket upgrade rejected", + options: option.HTTPClientOptions{ + Version: 2, + }, + buildRequest: func(t *testing.T) *http.Request { + t.Helper() + request := newAppleHTTPRequest(t, http.MethodGet, "https://localhost/socket", nil) + request.Header.Set("Connection", "Upgrade") + request.Header.Set("Upgrade", "websocket") + return request + }, + wantErrSubstr: "HTTP upgrade requests are unsupported in Apple HTTP engine", + }, + { + name: "missing url rejected", + options: option.HTTPClientOptions{ + Version: 2, + }, + buildRequest: func(t *testing.T) *http.Request { + t.Helper() + return &http.Request{Method: http.MethodGet} + }, + wantErrSubstr: "missing request URL", + }, + { + name: "unsupported scheme rejected", + options: option.HTTPClientOptions{ + Version: 2, + }, + buildRequest: func(t *testing.T) *http.Request { + t.Helper() + return newAppleHTTPRequest(t, http.MethodGet, "ftp://localhost/file", nil) + }, + wantErrSubstr: "unsupported URL scheme: ftp", + }, + { + name: "server name mismatch rejected", + options: option.HTTPClientOptions{ + Version: 2, + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "example.com", + }, + }, + }, + buildRequest: func(t *testing.T) *http.Request { + t.Helper() + return newAppleHTTPRequest(t, http.MethodGet, "https://localhost/path", nil) + }, + wantErrSubstr: "tls.server_name is unsupported in Apple HTTP engine unless it matches request host", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + transport := newAppleHTTPTestTransport(t, nil, testCase.options) + response, err := transport.RoundTrip(testCase.buildRequest(t)) + if err == nil { + response.Body.Close() + t.Fatal("expected error") + } + if !strings.Contains(err.Error(), testCase.wantErrSubstr) { + t.Fatalf("unexpected error: %v", err) + } + }) + } +} + +func TestAppleTransportCancellationRecovery(t *testing.T) { + server := startAppleHTTPTestServer(t, func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/block": + select { + case <-r.Context().Done(): + return + case <-time.After(appleHTTPTestTimeout): + http.Error(w, "request was not canceled", http.StatusGatewayTimeout) + } + default: + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("ok")) + } + }) + + transport := newAppleHTTPTestTransport(t, server, option.HTTPClientOptions{ + Version: 2, + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: appleHTTPServerTLSOptions(server), + }, + }) + + for index := range appleHTTPRecoveryLoops { + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + request := newAppleHTTPRequestWithContext(t, ctx, http.MethodGet, server.URL("/block"), nil) + response, err := transport.RoundTrip(request) + cancel() + if err == nil { + response.Body.Close() + t.Fatalf("iteration %d: expected cancellation error", index) + } + if !errors.Is(err, context.DeadlineExceeded) && !errors.Is(err, context.Canceled) { + t.Fatalf("iteration %d: unexpected cancellation error: %v", index, err) + } + + response, err = transport.RoundTrip(newAppleHTTPRequest(t, http.MethodGet, server.URL("/ok"), nil)) + if err != nil { + t.Fatalf("iteration %d: follow-up request failed: %v", index, err) + } + if body := readResponseBody(t, response); body != "ok" { + response.Body.Close() + t.Fatalf("iteration %d: unexpected follow-up body: %q", index, body) + } + response.Body.Close() + } +} + +func TestAppleTransportLifecycle(t *testing.T) { + server := startAppleHTTPTestServer(t, func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("ok")) + }) + + transport := newAppleHTTPTestTransport(t, server, option.HTTPClientOptions{ + Version: 2, + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: appleHTTPServerTLSOptions(server), + }, + }) + + assertAppleHTTPSucceeds(t, transport, server.URL("/original")) + + transport.CloseIdleConnections() + assertAppleHTTPSucceeds(t, transport, server.URL("/reset")) + + innerTransport := transport.(*appleTransport) + err := innerTransport.Close() + if err != nil { + t.Fatal(err) + } + + response, err := innerTransport.RoundTrip(newAppleHTTPRequest(t, http.MethodGet, server.URL("/closed"), nil)) + if err == nil { + response.Body.Close() + t.Fatal("expected closed transport to fail") + } + if !errors.Is(err, net.ErrClosed) { + t.Fatalf("unexpected closed transport error: %v", err) + } +} + +func startAppleHTTPTestServer(t *testing.T, handler http.HandlerFunc) *appleHTTPTestServer { + t.Helper() + + serverCertificate, serverCertificatePEM := newAppleHTTPTestCertificate(t, "localhost") + server := httptest.NewUnstartedServer(handler) + server.EnableHTTP2 = true + server.TLS = &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS12, + } + server.StartTLS() + t.Cleanup(server.Close) + + parsedURL, err := url.Parse(server.URL) + if err != nil { + t.Fatal(err) + } + baseURL := *parsedURL + baseURL.Host = net.JoinHostPort("localhost", parsedURL.Port()) + + return &appleHTTPTestServer{ + server: server, + baseURL: baseURL.String(), + dialHost: parsedURL.Hostname(), + certificate: serverCertificate, + certificatePEM: serverCertificatePEM, + publicKeyHash: certificatePublicKeySHA256(t, serverCertificate.Certificate[0]), + } +} + +func (s *appleHTTPTestServer) URL(path string) string { + if path == "" { + return s.baseURL + } + if strings.HasPrefix(path, "/") { + return s.baseURL + path + } + return s.baseURL + "/" + path +} + +func newAppleHTTPTestTransport(t *testing.T, server *appleHTTPTestServer, options option.HTTPClientOptions) innerTransport { + t.Helper() + + ctx := newAppleHTTPTestContext() + dialer := &appleHTTPTestDialer{ + hostMap: make(map[string]string), + } + if server != nil { + dialer.hostMap["localhost"] = server.dialHost + } + + transport, err := newAppleTransport(ctx, log.NewNOPFactory().NewLogger("httpclient"), dialer, options) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + _ = transport.Close() + }) + return transport +} + +func newAppleHTTPTestContext() context.Context { + return service.ContextWith[adapter.ConnectionManager]( + context.Background(), + route.NewConnectionManager(log.NewNOPFactory().NewLogger("connection")), + ) +} + +func appleTransportAnchorOptions(certificatePEM string) option.HTTPClientOptions { + return option.HTTPClientOptions{ + Version: 2, + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "localhost", + MinVersion: "1.2", + Certificate: badoption.Listable[string]{certificatePEM}, + }, + }, + } +} + +func restoreAppleTransportFactories(t *testing.T) { + t.Helper() + oldAnchors := newAppleUserAnchors + oldBridge := newAppleProxyBridge + oldSession := newAppleTransportSession + t.Cleanup(func() { + newAppleUserAnchors = oldAnchors + newAppleProxyBridge = oldBridge + newAppleTransportSession = oldSession + }) +} + +func (d *appleHTTPTestDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + host := destination.AddrString() + if destination.IsDomain() { + host = destination.Fqdn + if mappedHost, loaded := d.hostMap[host]; loaded { + host = mappedHost + } + } + return d.dialer.DialContext(ctx, network, net.JoinHostPort(host, strconv.Itoa(int(destination.Port)))) +} + +func (d *appleHTTPTestDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + host := destination.AddrString() + if destination.IsDomain() { + host = destination.Fqdn + if mappedHost, loaded := d.hostMap[host]; loaded { + host = mappedHost + } + } + if host == "" { + host = "127.0.0.1" + } + return d.listener.ListenPacket(ctx, N.NetworkUDP, net.JoinHostPort(host, strconv.Itoa(int(destination.Port)))) +} + +func newAppleHTTPTestCertificate(t *testing.T, serverName string) (stdtls.Certificate, string) { + t.Helper() + + privateKeyPEM, certificatePEM, err := boxTLS.GenerateCertificate(nil, nil, time.Now, serverName, time.Now().Add(time.Hour)) + if err != nil { + t.Fatal(err) + } + certificate, err := stdtls.X509KeyPair(certificatePEM, privateKeyPEM) + if err != nil { + t.Fatal(err) + } + return certificate, string(certificatePEM) +} + +func certificatePublicKeySHA256(t *testing.T, certificateDER []byte) []byte { + t.Helper() + + certificate, err := x509.ParseCertificate(certificateDER) + if err != nil { + t.Fatal(err) + } + publicKeyDER, err := x509.MarshalPKIXPublicKey(certificate.PublicKey) + if err != nil { + t.Fatal(err) + } + hashValue := sha256.Sum256(publicKeyDER) + return append([]byte(nil), hashValue[:]...) +} + +func appleHTTPServerTLSOptions(server *appleHTTPTestServer) *option.OutboundTLSOptions { + return &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "localhost", + Certificate: badoption.Listable[string]{server.certificatePEM}, + } +} + +func newAppleHTTPRequest(t *testing.T, method string, rawURL string, body []byte) *http.Request { + t.Helper() + return newAppleHTTPRequestWithContext(t, context.Background(), method, rawURL, body) +} + +func newAppleHTTPRequestWithContext(t *testing.T, ctx context.Context, method string, rawURL string, body []byte) *http.Request { + t.Helper() + request, err := http.NewRequestWithContext(ctx, method, rawURL, bytes.NewReader(body)) + if err != nil { + t.Fatal(err) + } + return request +} + +func waitObservedRequest(t *testing.T, requests <-chan appleHTTPObservedRequest) appleHTTPObservedRequest { + t.Helper() + + select { + case request := <-requests: + return request + case <-time.After(appleHTTPTestTimeout): + t.Fatal("timed out waiting for observed request") + return appleHTTPObservedRequest{} + } +} + +func readResponseBody(t *testing.T, response *http.Response) string { + t.Helper() + + body, err := io.ReadAll(response.Body) + if err != nil { + t.Fatal(err) + } + return string(body) +} + +func assertAppleHTTPSucceeds(t *testing.T, transport http.RoundTripper, rawURL string) { + t.Helper() + + response, err := transport.RoundTrip(newAppleHTTPRequest(t, http.MethodGet, rawURL, nil)) + if err != nil { + t.Fatal(err) + } + defer response.Body.Close() + if body := readResponseBody(t, response); body != "ok" { + t.Fatalf("unexpected response body: %q", body) + } +} diff --git a/common/httpclient/apple_transport_stub.go b/common/httpclient/apple_transport_stub.go new file mode 100644 index 0000000000..9735998f4e --- /dev/null +++ b/common/httpclient/apple_transport_stub.go @@ -0,0 +1,16 @@ +//go:build !darwin || !cgo + +package httpclient + +import ( + "context" + + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + N "github.com/sagernet/sing/common/network" +) + +func newAppleTransport(ctx context.Context, logger logger.ContextLogger, rawDialer N.Dialer, options option.HTTPClientOptions) (innerTransport, error) { + return nil, E.New("Apple HTTP engine is not available on non-Apple platforms") +} diff --git a/common/httpclient/client.go b/common/httpclient/client.go new file mode 100644 index 0000000000..9dbb8cc1d5 --- /dev/null +++ b/common/httpclient/client.go @@ -0,0 +1,131 @@ +package httpclient + +import ( + "context" + "time" + + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + N "github.com/sagernet/sing/common/network" +) + +func NewTransport(ctx context.Context, logger logger.ContextLogger, tag string, options option.HTTPClientOptions) (*ManagedTransport, error) { + rawDialer, err := dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: options.DialerOptions, + RemoteIsDomain: true, + DirectResolver: options.DirectResolver, + ResolverOnDetour: options.ResolveOnDetour, + NewDialer: options.ResolveOnDetour, + DisableEmptyDirectCheck: options.DisableEmptyDirectCheck, + DefaultOutbound: options.DefaultOutbound, + }) + if err != nil { + return nil, err + } + headers := options.Headers.Build() + host := headers.Get("Host") + headers.Del("Host") + + var cheapRebuild bool + switch options.Engine { + case C.TLSEngineApple: + inner, transportErr := newAppleTransport(ctx, logger, rawDialer, options) + if transportErr != nil { + return nil, transportErr + } + managedTransport := &ManagedTransport{ + dialer: rawDialer, + headers: headers, + host: host, + tag: tag, + factory: func() (innerTransport, error) { + return newAppleTransport(ctx, logger, rawDialer, options) + }, + } + managedTransport.epoch.Store(&transportEpoch{transport: inner}) + return managedTransport, nil + case "", C.TLSEngineGo: + cheapRebuild = true + default: + return nil, E.New("unknown HTTP engine: ", options.Engine) + } + tlsOptions := common.PtrValueOrDefault(options.TLS) + tlsOptions.Enabled = true + baseTLSConfig, err := tls.NewClientWithOptions(tls.ClientOptions{ + Context: ctx, + Logger: logger, + Options: tlsOptions, + AllowEmptyServerName: true, + }) + if err != nil { + return nil, err + } + inner, err := newTransport(rawDialer, baseTLSConfig, options) + if err != nil { + return nil, err + } + managedTransport := &ManagedTransport{ + cheapRebuild: cheapRebuild, + dialer: rawDialer, + headers: headers, + host: host, + tag: tag, + factory: func() (innerTransport, error) { + return newTransport(rawDialer, baseTLSConfig, options) + }, + } + managedTransport.epoch.Store(&transportEpoch{transport: inner}) + return managedTransport, nil +} + +func newTransport(rawDialer N.Dialer, baseTLSConfig tls.Config, options option.HTTPClientOptions) (innerTransport, error) { + version := options.Version + if version == 0 { + version = 2 + } + fallbackDelay := time.Duration(options.DialerOptions.FallbackDelay) + if fallbackDelay == 0 { + fallbackDelay = 300 * time.Millisecond + } + var transport innerTransport + var err error + switch version { + case 1: + transport = newHTTP1Transport(rawDialer, baseTLSConfig) + case 2: + if options.DisableVersionFallback { + transport, err = newHTTP2Transport(rawDialer, baseTLSConfig, options.HTTP2Options) + } else { + transport, err = newHTTP2FallbackTransport(rawDialer, baseTLSConfig, options.HTTP2Options) + } + case 3: + if baseTLSConfig != nil { + _, err = baseTLSConfig.STDConfig() + if err != nil { + return nil, err + } + } + if options.DisableVersionFallback { + transport, err = newHTTP3Transport(rawDialer, baseTLSConfig, options.HTTP3Options) + } else { + var h2Fallback innerTransport + h2Fallback, err = newHTTP2FallbackTransport(rawDialer, baseTLSConfig, options.HTTP2Options) + if err != nil { + return nil, err + } + transport, err = newHTTP3FallbackTransport(rawDialer, baseTLSConfig, h2Fallback, options.HTTP3Options, fallbackDelay) + } + default: + return nil, E.New("unknown HTTP version: ", version) + } + if err != nil { + return nil, err + } + return transport, nil +} diff --git a/common/httpclient/context.go b/common/httpclient/context.go new file mode 100644 index 0000000000..883a25e20c --- /dev/null +++ b/common/httpclient/context.go @@ -0,0 +1,14 @@ +package httpclient + +import "context" + +type transportKey struct{} + +func contextWithTransportTag(ctx context.Context, transportTag string) context.Context { + return context.WithValue(ctx, transportKey{}, transportTag) +} + +func transportTagFromContext(ctx context.Context) (string, bool) { + value, loaded := ctx.Value(transportKey{}).(string) + return value, loaded +} diff --git a/common/httpclient/helpers.go b/common/httpclient/helpers.go new file mode 100644 index 0000000000..7cc78cc6e1 --- /dev/null +++ b/common/httpclient/helpers.go @@ -0,0 +1,116 @@ +package httpclient + +import ( + "context" + stdTLS "crypto/tls" + "io" + "net" + "net/http" + "strings" + + "github.com/sagernet/sing-box/common/tls" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + "golang.org/x/net/idna" +) + +func dialTLS(ctx context.Context, rawDialer N.Dialer, baseTLSConfig tls.Config, destination M.Socksaddr, nextProtos []string, expectProto string) (net.Conn, error) { + if baseTLSConfig == nil { + return nil, E.New("TLS transport unavailable") + } + tlsConfig := baseTLSConfig.Clone() + if tlsConfig.ServerName() == "" && destination.IsValid() { + tlsConfig.SetServerName(destination.AddrString()) + } + tlsConfig.SetNextProtos(nextProtos) + conn, err := rawDialer.DialContext(ctx, N.NetworkTCP, destination) + if err != nil { + return nil, err + } + tlsConn, err := tls.ClientHandshake(ctx, conn, tlsConfig) + if err != nil { + conn.Close() + return nil, err + } + if expectProto != "" && tlsConn.ConnectionState().NegotiatedProtocol != expectProto { + tlsConn.Close() + return nil, errHTTP2Fallback + } + return tlsConn, nil +} + +func applyHeaders(request *http.Request, headers http.Header, host string) { + for header, values := range headers { + request.Header[header] = append([]string(nil), values...) + } + if host != "" { + request.Host = host + } +} + +func requestRequiresHTTP1(request *http.Request) bool { + return strings.Contains(strings.ToLower(request.Header.Get("Connection")), "upgrade") && + strings.EqualFold(request.Header.Get("Upgrade"), "websocket") +} + +func requestReplayable(request *http.Request) bool { + return request.Body == nil || request.Body == http.NoBody || request.GetBody != nil +} + +func cloneRequestForRetry(request *http.Request) *http.Request { + cloned := request.Clone(request.Context()) + if request.Body != nil && request.Body != http.NoBody && request.GetBody != nil { + cloned.Body = mustGetBody(request) + } + return cloned +} + +func mustGetBody(request *http.Request) io.ReadCloser { + body, err := request.GetBody() + if err != nil { + panic(err) + } + return body +} + +func requestAuthority(request *http.Request) string { + if request == nil || request.URL == nil || request.URL.Host == "" { + return "" + } + host, port, err := net.SplitHostPort(request.URL.Host) + if err != nil { + host = request.URL.Host + port = "" + } + if port == "" { + if request.URL.Scheme == "http" { + port = "80" + } else { + port = "443" + } + } + if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") { + return host + ":" + port + } + ascii, idnaErr := idna.Lookup.ToASCII(host) + if idnaErr == nil { + host = ascii + } else { + host = strings.ToLower(host) + } + return net.JoinHostPort(host, port) +} + +func buildSTDTLSConfig(baseTLSConfig tls.Config, destination M.Socksaddr, nextProtos []string) (*stdTLS.Config, error) { + if baseTLSConfig == nil { + return nil, nil + } + tlsConfig := baseTLSConfig.Clone() + if tlsConfig.ServerName() == "" && destination.IsValid() { + tlsConfig.SetServerName(destination.AddrString()) + } + tlsConfig.SetNextProtos(nextProtos) + return tlsConfig.STDConfig() +} diff --git a/common/httpclient/helpers_test.go b/common/httpclient/helpers_test.go new file mode 100644 index 0000000000..2c451e0a58 --- /dev/null +++ b/common/httpclient/helpers_test.go @@ -0,0 +1,51 @@ +package httpclient + +import ( + "net/http" + "net/url" + "testing" +) + +func TestRequestAuthority(t *testing.T) { + testCases := []struct { + name string + url string + expect string + }{ + {name: "https default port", url: "https://example.com/foo", expect: "example.com:443"}, + {name: "http default port", url: "http://example.com/foo", expect: "example.com:80"}, + {name: "https explicit port", url: "https://example.com:8443/foo", expect: "example.com:8443"}, + {name: "https uppercase host", url: "https://EXAMPLE.COM/foo", expect: "example.com:443"}, + {name: "https ipv6 default port", url: "https://[2001:db8::1]/foo", expect: "[2001:db8::1]:443"}, + {name: "https ipv6 explicit port", url: "https://[2001:db8::1]:8443/foo", expect: "[2001:db8::1]:8443"}, + {name: "https ipv4", url: "https://192.0.2.1/foo", expect: "192.0.2.1:443"}, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + parsed, err := url.Parse(testCase.url) + if err != nil { + t.Fatalf("parse url: %v", err) + } + got := requestAuthority(&http.Request{URL: parsed}) + if got != testCase.expect { + t.Fatalf("got %q, want %q", got, testCase.expect) + } + }) + } + + t.Run("nil request", func(t *testing.T) { + if got := requestAuthority(nil); got != "" { + t.Fatalf("got %q, want empty", got) + } + }) + t.Run("nil URL", func(t *testing.T) { + if got := requestAuthority(&http.Request{}); got != "" { + t.Fatalf("got %q, want empty", got) + } + }) + t.Run("empty host", func(t *testing.T) { + if got := requestAuthority(&http.Request{URL: &url.URL{Scheme: "https"}}); got != "" { + t.Fatalf("got %q, want empty", got) + } + }) +} diff --git a/common/httpclient/http1_transport.go b/common/httpclient/http1_transport.go new file mode 100644 index 0000000000..ad2ccedb8f --- /dev/null +++ b/common/httpclient/http1_transport.go @@ -0,0 +1,42 @@ +package httpclient + +import ( + "context" + "net" + "net/http" + + "github.com/sagernet/sing-box/common/tls" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +type http1Transport struct { + transport *http.Transport +} + +func newHTTP1Transport(rawDialer N.Dialer, baseTLSConfig tls.Config) *http1Transport { + transport := &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return rawDialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) + }, + } + if baseTLSConfig != nil { + transport.DialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) { + return dialTLS(ctx, rawDialer, baseTLSConfig, M.ParseSocksaddr(addr), []string{"http/1.1"}, "") + } + } + return &http1Transport{transport: transport} +} + +func (t *http1Transport) RoundTrip(request *http.Request) (*http.Response, error) { + return t.transport.RoundTrip(request) +} + +func (t *http1Transport) CloseIdleConnections() { + t.transport.CloseIdleConnections() +} + +func (t *http1Transport) Close() error { + t.CloseIdleConnections() + return nil +} diff --git a/common/httpclient/http2_config.go b/common/httpclient/http2_config.go new file mode 100644 index 0000000000..9e1d871fdc --- /dev/null +++ b/common/httpclient/http2_config.go @@ -0,0 +1,42 @@ +package httpclient + +import ( + stdTLS "crypto/tls" + "net/http" + "time" + + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + + "golang.org/x/net/http2" +) + +func CloneHTTP2Transport(transport *http2.Transport) *http2.Transport { + return &http2.Transport{ + ReadIdleTimeout: transport.ReadIdleTimeout, + PingTimeout: transport.PingTimeout, + DialTLSContext: transport.DialTLSContext, + } +} + +func ConfigureHTTP2Transport(options option.HTTP2Options) (*http2.Transport, error) { + stdTransport := &http.Transport{ + TLSClientConfig: &stdTLS.Config{}, + HTTP2: &http.HTTP2Config{ + MaxReceiveBufferPerStream: int(options.StreamReceiveWindow.Value()), + MaxReceiveBufferPerConnection: int(options.ConnectionReceiveWindow.Value()), + MaxConcurrentStreams: options.MaxConcurrentStreams, + SendPingTimeout: time.Duration(options.KeepAlivePeriod), + PingTimeout: time.Duration(options.IdleTimeout), + }, + } + h2Transport, err := http2.ConfigureTransports(stdTransport) + if err != nil { + return nil, E.Cause(err, "configure HTTP/2 transport") + } + // ConfigureTransports binds ConnPool to the throwaway http.Transport; sever it so DialTLSContext is used directly. + h2Transport.ConnPool = nil + h2Transport.ReadIdleTimeout = time.Duration(options.KeepAlivePeriod) + h2Transport.PingTimeout = time.Duration(options.IdleTimeout) + return h2Transport, nil +} diff --git a/common/httpclient/http2_fallback_transport.go b/common/httpclient/http2_fallback_transport.go new file mode 100644 index 0000000000..682b1ebadf --- /dev/null +++ b/common/httpclient/http2_fallback_transport.go @@ -0,0 +1,98 @@ +package httpclient + +import ( + "context" + stdTLS "crypto/tls" + "errors" + "net" + "net/http" + "sync" + + "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + "golang.org/x/net/http2" +) + +var errHTTP2Fallback = E.New("fallback to HTTP/1.1") + +type http2FallbackTransport struct { + h2Transport *http2.Transport + h1Transport *http1Transport + fallbackAccess sync.RWMutex + fallbackAuthority map[string]struct{} +} + +func newHTTP2FallbackTransport(rawDialer N.Dialer, baseTLSConfig tls.Config, options option.HTTP2Options) (*http2FallbackTransport, error) { + h1 := newHTTP1Transport(rawDialer, baseTLSConfig) + h2Transport, err := ConfigureHTTP2Transport(options) + if err != nil { + return nil, err + } + h2Transport.DialTLSContext = func(ctx context.Context, network, addr string, _ *stdTLS.Config) (net.Conn, error) { + return dialTLS(ctx, rawDialer, baseTLSConfig, M.ParseSocksaddr(addr), []string{http2.NextProtoTLS, "http/1.1"}, http2.NextProtoTLS) + } + return &http2FallbackTransport{ + h2Transport: h2Transport, + h1Transport: h1, + fallbackAuthority: make(map[string]struct{}), + }, nil +} + +func (t *http2FallbackTransport) isH2Fallback(authority string) bool { + if authority == "" { + return false + } + t.fallbackAccess.RLock() + _, found := t.fallbackAuthority[authority] + t.fallbackAccess.RUnlock() + return found +} + +func (t *http2FallbackTransport) markH2Fallback(authority string) { + if authority == "" { + return + } + t.fallbackAccess.Lock() + t.fallbackAuthority[authority] = struct{}{} + t.fallbackAccess.Unlock() +} + +func (t *http2FallbackTransport) RoundTrip(request *http.Request) (*http.Response, error) { + return t.roundTrip(request, true) +} + +func (t *http2FallbackTransport) roundTrip(request *http.Request, allowHTTP1Fallback bool) (*http.Response, error) { + if request.URL.Scheme != "https" || requestRequiresHTTP1(request) { + return t.h1Transport.RoundTrip(request) + } + authority := requestAuthority(request) + if t.isH2Fallback(authority) { + if !allowHTTP1Fallback { + return nil, errHTTP2Fallback + } + return t.h1Transport.RoundTrip(request) + } + response, err := t.h2Transport.RoundTrip(request) + if err == nil { + return response, nil + } + if !errors.Is(err, errHTTP2Fallback) || !allowHTTP1Fallback { + return nil, err + } + t.markH2Fallback(authority) + return t.h1Transport.RoundTrip(cloneRequestForRetry(request)) +} + +func (t *http2FallbackTransport) CloseIdleConnections() { + t.h1Transport.CloseIdleConnections() + t.h2Transport.CloseIdleConnections() +} + +func (t *http2FallbackTransport) Close() error { + t.CloseIdleConnections() + return nil +} diff --git a/common/httpclient/http2_fallback_transport_test.go b/common/httpclient/http2_fallback_transport_test.go new file mode 100644 index 0000000000..2c2085c863 --- /dev/null +++ b/common/httpclient/http2_fallback_transport_test.go @@ -0,0 +1,37 @@ +package httpclient + +import ( + "testing" +) + +func TestHTTP2FallbackAuthorityIsolation(t *testing.T) { + transport := &http2FallbackTransport{fallbackAuthority: make(map[string]struct{})} + + transport.markH2Fallback("a.example:443") + if !transport.isH2Fallback("a.example:443") { + t.Fatal("a.example:443 should be marked") + } + if transport.isH2Fallback("b.example:443") { + t.Fatal("b.example:443 must remain unmarked after marking a.example") + } + + transport.markH2Fallback("b.example:443") + if !transport.isH2Fallback("b.example:443") { + t.Fatal("b.example:443 should be marked after explicit mark") + } + if !transport.isH2Fallback("a.example:443") { + t.Fatal("a.example:443 mark must survive marking another authority") + } +} + +func TestHTTP2FallbackEmptyAuthorityNoOp(t *testing.T) { + transport := &http2FallbackTransport{fallbackAuthority: make(map[string]struct{})} + + transport.markH2Fallback("") + if len(transport.fallbackAuthority) != 0 { + t.Fatalf("empty authority must not be stored, got %d entries", len(transport.fallbackAuthority)) + } + if transport.isH2Fallback("") { + t.Fatal("isH2Fallback must be false for empty authority") + } +} diff --git a/common/httpclient/http2_transport.go b/common/httpclient/http2_transport.go new file mode 100644 index 0000000000..78e7ae6824 --- /dev/null +++ b/common/httpclient/http2_transport.go @@ -0,0 +1,52 @@ +package httpclient + +import ( + "context" + stdTLS "crypto/tls" + "net" + "net/http" + + "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/option" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + "golang.org/x/net/http2" +) + +type http2Transport struct { + h2Transport *http2.Transport + h1Transport *http1Transport +} + +func newHTTP2Transport(rawDialer N.Dialer, baseTLSConfig tls.Config, options option.HTTP2Options) (*http2Transport, error) { + h1 := newHTTP1Transport(rawDialer, baseTLSConfig) + h2Transport, err := ConfigureHTTP2Transport(options) + if err != nil { + return nil, err + } + h2Transport.DialTLSContext = func(ctx context.Context, network, addr string, _ *stdTLS.Config) (net.Conn, error) { + return dialTLS(ctx, rawDialer, baseTLSConfig, M.ParseSocksaddr(addr), []string{http2.NextProtoTLS}, http2.NextProtoTLS) + } + return &http2Transport{ + h2Transport: h2Transport, + h1Transport: h1, + }, nil +} + +func (t *http2Transport) RoundTrip(request *http.Request) (*http.Response, error) { + if request.URL.Scheme != "https" || requestRequiresHTTP1(request) { + return t.h1Transport.RoundTrip(request) + } + return t.h2Transport.RoundTrip(request) +} + +func (t *http2Transport) CloseIdleConnections() { + t.h1Transport.CloseIdleConnections() + t.h2Transport.CloseIdleConnections() +} + +func (t *http2Transport) Close() error { + t.CloseIdleConnections() + return nil +} diff --git a/common/httpclient/http3_transport.go b/common/httpclient/http3_transport.go new file mode 100644 index 0000000000..d3eb5bc155 --- /dev/null +++ b/common/httpclient/http3_transport.go @@ -0,0 +1,321 @@ +//go:build with_quic + +package httpclient + +import ( + "context" + stdTLS "crypto/tls" + "errors" + "net/http" + "sync" + "time" + + "github.com/sagernet/quic-go" + "github.com/sagernet/quic-go/http3" + "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +type http3Transport struct { + h3Transport *http3.Transport +} + +type http3BrokenEntry struct { + until time.Time + backoff time.Duration +} + +type http3FallbackTransport struct { + h3Transport *http3.Transport + h2Fallback innerTransport + fallbackDelay time.Duration + brokenAccess sync.Mutex + broken map[string]http3BrokenEntry +} + +func newHTTP3RoundTripper( + rawDialer N.Dialer, + baseTLSConfig tls.Config, + options option.QUICOptions, +) *http3.Transport { + var handshakeTimeout time.Duration + if baseTLSConfig != nil { + handshakeTimeout = baseTLSConfig.HandshakeTimeout() + } + quicConfig := &quic.Config{ + InitialStreamReceiveWindow: options.StreamReceiveWindow.Value(), + MaxStreamReceiveWindow: options.StreamReceiveWindow.Value(), + InitialConnectionReceiveWindow: options.ConnectionReceiveWindow.Value(), + MaxConnectionReceiveWindow: options.ConnectionReceiveWindow.Value(), + KeepAlivePeriod: time.Duration(options.KeepAlivePeriod), + MaxIdleTimeout: time.Duration(options.IdleTimeout), + DisablePathMTUDiscovery: options.DisablePathMTUDiscovery, + } + if options.InitialPacketSize > 0 { + quicConfig.InitialPacketSize = uint16(options.InitialPacketSize) + } + if options.MaxConcurrentStreams > 0 { + quicConfig.MaxIncomingStreams = int64(options.MaxConcurrentStreams) + } + if handshakeTimeout > 0 { + quicConfig.HandshakeIdleTimeout = handshakeTimeout + } + h3Transport := &http3.Transport{ + TLSClientConfig: &stdTLS.Config{}, + QUICConfig: quicConfig, + Dial: func(ctx context.Context, addr string, tlsConfig *stdTLS.Config, quicConfig *quic.Config) (*quic.Conn, error) { + if handshakeTimeout > 0 && quicConfig.HandshakeIdleTimeout == 0 { + quicConfig = quicConfig.Clone() + quicConfig.HandshakeIdleTimeout = handshakeTimeout + } + if baseTLSConfig != nil { + var err error + tlsConfig, err = buildSTDTLSConfig(baseTLSConfig, M.ParseSocksaddr(addr), []string{http3.NextProtoH3}) + if err != nil { + return nil, err + } + } else { + tlsConfig = tlsConfig.Clone() + tlsConfig.NextProtos = []string{http3.NextProtoH3} + } + conn, err := rawDialer.DialContext(ctx, N.NetworkUDP, M.ParseSocksaddr(addr)) + if err != nil { + return nil, err + } + quicConn, err := quic.DialEarly(ctx, bufio.NewUnbindPacketConn(conn), conn.RemoteAddr(), tlsConfig, quicConfig) + if err != nil { + conn.Close() + return nil, err + } + return quicConn, nil + }, + } + return h3Transport +} + +func newHTTP3Transport( + rawDialer N.Dialer, + baseTLSConfig tls.Config, + options option.QUICOptions, +) (innerTransport, error) { + return &http3Transport{ + h3Transport: newHTTP3RoundTripper(rawDialer, baseTLSConfig, options), + }, nil +} + +func newHTTP3FallbackTransport( + rawDialer N.Dialer, + baseTLSConfig tls.Config, + h2Fallback innerTransport, + options option.QUICOptions, + fallbackDelay time.Duration, +) (innerTransport, error) { + return &http3FallbackTransport{ + h3Transport: newHTTP3RoundTripper(rawDialer, baseTLSConfig, options), + h2Fallback: h2Fallback, + fallbackDelay: fallbackDelay, + broken: make(map[string]http3BrokenEntry), + }, nil +} + +func (t *http3Transport) RoundTrip(request *http.Request) (*http.Response, error) { + return t.h3Transport.RoundTrip(request) +} + +func (t *http3Transport) CloseIdleConnections() { + t.h3Transport.CloseIdleConnections() +} + +func (t *http3Transport) Close() error { + t.CloseIdleConnections() + return t.h3Transport.Close() +} + +func (t *http3FallbackTransport) RoundTrip(request *http.Request) (*http.Response, error) { + if request.URL.Scheme != "https" || requestRequiresHTTP1(request) { + return t.h2Fallback.RoundTrip(request) + } + return t.roundTripHTTP3(request) +} + +func (t *http3FallbackTransport) roundTripHTTP3(request *http.Request) (*http.Response, error) { + authority := requestAuthority(request) + if t.h3Broken(authority) { + return t.h2FallbackRoundTrip(request) + } + response, err := t.h3Transport.RoundTripOpt(request, http3.RoundTripOpt{OnlyCachedConn: true}) + if err == nil { + t.clearH3Broken(authority) + return response, nil + } + if !errors.Is(err, http3.ErrNoCachedConn) { + t.markH3Broken(authority) + return t.h2FallbackRoundTrip(cloneRequestForRetry(request)) + } + if !requestReplayable(request) { + response, err = t.h3Transport.RoundTrip(request) + if err == nil { + t.clearH3Broken(authority) + return response, nil + } + t.markH3Broken(authority) + return nil, err + } + return t.roundTripHTTP3Race(request, authority) +} + +func (t *http3FallbackTransport) roundTripHTTP3Race(request *http.Request, authority string) (*http.Response, error) { + ctx, cancel := context.WithCancel(request.Context()) + defer cancel() + type result struct { + response *http.Response + err error + h3 bool + } + results := make(chan result, 2) + startRoundTrip := func(request *http.Request, useH3 bool) { + request = request.WithContext(ctx) + var ( + response *http.Response + err error + ) + if useH3 { + response, err = t.h3Transport.RoundTrip(request) + } else { + response, err = t.h2FallbackRoundTrip(request) + } + results <- result{response: response, err: err, h3: useH3} + } + goroutines := 1 + received := 0 + drainRemaining := func() { + cancel() + for range goroutines - received { + go func() { + loser := <-results + if loser.response != nil && loser.response.Body != nil { + loser.response.Body.Close() + } + }() + } + } + go startRoundTrip(cloneRequestForRetry(request), true) + timer := time.NewTimer(t.fallbackDelay) + defer timer.Stop() + var ( + h3Err error + fallbackErr error + ) + for { + select { + case <-timer.C: + if goroutines == 1 { + goroutines++ + go startRoundTrip(cloneRequestForRetry(request), false) + } + case raceResult := <-results: + received++ + if raceResult.err == nil { + if raceResult.h3 { + t.clearH3Broken(authority) + } + drainRemaining() + return raceResult.response, nil + } + if raceResult.h3 { + t.markH3Broken(authority) + h3Err = raceResult.err + if goroutines == 1 { + goroutines++ + if !timer.Stop() { + select { + case <-timer.C: + default: + } + } + go startRoundTrip(cloneRequestForRetry(request), false) + } + } else { + fallbackErr = raceResult.err + } + if received < goroutines { + continue + } + drainRemaining() + switch { + case h3Err != nil && fallbackErr != nil: + return nil, E.Errors(h3Err, fallbackErr) + case fallbackErr != nil: + return nil, fallbackErr + default: + return nil, h3Err + } + } + } +} + +func (t *http3FallbackTransport) h2FallbackRoundTrip(request *http.Request) (*http.Response, error) { + if fallback, isFallback := t.h2Fallback.(*http2FallbackTransport); isFallback { + return fallback.roundTrip(request, true) + } + return t.h2Fallback.RoundTrip(request) +} + +func (t *http3FallbackTransport) CloseIdleConnections() { + t.h3Transport.CloseIdleConnections() + t.h2Fallback.CloseIdleConnections() +} + +func (t *http3FallbackTransport) Close() error { + t.CloseIdleConnections() + return t.h3Transport.Close() +} + +func (t *http3FallbackTransport) h3Broken(authority string) bool { + if authority == "" { + return false + } + t.brokenAccess.Lock() + defer t.brokenAccess.Unlock() + entry, found := t.broken[authority] + if !found { + return false + } + if entry.until.IsZero() || !time.Now().Before(entry.until) { + delete(t.broken, authority) + return false + } + return true +} + +func (t *http3FallbackTransport) clearH3Broken(authority string) { + if authority == "" { + return + } + t.brokenAccess.Lock() + delete(t.broken, authority) + t.brokenAccess.Unlock() +} + +func (t *http3FallbackTransport) markH3Broken(authority string) { + if authority == "" { + return + } + t.brokenAccess.Lock() + defer t.brokenAccess.Unlock() + entry := t.broken[authority] + if entry.backoff == 0 { + entry.backoff = 5 * time.Minute + } else { + entry.backoff *= 2 + if entry.backoff > 48*time.Hour { + entry.backoff = 48 * time.Hour + } + } + entry.until = time.Now().Add(entry.backoff) + t.broken[authority] = entry +} diff --git a/common/httpclient/http3_transport_stub.go b/common/httpclient/http3_transport_stub.go new file mode 100644 index 0000000000..f86a9f3653 --- /dev/null +++ b/common/httpclient/http3_transport_stub.go @@ -0,0 +1,30 @@ +//go:build !with_quic + +package httpclient + +import ( + "time" + + "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + N "github.com/sagernet/sing/common/network" +) + +func newHTTP3FallbackTransport( + rawDialer N.Dialer, + baseTLSConfig tls.Config, + h2Fallback innerTransport, + options option.QUICOptions, + fallbackDelay time.Duration, +) (innerTransport, error) { + return nil, E.New("HTTP/3 requires building with the with_quic tag") +} + +func newHTTP3Transport( + rawDialer N.Dialer, + baseTLSConfig tls.Config, + options option.QUICOptions, +) (innerTransport, error) { + return nil, E.New("HTTP/3 requires building with the with_quic tag") +} diff --git a/common/httpclient/http3_transport_test.go b/common/httpclient/http3_transport_test.go new file mode 100644 index 0000000000..600e88db06 --- /dev/null +++ b/common/httpclient/http3_transport_test.go @@ -0,0 +1,99 @@ +//go:build with_quic + +package httpclient + +import ( + "testing" + "time" +) + +func TestHTTP3BrokenAuthorityIsolation(t *testing.T) { + transport := &http3FallbackTransport{broken: make(map[string]http3BrokenEntry)} + + transport.markH3Broken("a.example:443") + if !transport.h3Broken("a.example:443") { + t.Fatal("a.example:443 should be broken after mark") + } + if transport.h3Broken("b.example:443") { + t.Fatal("b.example:443 must not be affected by marking a.example") + } +} + +func TestHTTP3BrokenBackoffPerAuthority(t *testing.T) { + transport := &http3FallbackTransport{broken: make(map[string]http3BrokenEntry)} + + transport.markH3Broken("a.example:443") + if transport.broken["a.example:443"].backoff != 5*time.Minute { + t.Fatalf("first mark should set backoff to 5m, got %v", transport.broken["a.example:443"].backoff) + } + transport.markH3Broken("a.example:443") + if transport.broken["a.example:443"].backoff != 10*time.Minute { + t.Fatalf("second mark should double backoff to 10m, got %v", transport.broken["a.example:443"].backoff) + } + transport.markH3Broken("a.example:443") + if transport.broken["a.example:443"].backoff != 20*time.Minute { + t.Fatalf("third mark should double to 20m, got %v", transport.broken["a.example:443"].backoff) + } + + if _, found := transport.broken["b.example:443"]; found { + t.Fatal("marking a.example must not leak into b.example backoff state") + } + + transport.markH3Broken("b.example:443") + if transport.broken["b.example:443"].backoff != 5*time.Minute { + t.Fatalf("b.example first mark should start at 5m independent of a.example, got %v", transport.broken["b.example:443"].backoff) + } +} + +func TestHTTP3BrokenBackoffCap(t *testing.T) { + transport := &http3FallbackTransport{broken: make(map[string]http3BrokenEntry)} + + transport.broken["a.example:443"] = http3BrokenEntry{backoff: 48 * time.Hour, until: time.Now().Add(48 * time.Hour)} + transport.markH3Broken("a.example:443") + if transport.broken["a.example:443"].backoff != 48*time.Hour { + t.Fatalf("backoff must cap at 48h, got %v", transport.broken["a.example:443"].backoff) + } +} + +func TestHTTP3BrokenClearDeletesEntry(t *testing.T) { + transport := &http3FallbackTransport{broken: make(map[string]http3BrokenEntry)} + + transport.markH3Broken("a.example:443") + transport.markH3Broken("b.example:443") + transport.clearH3Broken("a.example:443") + + if _, found := transport.broken["a.example:443"]; found { + t.Fatal("clearH3Broken must delete the entry") + } + if !transport.h3Broken("b.example:443") { + t.Fatal("clearing a.example must not affect b.example") + } +} + +func TestHTTP3BrokenExpiredEntryGarbageCollected(t *testing.T) { + transport := &http3FallbackTransport{broken: make(map[string]http3BrokenEntry)} + + transport.broken["a.example:443"] = http3BrokenEntry{ + backoff: 5 * time.Minute, + until: time.Now().Add(-time.Second), + } + if transport.h3Broken("a.example:443") { + t.Fatal("expired entry must report not broken") + } + if _, found := transport.broken["a.example:443"]; found { + t.Fatal("expired entry must be garbage-collected on read") + } +} + +func TestHTTP3BrokenEmptyAuthorityNoOp(t *testing.T) { + transport := &http3FallbackTransport{broken: make(map[string]http3BrokenEntry)} + + transport.markH3Broken("") + if len(transport.broken) != 0 { + t.Fatalf("markH3Broken must ignore empty authority, got %d entries", len(transport.broken)) + } + if transport.h3Broken("") { + t.Fatal("h3Broken must return false for empty authority") + } + transport.clearH3Broken("") +} diff --git a/common/httpclient/managed_transport.go b/common/httpclient/managed_transport.go new file mode 100644 index 0000000000..779eccda8f --- /dev/null +++ b/common/httpclient/managed_transport.go @@ -0,0 +1,209 @@ +package httpclient + +import ( + "io" + "net/http" + "sync" + "sync/atomic" + + "github.com/sagernet/sing-box/adapter" + E "github.com/sagernet/sing/common/exceptions" + N "github.com/sagernet/sing/common/network" +) + +type innerTransport interface { + http.RoundTripper + CloseIdleConnections() + Close() error +} + +var _ adapter.HTTPTransport = (*ManagedTransport)(nil) + +type ManagedTransport struct { + epoch atomic.Pointer[transportEpoch] + rebuildAccess sync.Mutex + factory func() (innerTransport, error) + cheapRebuild bool + + dialer N.Dialer + headers http.Header + host string + tag string +} + +type transportEpoch struct { + transport innerTransport + active atomic.Int64 + marked atomic.Bool + closeOnce sync.Once +} + +type managedResponseBody struct { + body io.ReadCloser + release func() + once sync.Once +} + +func (e *transportEpoch) tryClose() { + e.closeOnce.Do(func() { + e.transport.Close() + }) +} + +func (b *managedResponseBody) Read(p []byte) (int, error) { + return b.body.Read(p) +} + +func (b *managedResponseBody) Close() error { + err := b.body.Close() + b.once.Do(b.release) + return err +} + +func (t *ManagedTransport) getEpoch() (*transportEpoch, error) { + epoch := t.epoch.Load() + if epoch != nil { + return epoch, nil + } + t.rebuildAccess.Lock() + defer t.rebuildAccess.Unlock() + epoch = t.epoch.Load() + if epoch != nil { + return epoch, nil + } + inner, err := t.factory() + if err != nil { + return nil, err + } + epoch = &transportEpoch{transport: inner} + t.epoch.Store(epoch) + return epoch, nil +} + +func (t *ManagedTransport) acquireEpoch() (*transportEpoch, error) { + for { + epoch, err := t.getEpoch() + if err != nil { + return nil, err + } + epoch.active.Add(1) + if epoch == t.epoch.Load() { + return epoch, nil + } + t.releaseEpoch(epoch) + } +} + +func (t *ManagedTransport) releaseEpoch(epoch *transportEpoch) { + if epoch.active.Add(-1) == 0 && epoch.marked.Load() { + epoch.tryClose() + } +} + +func (t *ManagedTransport) retireEpoch(epoch *transportEpoch) { + if epoch == nil { + return + } + epoch.marked.Store(true) + if epoch.active.Load() == 0 { + epoch.tryClose() + } +} + +func (t *ManagedTransport) RoundTrip(request *http.Request) (*http.Response, error) { + epoch, err := t.acquireEpoch() + if err != nil { + return nil, E.Cause(err, "rebuild http transport") + } + if t.tag != "" { + if transportTag, loaded := transportTagFromContext(request.Context()); loaded && transportTag == t.tag { + t.releaseEpoch(epoch) + return nil, E.New("HTTP request loopback in transport[", t.tag, "]") + } + request = request.Clone(contextWithTransportTag(request.Context(), t.tag)) + } else if len(t.headers) > 0 || t.host != "" { + request = request.Clone(request.Context()) + } + applyHeaders(request, t.headers, t.host) + response, roundTripErr := epoch.transport.RoundTrip(request) + if roundTripErr != nil || response == nil || response.Body == nil { + t.releaseEpoch(epoch) + return response, roundTripErr + } + response.Body = &managedResponseBody{ + body: response.Body, + release: func() { t.releaseEpoch(epoch) }, + } + return response, roundTripErr +} + +func (t *ManagedTransport) CloseIdleConnections() { + oldEpoch := t.epoch.Swap(nil) + if oldEpoch == nil { + return + } + oldEpoch.transport.CloseIdleConnections() + t.retireEpoch(oldEpoch) +} + +func (t *ManagedTransport) Reset() { + oldEpoch := t.epoch.Swap(nil) + if t.cheapRebuild { + t.rebuildAccess.Lock() + if t.epoch.Load() == nil { + inner, err := t.factory() + if err == nil { + t.epoch.Store(&transportEpoch{transport: inner}) + } + } + t.rebuildAccess.Unlock() + } + t.retireEpoch(oldEpoch) +} + +func (t *ManagedTransport) close() error { + epoch := t.epoch.Swap(nil) + if epoch != nil { + return epoch.transport.Close() + } + return nil +} + +var _ adapter.HTTPTransport = (*sharedRef)(nil) + +type sharedRef struct { + managed *ManagedTransport + shared *sharedState + idle atomic.Bool +} + +type sharedState struct { + activeRefs atomic.Int32 +} + +func newSharedRef(managed *ManagedTransport, shared *sharedState) *sharedRef { + shared.activeRefs.Add(1) + return &sharedRef{ + managed: managed, + shared: shared, + } +} + +func (r *sharedRef) RoundTrip(request *http.Request) (*http.Response, error) { + if r.idle.CompareAndSwap(true, false) { + r.shared.activeRefs.Add(1) + } + return r.managed.RoundTrip(request) +} + +func (r *sharedRef) CloseIdleConnections() { + if r.idle.CompareAndSwap(false, true) { + if r.shared.activeRefs.Add(-1) == 0 { + r.managed.CloseIdleConnections() + } + } +} + +func (r *sharedRef) Reset() { + r.managed.Reset() +} diff --git a/common/httpclient/manager.go b/common/httpclient/manager.go new file mode 100644 index 0000000000..614e4f83bd --- /dev/null +++ b/common/httpclient/manager.go @@ -0,0 +1,179 @@ +package httpclient + +import ( + "context" + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" +) + +var ( + _ adapter.HTTPClientManager = (*Manager)(nil) + _ adapter.LifecycleService = (*Manager)(nil) +) + +type Manager struct { + ctx context.Context + logger log.ContextLogger + access sync.Mutex + defines map[string]option.HTTPClient + sharedTransports map[string]*sharedManagedTransport + managedTransports []*ManagedTransport + defaultTag string + defaultTransport *sharedManagedTransport + defaultTransportFallback func() (*ManagedTransport, error) +} + +type sharedManagedTransport struct { + managed *ManagedTransport + shared *sharedState +} + +func NewManager(ctx context.Context, logger log.ContextLogger, clients []option.HTTPClient, defaultHTTPClient string) *Manager { + defines := make(map[string]option.HTTPClient, len(clients)) + for _, client := range clients { + defines[client.Tag] = client + } + defaultTag := defaultHTTPClient + if defaultTag == "" && len(clients) > 0 { + defaultTag = clients[0].Tag + } + return &Manager{ + ctx: ctx, + logger: logger, + defines: defines, + sharedTransports: make(map[string]*sharedManagedTransport), + defaultTag: defaultTag, + } +} + +func (m *Manager) Initialize(defaultTransportFallback func() (*ManagedTransport, error)) { + m.defaultTransportFallback = defaultTransportFallback +} + +func (m *Manager) Name() string { + return "http-client" +} + +func (m *Manager) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + if m.defaultTag != "" { + sharedTransport, err := m.resolveShared(m.defaultTag) + if err != nil { + return E.Cause(err, "resolve default http client") + } + m.defaultTransport = sharedTransport + } + return nil +} + +func (m *Manager) DefaultTransport() adapter.HTTPTransport { + m.access.Lock() + defer m.access.Unlock() + if m.defaultTransport == nil && m.defaultTransportFallback != nil { + transport, err := m.defaultTransportFallback() + if err != nil { + m.logger.Error(E.Cause(err, "create default http client")) + return nil + } + m.managedTransports = append(m.managedTransports, transport) + m.defaultTransport = &sharedManagedTransport{ + managed: transport, + shared: &sharedState{}, + } + } + if m.defaultTransport == nil { + return nil + } + return newSharedRef(m.defaultTransport.managed, m.defaultTransport.shared) +} + +func (m *Manager) ResolveTransport(ctx context.Context, logger logger.ContextLogger, options option.HTTPClientOptions) (adapter.HTTPTransport, error) { + if options.Tag != "" { + if options.ResolveOnDetour { + define, loaded := m.defines[options.Tag] + if !loaded { + return nil, E.New("http_client not found: ", options.Tag) + } + resolvedOptions := define.Options() + resolvedOptions.ResolveOnDetour = true + transport, err := NewTransport(ctx, logger, options.Tag, resolvedOptions) + if err != nil { + return nil, err + } + m.trackTransport(transport) + return transport, nil + } + sharedTransport, err := m.resolveShared(options.Tag) + if err != nil { + return nil, err + } + return newSharedRef(sharedTransport.managed, sharedTransport.shared), nil + } + transport, err := NewTransport(ctx, logger, "", options) + if err != nil { + return nil, err + } + m.trackTransport(transport) + return transport, nil +} + +func (m *Manager) trackTransport(transport *ManagedTransport) { + m.access.Lock() + defer m.access.Unlock() + m.managedTransports = append(m.managedTransports, transport) +} + +func (m *Manager) resolveShared(tag string) (*sharedManagedTransport, error) { + m.access.Lock() + defer m.access.Unlock() + if sharedTransport, loaded := m.sharedTransports[tag]; loaded { + return sharedTransport, nil + } + define, loaded := m.defines[tag] + if !loaded { + return nil, E.New("http_client not found: ", tag) + } + transport, err := NewTransport(m.ctx, m.logger, tag, define.Options()) + if err != nil { + return nil, E.Cause(err, "create shared http_client[", tag, "]") + } + sharedTransport := &sharedManagedTransport{ + managed: transport, + shared: &sharedState{}, + } + m.sharedTransports[tag] = sharedTransport + m.managedTransports = append(m.managedTransports, transport) + return sharedTransport, nil +} + +func (m *Manager) ResetNetwork() { + m.access.Lock() + defer m.access.Unlock() + for _, transport := range m.managedTransports { + transport.Reset() + } +} + +func (m *Manager) Close() error { + m.access.Lock() + defer m.access.Unlock() + if m.managedTransports == nil { + return nil + } + var err error + for _, transport := range m.managedTransports { + err = E.Append(err, transport.close(), func(err error) error { + return E.Cause(err, "close http client") + }) + } + m.managedTransports = nil + m.sharedTransports = nil + return err +} diff --git a/common/humanize/bytes.go b/common/humanize/bytes.go deleted file mode 100644 index 6ee4d26898..0000000000 --- a/common/humanize/bytes.go +++ /dev/null @@ -1,158 +0,0 @@ -package humanize - -import ( - "fmt" - "math" - "strconv" - "strings" - "unicode" -) - -// IEC Sizes. -// kibis of bits -const ( - Byte = 1 << (iota * 10) - KiByte - MiByte - GiByte - TiByte - PiByte - EiByte -) - -// SI Sizes. -const ( - IByte = 1 - KByte = IByte * 1000 - MByte = KByte * 1000 - GByte = MByte * 1000 - TByte = GByte * 1000 - PByte = TByte * 1000 - EByte = PByte * 1000 -) - -var defaultSizeTable = map[string]uint64{ - "b": Byte, - "kib": KiByte, - "kb": KByte, - "mib": MiByte, - "mb": MByte, - "gib": GiByte, - "gb": GByte, - "tib": TiByte, - "tb": TByte, - "pib": PiByte, - "pb": PByte, - "eib": EiByte, - "eb": EByte, - // Without suffix - "": Byte, - "ki": KiByte, - "k": KByte, - "mi": MiByte, - "m": MByte, - "gi": GiByte, - "g": GByte, - "ti": TiByte, - "t": TByte, - "pi": PiByte, - "p": PByte, - "ei": EiByte, - "e": EByte, -} - -var memorysSizeTable = map[string]uint64{ - "b": Byte, - "kb": KiByte, - "mb": MiByte, - "gb": GiByte, - "tb": TiByte, - "pb": PiByte, - "eb": EiByte, - "": Byte, - "k": KiByte, - "m": MiByte, - "g": GiByte, - "t": TiByte, - "p": PiByte, - "e": EiByte, -} - -var ( - defaultSizes = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"} - iSizes = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"} -) - -func Bytes(s uint64) string { - return humanateBytes(s, 1000, defaultSizes) -} - -func MemoryBytes(s uint64) string { - return humanateBytes(s, 1024, defaultSizes) -} - -func IBytes(s uint64) string { - return humanateBytes(s, 1024, iSizes) -} - -func logn(n, b float64) float64 { - return math.Log(n) / math.Log(b) -} - -func humanateBytes(s uint64, base float64, sizes []string) string { - if s < 10 { - return fmt.Sprintf("%d B", s) - } - e := math.Floor(logn(float64(s), base)) - suffix := sizes[int(e)] - val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10 - f := "%.0f %s" - if val < 10 { - f = "%.1f %s" - } - - return fmt.Sprintf(f, val, suffix) -} - -func ParseBytes(s string) (uint64, error) { - return parseBytes0(s, defaultSizeTable) -} - -func ParseMemoryBytes(s string) (uint64, error) { - return parseBytes0(s, memorysSizeTable) -} - -func parseBytes0(s string, sizeTable map[string]uint64) (uint64, error) { - lastDigit := 0 - hasComma := false - for _, r := range s { - if !(unicode.IsDigit(r) || r == '.' || r == ',') { - break - } - if r == ',' { - hasComma = true - } - lastDigit++ - } - - num := s[:lastDigit] - if hasComma { - num = strings.Replace(num, ",", "", -1) - } - - f, err := strconv.ParseFloat(num, 64) - if err != nil { - return 0, err - } - - extra := strings.ToLower(strings.TrimSpace(s[lastDigit:])) - if m, ok := sizeTable[extra]; ok { - f *= float64(m) - if f >= math.MaxUint64 { - return 0, fmt.Errorf("too large: %v", s) - } - return uint64(f), nil - } - - return 0, fmt.Errorf("unhandled size name: %v", extra) -} diff --git a/common/interrupt/conn.go b/common/interrupt/conn.go index 6a6d31c68b..5ebbff8a9e 100644 --- a/common/interrupt/conn.go +++ b/common/interrupt/conn.go @@ -3,6 +3,7 @@ package interrupt import ( "net" + N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/x/list" ) @@ -30,7 +31,8 @@ func (c *Conn) Close() error { c.group.access.Lock() defer c.group.access.Unlock() c.group.connections.Remove(c.element) - return c.Conn.Close() + go c.Conn.Close() + return nil } func (c *Conn) ReaderReplaceable() bool { @@ -73,3 +75,32 @@ func (c *PacketConn) WriterReplaceable() bool { func (c *PacketConn) Upstream() any { return c.PacketConn } + +type SingPacketConn struct { + N.PacketConn + group *Group + element *list.Element[*groupConnItem] +} + +/*func (c *SingPacketConn) MarkAsInternal() { + c.element.Value.internal = true +}*/ + +func (c *SingPacketConn) Close() error { + c.group.access.Lock() + defer c.group.access.Unlock() + c.group.connections.Remove(c.element) + return c.PacketConn.Close() +} + +func (c *SingPacketConn) ReaderReplaceable() bool { + return true +} + +func (c *SingPacketConn) WriterReplaceable() bool { + return true +} + +func (c *SingPacketConn) Upstream() any { + return c.PacketConn +} diff --git a/common/interrupt/group.go b/common/interrupt/group.go index ba2e7f739b..bd3fbb0a2a 100644 --- a/common/interrupt/group.go +++ b/common/interrupt/group.go @@ -5,6 +5,7 @@ import ( "net" "sync" + N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/x/list" ) @@ -36,6 +37,13 @@ func (g *Group) NewPacketConn(conn net.PacketConn, isExternal bool) net.PacketCo return &PacketConn{PacketConn: conn, group: g, element: item} } +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}) + return &SingPacketConn{PacketConn: conn, group: g, element: item} +} + func (g *Group) Interrupt(interruptExternalConnections bool) { g.access.Lock() defer g.access.Unlock() diff --git a/common/ja3/LICENSE b/common/ja3/LICENSE new file mode 100644 index 0000000000..b42ec95db2 --- /dev/null +++ b/common/ja3/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2018, Open Systems AG +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/common/ja3/README.md b/common/ja3/README.md new file mode 100644 index 0000000000..5c0bd8ae27 --- /dev/null +++ b/common/ja3/README.md @@ -0,0 +1,3 @@ +# JA3 + +mod from: https://github.com/open-ch/ja3 \ No newline at end of file diff --git a/common/ja3/error.go b/common/ja3/error.go new file mode 100644 index 0000000000..cab8549255 --- /dev/null +++ b/common/ja3/error.go @@ -0,0 +1,31 @@ +// Copyright (c) 2018, Open Systems AG. All rights reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. + +package ja3 + +import "fmt" + +// Error types +const ( + LengthErr string = "length check %v failed" + ContentTypeErr string = "content type not matching" + VersionErr string = "version check %v failed" + HandshakeTypeErr string = "handshake type not matching" + SNITypeErr string = "SNI type not supported" +) + +// ParseError can be encountered while parsing a segment +type ParseError struct { + errType string + check int +} + +func (e *ParseError) Error() string { + if e.errType == LengthErr || e.errType == VersionErr { + return fmt.Sprintf(e.errType, e.check) + } + return fmt.Sprint(e.errType) +} diff --git a/common/ja3/ja3.go b/common/ja3/ja3.go new file mode 100644 index 0000000000..608819fe1e --- /dev/null +++ b/common/ja3/ja3.go @@ -0,0 +1,83 @@ +// Copyright (c) 2018, Open Systems AG. All rights reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. + +package ja3 + +import ( + "crypto/md5" + "encoding/hex" + + "golang.org/x/exp/slices" +) + +type ClientHello struct { + Version uint16 + CipherSuites []uint16 + Extensions []uint16 + EllipticCurves []uint16 + EllipticCurvePF []uint8 + Versions []uint16 + SignatureAlgorithms []uint16 + ServerName string + ja3ByteString []byte + ja3Hash string +} + +func (j *ClientHello) Equals(another *ClientHello, ignoreExtensionsSequence bool) bool { + if j.Version != another.Version { + return false + } + if !slices.Equal(j.CipherSuites, another.CipherSuites) { + return false + } + if !ignoreExtensionsSequence && !slices.Equal(j.Extensions, another.Extensions) { + return false + } + if ignoreExtensionsSequence && !slices.Equal(j.Extensions, another.sortedExtensions()) { + return false + } + if !slices.Equal(j.EllipticCurves, another.EllipticCurves) { + return false + } + if !slices.Equal(j.EllipticCurvePF, another.EllipticCurvePF) { + return false + } + if !slices.Equal(j.SignatureAlgorithms, another.SignatureAlgorithms) { + return false + } + return true +} + +func (j *ClientHello) sortedExtensions() []uint16 { + extensions := make([]uint16, len(j.Extensions)) + copy(extensions, j.Extensions) + slices.Sort(extensions) + return extensions +} + +func Compute(payload []byte) (*ClientHello, error) { + ja3 := ClientHello{} + err := ja3.parseSegment(payload) + return &ja3, err +} + +func (j *ClientHello) String() string { + if j.ja3ByteString == nil { + j.marshalJA3() + } + return string(j.ja3ByteString) +} + +func (j *ClientHello) Hash() string { + if j.ja3ByteString == nil { + j.marshalJA3() + } + if j.ja3Hash == "" { + h := md5.Sum(j.ja3ByteString) + j.ja3Hash = hex.EncodeToString(h[:]) + } + return j.ja3Hash +} diff --git a/common/ja3/parser.go b/common/ja3/parser.go new file mode 100644 index 0000000000..a4bd712391 --- /dev/null +++ b/common/ja3/parser.go @@ -0,0 +1,357 @@ +// Copyright (c) 2018, Open Systems AG. All rights reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. + +package ja3 + +import ( + "encoding/binary" + "strconv" +) + +const ( + // Constants used for parsing + recordLayerHeaderLen int = 5 + handshakeHeaderLen int = 6 + randomDataLen int = 32 + sessionIDHeaderLen int = 1 + cipherSuiteHeaderLen int = 2 + compressMethodHeaderLen int = 1 + extensionsHeaderLen int = 2 + extensionHeaderLen int = 4 + sniExtensionHeaderLen int = 5 + ecExtensionHeaderLen int = 2 + ecpfExtensionHeaderLen int = 1 + versionExtensionHeaderLen int = 1 + signatureAlgorithmsExtensionHeaderLen int = 2 + contentType uint8 = 22 + handshakeType uint8 = 1 + sniExtensionType uint16 = 0 + sniNameDNSHostnameType uint8 = 0 + ecExtensionType uint16 = 10 + ecpfExtensionType uint16 = 11 + versionExtensionType uint16 = 43 + signatureAlgorithmsExtensionType uint16 = 13 + + // Versions + // The bitmask covers the versions SSL3.0 to TLS1.2 + tlsVersionBitmask uint16 = 0xFFFC + tls13 uint16 = 0x0304 + + // GREASE values + // The bitmask covers all GREASE values + GreaseBitmask uint16 = 0x0F0F + + // Constants used for marshalling + dashByte = byte(45) + commaByte = byte(44) +) + +// parseSegment to populate the corresponding ClientHello object or return an error +func (j *ClientHello) parseSegment(segment []byte) error { + // Check if we can decode the next fields + if len(segment) < recordLayerHeaderLen { + return &ParseError{LengthErr, 1} + } + + // Check if we have "Content Type: Handshake (22)" + contType := uint8(segment[0]) + if contType != contentType { + return &ParseError{errType: ContentTypeErr} + } + + // Check if TLS record layer version is supported + tlsRecordVersion := uint16(segment[1])<<8 | uint16(segment[2]) + if tlsRecordVersion&tlsVersionBitmask != 0x0300 && tlsRecordVersion != tls13 { + return &ParseError{VersionErr, 1} + } + + // Check that the Handshake is as long as expected from the length field + segmentLen := uint16(segment[3])<<8 | uint16(segment[4]) + if len(segment[recordLayerHeaderLen:]) < int(segmentLen) { + return &ParseError{LengthErr, 2} + } + // Keep the Handshake messege, ignore any additional following record types + hs := segment[recordLayerHeaderLen : recordLayerHeaderLen+int(segmentLen)] + + err := j.parseHandshake(hs) + + return err +} + +// parseHandshake body +func (j *ClientHello) parseHandshake(hs []byte) error { + // Check if we can decode the next fields + if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen { + return &ParseError{LengthErr, 3} + } + + // Check if we have "Handshake Type: Client Hello (1)" + handshType := uint8(hs[0]) + if handshType != handshakeType { + return &ParseError{errType: HandshakeTypeErr} + } + + // Check if actual length of handshake matches (this is a great exclusion criterion for false positives, + // as these fields have to match the actual length of the rest of the segment) + handshakeLen := uint32(hs[1])<<16 | uint32(hs[2])<<8 | uint32(hs[3]) + if len(hs[4:]) != int(handshakeLen) { + return &ParseError{LengthErr, 4} + } + + // Check if Client Hello version is supported + tlsVersion := uint16(hs[4])<<8 | uint16(hs[5]) + if tlsVersion&tlsVersionBitmask != 0x0300 && tlsVersion != tls13 { + return &ParseError{VersionErr, 2} + } + j.Version = tlsVersion + + // Check if we can decode the next fields + sessionIDLen := uint8(hs[38]) + if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen) { + return &ParseError{LengthErr, 5} + } + + // Cipher Suites + cs := hs[handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen):] + + // Check if we can decode the next fields + if len(cs) < cipherSuiteHeaderLen { + return &ParseError{LengthErr, 6} + } + + csLen := uint16(cs[0])<<8 | uint16(cs[1]) + numCiphers := int(csLen / 2) + cipherSuites := make([]uint16, 0, numCiphers) + + // Check if we can decode the next fields + if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen { + return &ParseError{LengthErr, 7} + } + + for i := range numCiphers { + cipherSuite := uint16(cs[2+i<<1])<<8 | uint16(cs[3+i<<1]) + cipherSuites = append(cipherSuites, cipherSuite) + } + j.CipherSuites = cipherSuites + + // Check if we can decode the next fields + compressMethodLen := uint16(cs[cipherSuiteHeaderLen+int(csLen)]) + if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen) { + return &ParseError{LengthErr, 8} + } + + // Extensions + exs := cs[cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen):] + + err := j.parseExtensions(exs) + + return err +} + +// parseExtensions of the handshake +func (j *ClientHello) parseExtensions(exs []byte) error { + // Check for no extensions, this fields header is nonexistent if no body is used + if len(exs) == 0 { + return nil + } + + // Check if we can decode the next fields + if len(exs) < extensionsHeaderLen { + return &ParseError{LengthErr, 9} + } + + exsLen := uint16(exs[0])<<8 | uint16(exs[1]) + exs = exs[extensionsHeaderLen:] + + // Check if we can decode the next fields + if len(exs) < int(exsLen) { + return &ParseError{LengthErr, 10} + } + + var sni []byte + var extensions, ellipticCurves []uint16 + var ellipticCurvePF []uint8 + var versions []uint16 + var signatureAlgorithms []uint16 + for len(exs) > 0 { + + // Check if we can decode the next fields + if len(exs) < extensionHeaderLen { + return &ParseError{LengthErr, 11} + } + + exType := uint16(exs[0])<<8 | uint16(exs[1]) + exLen := uint16(exs[2])<<8 | uint16(exs[3]) + // Ignore any GREASE extensions + extensions = append(extensions, exType) + // Check if we can decode the next fields + if len(exs) < extensionHeaderLen+int(exLen) { + return &ParseError{LengthErr, 12} + } + + sex := exs[extensionHeaderLen : extensionHeaderLen+int(exLen)] + + switch exType { + case sniExtensionType: // Extensions: server_name + + // Check if we can decode the next fields + if len(sex) < sniExtensionHeaderLen { + return &ParseError{LengthErr, 13} + } + + sniType := uint8(sex[2]) + sniLen := uint16(sex[3])<<8 | uint16(sex[4]) + sex = sex[sniExtensionHeaderLen:] + + // Check if we can decode the next fields + if len(sex) != int(sniLen) { + return &ParseError{LengthErr, 14} + } + + switch sniType { + case sniNameDNSHostnameType: + sni = sex + default: + return &ParseError{errType: SNITypeErr} + } + case ecExtensionType: // Extensions: supported_groups + + // Check if we can decode the next fields + if len(sex) < ecExtensionHeaderLen { + return &ParseError{LengthErr, 15} + } + + ecsLen := uint16(sex[0])<<8 | uint16(sex[1]) + numCurves := int(ecsLen / 2) + ellipticCurves = make([]uint16, 0, numCurves) + sex = sex[ecExtensionHeaderLen:] + + // Check if we can decode the next fields + if len(sex) != int(ecsLen) { + return &ParseError{LengthErr, 16} + } + + for i := range numCurves { + ecType := uint16(sex[i*2])<<8 | uint16(sex[1+i*2]) + ellipticCurves = append(ellipticCurves, ecType) + } + + case ecpfExtensionType: // Extensions: ec_point_formats + + // Check if we can decode the next fields + if len(sex) < ecpfExtensionHeaderLen { + return &ParseError{LengthErr, 17} + } + + ecpfsLen := uint8(sex[0]) + numPF := int(ecpfsLen) + ellipticCurvePF = make([]uint8, numPF) + sex = sex[ecpfExtensionHeaderLen:] + + // Check if we can decode the next fields + if len(sex) != numPF { + return &ParseError{LengthErr, 18} + } + + for i := range numPF { + ellipticCurvePF[i] = uint8(sex[i]) + } + case versionExtensionType: + if len(sex) < versionExtensionHeaderLen { + return &ParseError{LengthErr, 19} + } + versionsLen := int(sex[0]) + for i := 0; i < versionsLen; i += 2 { + versions = append(versions, binary.BigEndian.Uint16(sex[1:][i:])) + } + case signatureAlgorithmsExtensionType: + if len(sex) < signatureAlgorithmsExtensionHeaderLen { + return &ParseError{LengthErr, 20} + } + ssaLen := binary.BigEndian.Uint16(sex) + for i := 0; i < int(ssaLen); i += 2 { + signatureAlgorithms = append(signatureAlgorithms, binary.BigEndian.Uint16(sex[2:][i:])) + } + } + exs = exs[4+exLen:] + } + j.ServerName = string(sni) + j.Extensions = extensions + j.EllipticCurves = ellipticCurves + j.EllipticCurvePF = ellipticCurvePF + j.Versions = versions + j.SignatureAlgorithms = signatureAlgorithms + return nil +} + +// marshalJA3 into a byte string +func (j *ClientHello) marshalJA3() { + // An uint16 can contain numbers with up to 5 digits and an uint8 can contain numbers with up to 3 digits, but we + // also need a byte for each separating character, except at the end. + byteStringLen := 6*(1+len(j.CipherSuites)+len(j.Extensions)+len(j.EllipticCurves)) + 4*len(j.EllipticCurvePF) - 1 + byteString := make([]byte, 0, byteStringLen) + + // Version + byteString = strconv.AppendUint(byteString, uint64(j.Version), 10) + byteString = append(byteString, commaByte) + + // Cipher Suites + if len(j.CipherSuites) != 0 { + for _, val := range j.CipherSuites { + if val&GreaseBitmask != 0x0A0A { + continue + } + byteString = strconv.AppendUint(byteString, uint64(val), 10) + byteString = append(byteString, dashByte) + } + // Replace last dash with a comma + byteString[len(byteString)-1] = commaByte + } else { + byteString = append(byteString, commaByte) + } + + // Extensions + if len(j.Extensions) != 0 { + for _, val := range j.Extensions { + if val&GreaseBitmask != 0x0A0A { + continue + } + byteString = strconv.AppendUint(byteString, uint64(val), 10) + byteString = append(byteString, dashByte) + } + // Replace last dash with a comma + byteString[len(byteString)-1] = commaByte + } else { + byteString = append(byteString, commaByte) + } + + // Elliptic curves + if len(j.EllipticCurves) != 0 { + for _, val := range j.EllipticCurves { + if val&GreaseBitmask != 0x0A0A { + continue + } + byteString = strconv.AppendUint(byteString, uint64(val), 10) + byteString = append(byteString, dashByte) + } + // Replace last dash with a comma + byteString[len(byteString)-1] = commaByte + } else { + byteString = append(byteString, commaByte) + } + + // ECPF + if len(j.EllipticCurvePF) != 0 { + for _, val := range j.EllipticCurvePF { + byteString = strconv.AppendUint(byteString, uint64(val), 10) + byteString = append(byteString, dashByte) + } + // Remove last dash + byteString = byteString[:len(byteString)-1] + } + + j.ja3ByteString = byteString +} diff --git a/common/ktls/ktls.go b/common/ktls/ktls.go new file mode 100644 index 0000000000..33a59b13e3 --- /dev/null +++ b/common/ktls/ktls.go @@ -0,0 +1,133 @@ +//go:build linux && go1.25 && badlinkname + +package ktls + +import ( + "bytes" + "context" + "crypto/tls" + "errors" + "io" + "net" + "os" + "syscall" + + "github.com/sagernet/sing-box/common/badtls" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + N "github.com/sagernet/sing/common/network" + aTLS "github.com/sagernet/sing/common/tls" + + "golang.org/x/sys/unix" +) + +type Conn struct { + aTLS.Conn + ctx context.Context + logger logger.ContextLogger + conn net.Conn + rawConn *badtls.RawConn + syscallConn syscall.Conn + rawSyscallConn syscall.RawConn + readWaitOptions N.ReadWaitOptions + kernelTx bool + kernelRx bool + pendingRxSplice bool +} + +func NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) { + err := Load() + if err != nil { + return nil, err + } + syscallConn, isSyscallConn := N.CastReader[interface { + io.Reader + syscall.Conn + }](conn.NetConn()) + if !isSyscallConn { + return nil, os.ErrInvalid + } + rawSyscallConn, err := syscallConn.SyscallConn() + if err != nil { + return nil, err + } + rawConn, err := badtls.NewRawConn(conn) + if err != nil { + return nil, err + } + if *rawConn.Vers != tls.VersionTLS13 { + return nil, os.ErrInvalid + } + for rawConn.RawInput.Len() > 0 { + err = rawConn.ReadRecord() + if err != nil { + return nil, err + } + for rawConn.Hand.Len() > 0 { + err = rawConn.HandlePostHandshakeMessage() + if err != nil { + return nil, E.Cause(err, "handle post-handshake messages") + } + } + } + kConn := &Conn{ + Conn: conn, + ctx: ctx, + logger: logger, + conn: conn.NetConn(), + rawConn: rawConn, + syscallConn: syscallConn, + rawSyscallConn: rawSyscallConn, + } + err = kConn.setupKernel(txOffload, rxOffload) + if err != nil { + return nil, err + } + return kConn, nil +} + +func (c *Conn) Upstream() any { + return c.Conn +} + +func (c *Conn) SyscallConnForRead() syscall.RawConn { + if !c.kernelRx { + return nil + } + if !*c.rawConn.IsClient { + c.logger.WarnContext(c.ctx, "ktls: RX splice is unavailable on the server size, since it will cause an unknown failure") + return nil + } + c.logger.DebugContext(c.ctx, "ktls: RX splice requested") + return c.rawSyscallConn +} + +func (c *Conn) HandleSyscallReadError(inputErr error) ([]byte, error) { + if errors.Is(inputErr, unix.EINVAL) { + c.pendingRxSplice = true + err := c.readRecord() + if err != nil { + return nil, E.Cause(err, "ktls: handle non-application-data record") + } + var input bytes.Buffer + if c.rawConn.Input.Len() > 0 { + _, err = c.rawConn.Input.WriteTo(&input) + if err != nil { + return nil, err + } + } + return input.Bytes(), nil + } else if errors.Is(inputErr, unix.EBADMSG) { + return nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertBadRecordMAC)) + } else { + return nil, E.Cause(inputErr, "ktls: unexpected errno") + } +} + +func (c *Conn) SyscallConnForWrite() syscall.RawConn { + if !c.kernelTx { + return nil + } + c.logger.DebugContext(c.ctx, "ktls: TX splice requested") + return c.rawSyscallConn +} diff --git a/common/ktls/ktls_alert.go b/common/ktls/ktls_alert.go new file mode 100644 index 0000000000..e755feae72 --- /dev/null +++ b/common/ktls/ktls_alert.go @@ -0,0 +1,80 @@ +// 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. + +//go:build linux && go1.25 && badlinkname + +package ktls + +import ( + "crypto/tls" + "net" +) + +const ( + // alert level + alertLevelWarning = 1 + alertLevelError = 2 +) + +const ( + alertCloseNotify = 0 + alertUnexpectedMessage = 10 + alertBadRecordMAC = 20 + alertDecryptionFailed = 21 + alertRecordOverflow = 22 + alertDecompressionFailure = 30 + alertHandshakeFailure = 40 + alertBadCertificate = 42 + alertUnsupportedCertificate = 43 + alertCertificateRevoked = 44 + alertCertificateExpired = 45 + alertCertificateUnknown = 46 + alertIllegalParameter = 47 + alertUnknownCA = 48 + alertAccessDenied = 49 + alertDecodeError = 50 + alertDecryptError = 51 + alertExportRestriction = 60 + alertProtocolVersion = 70 + alertInsufficientSecurity = 71 + alertInternalError = 80 + alertInappropriateFallback = 86 + alertUserCanceled = 90 + alertNoRenegotiation = 100 + alertMissingExtension = 109 + alertUnsupportedExtension = 110 + alertCertificateUnobtainable = 111 + alertUnrecognizedName = 112 + alertBadCertificateStatusResponse = 113 + alertBadCertificateHashValue = 114 + alertUnknownPSKIdentity = 115 + alertCertificateRequired = 116 + alertNoApplicationProtocol = 120 + alertECHRequired = 121 +) + +func (c *Conn) sendAlertLocked(err uint8) error { + switch err { + case alertNoRenegotiation, alertCloseNotify: + c.rawConn.Tmp[0] = alertLevelWarning + default: + c.rawConn.Tmp[0] = alertLevelError + } + c.rawConn.Tmp[1] = byte(err) + + _, writeErr := c.writeRecordLocked(recordTypeAlert, c.rawConn.Tmp[0:2]) + if err == alertCloseNotify { + // closeNotify is a special case in that it isn't an error. + return writeErr + } + + return c.rawConn.Out.SetErrorLocked(&net.OpError{Op: "local error", Err: tls.AlertError(err)}) +} + +// sendAlert sends a TLS alert message. +func (c *Conn) sendAlert(err uint8) error { + c.rawConn.Out.Lock() + defer c.rawConn.Out.Unlock() + return c.sendAlertLocked(err) +} diff --git a/common/ktls/ktls_cipher_suites_linux.go b/common/ktls/ktls_cipher_suites_linux.go new file mode 100644 index 0000000000..571f025116 --- /dev/null +++ b/common/ktls/ktls_cipher_suites_linux.go @@ -0,0 +1,326 @@ +// Copyright 2010 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. + +//go:build linux && go1.25 && badlinkname + +package ktls + +import ( + "crypto/tls" + "unsafe" + + "github.com/sagernet/sing-box/common/badtls" +) + +type kernelCryptoCipherType uint16 + +const ( + TLS_CIPHER_AES_GCM_128 kernelCryptoCipherType = 51 + TLS_CIPHER_AES_GCM_128_IV_SIZE kernelCryptoCipherType = 8 + TLS_CIPHER_AES_GCM_128_KEY_SIZE kernelCryptoCipherType = 16 + TLS_CIPHER_AES_GCM_128_SALT_SIZE kernelCryptoCipherType = 4 + TLS_CIPHER_AES_GCM_128_TAG_SIZE kernelCryptoCipherType = 16 + TLS_CIPHER_AES_GCM_128_REC_SEQ_SIZE kernelCryptoCipherType = 8 + + TLS_CIPHER_AES_GCM_256 kernelCryptoCipherType = 52 + TLS_CIPHER_AES_GCM_256_IV_SIZE kernelCryptoCipherType = 8 + TLS_CIPHER_AES_GCM_256_KEY_SIZE kernelCryptoCipherType = 32 + TLS_CIPHER_AES_GCM_256_SALT_SIZE kernelCryptoCipherType = 4 + TLS_CIPHER_AES_GCM_256_TAG_SIZE kernelCryptoCipherType = 16 + TLS_CIPHER_AES_GCM_256_REC_SEQ_SIZE kernelCryptoCipherType = 8 + + TLS_CIPHER_AES_CCM_128 kernelCryptoCipherType = 53 + TLS_CIPHER_AES_CCM_128_IV_SIZE kernelCryptoCipherType = 8 + TLS_CIPHER_AES_CCM_128_KEY_SIZE kernelCryptoCipherType = 16 + TLS_CIPHER_AES_CCM_128_SALT_SIZE kernelCryptoCipherType = 4 + TLS_CIPHER_AES_CCM_128_TAG_SIZE kernelCryptoCipherType = 16 + TLS_CIPHER_AES_CCM_128_REC_SEQ_SIZE kernelCryptoCipherType = 8 + + TLS_CIPHER_CHACHA20_POLY1305 kernelCryptoCipherType = 54 + TLS_CIPHER_CHACHA20_POLY1305_IV_SIZE kernelCryptoCipherType = 12 + TLS_CIPHER_CHACHA20_POLY1305_KEY_SIZE kernelCryptoCipherType = 32 + TLS_CIPHER_CHACHA20_POLY1305_SALT_SIZE kernelCryptoCipherType = 0 + TLS_CIPHER_CHACHA20_POLY1305_TAG_SIZE kernelCryptoCipherType = 16 + TLS_CIPHER_CHACHA20_POLY1305_REC_SEQ_SIZE kernelCryptoCipherType = 8 + + // TLS_CIPHER_SM4_GCM kernelCryptoCipherType = 55 + // TLS_CIPHER_SM4_GCM_IV_SIZE kernelCryptoCipherType = 8 + // TLS_CIPHER_SM4_GCM_KEY_SIZE kernelCryptoCipherType = 16 + // TLS_CIPHER_SM4_GCM_SALT_SIZE kernelCryptoCipherType = 4 + // TLS_CIPHER_SM4_GCM_TAG_SIZE kernelCryptoCipherType = 16 + // TLS_CIPHER_SM4_GCM_REC_SEQ_SIZE kernelCryptoCipherType = 8 + + // TLS_CIPHER_SM4_CCM kernelCryptoCipherType = 56 + // TLS_CIPHER_SM4_CCM_IV_SIZE kernelCryptoCipherType = 8 + // TLS_CIPHER_SM4_CCM_KEY_SIZE kernelCryptoCipherType = 16 + // TLS_CIPHER_SM4_CCM_SALT_SIZE kernelCryptoCipherType = 4 + // TLS_CIPHER_SM4_CCM_TAG_SIZE kernelCryptoCipherType = 16 + // TLS_CIPHER_SM4_CCM_REC_SEQ_SIZE kernelCryptoCipherType = 8 + + TLS_CIPHER_ARIA_GCM_128 kernelCryptoCipherType = 57 + TLS_CIPHER_ARIA_GCM_128_IV_SIZE kernelCryptoCipherType = 8 + TLS_CIPHER_ARIA_GCM_128_KEY_SIZE kernelCryptoCipherType = 16 + TLS_CIPHER_ARIA_GCM_128_SALT_SIZE kernelCryptoCipherType = 4 + TLS_CIPHER_ARIA_GCM_128_TAG_SIZE kernelCryptoCipherType = 16 + TLS_CIPHER_ARIA_GCM_128_REC_SEQ_SIZE kernelCryptoCipherType = 8 + + TLS_CIPHER_ARIA_GCM_256 kernelCryptoCipherType = 58 + TLS_CIPHER_ARIA_GCM_256_IV_SIZE kernelCryptoCipherType = 8 + TLS_CIPHER_ARIA_GCM_256_KEY_SIZE kernelCryptoCipherType = 32 + TLS_CIPHER_ARIA_GCM_256_SALT_SIZE kernelCryptoCipherType = 4 + TLS_CIPHER_ARIA_GCM_256_TAG_SIZE kernelCryptoCipherType = 16 + TLS_CIPHER_ARIA_GCM_256_REC_SEQ_SIZE kernelCryptoCipherType = 8 +) + +type kernelCrypto interface { + String() string +} + +type kernelCryptoInfo struct { + version uint16 + cipher_type kernelCryptoCipherType +} + +var _ kernelCrypto = &kernelCryptoAES128GCM{} + +type kernelCryptoAES128GCM struct { + kernelCryptoInfo + iv [TLS_CIPHER_AES_GCM_128_IV_SIZE]byte + key [TLS_CIPHER_AES_GCM_128_KEY_SIZE]byte + salt [TLS_CIPHER_AES_GCM_128_SALT_SIZE]byte + rec_seq [TLS_CIPHER_AES_GCM_128_REC_SEQ_SIZE]byte +} + +func (crypto *kernelCryptoAES128GCM) String() string { + crypto.cipher_type = TLS_CIPHER_AES_GCM_128 + return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:]) +} + +var _ kernelCrypto = &kernelCryptoAES256GCM{} + +type kernelCryptoAES256GCM struct { + kernelCryptoInfo + iv [TLS_CIPHER_AES_GCM_256_IV_SIZE]byte + key [TLS_CIPHER_AES_GCM_256_KEY_SIZE]byte + salt [TLS_CIPHER_AES_GCM_256_SALT_SIZE]byte + rec_seq [TLS_CIPHER_AES_GCM_256_REC_SEQ_SIZE]byte +} + +func (crypto *kernelCryptoAES256GCM) String() string { + crypto.cipher_type = TLS_CIPHER_AES_GCM_256 + return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:]) +} + +var _ kernelCrypto = &kernelCryptoAES128CCM{} + +type kernelCryptoAES128CCM struct { + kernelCryptoInfo + iv [TLS_CIPHER_AES_CCM_128_IV_SIZE]byte + key [TLS_CIPHER_AES_CCM_128_KEY_SIZE]byte + salt [TLS_CIPHER_AES_CCM_128_SALT_SIZE]byte + rec_seq [TLS_CIPHER_AES_CCM_128_REC_SEQ_SIZE]byte +} + +func (crypto *kernelCryptoAES128CCM) String() string { + crypto.cipher_type = TLS_CIPHER_AES_CCM_128 + return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:]) +} + +var _ kernelCrypto = &kernelCryptoChacha20Poly1035{} + +type kernelCryptoChacha20Poly1035 struct { + kernelCryptoInfo + iv [TLS_CIPHER_CHACHA20_POLY1305_IV_SIZE]byte + key [TLS_CIPHER_CHACHA20_POLY1305_KEY_SIZE]byte + salt [TLS_CIPHER_CHACHA20_POLY1305_SALT_SIZE]byte + rec_seq [TLS_CIPHER_CHACHA20_POLY1305_REC_SEQ_SIZE]byte +} + +func (crypto *kernelCryptoChacha20Poly1035) String() string { + crypto.cipher_type = TLS_CIPHER_CHACHA20_POLY1305 + return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:]) +} + +// var _ kernelCrypto = &kernelCryptoSM4GCM{} + +// type kernelCryptoSM4GCM struct { +// kernelCryptoInfo +// iv [TLS_CIPHER_SM4_GCM_IV_SIZE]byte +// key [TLS_CIPHER_SM4_GCM_KEY_SIZE]byte +// salt [TLS_CIPHER_SM4_GCM_SALT_SIZE]byte +// rec_seq [TLS_CIPHER_SM4_GCM_REC_SEQ_SIZE]byte +// } + +// func (crypto *kernelCryptoSM4GCM) String() string { +// crypto.cipher_type = TLS_CIPHER_SM4_GCM +// return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:]) +// } + +// var _ kernelCrypto = &kernelCryptoSM4CCM{} + +// type kernelCryptoSM4CCM struct { +// kernelCryptoInfo +// iv [TLS_CIPHER_SM4_CCM_IV_SIZE]byte +// key [TLS_CIPHER_SM4_CCM_KEY_SIZE]byte +// salt [TLS_CIPHER_SM4_CCM_SALT_SIZE]byte +// rec_seq [TLS_CIPHER_SM4_CCM_REC_SEQ_SIZE]byte +// } + +// func (crypto *kernelCryptoSM4CCM) String() string { +// crypto.cipher_type = TLS_CIPHER_SM4_CCM +// return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:]) +// } + +var _ kernelCrypto = &kernelCryptoARIA128GCM{} + +type kernelCryptoARIA128GCM struct { + kernelCryptoInfo + iv [TLS_CIPHER_ARIA_GCM_128_IV_SIZE]byte + key [TLS_CIPHER_ARIA_GCM_128_KEY_SIZE]byte + salt [TLS_CIPHER_ARIA_GCM_128_SALT_SIZE]byte + rec_seq [TLS_CIPHER_ARIA_GCM_128_REC_SEQ_SIZE]byte +} + +func (crypto *kernelCryptoARIA128GCM) String() string { + crypto.cipher_type = TLS_CIPHER_ARIA_GCM_128 + return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:]) +} + +var _ kernelCrypto = &kernelCryptoARIA256GCM{} + +type kernelCryptoARIA256GCM struct { + kernelCryptoInfo + iv [TLS_CIPHER_ARIA_GCM_256_IV_SIZE]byte + key [TLS_CIPHER_ARIA_GCM_256_KEY_SIZE]byte + salt [TLS_CIPHER_ARIA_GCM_256_SALT_SIZE]byte + rec_seq [TLS_CIPHER_ARIA_GCM_256_REC_SEQ_SIZE]byte +} + +func (crypto *kernelCryptoARIA256GCM) String() string { + crypto.cipher_type = TLS_CIPHER_ARIA_GCM_256 + return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:]) +} + +func kernelCipher(kernel *Support, hc *badtls.RawHalfConn, cipherSuite uint16, isRX bool) kernelCrypto { + if !kernel.TLS { + return nil + } + + switch *hc.Version { + case tls.VersionTLS12: + if isRX && !kernel.TLS_Version13_RX { + return nil + } + + case tls.VersionTLS13: + if !kernel.TLS_Version13 { + return nil + } + + if isRX && !kernel.TLS_Version13_RX { + return nil + } + + default: + return nil + } + + var key, iv []byte + if *hc.Version == tls.VersionTLS13 { + key, iv = trafficKey(cipherSuiteTLS13ByID(cipherSuite), *hc.TrafficSecret) + /*if isRX { + key, iv = trafficKey(cipherSuiteTLS13ByID(cipherSuite), keyLog.RemoteTrafficSecret) + } else { + key, iv = trafficKey(cipherSuiteTLS13ByID(cipherSuite), keyLog.TrafficSecret) + }*/ + } else { + // csPtr := cipherSuiteByID(cipherSuite) + // keysFromMasterSecret(*hc.Version, csPtr, keyLog.Secret, keyLog.Random) + return nil + } + + switch cipherSuite { + case tls.TLS_AES_128_GCM_SHA256, tls.TLS_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + crypto := new(kernelCryptoAES128GCM) + + crypto.version = *hc.Version + copy(crypto.key[:], key) + copy(crypto.iv[:], iv[4:]) + copy(crypto.salt[:], iv[:4]) + crypto.rec_seq = *hc.Seq + + return crypto + case tls.TLS_AES_256_GCM_SHA384, tls.TLS_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + if !kernel.TLS_AES_256_GCM { + return nil + } + + crypto := new(kernelCryptoAES256GCM) + + crypto.version = *hc.Version + copy(crypto.key[:], key) + copy(crypto.iv[:], iv[4:]) + copy(crypto.salt[:], iv[:4]) + crypto.rec_seq = *hc.Seq + + return crypto + //case tls.TLS_AES_128_CCM_SHA256, tls.TLS_RSA_WITH_AES_128_CCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_SHA256: + // if !kernel.TLS_AES_128_CCM { + // return nil + // } + // + // crypto := new(kernelCryptoAES128CCM) + // + // crypto.version = *hc.Version + // copy(crypto.key[:], key) + // copy(crypto.iv[:], iv[4:]) + // copy(crypto.salt[:], iv[:4]) + // crypto.rec_seq = *hc.Seq + // + // return crypto + case tls.TLS_CHACHA20_POLY1305_SHA256, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: + if !kernel.TLS_CHACHA20_POLY1305 { + return nil + } + + crypto := new(kernelCryptoChacha20Poly1035) + + crypto.version = *hc.Version + copy(crypto.key[:], key) + copy(crypto.iv[:], iv) + crypto.rec_seq = *hc.Seq + + return crypto + //case tls.TLS_RSA_WITH_ARIA_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256: + // if !kernel.TLS_ARIA_GCM { + // return nil + // } + // + // crypto := new(kernelCryptoARIA128GCM) + // + // crypto.version = *hc.Version + // copy(crypto.key[:], key) + // copy(crypto.iv[:], iv[4:]) + // copy(crypto.salt[:], iv[:4]) + // crypto.rec_seq = *hc.Seq + // + // return crypto + //case tls.TLS_RSA_WITH_ARIA_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384: + // if !kernel.TLS_ARIA_GCM { + // return nil + // } + // + // crypto := new(kernelCryptoARIA256GCM) + // + // crypto.version = *hc.Version + // copy(crypto.key[:], key) + // copy(crypto.iv[:], iv[4:]) + // copy(crypto.salt[:], iv[:4]) + // crypto.rec_seq = *hc.Seq + // + // return crypto + default: + return nil + } +} diff --git a/common/ktls/ktls_close.go b/common/ktls/ktls_close.go new file mode 100644 index 0000000000..2052524d5e --- /dev/null +++ b/common/ktls/ktls_close.go @@ -0,0 +1,67 @@ +// 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. + +//go:build linux && go1.25 && badlinkname + +package ktls + +import ( + "fmt" + "net" + "time" +) + +func (c *Conn) Close() error { + if !c.kernelTx { + return c.Conn.Close() + } + + // Interlock with Conn.Write above. + var x int32 + for { + x = c.rawConn.ActiveCall.Load() + if x&1 != 0 { + return net.ErrClosed + } + if c.rawConn.ActiveCall.CompareAndSwap(x, x|1) { + break + } + } + if x != 0 { + // io.Writer and io.Closer should not be used concurrently. + // If Close is called while a Write is currently in-flight, + // interpret that as a sign that this Close is really just + // being used to break the Write and/or clean up resources and + // avoid sending the alertCloseNotify, which may block + // waiting on handshakeMutex or the c.out mutex. + return c.conn.Close() + } + + var alertErr error + if c.rawConn.IsHandshakeComplete.Load() { + if err := c.closeNotify(); err != nil { + alertErr = fmt.Errorf("tls: failed to send closeNotify alert (but connection was closed anyway): %w", err) + } + } + + if err := c.conn.Close(); err != nil { + return err + } + return alertErr +} + +func (c *Conn) closeNotify() error { + c.rawConn.Out.Lock() + defer c.rawConn.Out.Unlock() + + if !*c.rawConn.CloseNotifySent { + // Set a Write Deadline to prevent possibly blocking forever. + c.SetWriteDeadline(time.Now().Add(time.Second * 5)) + *c.rawConn.CloseNotifyErr = c.sendAlertLocked(alertCloseNotify) + *c.rawConn.CloseNotifySent = true + // Any subsequent writes will fail. + c.SetWriteDeadline(time.Now()) + } + return *c.rawConn.CloseNotifyErr +} diff --git a/common/ktls/ktls_const.go b/common/ktls/ktls_const.go new file mode 100644 index 0000000000..40cff7605f --- /dev/null +++ b/common/ktls/ktls_const.go @@ -0,0 +1,24 @@ +// 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. + +//go:build linux && go1.25 && badlinkname + +package ktls + +const ( + maxPlaintext = 16384 // maximum plaintext payload length + maxCiphertext = 16384 + 2048 // maximum ciphertext payload length + maxCiphertextTLS13 = 16384 + 256 // maximum ciphertext length in TLS 1.3 + recordHeaderLen = 5 // record header length + maxHandshake = 65536 // maximum handshake we support (protocol max is 16 MB) + maxHandshakeCertificateMsg = 262144 // maximum certificate message size (256 KiB) + maxUselessRecords = 16 // maximum number of consecutive non-advancing records +) + +const ( + recordTypeChangeCipherSpec = 20 + recordTypeAlert = 21 + recordTypeHandshake = 22 + recordTypeApplicationData = 23 +) diff --git a/common/ktls/ktls_handshake_messages.go b/common/ktls/ktls_handshake_messages.go new file mode 100644 index 0000000000..adf023bbff --- /dev/null +++ b/common/ktls/ktls_handshake_messages.go @@ -0,0 +1,186 @@ +// 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. + +//go:build linux && go1.25 && badlinkname + +package ktls + +import "golang.org/x/crypto/cryptobyte" + +// readUint8LengthPrefixed acts like s.ReadUint8LengthPrefixed, but targets a +// []byte instead of a cryptobyte.String. +func readUint8LengthPrefixed(s *cryptobyte.String, out *[]byte) bool { + return s.ReadUint8LengthPrefixed((*cryptobyte.String)(out)) +} + +// readUint16LengthPrefixed acts like s.ReadUint16LengthPrefixed, but targets a +// []byte instead of a cryptobyte.String. +func readUint16LengthPrefixed(s *cryptobyte.String, out *[]byte) bool { + return s.ReadUint16LengthPrefixed((*cryptobyte.String)(out)) +} + +type keyUpdateMsg struct { + updateRequested bool +} + +func (m *keyUpdateMsg) marshal() ([]byte, error) { + var b cryptobyte.Builder + b.AddUint8(typeKeyUpdate) + b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) { + if m.updateRequested { + b.AddUint8(1) + } else { + b.AddUint8(0) + } + }) + + return b.Bytes() +} + +func (m *keyUpdateMsg) unmarshal(data []byte) bool { + s := cryptobyte.String(data) + + var updateRequested uint8 + if !s.Skip(4) || // message type and uint24 length field + !s.ReadUint8(&updateRequested) || !s.Empty() { + return false + } + switch updateRequested { + case 0: + m.updateRequested = false + case 1: + m.updateRequested = true + default: + return false + } + return true +} + +// TLS handshake message types. +const ( + typeHelloRequest uint8 = 0 + typeClientHello uint8 = 1 + typeServerHello uint8 = 2 + typeNewSessionTicket uint8 = 4 + typeEndOfEarlyData uint8 = 5 + typeEncryptedExtensions uint8 = 8 + typeCertificate uint8 = 11 + typeServerKeyExchange uint8 = 12 + typeCertificateRequest uint8 = 13 + typeServerHelloDone uint8 = 14 + typeCertificateVerify uint8 = 15 + typeClientKeyExchange uint8 = 16 + typeFinished uint8 = 20 + typeCertificateStatus uint8 = 22 + typeKeyUpdate uint8 = 24 + typeCompressedCertificate uint8 = 25 + typeMessageHash uint8 = 254 // synthetic message +) + +// TLS extension numbers +const ( + extensionServerName uint16 = 0 + extensionStatusRequest uint16 = 5 + extensionSupportedCurves uint16 = 10 // supported_groups in TLS 1.3, see RFC 8446, Section 4.2.7 + extensionSupportedPoints uint16 = 11 + extensionSignatureAlgorithms uint16 = 13 + extensionALPN uint16 = 16 + extensionSCT uint16 = 18 + extensionPadding uint16 = 21 + extensionExtendedMasterSecret uint16 = 23 + extensionCompressCertificate uint16 = 27 // compress_certificate in TLS 1.3 + extensionSessionTicket uint16 = 35 + extensionPreSharedKey uint16 = 41 + extensionEarlyData uint16 = 42 + extensionSupportedVersions uint16 = 43 + extensionCookie uint16 = 44 + extensionPSKModes uint16 = 45 + extensionCertificateAuthorities uint16 = 47 + extensionSignatureAlgorithmsCert uint16 = 50 + extensionKeyShare uint16 = 51 + extensionQUICTransportParameters uint16 = 57 + extensionALPS uint16 = 17513 + extensionRenegotiationInfo uint16 = 0xff01 + extensionECHOuterExtensions uint16 = 0xfd00 + extensionEncryptedClientHello uint16 = 0xfe0d +) + +type handshakeMessage interface { + marshal() ([]byte, error) + unmarshal([]byte) bool +} +type newSessionTicketMsgTLS13 struct { + lifetime uint32 + ageAdd uint32 + nonce []byte + label []byte + maxEarlyData uint32 +} + +func (m *newSessionTicketMsgTLS13) marshal() ([]byte, error) { + var b cryptobyte.Builder + b.AddUint8(typeNewSessionTicket) + b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddUint32(m.lifetime) + b.AddUint32(m.ageAdd) + b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(m.nonce) + }) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(m.label) + }) + + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + if m.maxEarlyData > 0 { + b.AddUint16(extensionEarlyData) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddUint32(m.maxEarlyData) + }) + } + }) + }) + + return b.Bytes() +} + +func (m *newSessionTicketMsgTLS13) unmarshal(data []byte) bool { + *m = newSessionTicketMsgTLS13{} + s := cryptobyte.String(data) + + var extensions cryptobyte.String + if !s.Skip(4) || // message type and uint24 length field + !s.ReadUint32(&m.lifetime) || + !s.ReadUint32(&m.ageAdd) || + !readUint8LengthPrefixed(&s, &m.nonce) || + !readUint16LengthPrefixed(&s, &m.label) || + !s.ReadUint16LengthPrefixed(&extensions) || + !s.Empty() { + return false + } + + for !extensions.Empty() { + var extension uint16 + var extData cryptobyte.String + if !extensions.ReadUint16(&extension) || + !extensions.ReadUint16LengthPrefixed(&extData) { + return false + } + + switch extension { + case extensionEarlyData: + if !extData.ReadUint32(&m.maxEarlyData) { + return false + } + default: + // Ignore unknown extensions. + continue + } + + if !extData.Empty() { + return false + } + } + + return true +} diff --git a/common/ktls/ktls_key_update.go b/common/ktls/ktls_key_update.go new file mode 100644 index 0000000000..35268e8fa9 --- /dev/null +++ b/common/ktls/ktls_key_update.go @@ -0,0 +1,173 @@ +// 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. + +//go:build linux && go1.25 && badlinkname + +package ktls + +import ( + "crypto/tls" + "errors" + "fmt" + "io" + "os" +) + +// handlePostHandshakeMessage processes a handshake message arrived after the +// handshake is complete. Up to TLS 1.2, it indicates the start of a renegotiation. +func (c *Conn) handlePostHandshakeMessage() error { + if *c.rawConn.Vers != tls.VersionTLS13 { + return errors.New("ktls: kernel does not support TLS 1.2 renegotiation") + } + + msg, err := c.readHandshake(nil) + if err != nil { + return err + } + //c.retryCount++ + //if c.retryCount > maxUselessRecords { + // c.sendAlert(alertUnexpectedMessage) + // return c.in.setErrorLocked(errors.New("tls: too many non-advancing records")) + //} + + switch msg := msg.(type) { + case *newSessionTicketMsgTLS13: + // return errors.New("ktls: received new session ticket") + return nil + case *keyUpdateMsg: + return c.handleKeyUpdate(msg) + } + // The QUIC layer is supposed to treat an unexpected post-handshake CertificateRequest + // as a QUIC-level PROTOCOL_VIOLATION error (RFC 9001, Section 4.4). Returning an + // unexpected_message alert here doesn't provide it with enough information to distinguish + // this condition from other unexpected messages. This is probably fine. + c.sendAlert(alertUnexpectedMessage) + return fmt.Errorf("tls: received unexpected handshake message of type %T", msg) +} + +func (c *Conn) handleKeyUpdate(keyUpdate *keyUpdateMsg) error { + //if c.quic != nil { + // c.sendAlert(alertUnexpectedMessage) + // return c.in.setErrorLocked(errors.New("tls: received unexpected key update message")) + //} + + cipherSuite := cipherSuiteTLS13ByID(*c.rawConn.CipherSuite) + if cipherSuite == nil { + return c.rawConn.In.SetErrorLocked(c.sendAlert(alertInternalError)) + } + + newSecret := nextTrafficSecret(cipherSuite, *c.rawConn.In.TrafficSecret) + c.rawConn.In.SetTrafficSecret(cipherSuite, 0 /*tls.QUICEncryptionLevelInitial*/, newSecret) + + err := c.resetupRX() + if err != nil { + c.sendAlert(alertInternalError) + return c.rawConn.In.SetErrorLocked(fmt.Errorf("ktls: resetupRX failed: %w", err)) + } + + if keyUpdate.updateRequested { + c.rawConn.Out.Lock() + defer c.rawConn.Out.Unlock() + + resetup, err := c.resetupTX() + if err != nil { + c.sendAlertLocked(alertInternalError) + return c.rawConn.Out.SetErrorLocked(fmt.Errorf("ktls: resetupTX failed: %w", err)) + } + + msg := &keyUpdateMsg{} + msgBytes, err := msg.marshal() + if err != nil { + return err + } + _, err = c.writeRecordLocked(recordTypeHandshake, msgBytes) + if err != nil { + // Surface the error at the next write. + c.rawConn.Out.SetErrorLocked(err) + return nil + } + + newSecret := nextTrafficSecret(cipherSuite, *c.rawConn.Out.TrafficSecret) + c.rawConn.Out.SetTrafficSecret(cipherSuite, 0 /*QUICEncryptionLevelInitial*/, newSecret) + + err = resetup() + if err != nil { + return c.rawConn.Out.SetErrorLocked(fmt.Errorf("ktls: resetupTX failed: %w", err)) + } + } + + return nil +} + +func (c *Conn) readHandshakeBytes(n int) error { + //if c.quic != nil { + // return c.quicReadHandshakeBytes(n) + //} + for c.rawConn.Hand.Len() < n { + if err := c.readRecord(); err != nil { + return err + } + } + return nil +} + +func (c *Conn) readHandshake(transcript io.Writer) (any, error) { + if err := c.readHandshakeBytes(4); err != nil { + return nil, err + } + data := c.rawConn.Hand.Bytes() + + maxHandshakeSize := maxHandshake + // hasVers indicates we're past the first message, forcing someone trying to + // make us just allocate a large buffer to at least do the initial part of + // the handshake first. + //if c.haveVers && data[0] == typeCertificate { + // Since certificate messages are likely to be the only messages that + // can be larger than maxHandshake, we use a special limit for just + // those messages. + //maxHandshakeSize = maxHandshakeCertificateMsg + //} + + n := int(data[1])<<16 | int(data[2])<<8 | int(data[3]) + if n > maxHandshakeSize { + c.sendAlertLocked(alertInternalError) + return nil, c.rawConn.In.SetErrorLocked(fmt.Errorf("tls: handshake message of length %d bytes exceeds maximum of %d bytes", n, maxHandshakeSize)) + } + if err := c.readHandshakeBytes(4 + n); err != nil { + return nil, err + } + data = c.rawConn.Hand.Next(4 + n) + return c.unmarshalHandshakeMessage(data, transcript) +} + +func (c *Conn) unmarshalHandshakeMessage(data []byte, transcript io.Writer) (any, error) { + var m handshakeMessage + switch data[0] { + case typeNewSessionTicket: + if *c.rawConn.Vers == tls.VersionTLS13 { + m = new(newSessionTicketMsgTLS13) + } else { + return nil, os.ErrInvalid + } + case typeKeyUpdate: + m = new(keyUpdateMsg) + default: + return nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage)) + } + + // The handshake message unmarshalers + // expect to be able to keep references to data, + // so pass in a fresh copy that won't be overwritten. + data = append([]byte(nil), data...) + + if !m.unmarshal(data) { + return nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertDecodeError)) + } + + if transcript != nil { + transcript.Write(data) + } + + return m, nil +} diff --git a/common/ktls/ktls_linux.go b/common/ktls/ktls_linux.go new file mode 100644 index 0000000000..1e3277511e --- /dev/null +++ b/common/ktls/ktls_linux.go @@ -0,0 +1,329 @@ +//go:build linux && go1.25 && badlinkname + +package ktls + +import ( + "crypto/tls" + "errors" + "io" + "os" + "strings" + "sync" + "syscall" + "unsafe" + + "github.com/sagernet/sing-box/common/badversion" + "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/shell" + + "golang.org/x/sys/unix" +) + +// mod from https://gitlab.com/go-extension/tls + +const ( + TLS_TX = 1 + TLS_RX = 2 + TLS_TX_ZEROCOPY_RO = 3 // TX zerocopy (only sendfile now) + TLS_RX_EXPECT_NO_PAD = 4 // Attempt opportunistic zero-copy, TLS 1.3 only + + TLS_SET_RECORD_TYPE = 1 + TLS_GET_RECORD_TYPE = 2 +) + +type Support struct { + TLS, TLS_RX bool + TLS_Version13, TLS_Version13_RX bool + + TLS_TX_ZEROCOPY bool + TLS_RX_NOPADDING bool + + TLS_AES_256_GCM bool + TLS_AES_128_CCM bool + TLS_CHACHA20_POLY1305 bool + TLS_SM4 bool + TLS_ARIA_GCM bool + + TLS_Version13_KeyUpdate bool +} + +var KernelSupport = sync.OnceValues(func() (*Support, error) { + var uname unix.Utsname + err := unix.Uname(&uname) + if err != nil { + return nil, err + } + + kernelVersion := badversion.Parse(strings.Trim(string(uname.Release[:]), "\x00")) + if err != nil { + return nil, err + } + var support Support + switch { + case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 6, Minor: 14}): + support.TLS_Version13_KeyUpdate = true + fallthrough + case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 6, Minor: 1}): + support.TLS_ARIA_GCM = true + fallthrough + case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 6}): + support.TLS_Version13_RX = true + support.TLS_RX_NOPADDING = true + fallthrough + case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 19}): + support.TLS_TX_ZEROCOPY = true + fallthrough + case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 16}): + support.TLS_SM4 = true + fallthrough + case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 11}): + support.TLS_CHACHA20_POLY1305 = true + fallthrough + case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 2}): + support.TLS_AES_128_CCM = true + fallthrough + case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 1}): + support.TLS_AES_256_GCM = true + support.TLS_Version13 = true + fallthrough + case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 4, Minor: 17}): + support.TLS_RX = true + fallthrough + case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 4, Minor: 13}): + support.TLS = true + } + + if support.TLS && support.TLS_Version13 { + _, err := os.Stat("/sys/module/tls") + if err != nil { + if os.Getuid() == 0 { + output, err := shell.Exec("modprobe", "tls").Read() + if err != nil { + return nil, E.Extend(E.Cause(err, "modprobe tls"), output) + } + } else { + return nil, E.New("ktls: kernel TLS module not loaded") + } + } + } + + return &support, nil +}) + +func Load() error { + support, err := KernelSupport() + if err != nil { + return E.Cause(err, "ktls: check availability") + } + if !support.TLS || !support.TLS_Version13 { + return E.New("ktls: kernel does not support TLS 1.3") + } + return nil +} + +func (c *Conn) setupKernel(txOffload, rxOffload bool) error { + if !txOffload && !rxOffload { + return os.ErrInvalid + } + support, err := KernelSupport() + if err != nil { + return E.Cause(err, "check availability") + } + if !support.TLS || !support.TLS_Version13 { + return E.New("kernel does not support TLS 1.3") + } + c.rawConn.Out.Lock() + defer c.rawConn.Out.Unlock() + err = control.Raw(c.rawSyscallConn, func(fd uintptr) error { + return syscall.SetsockoptString(int(fd), unix.SOL_TCP, unix.TCP_ULP, "tls") + }) + if err != nil { + return os.NewSyscallError("setsockopt", err) + } + + if txOffload { + txCrypto := kernelCipher(support, c.rawConn.Out, *c.rawConn.CipherSuite, false) + if txCrypto == nil { + return E.New("unsupported cipher suite") + } + err = control.Raw(c.rawSyscallConn, func(fd uintptr) error { + return syscall.SetsockoptString(int(fd), unix.SOL_TLS, TLS_TX, txCrypto.String()) + }) + if err != nil { + return err + } + if support.TLS_TX_ZEROCOPY { + err = control.Raw(c.rawSyscallConn, func(fd uintptr) error { + return syscall.SetsockoptInt(int(fd), unix.SOL_TLS, TLS_TX_ZEROCOPY_RO, 1) + }) + if err != nil { + return err + } + } + c.kernelTx = true + c.logger.DebugContext(c.ctx, "ktls: kernel TLS TX enabled") + } + + if rxOffload { + rxCrypto := kernelCipher(support, c.rawConn.In, *c.rawConn.CipherSuite, true) + if rxCrypto == nil { + return E.New("unsupported cipher suite") + } + err = control.Raw(c.rawSyscallConn, func(fd uintptr) error { + return syscall.SetsockoptString(int(fd), unix.SOL_TLS, TLS_RX, rxCrypto.String()) + }) + if err != nil { + return err + } + if *c.rawConn.Vers >= tls.VersionTLS13 && support.TLS_RX_NOPADDING { + err = control.Raw(c.rawSyscallConn, func(fd uintptr) error { + return syscall.SetsockoptInt(int(fd), unix.SOL_TLS, TLS_RX_EXPECT_NO_PAD, 1) + }) + if err != nil { + return err + } + } + c.kernelRx = true + c.logger.DebugContext(c.ctx, "ktls: kernel TLS RX enabled") + } + return nil +} + +func (c *Conn) resetupTX() (func() error, error) { + if !c.kernelTx { + return nil, nil + } + support, err := KernelSupport() + if err != nil { + return nil, err + } + if !support.TLS_Version13_KeyUpdate { + return nil, errors.New("ktls: kernel does not support rekey") + } + txCrypto := kernelCipher(support, c.rawConn.Out, *c.rawConn.CipherSuite, false) + if txCrypto == nil { + return nil, errors.New("ktls: set kernelCipher on unsupported tls session") + } + return func() error { + return control.Raw(c.rawSyscallConn, func(fd uintptr) error { + return syscall.SetsockoptString(int(fd), unix.SOL_TLS, TLS_TX, txCrypto.String()) + }) + }, nil +} + +func (c *Conn) resetupRX() error { + if !c.kernelRx { + return nil + } + support, err := KernelSupport() + if err != nil { + return err + } + if !support.TLS_Version13_KeyUpdate { + return errors.New("ktls: kernel does not support rekey") + } + rxCrypto := kernelCipher(support, c.rawConn.In, *c.rawConn.CipherSuite, true) + if rxCrypto == nil { + return errors.New("ktls: set kernelCipher on unsupported tls session") + } + return control.Raw(c.rawSyscallConn, func(fd uintptr) error { + return syscall.SetsockoptString(int(fd), unix.SOL_TLS, TLS_RX, rxCrypto.String()) + }) +} + +func (c *Conn) readKernelRecord() (uint8, []byte, error) { + if c.rawConn.RawInput.Len() < maxPlaintext { + c.rawConn.RawInput.Grow(maxPlaintext - c.rawConn.RawInput.Len()) + } + + data := c.rawConn.RawInput.Bytes()[:maxPlaintext] + + // cmsg for record type + buffer := make([]byte, unix.CmsgSpace(1)) + cmsg := (*unix.Cmsghdr)(unsafe.Pointer(&buffer[0])) + cmsg.SetLen(unix.CmsgLen(1)) + + var iov unix.Iovec + iov.Base = &data[0] + iov.SetLen(len(data)) + + var msg unix.Msghdr + msg.Control = &buffer[0] + msg.Controllen = cmsg.Len + msg.Iov = &iov + msg.Iovlen = 1 + + var n int + var err error + er := c.rawSyscallConn.Read(func(fd uintptr) bool { + n, err = recvmsg(int(fd), &msg, 0) + return err != unix.EAGAIN || c.pendingRxSplice + }) + if er != nil { + return 0, nil, er + } + switch err { + case nil: + case syscall.EINVAL, syscall.EAGAIN: + return 0, nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertProtocolVersion)) + case syscall.EMSGSIZE: + return 0, nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertRecordOverflow)) + case syscall.EBADMSG: + return 0, nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertDecryptError)) + default: + return 0, nil, err + } + + if n <= 0 { + return 0, nil, c.rawConn.In.SetErrorLocked(io.EOF) + } + + if cmsg.Level == unix.SOL_TLS && cmsg.Type == TLS_GET_RECORD_TYPE { + typ := buffer[unix.CmsgLen(0)] + return typ, data[:n], nil + } + + return recordTypeApplicationData, data[:n], nil +} + +func (c *Conn) writeKernelRecord(typ uint16, data []byte) (int, error) { + if typ == recordTypeApplicationData { + return c.conn.Write(data) + } + + // cmsg for record type + buffer := make([]byte, unix.CmsgSpace(1)) + cmsg := (*unix.Cmsghdr)(unsafe.Pointer(&buffer[0])) + cmsg.SetLen(unix.CmsgLen(1)) + buffer[unix.CmsgLen(0)] = byte(typ) + cmsg.Level = unix.SOL_TLS + cmsg.Type = TLS_SET_RECORD_TYPE + + var iov unix.Iovec + iov.Base = &data[0] + iov.SetLen(len(data)) + + var msg unix.Msghdr + msg.Control = &buffer[0] + msg.Controllen = cmsg.Len + msg.Iov = &iov + msg.Iovlen = 1 + + var n int + var err error + ew := c.rawSyscallConn.Write(func(fd uintptr) bool { + n, err = sendmsg(int(fd), &msg, 0) + return err != unix.EAGAIN + }) + if ew != nil { + return 0, ew + } + return n, err +} + +//go:linkname recvmsg golang.org/x/sys/unix.recvmsg +func recvmsg(fd int, msg *unix.Msghdr, flags int) (n int, err error) + +//go:linkname sendmsg golang.org/x/sys/unix.sendmsg +func sendmsg(fd int, msg *unix.Msghdr, flags int) (n int, err error) diff --git a/common/ktls/ktls_prf.go b/common/ktls/ktls_prf.go new file mode 100644 index 0000000000..ecf0b735a9 --- /dev/null +++ b/common/ktls/ktls_prf.go @@ -0,0 +1,24 @@ +// 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. + +//go:build linux && go1.25 && badlinkname + +package ktls + +import "unsafe" + +//go:linkname cipherSuiteByID github.com/metacubex/utls.cipherSuiteByID +func cipherSuiteByID(id uint16) unsafe.Pointer + +//go:linkname keysFromMasterSecret github.com/metacubex/utls.keysFromMasterSecret +func keysFromMasterSecret(version uint16, suite unsafe.Pointer, masterSecret, clientRandom, serverRandom []byte, macLen, keyLen, ivLen int) (clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV []byte) + +//go:linkname cipherSuiteTLS13ByID github.com/metacubex/utls.cipherSuiteTLS13ByID +func cipherSuiteTLS13ByID(id uint16) unsafe.Pointer + +//go:linkname nextTrafficSecret github.com/metacubex/utls.(*cipherSuiteTLS13).nextTrafficSecret +func nextTrafficSecret(cs unsafe.Pointer, trafficSecret []byte) []byte + +//go:linkname trafficKey github.com/metacubex/utls.(*cipherSuiteTLS13).trafficKey +func trafficKey(cs unsafe.Pointer, trafficSecret []byte) (key, iv []byte) diff --git a/common/ktls/ktls_read.go b/common/ktls/ktls_read.go new file mode 100644 index 0000000000..5609bfb5eb --- /dev/null +++ b/common/ktls/ktls_read.go @@ -0,0 +1,293 @@ +// 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. + +//go:build linux && go1.25 && badlinkname + +package ktls + +import ( + "bytes" + "crypto/tls" + "fmt" + "io" + "net" + "unsafe" +) + +func (c *Conn) Read(b []byte) (int, error) { + if !c.kernelRx { + return c.Conn.Read(b) + } + + if len(b) == 0 { + // Put this after Handshake, in case people were calling + // Read(nil) for the side effect of the Handshake. + return 0, nil + } + + c.rawConn.In.Lock() + defer c.rawConn.In.Unlock() + + for c.rawConn.Input.Len() == 0 { + if err := c.readRecord(); err != nil { + return 0, err + } + for c.rawConn.Hand.Len() > 0 { + if err := c.handlePostHandshakeMessage(); err != nil { + return 0, err + } + } + } + + n, _ := c.rawConn.Input.Read(b) + + // If a close-notify alert is waiting, read it so that we can return (n, + // EOF) instead of (n, nil), to signal to the HTTP response reading + // goroutine that the connection is now closed. This eliminates a race + // where the HTTP response reading goroutine would otherwise not observe + // the EOF until its next read, by which time a client goroutine might + // have already tried to reuse the HTTP connection for a new request. + // See https://golang.org/cl/76400046 and https://golang.org/issue/3514 + if n != 0 && c.rawConn.Input.Len() == 0 && c.rawConn.RawInput.Len() > 0 && + c.rawConn.RawInput.Bytes()[0] == recordTypeAlert { + if err := c.readRecord(); err != nil { + return n, err // will be io.EOF on closeNotify + } + } + + return n, nil +} + +func (c *Conn) readRecord() error { + if *c.rawConn.In.Err != nil { + return *c.rawConn.In.Err + } + + typ, data, err := c.readRawRecord() + if err != nil { + return err + } + + if len(data) > maxPlaintext { + return c.rawConn.In.SetErrorLocked(c.sendAlert(alertRecordOverflow)) + } + + // Application Data messages are always protected. + if c.rawConn.In.Cipher == nil && typ == recordTypeApplicationData { + return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage)) + } + + //if typ != recordTypeAlert && typ != recordTypeChangeCipherSpec && len(data) > 0 { + // This is a state-advancing message: reset the retry count. + // c.retryCount = 0 + //} + + // Handshake messages MUST NOT be interleaved with other record types in TLS 1.3. + if *c.rawConn.Vers == tls.VersionTLS13 && typ != recordTypeHandshake && c.rawConn.Hand.Len() > 0 { + return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage)) + } + + switch typ { + default: + return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage)) + case recordTypeAlert: + //if c.quic != nil { + // return c.rawConn.In.setErrorLocked(c.sendAlert(alertUnexpectedMessage)) + //} + if len(data) != 2 { + return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage)) + } + if data[1] == alertCloseNotify { + return c.rawConn.In.SetErrorLocked(io.EOF) + } + if *c.rawConn.Vers == tls.VersionTLS13 { + // TLS 1.3 removed warning-level alerts except for alertUserCanceled + // (RFC 8446, § 6.1). Since at least one major implementation + // (https://bugs.openjdk.org/browse/JDK-8323517) misuses this alert, + // many TLS stacks now ignore it outright when seen in a TLS 1.3 + // handshake (e.g. BoringSSL, NSS, Rustls). + if data[1] == alertUserCanceled { + // Like TLS 1.2 alertLevelWarning alerts, we drop the record and retry. + return c.retryReadRecord( /*expectChangeCipherSpec*/ ) + } + return c.rawConn.In.SetErrorLocked(&net.OpError{Op: "remote error", Err: tls.AlertError(data[1])}) + } + switch data[0] { + case alertLevelWarning: + // Drop the record on the floor and retry. + return c.retryReadRecord( /*expectChangeCipherSpec*/ ) + case alertLevelError: + return c.rawConn.In.SetErrorLocked(&net.OpError{Op: "remote error", Err: tls.AlertError(data[1])}) + default: + return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage)) + } + + case recordTypeChangeCipherSpec: + if len(data) != 1 || data[0] != 1 { + return c.rawConn.In.SetErrorLocked(c.sendAlert(alertDecodeError)) + } + // Handshake messages are not allowed to fragment across the CCS. + if c.rawConn.Hand.Len() > 0 { + return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage)) + } + // In TLS 1.3, change_cipher_spec records are ignored until the + // Finished. See RFC 8446, Appendix D.4. Note that according to Section + // 5, a server can send a ChangeCipherSpec before its ServerHello, when + // c.vers is still unset. That's not useful though and suspicious if the + // server then selects a lower protocol version, so don't allow that. + if *c.rawConn.Vers == tls.VersionTLS13 { + return c.retryReadRecord( /*expectChangeCipherSpec*/ ) + } + // if !expectChangeCipherSpec { + return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage)) + //} + //if err := c.rawConn.In.changeCipherSpec(); err != nil { + // return c.rawConn.In.setErrorLocked(c.sendAlert(err.(alert))) + //} + + case recordTypeApplicationData: + // Some OpenSSL servers send empty records in order to randomize the + // CBC RawIV. Ignore a limited number of empty records. + if len(data) == 0 { + return c.retryReadRecord( /*expectChangeCipherSpec*/ ) + } + // Note that data is owned by c.rawInput, following the Next call above, + // to avoid copying the plaintext. This is safe because c.rawInput is + // not read from or written to until c.input is drained. + c.rawConn.Input.Reset(data) + case recordTypeHandshake: + if len(data) == 0 { + return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage)) + } + c.rawConn.Hand.Write(data) + } + + return nil +} + +//nolint:staticcheck +func (c *Conn) readRawRecord() (typ uint8, data []byte, err error) { + // Read from kernel. + if c.kernelRx { + return c.readKernelRecord() + } + + // Read header, payload. + if err = c.readFromUntil(c.conn, recordHeaderLen); err != nil { + // RFC 8446, Section 6.1 suggests that EOF without an alertCloseNotify + // is an error, but popular web sites seem to do this, so we accept it + // if and only if at the record boundary. + if err == io.ErrUnexpectedEOF && c.rawConn.RawInput.Len() == 0 { + err = io.EOF + } + if e, ok := err.(net.Error); !ok || !e.Temporary() { + c.rawConn.In.SetErrorLocked(err) + } + return + } + hdr := c.rawConn.RawInput.Bytes()[:recordHeaderLen] + typ = hdr[0] + + vers := uint16(hdr[1])<<8 | uint16(hdr[2]) + expectedVers := *c.rawConn.Vers + if expectedVers == tls.VersionTLS13 { + // All TLS 1.3 records are expected to have 0x0303 (1.2) after + // the initial hello (RFC 8446 Section 5.1). + expectedVers = tls.VersionTLS12 + } + n := int(hdr[3])<<8 | int(hdr[4]) + if /*c.haveVers && */ vers != expectedVers { + c.sendAlert(alertProtocolVersion) + msg := fmt.Sprintf("received record with version %x when expecting version %x", vers, expectedVers) + err = c.rawConn.In.SetErrorLocked(c.newRecordHeaderError(nil, msg)) + return + } + //if !c.haveVers { + // // First message, be extra suspicious: this might not be a TLS + // // client. Bail out before reading a full 'body', if possible. + // // The current max version is 3.3 so if the version is >= 16.0, + // // it's probably not real. + // if (typ != recordTypeAlert && typ != recordTypeHandshake) || vers >= 0x1000 { + // err = c.rawConn.In.SetErrorLocked(c.newRecordHeaderError(c.conn, "first record does not look like a TLS handshake")) + // return + // } + //} + if *c.rawConn.Vers == tls.VersionTLS13 && n > maxCiphertextTLS13 || n > maxCiphertext { + c.sendAlert(alertRecordOverflow) + msg := fmt.Sprintf("oversized record received with length %d", n) + err = c.rawConn.In.SetErrorLocked(c.newRecordHeaderError(nil, msg)) + return + } + if err = c.readFromUntil(c.conn, recordHeaderLen+n); err != nil { + if e, ok := err.(net.Error); !ok || !e.Temporary() { + c.rawConn.In.SetErrorLocked(err) + } + return + } + + // Process message. + 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]))) + return + } + return +} + +// retryReadRecord recurs into readRecordOrCCS to drop a non-advancing record, like +// a warning alert, empty application_data, or a change_cipher_spec in TLS 1.3. +func (c *Conn) retryReadRecord( /*expectChangeCipherSpec bool*/ ) error { + //c.retryCount++ + //if c.retryCount > maxUselessRecords { + // c.sendAlert(alertUnexpectedMessage) + // return c.in.setErrorLocked(errors.New("tls: too many ignored records")) + //} + return c.readRecord( /*expectChangeCipherSpec*/ ) +} + +// atLeastReader reads from R, stopping with EOF once at least N bytes have been +// read. It is different from an io.LimitedReader in that it doesn't cut short +// the last Read call, and in that it considers an early EOF an error. +type atLeastReader struct { + R io.Reader + N int64 +} + +func (r *atLeastReader) Read(p []byte) (int, error) { + if r.N <= 0 { + return 0, io.EOF + } + n, err := r.R.Read(p) + r.N -= int64(n) // won't underflow unless len(p) >= n > 9223372036854775809 + if r.N > 0 && err == io.EOF { + return n, io.ErrUnexpectedEOF + } + if r.N <= 0 && err == nil { + return n, io.EOF + } + return n, err +} + +// readFromUntil reads from r into c.rawConn.RawInput until c.rawConn.RawInput contains +// at least n bytes or else returns an error. +func (c *Conn) readFromUntil(r io.Reader, n int) error { + if c.rawConn.RawInput.Len() >= n { + return nil + } + needs := n - c.rawConn.RawInput.Len() + // There might be extra input waiting on the wire. Make a best effort + // attempt to fetch it so that it can be used in (*Conn).Read to + // "predict" closeNotify alerts. + c.rawConn.RawInput.Grow(needs + bytes.MinRead) + _, err := c.rawConn.RawInput.ReadFrom(&atLeastReader{r, int64(needs)}) + return err +} + +func (c *Conn) newRecordHeaderError(conn net.Conn, msg string) (err tls.RecordHeaderError) { + err.Msg = msg + err.Conn = conn + copy(err.RecordHeader[:], c.rawConn.RawInput.Bytes()) + return err +} diff --git a/common/ktls/ktls_read_wait.go b/common/ktls/ktls_read_wait.go new file mode 100644 index 0000000000..4b4edc1e09 --- /dev/null +++ b/common/ktls/ktls_read_wait.go @@ -0,0 +1,41 @@ +// 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. + +//go:build linux && go1.25 && badlinkname + +package ktls + +import ( + "github.com/sagernet/sing/common/buf" + N "github.com/sagernet/sing/common/network" +) + +func (c *Conn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy bool) { + c.readWaitOptions = options + return false +} + +func (c *Conn) WaitReadBuffer() (buffer *buf.Buffer, err error) { + c.rawConn.In.Lock() + defer c.rawConn.In.Unlock() + for c.rawConn.Input.Len() == 0 { + err = c.readRecord() + if err != nil { + return + } + } + buffer = c.readWaitOptions.NewBuffer() + n, err := c.rawConn.Input.Read(buffer.FreeBytes()) + if err != nil { + buffer.Release() + return + } + buffer.Truncate(n) + if n != 0 && c.rawConn.Input.Len() == 0 && c.rawConn.Input.Len() > 0 && + c.rawConn.RawInput.Bytes()[0] == recordTypeAlert { + _ = c.rawConn.ReadRecord() + } + c.readWaitOptions.PostReturn(buffer) + return +} diff --git a/common/ktls/ktls_stub_nolinkname.go b/common/ktls/ktls_stub_nolinkname.go new file mode 100644 index 0000000000..44a0b30ccb --- /dev/null +++ b/common/ktls/ktls_stub_nolinkname.go @@ -0,0 +1,15 @@ +//go:build linux && go1.25 && !badlinkname + +package ktls + +import ( + "context" + + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + aTLS "github.com/sagernet/sing/common/tls" +) + +func NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) { + return nil, E.New("kTLS requires build flags `badlinkname` and `-ldflags=-checklinkname=0`, please recompile your binary") +} diff --git a/common/ktls/ktls_stub_nonlinux.go b/common/ktls/ktls_stub_nonlinux.go new file mode 100644 index 0000000000..e754b7759a --- /dev/null +++ b/common/ktls/ktls_stub_nonlinux.go @@ -0,0 +1,15 @@ +//go:build !linux + +package ktls + +import ( + "context" + + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + aTLS "github.com/sagernet/sing/common/tls" +) + +func NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) { + return nil, E.New("kTLS is only supported on Linux") +} diff --git a/common/ktls/ktls_stub_oldgo.go b/common/ktls/ktls_stub_oldgo.go new file mode 100644 index 0000000000..613bf7f1e4 --- /dev/null +++ b/common/ktls/ktls_stub_oldgo.go @@ -0,0 +1,15 @@ +//go:build linux && !go1.25 + +package ktls + +import ( + "context" + + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + aTLS "github.com/sagernet/sing/common/tls" +) + +func NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) { + return nil, E.New("kTLS requires Go 1.25 or later, please recompile your binary") +} diff --git a/common/ktls/ktls_write.go b/common/ktls/ktls_write.go new file mode 100644 index 0000000000..f4e0f65d9e --- /dev/null +++ b/common/ktls/ktls_write.go @@ -0,0 +1,81 @@ +// 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. + +//go:build linux && go1.25 && badlinkname + +package ktls + +import ( + "crypto/cipher" + "crypto/tls" + "errors" + "net" +) + +func (c *Conn) Write(b []byte) (int, error) { + if !c.kernelTx { + return c.Conn.Write(b) + } + // interlock with Close below + for { + x := c.rawConn.ActiveCall.Load() + if x&1 != 0 { + return 0, net.ErrClosed + } + if c.rawConn.ActiveCall.CompareAndSwap(x, x+2) { + break + } + } + defer c.rawConn.ActiveCall.Add(-2) + + //if err := c.Conn.HandshakeContext(context.Background()); err != nil { + // return 0, err + //} + + c.rawConn.Out.Lock() + defer c.rawConn.Out.Unlock() + + if err := *c.rawConn.Out.Err; err != nil { + return 0, err + } + + if !c.rawConn.IsHandshakeComplete.Load() { + return 0, tls.AlertError(alertInternalError) + } + + if *c.rawConn.CloseNotifySent { + // return 0, errShutdown + return 0, errors.New("tls: protocol is shutdown") + } + + // TLS 1.0 is susceptible to a chosen-plaintext + // attack when using block mode ciphers due to predictable IVs. + // This can be prevented by splitting each Application Data + // record into two records, effectively randomizing the RawIV. + // + // https://www.openssl.org/~bodo/tls-cbc.txt + // https://bugzilla.mozilla.org/show_bug.cgi?id=665814 + // https://www.imperialviolet.org/2012/01/15/beastfollowup.html + + var m int + if len(b) > 1 && *c.rawConn.Vers == tls.VersionTLS10 { + if _, ok := (*c.rawConn.Out.Cipher).(cipher.BlockMode); ok { + n, err := c.writeRecordLocked(recordTypeApplicationData, b[:1]) + if err != nil { + return n, c.rawConn.Out.SetErrorLocked(err) + } + m, b = 1, b[1:] + } + } + + n, err := c.writeRecordLocked(recordTypeApplicationData, b) + return n + m, c.rawConn.Out.SetErrorLocked(err) +} + +func (c *Conn) writeRecordLocked(typ uint16, data []byte) (n int, err error) { + if !c.kernelTx { + return c.rawConn.WriteRecordLocked(typ, data) + } + return c.writeKernelRecord(typ, data) +} diff --git a/common/listener/listener.go b/common/listener/listener.go new file mode 100644 index 0000000000..4ca82b6203 --- /dev/null +++ b/common/listener/listener.go @@ -0,0 +1,172 @@ +package listener + +import ( + "context" + "net" + "net/netip" + "runtime" + "strings" + "sync/atomic" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/settings" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + "github.com/vishvananda/netns" +) + +type Listener struct { + ctx context.Context + logger logger.ContextLogger + network []string + listenOptions option.ListenOptions + connHandler adapter.ConnectionHandler + packetHandler adapter.PacketHandler + oobPacketHandler adapter.OOBPacketHandler + threadUnsafePacketWriter bool + disablePacketOutput bool + setSystemProxy bool + systemProxySOCKS bool + tproxy bool + + tcpListener net.Listener + systemProxy settings.SystemProxy + udpConn *net.UDPConn + udpAddr M.Socksaddr + packetOutbound chan *N.PacketBuffer + packetOutboundClosed chan struct{} + shutdown atomic.Bool +} + +type Options struct { + Context context.Context + Logger logger.ContextLogger + Network []string + Listen option.ListenOptions + ConnectionHandler adapter.ConnectionHandler + PacketHandler adapter.PacketHandler + OOBPacketHandler adapter.OOBPacketHandler + ThreadUnsafePacketWriter bool + DisablePacketOutput bool + SetSystemProxy bool + SystemProxySOCKS bool + TProxy bool +} + +func New( + options Options, +) *Listener { + return &Listener{ + ctx: options.Context, + logger: options.Logger, + network: options.Network, + listenOptions: options.Listen, + connHandler: options.ConnectionHandler, + packetHandler: options.PacketHandler, + oobPacketHandler: options.OOBPacketHandler, + threadUnsafePacketWriter: options.ThreadUnsafePacketWriter, + disablePacketOutput: options.DisablePacketOutput, + setSystemProxy: options.SetSystemProxy, + systemProxySOCKS: options.SystemProxySOCKS, + tproxy: options.TProxy, + } +} + +func (l *Listener) Start() error { + if common.Contains(l.network, N.NetworkTCP) { + _, err := l.ListenTCP() + if err != nil { + return err + } + go l.loopTCPIn() + } + if common.Contains(l.network, N.NetworkUDP) { + _, err := l.ListenUDP() + if err != nil { + return err + } + l.packetOutboundClosed = make(chan struct{}) + l.packetOutbound = make(chan *N.PacketBuffer, 64) + go l.loopUDPIn() + if !l.disablePacketOutput { + go l.loopUDPOut() + } + } + if l.setSystemProxy { + listenPort := M.SocksaddrFromNet(l.tcpListener.Addr()).Port + var listenAddrString string + listenAddr := l.listenOptions.Listen.Build(netip.IPv4Unspecified()) + if listenAddr.IsUnspecified() { + listenAddrString = "127.0.0.1" + } else { + listenAddrString = listenAddr.String() + } + systemProxy, err := settings.NewSystemProxy(l.ctx, M.ParseSocksaddrHostPort(listenAddrString, listenPort), l.systemProxySOCKS) + if err != nil { + return E.Cause(err, "initialize system proxy") + } + err = systemProxy.Enable() + if err != nil { + return E.Cause(err, "set system proxy") + } + l.systemProxy = systemProxy + } + return nil +} + +func (l *Listener) Close() error { + l.shutdown.Store(true) + var err error + if l.systemProxy != nil && l.systemProxy.IsEnabled() { + err = l.systemProxy.Disable() + } + return E.Errors(err, common.Close( + l.tcpListener, + common.PtrOrNil(l.udpConn), + )) +} + +func (l *Listener) TCPListener() net.Listener { + return l.tcpListener +} + +func (l *Listener) UDPConn() *net.UDPConn { + return l.udpConn +} + +func (l *Listener) ListenOptions() option.ListenOptions { + return l.listenOptions +} + +func ListenNetworkNamespace[T any](nameOrPath string, block func() (T, error)) (T, error) { + if nameOrPath != "" { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + currentNs, err := netns.Get() + 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, "/") { + targetNs, err = netns.GetFromPath(nameOrPath) + } else { + targetNs, err = netns.GetFromName(nameOrPath) + } + if err != nil { + return common.DefaultValue[T](), E.Cause(err, "get netns ", nameOrPath) + } + defer targetNs.Close() + err = netns.Set(targetNs) + if err != nil { + return common.DefaultValue[T](), E.Cause(err, "set netns to ", nameOrPath) + } + } + return block() +} diff --git a/common/listener/listener_tcp.go b/common/listener/listener_tcp.go new file mode 100644 index 0000000000..8cfdd6e6c3 --- /dev/null +++ b/common/listener/listener_tcp.go @@ -0,0 +1,111 @@ +package listener + +import ( + "net" + "net/netip" + "strings" + "syscall" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/redir" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" + + "github.com/database64128/tfo-go/v2" +) + +func (l *Listener) ListenTCP() (net.Listener, error) { + //nolint:staticcheck + if l.listenOptions.ProxyProtocol || l.listenOptions.ProxyProtocolAcceptNoHeader { + return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0") + } + var err error + bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort) + var listenConfig net.ListenConfig + if l.listenOptions.BindInterface != "" { + listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1)) + } + if l.listenOptions.RoutingMark != 0 { + listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark))) + } + if l.listenOptions.ReuseAddr { + listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr()) + } + if l.listenOptions.DisableTCPKeepAlive { + listenConfig.KeepAlive = -1 + listenConfig.KeepAliveConfig.Enable = false + } else { + keepIdle := time.Duration(l.listenOptions.TCPKeepAlive) + if keepIdle == 0 { + keepIdle = C.TCPKeepAliveInitial + } + keepInterval := time.Duration(l.listenOptions.TCPKeepAliveInterval) + if keepInterval == 0 { + keepInterval = C.TCPKeepAliveInterval + } + listenConfig.KeepAliveConfig = net.KeepAliveConfig{ + Enable: true, + Idle: keepIdle, + Interval: keepInterval, + } + } + if l.listenOptions.TCPMultiPath { + listenConfig.SetMultipathTCP(true) + } + if l.tproxy { + listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error { + return control.Raw(conn, func(fd uintptr) error { + return redir.TProxy(fd, !strings.HasSuffix(network, "4"), false) + }) + }) + } + tcpListener, err := ListenNetworkNamespace[net.Listener](l.listenOptions.NetNs, func() (net.Listener, error) { + if l.listenOptions.TCPFastOpen { + var tfoConfig tfo.ListenConfig + tfoConfig.ListenConfig = listenConfig + return tfoConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String()) + } else { + return listenConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String()) + } + }) + if err != nil { + return nil, err + } + l.logger.Info("tcp server started at ", tcpListener.Addr()) + l.tcpListener = tcpListener + return tcpListener, err +} + +func (l *Listener) loopTCPIn() { + tcpListener := l.tcpListener + var metadata adapter.InboundContext + for { + conn, err := tcpListener.Accept() + if err != nil { + //nolint:staticcheck + if netError, isNetError := err.(net.Error); isNetError && netError.Temporary() { + l.logger.Error(err) + continue + } + if l.shutdown.Load() && E.IsClosed(err) { + return + } + l.tcpListener.Close() + l.logger.Error("tcp listener closed: ", err) + continue + } + //nolint:staticcheck + metadata.InboundDetour = l.listenOptions.Detour + metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap() + metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() + ctx := log.ContextWithNewID(l.ctx) + l.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) + go l.connHandler.NewConnection(ctx, conn, metadata, nil) + } +} diff --git a/common/listener/listener_udp.go b/common/listener/listener_udp.go new file mode 100644 index 0000000000..ed48a75553 --- /dev/null +++ b/common/listener/listener_udp.go @@ -0,0 +1,289 @@ +package listener + +import ( + "context" + "net" + "net/netip" + "os" + "strings" + "syscall" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/redir" + "github.com/sagernet/sing/common/buf" + sBufio "github.com/sagernet/sing/common/bufio" + "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" +) + +const udpOutputBatchSize = 128 + +func (l *Listener) ListenUDP() (net.PacketConn, error) { + bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort) + var listenConfig net.ListenConfig + if l.listenOptions.BindInterface != "" { + listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1)) + } + if l.listenOptions.RoutingMark != 0 { + listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark))) + } + if l.listenOptions.ReuseAddr { + listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr()) + } + var udpFragment bool + if l.listenOptions.UDPFragment != nil { + udpFragment = *l.listenOptions.UDPFragment + } else { + udpFragment = l.listenOptions.UDPFragmentDefault + } + if !udpFragment { + listenConfig.Control = control.Append(listenConfig.Control, control.DisableUDPFragment()) + } + if l.tproxy { + listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error { + return control.Raw(conn, func(fd uintptr) error { + return redir.TProxy(fd, !strings.HasSuffix(network, "4"), true) + }) + }) + } + udpConn, err := ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) { + return listenConfig.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String()) + }) + if err != nil { + return nil, err + } + l.udpConn = udpConn.(*net.UDPConn) + l.udpAddr = bindAddr + l.logger.Info("udp server started at ", udpConn.LocalAddr()) + return udpConn, err +} + +func (l *Listener) DialContext(dialer net.Dialer, ctx context.Context, network string, address string) (net.Conn, error) { + return ListenNetworkNamespace[net.Conn](l.listenOptions.NetNs, func() (net.Conn, error) { + if l.listenOptions.BindInterface != "" { + dialer.Control = control.Append(dialer.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1)) + } + if l.listenOptions.RoutingMark != 0 { + dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark))) + } + if l.listenOptions.ReuseAddr { + dialer.Control = control.Append(dialer.Control, control.ReuseAddr()) + } + return dialer.DialContext(ctx, network, address) + }) +} + +func (l *Listener) ListenPacket(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.PacketConn, error) { + return ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) { + if l.listenOptions.BindInterface != "" { + listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1)) + } + if l.listenOptions.RoutingMark != 0 { + listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark))) + } + if l.listenOptions.ReuseAddr { + listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr()) + } + return listenConfig.ListenPacket(ctx, network, address) + }) +} + +func (l *Listener) UDPAddr() M.Socksaddr { + return l.udpAddr +} + +func (l *Listener) PacketWriter() N.PacketWriter { + return (*packetWriter)(l) +} + +func (l *Listener) loopUDPIn() { + defer close(l.packetOutboundClosed) + if l.oobPacketHandler == nil { + if batchHandler, isBatchHandler := l.packetHandler.(adapter.PacketBatchHandler); isBatchHandler { + packetConn := sBufio.NewPacketConn(l.udpConn) + if readWaiter, created := sBufio.CreatePacketBatchReadWaiter(packetConn); created { + l.loopUDPInBatch(batchHandler, readWaiter) + return + } + } + } + var buffer *buf.Buffer + if !l.threadUnsafePacketWriter { + buffer = buf.NewPacket() + defer buffer.Release() + buffer.IncRef() + defer buffer.DecRef() + } + if l.oobPacketHandler != nil { + oob := make([]byte, 1024) + for { + if l.threadUnsafePacketWriter { + buffer = buf.NewPacket() + } else { + buffer.Reset() + } + n, oobN, _, addr, err := l.udpConn.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob) + if err != nil { + if l.threadUnsafePacketWriter { + buffer.Release() + } + if l.shutdown.Load() && E.IsClosed(err) { + return + } + l.udpConn.Close() + l.logger.Error("udp listener closed: ", err) + return + } + buffer.Truncate(n) + l.oobPacketHandler.NewPacket(buffer, oob[:oobN], M.SocksaddrFromNetIP(addr).Unwrap()) + } + } else { + for { + if l.threadUnsafePacketWriter { + buffer = buf.NewPacket() + } else { + buffer.Reset() + } + n, addr, err := l.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) + if err != nil { + if l.threadUnsafePacketWriter { + buffer.Release() + } + if l.shutdown.Load() && E.IsClosed(err) { + return + } + l.udpConn.Close() + l.logger.Error("udp listener closed: ", err) + return + } + buffer.Truncate(n) + l.packetHandler.NewPacket(buffer, M.SocksaddrFromNetIP(addr).Unwrap()) + } + } +} + +func (l *Listener) loopUDPInBatch(handler adapter.PacketBatchHandler, readWaiter N.PacketBatchReadWaiter) { + readWaitOptions := N.ReadWaitOptions{ + BatchSize: sBufio.DefaultPacketReadBatchSize, + } + readWaiter.InitializeReadWaiter(readWaitOptions) + for { + buffers, sources, err := readWaiter.WaitReadPackets() + if err != nil { + buf.ReleaseMulti(buffers) + if l.shutdown.Load() && E.IsClosed(err) { + return + } + l.udpConn.Close() + l.logger.Error("udp listener closed: ", err) + return + } + handler.NewPacketBatch(buffers, sources) + } +} + +func (l *Listener) loopUDPOut() { + packetConn := sBufio.NewPacketConn(l.udpConn) + batchWriter := sBufio.NewPacketBatchWriter(packetConn) + packets := make([]*N.PacketBuffer, 0, udpOutputBatchSize) + buffers := make([]*buf.Buffer, 0, udpOutputBatchSize) + destinations := make([]M.Socksaddr, 0, udpOutputBatchSize) + for { + select { + case packet := <-l.packetOutbound: + packets = append(packets, packet) + case <-l.packetOutboundClosed: + l.releasePacketOutbound() + return + } + drain: + for len(packets) < udpOutputBatchSize { + select { + case packet := <-l.packetOutbound: + packets = append(packets, packet) + default: + break drain + } + } + for _, packet := range packets { + buffers = append(buffers, packet.Buffer) + destinations = append(destinations, packet.Destination) + } + err := batchWriter.WritePacketBatch(buffers, destinations) + for _, packet := range packets { + N.PutPacketBuffer(packet) + } + packets = packets[:0] + buffers = buffers[:0] + destinations = destinations[:0] + if err != nil { + if l.shutdown.Load() && E.IsClosed(err) { + return + } + l.logger.Error("udp listener write back: ", err) + } + } +} + +func (l *Listener) releasePacketOutbound() { + for { + select { + case packet := <-l.packetOutbound: + packet.Buffer.Release() + N.PutPacketBuffer(packet) + default: + return + } + } +} + +type packetWriter Listener + +func (w *packetWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { + packet := N.NewPacketBuffer() + packet.Buffer = buffer + packet.Destination = destination + select { + case w.packetOutbound <- packet: + return nil + default: + buffer.Release() + N.PutPacketBuffer(packet) + if w.shutdown.Load() { + return os.ErrClosed + } + w.logger.Trace("dropped packet to ", destination) + return nil + } +} + +func (w *packetWriter) WritePacketBatch(buffers []*buf.Buffer, destinations []M.Socksaddr) error { + if len(buffers) == 0 || len(buffers) != len(destinations) { + buf.ReleaseMulti(buffers) + return os.ErrInvalid + } + for index, buffer := range buffers { + packet := N.NewPacketBuffer() + packet.Buffer = buffer + packet.Destination = destinations[index] + select { + case w.packetOutbound <- packet: + default: + buffer.Release() + N.PutPacketBuffer(packet) + buf.ReleaseMulti(buffers[index+1:]) + if w.shutdown.Load() { + return os.ErrClosed + } + w.logger.Trace("dropped packet batch to ", destinations[index]) + return nil + } + } + return nil +} + +func (w *packetWriter) WriteIsThreadUnsafe() { +} diff --git a/common/monitoring/brodcaster.go b/common/monitoring/brodcaster.go new file mode 100644 index 0000000000..b7cf4f0bb3 --- /dev/null +++ b/common/monitoring/brodcaster.go @@ -0,0 +1,92 @@ +package monitoring + +import ( + "context" + "sync" +) + +type Broadcaster[T any] struct { + ctx context.Context + cancel context.CancelFunc + + mu sync.RWMutex + subs map[chan T]struct{} + once sync.Once +} + +func NewBroadcaster[T any](parent context.Context) *Broadcaster[T] { + ctx, cancel := context.WithCancel(parent) + b := &Broadcaster[T]{ + ctx: ctx, + cancel: cancel, + subs: make(map[chan T]struct{}), + } + + go b.watchContext() + return b +} + +func (b *Broadcaster[T]) watchContext() { + <-b.ctx.Done() + b.closeAll() +} + +func (b *Broadcaster[T]) Subscribe(buffer int) <-chan T { + ch := make(chan T, buffer) + + b.mu.Lock() + defer b.mu.Unlock() + + select { + case <-b.ctx.Done(): + close(ch) + default: + b.subs[ch] = struct{}{} + } + + return ch +} + +func (b *Broadcaster[T]) Unsubscribe(ch <-chan T) { + b.mu.Lock() + defer b.mu.Unlock() + + for sub := range b.subs { + if sub == ch { + delete(b.subs, sub) + close(sub) + return + } + } +} + +func (b *Broadcaster[T]) Publish(event T) { + b.mu.RLock() + defer b.mu.RUnlock() + + for ch := range b.subs { + select { + case ch <- event: + case <-b.ctx.Done(): + return + default: + // slow subscriber → drop event + } + } +} + +func (b *Broadcaster[T]) Close() { + b.once.Do(func() { + b.cancel() + }) +} + +func (b *Broadcaster[T]) closeAll() { + b.mu.Lock() + defer b.mu.Unlock() + + for ch := range b.subs { + close(ch) + } + b.subs = nil +} diff --git a/common/monitoring/outbound_monitoring.go b/common/monitoring/outbound_monitoring.go new file mode 100644 index 0000000000..75ed268078 --- /dev/null +++ b/common/monitoring/outbound_monitoring.go @@ -0,0 +1,1067 @@ +package monitoring + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net" + "sort" + "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" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/service" + + "github.com/sagernet/sing-box/hiddify/ipinfo" + "github.com/sagernet/sing/common/x/list" + + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service/pause" +) + +const TimeoutDelay uint16 = 65535 + +var _ adapter.ConnectionTracker = (*OutboundMonitoring)(nil) +var _ adapter.LifecycleService = (*OutboundMonitoring)(nil) +var _ adapter.InterfaceUpdateListener = (*OutboundMonitoring)(nil) + +const ( + defaultWorkerCount = 10 + defaultDebounceWindow = 500 * time.Millisecond + defaultURLTestTimeout = 5 * time.Second + defaultIdleTimeout = 10 * time.Minute + defaultInterval = 5 * time.Minute + defaultURLTest = "https://www.gstatic.com/generate_204" +) + +// func RegisterService(registry *boxService.Registry) { +// boxService.Register[option.MonitoringOptions](registry, C.TypeOutboundMonitor, func(ctx context.Context, logger log.ContextLogger, tag string, options option.MonitoringOptions) (adapter.Service, error) { +// return NewOutboundMonitoring(ctx, logger, tag, options) +// }) +// } + +func Get(ctx context.Context) *OutboundMonitoring { + return service.PtrFromContext[OutboundMonitoring](ctx) +} + +// OutboundMonitoring orchestrates URL testing and traffic sampling for outbounds. +type OutboundMonitoring struct { + endpointManager adapter.EndpointManager + outboundManager adapter.OutboundManager + logger log.ContextLogger + cache adapter.CacheFile + ctx context.Context + cancel context.CancelFunc + tag string + pause pause.Manager + pauseCallback *list.Element[pause.Callback] + started bool + urls []string + currentLinkIndex atomic.Uint32 + access sync.Mutex + idleTimeout time.Duration + lastActive common.TypedValue[time.Time] + workersRunning atomic.Bool + mainInterval time.Duration + debounceWindow time.Duration + urlTestTimeout time.Duration + workersCount int + history adapter.URLTestHistoryStorage + + mainTicker *time.Ticker + + priorityQueue chan *testTask + normalQueue chan *testTask + + outbounds map[string]*outboundState + groups map[string]*groupState + + cacheDirty atomic.Bool + + cycleSeq uint64 + cycleRunning atomic.Bool + + workerWG sync.WaitGroup + schedulerWG sync.WaitGroup + closerOnce sync.Once +} + +// InterfaceUpdated implements [adapter.InterfaceUpdateListener]. +func (m *OutboundMonitoring) InterfaceUpdated() { + m.startCycleOnce() +} + +// Name implements [adapter.LifecycleService]. +func (m *OutboundMonitoring) Name() string { + return "outbound-monitoring" +} + +func (m *OutboundMonitoring) OutboundsHistory(groupTag string) map[string]*adapter.URLTestHistory { + + histories := make(map[string]*adapter.URLTestHistory) + + grp, ok := m.groups[groupTag] + if !ok { + return histories + } + //m.logger.Debug("collecting history for group ", groupTag, " with ", len(grp.outbounds), " outbounds") + for outboundTag := range grp.outbounds { + histories[outboundTag] = m.getUrlTest(outboundTag) + // m.logger.Error("checking history for outbound ", outboundTag) + + } + return histories +} + +func (m *OutboundMonitoring) getUrlTest(outboundTag string) *adapter.URLTestHistory { + state, ok := m.outbounds[outboundTag] + if !ok { + return nil + } + + if grp, ok := m.groups[outboundTag]; ok { + realtag := RealTag(state.outbound) + //m.logger.Debug("outbound ", outboundTag, " is a group, checking group ", grp.tag, " with real tag ", realtag) + if realtag != "" && realtag != outboundTag { + return m.getUrlTest(realtag) + } + + return m.getMinGroupOutboundHistory(grp.tag) + + } + state.mu.Lock() + his := state.history + his.IsFromCache = state.from_cache + state.mu.Unlock() + return &his + +} + +func (m *OutboundMonitoring) getMinGroupOutboundHistory(groupTag string) *adapter.URLTestHistory { + grp, ok := m.groups[groupTag] + if !ok { + return nil + } + var minHis *adapter.URLTestHistory + var minHisFromCache *adapter.URLTestHistory + for outboundTag := range grp.outbounds { + his := m.getUrlTest(outboundTag) + if his == nil || his.Delay == 0 { + continue + } + if !his.IsFromCache { + if minHis == nil { + minHis = his + } else if his.Delay < minHis.Delay { + minHis.Delay = his.Delay + minHis.IpInfo = his.IpInfo + } else if minHis.IpInfo == nil { + minHis.IpInfo = his.IpInfo + } + } else { + if minHisFromCache == nil { + minHisFromCache = his + } else if his.Delay < minHisFromCache.Delay { + minHisFromCache.Delay = his.Delay + minHisFromCache.IpInfo = his.IpInfo + } else if minHisFromCache.IpInfo == nil { + minHisFromCache.IpInfo = his.IpInfo + } + } + } + + final := minHis + if minHis == nil || minHis.Delay >= TimeoutDelay { + final = minHisFromCache + } else if minHisFromCache != nil && minHis.IpInfo == nil { + final.IpInfo = minHisFromCache.IpInfo + } + + return final + +} + +func (m *OutboundMonitoring) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) net.Conn { + m.Touch() + return conn +} +func (m *OutboundMonitoring) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) N.PacketConn { + m.Touch() + return conn +} + +// NewOutboundMonitoring creates and starts a monitoring instance. +func NewOutboundMonitoring(ctx context.Context, logger log.ContextLogger, options option.MonitoringOptions) (*OutboundMonitoring, error) { + if options.Interval <= 0 { + options.Interval = badoption.Duration(defaultInterval) + } + if options.Workers <= 0 { + options.Workers = defaultWorkerCount + } + if options.URLTestTimeout <= 0 { + options.URLTestTimeout = badoption.Duration(defaultURLTestTimeout) + } + if options.IdleTimeout <= 0 { + options.IdleTimeout = badoption.Duration(defaultIdleTimeout) + } + if options.DebounceWindow <= 0 { + options.DebounceWindow = badoption.Duration(defaultDebounceWindow) + } + + cloned := append([]string(nil), options.URLs...) + if len(cloned) == 0 { + cloned = []string{defaultURLTest} + } + + var history adapter.URLTestHistoryStorage + if historyFromCtx := service.PtrFromContext[urltest.HistoryStorage](ctx); historyFromCtx != nil { + history = historyFromCtx + } else if clashServer := service.FromContext[adapter.ClashServer](ctx); clashServer != nil { + history = clashServer.HistoryStorage() + } else { + history = urltest.NewHistoryStorage() + } + + ctx, cancel := context.WithCancel(ctx) + m := &OutboundMonitoring{ + ctx: ctx, + cancel: cancel, + urls: cloned, + pause: service.FromContext[pause.Manager](ctx), + started: false, + logger: logger, + outboundManager: service.FromContext[adapter.OutboundManager](ctx), + endpointManager: service.FromContext[adapter.EndpointManager](ctx), + + history: history, + + mainInterval: options.Interval.Build(), + idleTimeout: options.IdleTimeout.Build(), + workersCount: options.Workers, + urlTestTimeout: options.URLTestTimeout.Build(), + debounceWindow: options.DebounceWindow.Build(), + + priorityQueue: make(chan *testTask, 1000), + normalQueue: make(chan *testTask, 10000), + outbounds: make(map[string]*outboundState), + groups: make(map[string]*groupState), + } + + return m, nil +} + +func (m *OutboundMonitoring) Start(stage adapter.StartStage) error { + m.logger.Info("starting outbound monitoring ", stage) + switch stage { + case adapter.StartStateInitialize: + m.cache = service.FromContext[adapter.CacheFile](m.ctx) + + for _, outbound := range m.outboundManager.Outbounds() { + // if _, ok := outbound.(adapter.OutboundGroup); !ok { + m.outbounds[outbound.Tag()] = &outboundState{groupTags: []string{}, invalid: true, outbound: outbound, dependencies: outbound.Dependencies()} + // } + //m.logger.Info("registered outbound for monitoring: ", outbound.Tag()) + } + for _, outbound := range m.endpointManager.Endpoints() { + // if _, ok := outbound.(adapter.OutboundGroup); !ok { + m.outbounds[outbound.Tag()] = &outboundState{groupTags: []string{}, invalid: true, outbound: outbound, dependencies: outbound.Dependencies()} + // } + //m.logger.Info("registered outbound for monitoring: ", outbound.Tag()) + } + for tag, outbound := range m.outbounds { + for _, dep := range outbound.dependencies { + m.outbounds[dep].dependenciesInverse = append(m.outbounds[dep].dependenciesInverse, tag) + } + } + + m.logger.Info("registered ", len(m.outbounds), " outbounds for monitoring") + grp := m.makeGroup("") + for tag := range m.outbounds { + grp.outbounds[tag] = struct{}{} + m.outbounds[tag].groupTags = append(m.outbounds[tag].groupTags, "") + } + for _, outbound := range m.outboundManager.Outbounds() { + if og, ok := outbound.(adapter.OutboundGroup); ok { + groupTag := og.Tag() + grp := m.makeGroup(groupTag) + for _, tag := range og.All() { + if _, exists := m.outbounds[tag]; !exists { + return errors.New("outbound monitoring: outbound not found: " + tag + " in group " + groupTag) + } + grp.outbounds[tag] = struct{}{} + m.outbounds[tag].groupTags = append(m.outbounds[tag].groupTags, groupTag) + } + + //m.logger.Info("registered outbound group for monitoring: ", groupTag, " with ", len(og.All()), " outbounds") + + } + } + m.logger.Info("registered ", len(m.groups), " outbound groups for monitoring") + m.loadHistory() + case adapter.StartStatePostStart: + for i := 0; i < m.workersCount; i++ { + m.workerWG.Add(1) + go m.workerLoop() + } + for groupTag := range m.groups { + m.schedulerWG.Add(1) + go m.groupNotifierLoop(m.groups[groupTag]) + } + + m.started = true + m.Touch() + } + + return nil +} + +func (m *OutboundMonitoring) startTimerWorkers() { + if !m.workersRunning.CompareAndSwap(false, true) { + return + } + m.mainTicker = time.NewTicker(m.mainInterval) + + m.pauseCallback = pause.RegisterTicker(m.pause, m.mainTicker, m.mainInterval, nil) + m.schedulerWG.Add(1) + go m.scheduleLoop() +} +func (m *OutboundMonitoring) stopTimerWorkers() { + if !m.workersRunning.CompareAndSwap(true, false) { + return + } + m.mainTicker.Stop() + m.mainTicker = nil + if m.cacheDirty.Load() { + m.saveHistory() + } + + m.pause.UnregisterCallback(m.pauseCallback) +} + +func (m *OutboundMonitoring) SignalChange(outboundTag string) error { + if grp, ok := m.groups[outboundTag]; ok { + grp.notifyCh <- struct{}{} + return nil + } + state := m.getState(outboundTag) + if state == nil { + return errors.New("outbound not registered") + } + for _, groupTag := range state.groupTags { + if grp, ok := m.groups[groupTag]; ok { + grp.notifyCh <- struct{}{} + } + } + return nil + +} +func (m *OutboundMonitoring) TestNow(outboundTag string) error { + m.testParents(outboundTag, true) + return m.testNow(outboundTag, true) +} +func (m *OutboundMonitoring) testNow(outboundTag string, priority bool) error { + m.logger.Info("testing outbound ", outboundTag, " with priority: ", priority) + if grp, ok := m.groups[outboundTag]; ok { + for tag := range grp.outbounds { + m.testNow(tag, false) + } + } else { + state := m.getState(outboundTag) + if state == nil { + return errors.New("outbound not registered") + } + + task := &testTask{ + outboundTag: outboundTag, + cycleID: m.cycleSeq, + priority: priority, + } + + if !m.enqueueTask(task) { + // return errors.New("test already queued") + } + } + return nil +} + +func (m *OutboundMonitoring) testParents(outboundTag string, first bool) { + state := m.getState(outboundTag) + if state == nil { + return + } + if _, ok := m.groups[outboundTag]; !ok && !first { + m.logger.Info("testing outbound ", outboundTag) + m.testNow(outboundTag, true) + } + for _, dep := range state.dependenciesInverse { + m.logger.Info("testing parent outbound ", dep, " of ", outboundTag) + m.testParents(dep, false) + } +} + +// InvalidateTest marks the cached test result as invalid so it will be retested. +func (m *OutboundMonitoring) InvalidateTest(outboundTag string) error { + state := m.getState(outboundTag) + if state == nil { + return errors.New("outbound not registered") + } + state.mu.Lock() + state.invalid = true + state.mu.Unlock() + + m.enqueueTask(&testTask{ + outboundTag: outboundTag, + cycleID: m.cycleSeq, + priority: true, + }) + + return nil +} + +func (m *OutboundMonitoring) SubscribeGroup(groupTag string) (observer <-chan GroupEvent, err error) { + + if g, ok := m.groups[groupTag]; ok { + return g.observer.Subscribe(1), nil + } + return nil, E.New("group not found ", groupTag) +} +func (m *OutboundMonitoring) UnsubscribeGroup(groupTag string, observer <-chan GroupEvent) (err error) { + if g, ok := m.groups[groupTag]; ok { + g.observer.Unsubscribe(observer) + return nil + } + return E.New("group not found ", groupTag) +} + +func (m *OutboundMonitoring) Close() error { + m.closerOnce.Do(func() { + m.stopTimerWorkers() + + // close(m.priorityQueue) + // close(m.normalQueue) + for _, g := range m.groups { + if g.observer != nil { + g.observer.Close() + } + } + m.cancel() + m.workerWG.Wait() + m.schedulerWG.Wait() + + }) + return nil +} + +func (m *OutboundMonitoring) scheduleLoop() { + m.logger.Info("outbound monitoring schedule loop started") + m.startCycleOnce() + ticker := m.mainTicker + for { + select { + case <-m.ctx.Done(): + m.schedulerWG.Done() + return + case <-ticker.C: + if time.Since(m.lastActive.Load()) > m.idleTimeout { + m.schedulerWG.Done() + m.stopTimerWorkers() + return + } + m.startCycleOnce() + } + } +} + +func (m *OutboundMonitoring) workerLoop() { + + defer m.workerWG.Done() + for { + + select { + case <-m.ctx.Done(): + return + case task := <-m.priorityQueue: + m.executeTask(task) //for prioritising tasks, we execute them immediately + default: + select { + case <-m.ctx.Done(): + return + case task := <-m.priorityQueue: + m.executeTask(task) + case task := <-m.normalQueue: + m.executeTask(task) + } + } + } +} + +func (m *OutboundMonitoring) executeTask(task *testTask) { + select { + case <-m.ctx.Done(): + return + default: + } + + state := m.outbounds[task.outboundTag] + if state == nil { + return + } + + state.mu.Lock() + state.testing = true + state.mu.Unlock() + defer func() { + state.mu.Lock() + state.testing = false + state.mu.Unlock() + }() + state.mu.Lock() + cycle := task.cycleID + state.mu.Unlock() + + if cycle < 10 && !state.outbound.IsReady() { + m.logger.Info("outbound ", task.outboundTag, " is not ready, skipping test") + go func() { + select { + case <-m.ctx.Done(): + return + case <-time.After(3 * time.Second): + } + state.mu.Lock() + task.cycleID++ + state.mu.Unlock() + m.enqueueTask(task) + }() + return + } + done := make(chan struct{}) + go func() { + defer close(done) + delay, err := m.tester(m.ctx, task.outboundTag) + + outcome := testOutcome{ + outboundTag: task.outboundTag, + history: delay, + err: err, + cycleID: task.cycleID, + priority: task.priority, + } + + m.applyResult(outcome) + if task.resultCh != nil { + select { + case task.resultCh <- outcome: + case <-m.ctx.Done(): + default: + } + + } + }() + select { + case <-m.ctx.Done(): + return + case <-done: + } + +} + +func (m *OutboundMonitoring) tester(parent context.Context, tag string) (adapter.URLTestHistory, error) { + out, ok := m.outbounds[tag] + if !ok { + return adapter.URLTestHistory{Delay: 0}, errors.New("outbound not registered") + } + + idx := m.currentLinkIndex.Load() + + ctx, cancel := context.WithTimeout(parent, m.urlTestTimeout) + defer cancel() + + delay, err := urltest.URLTest(ctx, m.urls[idx], out.outbound) + + his := adapter.URLTestHistory{ + Time: time.Now(), + Delay: delay, + } + if err != nil || delay >= TimeoutDelay { + his.Delay = TimeoutDelay + m.logger.Warn("outbound ", tag, " URL test failed: ", err) + return his, err + } + select { + case <-parent.Done(): + return his, parent.Err() + default: + } + if out.history.IpInfo == nil || out.from_cache { + + ctx, cancel2 := context.WithTimeout(parent, m.urlTestTimeout) + defer cancel2() + + newip, t, err := ipinfo.GetIpInfo(m.logger, ctx, out.outbound) + if err == nil { + his.IpInfo = mergeIpInfo(out.history.IpInfo, newip) + if t < his.Delay { + his.Delay = t + } + } + } + if his.IpInfo != nil { + m.logger.Info("outbound ", tag, " IP ", fmt.Sprint(his.IpInfo), " (", his.Delay, "ms): ", err) + } else { + m.logger.Info("outbound ", tag, " , IP: - (", his.Delay, "ms)") + } + return his, nil +} + +func (m *OutboundMonitoring) startCycleOnce() bool { + if !m.cycleRunning.CompareAndSwap(false, true) { + return false + } + go func() { + defer m.cycleRunning.Store(false) + m.logger.Info("starting regular outbound monitoring cycle") + m.runCycle() + }() + return true +} + +func (m *OutboundMonitoring) runCycle() { + cycleID := atomic.AddUint64(&m.cycleSeq, 1) + tags := m.collectCycleTargets() + + if len(tags) == 0 { + return + } + defer func() { + if m.cacheDirty.Swap(false) { + m.saveHistory() + } + }() + + for idx, _ := range m.urls { + outcomes := m.runStage(cycleID, tags) + success := 0 + for _, result := range outcomes { + if result.err == nil { + success++ + } + } + if success > 0 || idx == len(m.urls)-1 { + return + } + m.currentLinkIndex.Store((m.currentLinkIndex.Load() + 1) % uint32(len(m.urls))) + } +} + +func (m *OutboundMonitoring) runStage(cycleID uint64, tags []string) []testOutcome { + resultCh := make(chan testOutcome, len(tags)) + + expected := 0 + for _, tag := range tags { + state := m.getState(tag) + if state == nil { + continue + } + + task := &testTask{ + outboundTag: tag, + cycleID: cycleID, + priority: false, + resultCh: resultCh, + } + if m.enqueueTask(task) { + expected++ + } + + } + + results := make([]testOutcome, 0, expected) + + for expected > 0 { + select { + case <-m.ctx.Done(): + return results + case r := <-resultCh: + results = append(results, r) + expected-- + } + } + + return results +} + +func (m *OutboundMonitoring) enqueueTask(task *testTask) bool { + + select { + case <-m.ctx.Done(): + return false + default: + } + state, ok := m.outbounds[task.outboundTag] + if !ok { + return false + } + state.mu.Lock() + defer state.mu.Unlock() + + if task.priority { + if state.priorityQueued { + return false + } + state.priorityQueued = true + } else { + if state.enqueuedCycle == task.cycleID { + return false + } + state.enqueuedCycle = task.cycleID + state.queued = true + } + if task.priority { + select { + case m.priorityQueue <- task: + return true + default: + return false + } + + } else { + select { + case m.normalQueue <- task: + return true + default: + return false + } + } + +} + +func (m *OutboundMonitoring) applyResult(outcome testOutcome) *adapter.URLTestHistory { + select { + case <-m.ctx.Done(): + return nil + default: + } + state, ok := m.outbounds[outcome.outboundTag] + if !ok { + return nil + } + state.mu.Lock() + defer state.mu.Unlock() + + state.queued = false + state.priorityQueued = false + state.enqueuedCycle = 0 + state.invalid = outcome.err != nil + state.lastURL = outcome.url + if (outcome.history.Delay != state.history.Delay) || state.history.IpInfo == nil || (outcome.history.IpInfo != nil) { + m.cacheDirty.Store(true) + } + state.history.Delay = outcome.history.Delay + state.history.Time = outcome.history.Time + state.from_cache = false + if outcome.history.IpInfo != nil { + state.history.IpInfo = outcome.history.IpInfo + } + m.history.StoreURLTestHistory(outcome.outboundTag, &state.history) + + m.emitGroupEvent(state.groupTags) + return &state.history +} + +func mergeIpInfo(old, new *ipinfo.IpInfo) *ipinfo.IpInfo { + if old == nil { + return new + } + if new == nil { + return old + } + new2 := *new // copy + if new2.CountryCode == "" { + new2.CountryCode = old.CountryCode + } + if new2.Org == "" { + new2.Org = old.Org + } + return &new2 +} + +func (m *OutboundMonitoring) collectCycleTargets() []string { + + tags := make([]string, 0, len(m.outbounds)) + + delays := make(map[string]uint16, len(tags)) + + for tag, state := range m.outbounds { + if _, ok := m.groups[tag]; ok { + continue + } + state.mu.Lock() + if state.testing || state.queued || state.priorityQueued { + state.mu.Unlock() + continue + } + if state.invalid || time.Since(state.history.Time) >= m.mainInterval { + tags = append(tags, tag) + delays[tag] = state.history.Delay + } + state.mu.Unlock() + } + + sort.SliceStable(tags, func(i, j int) bool { + return delays[tags[i]] < delays[tags[j]] + }) + return tags +} + +func (m *OutboundMonitoring) makeGroup(tag string) *groupState { + grp, ok := m.groups[tag] + if ok { + return grp + } + + grp = &groupState{ + tag: tag, + outbounds: make(map[string]struct{}), + observer: NewBroadcaster[GroupEvent](m.ctx), + notifyCh: make(chan struct{}, 1), + } + m.groups[tag] = grp + return grp +} + +func (m *OutboundMonitoring) Touch() { + if !m.started { + return + } + m.access.Lock() + defer m.access.Unlock() + if m.mainTicker != nil { + m.lastActive.Store(time.Now()) + return + } + m.startTimerWorkers() + +} + +func (m *OutboundMonitoring) emitGroupEvent(groupTags []string) { + for _, groupTag := range groupTags { + grp, ok := m.groups[groupTag] + if !ok || grp.observer == nil { + continue + } + + select { + case grp.notifyCh <- struct{}{}: + default: + } + } +} + +func (m *OutboundMonitoring) emitGroupEventThrottled(groupTag string, since time.Time) { + + grp, ok := m.groups[groupTag] + if !ok || grp.observer == nil { + return + } + tags := make([]string, 0, len(grp.outbounds)) + for tag := range grp.outbounds { + state := m.outbounds[tag] + if state == nil { + continue + } + state.mu.Lock() + if !state.history.Time.Before(since) { + tags = append(tags, tag) + } + state.mu.Unlock() + } + grp.observer.Publish(GroupEvent{ + GroupTag: groupTag, + From: since, + To: time.Now(), + tags: tags, + }) +} + +// func (m *OutboundMonitoring) OutboundsHistories() map[string]adapter.URLTestHistory { + +// histories := make(map[string]adapter.URLTestHistory) +// outbounds := m.outbounds +// for outboundTag, state := range outbounds { +// state.mu.Lock() +// histories[outboundTag] = state.history +// state.mu.Unlock() +// } +// return histories +// } +func RealTag(detour adapter.Outbound) string { + if group, isGroup := detour.(adapter.OutboundGroup); isGroup { + tag := group.Now() + if tag != "" { + return tag + } + } + return detour.Tag() +} + +func (m *OutboundMonitoring) groupNotifierLoop(grp *groupState) { + + defer m.schedulerWG.Done() + + var ( + timer *time.Timer + timerCh <-chan time.Time + since time.Time + ) + + for { + select { + case <-m.ctx.Done(): + if timer != nil { + timer.Stop() + } + return + case <-grp.notifyCh: + // if !m.cacheDirty.Load() { + // continue + // } + if timer == nil { + timer = time.NewTimer(m.debounceWindow) + timerCh = timer.C + } + case <-timerCh: + m.emitGroupEventThrottled(grp.tag, since) + since = time.Now() + if !timer.Stop() { + select { + case <-timer.C: + default: + } + } + timer = nil + timerCh = nil + } + } +} + +func (m *OutboundMonitoring) getState(tag string) *outboundState { + return m.outbounds[tag] +} + +type testTask struct { + outboundTag string + cycleID uint64 + priority bool + resultCh chan<- testOutcome +} + +type testOutcome struct { + outboundTag string + url string + history adapter.URLTestHistory + err error + cycleID uint64 + priority bool +} + +type outboundState struct { + mu sync.Mutex + + outbound adapter.Outbound + groupTags []string + dependenciesInverse []string + dependencies []string + lastURL string + + invalid bool + queued bool + priorityQueued bool + testing bool + enqueuedCycle uint64 + from_cache bool + + history adapter.URLTestHistory +} + +type GroupEvent struct { + GroupTag string + From time.Time + To time.Time + tags []string +} + +type groupState struct { + tag string + outbounds map[string]struct{} + + observer *Broadcaster[GroupEvent] + notifyCh chan struct{} + bestDelay uint16 +} + +type History struct { + OutboundData map[string]*adapter.URLTestHistory `json:"outbound_data"` +} + +func (m *OutboundMonitoring) saveHistory() error { + if m.cache == nil { + return nil + } + history := &History{ + OutboundData: make(map[string]*adapter.URLTestHistory), + } + for tag, state := range m.outbounds { + state.mu.Lock() + h := state.history + state.mu.Unlock() + history.OutboundData[tag] = &h + } + content, err := json.Marshal(history) + if err != nil { + m.logger.Error("failed to marshal outbound monitoring history: ", err) + return err + } + m.cache.SaveBinary("outbound_monitoring_history", &adapter.SavedBinary{ + LastUpdated: time.Now(), + Content: content, + }) + return nil +} +func (m *OutboundMonitoring) loadHistory() *History { + history := &History{} + if m.cache == nil { + return history + } + saved := m.cache.LoadBinary("outbound_monitoring_history") + if saved == nil { + return history + } + err := json.Unmarshal(saved.Content, history) + if err != nil { + m.logger.Error("failed to unmarshal outbound monitoring history: ", err) + return history + } + for tag, his := range history.OutboundData { + if state, ok := m.outbounds[tag]; ok && his != nil { + if _, ok := m.groups[tag]; ok { + continue + } + if his.Delay >= TimeoutDelay { + his.Delay = 0 + } + state.mu.Lock() + state.history = *his + state.from_cache = true + state.mu.Unlock() + } + } + return history +} diff --git a/common/mux/router.go b/common/mux/router.go index 8a2296852b..6de6c4d6c7 100644 --- a/common/mux/router.go +++ b/common/mux/router.go @@ -15,11 +15,11 @@ import ( ) type Router struct { - router adapter.ConnectionRouter + router adapter.ConnectionRouterEx service *mux.Service } -func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouter, error) { +func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouterEx, error) { if !options.Enabled { return router, nil } @@ -41,10 +41,10 @@ func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.Context NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context { return log.ContextWithNewID(ctx) }, - Logger: logger, - Handler: adapter.NewRouteContextHandler(router, logger), - Padding: options.Padding, - Brutal: brutalOptions, + Logger: logger, + HandlerEx: adapter.NewRouteContextHandler(router), + Padding: options.Padding, + Brutal: brutalOptions, }) if err != nil { return nil, err @@ -52,14 +52,29 @@ func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.Context return &Router{router, service}, nil } +// Deprecated: Use RouteConnectionEx instead. func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { if metadata.Destination == mux.Destination { + // TODO: check if WithContext is necessary return r.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata)) } else { return r.router.RouteConnection(ctx, conn, metadata) } } +// Deprecated: Use RoutePacketConnectionEx instead. func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { return r.router.RoutePacketConnection(ctx, conn, metadata) } + +func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + if metadata.Destination == mux.Destination { + r.service.NewConnectionEx(adapter.WithContext(ctx, &metadata), conn, metadata.Source, metadata.Destination, onClose) + return + } + r.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + r.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/common/mux/v2ray_legacy.go b/common/mux/v2ray_legacy.go deleted file mode 100644 index f53aff2db4..0000000000 --- a/common/mux/v2ray_legacy.go +++ /dev/null @@ -1,32 +0,0 @@ -package mux - -import ( - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - vmess "github.com/sagernet/sing-vmess" - "github.com/sagernet/sing/common/logger" - N "github.com/sagernet/sing/common/network" -) - -type V2RayLegacyRouter struct { - router adapter.ConnectionRouter - logger logger.ContextLogger -} - -func NewV2RayLegacyRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) adapter.ConnectionRouter { - return &V2RayLegacyRouter{router, logger} -} - -func (r *V2RayLegacyRouter) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - if metadata.Destination.Fqdn == vmess.MuxDestination.Fqdn { - r.logger.InfoContext(ctx, "inbound legacy multiplex connection") - return vmess.HandleMuxConnection(ctx, conn, adapter.NewRouteHandler(metadata, r.router, r.logger)) - } - return r.router.RouteConnection(ctx, conn, metadata) -} - -func (r *V2RayLegacyRouter) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return r.router.RoutePacketConnection(ctx, conn, metadata) -} diff --git a/common/networkquality/http.go b/common/networkquality/http.go new file mode 100644 index 0000000000..f9ff2a4a5b --- /dev/null +++ b/common/networkquality/http.go @@ -0,0 +1,142 @@ +package networkquality + +import ( + "context" + "fmt" + "net" + "net/http" + "strings" + + C "github.com/sagernet/sing-box/constant" + sBufio "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func FormatBitrate(bps int64) string { + switch { + case bps >= 1_000_000_000: + return fmt.Sprintf("%.1f Gbps", float64(bps)/1_000_000_000) + case bps >= 1_000_000: + return fmt.Sprintf("%.1f Mbps", float64(bps)/1_000_000) + case bps >= 1_000: + return fmt.Sprintf("%.1f Kbps", float64(bps)/1_000) + default: + return fmt.Sprintf("%d bps", bps) + } +} + +func NewHTTPClient(dialer N.Dialer) *http.Client { + transport := &http.Transport{ + ForceAttemptHTTP2: true, + TLSHandshakeTimeout: C.TCPTimeout, + } + if dialer != nil { + transport.DialContext = func(ctx context.Context, network string, addr string) (net.Conn, error) { + return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) + } + } + return &http.Client{Transport: transport} +} + +func baseTransportFromClient(client *http.Client) (*http.Transport, error) { + if client == nil { + return nil, E.New("http client is nil") + } + if client.Transport == nil { + return http.DefaultTransport.(*http.Transport).Clone(), nil + } + transport, ok := client.Transport.(*http.Transport) + if !ok { + return nil, E.New("http client transport must be *http.Transport") + } + return transport.Clone(), nil +} + +func newMeasurementClient( + baseClient *http.Client, + connectEndpoint string, + singleConnection bool, + disableKeepAlives bool, + readCounters []N.CountFunc, + writeCounters []N.CountFunc, +) (*http.Client, error) { + transport, err := baseTransportFromClient(baseClient) + if err != nil { + return nil, err + } + transport.DisableCompression = true + transport.DisableKeepAlives = disableKeepAlives + if singleConnection { + transport.MaxConnsPerHost = 1 + transport.MaxIdleConnsPerHost = 1 + transport.MaxIdleConns = 1 + } + + baseDialContext := transport.DialContext + if baseDialContext == nil { + dialer := &net.Dialer{} + baseDialContext = dialer.DialContext + } + transport.DialContext = func(ctx context.Context, network string, addr string) (net.Conn, error) { + dialAddr := addr + if connectEndpoint != "" { + dialAddr = rewriteDialAddress(addr, connectEndpoint) + } + conn, dialErr := baseDialContext(ctx, network, dialAddr) + if dialErr != nil { + return nil, dialErr + } + if len(readCounters) > 0 || len(writeCounters) > 0 { + return sBufio.NewCounterConn(conn, readCounters, writeCounters), nil + } + return conn, nil + } + + return &http.Client{ + Transport: transport, + CheckRedirect: baseClient.CheckRedirect, + Jar: baseClient.Jar, + Timeout: baseClient.Timeout, + }, nil +} + +type MeasurementClientFactory func( + connectEndpoint string, + singleConnection bool, + disableKeepAlives bool, + readCounters []N.CountFunc, + writeCounters []N.CountFunc, +) (*http.Client, error) + +func defaultMeasurementClientFactory(baseClient *http.Client) MeasurementClientFactory { + return func(connectEndpoint string, singleConnection, disableKeepAlives bool, readCounters, writeCounters []N.CountFunc) (*http.Client, error) { + return newMeasurementClient(baseClient, connectEndpoint, singleConnection, disableKeepAlives, readCounters, writeCounters) + } +} + +func NewOptionalHTTP3Factory(dialer N.Dialer, useHTTP3 bool) (MeasurementClientFactory, error) { + if !useHTTP3 { + return nil, nil + } + return NewHTTP3MeasurementClientFactory(dialer) +} + +func rewriteDialAddress(addr string, connectEndpoint string) string { + connectEndpoint = strings.TrimSpace(connectEndpoint) + host, port, err := net.SplitHostPort(addr) + if err != nil { + return addr + } + endpointHost, endpointPort, err := net.SplitHostPort(connectEndpoint) + if err == nil { + host = endpointHost + if endpointPort != "" { + port = endpointPort + } + } else if connectEndpoint != "" { + host = connectEndpoint + } + return net.JoinHostPort(host, port) +} diff --git a/common/networkquality/http3.go b/common/networkquality/http3.go new file mode 100644 index 0000000000..0e907821d2 --- /dev/null +++ b/common/networkquality/http3.go @@ -0,0 +1,55 @@ +//go:build with_quic + +package networkquality + +import ( + "context" + "crypto/tls" + "net" + "net/http" + + "github.com/sagernet/quic-go" + "github.com/sagernet/quic-go/http3" + sBufio "github.com/sagernet/sing/common/bufio" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func NewHTTP3MeasurementClientFactory(dialer N.Dialer) (MeasurementClientFactory, error) { + // singleConnection and disableKeepAlives are not applied: + // HTTP/3 multiplexes streams over a single QUIC connection by default. + return func(connectEndpoint string, _, _ bool, readCounters, writeCounters []N.CountFunc) (*http.Client, error) { + transport := &http3.Transport{ + Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) { + dialAddr := addr + if connectEndpoint != "" { + dialAddr = rewriteDialAddress(addr, connectEndpoint) + } + destination := M.ParseSocksaddr(dialAddr) + var udpConn net.Conn + var dialErr error + if dialer != nil { + udpConn, dialErr = dialer.DialContext(ctx, N.NetworkUDP, destination) + } else { + var netDialer net.Dialer + udpConn, dialErr = netDialer.DialContext(ctx, N.NetworkUDP, destination.String()) + } + if dialErr != nil { + return nil, dialErr + } + wrappedConn := udpConn + if len(readCounters) > 0 || len(writeCounters) > 0 { + wrappedConn = sBufio.NewCounterConn(udpConn, readCounters, writeCounters) + } + packetConn := sBufio.NewUnbindPacketConn(wrappedConn) + quicConn, dialErr := quic.DialEarly(ctx, packetConn, udpConn.RemoteAddr(), tlsCfg, cfg) + if dialErr != nil { + udpConn.Close() + return nil, dialErr + } + return quicConn, nil + }, + } + return &http.Client{Transport: transport}, nil + }, nil +} diff --git a/common/networkquality/http3_stub.go b/common/networkquality/http3_stub.go new file mode 100644 index 0000000000..632837e68d --- /dev/null +++ b/common/networkquality/http3_stub.go @@ -0,0 +1,12 @@ +//go:build !with_quic + +package networkquality + +import ( + C "github.com/sagernet/sing-box/constant" + N "github.com/sagernet/sing/common/network" +) + +func NewHTTP3MeasurementClientFactory(dialer N.Dialer) (MeasurementClientFactory, error) { + return nil, C.ErrQUICNotIncluded +} diff --git a/common/networkquality/networkquality.go b/common/networkquality/networkquality.go new file mode 100644 index 0000000000..3247705d9e --- /dev/null +++ b/common/networkquality/networkquality.go @@ -0,0 +1,1420 @@ +package networkquality + +import ( + "context" + "crypto/tls" + "encoding/json" + "io" + "math" + "math/rand" + "net/http" + "net/http/httptrace" + "net/url" + "slices" + "sort" + "strings" + "sync" + "sync/atomic" + "time" + + sBufio "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + N "github.com/sagernet/sing/common/network" +) + +const DefaultConfigURL = "https://mensura.cdn-apple.com/api/v1/gm/config" + +type Config struct { + Version int `json:"version"` + TestEndpoint string `json:"test_endpoint"` + URLs URLs `json:"urls"` +} + +type URLs struct { + SmallHTTPSDownloadURL string `json:"small_https_download_url"` + LargeHTTPSDownloadURL string `json:"large_https_download_url"` + HTTPSUploadURL string `json:"https_upload_url"` + SmallDownloadURL string `json:"small_download_url"` + LargeDownloadURL string `json:"large_download_url"` + UploadURL string `json:"upload_url"` +} + +func (u *URLs) smallDownloadURL() string { + if u.SmallHTTPSDownloadURL != "" { + return u.SmallHTTPSDownloadURL + } + return u.SmallDownloadURL +} + +func (u *URLs) largeDownloadURL() string { + if u.LargeHTTPSDownloadURL != "" { + return u.LargeHTTPSDownloadURL + } + return u.LargeDownloadURL +} + +func (u *URLs) uploadURL() string { + if u.HTTPSUploadURL != "" { + return u.HTTPSUploadURL + } + return u.UploadURL +} + +type Accuracy int32 + +const ( + AccuracyLow Accuracy = 0 + AccuracyMedium Accuracy = 1 + AccuracyHigh Accuracy = 2 +) + +func (a Accuracy) String() string { + switch a { + case AccuracyHigh: + return "High" + case AccuracyMedium: + return "Medium" + default: + return "Low" + } +} + +type Result struct { + DownloadCapacity int64 + UploadCapacity int64 + DownloadRPM int32 + UploadRPM int32 + IdleLatencyMs int32 + DownloadCapacityAccuracy Accuracy + UploadCapacityAccuracy Accuracy + DownloadRPMAccuracy Accuracy + UploadRPMAccuracy Accuracy +} + +type Progress struct { + Phase Phase + DownloadCapacity int64 + UploadCapacity int64 + DownloadRPM int32 + UploadRPM int32 + IdleLatencyMs int32 + ElapsedMs int64 + DownloadCapacityAccuracy Accuracy + UploadCapacityAccuracy Accuracy + DownloadRPMAccuracy Accuracy + UploadRPMAccuracy Accuracy +} + +type Phase int32 + +const ( + PhaseIdle Phase = 0 + PhaseDownload Phase = 1 + PhaseUpload Phase = 2 + PhaseDone Phase = 3 +) + +type Options struct { + ConfigURL string + HTTPClient *http.Client + NewMeasurementClient MeasurementClientFactory + Serial bool + MaxRuntime time.Duration + OnProgress func(Progress) + Context context.Context +} + +const DefaultMaxRuntime = 20 * time.Second + +type measurementSettings struct { + idleProbeCount int + testTimeout time.Duration + stabilityInterval time.Duration + sampleInterval time.Duration + progressInterval time.Duration + maxProbesPerSecond int + initialConnections int + maxConnections int + movingAvgDistance int + trimPercent int + stdDevTolerancePct float64 + maxProbeCapacityPct float64 +} + +var settings = measurementSettings{ + idleProbeCount: 5, + testTimeout: DefaultMaxRuntime, + stabilityInterval: time.Second, + sampleInterval: 250 * time.Millisecond, + progressInterval: 500 * time.Millisecond, + maxProbesPerSecond: 100, + initialConnections: 1, + maxConnections: 16, + movingAvgDistance: 4, + trimPercent: 5, + stdDevTolerancePct: 5, + maxProbeCapacityPct: 0.05, +} + +type resolvedConfig struct { + smallURL *url.URL + largeURL *url.URL + uploadURL *url.URL + connectEndpoint string +} + +type directionPlan struct { + dataURL *url.URL + probeURL *url.URL + connectEndpoint string + isUpload bool +} + +type probeTrace struct { + reused bool + connectStart time.Time + connectDone time.Time + tlsStart time.Time + tlsDone time.Time + tlsVersion uint16 + gotConn time.Time + wroteRequest time.Time + firstResponseByte time.Time +} + +type probeMeasurement struct { + total time.Duration + tcp time.Duration + tls time.Duration + httpFirst time.Duration + httpLoaded time.Duration + bytes int64 + reused bool +} + +type probeRound struct { + interval int + tcp time.Duration + tls time.Duration + httpFirst time.Duration + httpLoaded time.Duration +} + +func (p probeRound) responsivenessLatency() float64 { + var foreignSamples []float64 + if p.tcp > 0 { + foreignSamples = append(foreignSamples, durationMillis(p.tcp)) + } + if p.tls > 0 { + foreignSamples = append(foreignSamples, durationMillis(p.tls)) + } + if p.httpFirst > 0 { + foreignSamples = append(foreignSamples, durationMillis(p.httpFirst)) + } + if len(foreignSamples) == 0 || p.httpLoaded <= 0 { + return 0 + } + return (meanFloat64s(foreignSamples) + durationMillis(p.httpLoaded)) / 2 +} + +const maxConsecutiveErrors = 3 + +type loadConnection struct { + client *http.Client + dataURL *url.URL + isUpload bool + active atomic.Bool + ready atomic.Bool +} + +func (c *loadConnection) run(ctx context.Context, onError func(error)) { + defer closeMeasurementClient(c.client) + markActive := func() { + c.ready.Store(true) + c.active.Store(true) + } + var consecutiveErrors int + for { + select { + case <-ctx.Done(): + return + default: + } + var err error + if c.isUpload { + err = runUploadRequest(ctx, c.client, c.dataURL.String(), markActive) + } else { + err = runDownloadRequest(ctx, c.client, c.dataURL.String(), markActive) + } + c.active.Store(false) + if err != nil { + if ctx.Err() != nil { + return + } + consecutiveErrors++ + if consecutiveErrors > maxConsecutiveErrors { + onError(err) + return + } + c.client.CloseIdleConnections() + continue + } + consecutiveErrors = 0 + } +} + +type intervalThroughput struct { + interval int + bps float64 +} + +type intervalWindow struct { + lower int + upper int +} + +type stabilityTracker struct { + window int + stdDevTolerancePct float64 + instantaneous []float64 + movingAverages []float64 +} + +func (s *stabilityTracker) add(value float64) bool { + if value <= 0 || math.IsNaN(value) || math.IsInf(value, 0) { + return false + } + s.instantaneous = append(s.instantaneous, value) + if len(s.instantaneous) > s.window { + s.instantaneous = s.instantaneous[len(s.instantaneous)-s.window:] + } + s.movingAverages = append(s.movingAverages, meanFloat64s(s.instantaneous)) + if len(s.movingAverages) > s.window { + s.movingAverages = s.movingAverages[len(s.movingAverages)-s.window:] + } + return s.stable() +} + +func (s *stabilityTracker) ready() bool { + return len(s.movingAverages) >= s.window +} + +func (s *stabilityTracker) accuracy() Accuracy { + if s.stable() { + return AccuracyHigh + } + if s.ready() { + return AccuracyMedium + } + return AccuracyLow +} + +func (s *stabilityTracker) stable() bool { + if len(s.movingAverages) < s.window { + return false + } + currentAverage := s.movingAverages[len(s.movingAverages)-1] + if currentAverage <= 0 { + return false + } + return stdDevFloat64s(s.movingAverages) <= currentAverage*(s.stdDevTolerancePct/100) +} + +type directionMeasurement struct { + capacity int64 + rpm int32 + capacityAccuracy Accuracy + rpmAccuracy Accuracy +} + +type directionRunner struct { + factory MeasurementClientFactory + plan directionPlan + probeBytes int64 + + errCh chan error + errOnce sync.Once + wg sync.WaitGroup + + totalBytes atomic.Int64 + currentCapacity atomic.Int64 + currentRPM atomic.Int32 + currentInterval atomic.Int64 + + connMu sync.Mutex + connections []*loadConnection + + probeMu sync.Mutex + probeRounds []probeRound + intervalProbeValues []float64 + responsivenessWindow *intervalWindow + throughputs []intervalThroughput + throughputWindow *intervalWindow +} + +func newDirectionRunner(factory MeasurementClientFactory, plan directionPlan, probeBytes int64) *directionRunner { + return &directionRunner{ + factory: factory, + plan: plan, + probeBytes: probeBytes, + errCh: make(chan error, 1), + } +} + +func (r *directionRunner) fail(err error) { + if err == nil { + return + } + r.errOnce.Do(func() { + select { + case r.errCh <- err: + default: + } + }) +} + +func (r *directionRunner) onConnectionFailed(err error) { + r.connMu.Lock() + activeCount := 0 + for _, conn := range r.connections { + if conn.active.Load() { + activeCount++ + } + } + r.connMu.Unlock() + if activeCount == 0 { + r.fail(err) + } +} + +func (r *directionRunner) addConnection(ctx context.Context) error { + counter := N.CountFunc(func(n int64) { r.totalBytes.Add(n) }) + var readCounters, writeCounters []N.CountFunc + if r.plan.isUpload { + writeCounters = []N.CountFunc{counter} + } else { + readCounters = []N.CountFunc{counter} + } + client, err := r.factory(r.plan.connectEndpoint, true, false, readCounters, writeCounters) + if err != nil { + return err + } + conn := &loadConnection{ + client: client, + dataURL: r.plan.dataURL, + isUpload: r.plan.isUpload, + } + r.connMu.Lock() + r.connections = append(r.connections, conn) + r.connMu.Unlock() + r.wg.Add(1) + go func() { + defer r.wg.Done() + conn.run(ctx, r.onConnectionFailed) + }() + return nil +} + +func (r *directionRunner) connectionCount() int { + r.connMu.Lock() + defer r.connMu.Unlock() + return len(r.connections) +} + +func (r *directionRunner) pickReadyConnection() *loadConnection { + r.connMu.Lock() + defer r.connMu.Unlock() + var ready []*loadConnection + for _, conn := range r.connections { + if conn.ready.Load() && conn.active.Load() { + ready = append(ready, conn) + } + } + if len(ready) == 0 { + return nil + } + return ready[rand.Intn(len(ready))] +} + +func (r *directionRunner) startProber(ctx context.Context) { + r.wg.Add(1) + go func() { + defer r.wg.Done() + ticker := time.NewTicker(r.probeInterval()) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + } + conn := r.pickReadyConnection() + if conn == nil { + continue + } + r.runProbeRound(ctx, conn.client) + ticker.Reset(r.probeInterval()) + } + }() +} + +func (r *directionRunner) runProbeRound(ctx context.Context, selfClient *http.Client) { + foreignClient, err := r.factory(r.plan.connectEndpoint, true, true, nil, nil) + if err != nil { + return + } + defer closeMeasurementClient(foreignClient) + round, err := collectProbeRound(ctx, foreignClient, selfClient, r.plan.probeURL.String()) + if err != nil { + return + } + r.recordProbeRound(probeRound{ + interval: int(r.currentInterval.Load()), + tcp: round.tcp, + tls: round.tls, + httpFirst: round.httpFirst, + httpLoaded: round.httpLoaded, + }) +} + +func (r *directionRunner) probeInterval() time.Duration { + interval := time.Second / time.Duration(settings.maxProbesPerSecond) + capacity := r.currentCapacity.Load() + if capacity <= 0 || r.probeBytes <= 0 || settings.maxProbeCapacityPct <= 0 { + return interval + } + bitsPerRound := float64(r.probeBytes*2) * 8 + minSeconds := bitsPerRound / (float64(capacity) * settings.maxProbeCapacityPct) + if minSeconds <= 0 { + return interval + } + capacityInterval := time.Duration(minSeconds * float64(time.Second)) + if capacityInterval > interval { + interval = capacityInterval + } + return interval +} + +func (r *directionRunner) recordProbeRound(round probeRound) { + r.probeMu.Lock() + r.probeRounds = append(r.probeRounds, round) + if latency := round.responsivenessLatency(); latency > 0 { + r.intervalProbeValues = append(r.intervalProbeValues, latency) + } + r.currentRPM.Store(calculateRPM(r.probeRounds)) + r.probeMu.Unlock() +} + +func (r *directionRunner) swapIntervalProbeValues() []float64 { + r.probeMu.Lock() + defer r.probeMu.Unlock() + values := append([]float64(nil), r.intervalProbeValues...) + r.intervalProbeValues = nil + return values +} + +func (r *directionRunner) setResponsivenessWindow(currentInterval int) { + lower := max(currentInterval-settings.movingAvgDistance+1, 0) + r.probeMu.Lock() + r.responsivenessWindow = &intervalWindow{lower: lower, upper: currentInterval} + r.probeMu.Unlock() +} + +func (r *directionRunner) recordThroughput(interval int, bps float64) { + r.probeMu.Lock() + r.throughputs = append(r.throughputs, intervalThroughput{interval: interval, bps: bps}) + r.probeMu.Unlock() +} + +func (r *directionRunner) setThroughputWindow(currentInterval int) { + lower := max(currentInterval-settings.movingAvgDistance+1, 0) + r.probeMu.Lock() + r.throughputWindow = &intervalWindow{lower: lower, upper: currentInterval} + r.probeMu.Unlock() +} + +func (r *directionRunner) finalRPM() int32 { + r.probeMu.Lock() + defer r.probeMu.Unlock() + if r.responsivenessWindow == nil { + return calculateRPM(r.probeRounds) + } + var rounds []probeRound + for _, round := range r.probeRounds { + if round.interval >= r.responsivenessWindow.lower && round.interval <= r.responsivenessWindow.upper { + rounds = append(rounds, round) + } + } + if len(rounds) == 0 { + rounds = r.probeRounds + } + return calculateRPM(rounds) +} + +func (r *directionRunner) finalCapacity(totalDuration time.Duration) int64 { + r.probeMu.Lock() + defer r.probeMu.Unlock() + var samples []float64 + if r.throughputWindow != nil { + for _, sample := range r.throughputs { + if sample.interval >= r.throughputWindow.lower && sample.interval <= r.throughputWindow.upper { + samples = append(samples, sample.bps) + } + } + } + if len(samples) == 0 { + for _, sample := range r.throughputs { + samples = append(samples, sample.bps) + } + } + if len(samples) > 0 { + return int64(math.Round(upperTrimmedMean(samples, settings.trimPercent))) + } + if totalDuration > 0 { + return int64(float64(r.totalBytes.Load()) * 8 / totalDuration.Seconds()) + } + return 0 +} + +func (r *directionRunner) wait() { + r.wg.Wait() +} + +func Run(options Options) (*Result, error) { + ctx := options.Context + if ctx == nil { + ctx = context.Background() + } + if options.HTTPClient == nil { + return nil, E.New("http client is required") + } + maxRuntime, err := normalizeMaxRuntime(options.MaxRuntime) + if err != nil { + return nil, err + } + configURL := resolveConfigURL(options.ConfigURL) + config, err := fetchConfig(ctx, options.HTTPClient, configURL) + if err != nil { + return nil, E.Cause(err, "fetch config") + } + resolved, err := validateConfig(config) + if err != nil { + return nil, E.Cause(err, "validate config") + } + + start := time.Now() + report := func(progress Progress) { + if options.OnProgress == nil { + return + } + progress.ElapsedMs = time.Since(start).Milliseconds() + options.OnProgress(progress) + } + + factory := options.NewMeasurementClient + if factory == nil { + factory = defaultMeasurementClientFactory(options.HTTPClient) + } + + report(Progress{Phase: PhaseIdle}) + idleLatency, probeBytes, err := measureIdleLatency(ctx, factory, resolved) + if err != nil { + return nil, E.Cause(err, "measure idle latency") + } + report(Progress{Phase: PhaseIdle, IdleLatencyMs: idleLatency}) + + start = time.Now() + + var download, upload *directionMeasurement + if options.Serial { + download, upload, err = measureSerial( + ctx, + factory, + resolved, + idleLatency, + probeBytes, + maxRuntime, + report, + ) + } else { + download, upload, err = measureParallel( + ctx, + factory, + resolved, + idleLatency, + probeBytes, + maxRuntime, + report, + ) + } + if err != nil { + return nil, err + } + + result := &Result{ + DownloadCapacity: download.capacity, + UploadCapacity: upload.capacity, + DownloadRPM: download.rpm, + UploadRPM: upload.rpm, + IdleLatencyMs: idleLatency, + DownloadCapacityAccuracy: download.capacityAccuracy, + UploadCapacityAccuracy: upload.capacityAccuracy, + DownloadRPMAccuracy: download.rpmAccuracy, + UploadRPMAccuracy: upload.rpmAccuracy, + } + report(Progress{ + Phase: PhaseDone, + DownloadCapacity: result.DownloadCapacity, + UploadCapacity: result.UploadCapacity, + DownloadRPM: result.DownloadRPM, + UploadRPM: result.UploadRPM, + IdleLatencyMs: result.IdleLatencyMs, + DownloadCapacityAccuracy: result.DownloadCapacityAccuracy, + UploadCapacityAccuracy: result.UploadCapacityAccuracy, + DownloadRPMAccuracy: result.DownloadRPMAccuracy, + UploadRPMAccuracy: result.UploadRPMAccuracy, + }) + return result, nil +} + +func normalizeMaxRuntime(maxRuntime time.Duration) (time.Duration, error) { + if maxRuntime == 0 { + return settings.testTimeout, nil + } + if maxRuntime < 0 { + return 0, E.New("max runtime must be positive") + } + return maxRuntime, nil +} + +func measureSerial( + ctx context.Context, + factory MeasurementClientFactory, + resolved *resolvedConfig, + idleLatency int32, + probeBytes int64, + maxRuntime time.Duration, + report func(Progress), +) (*directionMeasurement, *directionMeasurement, error) { + downloadRuntime, uploadRuntime := splitRuntimeBudget(maxRuntime, 2) + report(Progress{Phase: PhaseDownload, IdleLatencyMs: idleLatency}) + download, err := measureDirection(ctx, factory, directionPlan{ + dataURL: resolved.largeURL, + probeURL: resolved.smallURL, + connectEndpoint: resolved.connectEndpoint, + }, probeBytes, downloadRuntime, func(capacity int64, rpm int32) { + report(Progress{ + Phase: PhaseDownload, + DownloadCapacity: capacity, + DownloadRPM: rpm, + IdleLatencyMs: idleLatency, + }) + }) + if err != nil { + return nil, nil, E.Cause(err, "measure download") + } + + report(Progress{ + Phase: PhaseUpload, + DownloadCapacity: download.capacity, + DownloadRPM: download.rpm, + IdleLatencyMs: idleLatency, + }) + upload, err := measureDirection(ctx, factory, directionPlan{ + dataURL: resolved.uploadURL, + probeURL: resolved.smallURL, + connectEndpoint: resolved.connectEndpoint, + isUpload: true, + }, probeBytes, uploadRuntime, func(capacity int64, rpm int32) { + report(Progress{ + Phase: PhaseUpload, + DownloadCapacity: download.capacity, + UploadCapacity: capacity, + DownloadRPM: download.rpm, + UploadRPM: rpm, + IdleLatencyMs: idleLatency, + }) + }) + if err != nil { + return nil, nil, E.Cause(err, "measure upload") + } + return download, upload, nil +} + +func measureParallel( + ctx context.Context, + factory MeasurementClientFactory, + resolved *resolvedConfig, + idleLatency int32, + probeBytes int64, + maxRuntime time.Duration, + report func(Progress), +) (*directionMeasurement, *directionMeasurement, error) { + type parallelResult struct { + measurement *directionMeasurement + err error + } + type progressState struct { + sync.Mutex + downloadCapacity int64 + uploadCapacity int64 + downloadRPM int32 + uploadRPM int32 + } + + parallelCtx, cancel := context.WithCancel(ctx) + defer cancel() + + report(Progress{Phase: PhaseDownload, IdleLatencyMs: idleLatency}) + report(Progress{Phase: PhaseUpload, IdleLatencyMs: idleLatency}) + + var state progressState + sendProgress := func(phase Phase, capacity int64, rpm int32) { + state.Lock() + if phase == PhaseDownload { + state.downloadCapacity = capacity + state.downloadRPM = rpm + } else { + state.uploadCapacity = capacity + state.uploadRPM = rpm + } + snapshot := Progress{ + Phase: phase, + DownloadCapacity: state.downloadCapacity, + UploadCapacity: state.uploadCapacity, + DownloadRPM: state.downloadRPM, + UploadRPM: state.uploadRPM, + IdleLatencyMs: idleLatency, + } + state.Unlock() + report(snapshot) + } + + var wg sync.WaitGroup + downloadCh := make(chan parallelResult, 1) + uploadCh := make(chan parallelResult, 1) + wg.Add(2) + go func() { + defer wg.Done() + measurement, err := measureDirection(parallelCtx, factory, directionPlan{ + dataURL: resolved.largeURL, + probeURL: resolved.smallURL, + connectEndpoint: resolved.connectEndpoint, + }, probeBytes, maxRuntime, func(capacity int64, rpm int32) { + sendProgress(PhaseDownload, capacity, rpm) + }) + if err != nil { + cancel() + downloadCh <- parallelResult{err: E.Cause(err, "measure download")} + return + } + downloadCh <- parallelResult{measurement: measurement} + }() + go func() { + defer wg.Done() + measurement, err := measureDirection(parallelCtx, factory, directionPlan{ + dataURL: resolved.uploadURL, + probeURL: resolved.smallURL, + connectEndpoint: resolved.connectEndpoint, + isUpload: true, + }, probeBytes, maxRuntime, func(capacity int64, rpm int32) { + sendProgress(PhaseUpload, capacity, rpm) + }) + if err != nil { + cancel() + uploadCh <- parallelResult{err: E.Cause(err, "measure upload")} + return + } + uploadCh <- parallelResult{measurement: measurement} + }() + + download := <-downloadCh + upload := <-uploadCh + wg.Wait() + if download.err != nil { + return nil, nil, download.err + } + if upload.err != nil { + return nil, nil, upload.err + } + return download.measurement, upload.measurement, nil +} + +func resolveConfigURL(rawURL string) string { + rawURL = strings.TrimSpace(rawURL) + if rawURL == "" { + return DefaultConfigURL + } + if !strings.Contains(rawURL, "://") && !strings.Contains(rawURL, "/") { + return "https://" + rawURL + "/.well-known/nq" + } + parsedURL, err := url.Parse(rawURL) + if err != nil { + return rawURL + } + if parsedURL.Scheme != "" && parsedURL.Host != "" && (parsedURL.Path == "" || parsedURL.Path == "/") { + parsedURL.Path = "/.well-known/nq" + return parsedURL.String() + } + return rawURL +} + +func fetchConfig(ctx context.Context, client *http.Client, configURL string) (*Config, error) { + req, err := newRequest(ctx, http.MethodGet, configURL, nil) + if err != nil { + return nil, err + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if err = validateResponse(resp); err != nil { + return nil, err + } + var config Config + if err = json.NewDecoder(resp.Body).Decode(&config); err != nil { + return nil, E.Cause(err, "decode config") + } + return &config, nil +} + +func validateConfig(config *Config) (*resolvedConfig, error) { + if config == nil { + return nil, E.New("config is nil") + } + if config.Version != 1 { + return nil, E.New("unsupported config version: ", config.Version) + } + parseURL := func(name string, rawURL string) (*url.URL, error) { + if rawURL == "" { + return nil, E.New("config missing required URL: ", name) + } + parsedURL, err := url.Parse(rawURL) + if err != nil { + return nil, E.Cause(err, "parse "+name) + } + if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" { + return nil, E.New("unsupported URL scheme in ", name, ": ", parsedURL.Scheme) + } + if parsedURL.Host == "" { + return nil, E.New("config missing host in ", name) + } + return parsedURL, nil + } + + smallURL, err := parseURL("small_download_url", config.URLs.smallDownloadURL()) + if err != nil { + return nil, err + } + largeURL, err := parseURL("large_download_url", config.URLs.largeDownloadURL()) + if err != nil { + return nil, err + } + uploadURL, err := parseURL("upload_url", config.URLs.uploadURL()) + if err != nil { + return nil, err + } + + if smallURL.Host != largeURL.Host || smallURL.Host != uploadURL.Host { + return nil, E.New("config URLs must use the same host") + } + + return &resolvedConfig{ + smallURL: smallURL, + largeURL: largeURL, + uploadURL: uploadURL, + connectEndpoint: strings.TrimSpace(config.TestEndpoint), + }, nil +} + +func measureIdleLatency(ctx context.Context, factory MeasurementClientFactory, config *resolvedConfig) (int32, int64, error) { + var latencies []int64 + var maxProbeBytes int64 + for i := 0; i < settings.idleProbeCount; i++ { + select { + case <-ctx.Done(): + return 0, 0, ctx.Err() + default: + } + client, err := factory(config.connectEndpoint, true, true, nil, nil) + if err != nil { + return 0, 0, err + } + measurement, err := runProbe(ctx, client, config.smallURL.String(), false) + closeMeasurementClient(client) + if err != nil { + return 0, 0, err + } + latencies = append(latencies, measurement.total.Milliseconds()) + if measurement.bytes > maxProbeBytes { + maxProbeBytes = measurement.bytes + } + } + slices.Sort(latencies) + return int32(latencies[len(latencies)/2]), maxProbeBytes, nil +} + +func measureDirection( + ctx context.Context, + factory MeasurementClientFactory, + plan directionPlan, + probeBytes int64, + maxRuntime time.Duration, + onProgress func(capacity int64, rpm int32), +) (*directionMeasurement, error) { + phaseCtx, phaseCancel := context.WithTimeout(ctx, maxRuntime) + defer phaseCancel() + + runner := newDirectionRunner(factory, plan, probeBytes) + defer runner.wait() + + for i := 0; i < settings.initialConnections; i++ { + err := runner.addConnection(phaseCtx) + if err != nil { + return nil, err + } + } + + runner.startProber(phaseCtx) + + throughputTracker := stabilityTracker{ + window: settings.movingAvgDistance, + stdDevTolerancePct: settings.stdDevTolerancePct, + } + responsivenessTracker := stabilityTracker{ + window: settings.movingAvgDistance, + stdDevTolerancePct: settings.stdDevTolerancePct, + } + + start := time.Now() + sampleTicker := time.NewTicker(settings.sampleInterval) + defer sampleTicker.Stop() + intervalTicker := time.NewTicker(settings.stabilityInterval) + defer intervalTicker.Stop() + progressTicker := time.NewTicker(settings.progressInterval) + defer progressTicker.Stop() + + prevSampleBytes := int64(0) + prevSampleTime := start + prevIntervalBytes := int64(0) + prevIntervalTime := start + var ewmaCapacity float64 + var goodputSaturated bool + var intervalIndex int + + for { + select { + case err := <-runner.errCh: + return nil, err + case now := <-sampleTicker.C: + currentBytes := runner.totalBytes.Load() + elapsed := now.Sub(prevSampleTime).Seconds() + if elapsed > 0 { + instantaneousBps := float64(currentBytes-prevSampleBytes) * 8 / elapsed + if ewmaCapacity == 0 { + ewmaCapacity = instantaneousBps + } else { + ewmaCapacity = 0.3*instantaneousBps + 0.7*ewmaCapacity + } + runner.currentCapacity.Store(int64(ewmaCapacity)) + } + prevSampleBytes = currentBytes + prevSampleTime = now + case <-intervalTicker.C: + now := time.Now() + currentBytes := runner.totalBytes.Load() + elapsed := now.Sub(prevIntervalTime).Seconds() + if elapsed > 0 { + intervalBps := float64(currentBytes-prevIntervalBytes) * 8 / elapsed + runner.recordThroughput(intervalIndex, intervalBps) + throughputStable := throughputTracker.add(intervalBps) + if throughputStable && runner.throughputWindow == nil { + runner.setThroughputWindow(intervalIndex) + } + if !goodputSaturated && (throughputStable || (runner.connectionCount() >= settings.maxConnections && throughputTracker.ready())) { + goodputSaturated = true + } + if runner.connectionCount() < settings.maxConnections { + err := runner.addConnection(phaseCtx) + if err != nil { + return nil, err + } + } + } + if goodputSaturated { + if values := runner.swapIntervalProbeValues(); len(values) > 0 { + if responsivenessTracker.add(upperTrimmedMean(values, settings.trimPercent)) && runner.responsivenessWindow == nil { + runner.setResponsivenessWindow(intervalIndex) + phaseCancel() + } + } + } + prevIntervalBytes = currentBytes + prevIntervalTime = now + intervalIndex++ + runner.currentInterval.Store(int64(intervalIndex)) + case <-progressTicker.C: + if onProgress != nil { + onProgress(int64(ewmaCapacity), runner.currentRPM.Load()) + } + case <-phaseCtx.Done(): + if ctx.Err() != nil { + return nil, ctx.Err() + } + totalDuration := time.Since(start) + return &directionMeasurement{ + capacity: runner.finalCapacity(totalDuration), + rpm: runner.finalRPM(), + capacityAccuracy: throughputTracker.accuracy(), + rpmAccuracy: responsivenessTracker.accuracy(), + }, nil + } + } +} + +func splitRuntimeBudget(total time.Duration, directions int) (time.Duration, time.Duration) { + if directions <= 1 { + return total, total + } + first := total / time.Duration(directions) + second := total - first + return first, second +} + +func collectProbeRound(ctx context.Context, foreignClient *http.Client, selfClient *http.Client, rawURL string) (probeMeasurement, error) { + var foreignResult probeMeasurement + var selfResult probeMeasurement + var foreignErr error + var selfErr error + + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + foreignResult, foreignErr = runProbe(ctx, foreignClient, rawURL, false) + }() + go func() { + defer wg.Done() + selfResult, selfErr = runProbe(ctx, selfClient, rawURL, true) + }() + wg.Wait() + + if foreignErr != nil { + return probeMeasurement{}, E.Cause(foreignErr, "foreign probe") + } + if selfErr != nil { + return probeMeasurement{}, E.Cause(selfErr, "self probe") + } + return probeMeasurement{ + tcp: foreignResult.tcp, + tls: foreignResult.tls, + httpFirst: foreignResult.httpFirst, + httpLoaded: selfResult.httpLoaded, + }, nil +} + +func runProbe(ctx context.Context, client *http.Client, rawURL string, expectReuse bool) (probeMeasurement, error) { + var trace probeTrace + start := time.Now() + req, err := newRequest(httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{ + ConnectStart: func(string, string) { + if trace.connectStart.IsZero() { + trace.connectStart = time.Now() + } + }, + ConnectDone: func(string, string, error) { + if trace.connectDone.IsZero() { + trace.connectDone = time.Now() + } + }, + TLSHandshakeStart: func() { + if trace.tlsStart.IsZero() { + trace.tlsStart = time.Now() + } + }, + TLSHandshakeDone: func(state tls.ConnectionState, _ error) { + if trace.tlsDone.IsZero() { + trace.tlsDone = time.Now() + trace.tlsVersion = state.Version + } + }, + GotConn: func(info httptrace.GotConnInfo) { + trace.reused = info.Reused + trace.gotConn = time.Now() + }, + WroteRequest: func(httptrace.WroteRequestInfo) { + trace.wroteRequest = time.Now() + }, + GotFirstResponseByte: func() { + trace.firstResponseByte = time.Now() + }, + }), http.MethodGet, rawURL, nil) + if err != nil { + return probeMeasurement{}, err + } + if !expectReuse { + req.Close = true + } + resp, err := client.Do(req) + if err != nil { + return probeMeasurement{}, err + } + defer resp.Body.Close() + if err = validateResponse(resp); err != nil { + return probeMeasurement{}, err + } + n, err := io.Copy(io.Discard, resp.Body) + end := time.Now() + if err != nil { + return probeMeasurement{}, err + } + if expectReuse && !trace.reused { + return probeMeasurement{}, E.New("self probe did not reuse an existing connection") + } + + httpStart := trace.wroteRequest + if httpStart.IsZero() { + switch { + case !trace.tlsDone.IsZero(): + httpStart = trace.tlsDone + case !trace.connectDone.IsZero(): + httpStart = trace.connectDone + case !trace.gotConn.IsZero(): + httpStart = trace.gotConn + default: + httpStart = start + } + } + + measurement := probeMeasurement{ + total: end.Sub(start), + bytes: n, + reused: trace.reused, + } + if !trace.connectStart.IsZero() && !trace.connectDone.IsZero() && trace.connectDone.After(trace.connectStart) { + measurement.tcp = trace.connectDone.Sub(trace.connectStart) + } + if !trace.tlsStart.IsZero() && !trace.tlsDone.IsZero() && trace.tlsDone.After(trace.tlsStart) { + measurement.tls = trace.tlsDone.Sub(trace.tlsStart) + if roundTrips := tlsHandshakeRoundTrips(trace.tlsVersion); roundTrips > 1 { + measurement.tls /= time.Duration(roundTrips) + } + } + if !trace.firstResponseByte.IsZero() && trace.firstResponseByte.After(httpStart) { + measurement.httpFirst = trace.firstResponseByte.Sub(httpStart) + } + if end.After(httpStart) { + measurement.httpLoaded = end.Sub(httpStart) + } + return measurement, nil +} + +func runDownloadRequest(ctx context.Context, client *http.Client, rawURL string, onActive func()) error { + req, err := newRequest(ctx, http.MethodGet, rawURL, nil) + if err != nil { + return err + } + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + err = validateResponse(resp) + if err != nil { + return err + } + if onActive != nil { + onActive() + } + _, err = sBufio.Copy(io.Discard, resp.Body) + if ctx.Err() != nil { + return nil + } + return err +} + +func runUploadRequest(ctx context.Context, client *http.Client, rawURL string, onActive func()) error { + body := &uploadBody{ + ctx: ctx, + onActive: onActive, + } + req, err := newRequest(ctx, http.MethodPost, rawURL, body) + if err != nil { + return err + } + req.ContentLength = -1 + req.Header.Set("Content-Type", "application/octet-stream") + resp, err := client.Do(req) + if err != nil { + if ctx.Err() != nil { + return nil + } + return err + } + defer resp.Body.Close() + err = validateResponse(resp) + if err != nil { + return err + } + _, _ = io.Copy(io.Discard, resp.Body) + <-ctx.Done() + return nil +} + +func newRequest(ctx context.Context, method string, rawURL string, body io.Reader) (*http.Request, error) { + req, err := http.NewRequestWithContext(ctx, method, rawURL, body) + if err != nil { + return nil, err + } + req.Header.Set("Accept-Encoding", "identity") + return req, nil +} + +func closeMeasurementClient(client *http.Client) { + if client == nil { + return + } + client.CloseIdleConnections() + if closer, ok := client.Transport.(interface{ Close() error }); ok { + _ = closer.Close() + } +} + +func validateResponse(resp *http.Response) error { + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return E.New("unexpected status: ", resp.Status) + } + if encoding := resp.Header.Get("Content-Encoding"); encoding != "" { + return E.New("unexpected content encoding: ", encoding) + } + return nil +} + +func calculateRPM(rounds []probeRound) int32 { + if len(rounds) == 0 { + return 0 + } + var tcpSamples []float64 + var tlsSamples []float64 + var httpFirstSamples []float64 + var httpLoadedSamples []float64 + for _, round := range rounds { + if round.tcp > 0 { + tcpSamples = append(tcpSamples, durationMillis(round.tcp)) + } + if round.tls > 0 { + tlsSamples = append(tlsSamples, durationMillis(round.tls)) + } + if round.httpFirst > 0 { + httpFirstSamples = append(httpFirstSamples, durationMillis(round.httpFirst)) + } + if round.httpLoaded > 0 { + httpLoadedSamples = append(httpLoadedSamples, durationMillis(round.httpLoaded)) + } + } + httpLoaded := upperTrimmedMean(httpLoadedSamples, settings.trimPercent) + if httpLoaded <= 0 { + return 0 + } + var foreignComponents []float64 + if tcp := upperTrimmedMean(tcpSamples, settings.trimPercent); tcp > 0 { + foreignComponents = append(foreignComponents, tcp) + } + if tls := upperTrimmedMean(tlsSamples, settings.trimPercent); tls > 0 { + foreignComponents = append(foreignComponents, tls) + } + if httpFirst := upperTrimmedMean(httpFirstSamples, settings.trimPercent); httpFirst > 0 { + foreignComponents = append(foreignComponents, httpFirst) + } + if len(foreignComponents) == 0 { + return 0 + } + foreignLatency := meanFloat64s(foreignComponents) + foreignRPM := 60000.0 / foreignLatency + loadedRPM := 60000.0 / httpLoaded + return int32(math.Round((foreignRPM + loadedRPM) / 2)) +} + +func tlsHandshakeRoundTrips(version uint16) int { + switch version { + case tls.VersionTLS12, tls.VersionTLS11, tls.VersionTLS10: + return 2 + default: + return 1 + } +} + +func durationMillis(value time.Duration) float64 { + return float64(value) / float64(time.Millisecond) +} + +func upperTrimmedMean(values []float64, trimPercent int) float64 { + trimmed := upperTrimFloat64s(values, trimPercent) + if len(trimmed) == 0 { + return 0 + } + return meanFloat64s(trimmed) +} + +func upperTrimFloat64s(values []float64, trimPercent int) []float64 { + if len(values) == 0 { + return nil + } + trimmed := append([]float64(nil), values...) + sort.Float64s(trimmed) + if trimPercent <= 0 { + return trimmed + } + trimCount := int(math.Floor(float64(len(trimmed)) * float64(trimPercent) / 100)) + if trimCount <= 0 || trimCount >= len(trimmed) { + return trimmed + } + return trimmed[:len(trimmed)-trimCount] +} + +func meanFloat64s(values []float64) float64 { + if len(values) == 0 { + return 0 + } + var total float64 + for _, value := range values { + total += value + } + return total / float64(len(values)) +} + +func stdDevFloat64s(values []float64) float64 { + if len(values) == 0 { + return 0 + } + mean := meanFloat64s(values) + var total float64 + for _, value := range values { + delta := value - mean + total += delta * delta + } + return math.Sqrt(total / float64(len(values))) +} + +type uploadBody struct { + ctx context.Context + activated atomic.Bool + onActive func() +} + +func (u *uploadBody) Read(p []byte) (int, error) { + if err := u.ctx.Err(); err != nil { + return 0, err + } + clear(p) + n := len(p) + if n > 0 && u.onActive != nil && u.activated.CompareAndSwap(false, true) { + u.onActive() + } + return n, nil +} + +func (u *uploadBody) Close() error { + return nil +} diff --git a/common/process/searcher.go b/common/process/searcher.go index cee81068ca..64305237ae 100644 --- a/common/process/searcher.go +++ b/common/process/searcher.go @@ -5,6 +5,7 @@ import ( "net/netip" "os/user" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-tun" E "github.com/sagernet/sing/common/exceptions" @@ -12,7 +13,8 @@ import ( ) type Searcher interface { - FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) + FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) + Close() error } var ErrNotFound = E.New("process not found") @@ -22,22 +24,15 @@ type Config struct { PackageManager tun.PackageManager } -type Info struct { - ProcessPath string - PackageName string - User string - UserId int32 -} - -func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) { +func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) { info, err := searcher.FindProcessInfo(ctx, network, source, destination) if err != nil { return nil, err } - if info.UserId != -1 { + if info.UserId != -1 && info.UserName == "" { osUser, _ := user.LookupId(F.ToString(info.UserId)) if osUser != nil { - info.User = osUser.Username + info.UserName = osUser.Username } } return info, nil diff --git a/common/process/searcher_android.go b/common/process/searcher_android.go index e1835b476c..e634774488 100644 --- a/common/process/searcher_android.go +++ b/common/process/searcher_android.go @@ -4,7 +4,10 @@ import ( "context" "net/netip" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" ) var _ Searcher = (*androidSearcher)(nil) @@ -14,25 +17,36 @@ type androidSearcher struct { } func NewSearcher(config Config) (Searcher, error) { + if config.PackageManager == nil { + return nil, E.New("missing package manager") + } return &androidSearcher{config.PackageManager}, nil } -func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) { - _, uid, err := resolveSocketByNetlink(network, source, destination) +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) + 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 &Info{ - UserId: int32(uid), - PackageName: 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 &Info{ - UserId: int32(uid), - PackageName: packageName, - }, nil + if packages, loaded := s.packageManager.PackagesByID(appID); loaded { + packageNames = append(packageNames, packages...) } - return &Info{UserId: int32(uid)}, nil + packageNames = common.Uniq(packageNames) + return &adapter.ConnectionOwner{ + UserId: int32(uid), + AndroidPackageNames: packageNames, + }, nil } diff --git a/common/process/searcher_darwin.go b/common/process/searcher_darwin.go index 4344c7061b..1b5c0dd6ca 100644 --- a/common/process/searcher_darwin.go +++ b/common/process/searcher_darwin.go @@ -1,18 +1,15 @@ +//go:build darwin + package process import ( "context" - "encoding/binary" "net/netip" - "os" "strconv" "strings" "syscall" - "unsafe" - - N "github.com/sagernet/sing/common/network" - "golang.org/x/sys/unix" + "github.com/sagernet/sing-box/adapter" ) var _ Searcher = (*darwinSearcher)(nil) @@ -23,12 +20,12 @@ func NewSearcher(_ Config) (Searcher, error) { return &darwinSearcher{}, nil } -func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) { - processName, err := findProcessName(network, source.Addr(), int(source.Port())) - if err != nil { - return nil, err - } - return &Info{ProcessPath: processName, UserId: -1}, 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) } var structSize = func() int { @@ -46,95 +43,3 @@ 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 - } - // 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 - switch { - case flag&0x1 > 0 && isIPv4: - // ipv4 - srcIP = netip.AddrFrom4(*(*[4]byte)(buf[inp+76 : inp+80])) - case flag&0x2 > 0 && !isIPv4: - // ipv6 - srcIP = netip.AddrFrom16(*(*[16]byte)(buf[inp+64 : inp+80])) - default: - continue - } - - if ip != srcIP { - continue - } - - // xsocket_n.so_last_pid - pid := readNativeUint32(buf[so+68 : so+72]) - return getExecPathFromPID(pid) - } - - 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])) -} diff --git a/common/process/searcher_darwin_shared.go b/common/process/searcher_darwin_shared.go new file mode 100644 index 0000000000..0557ae676a --- /dev/null +++ b/common/process/searcher_darwin_shared.go @@ -0,0 +1,269 @@ +//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 +} diff --git a/common/process/searcher_linux.go b/common/process/searcher_linux.go index 39470205a4..9b1a916038 100644 --- a/common/process/searcher_linux.go +++ b/common/process/searcher_linux.go @@ -4,32 +4,82 @@ 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 + logger log.ContextLogger + diagConns [4]*socketDiagConn + processPathCache *uidProcessPathCache } func NewSearcher(config Config) (Searcher, error) { - return &linuxSearcher{config.Logger}, nil + 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...) } -func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) { - inode, uid, err := resolveSocketByNetlink(network, source, destination) +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) if err != nil { return nil, err } - processPath, err := resolveProcessNameByProcSearch(inode, uid) + processInfo := &adapter.ConnectionOwner{ + UserId: int32(uid), + } + processPath, err := s.processPathCache.findProcessPath(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 &Info{ - UserId: int32(uid), - ProcessPath: processPath, - }, nil + return querySocketDiagOnce(family, protocol, source) } diff --git a/common/process/searcher_linux_shared.go b/common/process/searcher_linux_shared.go index e75b0b4f9d..9e868f36ae 100644 --- a/common/process/searcher_linux_shared.go +++ b/common/process/searcher_linux_shared.go @@ -1,45 +1,70 @@ //go:build linux +//nolint:unused package process import ( - "bytes" "encoding/binary" - "fmt" - "net" + "errors" "net/netip" "os" - "path" + "path/filepath" "strings" + "sync" "syscall" + "time" "unicode" - "unsafe" - "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common" 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 ( - sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48 - socketDiagByFamily = 20 - pathProc = "/proc" + sizeOfSocketDiagRequestData = 56 + sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + sizeOfSocketDiagRequestData + socketDiagResponseMinSize = 72 + socketDiagByFamily = 20 + pathProc = "/proc" ) -func resolveSocketByNetlink(network string, source netip.AddrPort, destination netip.AddrPort) (inode, uid uint32, err error) { - var family uint8 - var protocol uint8 +type socketDiagConn struct { + access sync.Mutex + family uint8 + protocol uint8 + fd int +} + +type uidProcessPathCache struct { + cache freelru.Cache[uint32, *uidProcessPaths] +} + +type uidProcessPaths struct { + entries map[uint32]string +} + +func newSocketDiagConn(family, protocol uint8) *socketDiagConn { + return &socketDiagConn{ + family: family, + protocol: protocol, + fd: -1, + } +} + +func socketDiagConnIndex(family, protocol uint8) int { + index := 0 + if protocol == syscall.IPPROTO_UDP { + index += 2 + } + if family == syscall.AF_INET6 { + index++ + } + return index +} +func socketDiagSettings(network string, source netip.AddrPort) (family, protocol uint8, err error) { switch network { case N.NetworkTCP: protocol = syscall.IPPROTO_TCP @@ -48,151 +73,308 @@ func resolveSocketByNetlink(network string, source netip.AddrPort, destination n default: return 0, 0, os.ErrInvalid } - - if source.Addr().Is4() { + switch { + case source.Addr().Is4(): family = syscall.AF_INET - } else { + case source.Addr().Is6(): family = syscall.AF_INET6 + default: + return 0, 0, os.ErrInvalid } + return family, protocol, nil +} - req := packSocketDiagRequest(family, protocol, source) +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} +} - socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG) +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 0, 0, E.Cause(err, "dial netlink") + 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") } - defer syscall.Close(socket) + return processPath, 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 (c *socketDiagConn) Close() error { + c.access.Lock() + defer c.access.Unlock() + return c.closeLocked() +} - err = syscall.Connect(socket, &syscall.SockaddrNetlink{ - Family: syscall.AF_NETLINK, - Pad: 0, - Pid: 0, - Groups: 0, - }) - if err != nil { - return +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 +} - _, err = syscall.Write(socket, req) +func querySocketDiagOnce(family, protocol uint8, source netip.AddrPort) (inode, uid uint32, err error) { + fd, err := openSocketDiag() if err != nil { - return 0, 0, E.Cause(err, "write netlink request") + return 0, 0, E.Cause(err, "dial netlink") } + defer syscall.Close(fd) + return querySocketDiag(fd, packSocketDiagRequest(family, protocol, source, netip.AddrPort{}, true)) +} - buffer := buf.New() - defer buffer.Release() - - n, err := syscall.Read(socket, buffer.FreeBytes()) +func (c *socketDiagConn) ensureOpenLocked() error { + if c.fd != -1 { + return nil + } + fd, err := openSocketDiag() if err != nil { - return 0, 0, E.Cause(err, "read netlink response") + return err } + c.fd = fd + return nil +} - buffer.Truncate(n) - - messages, err := syscall.ParseNetlinkMessage(buffer.Bytes()) +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 0, 0, E.Cause(err, "parse netlink message") - } else if len(messages) == 0 { - return 0, 0, E.New("unexcepted netlink response") + return -1, err } - - message := messages[0] - if message.Header.Type&syscall.NLMSG_ERROR != 0 { - return 0, 0, E.New("netlink message: NLMSG_ERROR") + 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 } - - inode, uid = unpackSocketDiagResponse(&messages[0]) - return + if err = syscall.SetsockoptTimeval(fd, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, timeout); err != nil { + syscall.Close(fd) + return -1, err + } + if err = syscall.Connect(fd, &syscall.SockaddrNetlink{ + Family: syscall.AF_NETLINK, + Pid: 0, + Groups: 0, + }); err != nil { + syscall.Close(fd) + return -1, err + } + return fd, nil } -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) +func (c *socketDiagConn) closeLocked() error { + if c.fd == -1 { + return nil + } + err := syscall.Close(c.fd) + c.fd = -1 + return err +} - binary.BigEndian.PutUint16(buf[24:26], source.Port()) - binary.BigEndian.PutUint16(buf[26:28], 0) +func packSocketDiagRequest(family, protocol byte, source netip.AddrPort, destination netip.AddrPort, dump bool) []byte { + request := make([]byte, sizeOfSocketDiagRequest) - copy(buf[28:44], s) - copy(buf[44:60], net.IPv6zero) + 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 +} - nativeEndian.PutUint32(buf[60:64], 0) - nativeEndian.PutUint64(buf[64:72], 0xFFFFFFFFFFFFFFFF) +func querySocketDiag(fd int, request []byte) (inode, uid uint32, err error) { + _, err = syscall.Write(fd, request) + if err != nil { + return 0, 0, E.Cause(err, "write netlink request") + } + buffer := make([]byte, 64<<10) + n, err := syscall.Read(fd, buffer) + if err != nil { + return 0, 0, E.Cause(err, "read netlink response") + } + messages, err := syscall.ParseNetlinkMessage(buffer[:n]) + if err != nil { + return 0, 0, E.Cause(err, "parse netlink message") + } + return unpackSocketDiagMessages(messages) +} - return buf +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 unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid uint32) { - if len(msg.Data) < 72 { + if len(msg.Data) < socketDiagResponseMinSize { 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]) +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) + } +} - return +func shouldRetrySocketDiag(err error) bool { + return err != nil && !errors.Is(err, ErrNotFound) } -func resolveProcessNameByProcSearch(inode, uid uint32) (string, error) { +func buildProcessPathByUIDCache(uid uint32) (map[uint32]string, error) { files, err := os.ReadDir(pathProc) if err != nil { - return "", err + return nil, err } - buffer := make([]byte, syscall.PathMax) - socket := []byte(fmt.Sprintf("socket:[%d]", inode)) - - for _, f := range files { - if !f.IsDir() || !isPid(f.Name()) { + processPaths := make(map[uint32]string) + for _, file := range files { + if !file.IsDir() || !isPid(file.Name()) { continue } - - info, err := f.Info() + info, err := file.Info() if err != nil { - return "", err + if isIgnorableProcError(err) { + continue + } + return nil, err } if info.Sys().(*syscall.Stat_t).Uid != uid { continue } - - processPath := path.Join(pathProc, f.Name()) - fdPath := path.Join(processPath, "fd") - + 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 + } fds, err := os.ReadDir(fdPath) if err != nil { continue } - for _, fd := range fds { - n, err := syscall.Readlink(path.Join(fdPath, fd.Name()), buffer) + n, err := syscall.Readlink(filepath.Join(fdPath, fd.Name()), buffer) if err != nil { continue } - - if bytes.Equal(buffer[:n], socket) { - return os.Readlink(path.Join(processPath, "exe")) + inode, ok := parseSocketInode(buffer[:n]) + if !ok { + continue + } + if _, loaded := processPaths[inode]; !loaded { + processPaths[inode] = exePath } } } + return processPaths, nil +} + +func isIgnorableProcError(err error) bool { + return os.IsNotExist(err) || os.IsPermission(err) +} - return "", fmt.Errorf("process of uid(%d),inode(%d) not found", uid, inode) +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 } func isPid(s string) bool { diff --git a/common/process/searcher_linux_shared_test.go b/common/process/searcher_linux_shared_test.go new file mode 100644 index 0000000000..1befff4e29 --- /dev/null +++ b/common/process/searcher_linux_shared_test.go @@ -0,0 +1,60 @@ +//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)) +} diff --git a/common/process/searcher_windows.go b/common/process/searcher_windows.go index 5b3d59b5ab..39695355b8 100644 --- a/common/process/searcher_windows.go +++ b/common/process/searcher_windows.go @@ -2,14 +2,12 @@ package process import ( "context" - "fmt" "net/netip" - "os" "syscall" - "unsafe" + "github.com/sagernet/sing-box/adapter" E "github.com/sagernet/sing/common/exceptions" - N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/winiphlpapi" "golang.org/x/sys/windows" ) @@ -26,209 +24,43 @@ func NewSearcher(_ Config) (Searcher, error) { return &windowsSearcher{}, nil } -var ( - modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll") - procGetExtendedTcpTable = modiphlpapi.NewProc("GetExtendedTcpTable") - procGetExtendedUdpTable = modiphlpapi.NewProc("GetExtendedUdpTable") - modkernel32 = windows.NewLazySystemDLL("kernel32.dll") - procQueryFullProcessImageNameW = modkernel32.NewProc("QueryFullProcessImageNameW") -) - func initWin32API() error { - err := modiphlpapi.Load() - if err != nil { - return E.Cause(err, "load iphlpapi.dll") - } - - err = procGetExtendedTcpTable.Find() - if err != nil { - return E.Cause(err, "load iphlpapi::GetExtendedTcpTable") - } - - err = procGetExtendedUdpTable.Find() - if err != nil { - return E.Cause(err, "load iphlpapi::GetExtendedUdpTable") - } - - err = modkernel32.Load() - if err != nil { - return E.Cause(err, "load kernel32.dll") - } - - err = procQueryFullProcessImageNameW.Find() - if err != nil { - return E.Cause(err, "load kernel32::QueryFullProcessImageNameW") - } + 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) (*Info, error) { - processName, err := findProcessName(network, source.Addr(), int(source.Port())) +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 { return nil, err } - return &Info{ProcessPath: processName, UserId: -1}, nil -} - -func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) { - family := windows.AF_INET - if ip.Is6() { - family = windows.AF_INET6 - } - - const ( - tcpTablePidConn = 4 - udpTablePid = 1 - ) - - var class int - var fn uintptr - switch network { - case N.NetworkTCP: - fn = procGetExtendedTcpTable.Addr() - class = tcpTablePidConn - case N.NetworkUDP: - fn = procGetExtendedUdpTable.Addr() - class = udpTablePid - default: - return "", os.ErrInvalid - } - - buf, err := getTransportTable(fn, family, class) - if err != nil { - return "", err - } - - s := newSearcher(family == windows.AF_INET, network == N.NetworkTCP) - - pid, err := s.Search(buf, ip, uint16(srcPort)) + path, err := getProcessPath(pid) if err != nil { - return "", err - } - return getExecPathFromPID(pid) -} - -type searcher struct { - itemSize int - port int - ip int - ipSize int - pid int - tcpState int -} - -func (s *searcher) Search(b []byte, ip netip.Addr, port uint16) (uint32, error) { - n := int(readNativeUint32(b[:4])) - itemSize := s.itemSize - for i := 0; i < n; i++ { - row := b[4+itemSize*i : 4+itemSize*(i+1)] - - if s.tcpState >= 0 { - tcpState := readNativeUint32(row[s.tcpState : s.tcpState+4]) - // MIB_TCP_STATE_ESTAB, only check established connections for TCP - if tcpState != 5 { - continue - } - } - - // according to MSDN, only the lower 16 bits of dwLocalPort are used and the port number is in network endian. - // this field can be illustrated as follows depends on different machine endianess: - // little endian: [ MSB LSB 0 0 ] interpret as native uint32 is ((LSB<<8)|MSB) - // big endian: [ 0 0 MSB LSB ] interpret as native uint32 is ((MSB<<8)|LSB) - // so we need an syscall.Ntohs on the lower 16 bits after read the port as native uint32 - srcPort := syscall.Ntohs(uint16(readNativeUint32(row[s.port : s.port+4]))) - if srcPort != port { - continue - } - - srcIP, _ := netip.AddrFromSlice(row[s.ip : s.ip+s.ipSize]) - // windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto - if ip != srcIP && (!srcIP.IsUnspecified() || s.tcpState != -1) { - continue - } - - pid := readNativeUint32(row[s.pid : s.pid+4]) - return pid, nil + return &adapter.ConnectionOwner{ProcessID: pid, UserId: -1}, err } - return 0, ErrNotFound + return &adapter.ConnectionOwner{ProcessID: pid, ProcessPath: path, UserId: -1}, nil } -func newSearcher(isV4, isTCP bool) *searcher { - var itemSize, port, ip, ipSize, pid int - tcpState := -1 - switch { - case isV4 && isTCP: - // struct MIB_TCPROW_OWNER_PID - itemSize, port, ip, ipSize, pid, tcpState = 24, 8, 4, 4, 20, 0 - case isV4 && !isTCP: - // struct MIB_UDPROW_OWNER_PID - itemSize, port, ip, ipSize, pid = 12, 4, 0, 4, 8 - case !isV4 && isTCP: - // struct MIB_TCP6ROW_OWNER_PID - itemSize, port, ip, ipSize, pid, tcpState = 56, 20, 0, 16, 52, 48 - case !isV4 && !isTCP: - // struct MIB_UDP6ROW_OWNER_PID - itemSize, port, ip, ipSize, pid = 28, 20, 0, 16, 24 - } - - return &searcher{ - itemSize: itemSize, - port: port, - ip: ip, - ipSize: ipSize, - pid: pid, - tcpState: tcpState, - } -} - -func getTransportTable(fn uintptr, family int, class int) ([]byte, error) { - for size, buf := uint32(8), make([]byte, 8); ; { - ptr := unsafe.Pointer(&buf[0]) - err, _, _ := syscall.SyscallN(fn, uintptr(ptr), uintptr(unsafe.Pointer(&size)), 0, uintptr(family), uintptr(class), 0) - - switch err { - case 0: - return buf, nil - case uintptr(syscall.ERROR_INSUFFICIENT_BUFFER): - buf = make([]byte, size) - default: - return nil, fmt.Errorf("syscall error: %d", err) - } - } -} - -func readNativeUint32(b []byte) uint32 { - return *(*uint32)(unsafe.Pointer(&b[0])) -} - -func getExecPathFromPID(pid uint32) (string, error) { - // kernel process starts with a colon in order to distinguish with normal processes +func getProcessPath(pid uint32) (string, error) { switch pid { case 0: - // reserved pid for system idle process return ":System Idle Process", nil case 4: - // reserved pid for windows kernel image return ":System", nil } - h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid) + handle, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid) if err != nil { return "", err } - defer windows.CloseHandle(h) - + defer windows.CloseHandle(handle) + size := uint32(syscall.MAX_LONG_PATH) buf := make([]uint16, syscall.MAX_LONG_PATH) - size := uint32(len(buf)) - r1, _, err := syscall.SyscallN( - procQueryFullProcessImageNameW.Addr(), - uintptr(h), - uintptr(0), - uintptr(unsafe.Pointer(&buf[0])), - uintptr(unsafe.Pointer(&size)), - ) - if r1 == 0 { + err = windows.QueryFullProcessImageName(handle, 0, &buf[0], &size) + if err != nil { return "", err } - return syscall.UTF16ToString(buf[:size]), nil + return windows.UTF16ToString(buf[:size]), nil } diff --git a/common/proxybridge/bridge.go b/common/proxybridge/bridge.go new file mode 100644 index 0000000000..3380cae447 --- /dev/null +++ b/common/proxybridge/bridge.go @@ -0,0 +1,115 @@ +package proxybridge + +import ( + std_bufio "bufio" + "context" + "crypto/rand" + "encoding/hex" + "net" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/auth" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/protocol/socks" + "github.com/sagernet/sing/service" +) + +type Bridge struct { + ctx context.Context + logger logger.ContextLogger + tag string + dialer N.Dialer + connection adapter.ConnectionManager + tcpListener *net.TCPListener + username string + password string + authenticator *auth.Authenticator +} + +func New(ctx context.Context, logger logger.ContextLogger, tag string, dialer N.Dialer) (*Bridge, error) { + username := randomHex(16) + password := randomHex(16) + tcpListener, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1)}) + if err != nil { + return nil, err + } + bridge := &Bridge{ + ctx: ctx, + logger: logger, + tag: tag, + dialer: dialer, + connection: service.FromContext[adapter.ConnectionManager](ctx), + tcpListener: tcpListener, + username: username, + password: password, + authenticator: auth.NewAuthenticator([]auth.User{{Username: username, Password: password}}), + } + go bridge.acceptLoop() + return bridge, nil +} + +func randomHex(size int) string { + raw := make([]byte, size) + rand.Read(raw) + return hex.EncodeToString(raw) +} + +func (b *Bridge) Port() uint16 { + return M.SocksaddrFromNet(b.tcpListener.Addr()).Port +} + +func (b *Bridge) Username() string { + return b.username +} + +func (b *Bridge) Password() string { + return b.password +} + +func (b *Bridge) Close() error { + return common.Close(b.tcpListener) +} + +func (b *Bridge) acceptLoop() { + for { + tcpConn, err := b.tcpListener.AcceptTCP() + if err != nil { + return + } + ctx := log.ContextWithNewID(b.ctx) + go func() { + hErr := socks.HandleConnectionEx(ctx, tcpConn, std_bufio.NewReader(tcpConn), b.authenticator, b, nil, 0, M.SocksaddrFromNet(tcpConn.RemoteAddr()), nil) + if hErr == nil { + return + } + if E.IsClosedOrCanceled(hErr) { + b.logger.DebugContext(ctx, E.Cause(hErr, b.tag, " connection closed")) + return + } + b.logger.ErrorContext(ctx, E.Cause(hErr, b.tag)) + }() + } +} + +func (b *Bridge) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Source = source + metadata.Destination = destination + metadata.Network = N.NetworkTCP + b.logger.InfoContext(ctx, b.tag, " connection to ", metadata.Destination) + b.connection.NewConnection(ctx, b.dialer, conn, metadata, onClose) +} + +func (b *Bridge) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Source = source + metadata.Destination = destination + metadata.Network = N.NetworkUDP + b.logger.InfoContext(ctx, b.tag, " packet connection to ", metadata.Destination) + b.connection.NewPacketConnection(ctx, b.dialer, conn, metadata, onClose) +} diff --git a/common/proxyproto/dialer.go b/common/proxyproto/dialer.go new file mode 100644 index 0000000000..f3fba6f4da --- /dev/null +++ b/common/proxyproto/dialer.go @@ -0,0 +1,50 @@ +package proxyproto + +import ( + "context" + "net" + "net/netip" + + "github.com/sagernet/sing-box/adapter" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + "github.com/pires/go-proxyproto" +) + +var _ N.Dialer = (*Dialer)(nil) + +type Dialer struct { + N.Dialer +} + +func (d *Dialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + switch N.NetworkName(network) { + case N.NetworkTCP: + conn, err := d.Dialer.DialContext(ctx, network, destination) + if err != nil { + return nil, err + } + var source M.Socksaddr + metadata := adapter.ContextFrom(ctx) + if metadata != nil { + source = metadata.Source + } + if !source.IsValid() { + source = M.SocksaddrFromNet(conn.LocalAddr()) + } + if destination.Addr.Is6() { + source = M.SocksaddrFrom(netip.AddrFrom16(source.Addr.As16()), source.Port) + } + h := proxyproto.HeaderProxyFromAddrs(1, source.TCPAddr(), destination.TCPAddr()) + _, err = h.WriteTo(conn) + if err != nil { + conn.Close() + return nil, E.Cause(err, "write proxy protocol header") + } + return conn, nil + default: + return d.Dialer.DialContext(ctx, network, destination) + } +} diff --git a/common/proxyproto/listener.go b/common/proxyproto/listener.go new file mode 100644 index 0000000000..23a839d719 --- /dev/null +++ b/common/proxyproto/listener.go @@ -0,0 +1,62 @@ +package proxyproto + +import ( + std_bufio "bufio" + "net" + + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + M "github.com/sagernet/sing/common/metadata" + + "github.com/pires/go-proxyproto" +) + +type Listener struct { + net.Listener + AcceptNoHeader bool +} + +func (l *Listener) Accept() (net.Conn, error) { + conn, err := l.Listener.Accept() + if err != nil { + return nil, err + } + bufReader := std_bufio.NewReader(conn) + header, err := proxyproto.Read(bufReader) + if err != nil && !(l.AcceptNoHeader && err == proxyproto.ErrNoProxyProtocol) { + return nil, &Error{err} + } + if bufReader.Buffered() > 0 { + cache := buf.NewSize(bufReader.Buffered()) + _, err = cache.ReadFullFrom(bufReader, cache.FreeLen()) + if err != nil { + return nil, &Error{err} + } + conn = bufio.NewCachedConn(conn, cache) + } + if header != nil { + return &bufio.AddrConn{Conn: conn, + Source: M.SocksaddrFromNet(header.SourceAddr).Unwrap(), + Destination: M.SocksaddrFromNet(header.DestinationAddr).Unwrap(), + }, nil + } + return conn, nil +} + +var _ net.Error = (*Error)(nil) + +type Error struct { + error +} + +func (e *Error) Unwrap() error { + return e.error +} + +func (e *Error) Timeout() bool { + return false +} + +func (e *Error) Temporary() bool { + return true +} diff --git a/common/redir/tproxy_linux.go b/common/redir/tproxy_linux.go index ccf940376e..a3ccade08d 100644 --- a/common/redir/tproxy_linux.go +++ b/common/redir/tproxy_linux.go @@ -12,7 +12,7 @@ import ( "golang.org/x/sys/unix" ) -func TProxy(fd uintptr, isIPv6 bool) error { +func TProxy(fd uintptr, isIPv6 bool, isUDP bool) error { err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) if err == nil { err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1) @@ -20,11 +20,13 @@ func TProxy(fd uintptr, isIPv6 bool) error { if err == nil && isIPv6 { err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1) } - if err == nil { - err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1) - } - if err == nil && isIPv6 { - err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1) + if isUDP { + if err == nil { + err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1) + } + if err == nil && isIPv6 { + err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1) + } } return err } diff --git a/common/redir/tproxy_other.go b/common/redir/tproxy_other.go index 5869a195e8..42e31fee34 100644 --- a/common/redir/tproxy_other.go +++ b/common/redir/tproxy_other.go @@ -9,7 +9,7 @@ import ( "github.com/sagernet/sing/common/control" ) -func TProxy(fd uintptr, isIPv6 bool) error { +func TProxy(fd uintptr, isIPv6 bool, isUDP bool) error { return os.ErrInvalid } diff --git a/common/schannel/doc.go b/common/schannel/doc.go new file mode 100644 index 0000000000..bdcb2f1a36 --- /dev/null +++ b/common/schannel/doc.go @@ -0,0 +1,5 @@ +// Package schannel wraps the Windows Schannel security provider (SSPI) for +// client-side TLS. The public API is implemented on Windows only; on other +// platforms the package is empty and intended for transitive imports from +// build-tagged callers. +package schannel diff --git a/common/schannel/schannel_windows.go b/common/schannel/schannel_windows.go new file mode 100644 index 0000000000..d912ca2315 --- /dev/null +++ b/common/schannel/schannel_windows.go @@ -0,0 +1,719 @@ +package schannel + +import ( + "bytes" + "crypto/tls" + "encoding/binary" + "os" + "sync" + "syscall" + "unsafe" + + E "github.com/sagernet/sing/common/exceptions" + + "golang.org/x/sys/windows" +) + +const clientCredentialFlags = schCredManualCredValidation | schCredNoDefaultCreds | schUseStrongCrypto + +var versionCheck = sync.OnceValue(func() error { + major, _, build := windows.RtlGetNtVersionNumbers() + build &= 0xffff + if major < 10 || (major == 10 && build < 17763) { + return E.New("Windows TLS engine requires Windows build 17763 or later (Windows 10 version 1809, Windows Server 2019, or newer)") + } + return nil +}) + +// CheckPlatform returns an error when the running Windows version does not +// support the SCH_CREDENTIALS structure used by this package. +func CheckPlatform() error { + return versionCheck() +} + +type clientCredentialKey struct { + disabledProtocols uint32 + flags uint32 +} + +type clientCredential struct { + key clientCredentialKey + once sync.Once + handle secHandle + tlsParams tlsParameters + err error +} + +var clientCredentialCache sync.Map + +func cachedClientCredential(minVersion, maxVersion uint16) (*clientCredential, error) { + key := clientCredentialKey{ + disabledProtocols: disabledProtocolsMask(minVersion, maxVersion), + flags: clientCredentialFlags, + } + actual, _ := clientCredentialCache.LoadOrStore(key, &clientCredential{key: key}) + credential := actual.(*clientCredential) + credential.once.Do(func() { + credential.err = credential.acquire() + }) + if credential.err != nil { + clientCredentialCache.Delete(key) + return nil, credential.err + } + return credential, nil +} + +func (c *clientCredential) acquire() error { + c.tlsParams.grbitDisabledProtocols = c.key.disabledProtocols + sch := schCredentials{ + dwVersion: schCredentialsVersion, + dwFlags: c.key.flags, + cTlsParameters: 1, + pTlsParameters: &c.tlsParams, + } + pkg, err := windows.UTF16PtrFromString(unispNameW) + if err != nil { + return err + } + var expiry windows.Filetime + status := sspiAcquireCredentialsHandle( + nil, + pkg, + secPkgCredOutbound, + nil, + unsafe.Pointer(&sch), + 0, + 0, + &c.handle, + &expiry, + ) + if status != secEOK { + return sspiError("AcquireCredentialsHandle", status) + } + return nil +} + +// ClientContext owns the per-connection Schannel security context and drives +// it through handshake and application-data phases. +type ClientContext struct { + credential *clientCredential + handle secHandle + targetName *uint16 + + // alpnBuffer is the SEC_APPLICATION_PROTOCOLS blob; kept alive for the + // duration of the first handshake call. + alpnBuffer []byte + + firstCall bool + valid bool +} + +// NewClientContext allocates a new client context, reuses the Schannel +// credential handle for the supplied TLS version bounds, and advertises ALPN +// protocols through an SECBUFFER_APPLICATION_PROTOCOLS buffer on the first +// handshake call. +func NewClientContext(minVersion, maxVersion uint16, serverName string, alpn []string) (*ClientContext, error) { + if minVersion != 0 && maxVersion != 0 && minVersion > maxVersion { + return nil, os.ErrInvalid + } + err := CheckPlatform() + if err != nil { + return nil, err + } + targetName, err := windows.UTF16PtrFromString(serverName) + if err != nil { + return nil, err + } + credential, err := cachedClientCredential(minVersion, maxVersion) + if err != nil { + return nil, err + } + c := &ClientContext{ + credential: credential, + targetName: targetName, + firstCall: true, + } + if len(alpn) > 0 { + c.alpnBuffer, err = encodeAlpnBuffer(alpn) + if err != nil { + return nil, err + } + } + return c, nil +} + +// Close releases the per-connection security context. Safe to call multiple +// times. +func (c *ClientContext) Close() { + if c == nil { + return + } + if c.valid { + sspiDeleteSecurityContext(&c.handle) + c.valid = false + c.handle = secHandle{} + } +} + +type StepResult struct { + // Output must be written to the peer verbatim before the next Step call. + // When Done is true, leftover input[Consumed:] is the first application + // ciphertext — not more handshake bytes. + Output []byte + Consumed int + Done bool + Incomplete bool +} + +// Step drives one handshake iteration. Input may be nil on the first call. +// Callers must write Output to the peer, append more peer bytes when +// Incomplete is true, and loop until Done is true. +func (c *ClientContext) Step(input []byte) (StepResult, error) { + var inputDesc *secBufferDesc + var inputBufs [2]secBuffer + if c.firstCall { + if len(c.alpnBuffer) > 0 { + inputBufs[0].bufferType = secbufferApplicationProtocols + inputBufs[0].cbBuffer = uint32(len(c.alpnBuffer)) + inputBufs[0].pvBuffer = &c.alpnBuffer[0] + inputDesc = &secBufferDesc{ + ulVersion: secbufferVersion, + cBuffers: 1, + pBuffers: &inputBufs[0], + } + } + } else { + if len(input) == 0 { + return StepResult{}, E.New("schannel: empty handshake input after first step") + } + inputBufs[0].bufferType = secbufferToken + inputBufs[0].cbBuffer = uint32(len(input)) + inputBufs[0].pvBuffer = &input[0] + inputBufs[1].bufferType = secbufferEmpty + inputDesc = &secBufferDesc{ + ulVersion: secbufferVersion, + cBuffers: 2, + pBuffers: &inputBufs[0], + } + } + + result, terminal, err := c.runInitializeSecurityContext(inputDesc, "InitializeSecurityContext") + if err != nil || terminal { + return result, err + } + + switch { + case c.firstCall: + result.Consumed = 0 + case inputBufs[1].bufferType == secbufferExtra && inputBufs[1].cbBuffer > 0: + consumed, extraErr := consumedFromExtra(&inputBufs[1], len(input)) + if extraErr != nil { + return result, extraErr + } + result.Consumed = consumed + default: + result.Consumed = len(input) + } + + c.firstCall = false + c.alpnBuffer = nil + return result, nil +} + +// StreamSizes must be called after Step returns Done=true. +func (c *ClientContext) StreamSizes() (header, trailer, maxMessage uint32, err error) { + var sizes secPkgContextStreamSizes + status := sspiQueryContextAttributes(&c.handle, secpkgAttrStreamSizes, unsafe.Pointer(&sizes)) + if status != secEOK { + return 0, 0, 0, sspiError("QueryContextAttributes(stream sizes)", status) + } + return sizes.cbHeader, sizes.cbTrailer, sizes.cbMaximumMessage, nil +} + +// Encrypt wraps a plaintext chunk into a TLS record using the supplied +// backing buffer which must have room for header + plaintext + trailer bytes. +// Plaintext is copied into buffer starting at `header` offset before calling +// EncryptMessage. Returns the encrypted record as a slice into buffer. +func (c *ClientContext) Encrypt(header, trailer uint32, plaintext []byte, buffer []byte) ([]byte, error) { + if len(buffer) < int(header)+len(plaintext)+int(trailer) { + return nil, E.New("schannel: encrypt buffer too small") + } + copy(buffer[header:], plaintext) + headerPtr := &buffer[0] + dataPtr := &buffer[header] + trailerPtr := &buffer[int(header)+len(plaintext)] + + bufs := [4]secBuffer{ + {cbBuffer: header, bufferType: secbufferStreamHeader, pvBuffer: headerPtr}, + {cbBuffer: uint32(len(plaintext)), bufferType: secbufferData, pvBuffer: dataPtr}, + {cbBuffer: trailer, bufferType: secbufferStreamTrailer, pvBuffer: trailerPtr}, + {bufferType: secbufferEmpty}, + } + desc := secBufferDesc{ + ulVersion: secbufferVersion, + cBuffers: 4, + pBuffers: &bufs[0], + } + status := sspiEncryptMessage(&c.handle, 0, &desc, 0) + if status != secEOK { + return nil, sspiError("EncryptMessage", status) + } + total := int(bufs[0].cbBuffer + bufs[1].cbBuffer + bufs[2].cbBuffer) + return buffer[:total], nil +} + +type DecryptResult struct { + // Plaintext aliases memory inside the input buffer passed to Decrypt; + // callers must copy before the next Decrypt call reuses that buffer. + Plaintext []byte + // ConsumedTotal is the number of input bytes Schannel consumed, i.e. + // input[ConsumedTotal:] are unprocessed leftover ciphertext. + ConsumedTotal int + // RenegotiateToken aliases the post-handshake token that must be fed back + // through InitializeSecurityContext after SEC_I_RENEGOTIATE. + RenegotiateToken []byte + Incomplete bool + Renegotiate bool + Expired bool +} + +// Decrypt processes a chunk of TLS ciphertext in-place. The returned Plaintext +// aliases memory inside input until the next Decrypt call; callers must copy +// the bytes they want to keep. +func (c *ClientContext) Decrypt(input []byte) (DecryptResult, error) { + var result DecryptResult + if len(input) == 0 { + result.Incomplete = true + return result, nil + } + bufs := [4]secBuffer{ + {cbBuffer: uint32(len(input)), bufferType: secbufferData, pvBuffer: &input[0]}, + {bufferType: secbufferEmpty}, + {bufferType: secbufferEmpty}, + {bufferType: secbufferEmpty}, + } + desc := secBufferDesc{ + ulVersion: secbufferVersion, + cBuffers: 4, + pBuffers: &bufs[0], + } + status := sspiDecryptMessage(&c.handle, &desc, 0, nil) + switch status { + case secEOK: + case secEIncompleteMessage: + result.Incomplete = true + return result, nil + case secIContextExpired: + result.Expired = true + return result, nil + case secIRenegotiate: + result.Renegotiate = true + default: + return result, sspiError("DecryptMessage", status) + } + return parseDecryptResult(input, bufs[:], result.Renegotiate) +} + +// PostHandshake processes a TLS 1.3 post-handshake message +// (NewSessionTicket, KeyUpdate) after DecryptMessage returned +// SEC_I_RENEGOTIATE. Pass the token preserved from Decrypt on the first call; +// pass more peer bytes on subsequent calls when Incomplete. +func (c *ClientContext) PostHandshake(input []byte) (StepResult, error) { + var inputDesc *secBufferDesc + var inputBufs [2]secBuffer + if len(input) > 0 { + inputBufs[0].bufferType = secbufferToken + inputBufs[0].cbBuffer = uint32(len(input)) + inputBufs[0].pvBuffer = &input[0] + inputBufs[1].bufferType = secbufferEmpty + inputDesc = &secBufferDesc{ + ulVersion: secbufferVersion, + cBuffers: 2, + pBuffers: &inputBufs[0], + } + } + + result, terminal, err := c.runInitializeSecurityContext(inputDesc, "InitializeSecurityContext(post-handshake)") + if err != nil || terminal { + return result, err + } + + if len(input) > 0 && inputBufs[1].bufferType == secbufferExtra && inputBufs[1].cbBuffer > 0 { + consumed, extraErr := consumedFromExtra(&inputBufs[1], len(input)) + if extraErr != nil { + return result, extraErr + } + result.Consumed = consumed + } else { + result.Consumed = len(input) + } + return result, nil +} + +func parseDecryptResult(input []byte, bufs []secBuffer, renegotiate bool) (DecryptResult, error) { + var result DecryptResult + var dataBuffer, extraBuffer *secBuffer + for index := range bufs { + switch bufs[index].bufferType { + case secbufferData: + dataBuffer = &bufs[index] + case secbufferExtra: + extraBuffer = &bufs[index] + } + } + if dataBuffer != nil && dataBuffer.cbBuffer > 0 && dataBuffer.pvBuffer != nil { + result.Plaintext = unsafe.Slice(dataBuffer.pvBuffer, int(dataBuffer.cbBuffer)) + } + if extraBuffer != nil && extraBuffer.cbBuffer > 0 { + consumed, err := consumedFromExtra(extraBuffer, len(input)) + if err != nil { + return result, err + } + result.ConsumedTotal = consumed + } else { + result.ConsumedTotal = len(input) + } + if renegotiate { + result.Renegotiate = true + if extraBuffer != nil && extraBuffer.cbBuffer > 0 { + result.RenegotiateToken = input[result.ConsumedTotal:] + } else { + result.RenegotiateToken = input + } + } + return result, nil +} + +// ApplicationProtocol returns the empty string when ALPN was not negotiated. +func (c *ClientContext) ApplicationProtocol() (string, error) { + var info secPkgContextApplicationProtocol + status := sspiQueryContextAttributes(&c.handle, secpkgAttrApplicationProtocol, unsafe.Pointer(&info)) + if status != secEOK { + return "", sspiError("QueryContextAttributes(application protocol)", status) + } + if info.protoNegoStatus != secApplicationProtocolNegotiationStatusSuccess { + return "", nil + } + size := int(info.protocolIDSize) + if size > len(info.protocolID) { + return "", E.New("schannel: invalid ALPN protocol size") + } + return string(info.protocolID[:size]), nil +} + +// ConnectionInfo reports the negotiated TLS version and cipher suite. +// cipherSuite may be zero when the Windows build does not return a +// mappable cipher name. +func (c *ClientContext) ConnectionInfo() (version, cipherSuite uint16, err error) { + var info secPkgContextConnectionInfo + status := sspiQueryContextAttributes(&c.handle, secpkgAttrConnectionInfo, unsafe.Pointer(&info)) + if status != secEOK { + return 0, 0, sspiError("QueryContextAttributes(connection info)", status) + } + version = sspProtocolToTLSVersion(info.dwProtocol) + + var cipherInfo secPkgContextCipherInfo + cipherInfo.dwVersion = 1 + status = sspiQueryContextAttributes(&c.handle, secpkgAttrCipherInfo, unsafe.Pointer(&cipherInfo)) + if status == secEOK { + cipherSuite = cipherSuiteID(windows.UTF16ToString(cipherInfo.szCipherSuite[:])) + } + return version, cipherSuite, nil +} + +func cipherSuiteID(name string) uint16 { + for _, suite := range tls.CipherSuites() { + if suite.Name == name { + return suite.ID + } + } + for _, suite := range tls.InsecureCipherSuites() { + if suite.Name == name { + return suite.ID + } + } + return 0 +} + +// RemoteCertificateChain returns freshly allocated DER bytes ordered +// leaf → intermediates. +func (c *ClientContext) RemoteCertificateChain() ([][]byte, error) { + var leaf *windows.CertContext + status := sspiQueryContextAttributes(&c.handle, secpkgAttrRemoteCertContext, unsafe.Pointer(&leaf)) + if status != secEOK { + return nil, sspiError("QueryContextAttributes(remote cert context)", status) + } + if leaf == nil { + return nil, nil + } + defer windows.CertFreeCertificateContext(leaf) + + chain, err := buildCertChainDER(leaf) + if err != nil { + return [][]byte{certContextDER(leaf)}, nil + } + return chain, nil +} + +const handshakeContextReq = iscReqSequenceDetect | + iscReqReplayDetect | + iscReqConfidentiality | + iscReqAllocateMemory | + iscReqStream | + iscReqUseSuppliedCreds | + iscReqManualCredValidation | + iscReqExtendedError + +// runInitializeSecurityContext returns terminal=true when the result is +// final (error or more-data-needed), signalling that the caller must skip +// extra-buffer post-processing. +func (c *ClientContext) runInitializeSecurityContext(inputDesc *secBufferDesc, opLabel string) (StepResult, bool, error) { + var outputBufs [1]secBuffer + outputBufs[0].bufferType = secbufferToken + outputDesc := secBufferDesc{ + ulVersion: secbufferVersion, + cBuffers: 1, + pBuffers: &outputBufs[0], + } + var ctxIn *secHandle + if c.valid { + ctxIn = &c.handle + } + var contextAttr uint32 + var expiry windows.Filetime + status := sspiInitializeSecurityContext( + &c.credential.handle, + ctxIn, + c.targetName, + handshakeContextReq, + 0, + 0, + inputDesc, + 0, + &c.handle, + &outputDesc, + &contextAttr, + &expiry, + ) + + switch status { + case secEOK, secICompleteNeeded, secICompleteAndContinue, secIContinueNeeded: + c.valid = true + } + if status == secICompleteNeeded || status == secICompleteAndContinue { + completeStatus := sspiCompleteAuthToken(&c.handle, &outputDesc) + if completeStatus != secEOK { + if outputBufs[0].pvBuffer != nil { + sspiFreeContextBuffer(outputBufs[0].pvBuffer) + } + return StepResult{}, true, sspiError("CompleteAuthToken", completeStatus) + } + } + + var result StepResult + if outputBufs[0].cbBuffer > 0 && outputBufs[0].pvBuffer != nil { + result.Output = unsafeSliceCopy(outputBufs[0].pvBuffer, int(outputBufs[0].cbBuffer)) + sspiFreeContextBuffer(outputBufs[0].pvBuffer) + } + + switch status { + case secEOK, secICompleteNeeded: + result.Done = true + return result, false, nil + case secIContinueNeeded, secICompleteAndContinue: + return result, false, nil + case secEIncompleteMessage: + c.valid = true + result.Incomplete = true + return result, true, nil + default: + return result, true, sspiError(opLabel, status) + } +} + +func consumedFromExtra(extraBuf *secBuffer, inputLen int) (int, error) { + extraLen := int(extraBuf.cbBuffer) + if extraLen > inputLen { + return 0, E.New("schannel: SECBUFFER_EXTRA exceeds input length") + } + return inputLen - extraLen, nil +} + +func disabledProtocolsMask(minVersion, maxVersion uint16) uint32 { + allowed := uint32(0) + versions := []struct { + id uint16 + mask uint32 + }{ + {tls.VersionTLS10, spProtTLS10Client}, + {tls.VersionTLS11, spProtTLS11Client}, + {tls.VersionTLS12, spProtTLS12Client}, + {tls.VersionTLS13, spProtTLS13Client}, + } + effectiveMin := minVersion + if effectiveMin == 0 { + effectiveMin = tls.VersionTLS12 + if maxVersion != 0 && maxVersion < tls.VersionTLS12 { + effectiveMin = versions[0].id + } + } + effectiveMax := maxVersion + if effectiveMax == 0 { + effectiveMax = tls.VersionTLS13 + } + for _, v := range versions { + if v.id >= effectiveMin && v.id <= effectiveMax { + allowed |= v.mask + } + } + if allowed == 0 { + return 0 + } + return spProtAllTLSClients &^ allowed +} + +func sspProtocolToTLSVersion(sp uint32) uint16 { + switch { + case sp&spProtTLS13Client != 0: + return tls.VersionTLS13 + case sp&spProtTLS12Client != 0: + return tls.VersionTLS12 + case sp&spProtTLS11Client != 0: + return tls.VersionTLS11 + case sp&spProtTLS10Client != 0: + return tls.VersionTLS10 + } + return 0 +} + +func encodeAlpnBuffer(protocols []string) ([]byte, error) { + var protoList []byte + for _, proto := range protocols { + if len(proto) == 0 || len(proto) > 255 { + return nil, E.New("schannel: invalid ALPN protocol: ", proto) + } + protoList = append(protoList, byte(len(proto))) + protoList = append(protoList, []byte(proto)...) + } + if len(protoList) > 0xFFFF { + return nil, E.New("schannel: ALPN list too long") + } + // Layout: + // uint32 ProtocolListsSize + // uint32 ProtoNegoExt + // uint16 ProtocolListSize + // bytes ProtocolList + inner := 4 + 2 + len(protoList) + buffer := make([]byte, 4+inner) + binary.LittleEndian.PutUint32(buffer[0:4], uint32(inner)) + binary.LittleEndian.PutUint32(buffer[4:8], secApplicationProtocolNegotiationExtALPN) + binary.LittleEndian.PutUint16(buffer[8:10], uint16(len(protoList))) + copy(buffer[10:], protoList) + return buffer, nil +} + +func unsafeSliceCopy(ptr *byte, size int) []byte { + if ptr == nil || size <= 0 { + return nil + } + out := make([]byte, size) + copy(out, unsafe.Slice(ptr, size)) + return out +} + +func certContextDER(ctx *windows.CertContext) []byte { + if ctx == nil || ctx.EncodedCert == nil || ctx.Length == 0 { + return nil + } + out := make([]byte, ctx.Length) + copy(out, unsafe.Slice(ctx.EncodedCert, int(ctx.Length))) + return out +} + +func buildCertChainDER(leaf *windows.CertContext) ([][]byte, error) { + var chainPara windows.CertChainPara + chainPara.Size = uint32(unsafe.Sizeof(chainPara)) + var chainCtx *windows.CertChainContext + err := windows.CertGetCertificateChain(0, leaf, nil, leaf.Store, &chainPara, 0, 0, &chainCtx) + if err != nil { + return nil, err + } + defer windows.CertFreeCertificateChain(chainCtx) + return extractCertChainDER(chainCtx) +} + +func extractCertChainDER(chainCtx *windows.CertChainContext) ([][]byte, error) { + if chainCtx == nil || chainCtx.ChainCount == 0 || chainCtx.Chains == nil { + return nil, E.New("schannel: empty certificate chain") + } + chains := unsafe.Slice(chainCtx.Chains, int(chainCtx.ChainCount)) + chain := chains[0] + if chain == nil || chain.NumElements == 0 || chain.Elements == nil { + return nil, E.New("schannel: empty certificate chain") + } + elements := unsafe.Slice(chain.Elements, int(chain.NumElements)) + if len(elements) > 1 && + chain.TrustStatus.ErrorStatus&windows.CERT_TRUST_IS_PARTIAL_CHAIN == 0 && + isSelfSignedCertContext(elements[len(elements)-1].CertContext) { + elements = elements[:len(elements)-1] + } + derChain := make([][]byte, 0, len(elements)) + for index, element := range elements { + if element == nil || element.CertContext == nil { + return nil, E.New("schannel: missing certificate chain element ", index) + } + der := certContextDER(element.CertContext) + if len(der) == 0 { + return nil, E.New("schannel: empty certificate chain element ", index) + } + derChain = append(derChain, der) + } + return derChain, nil +} + +func isSelfSignedCertContext(ctx *windows.CertContext) bool { + if ctx == nil || ctx.CertInfo == nil { + return false + } + return bytes.Equal( + certNameBlobBytes(ctx.CertInfo.Issuer), + certNameBlobBytes(ctx.CertInfo.Subject), + ) +} + +func certNameBlobBytes(blob windows.CertNameBlob) []byte { + if blob.Size == 0 || blob.Data == nil { + return nil + } + return unsafe.Slice(blob.Data, int(blob.Size)) +} + +func sspiError(where string, status syscall.Errno) error { + return E.New("schannel: ", where, ": ", formatStatus(status)) +} + +var statusNames = map[syscall.Errno]string{ + secEUnsupportedFunc: "SEC_E_UNSUPPORTED_FUNCTION", + secEInternalError: "SEC_E_INTERNAL_ERROR", + secEInvalidToken: "SEC_E_INVALID_TOKEN", + secELogonDenied: "SEC_E_LOGON_DENIED", + secEMessageAltered: "SEC_E_MESSAGE_ALTERED", + secENoAuthenticatingAuthority: "SEC_E_NO_AUTHENTICATING_AUTHORITY", + secEContextExpired: "SEC_E_CONTEXT_EXPIRED", + secEIncompleteMessage: "SEC_E_INCOMPLETE_MESSAGE", + secEIncompleteCreds: "SEC_E_INCOMPLETE_CREDENTIALS", + secEBufferTooSmall: "SEC_E_BUFFER_TOO_SMALL", + secEWrongPrincipal: "SEC_E_WRONG_PRINCIPAL", + secEIllegalMessage: "SEC_E_ILLEGAL_MESSAGE", + secECertUnknown: "SEC_E_CERT_UNKNOWN", + secECertExpired: "SEC_E_CERT_EXPIRED", + secEAlgorithmMismatch: "SEC_E_ALGORITHM_MISMATCH", +} + +func formatStatus(status syscall.Errno) string { + name, loaded := statusNames[status] + if !loaded { + return status.Error() + } + return name + ": " + status.Error() +} diff --git a/common/schannel/schannel_windows_test.go b/common/schannel/schannel_windows_test.go new file mode 100644 index 0000000000..35bcaabb21 --- /dev/null +++ b/common/schannel/schannel_windows_test.go @@ -0,0 +1,188 @@ +//go:build windows + +package schannel + +import ( + "bytes" + "crypto/tls" + "testing" + "unsafe" + + "golang.org/x/sys/windows" +) + +func TestExtractCertChainDERExcludesSelfSignedRoot(t *testing.T) { + leaf := certContextForTest([]byte("leaf"), []byte("intermediate"), []byte("leaf")) + intermediate := certContextForTest([]byte("intermediate"), []byte("root"), []byte("intermediate")) + root := certContextForTest([]byte("root"), []byte("root"), []byte("root")) + + chainCtx := certChainContextForTest(leaf, intermediate, root) + derChain, err := extractCertChainDER(chainCtx) + if err != nil { + t.Fatal(err) + } + if len(derChain) != 2 { + t.Fatalf("expected 2 certificates, got %d", len(derChain)) + } + if !bytes.Equal(derChain[0], []byte("leaf")) { + t.Fatalf("unexpected leaf certificate: %q", string(derChain[0])) + } + if !bytes.Equal(derChain[1], []byte("intermediate")) { + t.Fatalf("unexpected intermediate certificate: %q", string(derChain[1])) + } +} + +func TestExtractCertChainDERKeepsLastIntermediateWithoutRoot(t *testing.T) { + leaf := certContextForTest([]byte("leaf"), []byte("intermediate"), []byte("leaf")) + intermediate := certContextForTest([]byte("intermediate"), []byte("root"), []byte("intermediate")) + + chainCtx := certChainContextForTest(leaf, intermediate) + derChain, err := extractCertChainDER(chainCtx) + if err != nil { + t.Fatal(err) + } + if len(derChain) != 2 { + t.Fatalf("expected 2 certificates, got %d", len(derChain)) + } + if !bytes.Equal(derChain[1], []byte("intermediate")) { + t.Fatalf("unexpected last certificate: %q", string(derChain[1])) + } +} + +func TestDisabledProtocolsMask(t *testing.T) { + testCases := []struct { + name string + minVersion uint16 + maxVersion uint16 + want uint32 + }{ + { + name: "default range", + want: spProtAllTLSClients &^ (spProtTLS12Client | spProtTLS13Client), + }, + { + name: "default minimum with explicit max", + maxVersion: tls.VersionTLS12, + want: spProtAllTLSClients &^ spProtTLS12Client, + }, + { + name: "explicit tls10 range", + minVersion: tls.VersionTLS10, + maxVersion: tls.VersionTLS13, + want: 0, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + got := disabledProtocolsMask(testCase.minVersion, testCase.maxVersion) + if got != testCase.want { + t.Fatalf("disabledProtocolsMask(%#x, %#x) = %#x, want %#x", testCase.minVersion, testCase.maxVersion, got, testCase.want) + } + }) + } +} + +func TestClientCredentialCacheReusesVersionRange(t *testing.T) { + if err := CheckPlatform(); err != nil { + t.Skip(err) + } + first, err := NewClientContext(tls.VersionTLS12, tls.VersionTLS13, "localhost", []string{"h2"}) + if err != nil { + t.Fatal(err) + } + defer first.Close() + second, err := NewClientContext(tls.VersionTLS12, tls.VersionTLS13, "example.com", []string{"http/1.1"}) + if err != nil { + t.Fatal(err) + } + defer second.Close() + if first.credential != second.credential { + t.Fatal("expected same TLS version range to reuse credential") + } + + tls12Only, err := NewClientContext(tls.VersionTLS12, tls.VersionTLS12, "localhost", nil) + if err != nil { + t.Fatal(err) + } + defer tls12Only.Close() + if first.credential == tls12Only.credential { + t.Fatal("expected distinct TLS version range to use a distinct credential") + } + if first.credential.key.disabledProtocols != disabledProtocolsMask(tls.VersionTLS12, tls.VersionTLS13) { + t.Fatalf("unexpected cached disabled protocol mask: %#x", first.credential.key.disabledProtocols) + } +} + +func TestParseDecryptResultKeepsRenegotiateExtraToken(t *testing.T) { + input := []byte("plain-ticket") + result, err := parseDecryptResult(input, []secBuffer{ + {bufferType: secbufferExtra, cbBuffer: 6}, + }, true) + if err != nil { + t.Fatal(err) + } + if !result.Renegotiate { + t.Fatal("expected Renegotiate to be true") + } + if result.ConsumedTotal != len(input)-6 { + t.Fatalf("unexpected consumed total: %d", result.ConsumedTotal) + } + if !bytes.Equal(result.RenegotiateToken, []byte("ticket")) { + t.Fatalf("unexpected renegotiate token: %q", string(result.RenegotiateToken)) + } +} + +func TestParseDecryptResultKeepsRenegotiateWholeBufferWithoutExtra(t *testing.T) { + input := []byte("ticket") + result, err := parseDecryptResult(input, []secBuffer{ + {bufferType: secbufferData, cbBuffer: uint32(len(input)), pvBuffer: &input[0]}, + }, true) + if err != nil { + t.Fatal(err) + } + if !result.Renegotiate { + t.Fatal("expected Renegotiate to be true") + } + if result.ConsumedTotal != len(input) { + t.Fatalf("unexpected consumed total: %d", result.ConsumedTotal) + } + if !bytes.Equal(result.RenegotiateToken, input) { + t.Fatalf("unexpected renegotiate token: %q", string(result.RenegotiateToken)) + } +} + +func certChainContextForTest(certs ...*windows.CertContext) *windows.CertChainContext { + elements := make([]*windows.CertChainElement, 0, len(certs)) + for _, cert := range certs { + elements = append(elements, &windows.CertChainElement{CertContext: cert}) + } + simpleChain := &windows.CertSimpleChain{ + NumElements: uint32(len(elements)), + Elements: &elements[0], + } + chains := []*windows.CertSimpleChain{simpleChain} + return &windows.CertChainContext{ + ChainCount: 1, + Chains: &chains[0], + } +} + +func certContextForTest(der, issuer, subject []byte) *windows.CertContext { + certInfo := &windows.CertInfo{ + Issuer: certNameBlobForTest(issuer), + Subject: certNameBlobForTest(subject), + } + return &windows.CertContext{ + EncodedCert: &der[0], + Length: uint32(len(der)), + CertInfo: certInfo, + } +} + +func certNameBlobForTest(value []byte) windows.CertNameBlob { + return windows.CertNameBlob{ + Size: uint32(len(value)), + Data: (*byte)(unsafe.Pointer(&value[0])), + } +} diff --git a/common/schannel/syscall_windows.go b/common/schannel/syscall_windows.go new file mode 100644 index 0000000000..654869b664 --- /dev/null +++ b/common/schannel/syscall_windows.go @@ -0,0 +1,28 @@ +package schannel + +import ( + "syscall" + "unsafe" +) + +//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go syscall_windows.go + +// secur32.dll — SSPI / Schannel interface + +//sys sspiAcquireCredentialsHandle(principal *uint16, pkgname *uint16, credentialUse uint32, logonID *uint64, authData unsafe.Pointer, getKeyFn uintptr, getKeyArg uintptr, credential *secHandle, expiry *windows.Filetime) (ret syscall.Errno) = secur32.AcquireCredentialsHandleW +//sys sspiFreeCredentialsHandle(credential *secHandle) (ret syscall.Errno) = secur32.FreeCredentialsHandle +//sys sspiInitializeSecurityContext(credential *secHandle, context *secHandle, targetName *uint16, contextReq uint32, reserved1 uint32, targetDataRep uint32, input *secBufferDesc, reserved2 uint32, newContext *secHandle, output *secBufferDesc, contextAttr *uint32, expiry *windows.Filetime) (ret syscall.Errno) = secur32.InitializeSecurityContextW +//sys sspiDeleteSecurityContext(context *secHandle) (ret syscall.Errno) = secur32.DeleteSecurityContext +//sys sspiQueryContextAttributes(context *secHandle, attribute uint32, buffer unsafe.Pointer) (ret syscall.Errno) = secur32.QueryContextAttributesW +//sys sspiEncryptMessage(context *secHandle, qop uint32, message *secBufferDesc, sequenceNumber uint32) (ret syscall.Errno) = secur32.EncryptMessage +//sys sspiDecryptMessage(context *secHandle, message *secBufferDesc, sequenceNumber uint32, qop *uint32) (ret syscall.Errno) = secur32.DecryptMessage +//sys sspiFreeContextBuffer(buffer *byte) (ret syscall.Errno) = secur32.FreeContextBuffer + +// mkwinsyscall does not emit CompleteAuthToken for this package, so bind it manually. +var procCompleteAuthToken = modsecur32.NewProc("CompleteAuthToken") + +func sspiCompleteAuthToken(context *secHandle, token *secBufferDesc) (ret syscall.Errno) { + r0, _, _ := syscall.SyscallN(procCompleteAuthToken.Addr(), uintptr(unsafe.Pointer(context)), uintptr(unsafe.Pointer(token))) + ret = syscall.Errno(r0) + return +} diff --git a/common/schannel/types_windows.go b/common/schannel/types_windows.go new file mode 100644 index 0000000000..fd4f8b5439 --- /dev/null +++ b/common/schannel/types_windows.go @@ -0,0 +1,161 @@ +package schannel + +import ( + "syscall" + + "golang.org/x/sys/windows" +) + +const ( + unispNameW = "Microsoft Unified Security Protocol Provider" + + schCredentialsVersion = 5 + + secPkgCredOutbound = 2 + + iscReqSequenceDetect = 0x00000008 + iscReqReplayDetect = 0x00000004 + iscReqConfidentiality = 0x00000010 + iscReqAllocateMemory = 0x00000100 + iscReqStream = 0x00008000 + iscReqUseSuppliedCreds = 0x00000080 + iscReqManualCredValidation = 0x00080000 + iscReqExtendedError = 0x00004000 + + secbufferEmpty = 0 + secbufferData = 1 + secbufferToken = 2 + secbufferExtra = 5 + secbufferStreamTrailer = 6 + secbufferStreamHeader = 7 + secbufferApplicationProtocols = 18 + secbufferVersion = 0 + + secApplicationProtocolNegotiationExtALPN = 2 + + secApplicationProtocolNegotiationStatusSuccess = 1 + + schCredManualCredValidation = 0x00000008 + schCredNoDefaultCreds = 0x00000010 + schUseStrongCrypto = 0x00400000 + + spProtTLS10Client = 0x00000080 + spProtTLS11Client = 0x00000200 + spProtTLS12Client = 0x00000800 + spProtTLS13Client = 0x00002000 + + spProtAllTLSClients = spProtTLS10Client | spProtTLS11Client | spProtTLS12Client | spProtTLS13Client + + secpkgAttrStreamSizes = 4 + secpkgAttrConnectionInfo = 0x5A + secpkgAttrApplicationProtocol = 0x23 + secpkgAttrCipherInfo = 0x64 + secpkgAttrRemoteCertContext = 0x53 +) + +const ( + secEOK = syscall.Errno(windows.SEC_E_OK) + secICompleteNeeded = syscall.Errno(windows.SEC_I_COMPLETE_NEEDED) + secICompleteAndContinue = syscall.Errno(windows.SEC_I_COMPLETE_AND_CONTINUE) + secIContinueNeeded = syscall.Errno(windows.SEC_I_CONTINUE_NEEDED) + secIContextExpired = syscall.Errno(windows.SEC_I_CONTEXT_EXPIRED) + secIRenegotiate = syscall.Errno(windows.SEC_I_RENEGOTIATE) + secEIncompleteMessage = syscall.Errno(windows.SEC_E_INCOMPLETE_MESSAGE) + secEIncompleteCreds = syscall.Errno(windows.SEC_E_INCOMPLETE_CREDENTIALS) + secEBufferTooSmall = syscall.Errno(windows.SEC_E_BUFFER_TOO_SMALL) + secEMessageAltered = syscall.Errno(windows.SEC_E_MESSAGE_ALTERED) + secEContextExpired = syscall.Errno(windows.SEC_E_CONTEXT_EXPIRED) + secEUnsupportedFunc = syscall.Errno(windows.SEC_E_UNSUPPORTED_FUNCTION) + secEInvalidToken = syscall.Errno(windows.SEC_E_INVALID_TOKEN) + secELogonDenied = syscall.Errno(windows.SEC_E_LOGON_DENIED) + secEIllegalMessage = syscall.Errno(windows.SEC_E_ILLEGAL_MESSAGE) + secEWrongPrincipal = syscall.Errno(windows.SEC_E_WRONG_PRINCIPAL) + secECertUnknown = syscall.Errno(windows.SEC_E_CERT_UNKNOWN) + secECertExpired = syscall.Errno(windows.SEC_E_CERT_EXPIRED) + secEAlgorithmMismatch = syscall.Errno(windows.SEC_E_ALGORITHM_MISMATCH) + secEInternalError = syscall.Errno(windows.SEC_E_INTERNAL_ERROR) + secENoAuthenticatingAuthority = syscall.Errno(windows.SEC_E_NO_AUTHENTICATING_AUTHORITY) +) + +type secHandle struct { + lower uintptr + upper uintptr +} + +type secBuffer struct { + cbBuffer uint32 + bufferType uint32 + pvBuffer *byte +} + +type secBufferDesc struct { + ulVersion uint32 + cBuffers uint32 + pBuffers *secBuffer +} + +type schCredentials struct { + dwVersion uint32 + dwCredFormat uint32 + cCreds uint32 + paCred uintptr + hRootStore windows.Handle + cMappers uint32 + aphMappers uintptr + dwSessionLifespan uint32 + dwFlags uint32 + cTlsParameters uint32 + pTlsParameters *tlsParameters +} + +type tlsParameters struct { + _ uint32 // cAlpnIds + _ uintptr // rgstrAlpnIds + grbitDisabledProtocols uint32 + _ uint32 // cDisabledCrypto + _ uintptr // pDisabledCrypto + _ uint32 // dwFlags +} + +type secPkgContextStreamSizes struct { + cbHeader uint32 + cbTrailer uint32 + cbMaximumMessage uint32 + cBuffers uint32 + cbBlockSize uint32 +} + +type secPkgContextConnectionInfo struct { + dwProtocol uint32 + aiCipher uint32 + dwCipherStrength uint32 + aiHash uint32 + dwHashStrength uint32 + aiExch uint32 + dwExchStrength uint32 +} + +type secPkgContextApplicationProtocol struct { + protoNegoStatus uint32 + protoNegoExt uint32 + protocolIDSize byte + protocolID [255]byte +} + +type secPkgContextCipherInfo struct { + dwVersion uint32 + dwProtocol uint32 + dwCipherSuite uint32 + dwBaseCipherSuite uint32 + szCipherSuite [64]uint16 + szCipher [64]uint16 + dwCipherLen uint32 + dwCipherBlockLen uint32 + szHash [64]uint16 + dwHashLen uint32 + szExchange [64]uint16 + dwMinExchangeLen uint32 + dwMaxExchangeLen uint32 + szCertificate [64]uint16 + dwKeyType uint32 +} diff --git a/common/schannel/zsyscall_windows.go b/common/schannel/zsyscall_windows.go new file mode 100644 index 0000000000..85a7bc8087 --- /dev/null +++ b/common/schannel/zsyscall_windows.go @@ -0,0 +1,99 @@ +// Code generated by 'go generate'; DO NOT EDIT. + +package schannel + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +var _ unsafe.Pointer + +// Do the interface allocations only once for common +// Errno values. +const ( + errnoERROR_IO_PENDING = 997 +) + +var ( + errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) + errERROR_EINVAL error = syscall.EINVAL +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e syscall.Errno) error { + switch e { + case 0: + return errERROR_EINVAL + case errnoERROR_IO_PENDING: + return errERROR_IO_PENDING + } + // TODO: add more here, after collecting data on the common + // error values see on Windows. (perhaps when running + // all.bat?) + return e +} + +var ( + modsecur32 = windows.NewLazySystemDLL("secur32.dll") + + procAcquireCredentialsHandleW = modsecur32.NewProc("AcquireCredentialsHandleW") + procDecryptMessage = modsecur32.NewProc("DecryptMessage") + procDeleteSecurityContext = modsecur32.NewProc("DeleteSecurityContext") + procEncryptMessage = modsecur32.NewProc("EncryptMessage") + procFreeContextBuffer = modsecur32.NewProc("FreeContextBuffer") + procFreeCredentialsHandle = modsecur32.NewProc("FreeCredentialsHandle") + procInitializeSecurityContextW = modsecur32.NewProc("InitializeSecurityContextW") + procQueryContextAttributesW = modsecur32.NewProc("QueryContextAttributesW") +) + +func sspiAcquireCredentialsHandle(principal *uint16, pkgname *uint16, credentialUse uint32, logonID *uint64, authData unsafe.Pointer, getKeyFn uintptr, getKeyArg uintptr, credential *secHandle, expiry *windows.Filetime) (ret syscall.Errno) { + r0, _, _ := syscall.SyscallN(procAcquireCredentialsHandleW.Addr(), uintptr(unsafe.Pointer(principal)), uintptr(unsafe.Pointer(pkgname)), uintptr(credentialUse), uintptr(unsafe.Pointer(logonID)), uintptr(authData), uintptr(getKeyFn), uintptr(getKeyArg), uintptr(unsafe.Pointer(credential)), uintptr(unsafe.Pointer(expiry))) + ret = syscall.Errno(r0) + return +} + +func sspiDecryptMessage(context *secHandle, message *secBufferDesc, sequenceNumber uint32, qop *uint32) (ret syscall.Errno) { + r0, _, _ := syscall.SyscallN(procDecryptMessage.Addr(), uintptr(unsafe.Pointer(context)), uintptr(unsafe.Pointer(message)), uintptr(sequenceNumber), uintptr(unsafe.Pointer(qop))) + ret = syscall.Errno(r0) + return +} + +func sspiDeleteSecurityContext(context *secHandle) (ret syscall.Errno) { + r0, _, _ := syscall.SyscallN(procDeleteSecurityContext.Addr(), uintptr(unsafe.Pointer(context))) + ret = syscall.Errno(r0) + return +} + +func sspiEncryptMessage(context *secHandle, qop uint32, message *secBufferDesc, sequenceNumber uint32) (ret syscall.Errno) { + r0, _, _ := syscall.SyscallN(procEncryptMessage.Addr(), uintptr(unsafe.Pointer(context)), uintptr(qop), uintptr(unsafe.Pointer(message)), uintptr(sequenceNumber)) + ret = syscall.Errno(r0) + return +} + +func sspiFreeContextBuffer(buffer *byte) (ret syscall.Errno) { + r0, _, _ := syscall.SyscallN(procFreeContextBuffer.Addr(), uintptr(unsafe.Pointer(buffer))) + ret = syscall.Errno(r0) + return +} + +func sspiFreeCredentialsHandle(credential *secHandle) (ret syscall.Errno) { + r0, _, _ := syscall.SyscallN(procFreeCredentialsHandle.Addr(), uintptr(unsafe.Pointer(credential))) + ret = syscall.Errno(r0) + return +} + +func sspiInitializeSecurityContext(credential *secHandle, context *secHandle, targetName *uint16, contextReq uint32, reserved1 uint32, targetDataRep uint32, input *secBufferDesc, reserved2 uint32, newContext *secHandle, output *secBufferDesc, contextAttr *uint32, expiry *windows.Filetime) (ret syscall.Errno) { + r0, _, _ := syscall.SyscallN(procInitializeSecurityContextW.Addr(), uintptr(unsafe.Pointer(credential)), uintptr(unsafe.Pointer(context)), uintptr(unsafe.Pointer(targetName)), uintptr(contextReq), uintptr(reserved1), uintptr(targetDataRep), uintptr(unsafe.Pointer(input)), uintptr(reserved2), uintptr(unsafe.Pointer(newContext)), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(contextAttr)), uintptr(unsafe.Pointer(expiry))) + ret = syscall.Errno(r0) + return +} + +func sspiQueryContextAttributes(context *secHandle, attribute uint32, buffer unsafe.Pointer) (ret syscall.Errno) { + r0, _, _ := syscall.SyscallN(procQueryContextAttributesW.Addr(), uintptr(unsafe.Pointer(context)), uintptr(attribute), uintptr(buffer)) + ret = syscall.Errno(r0) + return +} diff --git a/common/settings/proxy_darwin.go b/common/settings/proxy_darwin.go index f03658a858..baaf6cedaa 100644 --- a/common/settings/proxy_darwin.go +++ b/common/settings/proxy_darwin.go @@ -2,16 +2,17 @@ package settings import ( "context" - "net/netip" "strconv" "strings" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/shell" "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service" ) type DarwinSystemProxy struct { @@ -24,7 +25,7 @@ type DarwinSystemProxy struct { } func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*DarwinSystemProxy, error) { - interfaceMonitor := adapter.RouterFromContext(ctx).InterfaceMonitor() + interfaceMonitor := service.FromContext[adapter.NetworkManager](ctx).InterfaceMonitor() if interfaceMonitor == nil { return nil, E.New("missing interface monitor") } @@ -33,7 +34,7 @@ func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bo serverAddr: serverAddr, supportSOCKS: supportSOCKS, } - proxy.element = interfaceMonitor.RegisterCallback(proxy.update) + proxy.element = interfaceMonitor.RegisterCallback(proxy.routeUpdate) return proxy, nil } @@ -65,25 +66,22 @@ func (p *DarwinSystemProxy) Disable() error { return err } -func (p *DarwinSystemProxy) update(event int) { - if event&tun.EventInterfaceUpdate == 0 { - return - } - if !p.isEnabled { +func (p *DarwinSystemProxy) routeUpdate(defaultInterface *control.Interface, flags int) { + if !p.isEnabled || defaultInterface == nil { return } _ = p.update0() } func (p *DarwinSystemProxy) update0() error { - newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified()) - if p.interfaceName == newInterfaceName { + newInterface := p.monitor.DefaultInterface() + if p.interfaceName == newInterface.Name { return nil } if p.interfaceName != "" { _ = p.Disable() } - p.interfaceName = newInterfaceName + p.interfaceName = newInterface.Name interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName) if err != nil { return err @@ -111,7 +109,7 @@ func getInterfaceDisplayName(name string) (string, error) { if err != nil { return "", err } - for _, deviceSpan := range strings.Split(string(content), "Ethernet Address") { + for deviceSpan := range strings.SplitSeq(string(content), "Ethernet Address") { if strings.Contains(deviceSpan, "Device: "+name) { substr := "Hardware Port: " deviceSpan = deviceSpan[strings.Index(deviceSpan, substr)+len(substr):] diff --git a/common/settings/time_stub.go b/common/settings/time_stub.go deleted file mode 100644 index 06204913dc..0000000000 --- a/common/settings/time_stub.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build !(windows || linux || darwin) - -package settings - -import ( - "os" - "time" -) - -func SetSystemTime(nowTime time.Time) error { - return os.ErrInvalid -} diff --git a/common/settings/time_unix.go b/common/settings/time_unix.go deleted file mode 100644 index 9eff244dfb..0000000000 --- a/common/settings/time_unix.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build linux || darwin - -package settings - -import ( - "time" - - "golang.org/x/sys/unix" -) - -func SetSystemTime(nowTime time.Time) error { - timeVal := unix.NsecToTimeval(nowTime.UnixNano()) - return unix.Settimeofday(&timeVal) -} diff --git a/common/settings/time_windows.go b/common/settings/time_windows.go deleted file mode 100644 index 5d95e3ba68..0000000000 --- a/common/settings/time_windows.go +++ /dev/null @@ -1,32 +0,0 @@ -package settings - -import ( - "time" - "unsafe" - - "golang.org/x/sys/windows" -) - -func SetSystemTime(nowTime time.Time) error { - var systemTime windows.Systemtime - systemTime.Year = uint16(nowTime.Year()) - systemTime.Month = uint16(nowTime.Month()) - systemTime.Day = uint16(nowTime.Day()) - systemTime.Hour = uint16(nowTime.Hour()) - systemTime.Minute = uint16(nowTime.Minute()) - systemTime.Second = uint16(nowTime.Second()) - systemTime.Milliseconds = uint16(nowTime.UnixMilli() - nowTime.Unix()*1000) - - dllKernel32 := windows.NewLazySystemDLL("kernel32.dll") - proc := dllKernel32.NewProc("SetSystemTime") - - _, _, err := proc.Call( - uintptr(unsafe.Pointer(&systemTime)), - ) - - if err != nil && err.Error() != "The operation completed successfully." { - return err - } - - return nil -} diff --git a/common/settings/wifi.go b/common/settings/wifi.go new file mode 100644 index 0000000000..62bef706ba --- /dev/null +++ b/common/settings/wifi.go @@ -0,0 +1,9 @@ +package settings + +import "github.com/sagernet/sing-box/adapter" + +type WIFIMonitor interface { + ReadWIFIState() adapter.WIFIState + Start() error + Close() error +} diff --git a/common/settings/wifi_linux.go b/common/settings/wifi_linux.go new file mode 100644 index 0000000000..9deed3c800 --- /dev/null +++ b/common/settings/wifi_linux.go @@ -0,0 +1,46 @@ +package settings + +import ( + "github.com/sagernet/sing-box/adapter" + E "github.com/sagernet/sing/common/exceptions" +) + +type LinuxWIFIMonitor struct { + monitor WIFIMonitor +} + +func NewWIFIMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) { + monitors := []func(func(adapter.WIFIState)) (WIFIMonitor, error){ + newNetworkManagerMonitor, + newIWDMonitor, + newWpaSupplicantMonitor, + newConnManMonitor, + } + var errors []error + for _, factory := range monitors { + monitor, err := factory(callback) + if err == nil { + return &LinuxWIFIMonitor{monitor: monitor}, nil + } + errors = append(errors, err) + } + return nil, E.Cause(E.Errors(errors...), "no supported WIFI manager found") +} + +func (m *LinuxWIFIMonitor) ReadWIFIState() adapter.WIFIState { + return m.monitor.ReadWIFIState() +} + +func (m *LinuxWIFIMonitor) Start() error { + if m.monitor != nil { + return m.monitor.Start() + } + return nil +} + +func (m *LinuxWIFIMonitor) Close() error { + if m.monitor != nil { + return m.monitor.Close() + } + return nil +} diff --git a/common/settings/wifi_linux_connman.go b/common/settings/wifi_linux_connman.go new file mode 100644 index 0000000000..46f6ea17b4 --- /dev/null +++ b/common/settings/wifi_linux_connman.go @@ -0,0 +1,168 @@ +//go:build linux + +package settings + +import ( + "context" + "strings" + "time" + + "github.com/sagernet/sing-box/adapter" + + "github.com/godbus/dbus/v5" +) + +type connmanMonitor struct { + conn *dbus.Conn + callback func(adapter.WIFIState) + cancel context.CancelFunc + signalChan chan *dbus.Signal +} + +func newConnManMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) { + conn, err := dbus.ConnectSystemBus() + if err != nil { + return nil, err + } + cmObj := conn.Object("net.connman", "/") + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + call := cmObj.CallWithContext(ctx, "net.connman.Manager.GetServices", 0) + if call.Err != nil { + conn.Close() + return nil, call.Err + } + return &connmanMonitor{conn: conn, callback: callback}, nil +} + +func (m *connmanMonitor) ReadWIFIState() adapter.WIFIState { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + cmObj := m.conn.Object("net.connman", "/") + var services []any + 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) + if !ok || len(servicePair) != 2 { + continue + } + + serviceProps, ok := servicePair[1].(map[string]dbus.Variant) + if !ok { + continue + } + + typeVariant, hasType := serviceProps["Type"] + if !hasType { + continue + } + serviceType, ok := typeVariant.Value().(string) + if !ok || serviceType != "wifi" { + continue + } + + stateVariant, hasState := serviceProps["State"] + if !hasState { + continue + } + state, ok := stateVariant.Value().(string) + if !ok || (state != "online" && state != "ready") { + continue + } + + nameVariant, hasName := serviceProps["Name"] + if !hasName { + continue + } + ssid, ok := nameVariant.Value().(string) + if !ok || ssid == "" { + continue + } + + bssidVariant, hasBSSID := serviceProps["BSSID"] + if !hasBSSID { + return adapter.WIFIState{SSID: ssid} + } + bssid, ok := bssidVariant.Value().(string) + if !ok { + return adapter.WIFIState{SSID: ssid} + } + + return adapter.WIFIState{ + SSID: ssid, + BSSID: strings.ToUpper(strings.ReplaceAll(bssid, ":", "")), + } + } + + return adapter.WIFIState{} +} + +func (m *connmanMonitor) Start() error { + if m.callback == nil { + return nil + } + ctx, cancel := context.WithCancel(context.Background()) + m.cancel = cancel + + m.signalChan = make(chan *dbus.Signal, 10) + m.conn.Signal(m.signalChan) + + err := m.conn.AddMatchSignal( + dbus.WithMatchInterface("net.connman.Service"), + dbus.WithMatchSender("net.connman"), + ) + if err != nil { + return err + } + + state := m.ReadWIFIState() + go m.monitorSignals(ctx, m.signalChan, state) + m.callback(state) + + return nil +} + +func (m *connmanMonitor) monitorSignals(ctx context.Context, signalChan chan *dbus.Signal, lastState adapter.WIFIState) { + for { + select { + case <-ctx.Done(): + return + case signal, ok := <-signalChan: + if !ok { + return + } + // godbus Signal.Name uses "interface.member" format (e.g. "net.connman.Service.PropertyChanged"), + // not just the member name. This differs from the D-Bus signal member in the match rule. + if signal.Name == "net.connman.Service.PropertyChanged" { + state := m.ReadWIFIState() + if state != lastState { + lastState = state + m.callback(state) + } + } + } + } +} + +func (m *connmanMonitor) Close() error { + if m.cancel != nil { + m.cancel() + } + if m.signalChan != nil { + m.conn.RemoveSignal(m.signalChan) + close(m.signalChan) + } + if m.conn != nil { + m.conn.RemoveMatchSignal( + dbus.WithMatchInterface("net.connman.Service"), + dbus.WithMatchSender("net.connman"), + ) + return m.conn.Close() + } + return nil +} diff --git a/common/settings/wifi_linux_iwd.go b/common/settings/wifi_linux_iwd.go new file mode 100644 index 0000000000..327f9c4797 --- /dev/null +++ b/common/settings/wifi_linux_iwd.go @@ -0,0 +1,190 @@ +//go:build linux + +package settings + +import ( + "context" + "strings" + "time" + + "github.com/sagernet/sing-box/adapter" + + "github.com/godbus/dbus/v5" +) + +type iwdMonitor struct { + conn *dbus.Conn + callback func(adapter.WIFIState) + cancel context.CancelFunc + signalChan chan *dbus.Signal +} + +func newIWDMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) { + conn, err := dbus.ConnectSystemBus() + if err != nil { + return nil, err + } + iwdObj := conn.Object("net.connman.iwd", "/") + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + call := iwdObj.CallWithContext(ctx, "org.freedesktop.DBus.ObjectManager.GetManagedObjects", 0) + if call.Err != nil { + conn.Close() + return nil, call.Err + } + return &iwdMonitor{conn: conn, callback: callback}, nil +} + +func (m *iwdMonitor) ReadWIFIState() adapter.WIFIState { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + iwdObj := m.conn.Object("net.connman.iwd", "/") + var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant + err := iwdObj.CallWithContext(ctx, "org.freedesktop.DBus.ObjectManager.GetManagedObjects", 0).Store(&objects) + if err != nil { + return adapter.WIFIState{} + } + + for _, interfaces := range objects { + stationProps, hasStation := interfaces["net.connman.iwd.Station"] + if !hasStation { + continue + } + + stateVariant, hasState := stationProps["State"] + if !hasState { + continue + } + state, ok := stateVariant.Value().(string) + if !ok || state != "connected" { + continue + } + + connectedNetworkVariant, hasNetwork := stationProps["ConnectedNetwork"] + if !hasNetwork { + continue + } + networkPath, ok := connectedNetworkVariant.Value().(dbus.ObjectPath) + if !ok || networkPath == "/" { + continue + } + + networkInterfaces, hasNetworkPath := objects[networkPath] + if !hasNetworkPath { + continue + } + + networkProps, hasNetworkInterface := networkInterfaces["net.connman.iwd.Network"] + if !hasNetworkInterface { + continue + } + + nameVariant, hasName := networkProps["Name"] + if !hasName { + continue + } + ssid, ok := nameVariant.Value().(string) + if !ok { + continue + } + + connectedBSSVariant, hasBSS := stationProps["ConnectedAccessPoint"] + if !hasBSS { + return adapter.WIFIState{SSID: ssid} + } + bssPath, ok := connectedBSSVariant.Value().(dbus.ObjectPath) + if !ok || bssPath == "/" { + return adapter.WIFIState{SSID: ssid} + } + + bssInterfaces, hasBSSPath := objects[bssPath] + if !hasBSSPath { + return adapter.WIFIState{SSID: ssid} + } + + bssProps, hasBSSInterface := bssInterfaces["net.connman.iwd.BasicServiceSet"] + if !hasBSSInterface { + return adapter.WIFIState{SSID: ssid} + } + + addressVariant, hasAddress := bssProps["Address"] + if !hasAddress { + return adapter.WIFIState{SSID: ssid} + } + bssid, ok := addressVariant.Value().(string) + if !ok { + return adapter.WIFIState{SSID: ssid} + } + + return adapter.WIFIState{ + SSID: ssid, + BSSID: strings.ToUpper(strings.ReplaceAll(bssid, ":", "")), + } + } + + return adapter.WIFIState{} +} + +func (m *iwdMonitor) Start() error { + if m.callback == nil { + return nil + } + ctx, cancel := context.WithCancel(context.Background()) + m.cancel = cancel + + m.signalChan = make(chan *dbus.Signal, 10) + m.conn.Signal(m.signalChan) + + err := m.conn.AddMatchSignal( + dbus.WithMatchInterface("org.freedesktop.DBus.Properties"), + dbus.WithMatchSender("net.connman.iwd"), + ) + if err != nil { + return err + } + + state := m.ReadWIFIState() + go m.monitorSignals(ctx, m.signalChan, state) + m.callback(state) + + return nil +} + +func (m *iwdMonitor) monitorSignals(ctx context.Context, signalChan chan *dbus.Signal, lastState adapter.WIFIState) { + for { + select { + case <-ctx.Done(): + return + case signal, ok := <-signalChan: + if !ok { + return + } + if signal.Name == "org.freedesktop.DBus.Properties.PropertiesChanged" { + state := m.ReadWIFIState() + if state != lastState { + lastState = state + m.callback(state) + } + } + } + } +} + +func (m *iwdMonitor) Close() error { + if m.cancel != nil { + m.cancel() + } + if m.signalChan != nil { + m.conn.RemoveSignal(m.signalChan) + close(m.signalChan) + } + if m.conn != nil { + m.conn.RemoveMatchSignal( + dbus.WithMatchInterface("org.freedesktop.DBus.Properties"), + dbus.WithMatchSender("net.connman.iwd"), + ) + return m.conn.Close() + } + return nil +} diff --git a/common/settings/wifi_linux_nm.go b/common/settings/wifi_linux_nm.go new file mode 100644 index 0000000000..77d897d41a --- /dev/null +++ b/common/settings/wifi_linux_nm.go @@ -0,0 +1,165 @@ +//go:build linux + +package settings + +import ( + "context" + "strings" + "time" + + "github.com/sagernet/sing-box/adapter" + + "github.com/godbus/dbus/v5" +) + +type networkManagerMonitor struct { + conn *dbus.Conn + callback func(adapter.WIFIState) + cancel context.CancelFunc + signalChan chan *dbus.Signal +} + +func newNetworkManagerMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) { + conn, err := dbus.ConnectSystemBus() + if err != nil { + return nil, err + } + nmObj := conn.Object("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager") + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + var state uint32 + err = nmObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager", "State").Store(&state) + if err != nil { + conn.Close() + return nil, err + } + return &networkManagerMonitor{conn: conn, callback: callback}, nil +} + +func (m *networkManagerMonitor) ReadWIFIState() adapter.WIFIState { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + nmObj := m.conn.Object("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager") + + var activeConnectionPaths []dbus.ObjectPath + err := nmObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager", "ActiveConnections").Store(&activeConnectionPaths) + if err != nil || len(activeConnectionPaths) == 0 { + return adapter.WIFIState{} + } + + for _, connectionPath := range activeConnectionPaths { + connObj := m.conn.Object("org.freedesktop.NetworkManager", connectionPath) + + var devicePaths []dbus.ObjectPath + err = connObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.Connection.Active", "Devices").Store(&devicePaths) + if err != nil || len(devicePaths) == 0 { + continue + } + + for _, devicePath := range devicePaths { + deviceObj := m.conn.Object("org.freedesktop.NetworkManager", devicePath) + + var deviceType uint32 + err = deviceObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.Device", "DeviceType").Store(&deviceType) + if err != nil || deviceType != 2 { + continue + } + + var accessPointPath dbus.ObjectPath + err = deviceObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.Device.Wireless", "ActiveAccessPoint").Store(&accessPointPath) + if err != nil || accessPointPath == "/" { + continue + } + + apObj := m.conn.Object("org.freedesktop.NetworkManager", accessPointPath) + + var ssidBytes []byte + err = apObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.AccessPoint", "Ssid").Store(&ssidBytes) + if err != nil { + continue + } + + var hwAddress string + err = apObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.AccessPoint", "HwAddress").Store(&hwAddress) + if err != nil { + continue + } + + ssid := strings.TrimSpace(string(ssidBytes)) + if ssid == "" { + continue + } + + return adapter.WIFIState{ + SSID: ssid, + BSSID: strings.ToUpper(strings.ReplaceAll(hwAddress, ":", "")), + } + } + } + + return adapter.WIFIState{} +} + +func (m *networkManagerMonitor) Start() error { + if m.callback == nil { + return nil + } + ctx, cancel := context.WithCancel(context.Background()) + m.cancel = cancel + + m.signalChan = make(chan *dbus.Signal, 10) + m.conn.Signal(m.signalChan) + + err := m.conn.AddMatchSignal( + dbus.WithMatchSender("org.freedesktop.NetworkManager"), + dbus.WithMatchInterface("org.freedesktop.DBus.Properties"), + ) + if err != nil { + return err + } + + state := m.ReadWIFIState() + go m.monitorSignals(ctx, m.signalChan, state) + m.callback(state) + + return nil +} + +func (m *networkManagerMonitor) monitorSignals(ctx context.Context, signalChan chan *dbus.Signal, lastState adapter.WIFIState) { + for { + select { + case <-ctx.Done(): + return + case signal, ok := <-signalChan: + if !ok { + return + } + if signal.Name == "org.freedesktop.DBus.Properties.PropertiesChanged" { + state := m.ReadWIFIState() + if state != lastState { + lastState = state + m.callback(state) + } + } + } + } +} + +func (m *networkManagerMonitor) Close() error { + if m.cancel != nil { + m.cancel() + } + if m.signalChan != nil { + m.conn.RemoveSignal(m.signalChan) + close(m.signalChan) + } + if m.conn != nil { + m.conn.RemoveMatchSignal( + dbus.WithMatchSender("org.freedesktop.NetworkManager"), + dbus.WithMatchInterface("org.freedesktop.DBus.Properties"), + ) + return m.conn.Close() + } + return nil +} diff --git a/common/settings/wifi_linux_wpa.go b/common/settings/wifi_linux_wpa.go new file mode 100644 index 0000000000..192c2f0115 --- /dev/null +++ b/common/settings/wifi_linux_wpa.go @@ -0,0 +1,226 @@ +//nolint:unused +package settings + +import ( + "bufio" + "context" + "fmt" + "net" + "os" + "path/filepath" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/sagernet/sing-box/adapter" +) + +var wpaSocketCounter atomic.Uint64 + +type wpaSupplicantMonitor struct { + socketPath string + callback func(adapter.WIFIState) + cancel context.CancelFunc + monitorConn *net.UnixConn + connMutex sync.Mutex +} + +func newWpaSupplicantMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) { + socketDirs := []string{"/var/run/wpa_supplicant", "/run/wpa_supplicant"} + for _, socketDir := range socketDirs { + entries, err := os.ReadDir(socketDir) + if err != nil { + continue + } + for _, entry := range entries { + if entry.IsDir() || entry.Name() == "." || entry.Name() == ".." { + continue + } + socketPath := filepath.Join(socketDir, entry.Name()) + id := wpaSocketCounter.Add(1) + localAddr := &net.UnixAddr{Name: fmt.Sprintf("@sing-box-wpa-%d-%d", os.Getpid(), id), Net: "unixgram"} + remoteAddr := &net.UnixAddr{Name: socketPath, Net: "unixgram"} + conn, err := net.DialUnix("unixgram", localAddr, remoteAddr) + if err != nil { + continue + } + conn.Close() + return &wpaSupplicantMonitor{socketPath: socketPath, callback: callback}, nil + } + } + return nil, os.ErrNotExist +} + +func (m *wpaSupplicantMonitor) ReadWIFIState() adapter.WIFIState { + id := wpaSocketCounter.Add(1) + localAddr := &net.UnixAddr{Name: fmt.Sprintf("@sing-box-wpa-%d-%d", os.Getpid(), id), Net: "unixgram"} + remoteAddr := &net.UnixAddr{Name: m.socketPath, Net: "unixgram"} + conn, err := net.DialUnix("unixgram", localAddr, remoteAddr) + if err != nil { + return adapter.WIFIState{} + } + defer conn.Close() + + conn.SetDeadline(time.Now().Add(3 * time.Second)) + + status, err := m.sendCommand(conn, "STATUS") + if err != nil { + return adapter.WIFIState{} + } + + var ssid, bssid string + var connected bool + scanner := bufio.NewScanner(strings.NewReader(status)) + for scanner.Scan() { + line := scanner.Text() + if after, ok := strings.CutPrefix(line, "wpa_state="); ok { + state := after + 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 + } + } + + if !connected || ssid == "" { + return adapter.WIFIState{} + } + + return adapter.WIFIState{ + SSID: ssid, + BSSID: strings.ToUpper(strings.ReplaceAll(bssid, ":", "")), + } +} + +// sendCommand sends a command to wpa_supplicant and returns the response. +// Commands are sent without trailing newlines per the wpa_supplicant control +// interface protocol - the official wpa_ctrl.c sends raw command strings. +func (m *wpaSupplicantMonitor) sendCommand(conn *net.UnixConn, command string) (string, error) { + _, err := conn.Write([]byte(command)) + if err != nil { + return "", err + } + + buf := make([]byte, 4096) + n, err := conn.Read(buf) + if err != nil { + return "", err + } + + response := string(buf[:n]) + if strings.HasPrefix(response, "FAIL") { + return "", os.ErrInvalid + } + + return strings.TrimSpace(response), nil +} + +func (m *wpaSupplicantMonitor) Start() error { + if m.callback == nil { + return nil + } + ctx, cancel := context.WithCancel(context.Background()) + m.cancel = cancel + + state := m.ReadWIFIState() + go m.monitorEvents(ctx, state) + m.callback(state) + + return nil +} + +func (m *wpaSupplicantMonitor) monitorEvents(ctx context.Context, lastState adapter.WIFIState) { + var consecutiveErrors int + var debounceTimer *time.Timer + var debounceMutex sync.Mutex + + localAddr := &net.UnixAddr{Name: fmt.Sprintf("@sing-box-wpa-mon-%d", os.Getpid()), Net: "unixgram"} + remoteAddr := &net.UnixAddr{Name: m.socketPath, Net: "unixgram"} + conn, err := net.DialUnix("unixgram", localAddr, remoteAddr) + if err != nil { + return + } + defer conn.Close() + + m.connMutex.Lock() + m.monitorConn = conn + m.connMutex.Unlock() + + // ATTACH/DETACH commands use os_strcmp() for exact matching in wpa_supplicant, + // so they must be sent without trailing newlines. + // See: https://w1.fi/cgit/hostap/tree/wpa_supplicant/ctrl_iface_unix.c + _, err = conn.Write([]byte("ATTACH")) + if err != nil { + return + } + + buf := make([]byte, 4096) + n, err := conn.Read(buf) + if err != nil || !strings.HasPrefix(string(buf[:n]), "OK") { + return + } + + for { + select { + case <-ctx.Done(): + debounceMutex.Lock() + if debounceTimer != nil { + debounceTimer.Stop() + } + debounceMutex.Unlock() + conn.Write([]byte("DETACH")) + return + default: + } + + conn.SetReadDeadline(time.Now().Add(30 * time.Second)) + n, err := conn.Read(buf) + if err != nil { + if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + continue + } + select { + case <-ctx.Done(): + return + default: + } + consecutiveErrors++ + if consecutiveErrors > 10 { + return + } + time.Sleep(time.Second) + continue + } + consecutiveErrors = 0 + + msg := string(buf[:n]) + if strings.Contains(msg, "CTRL-EVENT-CONNECTED") || strings.Contains(msg, "CTRL-EVENT-DISCONNECTED") { + debounceMutex.Lock() + if debounceTimer != nil { + debounceTimer.Stop() + } + debounceTimer = time.AfterFunc(500*time.Millisecond, func() { + state := m.ReadWIFIState() + if state != lastState { + lastState = state + m.callback(state) + } + }) + debounceMutex.Unlock() + } + } +} + +func (m *wpaSupplicantMonitor) Close() error { + if m.cancel != nil { + m.cancel() + } + m.connMutex.Lock() + if m.monitorConn != nil { + m.monitorConn.Close() + } + m.connMutex.Unlock() + return nil +} diff --git a/common/settings/wifi_stub.go b/common/settings/wifi_stub.go new file mode 100644 index 0000000000..499212e4c2 --- /dev/null +++ b/common/settings/wifi_stub.go @@ -0,0 +1,28 @@ +//go:build !linux && !windows + +//nolint:unused +package settings + +import ( + "os" + + "github.com/sagernet/sing-box/adapter" +) + +type stubWIFIMonitor struct{} + +func NewWIFIMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) { + return nil, os.ErrInvalid +} + +func (m *stubWIFIMonitor) ReadWIFIState() adapter.WIFIState { + return adapter.WIFIState{} +} + +func (m *stubWIFIMonitor) Start() error { + return nil +} + +func (m *stubWIFIMonitor) Close() error { + return nil +} diff --git a/common/settings/wifi_windows.go b/common/settings/wifi_windows.go new file mode 100644 index 0000000000..91b0d479c7 --- /dev/null +++ b/common/settings/wifi_windows.go @@ -0,0 +1,144 @@ +//go:build windows + +package settings + +import ( + "context" + "fmt" + "strings" + "sync" + "syscall" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common/winwlanapi" + + "golang.org/x/sys/windows" +) + +type windowsWIFIMonitor struct { + handle windows.Handle + callback func(adapter.WIFIState) + cancel context.CancelFunc + lastState adapter.WIFIState + mutex sync.Mutex +} + +func NewWIFIMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) { + handle, err := winwlanapi.OpenHandle() + if err != nil { + return nil, err + } + + interfaces, err := winwlanapi.EnumInterfaces(handle) + if err != nil { + winwlanapi.CloseHandle(handle) + return nil, err + } + if len(interfaces) == 0 { + winwlanapi.CloseHandle(handle) + return nil, fmt.Errorf("no wireless interfaces found") + } + + return &windowsWIFIMonitor{ + handle: handle, + callback: callback, + }, nil +} + +func (m *windowsWIFIMonitor) ReadWIFIState() adapter.WIFIState { + interfaces, err := winwlanapi.EnumInterfaces(m.handle) + if err != nil || len(interfaces) == 0 { + return adapter.WIFIState{} + } + + for _, iface := range interfaces { + if iface.InterfaceState != winwlanapi.InterfaceStateConnected { + continue + } + + guid := iface.InterfaceGUID + attrs, err := winwlanapi.QueryCurrentConnection(m.handle, &guid) + if err != nil { + continue + } + + ssidLength := attrs.AssociationAttributes.SSID.Length + if ssidLength == 0 || ssidLength > winwlanapi.Dot11SSIDMaxLength { + continue + } + + ssid := string(attrs.AssociationAttributes.SSID.SSID[:ssidLength]) + bssid := formatBSSID(attrs.AssociationAttributes.BSSID) + + return adapter.WIFIState{ + SSID: strings.TrimSpace(ssid), + BSSID: bssid, + } + } + + return adapter.WIFIState{} +} + +func formatBSSID(mac winwlanapi.Dot11MacAddress) string { + return fmt.Sprintf("%02X%02X%02X%02X%02X%02X", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]) +} + +func (m *windowsWIFIMonitor) Start() error { + if m.callback == nil { + return nil + } + + ctx, cancel := context.WithCancel(context.Background()) + m.cancel = cancel + + m.lastState = m.ReadWIFIState() + + callbackFunc := func(data *winwlanapi.NotificationData, callbackContext uintptr) uintptr { + if data.NotificationSource != winwlanapi.NotificationSourceACM { + return 0 + } + switch data.NotificationCode { + case winwlanapi.NotificationACMConnectionComplete, + winwlanapi.NotificationACMDisconnected: + m.checkAndNotify() + } + return 0 + } + + callbackPointer := syscall.NewCallback(callbackFunc) + + err := winwlanapi.RegisterNotification(m.handle, winwlanapi.NotificationSourceACM, callbackPointer, 0) + if err != nil { + cancel() + return err + } + + go func() { + <-ctx.Done() + }() + + m.callback(m.lastState) + return nil +} + +func (m *windowsWIFIMonitor) checkAndNotify() { + m.mutex.Lock() + defer m.mutex.Unlock() + + state := m.ReadWIFIState() + if state != m.lastState { + m.lastState = state + if m.callback != nil { + m.callback(state) + } + } +} + +func (m *windowsWIFIMonitor) Close() error { + if m.cancel != nil { + m.cancel() + } + winwlanapi.UnregisterNotification(m.handle) + return winwlanapi.CloseHandle(m.handle) +} diff --git a/common/sniff/bittorrent.go b/common/sniff/bittorrent.go new file mode 100644 index 0000000000..e4d9f4b8f6 --- /dev/null +++ b/common/sniff/bittorrent.go @@ -0,0 +1,107 @@ +package sniff + +import ( + "bytes" + "context" + "encoding/binary" + "io" + "os" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + E "github.com/sagernet/sing/common/exceptions" +) + +const ( + trackerConnectFlag = 0 + trackerProtocolID = 0x41727101980 + trackerConnectMinSize = 16 +) + +// BitTorrent detects if the stream is a BitTorrent connection. +// For the BitTorrent protocol specification, see https://www.bittorrent.org/beps/bep_0003.html +func BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error { + var first byte + err := binary.Read(reader, binary.BigEndian, &first) + if err != nil { + return E.Cause1(ErrNeedMoreData, err) + } + + if first != 19 { + return os.ErrInvalid + } + + const header = "BitTorrent protocol" + var protocol [19]byte + var n int + n, err = reader.Read(protocol[:]) + if string(protocol[:n]) != header[:n] { + return os.ErrInvalid + } + if err != nil { + return E.Cause1(ErrNeedMoreData, err) + } + if n < 19 { + return ErrNeedMoreData + } + + metadata.Protocol = C.ProtocolBitTorrent + return nil +} + +// UTP detects if the packet is a uTP connection packet. +// For the uTP protocol specification, see +// 1. https://www.bittorrent.org/beps/bep_0029.html +// 2. https://github.com/bittorrent/libutp/blob/2b364cbb0650bdab64a5de2abb4518f9f228ec44/utp_internal.cpp#L112 +func UTP(_ context.Context, metadata *adapter.InboundContext, packet []byte) error { + // A valid uTP packet must be at least 20 bytes long. + if len(packet) < 20 { + return os.ErrInvalid + } + + version := packet[0] & 0x0F + ty := packet[0] >> 4 + if version != 1 || ty > 4 { + return os.ErrInvalid + } + + // Validate the extensions + extension := packet[1] + reader := bytes.NewReader(packet[20:]) + for extension != 0 { + err := binary.Read(reader, binary.BigEndian, &extension) + if err != nil { + return err + } + if extension > 0x04 { + return os.ErrInvalid + } + var length byte + err = binary.Read(reader, binary.BigEndian, &length) + if err != nil { + return err + } + _, err = reader.Seek(int64(length), io.SeekCurrent) + if err != nil { + return err + } + } + metadata.Protocol = C.ProtocolBitTorrent + return nil +} + +// UDPTracker detects if the packet is a UDP Tracker Protocol packet. +// For the UDP Tracker Protocol specification, see https://www.bittorrent.org/beps/bep_0015.html +func UDPTracker(_ context.Context, metadata *adapter.InboundContext, packet []byte) error { + if len(packet) < trackerConnectMinSize { + return os.ErrInvalid + } + if binary.BigEndian.Uint64(packet[:8]) != trackerProtocolID { + return os.ErrInvalid + } + if binary.BigEndian.Uint32(packet[8:12]) != trackerConnectFlag { + return os.ErrInvalid + } + metadata.Protocol = C.ProtocolBitTorrent + return nil +} diff --git a/common/sniff/bittorrent_test.go b/common/sniff/bittorrent_test.go new file mode 100644 index 0000000000..fcb5f6fa42 --- /dev/null +++ b/common/sniff/bittorrent_test.go @@ -0,0 +1,110 @@ +package sniff_test + +import ( + "bytes" + "context" + "encoding/hex" + "testing" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/sniff" + C "github.com/sagernet/sing-box/constant" + + "github.com/stretchr/testify/require" +) + +func TestSniffBittorrent(t *testing.T) { + t.Parallel() + + packets := []string{ + "13426974546f7272656e742070726f746f636f6c0000000000100000e21ea9569b69bab33c97851d0298bdfa89bc90922d5554313631302dea812fcd6a3563e3be40c1d1", + "13426974546f7272656e742070726f746f636f6c00000000001000052aa4f5a7e209e54b32803d43670971c4c8caaa052d5452333030302d653369733079647675763638", + "13426974546f7272656e742070726f746f636f6c00000000001000052aa4f5a7e209e54b32803d43670971c4c8caaa052d5452343035302d6f7a316c6e79377931716130", + } + + for _, pkt := range packets { + pkt, err := hex.DecodeString(pkt) + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt)) + require.NoError(t, err) + require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol) + } +} + +func TestSniffIncompleteBittorrent(t *testing.T) { + t.Parallel() + + pkt, err := hex.DecodeString("13426974546f7272656e74") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt)) + require.ErrorIs(t, err, sniff.ErrNeedMoreData) +} + +func TestSniffNotBittorrent(t *testing.T) { + t.Parallel() + + pkt, err := hex.DecodeString("13426974546f7272656e75") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt)) + require.NotEmpty(t, err) + require.NotErrorIs(t, err, sniff.ErrNeedMoreData) +} + +func TestSniffUTP(t *testing.T) { + t.Parallel() + + packets := []string{ + "010041a282d7ee7b583afb160004000006d8318da776968f92d666f7963f32dae23ba0d2c810d8b8209cc4939f54fde9eeaa521c2c20c9ba7f43f4fb0375f28de06643b5e3ca4685ab7ac76adca99783be72ef05ed59ef4234f5712b75b4c7c0d7bee8fe2ca20ad626ba5bb0ffcc16bf06790896f888048cf72716419a07db1a3dca4550fbcea75b53e97235168a221cf3e553dfbb723961bd719fab038d86e0ecb74747f5a2cd669de1c4b9ad375f3a492d09d98cdfad745435625401315bbba98d35d32086299801377b93495a63a9efddb8d05f5b37a5c5b1c0a25e917f12007bb5e05013ada8aff544fab8cadf61d80ddb0b60f12741e44515a109d144fd53ef845acb4b5ccf0d6fc302d7003d76df3fc3423bb0237301c9e88f900c2d392a8e0fdb36d143cf7527a93fd0a2638b746e72f6699fffcd4fd15348fce780d4caa04382fd9faf1ca0ae377ca805da7536662b84f5ee18dd3ae38fcb095a7543e55f9069ae92c8cf54ae44e97b558d35e2545c66601ed2149cbc32bd6df199a2be7cf0da8b2ff137e0d23e776bc87248425013876d3a3cc31a83b424b752bd0346437f24b532978005d8f5b1b0be1a37a2489c32a18a9ad3118e3f9d30eb299bffae18e1f0677c2a5c185e62519093fe6bc2b7339299ea50a587989f726ca6443a75dd5bb936f6367c6355d80fae53ff529d740b2e5576e3eefdf1fdbfc69c3c8d8ac750512635de63e054bee1d3b689bc1b2bc3d2601e42a00b5c89066d173d4ae7ffedfd2274e5cf6d868fbe640aedb69b8246142f00b32d459974287537ddd5373460dcbc92f5cfdd7a3ed6020822ae922d947893752ca1983d0d32977374c384ac8f5ab566859019b7351526b9f13e932037a55bb052d9deb3b3c23317e0784fdc51a64f2159bfea3b069cf5caf02ee2c3c1a6b6b427bb16165713e8802d95b5c8ed77953690e994bd38c9ae113fedaf6ee7fc2b96c032ceafc2a530ad0422e84546b9c6ad8ef6ea02fa508abddd1805c38a7b42e9b7c971b1b636865ebec06ed754bb404cd6b4e6cc8cb77bd4a0c43410d5cd5ef8fe853a66d49b3b9e06cb141236cdbfdd5761601dc54d1250b86c660e0f898fe62526fdd9acf0eab60a3bbbb2151970461f28f10b31689594bea646c4b03ee197d63bdef4e5a7c22716b3bb9494a83b78ecd81b338b80ac6c09c43485b1b09ba41c74343832c78f0520c1d659ac9eb1502094141e82fb9e5e620970ebc0655514c43c294a7714cbf9a499d277daf089f556398a01589a77494bec8bfb60a108f3813b55368672b88c1af40f6b3c8b513f7c70c3e0efce85228b8b9ec67ba0393f9f7305024d8e2da6a26cf85613d14f249170ce1000089df4c9c260df7f8292aa2ecb5d5bac97656d59aa248caedea2d198e51ce87baece338716d114b458de02d65c9ff808ca5b5b73723b4d1e962d9ac2d98176544dc9984cf8554d07820ef3dd0861cfe57b478328046380de589adad94ee44743ffac73bb7361feca5d56f07cf8ce75080e261282ae30350d7882679b15cab9e7e53ddf93310b33f7390ae5d318bb53f387e6af5d0ef4f947fc9cb8e7e38b52c7f8d772ece6156b38d88796ea19df02c53723b44df7c76315a0de9462f27287e682d2b4cda1a68fe00d7e48c51ee981be44e1ca940fb5190c12655edb4a83c3a4f33e48a015692df4f0b3d61656e362aca657b5ae8c12db5a0db3db1e45135ee918b66918f40e53c4f83e9da0cddfe63f736ae751ab3837a30ae3220d8e8e311487093a7b90c7e7e40dd54ca750e19452f9193aa892aa6a6229ab493dadae988b1724f7898ee69c36d3eb7364c4adbeca811cfe2065873e78c2b6dfdf1595f7a7831c07e03cda82e4f86f76438dfb2b07c13638ce7b509cfa71b88b5102b39a203b423202088e1c2103319cb32c13c1e546ff8612fa194c95a7808ab767c265a1bd5fa0efed5c8ec1701876a00ec8", + "01001ecb68176f215d04326300100000dbcf30292d14b54e9ee2d115ee5b8ebc7fad3e882d4fcdd0c14c6b917c11cb4c6a9f410b52a33ae97c2ac77c7a2b122b8955e09af3c5c595f1b2e79ca57cfe44c44e069610773b9bc9ba223d7f6b383e3adddd03fb88a8476028e30979c2ef321ffc97c5c132bcf9ac5b410bbb5ec6cefca3c7209202a14c5ae922b6b157b0a80249d13ffe5b996af0bc8e54ba576d148372494303e7ead0602b05b9c8fc97d48508a028a04d63a1fd28b0edfcd5c51715f63188b53eefede98a76912dca98518551a8856567307a56a702cbfcc115ea0c755b418bc2c7b57721239b82f09fb24328a4b0ce0f109bcb2a64e04b8aadb1f8487585425acdf8fc4ec8ea93cfcec5ac098bb29d42ddef6e46b03f34a5de28316726699b7cb5195c33e5c48abe87d591d63f9991c84c30819d186d6e0e95fd83c8dff07aa669c4430989bcaccfeacb9bcadbdb4d8f1964dbeb9687745656edd30b21c66cc0a1d742a78717d134a19a7f02d285a4973b1a198c00cfdff4676608dc4f3e817e3463c3b4e2c80d3e8d4fbac541a58a2fb7ad6939f607f8144eff6c8b0adc28ee5609ea158987519892fb", + "21001ecb6817f2805d044fd700100000dbd03029", + "410277ef0b1fb1f60000000000040000c233000000080000000000000000", + } + + for _, pkt := range packets { + pkt, err := hex.DecodeString(pkt) + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.UTP(context.TODO(), &metadata, pkt) + require.NoError(t, err) + require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol) + } +} + +func TestSniffUDPTracker(t *testing.T) { + t.Parallel() + + connectPackets := []string{ + "00000417271019800000000078e90560", + "00000417271019800000000022c5d64d", + "000004172710198000000000b3863541", + } + + for _, pkt := range connectPackets { + pkt, err := hex.DecodeString(pkt) + require.NoError(t, err) + + var metadata adapter.InboundContext + err = sniff.UDPTracker(context.TODO(), &metadata, pkt) + require.NoError(t, err) + require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol) + } +} + +func TestSniffNotUTP(t *testing.T) { + t.Parallel() + + packets := []string{ + "0102736470696e674958d580121500000000000079aaed6717a39c27b07c0c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + } + for _, pkt := range packets { + pkt, err := hex.DecodeString(pkt) + require.NoError(t, err) + + var metadata adapter.InboundContext + err = sniff.UTP(context.TODO(), &metadata, pkt) + require.Error(t, err) + } +} diff --git a/common/sniff/dns.go b/common/sniff/dns.go index d69f3b158c..7125a08e0e 100644 --- a/common/sniff/dns.go +++ b/common/sniff/dns.go @@ -5,51 +5,53 @@ import ( "encoding/binary" "io" "os" - "time" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" - M "github.com/sagernet/sing/common/metadata" - "github.com/sagernet/sing/common/task" + E "github.com/sagernet/sing/common/exceptions" mDNS "github.com/miekg/dns" ) -func StreamDomainNameQuery(readCtx context.Context, reader io.Reader) (*adapter.InboundContext, error) { +func StreamDomainNameQuery(readCtx context.Context, metadata *adapter.InboundContext, reader io.Reader) error { var length uint16 err := binary.Read(reader, binary.BigEndian, &length) if err != nil { - return nil, err + return E.Cause1(ErrNeedMoreData, err) } - if length == 0 { - return nil, os.ErrInvalid + if length < 12 { + return os.ErrInvalid } buffer := buf.NewSize(int(length)) defer buffer.Release() - - readCtx, cancel := context.WithTimeout(readCtx, time.Millisecond*100) - var readTask task.Group - readTask.Append0(func(ctx context.Context) error { - return common.Error(buffer.ReadFullFrom(reader, buffer.FreeLen())) - }) - err = readTask.Run(readCtx) - cancel() + var n int + n, err = buffer.ReadFullFrom(reader, buffer.FreeLen()) + packet := buffer.Bytes() + if n > 2 && packet[2]&0x80 != 0 { // QR + return os.ErrInvalid + } + if n > 5 && packet[4] == 0 && packet[5] == 0 { // QDCOUNT + return os.ErrInvalid + } + for i := 6; i < 10; i++ { + // ANCOUNT, NSCOUNT + if n > i && packet[i] != 0 { + return os.ErrInvalid + } + } if err != nil { - return nil, err + return E.Cause1(ErrNeedMoreData, err) } - return DomainNameQuery(readCtx, buffer.Bytes()) + return DomainNameQuery(readCtx, metadata, packet) } -func DomainNameQuery(ctx context.Context, packet []byte) (*adapter.InboundContext, error) { +func DomainNameQuery(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error { var msg mDNS.Msg err := msg.Unpack(packet) - if err != nil { - return nil, err - } - if len(msg.Question) == 0 || msg.Question[0].Qclass != mDNS.ClassINET || !M.IsDomainName(msg.Question[0].Name) { - return nil, os.ErrInvalid + if err != nil || msg.Response || len(msg.Question) == 0 || len(msg.Answer) > 0 || len(msg.Ns) > 0 { + return err } - return &adapter.InboundContext{Protocol: C.ProtocolDNS}, nil + metadata.Protocol = C.ProtocolDNS + return nil } diff --git a/common/sniff/dns_test.go b/common/sniff/dns_test.go new file mode 100644 index 0000000000..d78b0bf591 --- /dev/null +++ b/common/sniff/dns_test.go @@ -0,0 +1,53 @@ +package sniff_test + +import ( + "bytes" + "context" + "encoding/hex" + "testing" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/sniff" + C "github.com/sagernet/sing-box/constant" + + "github.com/stretchr/testify/require" +) + +func TestSniffDNS(t *testing.T) { + t.Parallel() + query, err := hex.DecodeString("740701000001000000000000012a06676f6f676c6503636f6d0000010001") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.DomainNameQuery(context.TODO(), &metadata, query) + require.NoError(t, err) + require.Equal(t, C.ProtocolDNS, metadata.Protocol) +} + +func TestSniffStreamDNS(t *testing.T) { + t.Parallel() + query, err := hex.DecodeString("001e740701000001000000000000012a06676f6f676c6503636f6d0000010001") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query)) + require.NoError(t, err) + require.Equal(t, C.ProtocolDNS, metadata.Protocol) +} + +func TestSniffIncompleteStreamDNS(t *testing.T) { + t.Parallel() + query, err := hex.DecodeString("001e740701000001000000000000") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query)) + require.ErrorIs(t, err, sniff.ErrNeedMoreData) +} + +func TestSniffNotStreamDNS(t *testing.T) { + t.Parallel() + query, err := hex.DecodeString("001e740701000000000000000000") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query)) + require.NotEmpty(t, err) + require.NotErrorIs(t, err, sniff.ErrNeedMoreData) +} diff --git a/common/sniff/dtls.go b/common/sniff/dtls.go new file mode 100644 index 0000000000..e2704a27ae --- /dev/null +++ b/common/sniff/dtls.go @@ -0,0 +1,32 @@ +package sniff + +import ( + "context" + "os" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" +) + +func DTLSRecord(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error { + const fixedHeaderSize = 13 + if len(packet) < fixedHeaderSize { + return os.ErrInvalid + } + contentType := packet[0] + switch contentType { + case 20, 21, 22, 23, 25: + default: + return os.ErrInvalid + } + versionMajor := packet[1] + if versionMajor != 0xfe { + return os.ErrInvalid + } + versionMinor := packet[2] + if versionMinor != 0xff && versionMinor != 0xfd { + return os.ErrInvalid + } + metadata.Protocol = C.ProtocolDTLS + return nil +} diff --git a/common/sniff/dtls_test.go b/common/sniff/dtls_test.go new file mode 100644 index 0000000000..48c9b42b31 --- /dev/null +++ b/common/sniff/dtls_test.go @@ -0,0 +1,33 @@ +package sniff_test + +import ( + "context" + "encoding/hex" + "testing" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/sniff" + C "github.com/sagernet/sing-box/constant" + + "github.com/stretchr/testify/require" +) + +func TestSniffDTLSClientHello(t *testing.T) { + t.Parallel() + packet, err := hex.DecodeString("16fefd0000000000000000007e010000720000000000000072fefd668a43523798e064bd806d0c87660de9c611a59bbdfc3892c4e072d94f2cafc40000000cc02bc02fc00ac014c02cc0300100003c000d0010000e0403050306030401050106010807ff01000100000a00080006001d00170018000b00020100000e000900060008000700010000170000") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.DTLSRecord(context.Background(), &metadata, packet) + require.NoError(t, err) + require.Equal(t, metadata.Protocol, C.ProtocolDTLS) +} + +func TestSniffDTLSClientApplicationData(t *testing.T) { + t.Parallel() + packet, err := hex.DecodeString("17fefd000100000000000100440001000000000001a4f682b77ecadd10f3f3a2f78d90566212366ff8209fd77314f5a49352f9bb9bd12f4daba0b4736ae29e46b9714d3b424b3e6d0234736619b5aa0d3f") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.DTLSRecord(context.Background(), &metadata, packet) + require.NoError(t, err) + require.Equal(t, metadata.Protocol, C.ProtocolDTLS) +} diff --git a/common/sniff/http.go b/common/sniff/http.go index faf05fd304..012f2c99df 100644 --- a/common/sniff/http.go +++ b/common/sniff/http.go @@ -3,18 +3,26 @@ package sniff import ( std_bufio "bufio" "context" + "errors" "io" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" + E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/protocol/http" ) -func HTTPHost(ctx context.Context, reader io.Reader) (*adapter.InboundContext, error) { +func HTTPHost(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error { request, err := http.ReadRequest(std_bufio.NewReader(reader)) if err != nil { - return nil, err + if errors.Is(err, io.ErrUnexpectedEOF) { + return E.Cause1(ErrNeedMoreData, err) + } else { + return err + } } - return &adapter.InboundContext{Protocol: C.ProtocolHTTP, Domain: M.ParseSocksaddr(request.Host).AddrString()}, nil + metadata.Protocol = C.ProtocolHTTP + metadata.Domain = M.ParseSocksaddr(request.Host).AddrString() + return nil } diff --git a/common/sniff/http_test.go b/common/sniff/http_test.go index 98100e092a..9f64efa85e 100644 --- a/common/sniff/http_test.go +++ b/common/sniff/http_test.go @@ -5,6 +5,7 @@ import ( "strings" "testing" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/sniff" "github.com/stretchr/testify/require" @@ -13,7 +14,8 @@ import ( func TestSniffHTTP1(t *testing.T) { t.Parallel() pkt := "GET / HTTP/1.1\r\nHost: www.google.com\r\nAccept: */*\r\n\r\n" - metadata, err := sniff.HTTPHost(context.Background(), strings.NewReader(pkt)) + var metadata adapter.InboundContext + err := sniff.HTTPHost(context.Background(), &metadata, strings.NewReader(pkt)) require.NoError(t, err) require.Equal(t, metadata.Domain, "www.google.com") } @@ -21,7 +23,8 @@ func TestSniffHTTP1(t *testing.T) { func TestSniffHTTP1WithPort(t *testing.T) { t.Parallel() pkt := "GET / HTTP/1.1\r\nHost: www.gov.cn:8080\r\nAccept: */*\r\n\r\n" - metadata, err := sniff.HTTPHost(context.Background(), strings.NewReader(pkt)) + var metadata adapter.InboundContext + err := sniff.HTTPHost(context.Background(), &metadata, strings.NewReader(pkt)) require.NoError(t, err) require.Equal(t, metadata.Domain, "www.gov.cn") } diff --git a/common/sniff/internal/qtls/qtls.go b/common/sniff/internal/qtls/qtls.go index 9742de1e5d..72414c615c 100644 --- a/common/sniff/internal/qtls/qtls.go +++ b/common/sniff/internal/qtls/qtls.go @@ -54,9 +54,8 @@ 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) explicitNonceLen() int { return 0 } +func (f *xorNonceAEAD) NonceSize() int { return 8 } // 64-bit sequence number +func (f *xorNonceAEAD) Overhead() int { return f.aead.Overhead() } func (f *xorNonceAEAD) Seal(out, nonce, plaintext, additionalData []byte) []byte { for i, b := range nonce { diff --git a/common/sniff/ntp.go b/common/sniff/ntp.go new file mode 100644 index 0000000000..8b844c5bd6 --- /dev/null +++ b/common/sniff/ntp.go @@ -0,0 +1,58 @@ +package sniff + +import ( + "context" + "encoding/binary" + "os" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" +) + +func NTP(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error { + // NTP packets must be at least 48 bytes long (standard NTP header size). + pLen := len(packet) + if pLen < 48 { + return os.ErrInvalid + } + // Check the LI (Leap Indicator) and Version Number (VN) in the first byte. + // We'll primarily focus on ensuring the version is valid for NTP. + // Many NTP versions are used, but let's check for generally accepted ones (3 & 4 for IPv4, plus potential extensions/customizations) + firstByte := packet[0] + li := (firstByte >> 6) & 0x03 // Extract LI + vn := (firstByte >> 3) & 0x07 // Extract VN + mode := firstByte & 0x07 // Extract Mode + + // Leap Indicator should be a valid value (0-3). + if li > 3 { + return os.ErrInvalid + } + + // Version Check (common NTP versions are 3 and 4) + if vn != 3 && vn != 4 { + return os.ErrInvalid + } + + // Check the Mode field for a client request (Mode 3). This validates it *is* a request. + if mode != 3 { + return os.ErrInvalid + } + + // Check Root Delay and Root Dispersion. While not strictly *required* for a request, + // we can check if they appear to be reasonable values (not excessively large). + rootDelay := binary.BigEndian.Uint32(packet[4:8]) + rootDispersion := binary.BigEndian.Uint32(packet[8:12]) + + // Check for unreasonably large root delay and dispersion. NTP RFC specifies max values of approximately 16 seconds. + // Convert to milliseconds for easy comparison. Each unit is 1/2^16 seconds. + if float64(rootDelay)/65536.0 > 16.0 { + return os.ErrInvalid + } + if float64(rootDispersion)/65536.0 > 16.0 { + return os.ErrInvalid + } + + metadata.Protocol = C.ProtocolNTP + + return nil +} diff --git a/common/sniff/ntp_test.go b/common/sniff/ntp_test.go new file mode 100644 index 0000000000..5da947853c --- /dev/null +++ b/common/sniff/ntp_test.go @@ -0,0 +1,33 @@ +package sniff_test + +import ( + "context" + "encoding/hex" + "os" + "testing" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/sniff" + C "github.com/sagernet/sing-box/constant" + + "github.com/stretchr/testify/require" +) + +func TestSniffNTP(t *testing.T) { + t.Parallel() + packet, err := hex.DecodeString("1b0006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.NTP(context.Background(), &metadata, packet) + require.NoError(t, err) + require.Equal(t, metadata.Protocol, C.ProtocolNTP) +} + +func TestSniffNTPFailed(t *testing.T) { + t.Parallel() + packet, err := hex.DecodeString("400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.NTP(context.Background(), &metadata, packet) + require.ErrorIs(t, err, os.ErrInvalid) +} diff --git a/common/sniff/quic.go b/common/sniff/quic.go index 55dc1c0023..049bd2c14e 100644 --- a/common/sniff/quic.go +++ b/common/sniff/quic.go @@ -5,95 +5,97 @@ import ( "context" "crypto" "crypto/aes" + "crypto/tls" "encoding/binary" "io" "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/ja3" "github.com/sagernet/sing-box/common/sniff/internal/qtls" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" "golang.org/x/crypto/hkdf" ) -func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContext, error) { +func QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error { reader := bytes.NewReader(packet) - typeByte, err := reader.ReadByte() if err != nil { - return nil, err + return err } if typeByte&0x40 == 0 { - return nil, E.New("bad type byte") + return E.New("bad type byte") } var versionNumber uint32 err = binary.Read(reader, binary.BigEndian, &versionNumber) if err != nil { - return nil, err + return err } if versionNumber != qtls.VersionDraft29 && versionNumber != qtls.Version1 && versionNumber != qtls.Version2 { - return nil, E.New("bad version") + return E.New("bad version") } packetType := (typeByte & 0x30) >> 4 if packetType == 0 && versionNumber == qtls.Version2 || packetType == 2 && versionNumber != qtls.Version2 || packetType > 2 { - return nil, E.New("bad packet type") + return E.New("bad packet type") } destConnIDLen, err := reader.ReadByte() if err != nil { - return nil, err + return err } if destConnIDLen == 0 || destConnIDLen > 20 { - return nil, E.New("bad destination connection id length") + return E.New("bad destination connection id length") } destConnID := make([]byte, destConnIDLen) _, err = io.ReadFull(reader, destConnID) if err != nil { - return nil, err + return err } srcConnIDLen, err := reader.ReadByte() if err != nil { - return nil, err + return err } _, err = io.CopyN(io.Discard, reader, int64(srcConnIDLen)) if err != nil { - return nil, err + return err } tokenLen, err := qtls.ReadUvarint(reader) if err != nil { - return nil, err + return err } _, err = io.CopyN(io.Discard, reader, int64(tokenLen)) if err != nil { - return nil, err + return err } packetLen, err := qtls.ReadUvarint(reader) if err != nil { - return nil, err + return err } hdrLen := int(reader.Size()) - reader.Len() if hdrLen+int(packetLen) > len(packet) { - return nil, os.ErrInvalid + return os.ErrInvalid } _, err = io.CopyN(io.Discard, reader, 4) if err != nil { - return nil, err + return err } pnBytes := make([]byte, aes.BlockSize) _, err = io.ReadFull(reader, pnBytes) if err != nil { - return nil, err + return err } var salt []byte @@ -117,7 +119,7 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex hpKey := qtls.HKDFExpandLabel(crypto.SHA256, secret, []byte{}, hkdfHeaderProtectionLabel, 16) block, err := aes.NewCipher(hpKey) if err != nil { - return nil, err + return err } mask := make([]byte, aes.BlockSize) block.Encrypt(mask, pnBytes) @@ -129,7 +131,7 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex } packetNumberLength := newPacket[0]&0x3 + 1 if hdrLen+int(packetNumberLength) > int(packetLen)+hdrLen { - return nil, os.ErrInvalid + return os.ErrInvalid } var packetNumber uint32 switch packetNumberLength { @@ -142,7 +144,7 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex case 4: packetNumber = binary.BigEndian.Uint32(newPacket[hdrLen:]) default: - return nil, E.New("bad packet number length") + return E.New("bad packet number length") } extHdrLen := hdrLen + int(packetNumberLength) copy(newPacket[extHdrLen:hdrLen+4], packet[extHdrLen:]) @@ -166,138 +168,206 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex binary.BigEndian.PutUint64(nonce[len(nonce)-8:], uint64(packetNumber)) decrypted, err := cipher.Open(newPacket[extHdrLen:extHdrLen], nonce, data, newPacket[:extHdrLen]) if err != nil { - return nil, err + return err } var frameType byte - var frameLen uint64 - var fragments []struct { - offset uint64 - length uint64 - payload []byte - } + var fragments []qCryptoFragment decryptedReader := bytes.NewReader(decrypted) + const ( + frameTypePadding = 0x00 + frameTypePing = 0x01 + frameTypeAck = 0x02 + frameTypeAck2 = 0x03 + frameTypeCrypto = 0x06 + frameTypeConnectionClose = 0x1c + ) + var frameTypeList []uint8 for { frameType, err = decryptedReader.ReadByte() if err == io.EOF { break } + frameTypeList = append(frameTypeList, frameType) switch frameType { - case 0x00: // PADDING + case frameTypePadding: continue - case 0x01: // PING + case frameTypePing: continue - case 0x02, 0x03: // ACK + case frameTypeAck, frameTypeAck2: _, err = qtls.ReadUvarint(decryptedReader) // Largest Acknowledged if err != nil { - return nil, err + return err } _, err = qtls.ReadUvarint(decryptedReader) // ACK Delay if err != nil { - return nil, err + return err } ackRangeCount, err := qtls.ReadUvarint(decryptedReader) // ACK Range Count if err != nil { - return nil, err + return err } _, err = qtls.ReadUvarint(decryptedReader) // First ACK Range if err != nil { - return nil, err + return err } for i := 0; i < int(ackRangeCount); i++ { _, err = qtls.ReadUvarint(decryptedReader) // Gap if err != nil { - return nil, err + return err } _, err = qtls.ReadUvarint(decryptedReader) // ACK Range Length if err != nil { - return nil, err + return err } } if frameType == 0x03 { _, err = qtls.ReadUvarint(decryptedReader) // ECT0 Count if err != nil { - return nil, err + return err } _, err = qtls.ReadUvarint(decryptedReader) // ECT1 Count if err != nil { - return nil, err + return err } _, err = qtls.ReadUvarint(decryptedReader) // ECN-CE Count if err != nil { - return nil, err + return err } } - case 0x06: // CRYPTO + case frameTypeCrypto: var offset uint64 offset, err = qtls.ReadUvarint(decryptedReader) if err != nil { - return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, err + return err } var length uint64 length, err = qtls.ReadUvarint(decryptedReader) if err != nil { - return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, err + return err } index := len(decrypted) - decryptedReader.Len() - fragments = append(fragments, struct { - offset uint64 - length uint64 - payload []byte - }{offset, length, decrypted[index : index+int(length)]}) - frameLen += length + fragments = append(fragments, qCryptoFragment{offset, length, decrypted[index : index+int(length)]}) _, err = decryptedReader.Seek(int64(length), io.SeekCurrent) if err != nil { - return nil, err + return err } - case 0x1c: // CONNECTION_CLOSE + case frameTypeConnectionClose: _, err = qtls.ReadUvarint(decryptedReader) // Error Code if err != nil { - return nil, err + return err } _, err = qtls.ReadUvarint(decryptedReader) // Frame Type if err != nil { - return nil, err + return err } var length uint64 length, err = qtls.ReadUvarint(decryptedReader) // Reason Phrase Length if err != nil { - return nil, err + return err } _, err = decryptedReader.Seek(int64(length), io.SeekCurrent) // Reason Phrase if err != nil { - return nil, err + return err } default: - return nil, os.ErrInvalid + return os.ErrInvalid } } - tlsHdr := make([]byte, 5) - tlsHdr[0] = 0x16 - binary.BigEndian.PutUint16(tlsHdr[1:], uint16(0x0303)) - binary.BigEndian.PutUint16(tlsHdr[3:], uint16(frameLen)) + if metadata.SniffContext != nil { + fragments = append(fragments, metadata.SniffContext.([]qCryptoFragment)...) + metadata.SniffContext = nil + } + var frameLen uint64 + for _, fragment := range fragments { + frameLen += fragment.length + } + buffer := buf.NewSize(5 + int(frameLen)) + defer buffer.Release() + buffer.WriteByte(0x16) + binary.Write(buffer, binary.BigEndian, uint16(0x0303)) + binary.Write(buffer, binary.BigEndian, uint16(frameLen)) var index uint64 var length int - var readers []io.Reader - readers = append(readers, bytes.NewReader(tlsHdr)) find: for { for _, fragment := range fragments { if fragment.offset == index { - readers = append(readers, bytes.NewReader(fragment.payload)) + buffer.Write(fragment.payload) index = fragment.offset + fragment.length length++ continue find } } - if length == len(fragments) { + break + } + metadata.Protocol = C.ProtocolQUIC + fingerprint, err := ja3.Compute(buffer.Bytes()) + if err != nil { + metadata.SniffContext = fragments + return E.Cause1(ErrNeedMoreData, err) + } + metadata.Domain = fingerprint.ServerName + for metadata.Client == "" { + if len(frameTypeList) == 1 { + metadata.Client = C.ClientFirefox + break + } + if frameTypeList[0] == frameTypeCrypto && isZero(frameTypeList[1:]) { + if len(fingerprint.Versions) == 2 && fingerprint.Versions[0]&ja3.GreaseBitmask == 0x0A0A && + len(fingerprint.EllipticCurves) == 5 && fingerprint.EllipticCurves[0]&ja3.GreaseBitmask == 0x0A0A { + metadata.Client = C.ClientSafari + break + } + if len(fingerprint.CipherSuites) == 1 && fingerprint.CipherSuites[0] == tls.TLS_AES_256_GCM_SHA384 && + len(fingerprint.EllipticCurves) == 1 && fingerprint.EllipticCurves[0] == uint16(tls.X25519) && + len(fingerprint.SignatureAlgorithms) == 1 && fingerprint.SignatureAlgorithms[0] == uint16(tls.ECDSAWithP256AndSHA256) { + metadata.Client = C.ClientSafari + break + } + } + + if frameTypeList[len(frameTypeList)-1] == frameTypeCrypto && isZero(frameTypeList[:len(frameTypeList)-1]) { + metadata.Client = C.ClientQUICGo + break + } + + if count(frameTypeList, frameTypeCrypto) > 1 || count(frameTypeList, frameTypePing) > 0 { + if isQUICGo(fingerprint) { + metadata.Client = C.ClientQUICGo + } else { + metadata.Client = C.ClientChromium + } break } - return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, E.New("bad fragments") + + metadata.Client = C.ClientUnknown + //nolint:staticcheck + break } - metadata, err := TLSClientHello(ctx, io.MultiReader(readers...)) - if err != nil { - return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, err + return nil +} + +func isZero(slices []uint8) bool { + for _, slice := range slices { + if slice != 0 { + return false + } } - metadata.Protocol = C.ProtocolQUIC - return metadata, nil + return true +} + +func count(slices []uint8, value uint8) int { + var times int + for _, slice := range slices { + if slice == value { + times++ + } + } + return times +} + +type qCryptoFragment struct { + offset uint64 + length uint64 + payload []byte } diff --git a/common/sniff/quic_blacklist.go b/common/sniff/quic_blacklist.go new file mode 100644 index 0000000000..bdf9cdb111 --- /dev/null +++ b/common/sniff/quic_blacklist.go @@ -0,0 +1,24 @@ +package sniff + +import ( + "slices" + + "github.com/sagernet/sing-box/common/ja3" +) + +const ( + // X25519Kyber768Draft00 - post-quantum curve used by Go crypto/tls + x25519Kyber768Draft00 uint16 = 0x11EC // 4588 + // renegotiation_info extension used by Go crypto/tls + extensionRenegotiationInfo uint16 = 0xFF01 // 65281 +) + +// isQUICGo detects native quic-go by checking for Go crypto/tls specific features. +// 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 + } + return slices.Contains(fingerprint.Extensions, extensionRenegotiationInfo) +} diff --git a/common/sniff/quic_capture_test.go b/common/sniff/quic_capture_test.go new file mode 100644 index 0000000000..7af3b6a226 --- /dev/null +++ b/common/sniff/quic_capture_test.go @@ -0,0 +1,188 @@ +package sniff_test + +import ( + "context" + "crypto/tls" + "encoding/hex" + "errors" + "net" + "testing" + "time" + + "github.com/sagernet/quic-go" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/sniff" + + "github.com/stretchr/testify/require" +) + +func TestSniffQUICQuicGoFingerprint(t *testing.T) { + t.Parallel() + const testSNI = "test.example.com" + + udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}) + require.NoError(t, err) + defer udpConn.Close() + + serverAddr := udpConn.LocalAddr().(*net.UDPAddr) + packetsChan := make(chan [][]byte, 1) + + go func() { + var packets [][]byte + udpConn.SetReadDeadline(time.Now().Add(3 * time.Second)) + for range 10 { + buf := make([]byte, 2048) + n, _, err := udpConn.ReadFromUDP(buf) + if err != nil { + break + } + packets = append(packets, buf[:n]) + } + packetsChan <- packets + }() + + clientConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}) + require.NoError(t, err) + defer clientConn.Close() + + tlsConfig := &tls.Config{ + ServerName: testSNI, + InsecureSkipVerify: true, + NextProtos: []string{"h3"}, + } + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + _, _ = quic.Dial(ctx, clientConn, serverAddr, tlsConfig, &quic.Config{}) + + select { + case packets := <-packetsChan: + t.Logf("Captured %d packets", len(packets)) + + var metadata adapter.InboundContext + for i, pkt := range packets { + err := sniff.QUICClientHello(context.Background(), &metadata, pkt) + t.Logf("Packet %d: err=%v, domain=%s, client=%s", i, err, metadata.Domain, metadata.Client) + if metadata.Domain != "" { + break + } + } + + t.Logf("\n=== quic-go TLS Fingerprint Analysis ===") + t.Logf("Domain: %s", metadata.Domain) + t.Logf("Client: %s", metadata.Client) + t.Logf("Protocol: %s", metadata.Protocol) + + // The client should be identified as quic-go, not chromium + // Current issue: it's being identified as chromium + if metadata.Client == "chromium" { + t.Log("WARNING: quic-go is being misidentified as chromium!") + } + + case <-time.After(5 * time.Second): + t.Fatal("Timeout") + } +} + +func TestSniffQUICInitialFromQuicGo(t *testing.T) { + t.Parallel() + + const testSNI = "test.example.com" + + // Create UDP listener to capture ALL initial packets + udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}) + require.NoError(t, err) + defer udpConn.Close() + + serverAddr := udpConn.LocalAddr().(*net.UDPAddr) + + // Channel to receive captured packets + packetsChan := make(chan [][]byte, 1) + + // Start goroutine to capture packets + go func() { + var packets [][]byte + udpConn.SetReadDeadline(time.Now().Add(3 * time.Second)) + for range 5 { // Capture up to 5 packets + buf := make([]byte, 2048) + n, _, err := udpConn.ReadFromUDP(buf) + if err != nil { + break + } + packets = append(packets, buf[:n]) + } + packetsChan <- packets + }() + + // Create QUIC client connection (will fail but we capture the initial packet) + clientConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}) + require.NoError(t, err) + defer clientConn.Close() + + tlsConfig := &tls.Config{ + ServerName: testSNI, + InsecureSkipVerify: true, + NextProtos: []string{"h3"}, + } + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + // This will fail (no server) but sends initial packet + _, _ = quic.Dial(ctx, clientConn, serverAddr, tlsConfig, &quic.Config{}) + + // Wait for captured packets + select { + case packets := <-packetsChan: + t.Logf("Captured %d QUIC packets", len(packets)) + + for i, packet := range packets { + t.Logf("Packet %d: length=%d, first 30 bytes: %x", i, len(packet), packet[:min(30, len(packet))]) + } + + // Test sniffer with first packet + if len(packets) > 0 { + var metadata adapter.InboundContext + err := sniff.QUICClientHello(context.Background(), &metadata, packets[0]) + + t.Logf("First packet sniff error: %v", err) + t.Logf("Protocol: %s", metadata.Protocol) + t.Logf("Domain: %s", metadata.Domain) + t.Logf("Client: %s", metadata.Client) + + // If first packet needs more data, try with subsequent packets + // IMPORTANT: reuse metadata to accumulate CRYPTO fragments via SniffContext + if errors.Is(err, sniff.ErrNeedMoreData) && len(packets) > 1 { + t.Log("First packet needs more data, trying subsequent packets with shared context...") + for i := 1; i < len(packets); i++ { + // Reuse same metadata to accumulate fragments + err = sniff.QUICClientHello(context.Background(), &metadata, packets[i]) + t.Logf("Packet %d sniff result: err=%v, domain=%s, sniffCtx=%v", i, err, metadata.Domain, metadata.SniffContext != nil) + if metadata.Domain != "" || (err != nil && !errors.Is(err, sniff.ErrNeedMoreData)) { + break + } + } + } + + // Print hex dump for debugging + t.Logf("First packet hex:\n%s", hex.Dump(packets[0][:min(256, len(packets[0]))])) + + // Log final results + t.Logf("Final: Protocol=%s, Domain=%s, Client=%s", metadata.Protocol, metadata.Domain, metadata.Client) + + // Verify SNI extraction + if metadata.Domain == "" { + t.Errorf("Failed to extract SNI, expected: %s", testSNI) + } else { + require.Equal(t, testSNI, metadata.Domain, "SNI should match") + } + + // Check client identification - quic-go should be identified as quic-go, not chromium + t.Logf("Client identified as: %s (expected: quic-go)", metadata.Client) + } + + case <-time.After(5 * time.Second): + t.Fatal("Timeout waiting for QUIC packets") + } +} diff --git a/common/sniff/quic_test.go b/common/sniff/quic_test.go index 807197654b..e2f5372472 100644 --- a/common/sniff/quic_test.go +++ b/common/sniff/quic_test.go @@ -5,31 +5,89 @@ import ( "encoding/hex" "testing" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/sniff" + C "github.com/sagernet/sing-box/constant" "github.com/stretchr/testify/require" ) -func TestSniffQUICv1(t *testing.T) { +func TestSniffQUICChromeNew(t *testing.T) { t.Parallel() - pkt, err := hex.DecodeString("cc0000000108d2dc7bad02241f5003796e71004215a71bfcb05159416c724be418537389acdd9a4047306283dcb4d7a9cad5cc06322042d204da67a8dbaa328ab476bb428b48fd001501863afd203f8d4ef085629d664f1a734a65969a47e4a63d4e01a21f18c1d90db0c027180906dc135f9ae421bb8617314c8d54c175fef3d3383d310d0916ebcbd6eed9329befbbb109d8fd4af1d2cf9d6adce8e6c1260a7f8256e273e326da0aa7cc148d76e7a08489dc9d52ade89c027cbc3491ada46417c2c04e2ca768e9a7dd6aa00c594e48b678927325da796817693499bb727050cb3baf3d3291a397c3a8d868e8ec7b8f7295e347455c9dadbe2252ae917ac793d958c7fb8a3d2cdb34e3891eb4286f18617556ff7216dd60256aa5b1d11ff4753459fc5f9dedf11d483a26a0835dc6cd50e1c1f54f86e8f1e502821183cd874f6447a74e818bf3445c7795acf4559d1c1fac474911d2ead5c8d23e4aa4f67afb66efe305a30a0b5d825679b31ddc186cbea936535795c7e8c378c87b8c5adc065154d15bae8f85ac8fec2da40c3aa623b682a065440831555011d7647cde44446a0fb4cf5892f2c088ae1920643094be72e3c499fe8d265caf939e8ab607a5b9317917d2a32a812e8a0e6a2f84721bbb5984ffd242838f705d13f4cfb249bc6a5c80d58ac2595edf56648ec3fe21d787573c253a79805252d6d81e26d367d4ff29ef66b5fe8992086af7bada8cad10b82a7c0dc406c5b6d0c5ec3c583e767f759ce08cad6c3c8f91e5a8") + pkt, err := hex.DecodeString("ca0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea4489ad89c322f75f9a383c90d126a0b21104cb519c2bb32e6a134e86896452e942b26c519b8c7ac9e4c99fae5e1f65cf08fb98443b30e4567932e8fb0789820d8f33037b59ac8113530258c9467dfb52489396dae01f099d28b234efa107fa411f2a1ffa2abe74988e03d662d4296024e95ce0fe1671724937157f77b84990478a2d4060676cf0827b4e8c600654111750414dafa0cccb332f3020c2922a015f445df5edc9c7d2d1ceea9fddcc9ff821c9183aa39a70da20fcc057579e1051c1c899148d6cf9d08b4919822082d040d1ce03ca4f216be6cb7ef03db6df0993ef1ccce5c8c648980554f41704526e1809d2545739f5872e75ec797db1c99f5682e2eda9363cb32aa367b7b363c782ddbacf874183cc15c8a2db068dd4093eebdd096ad33832a7939deb0a872279744f5a56dc001ba62fac973bf680f3b362bdd336add4dd102f462b773bf70bfce1921070a802a92025273a177186d1a643081b42175eb789ccddadb71033ef4feacbf6fd282ab622cf61669d73cda559e411c6ccdd8f003443b6933b7729b7a357aa4aa2fba0f365f829a4d497afb5dc2648a53bc9f3e786d955069d0a4781088a5463747dfe9958ea19ea444eae947ec6a67640955f710f93640084f3fbb8ad259b68dbc0ee0b7fab2d81bffd83ed8a6d33522dbfef43bec0a0fb4bdf1cb712dc4ced0680c0687fa240fd157baa232b1c84e14adce6421cf9270f9b3972f98fc67b344b8a4f1fb551e26f7f76d484ed9f8197f231dc5d9a44cc0ddce73d7f810a620851f4e97eb5037ab5135d7c3be5b80cc32d19910b8387aca64c93c02dc3e35238b78e6aff470722078982e58802844932b6041446bfdcc97ba640cbb86721bcd0f40f27b77aa6287ce5674ec1720134b9302875482c3269787e004b9edb483d44f326eef38c0e83cb46af96488c2e696bc2524567fb29c1e8edcd5a73615496d172d46a9d29e0505c0018b7bbb00165eca0389e09c4b1d73b6cc4a2f735a720650134a2e98e8105e20695cf231b92586237dfe0f99c897414e51c21627496276535f07abb53fb2b554376fe520fa45a3e944fd91dfe7a72aead08842b6b63d8edf861fb911954c83bd9a896eb9da4af5eff646455069d747facd4e77c254096843bff7c3e9031dbdf8dc37ea45f1122922fcbc322ec1378f3c7c1af0da62e1052e6210f1b23073f93a82d90e14cb20bc4501d487a1c848674d57a7c269b13590b3a99d8b8b4f6d0dfbd1d2cbbe7a32c0d5c84ae7ec438b0b19f3862d8fabaa828d06c7e3c6967405cd56a1ae90f38633e2ee0e3ecfca3df399fe12f029e0860a1a30da010300d0c94f0bf56091d00011488c1429928b21c739ebf50ba8be91116315d3173f6d2c56735722478c4d74392ba84d1727036b3d64e8c2263b0f33cb8086be587ca6b3940259c06afa2683868856529303ae12e91d7ca874568be7f2bfaa0656dfab0ed31ed90eaea10fb7f3433ec59a334abe6211d547fa0c825ac45d3691e749d15432008de83e9f6d98f368359137ae803d9189b3386f800c7c0cf4b615d1983cf82d9981a8105b60a80fe66c9b0d439b5ba153dd19e9e7483a01cf3b02b4597540b38e658d4eb8455e030b2bf2690bdd78c23f16fe5") require.NoError(t, err) - metadata, err := sniff.QUICClientHello(context.Background(), pkt) + var metadata adapter.InboundContext + err = sniff.QUICClientHello(context.Background(), &metadata, pkt) + require.Equal(t, metadata.Protocol, C.ProtocolQUIC) + require.Empty(t, metadata.Client) + require.ErrorIs(t, err, sniff.ErrNeedMoreData) + pkt, err = hex.DecodeString("cc0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea44894b626c685cd5d5c965f7e97b3a1bdc520b75813e747f37a3ae83ad38b9ca2acb0de4fc9424839a50c8fb815a62b498609fbbc59145698860e0509cc08a04d1b119daef844ba2f09c16e2665e5cc0b47624b71f7b950c54fd56b4a1fbb826cba44eeeee3949ced8f5de60d4c81b19ee59f75aa1abb33f22c6b13c27095eb1e99cff01fdc93e6e88da2622ee18c08a79f508befd7e33e99bca60e64bef9a47b764384bd93823daeeb6fcb4d7cfbc4ab53eff59b3636f6dcaaf229b5a94941b5712807166b9bd5e82cb4a9708a71451c4cd6f6e33fb2fe40c8c70dd51a30b37ff9c5e35783debde0093fde19ce074b4887b3c90980b107b9c0f32cf61a66f37c251b789abc4d27fc421207966846c8cc7faa42d9af6ad355a6bc94cb78223b612be8b3e2a4df61fee83a674a0ceb8b7c3a29b97102cda22fecdf6a4628e5b612bc17eab64d6f75feedd0b106c0419e484e66725759964cb5935ac5125e5ae920cd280bd40df57c1d7ae1845700bd4eb7b7ab12bc0850950bfe6e69edd6ac1daa5db2c2b07484327196e561c513462d72872dc6771c39f6b60d46a1f2c92343b7338450a0ef8e39f97fa70652b3a12cd04043698951627aaaa82cc95e76df92021d30e8014c984f12eea0143de8b17e5e4a36ec07bf4814251b391f168a59ef75afcd2319249aaba930f06bb7a11b9491e6f71b3d5774a6503a965e94edd0a67737282fc9cb0271779ff14151b7aa9267bb8f7d643185512515aeea513c0c98bfae782381a3317064195d8825cf8b25c17cdab5fced02612a3f2870e40df57e6ca3f08228a2b04e8de1425eb4b970118f9bbdc212223ff86a5d6b648cdf2366722f21de4b14a1014879eadb69215cdb1aa2a9f4f310ecfe3116214fe3ab0a23f4775a0a54b48d7dfd8f7283ed687b3ac7e1a7e42a0bdc3478aba8651c03e1e9cc9df17d106b8130afe854269b0103b7a696f452721887b19d8181830073c9f10684c65f96d3a6c6efbae044eec03d6399e001fa44d54635dc72f9b8ea6b87d0f452cad1e1e32273e2b47c40f2730235adcae8523b8282f86b8cf1ab63ae54aaa06130df3bbf6ecac7d7d1d43d2a87aea837267ff8ccfaa4b7e47b7ded909e6603d0b928a304f8915c839153598adc4178eb48bc0e98ad7793d7980275e1e491ba4847a4a04ae30fe7f5cc7d4b6f4f63a525e9964d72245860ca76a668a4654adb6619f16e9db79131e5675b93cafb96c92f1da8464d4fef2a22e7f9db695965fe2cc27ea30974629c8fe17cfa2f860179e1eb9faaa88a91ec9ce6da28c1a2894c3b932b5e1c807146718cc77ca13c61eaae00c7c99e019f599772064b198c5c2c5e863336367673630b417ac845ddb7c93b0856317e5d64bab208c5730abc2c63536784fbeaaec139dffc917e775715f1e42164ddef5138d4d163609ab3fbdcab968f8738385c0e7e34ff3cf7771a1dc5ba25a8850fdf96dabafa21f9065f307457ce9af4b7a73450c9d20a3b46fa8d3a1163d22bd01a7d17f0ec274181bf9640fa941427694bfeb1346089f7a851efe0fbb7a2041fa6bb6541ccbad77dd3e1a97999fc05f1fef070e7b5c4b385b8b2a8cc32483fdeba6a373970de2fa4139ba18e5916f949aab0aab2894") require.NoError(t, err) - require.Equal(t, metadata.Domain, "cloudflare-quic.com") + err = sniff.QUICClientHello(context.Background(), &metadata, pkt) + require.ErrorIs(t, err, sniff.ErrNeedMoreData) + pkt, err = hex.DecodeString("c20000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea4489e2ff30c43a5f63beb2e4501ce7754085bcbe838003a0b4bccb53863c0766df7eac073c2bdc170772b157997945acdc2ab2e84750cc9aa0ffa0fdc023da7fc565a14f87f7c563dbc9183dd226aab79957d263f66e64b85a1b15a24516bd2c7c04eea4fa0a34ef9849c21585db2e4adb7c05e265c4f38d8ffe4cbed0f3b0e68f3693bf1f726c3fb135b8e32a5d22931d7c55fc2ff4b9a354933ab14544df3cdaf3e3217dfb8d7feb3465dc34df6320ea486f12e5b2d609aaa5f4515c20c86fc440f8087be0ee3d339835746ae2573c2afdee6bb6ef7e9eb541feae9209391b2902cfb0bdaccd9da8d290714638b7da588d4a656ca6eabba78b7363922d6037cf060b161a42019d4feb4156459103cffdeefd0e63114af2b0e0c39e70ebc7fecb8dd1ebb8d60b2137f509bb7dcef5f1d3e06ab1d391466652d57440a410fb4f58a6ce1fb62feb453241f64e110709f59a3d9ebdac94f811337d0e4a80fd6b56b2a70cd6eebbf98e1661291da6bf5beb8b8afc376dfd20eb76afe709e8e8f28e0ef82105954e346546ad25973df43f4acddbec0ffd9b215f62abebebf71305b5ea993560316f69430bf5afe50420340622f802b5830f3bcebffff04980c75a59d28902879e5d51a4fb21062a4ae13c42297075b21d54ee04303879c1157e7470c1451673c98a2f3921f2f3e8f6acfe85b01caaca66b59e5ebffbfe68e5e9ab17e9a1b857eb409df91cb76767fc1814fd3c522a9b117edd0b02526e469cb4afb291a4dcc74c79b47ec6e7ce558c597129366f83ec306b11d2598c705fd4ee9ee99df6b7039bef13b08fc6f26853ad213829d24f895747d45a47414f931c583fb6c3e4f6c27d0c2b81a5f3cee390ec6314e1fec637e8d28b675e97caafdfbf8c25d34a635083a7553d219dd80dbb39087d74c6ad6192ca6f48a3ff8d47db41b2a492c63fcd780012780931dae0a325f9dcbd772d09a700f132c4bc1d9809b25b9751b694eb72a8ba4db7208d2b1bab63e1845208e4f841ea30218a559db98751589716b6d059ca673378f5fe7c7d8a1c82e14a561c47313bbcc278412ba86ffb2b87ec308eab9df696f5b4b54f8e361731bf232820a02a35fda7e5d4bf01b8f005ad299a055116e7b23c181f15a66442cf6032ca477bccc55b79d424eb4f245847bd81a581dc369dd20b1a4892733bde3c38e492c0039f69f2b947a4dc251a49ee7ccc0f36b3b75a555fa1d126db75f94dab60f52f6b15a877a0c380b59f82d35c570bc5f8051e9ef87db51f52383d47b50829b7f9e947ccc67aa280566aa48b4a85c1c7eca6f542789d8abcc050f1aa3cc221b6859656a21454aa21c7bfb9d12115f61c3ed46263ade68a8d3679fa62a659a5da7817406bd16618fccf33ed208ada1b03584e8b485d3cb6ed80a0774e60b6cd55aff64169ea998cf8235997049515abac58e0169ca07fb1c8c4c8b2803ba9d27b44c045d0a1cac86e5e188195c68001f53eb44851b6d821fc01ccbb41e27f38e6ddd66540c2d62ed6e0d551e22c0f26b60078c74a6302a1ed3d9e8fc0861257a63f6ac4e759fd54bff088becd28e30944a6c15db4fc8ae6244346869add946d9d92c430d737e042fa18b28a8ed64d1e8987ad9061cdc1335f") + require.NoError(t, err) + err = sniff.QUICClientHello(context.Background(), &metadata, pkt) + require.NoError(t, err) + require.Equal(t, "www.google.com", metadata.Domain) +} + +func TestSniffQUICChromium(t *testing.T) { + t.Parallel() + pkt, err := hex.DecodeString("c30000000108f40d654cc09b27f5000044d08a94548e57e43cc5483f129986187c432d58d46674830442988f869566a6e31e2ae37c9f7acbf61cc81621594fab0b3dfdc1635460b32389563dc8e74006315661cd22694114612973c1c45910621713a48b375854f095e8a77ccf3afa64e972f0f7f7002f50e0b014b1b146ea47c07fb20b73ad5587872b51a0b3fafdf1c4cf4fe6f8b112142392efa25d993abe2f42582be145148bdfe12edcd96c3655b65a4781b093e5594ba8e3ae5320f12e8314fc3ca374128cc43381046c322b964681ed4395c813b28534505118201459665a44b8f0abead877de322e9040631d20b05f15b81fa7ff785d4041aecc37c7e2ccdc5d1532787ce566517e8985fd5c200dbfd1e67bc255efaba94cfc07bb52fea4a90887413b134f2715b5643542aa897c6116486f428d82da64d2a2c1e1bdd40bd592558901a554b003d6966ac5a7b8b9413eddbf6ef21f28386c74981e3ce1d724c341e95494907626659692720c81114ca4acea35a14c402cfa3dc2228446e78dc1b81fa4325cf7e314a9cad6a6bdff33b3351dcba74eb15fae67f1227283aa4cdd64bcadf8f19358333f8549b596f4350297b5c65274565869d497398339947b9d3d064e5b06d39d34b436d8a41c1a3880de10bd26c3b1c5b4e2a49b0d4d07b8d90cd9e92bc611564d19ea8ec33099e92033caf21f5307dbeaa4708b99eb313bff99e2081ac25fd12d6a72e8335e0724f6718fe023cd0ad0d6e6a6309f09c9c391eec2bc08e9c3210a043c08e1759f354c121f6517fff4d6e20711a871e41285d48d930352fddffb92c96ba57df045ce99f8bfdfa8edc0969ce68a51e9fbb4f54b956d9df74a9e4af27ed2b27839bce1cffeca8333c0aaee81a570217442f9029ba8fedb84a2cf4be4d910982d891ea00e816c7fb98e8020e896a9c6fdd9106611da0a99dde18df1b7a8f6327acb1eed9ad93314451e48cb0dfb9571728521ca3db2ac0968159d5622556a55d51a422d11995b650949aaefc5d24c16080446dfc4fbc10353f9f93ce161ab513367bb89ab83988e0630b689e174e27bcfcc31996ee7b0bca909e251b82d69a28fee5a5d662e127508cd19dbbe5097b7d5b62a49203d66764197a527e472e2627e44a93d44177dace9d60e7d0e03305ddf4cfe47cdf2362e14de79ef46a6763ce696cd7854a48d9419a0817507a4713ffd4977b906d4f2b5fb6dbe1bd15bc505d5fea582190bf531a45d5ee026da8918547fd5105f15e5d061c7b0cf80a34990366ed8e91e13c2f0d85e5dad537298808d193cf54b7eaac33f10051f74cb6b75e52f81618c36f03d86aef613ba237a1a793ba1539938a38f62ccaf7bd5f6c5e0ce53cde4012fcf2b758214a0422d2faaa798e86e19d7481b42df2b36a73d287ff28c20cce01ce598771fec16a8f1f00305c06010126013a6c1de9f589b4e79d693717cd88ad1c42a2d99fa96617ba0bc6365b68e21a70ebc447904aa27979e1514433cfd83bfec09f137c747d47582cb63eb28f873fb94cf7a59ff764ddfbb687d79a58bb10f85949269f7f72c611a5e0fbb52adfa298ff060ec2eb7216fd7302ea8fb07798cbb3be25cb53ac8161aac2b5bbcfbcfb01c113d28bd1cb0333fb89ac82a95930f7abded0a2f5a623cc6a1f62bf3f38ef1b81c1e50a634f657dbb6770e4af45879e2fb1e00c742e7b52205c8015b5c0f5b1e40186ff9aa7288ab3e01a51fb87761f9bc6837082af109b39cc9f620") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.QUICClientHello(context.Background(), &metadata, pkt) + require.Equal(t, metadata.Protocol, C.ProtocolQUIC) + require.Empty(t, metadata.Client) + require.ErrorIs(t, err, sniff.ErrNeedMoreData) + pkt, err = hex.DecodeString("c90000000108f40d654cc09b27f5000044d073eb38807026d4088455e650e7ccf750d01a72f15f9bfc8ff40d223499db1a485cff14dbd45b9be118172834dc35dca3cf62f61a1266f40b92faf3d28d67a466cfdca678ddced15cd606d31959cf441828467857b226d1a241847c82c57312cefe68ba5042d929919bcd4403b39e5699fe87dda05df1b3801e048edee792458e9b1a9b1d4039df05847bcee3be567494b5876e3bd4c3220fe9dfdb2c07d77410f907f744251ef15536cc03b267d3668d5b75bc1ad2fe735cd3bb73519dd9f1625a49e17ad27bdeccf706c83b5ea339a0a05dd0072f4a8f162bd29926b4997f05613c6e4b0270b0c02805ca0543f27c1ff8505a5750bdd33529ee73c491050a10c6903f53c1121dbe0380e84c007c8df74a1b02443ed80ba7766aef5549e618d4fd249844ee28565142005369869299e8c3035ecef3d799f6cada8549e75b4ce4cbf4c85ef071fd7ff067b1ca9b5968dc41d13d011f6d7843823bac97acb1eb8ee45883f0f254b5f9bd4c763b67e2d8c70a7618a0ef0de304cf597a485126e09f8b2fd795b394c0b4bc4cd2634c2057970da2c798c5e8af7aed4f76f5e25d04e3f8c9c5a5b150d17e0d4c74229898c69b8dc7b8bcc9d359eb441de75c68fbdebec62fb669dcccfb1aad03e3fa073adb2ccf7bb14cbaf99e307d2c903ee71a8f028102eb510caee7e7397512086a78d1f95635c7d06845b5a708652dc4e5cd61245aae5b3c05b84815d84d367bce9b9e3f6d6b90701ac3679233c14d5ce2a1eff26469c966266dc6284bdb95c9c6158934c413a872ce22101e4163e3293d236b301592ca4ccacc1fd4c37066e79c2d9857c8a2560dcf0b33b19163c4240c471b19907476e7e25c65f7eb37276594a0f6b4c33c340cc3284178f17ac5e34dbe7509db890e4ddfd0540fbf9deb32a0101d24fe58b26c5f81c627db9d6ae59d7a111a3d5d1f6109f4eec0d0234e6d73c73a44f50999462724b51ce0fd8283535d70d9e83872c79c59897407a0736741011ae5c64862eb0712f9e7b07aa1d5418ca3fde8626257c6fe418f3c5479055bb2b0ab4c25f649923fc2a41c79aaa7d0f3af6d8b8cf06f61f0230d09bbb60bb49b9e49cc5973748a6cf7ffdee7804d424f9423c63e7ff22f4bd24e4867636ef9fe8dd37f59941a8a47c27765caa8e875a30b62834f17c569227e5e6ed15d58e05d36e76332befad065a2cd4079e66d5af189b0337624c89b1560c3b1b0befd5c1f20e6de8e3d664b3ac06b3d154b488983e14aa93266f5f8b621d2a9bb7ccce509eb26e025c9c45f7cccc09ce85b3103af0c93ce9822f82ecb168ca3177829afb2ea0da2c380e7b1728add55a5d42632e2290363d4cbe432b67e13691648e1acfab22cf0d551eee857709b428bb78e27a45aff6eca301c02e4d13cf36cc2494fdd1aef8dede6e18febd79dca4c6964d09b91c25a08f0947c76ab5104de9404459c2edf5f4adb9dfd771be83656f77fbbafb1ad3281717066010be8778952495383c9f2cf0a38527228c662a35171c5981731f1af09bab842fe6c3162ad4152a4221f560eb6f9bea66b294ffbd3643da2fe34096da13c246505452540177a2a0a1a69106e5cfc279a4890fc3be2952f26be245f930e6c2d9e7e26ee960481e72b99594a1185b46b94b6436d00ba6c70ffe135d43907c92c6f1c09fb9453f103730714f5700fa4347f9715c774cb04a7218dacc66d9c2fade18b14e684aa7fc9ebda0a28") + require.NoError(t, err) + err = sniff.QUICClientHello(context.Background(), &metadata, pkt) + require.NoError(t, err) + require.Equal(t, metadata.Domain, "google.com") +} + +func TestSniffUQUICChrome115(t *testing.T) { + t.Parallel() + pkt, err := hex.DecodeString("cb0000000108181e17c387120abc000044d0705b6a3ef9ee37a8d3949a7d393ed078243c2ee2c3627fad1c3f107c117f4f071131ad61848068fcbbe5c65803c147f7f8ec5e2cd77b77beea23ba779d936dccac540f8396400e3190ea35cc2942af4171a04cb14272491920f90124959f44e80143678c0b52f5d31af319aaa589db2f940f004562724d0af40f737e1bb0002a071e6a1dbc9f52c64f070806a5010abed0298053634d9c9126bd7949ae5087998ade762c0ad06691d99c0875a38c601fc1ee77bfc3b8c11381829f2c9bdd022f4499c43ff1d6aee1a0d296861461dda217d22c568b276016ef3929e59d2f7d7ddf7809920fb7dc805641608949f3f8466ab3d37149aac501f0b107d808f3add4acfc657e4a82e2b88e97a6c74a00c419548760ab3414ba13915c78a1ca79dceee8d59fbe299f20b671ac44823218368b2a026baa55170cf549519ac21dbb6d31d248bd339438a4e663bcdca1fe3ae3f045a5dc19b122e9db9d7af9757076666dda4e9ace1c67def77fa14786f0cab3ebf7a270ea6e2b37838318c95779f80c3b8471948d0046c3614b3a13477c939a39a7855d85d13522a45ae0765739cd5eedef87237e824a929983ace27640c6495dbf5a72fa0b96893dc5d28f3988249a57bdb458d460b4a57043de3da750a76b6e5d2259247ca27cd864ea18f0d09aa62ab6eb7c014fb43179b2a1963d170b756cce83eeaebff78a828d025c811848e16ff862a8080d093478cd2208c8ab0803178325bc0d9d6bb25e62fa50c4ad15cf80916da6578796932036c72e43eb480d1e423ed812ac75a97722f8416529b82ba8ee2219c535012282bb17066bd53e78b87a71abdb7ebdb2a7c2766ff8397962e87d0f85485b64b4ee81cc84f99c47f33f2b0872716441992773f59186e38d32dbf5609a6fda94cb928cd25f5a7a3ab736b5a4236b6d5409ab18892c6a4d3480fc2350abfdf0bab1cedb55bdf0760fdb703e6688f4de596254eed4ed3e67eb03d0717b8e15b31e735214e588c87ae36bc6c310e1894b4c15143e4ccf287b2dbc707a946bf9671ae3c574f9486b2c82eec784bba4cbc76113cbe0f97ac8c13cfa38f2925ab9d06887a612ce48280a91d7e074e6caf898d88e2bbf71360899abf48a03f9a70cf2891199f2d63b116f4871af0ebb4f4906792f66cc21d1609f189138532875c129a68c73e7bcd3b5d8100beac1d8ac4b20d94a59ac8df5a5af58a9acb20413eadf97189f5f19ff889155f0c4d37514ec184eb6903967ff38a41fc087abb0f2cad3761d6e3f95f92a09a72f5c065b16e188088b87460241f27ecdb1bc6ece92c8d36b2d68b58d0fb4d4b3c928c579ade8ae5a995833aadd297c30a37f7bc35440fc97070e1b198e0fac00157452177d16d2803b4239997452b4ad3a951173bdec47a033fd7f8a7942accaa9aaa905b3c5a2175e7c3e07c48bf25331727fd69cd1e64d74d8c9d4a6f8f4491adb7bc911505cb19877083d8f21a12475e313fccf57877ff3556318e81ed9145dd9427f2b65275440893035f417481f721c69215af8ae103530cd0a1d35bf2cb5a27628f8d44d7c6f5ec12ce79d0a8333e0eb48771115d0a191304e46b8db19bbe5c40f1c346dde98e76ff5e21ff38d2c34e60cb07766ed529dd6d2cbacd7fbf1ed8a0e6e40decad0ca5021e91552be87c156d3ae2fffef41c65b14ba6d488f2c3227a1ab11ffce0e2dc47723a69da27a67a7f26e1cb13a7103af9b87a8db8e18ea") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.QUICClientHello(context.Background(), &metadata, pkt) + require.NoError(t, err) + require.Equal(t, metadata.Protocol, C.ProtocolQUIC) + require.Equal(t, metadata.Client, C.ClientChromium) + require.Equal(t, metadata.Domain, "www.google.com") +} + +func TestSniffQUICFirefox(t *testing.T) { + t.Parallel() + pkt, err := hex.DecodeString("c8000000010867f174d7ebfe1b0803cd9c20004286de068f7963cf1736349ee6ebe0ddcd3e4cd0041a51ced3f7ce9eea1fb595458e74bdb4b792b16449bd8cae71419862c4fcbe766eaec7d1af65cd298e1dd46f8bd94a77ab4ca28c54b8e9773de3f02d7cb2463c9f7dcacfb311f024b0266ec6ab7bfb615b4148333fb4d4ece7c4cd90029ca30c2cbae2216b428499ec873fa125797e71c5a5da85087760ad37ca610020f71b76e82651c47576e20bf33cf676cb2d400b8c09d3c8cb4e21c47d2b21f6b68732bef30c8cefd5c723fc23eb29e6f7f65a5e52aad9055c1fb3d8b1811f0380b38d7e2eee8eb37dd5bd5d4ca4b66540175d916289d88a9df7c161964d713999c5057d27edb298ef5164352568b0d4bac3c15d90456e8fd460e41b81d0ec1b1e94b87d3333cc6908b018e0914ae1f214d73e75398da3d55a0106161d3a75897b4eb66e98c59010fae75f0d367d38be48c3a5c58bc8a30773c3fff50690ac9d487822f85d4f5713d626baa92d36e858dd21259cf814bce0b90d18da88a1ade40113e5a088cdb304a2558879152a8cf15c1839e056378aa41acba6fcb9974dee54bd50b5d4eb2c475654e06c0ec06b7f18f4462c808684843a1071041b9bfb2688324e0120144944416e30e83eedbbbcbc275b1f53762d3db18f0998ce54f0e1c512946b4098f07781d49264fa148f4c8220a3b02e73d7f15554aa370aafeff73cb75c52c494edf90f0261abfdd32a4d670f729de50266162687aa8efe14b8506f313b058b02aaaab5825428f5f4510b8e49451fdcb7b5a4af4b59c831afcb89fb4f64dba78e3b38387e87e9e8cdaa1f3b700a87c7d442388863b8950296e5773b38f308d62f52548c0bbf308e40540747cca5bf99b1345bc0d70b8f0e69a83b85a8d69f795b87f93e2bfccf52b529afea4ff6fd456957000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.QUICClientHello(context.Background(), &metadata, pkt) + require.NoError(t, err) + require.Equal(t, metadata.Protocol, C.ProtocolQUIC) + require.Equal(t, metadata.Client, C.ClientFirefox) + require.Equal(t, metadata.Domain, "www.google.com") } -func TestSniffQUICFragment(t *testing.T) { +func TestSniffQUICSafari(t *testing.T) { t.Parallel() - pkt, err := hex.DecodeString("cc00000001082e3d5d1b64040c55000044d0ccea69e773f6631c1d18b04ae9ee75fcfc34ef74fa62533c93534338a86f101a05d70e0697fb483063fa85db1c59ccfbda5c35234931d8524d8aac37eaaad649470a67794cd754b23c98695238b8363452333bc8c4858376b4166e001da2006e35cf98a91e11a56419b2786775284942d0f7163982f7c248867d12dd374957481dbc564013ff785e1916195eef671f725908f761099d992d69231336ba81d9e25fe2fa3a6eff4318a6ccf10176fc841a1b315f7b35c5b292266fc869d76ca533e7d14e86d82db2e22eacd350977e47d2e012d8a5891c5aaf2a0f4c2b2dae897c161e5b68cbb4dee952472bdc1e21504b8f02534ec4366ce3f8bf86efc78e0232778fbd554457567112abdcafcf6d4d8fcf35083c25d9495679614aba21696e338c62b585046cc55ba8c09c844361d889a47c3ea703b4e23545a9ab2c0bb369693a9ddfb5daffa85cf80fdd6ad66738664e5b0a551729b4955cff7255afcb04dee88c2f072c9de7400947a1bd9327ac5d012a33000ada021d4c03d249fb017d6ac9200b2f9436beab8183ddfbe2d8aee31ffb7df9e1cc181c1af80c39a89965d18ed12da8e3ebe2ae1fbe4b348f83ba19e3e3d1c9b22bcf03ab6ad9b30fe180623faa291ebad83bcd71d7b57f2f5e2f3b8e81d24fb70b2f2159239e8f21ffafef2747aba47d97ab4081e603c018b10678cf99cab1fb42156a14486fa435153979d7279fd22cd40af7088bfc7eff41af2f4b3c0c8864d0040d74dff427f7bffdb8c278474ea00311326cf4925471a8cf596cb92119f19e0f789490ba9cb77b98015a987d93e0324cf1a38b55109f00c3e6ddc5180fb107bf468323afec9bb49fd6a86418569789d66cafe3b8253c2aebb3af3782c1c54dd560487d031d28e6a6e23e159581bb1d47efc4da3fe1d169f9ffb0ca9ba61af0a38a92fde5bc5e6ec026e8378a6315a7b95abf1d2da790a391306ce74d0baf8e2ce648ca74c487f2c0a76a28a80cdf5bd34316eb607684fe7e6d9e83824a00e07660d0b90e3cddd61ebf10748263474afa88c300549e64ce2e90560bb1a12dee7e9484f729a8a4ee7c5651adb5194b3b3ae38e501567c7dbf36e7bb37a2c20b74655f47f2d9af18e52e9d4c9c9eee8e63745779b8f0b06f3a09d846ba62eb978ad77c85de1ee2fee3fbb4c2d283c73e1ccba56a4658e48a2665d200f7f9342f8e84c2ba490094a4f94feec89e42d2f654f564c2beb2997bafa1fc2c68ad8e160b63587d49abc31b834878d52acfb05fb73d0e059b206162e3c90b40c4bc08407ffcb3c08431895b691a3fea923f1f3b48db75d3e6b91fd319ffe4d486e0e14bd5c6affc838dee63d9e0b80f169b5e6c02c7321dcb20deb2b8e707b60e345a308d505bbf26a93d8f18b39d62632e9a77cbe48b3b32eb8819d6311a49820d40f5acbf0273c91c36b2269a03e72ee64df3dfb10ddefe73c64ef60870b2b77bd99dea655f5fe791b538a929a14d99f6d69685d72431ea5f0f4b27a044f2f575ab474fcc3857895934de1ca2581798eaef2c17fe5aaf2e6add97fa32997c7026f15c1b1ad0e6043ae506027a7c0242546fdc851cca39a204e56879f2cef838be8ec66e0f2292f8c862e06f810eb9b80c7a467ce6e90155206352c7f82b1173ba3b98d35bb72c259a60db20dd1a43fe6d7aef0265e6eaa5caafd9b64b448ff745a2046acbdb65cf2a5007809808a4828dc99097feedc734c236260c584") + pkt, err := hex.DecodeString("c70000000108e4e75af2e223198a0000449ef2d83cb4473a62765eba67424cd4a5817315cbf55a9e8daaca360904b0bae60b1629cfeba11e2dfbbf5ea4c588cb134e31af36fd7a409fb0fcc0187e9b56037ac37964ed20a8c1ca19fd6cfd53398324b3d0c71537294f769db208fa998b6811234a4a7eb3b5eceb457ae92e3a2d98f7c110702db8064b5c29fa3298eb1d0529fd445a84a5fd6ff8709be90f8af4f94998d8a8f2953bb05ad08c80668eca784c6aec959114e68e5b827e7c41c79f2277c716a967e7fcc8d1b77442e6cb18329dbedb34b473516b468cba5fc20659e655fbe37f36408289b9a475fcee091bd82828d3be00367e9e5cec9423bb97854abdada1d7562a3777756eb3bddef826ddc1ef46137cb01bb504a54d410d9bcb74cd5f959050c84edf343fa6a49708c228a758ee7adbbadf260b2f1984911489712e2cb364a3d6520badba4b7e539b9c163eeddfd96c0abb0de151e47496bb9750be76ee17ccdb61d35d2c6795174037d6f9d282c3f36c4d9a90b64f3b6ddd0cf4d9ed8e6f7805e25928fa04b087e63ae02761df30720cc01dfc32b64c575c8a66ef82e9a17400ff80cd8609b93ba16d668f4aa734e71c4a5d145f14ee1151bec970214e0ff83fc3e1e85d8694f2975f9155c57c18b7b69bb6a36832a9435f1f4b346a7be188f3a75f9ad2cc6ad0a3d26d6fa7d4c1179bd49bd5989d15ba43ff602890107db96484695086627356750d7b2b3b714ba65d564654e8f60ac10f5b6d3bfb507e8eaa31bab1da2d676195046d165c7f8b32829c9f9b68d97b2af7ac04a1369357e4b65de2b2f24eaf27cc8d95e05db001adebe726f927a94e43e62ce671e6e306e16f05aafcbe6c49080e80286d7939f375023d110a5ad9069364ae928ca480454a9dcddd61bc48b7efeb716a5bd6c7cd39c486ceb20c738af6abf22ba1ddd8b4a3b781fc2f251173409e1aadccbd7514e97106d0ebfc3af6e59445f74cd733a1ba99b10fce3fb4e9f7c88f5e25b567f5ba2b8dabacd375e7faf7634bfa178cbe51aee63032c5126b196ea47b02385fc3062a000fb7e4b4d0d12e74579f8830ede20d10829496032b2cc56743287f9a9b4d5091877a82fea44deb2cffac8a379f78a151d99e28cbc74d732c083bf06d50584e3f18f254e71a48d6ababaf6fff6f425e9be001510dfbe6a32a27792c00ada036b62ddb90c706d7b882c76a7072f5dd11c69a1f49d4ba183cb0b57545419fa27b9b9706098848935ae9c9e8fbe9fac165d1339128b991a73d20e7795e8d6a8c6adfbf20bf13ada43f2aef3ba78c14697910507132623f721387dce60c4707225b84d9782d469a5d9eaa099f35d6a590ef142ddef766495cf3337815ceef5ff2b3ed352637e72b5c23a2a8ff7d7440236a19b981d47f8e519a0431ebfbc0b78d8a36798b4c060c0c6793499f1e2e818862560a5b501c8d02ba1517be1941da2af5b174e0189c62978d878eb0f9c9db3a9221c28fb94645cf6e85ff2eea8c65ba3083a7382b131b83102dd67aa5453ad7375a4eb8c69fc479fbd29dab8924f801d253f2c997120b705c6e5217fb74702e2f1038917dd5fb0eeb7ae1bf7a668fc7d50c034b4cd5a057a8482e6bc9c921297f44e76967265623a167cd9883eb6e64bc77856dc333bd605d7df3bed0e5cecb5a99fe8b62873d58530f") require.NoError(t, err) - metadata, err := sniff.QUICClientHello(context.Background(), pkt) + var metadata adapter.InboundContext + err = sniff.QUICClientHello(context.Background(), &metadata, pkt) require.NoError(t, err) - require.Equal(t, metadata.Domain, "cloudflare-quic.com") + require.Equal(t, metadata.Protocol, C.ProtocolQUIC) + require.Equal(t, metadata.Client, C.ClientSafari) + require.Equal(t, metadata.Domain, "www.google.com") } func FuzzSniffQUIC(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { - sniff.QUICClientHello(context.Background(), data) + var metadata adapter.InboundContext + err := sniff.QUICClientHello(context.Background(), &metadata, data) + require.Error(t, err) }) } diff --git a/common/sniff/rdp.go b/common/sniff/rdp.go new file mode 100644 index 0000000000..37551fef24 --- /dev/null +++ b/common/sniff/rdp.go @@ -0,0 +1,91 @@ +package sniff + +import ( + "context" + "encoding/binary" + "io" + "os" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/rw" +) + +func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error { + var tpktVersion uint8 + err := binary.Read(reader, binary.BigEndian, &tpktVersion) + if err != nil { + return E.Cause1(ErrNeedMoreData, err) + } + if tpktVersion != 0x03 { + return os.ErrInvalid + } + + var tpktReserved uint8 + err = binary.Read(reader, binary.BigEndian, &tpktReserved) + if err != nil { + return E.Cause1(ErrNeedMoreData, err) + } + if tpktReserved != 0x00 { + return os.ErrInvalid + } + + var tpktLength uint16 + err = binary.Read(reader, binary.BigEndian, &tpktLength) + if err != nil { + return E.Cause1(ErrNeedMoreData, err) + } + + if tpktLength != 19 { + return os.ErrInvalid + } + + var cotpLength uint8 + err = binary.Read(reader, binary.BigEndian, &cotpLength) + if err != nil { + return E.Cause1(ErrNeedMoreData, err) + } + + if cotpLength != 14 { + return os.ErrInvalid + } + + var cotpTpduType uint8 + err = binary.Read(reader, binary.BigEndian, &cotpTpduType) + if err != nil { + return E.Cause1(ErrNeedMoreData, err) + } + if cotpTpduType != 0xE0 { + return os.ErrInvalid + } + + err = rw.SkipN(reader, 5) + if err != nil { + return E.Cause1(ErrNeedMoreData, err) + } + + var rdpType uint8 + err = binary.Read(reader, binary.BigEndian, &rdpType) + if err != nil { + return E.Cause1(ErrNeedMoreData, err) + } + if rdpType != 0x01 { + return os.ErrInvalid + } + var rdpFlags uint8 + err = binary.Read(reader, binary.BigEndian, &rdpFlags) + if err != nil { + return E.Cause1(ErrNeedMoreData, err) + } + var rdpLength uint8 + err = binary.Read(reader, binary.BigEndian, &rdpLength) + if err != nil { + return E.Cause1(ErrNeedMoreData, err) + } + if rdpLength != 8 { + return os.ErrInvalid + } + metadata.Protocol = C.ProtocolRDP + return nil +} diff --git a/common/sniff/rdp_test.go b/common/sniff/rdp_test.go new file mode 100644 index 0000000000..06fa3ab275 --- /dev/null +++ b/common/sniff/rdp_test.go @@ -0,0 +1,25 @@ +package sniff_test + +import ( + "bytes" + "context" + "encoding/hex" + "testing" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/sniff" + C "github.com/sagernet/sing-box/constant" + + "github.com/stretchr/testify/require" +) + +func TestSniffRDP(t *testing.T) { + t.Parallel() + + pkt, err := hex.DecodeString("030000130ee00000000000010008000b000000010008000b000000") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.RDP(context.TODO(), &metadata, bytes.NewReader(pkt)) + require.NoError(t, err) + require.Equal(t, C.ProtocolRDP, metadata.Protocol) +} diff --git a/common/sniff/sniff.go b/common/sniff/sniff.go index 424311a86e..b3651e1ffa 100644 --- a/common/sniff/sniff.go +++ b/common/sniff/sniff.go @@ -3,60 +3,86 @@ package sniff import ( "bytes" "context" + "errors" "io" "net" "time" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" ) type ( - StreamSniffer = func(ctx context.Context, reader io.Reader) (*adapter.InboundContext, error) - PacketSniffer = func(ctx context.Context, packet []byte) (*adapter.InboundContext, error) + StreamSniffer = func(ctx context.Context, metadata *adapter.InboundContext, reader io.Reader) error + PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error ) -func PeekStream(ctx context.Context, conn net.Conn, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) (*adapter.InboundContext, error) { +var ErrNeedMoreData = E.New("need more data") + +func Skip(metadata *adapter.InboundContext) bool { + // skip server first protocols + switch metadata.Destination.Port { + case 25, 465, 587: + // SMTP + return true + case 143, 993: + // IMAP + return true + case 110, 995: + // POP3 + return true + } + return false +} + +func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.Conn, buffers []*buf.Buffer, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) error { if timeout == 0 { timeout = C.ReadPayloadTimeout } deadline := time.Now().Add(timeout) - var errors []error - - for i := 0; i < 3; i++ { + var sniffError error + for i := 0; ; i++ { err := conn.SetReadDeadline(deadline) if err != nil { - return nil, E.Cause(err, "set read deadline") + return E.Cause(err, "set read deadline") } _, err = buffer.ReadOnceFrom(conn) - err = E.Errors(err, conn.SetReadDeadline(time.Time{})) + _ = conn.SetReadDeadline(time.Time{}) if err != nil { if i > 0 { break } - return nil, E.Cause(err, "read payload") + return E.Cause(err, "read payload") } + sniffError = nil for _, sniffer := range sniffers { - metadata, err := sniffer(ctx, bytes.NewReader(buffer.Bytes())) - if metadata != nil { - return metadata, nil + reader := io.MultiReader(common.Map(append(buffers, buffer), func(it *buf.Buffer) io.Reader { + return bytes.NewReader(it.Bytes()) + })...) + err = sniffer(ctx, metadata, reader) + if err == nil { + return nil } - errors = append(errors, err) + sniffError = E.Errors(sniffError, err) + } + if !errors.Is(sniffError, ErrNeedMoreData) { + break } } - return nil, E.Errors(errors...) + return sniffError } -func PeekPacket(ctx context.Context, packet []byte, sniffers ...PacketSniffer) (*adapter.InboundContext, error) { - var errors []error +func PeekPacket(ctx context.Context, metadata *adapter.InboundContext, packet []byte, sniffers ...PacketSniffer) error { + var sniffError []error for _, sniffer := range sniffers { - metadata, err := sniffer(ctx, packet) - if metadata != nil { - return metadata, nil + err := sniffer(ctx, metadata, packet) + if err == nil { + return nil } - errors = append(errors, err) + sniffError = append(sniffError, err) } - return nil, E.Errors(errors...) + return E.Errors(sniffError...) } diff --git a/common/sniff/ssh.go b/common/sniff/ssh.go new file mode 100644 index 0000000000..dce5d54f62 --- /dev/null +++ b/common/sniff/ssh.go @@ -0,0 +1,31 @@ +package sniff + +import ( + "bufio" + "context" + "io" + "os" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + E "github.com/sagernet/sing/common/exceptions" +) + +func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error { + const sshPrefix = "SSH-2.0-" + bReader := bufio.NewReader(reader) + prefix, err := bReader.Peek(len(sshPrefix)) + if string(prefix[:]) != sshPrefix[:len(prefix)] { + return os.ErrInvalid + } + if err != nil { + return E.Cause1(ErrNeedMoreData, err) + } + fistLine, _, err := bReader.ReadLine() + if err != nil { + return err + } + metadata.Protocol = C.ProtocolSSH + metadata.Client = string(fistLine)[8:] + return nil +} diff --git a/common/sniff/ssh_test.go b/common/sniff/ssh_test.go new file mode 100644 index 0000000000..7cea5aab0f --- /dev/null +++ b/common/sniff/ssh_test.go @@ -0,0 +1,47 @@ +package sniff_test + +import ( + "bytes" + "context" + "encoding/hex" + "testing" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/sniff" + C "github.com/sagernet/sing-box/constant" + + "github.com/stretchr/testify/require" +) + +func TestSniffSSH(t *testing.T) { + t.Parallel() + + pkt, err := hex.DecodeString("5353482d322e302d64726f70626561720d0a000001a40a1492892570d1223aef61b0d647972c8bd30000009f637572766532353531392d7368613235362c637572766532353531392d736861323536406c69627373682e6f72672c6469666669652d68656c6c6d616e2d67726f757031342d7368613235362c6469666669652d68656c6c6d616e2d67726f757031342d736861312c6b6578677565737332406d6174742e7563632e61736e2e61752c6b65782d7374726963742d732d763030406f70656e7373682e636f6d000000207373682d656432353531392c7273612d736861322d3235362c7373682d7273610000003363686163686132302d706f6c7931333035406f70656e7373682e636f6d2c6165733132382d6374722c6165733235362d6374720000003363686163686132302d706f6c7931333035406f70656e7373682e636f6d2c6165733132382d6374722c6165733235362d63747200000017686d61632d736861312c686d61632d736861322d32353600000017686d61632d736861312c686d61632d736861322d323536000000046e6f6e65000000046e6f6e65000000000000000000000000002aa6ed090585b7d635b6") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt)) + require.NoError(t, err) + require.Equal(t, C.ProtocolSSH, metadata.Protocol) + require.Equal(t, "dropbear", metadata.Client) +} + +func TestSniffIncompleteSSH(t *testing.T) { + t.Parallel() + + pkt, err := hex.DecodeString("5353482d322e30") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt)) + require.ErrorIs(t, err, sniff.ErrNeedMoreData) +} + +func TestSniffNotSSH(t *testing.T) { + t.Parallel() + + pkt, err := hex.DecodeString("5353482d322e31") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt)) + require.NotEmpty(t, err) + require.NotErrorIs(t, err, sniff.ErrNeedMoreData) +} diff --git a/common/sniff/stun.go b/common/sniff/stun.go index 66a72d7e17..dfd1125978 100644 --- a/common/sniff/stun.go +++ b/common/sniff/stun.go @@ -9,16 +9,17 @@ import ( C "github.com/sagernet/sing-box/constant" ) -func STUNMessage(ctx context.Context, packet []byte) (*adapter.InboundContext, error) { +func STUNMessage(_ context.Context, metadata *adapter.InboundContext, packet []byte) error { pLen := len(packet) if pLen < 20 { - return nil, os.ErrInvalid + return os.ErrInvalid } if binary.BigEndian.Uint32(packet[4:8]) != 0x2112A442 { - return nil, os.ErrInvalid + return os.ErrInvalid } if len(packet) < 20+int(binary.BigEndian.Uint16(packet[2:4])) { - return nil, os.ErrInvalid + return os.ErrInvalid } - return &adapter.InboundContext{Protocol: C.ProtocolSTUN}, nil + metadata.Protocol = C.ProtocolSTUN + return nil } diff --git a/common/sniff/stun_test.go b/common/sniff/stun_test.go index 6da89162a9..f465763e1f 100644 --- a/common/sniff/stun_test.go +++ b/common/sniff/stun_test.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "testing" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/sniff" C "github.com/sagernet/sing-box/constant" @@ -15,14 +16,16 @@ func TestSniffSTUN(t *testing.T) { t.Parallel() packet, err := hex.DecodeString("000100002112a44224b1a025d0c180c484341306") require.NoError(t, err) - metadata, err := sniff.STUNMessage(context.Background(), packet) + var metadata adapter.InboundContext + err = sniff.STUNMessage(context.Background(), &metadata, packet) require.NoError(t, err) require.Equal(t, metadata.Protocol, C.ProtocolSTUN) } func FuzzSniffSTUN(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { - if _, err := sniff.STUNMessage(context.Background(), data); err == nil { + var metadata adapter.InboundContext + if err := sniff.STUNMessage(context.Background(), &metadata, data); err == nil { t.Fail() } }) diff --git a/common/sniff/tls.go b/common/sniff/tls.go index dcba195afe..613086e810 100644 --- a/common/sniff/tls.go +++ b/common/sniff/tls.go @@ -3,14 +3,16 @@ package sniff import ( "context" "crypto/tls" + "errors" "io" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" ) -func TLSClientHello(ctx context.Context, reader io.Reader) (*adapter.InboundContext, error) { +func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reader io.Reader) error { var clientHello *tls.ClientHelloInfo err := tls.Server(bufio.NewReadOnlyConn(reader), &tls.Config{ GetConfigForClient: func(argHello *tls.ClientHelloInfo) (*tls.Config, error) { @@ -19,7 +21,13 @@ func TLSClientHello(ctx context.Context, reader io.Reader) (*adapter.InboundCont }, }).HandshakeContext(ctx) if clientHello != nil { - return &adapter.InboundContext{Protocol: C.ProtocolTLS, Domain: clientHello.ServerName}, nil + metadata.Protocol = C.ProtocolTLS + metadata.Domain = clientHello.ServerName + return nil + } + if errors.Is(err, io.ErrUnexpectedEOF) { + return E.Cause1(ErrNeedMoreData, err) + } else { + return err } - return nil, err } diff --git a/common/srs/binary.go b/common/srs/binary.go index faf4cd17b2..e625ac89c8 100644 --- a/common/srs/binary.go +++ b/common/srs/binary.go @@ -1,6 +1,7 @@ package srs import ( + "bufio" "compress/zlib" "encoding/binary" "io" @@ -11,7 +12,9 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/domain" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/rw" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" + "github.com/sagernet/sing/common/varbin" "go4.org/netipx" ) @@ -35,38 +38,48 @@ const ( ruleItemPackageName ruleItemWIFISSID ruleItemWIFIBSSID + ruleItemAdGuardDomain + ruleItemProcessPathRegex + ruleItemNetworkType + ruleItemNetworkIsExpensive + ruleItemNetworkIsConstrained + ruleItemNetworkInterfaceAddress + ruleItemDefaultInterfaceAddress + ruleItemPackageNameRegex ruleItemFinal uint8 = 0xFF ) -func Read(reader io.Reader, recovery bool) (ruleSet option.PlainRuleSet, err error) { +func Read(reader io.Reader, recover bool) (ruleSetCompat option.PlainRuleSetCompat, err error) { var magicBytes [3]byte _, err = io.ReadFull(reader, magicBytes[:]) if err != nil { return } if magicBytes != MagicBytes { - err = E.New("invalid sing-box rule set file") + err = E.New("invalid sing-box rule-set file") return } var version uint8 err = binary.Read(reader, binary.BigEndian, &version) if err != nil { - return ruleSet, err + return ruleSetCompat, err } - if version != 1 { - return ruleSet, E.New("unsupported version: ", version) + if version > C.RuleSetVersionCurrent { + return ruleSetCompat, E.New("unsupported version: ", version) } - zReader, err := zlib.NewReader(reader) + compressReader, err := zlib.NewReader(reader) if err != nil { return } - length, err := rw.ReadUVariant(zReader) + bReader := bufio.NewReader(compressReader) + length, err := binary.ReadUvarint(bReader) if err != nil { return } - ruleSet.Rules = make([]option.HeadlessRule, length) - for i := uint64(0); i < length; i++ { - ruleSet.Rules[i], err = readRule(zReader, recovery) + ruleSetCompat.Version = version + ruleSetCompat.Options.Rules = make([]option.HeadlessRule, length) + for i := range length { + ruleSetCompat.Options.Rules[i], err = readRule(bReader, recover) if err != nil { err = E.Cause(err, "read rule[", i, "]") return @@ -75,33 +88,38 @@ func Read(reader io.Reader, recovery bool) (ruleSet option.PlainRuleSet, err err return } -func Write(writer io.Writer, ruleSet option.PlainRuleSet) error { +func Write(writer io.Writer, ruleSet option.PlainRuleSet, generateVersion uint8) error { _, err := writer.Write(MagicBytes[:]) if err != nil { return err } - err = binary.Write(writer, binary.BigEndian, uint8(1)) + err = binary.Write(writer, binary.BigEndian, generateVersion) if err != nil { return err } - zWriter, err := zlib.NewWriterLevel(writer, zlib.BestCompression) + compressWriter, err := zlib.NewWriterLevel(writer, zlib.BestCompression) if err != nil { return err } - err = rw.WriteUVariant(zWriter, uint64(len(ruleSet.Rules))) + bWriter := bufio.NewWriter(compressWriter) + _, err = varbin.WriteUvarint(bWriter, uint64(len(ruleSet.Rules))) if err != nil { return err } for _, rule := range ruleSet.Rules { - err = writeRule(zWriter, rule) + err = writeRule(bWriter, rule, generateVersion) if err != nil { return err } } - return zWriter.Close() + err = bWriter.Flush() + if err != nil { + return err + } + return compressWriter.Close() } -func readRule(reader io.Reader, recovery bool) (rule option.HeadlessRule, err error) { +func readRule(reader varbin.Reader, recover bool) (rule option.HeadlessRule, err error) { var ruleType uint8 err = binary.Read(reader, binary.BigEndian, &ruleType) if err != nil { @@ -110,28 +128,28 @@ func readRule(reader io.Reader, recovery bool) (rule option.HeadlessRule, err er switch ruleType { case 0: rule.Type = C.RuleTypeDefault - rule.DefaultOptions, err = readDefaultRule(reader, recovery) + rule.DefaultOptions, err = readDefaultRule(reader, recover) case 1: rule.Type = C.RuleTypeLogical - rule.LogicalOptions, err = readLogicalRule(reader, recovery) + rule.LogicalOptions, err = readLogicalRule(reader, recover) default: err = E.New("unknown rule type: ", ruleType) } return } -func writeRule(writer io.Writer, rule option.HeadlessRule) error { +func writeRule(writer varbin.Writer, rule option.HeadlessRule, generateVersion uint8) error { switch rule.Type { case C.RuleTypeDefault: - return writeDefaultRule(writer, rule.DefaultOptions) + return writeDefaultRule(writer, rule.DefaultOptions, generateVersion) case C.RuleTypeLogical: - return writeLogicalRule(writer, rule.LogicalOptions) + return writeLogicalRule(writer, rule.LogicalOptions, generateVersion) default: panic("unknown rule type: " + rule.Type) } } -func readDefaultRule(reader io.Reader, recovery bool) (rule option.DefaultHeadlessRule, err error) { +func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHeadlessRule, err error) { var lastItemType uint8 for { var itemType uint8 @@ -158,6 +176,9 @@ func readDefaultRule(reader io.Reader, recovery bool) (rule option.DefaultHeadle return } rule.DomainMatcher = matcher + if recover { + rule.Domain, rule.DomainSuffix = matcher.Dump() + } case ruleItemDomainKeyword: rule.DomainKeyword, err = readRuleItemString(reader) case ruleItemDomainRegex: @@ -167,7 +188,7 @@ func readDefaultRule(reader io.Reader, recovery bool) (rule option.DefaultHeadle if err != nil { return } - if recovery { + if recover { rule.SourceIPCIDR = common.Map(rule.SourceIPSet.Prefixes(), netip.Prefix.String) } case ruleItemIPCIDR: @@ -175,7 +196,7 @@ func readDefaultRule(reader io.Reader, recovery bool) (rule option.DefaultHeadle if err != nil { return } - if recovery { + if recover { rule.IPCIDR = common.Map(rule.IPSet.Prefixes(), netip.Prefix.String) } case ruleItemSourcePort: @@ -190,12 +211,77 @@ func readDefaultRule(reader io.Reader, recovery bool) (rule option.DefaultHeadle rule.ProcessName, err = readRuleItemString(reader) case ruleItemProcessPath: rule.ProcessPath, err = readRuleItemString(reader) + case ruleItemProcessPathRegex: + rule.ProcessPathRegex, err = readRuleItemString(reader) case ruleItemPackageName: rule.PackageName, err = readRuleItemString(reader) + case ruleItemPackageNameRegex: + rule.PackageNameRegex, err = readRuleItemString(reader) case ruleItemWIFISSID: rule.WIFISSID, err = readRuleItemString(reader) case ruleItemWIFIBSSID: rule.WIFIBSSID, err = readRuleItemString(reader) + case ruleItemAdGuardDomain: + var matcher *domain.AdGuardMatcher + matcher, err = domain.ReadAdGuardMatcher(reader) + if err != nil { + return + } + rule.AdGuardDomainMatcher = matcher + if recover { + rule.AdGuardDomain = matcher.Dump() + } + case ruleItemNetworkType: + rule.NetworkType, err = readRuleItemUint8[option.InterfaceType](reader) + case ruleItemNetworkIsExpensive: + rule.NetworkIsExpensive = true + case ruleItemNetworkIsConstrained: + rule.NetworkIsConstrained = true + case ruleItemNetworkInterfaceAddress: + rule.NetworkInterfaceAddress = new(badjson.TypedMap[option.InterfaceType, badoption.Listable[*badoption.Prefixable]]) + var size uint64 + size, err = binary.ReadUvarint(reader) + if err != nil { + return + } + for i := uint64(0); i < size; i++ { + var key uint8 + err = binary.Read(reader, binary.BigEndian, &key) + if err != nil { + return + } + var value []*badoption.Prefixable + var prefixCount uint64 + prefixCount, err = binary.ReadUvarint(reader) + if err != nil { + return + } + for j := uint64(0); j < prefixCount; j++ { + var prefix netip.Prefix + prefix, err = readPrefix(reader) + if err != nil { + return + } + value = append(value, common.Ptr(badoption.Prefixable(prefix))) + } + rule.NetworkInterfaceAddress.Put(option.InterfaceType(key), value) + } + case ruleItemDefaultInterfaceAddress: + var value []*badoption.Prefixable + var prefixCount uint64 + prefixCount, err = binary.ReadUvarint(reader) + if err != nil { + return + } + for j := uint64(0); j < prefixCount; j++ { + var prefix netip.Prefix + prefix, err = readPrefix(reader) + if err != nil { + return + } + value = append(value, common.Ptr(badoption.Prefixable(prefix))) + } + rule.DefaultInterfaceAddress = value case ruleItemFinal: err = binary.Read(reader, binary.BigEndian, &rule.Invert) return @@ -209,7 +295,7 @@ func readDefaultRule(reader io.Reader, recovery bool) (rule option.DefaultHeadle } } -func writeDefaultRule(writer io.Writer, rule option.DefaultHeadlessRule) error { +func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, generateVersion uint8) error { err := binary.Write(writer, binary.BigEndian, uint8(0)) if err != nil { return err @@ -233,7 +319,7 @@ func writeDefaultRule(writer io.Writer, rule option.DefaultHeadlessRule) error { if err != nil { return err } - err = domain.NewMatcher(rule.Domain, rule.DomainSuffix).Write(writer) + err = domain.NewMatcher(rule.Domain, rule.DomainSuffix, generateVersion == C.RuleSetVersion1).Write(writer) if err != nil { return err } @@ -298,12 +384,102 @@ func writeDefaultRule(writer io.Writer, rule option.DefaultHeadlessRule) error { return err } } + if len(rule.ProcessPathRegex) > 0 { + err = writeRuleItemString(writer, ruleItemProcessPathRegex, rule.ProcessPathRegex) + if err != nil { + return err + } + } if len(rule.PackageName) > 0 { err = writeRuleItemString(writer, ruleItemPackageName, rule.PackageName) if err != nil { return err } } + if len(rule.PackageNameRegex) > 0 { + if generateVersion < C.RuleSetVersion5 { + return E.New("`package_name_regex` rule item is only supported in version 5 or later") + } + err = writeRuleItemString(writer, ruleItemPackageNameRegex, rule.PackageNameRegex) + if err != nil { + return err + } + } + if len(rule.NetworkType) > 0 { + if generateVersion < C.RuleSetVersion3 { + return E.New("`network_type` rule item is only supported in version 3 or later") + } + err = writeRuleItemUint8(writer, ruleItemNetworkType, rule.NetworkType) + if err != nil { + return err + } + } + if rule.NetworkIsExpensive { + if generateVersion < C.RuleSetVersion3 { + return E.New("`network_is_expensive` rule item is only supported in version 3 or later") + } + err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsExpensive) + if err != nil { + return err + } + } + if rule.NetworkIsConstrained { + if generateVersion < C.RuleSetVersion3 { + return E.New("`network_is_constrained` rule item is only supported in version 3 or later") + } + err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsConstrained) + if err != nil { + return err + } + } + if rule.NetworkInterfaceAddress != nil && rule.NetworkInterfaceAddress.Size() > 0 { + if generateVersion < C.RuleSetVersion4 { + return E.New("`network_interface_address` rule item is only supported in version 4 or later") + } + err = writer.WriteByte(ruleItemNetworkInterfaceAddress) + if err != nil { + return err + } + _, err = varbin.WriteUvarint(writer, uint64(rule.NetworkInterfaceAddress.Size())) + if err != nil { + return err + } + for _, entry := range rule.NetworkInterfaceAddress.Entries() { + err = binary.Write(writer, binary.BigEndian, uint8(entry.Key.Build())) + if err != nil { + return err + } + _, err = varbin.WriteUvarint(writer, uint64(len(entry.Value))) + if err != nil { + return err + } + for _, rawPrefix := range entry.Value { + err = writePrefix(writer, rawPrefix.Build(netip.Prefix{})) + if err != nil { + return err + } + } + } + } + if len(rule.DefaultInterfaceAddress) > 0 { + if generateVersion < C.RuleSetVersion4 { + return E.New("`default_interface_address` rule item is only supported in version 4 or later") + } + err = writer.WriteByte(ruleItemDefaultInterfaceAddress) + if err != nil { + return err + } + _, err = varbin.WriteUvarint(writer, uint64(len(rule.DefaultInterfaceAddress))) + if err != nil { + return err + } + for _, rawPrefix := range rule.DefaultInterfaceAddress { + err = writePrefix(writer, rawPrefix.Build(netip.Prefix{})) + if err != nil { + return err + } + } + } if len(rule.WIFISSID) > 0 { err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID) if err != nil { @@ -316,6 +492,19 @@ func writeDefaultRule(writer io.Writer, rule option.DefaultHeadlessRule) error { return err } } + if len(rule.AdGuardDomain) > 0 { + if generateVersion < C.RuleSetVersion2 { + return E.New("AdGuard rule items is only supported in version 2 or later") + } + err = binary.Write(writer, binary.BigEndian, ruleItemAdGuardDomain) + if err != nil { + return err + } + err = domain.NewAdGuardMatcher(rule.AdGuardDomain).Write(writer) + if err != nil { + return err + } + } err = binary.Write(writer, binary.BigEndian, ruleItemFinal) if err != nil { return err @@ -327,73 +516,43 @@ func writeDefaultRule(writer io.Writer, rule option.DefaultHeadlessRule) error { return nil } -func readRuleItemString(reader io.Reader) ([]string, error) { - length, err := rw.ReadUVariant(reader) - if err != nil { - return nil, err - } - value := make([]string, length) - for i := uint64(0); i < length; i++ { - value[i], err = rw.ReadVString(reader) - if err != nil { - return nil, err - } - } - return value, nil +func readRuleItemString(reader varbin.Reader) ([]string, error) { + return varbin.ReadValue[[]string](reader, binary.BigEndian) } -func writeRuleItemString(writer io.Writer, itemType uint8, value []string) error { - err := binary.Write(writer, binary.BigEndian, itemType) +func writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) error { + err := writer.WriteByte(itemType) if err != nil { return err } - err = rw.WriteUVariant(writer, uint64(len(value))) - if err != nil { - return err - } - for _, item := range value { - err = rw.WriteVString(writer, item) - if err != nil { - return err - } - } - return nil + return varbin.Write(writer, binary.BigEndian, value) } -func readRuleItemUint16(reader io.Reader) ([]uint16, error) { - length, err := rw.ReadUVariant(reader) - if err != nil { - return nil, err - } - value := make([]uint16, length) - for i := uint64(0); i < length; i++ { - err = binary.Read(reader, binary.BigEndian, &value[i]) - if err != nil { - return nil, err - } - } - return value, nil +func readRuleItemUint8[E ~uint8](reader varbin.Reader) ([]E, error) { + return varbin.ReadValue[[]E](reader, binary.BigEndian) } -func writeRuleItemUint16(writer io.Writer, itemType uint8, value []uint16) error { - err := binary.Write(writer, binary.BigEndian, itemType) +func writeRuleItemUint8[E ~uint8](writer varbin.Writer, itemType uint8, value []E) error { + err := writer.WriteByte(itemType) if err != nil { return err } - err = rw.WriteUVariant(writer, uint64(len(value))) + return varbin.Write(writer, binary.BigEndian, value) +} + +func readRuleItemUint16(reader varbin.Reader) ([]uint16, error) { + return varbin.ReadValue[[]uint16](reader, binary.BigEndian) +} + +func writeRuleItemUint16(writer varbin.Writer, itemType uint8, value []uint16) error { + err := writer.WriteByte(itemType) if err != nil { return err } - for _, item := range value { - err = binary.Write(writer, binary.BigEndian, item) - if err != nil { - return err - } - } - return nil + return varbin.Write(writer, binary.BigEndian, value) } -func writeRuleItemCIDR(writer io.Writer, itemType uint8, value []string) error { +func writeRuleItemCIDR(writer varbin.Writer, itemType uint8, value []string) error { var builder netipx.IPSetBuilder for i, prefixString := range value { prefix, err := netip.ParsePrefix(prefixString) @@ -419,9 +578,8 @@ func writeRuleItemCIDR(writer io.Writer, itemType uint8, value []string) error { return writeIPSet(writer, ipSet) } -func readLogicalRule(reader io.Reader, recovery bool) (logicalRule option.LogicalHeadlessRule, err error) { - var mode uint8 - err = binary.Read(reader, binary.BigEndian, &mode) +func readLogicalRule(reader varbin.Reader, recovery bool) (logicalRule option.LogicalHeadlessRule, err error) { + mode, err := reader.ReadByte() if err != nil { return } @@ -434,12 +592,12 @@ func readLogicalRule(reader io.Reader, recovery bool) (logicalRule option.Logica err = E.New("unknown logical mode: ", mode) return } - length, err := rw.ReadUVariant(reader) + length, err := binary.ReadUvarint(reader) if err != nil { return } logicalRule.Rules = make([]option.HeadlessRule, length) - for i := uint64(0); i < length; i++ { + for i := range length { logicalRule.Rules[i], err = readRule(reader, recovery) if err != nil { err = E.Cause(err, "read logical rule [", i, "]") @@ -453,7 +611,7 @@ func readLogicalRule(reader io.Reader, recovery bool) (logicalRule option.Logica return } -func writeLogicalRule(writer io.Writer, logicalRule option.LogicalHeadlessRule) error { +func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRule, generateVersion uint8) error { err := binary.Write(writer, binary.BigEndian, uint8(1)) if err != nil { return err @@ -469,12 +627,12 @@ func writeLogicalRule(writer io.Writer, logicalRule option.LogicalHeadlessRule) if err != nil { return err } - err = rw.WriteUVariant(writer, uint64(len(logicalRule.Rules))) + _, err = varbin.WriteUvarint(writer, uint64(len(logicalRule.Rules))) if err != nil { return err } for _, rule := range logicalRule.Rules { - err = writeRule(writer, rule) + err = writeRule(writer, rule, generateVersion) if err != nil { return err } diff --git a/common/srs/compat_test.go b/common/srs/compat_test.go new file mode 100644 index 0000000000..46f3c11440 --- /dev/null +++ b/common/srs/compat_test.go @@ -0,0 +1,494 @@ +package srs + +import ( + "bufio" + "bytes" + "encoding/binary" + "net/netip" + "strings" + "testing" + "unsafe" + + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/varbin" + + "github.com/stretchr/testify/require" + "go4.org/netipx" +) + +// Old implementations using varbin reflection-based serialization + +func oldWriteStringSlice(writer varbin.Writer, value []string) error { + //nolint:staticcheck + return varbin.Write(writer, binary.BigEndian, value) +} + +func oldReadStringSlice(reader varbin.Reader) ([]string, error) { + //nolint:staticcheck + return varbin.ReadValue[[]string](reader, binary.BigEndian) +} + +func oldWriteUint8Slice[E ~uint8](writer varbin.Writer, value []E) error { + //nolint:staticcheck + return varbin.Write(writer, binary.BigEndian, value) +} + +func oldReadUint8Slice[E ~uint8](reader varbin.Reader) ([]E, error) { + //nolint:staticcheck + return varbin.ReadValue[[]E](reader, binary.BigEndian) +} + +func oldWriteUint16Slice(writer varbin.Writer, value []uint16) error { + //nolint:staticcheck + return varbin.Write(writer, binary.BigEndian, value) +} + +func oldReadUint16Slice(reader varbin.Reader) ([]uint16, error) { + //nolint:staticcheck + return varbin.ReadValue[[]uint16](reader, binary.BigEndian) +} + +func oldWritePrefix(writer varbin.Writer, prefix netip.Prefix) error { + //nolint:staticcheck + err := varbin.Write(writer, binary.BigEndian, prefix.Addr().AsSlice()) + if err != nil { + return err + } + return binary.Write(writer, binary.BigEndian, uint8(prefix.Bits())) +} + +type oldIPRangeData struct { + From []byte + To []byte +} + +// Note: The old writeIPSet had a bug where varbin.Write(writer, binary.BigEndian, data) +// with a struct VALUE (not pointer) silently wrote nothing because field.CanSet() returned false. +// This caused IP range data to be missing from the output. +// The new implementation correctly writes all range data. +// +// The old readIPSet used varbin.Read with a pre-allocated slice, which worked because +// slice elements are addressable and CanSet() returns true for them. +// +// For compatibility testing, we verify: +// 1. New write produces correct output with range data +// 2. New read can parse the new format correctly +// 3. Round-trip works correctly + +func oldReadIPSet(reader varbin.Reader) (*netipx.IPSet, error) { + version, err := reader.ReadByte() + if err != nil { + return nil, err + } + if version != 1 { + return nil, err + } + var length uint64 + err = binary.Read(reader, binary.BigEndian, &length) + if err != nil { + return nil, err + } + ranges := make([]oldIPRangeData, length) + //nolint:staticcheck + err = varbin.Read(reader, binary.BigEndian, &ranges) + if err != nil { + return nil, err + } + mySet := &myIPSet{ + rr: make([]myIPRange, len(ranges)), + } + for i, rangeData := range ranges { + mySet.rr[i].from = M.AddrFromIP(rangeData.From) + mySet.rr[i].to = M.AddrFromIP(rangeData.To) + } + return (*netipx.IPSet)(unsafe.Pointer(mySet)), nil +} + +// New write functions (without itemType prefix for testing) + +func newWriteStringSlice(writer varbin.Writer, value []string) error { + _, err := varbin.WriteUvarint(writer, uint64(len(value))) + if err != nil { + return err + } + for _, s := range value { + _, err = varbin.WriteUvarint(writer, uint64(len(s))) + if err != nil { + return err + } + _, err = writer.Write([]byte(s)) + if err != nil { + return err + } + } + return nil +} + +func newWriteUint8Slice[E ~uint8](writer varbin.Writer, value []E) error { + _, err := varbin.WriteUvarint(writer, uint64(len(value))) + if err != nil { + return err + } + _, err = writer.Write(*(*[]byte)(unsafe.Pointer(&value))) + return err +} + +func newWriteUint16Slice(writer varbin.Writer, value []uint16) error { + _, err := varbin.WriteUvarint(writer, uint64(len(value))) + if err != nil { + return err + } + return binary.Write(writer, binary.BigEndian, value) +} + +func newWritePrefix(writer varbin.Writer, prefix netip.Prefix) error { + addrSlice := prefix.Addr().AsSlice() + _, err := varbin.WriteUvarint(writer, uint64(len(addrSlice))) + if err != nil { + return err + } + _, err = writer.Write(addrSlice) + if err != nil { + return err + } + return writer.WriteByte(uint8(prefix.Bits())) +} + +// Tests + +func TestStringSliceCompat(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + input []string + }{ + {"nil", nil}, + {"empty", []string{}}, + {"single_empty", []string{""}}, + {"single", []string{"test"}}, + {"multi", []string{"a", "b", "c"}}, + {"with_empty", []string{"a", "", "c"}}, + {"utf8", []string{"测试", "テスト", "тест"}}, + {"long_string", []string{strings.Repeat("x", 128)}}, + {"many_elements", generateStrings(128)}, + {"many_elements_256", generateStrings(256)}, + {"127_byte_string", []string{strings.Repeat("x", 127)}}, + {"128_byte_string", []string{strings.Repeat("x", 128)}}, + {"mixed_lengths", []string{"a", strings.Repeat("b", 100), "", strings.Repeat("c", 200)}}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Old write + var oldBuf bytes.Buffer + err := oldWriteStringSlice(&oldBuf, tc.input) + require.NoError(t, err) + + // New write + var newBuf bytes.Buffer + err = newWriteStringSlice(&newBuf, tc.input) + require.NoError(t, err) + + // Bytes must match + require.Equal(t, oldBuf.Bytes(), newBuf.Bytes(), + "mismatch for %q\nold: %x\nnew: %x", tc.name, oldBuf.Bytes(), newBuf.Bytes()) + + // New write -> old read + readBack, err := oldReadStringSlice(bufio.NewReader(bytes.NewReader(newBuf.Bytes()))) + require.NoError(t, err) + requireStringSliceEqual(t, tc.input, readBack) + + // Old write -> new read + readBack2, err := readRuleItemString(bufio.NewReader(bytes.NewReader(oldBuf.Bytes()))) + require.NoError(t, err) + requireStringSliceEqual(t, tc.input, readBack2) + }) + } +} + +func TestUint8SliceCompat(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + input []uint8 + }{ + {"nil", nil}, + {"empty", []uint8{}}, + {"single_zero", []uint8{0}}, + {"single_max", []uint8{255}}, + {"multi", []uint8{0, 1, 127, 128, 255}}, + {"boundary", []uint8{0x00, 0x7f, 0x80, 0xff}}, + {"sequential", generateUint8Slice(256)}, + {"127_elements", generateUint8Slice(127)}, + {"128_elements", generateUint8Slice(128)}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Old write + var oldBuf bytes.Buffer + err := oldWriteUint8Slice(&oldBuf, tc.input) + require.NoError(t, err) + + // New write + var newBuf bytes.Buffer + err = newWriteUint8Slice(&newBuf, tc.input) + require.NoError(t, err) + + // Bytes must match + require.Equal(t, oldBuf.Bytes(), newBuf.Bytes(), + "mismatch for %q\nold: %x\nnew: %x", tc.name, oldBuf.Bytes(), newBuf.Bytes()) + + // New write -> old read + readBack, err := oldReadUint8Slice[uint8](bufio.NewReader(bytes.NewReader(newBuf.Bytes()))) + require.NoError(t, err) + requireUint8SliceEqual(t, tc.input, readBack) + + // Old write -> new read + readBack2, err := readRuleItemUint8[uint8](bufio.NewReader(bytes.NewReader(oldBuf.Bytes()))) + require.NoError(t, err) + requireUint8SliceEqual(t, tc.input, readBack2) + }) + } +} + +func TestUint16SliceCompat(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + input []uint16 + }{ + {"nil", nil}, + {"empty", []uint16{}}, + {"single_zero", []uint16{0}}, + {"single_max", []uint16{65535}}, + {"multi", []uint16{0, 255, 256, 32767, 32768, 65535}}, + {"ports", []uint16{80, 443, 8080, 8443}}, + {"127_elements", generateUint16Slice(127)}, + {"128_elements", generateUint16Slice(128)}, + {"256_elements", generateUint16Slice(256)}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Old write + var oldBuf bytes.Buffer + err := oldWriteUint16Slice(&oldBuf, tc.input) + require.NoError(t, err) + + // New write + var newBuf bytes.Buffer + err = newWriteUint16Slice(&newBuf, tc.input) + require.NoError(t, err) + + // Bytes must match + require.Equal(t, oldBuf.Bytes(), newBuf.Bytes(), + "mismatch for %q\nold: %x\nnew: %x", tc.name, oldBuf.Bytes(), newBuf.Bytes()) + + // New write -> old read + readBack, err := oldReadUint16Slice(bufio.NewReader(bytes.NewReader(newBuf.Bytes()))) + require.NoError(t, err) + requireUint16SliceEqual(t, tc.input, readBack) + + // Old write -> new read + readBack2, err := readRuleItemUint16(bufio.NewReader(bytes.NewReader(oldBuf.Bytes()))) + require.NoError(t, err) + requireUint16SliceEqual(t, tc.input, readBack2) + }) + } +} + +func TestPrefixCompat(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + input netip.Prefix + }{ + {"ipv4_0", netip.MustParsePrefix("0.0.0.0/0")}, + {"ipv4_8", netip.MustParsePrefix("10.0.0.0/8")}, + {"ipv4_16", netip.MustParsePrefix("192.168.0.0/16")}, + {"ipv4_24", netip.MustParsePrefix("192.168.1.0/24")}, + {"ipv4_32", netip.MustParsePrefix("1.2.3.4/32")}, + {"ipv6_0", netip.MustParsePrefix("::/0")}, + {"ipv6_64", netip.MustParsePrefix("2001:db8::/64")}, + {"ipv6_128", netip.MustParsePrefix("::1/128")}, + {"ipv6_full", netip.MustParsePrefix("2001:0db8:85a3:0000:0000:8a2e:0370:7334/128")}, + {"ipv4_private", netip.MustParsePrefix("172.16.0.0/12")}, + {"ipv6_link_local", netip.MustParsePrefix("fe80::/10")}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Old write + var oldBuf bytes.Buffer + err := oldWritePrefix(&oldBuf, tc.input) + require.NoError(t, err) + + // New write + var newBuf bytes.Buffer + err = newWritePrefix(&newBuf, tc.input) + require.NoError(t, err) + + // Bytes must match + require.Equal(t, oldBuf.Bytes(), newBuf.Bytes(), + "mismatch for %q\nold: %x\nnew: %x", tc.name, oldBuf.Bytes(), newBuf.Bytes()) + + // New write -> new read (no old read for prefix) + readBack, err := readPrefix(bufio.NewReader(bytes.NewReader(newBuf.Bytes()))) + require.NoError(t, err) + require.Equal(t, tc.input, readBack) + + // Old write -> new read + readBack2, err := readPrefix(bufio.NewReader(bytes.NewReader(oldBuf.Bytes()))) + require.NoError(t, err) + require.Equal(t, tc.input, readBack2) + }) + } +} + +func TestIPSetCompat(t *testing.T) { + t.Parallel() + + // Note: The old writeIPSet was buggy (varbin.Write with struct values wrote nothing). + // This test verifies the new implementation writes correct data and round-trips correctly. + + cases := []struct { + name string + input *netipx.IPSet + }{ + {"single_ipv4", buildIPSet("1.2.3.4")}, + {"ipv4_range", buildIPSet("192.168.0.0/16")}, + {"multi_ipv4", buildIPSet("10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16")}, + {"single_ipv6", buildIPSet("::1")}, + {"ipv6_range", buildIPSet("2001:db8::/32")}, + {"mixed", buildIPSet("10.0.0.0/8", "::1", "2001:db8::/32")}, + {"large", buildLargeIPSet(100)}, + {"adjacent_ranges", buildIPSet("192.168.0.0/24", "192.168.1.0/24", "192.168.2.0/24")}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // New write + var newBuf bytes.Buffer + err := writeIPSet(&newBuf, tc.input) + require.NoError(t, err) + + // Verify format starts with version byte (1) + uint64 count + require.True(t, len(newBuf.Bytes()) >= 9, "output too short") + require.Equal(t, byte(1), newBuf.Bytes()[0], "version byte mismatch") + + // New write -> old read (varbin.Read with pre-allocated slice works correctly) + readBack, err := oldReadIPSet(bufio.NewReader(bytes.NewReader(newBuf.Bytes()))) + require.NoError(t, err) + requireIPSetEqual(t, tc.input, readBack) + + // New write -> new read + readBack2, err := readIPSet(bufio.NewReader(bytes.NewReader(newBuf.Bytes()))) + require.NoError(t, err) + requireIPSetEqual(t, tc.input, readBack2) + }) + } +} + +// Helper functions + +func generateStrings(count int) []string { + result := make([]string, count) + for i := range result { + result[i] = strings.Repeat("x", i%50) + } + return result +} + +func generateUint8Slice(count int) []uint8 { + result := make([]uint8, count) + for i := range result { + result[i] = uint8(i % 256) + } + return result +} + +func generateUint16Slice(count int) []uint16 { + result := make([]uint16, count) + for i := range result { + result[i] = uint16(i * 257) + } + return result +} + +func buildIPSet(cidrs ...string) *netipx.IPSet { + var builder netipx.IPSetBuilder + for _, cidr := range cidrs { + prefix, err := netip.ParsePrefix(cidr) + if err != nil { + addr, err := netip.ParseAddr(cidr) + if err != nil { + panic(err) + } + builder.Add(addr) + } else { + builder.AddPrefix(prefix) + } + } + set, _ := builder.IPSet() + return set +} + +func buildLargeIPSet(count int) *netipx.IPSet { + var builder netipx.IPSetBuilder + for i := range count { + prefix := netip.PrefixFrom(netip.AddrFrom4([4]byte{10, byte(i / 256), byte(i % 256), 0}), 24) + builder.AddPrefix(prefix) + } + set, _ := builder.IPSet() + return set +} + +func requireStringSliceEqual(t *testing.T, expected, actual []string) { + t.Helper() + if len(expected) == 0 && len(actual) == 0 { + return + } + require.Equal(t, expected, actual) +} + +func requireUint8SliceEqual(t *testing.T, expected, actual []uint8) { + t.Helper() + if len(expected) == 0 && len(actual) == 0 { + return + } + require.Equal(t, expected, actual) +} + +func requireUint16SliceEqual(t *testing.T, expected, actual []uint16) { + t.Helper() + if len(expected) == 0 && len(actual) == 0 { + return + } + require.Equal(t, expected, actual) +} + +func requireIPSetEqual(t *testing.T, expected, actual *netipx.IPSet) { + t.Helper() + expectedRanges := expected.Ranges() + actualRanges := actual.Ranges() + require.Equal(t, len(expectedRanges), len(actualRanges), "range count mismatch") + for i := range expectedRanges { + require.Equal(t, expectedRanges[i].From(), actualRanges[i].From(), "range[%d].from mismatch", i) + require.Equal(t, expectedRanges[i].To(), actualRanges[i].To(), "range[%d].to mismatch", i) + } +} diff --git a/common/srs/ip_cidr.go b/common/srs/ip_cidr.go new file mode 100644 index 0000000000..93ae84ad32 --- /dev/null +++ b/common/srs/ip_cidr.go @@ -0,0 +1,33 @@ +package srs + +import ( + "encoding/binary" + "net/netip" + + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/varbin" +) + +func readPrefix(reader varbin.Reader) (netip.Prefix, error) { + addrSlice, err := varbin.ReadValue[[]byte](reader, binary.BigEndian) + if err != nil { + return netip.Prefix{}, err + } + prefixBits, err := varbin.ReadValue[uint8](reader, binary.BigEndian) + if err != nil { + return netip.Prefix{}, err + } + return netip.PrefixFrom(M.AddrFromIP(addrSlice), int(prefixBits)), nil +} + +func writePrefix(writer varbin.Writer, prefix netip.Prefix) error { + err := varbin.Write(writer, binary.BigEndian, prefix.Addr().AsSlice()) + if err != nil { + return err + } + err = binary.Write(writer, binary.BigEndian, uint8(prefix.Bits())) + if err != nil { + return err + } + return nil +} diff --git a/common/srs/ip_set.go b/common/srs/ip_set.go index b346da26f6..044dc823b3 100644 --- a/common/srs/ip_set.go +++ b/common/srs/ip_set.go @@ -2,11 +2,13 @@ package srs import ( "encoding/binary" - "io" "net/netip" + "os" "unsafe" - "github.com/sagernet/sing/common/rw" + "github.com/sagernet/sing/common" + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/varbin" "go4.org/netipx" ) @@ -20,94 +22,57 @@ type myIPRange struct { to netip.Addr } -func readIPSet(reader io.Reader) (*netipx.IPSet, error) { - var version uint8 - err := binary.Read(reader, binary.BigEndian, &version) +type myIPRangeData struct { + From []byte + To []byte +} + +func readIPSet(reader varbin.Reader) (*netipx.IPSet, error) { + version, err := reader.ReadByte() if err != nil { return nil, err } + if version != 1 { + return nil, os.ErrInvalid + } + // WTF why using uint64 here var length uint64 err = binary.Read(reader, binary.BigEndian, &length) if err != nil { return nil, err } + ranges := make([]myIPRangeData, length) + err = varbin.Read(reader, binary.BigEndian, &ranges) + if err != nil { + return nil, err + } mySet := &myIPSet{ - rr: make([]myIPRange, length), + rr: make([]myIPRange, len(ranges)), } - for i := uint64(0); i < length; i++ { - var ( - fromLen uint64 - toLen uint64 - fromAddr netip.Addr - toAddr netip.Addr - ) - fromLen, err = rw.ReadUVariant(reader) - if err != nil { - return nil, err - } - fromBytes := make([]byte, fromLen) - _, err = io.ReadFull(reader, fromBytes) - if err != nil { - return nil, err - } - err = fromAddr.UnmarshalBinary(fromBytes) - if err != nil { - return nil, err - } - toLen, err = rw.ReadUVariant(reader) - if err != nil { - return nil, err - } - toBytes := make([]byte, toLen) - _, err = io.ReadFull(reader, toBytes) - if err != nil { - return nil, err - } - err = toAddr.UnmarshalBinary(toBytes) - if err != nil { - return nil, err - } - mySet.rr[i] = myIPRange{fromAddr, toAddr} + for i, rangeData := range ranges { + mySet.rr[i].from = M.AddrFromIP(rangeData.From) + mySet.rr[i].to = M.AddrFromIP(rangeData.To) } return (*netipx.IPSet)(unsafe.Pointer(mySet)), nil } -func writeIPSet(writer io.Writer, set *netipx.IPSet) error { - err := binary.Write(writer, binary.BigEndian, uint8(1)) +func writeIPSet(writer varbin.Writer, set *netipx.IPSet) error { + err := writer.WriteByte(1) if err != nil { return err } - mySet := (*myIPSet)(unsafe.Pointer(set)) - err = binary.Write(writer, binary.BigEndian, uint64(len(mySet.rr))) + dataList := common.Map((*myIPSet)(unsafe.Pointer(set)).rr, func(rr myIPRange) myIPRangeData { + return myIPRangeData{ + From: rr.from.AsSlice(), + To: rr.to.AsSlice(), + } + }) + err = binary.Write(writer, binary.BigEndian, uint64(len(dataList))) if err != nil { return err } - for _, rr := range mySet.rr { - var ( - fromBinary []byte - toBinary []byte - ) - fromBinary, err = rr.from.MarshalBinary() - if err != nil { - return err - } - err = rw.WriteUVariant(writer, uint64(len(fromBinary))) - if err != nil { - return err - } - _, err = writer.Write(fromBinary) - if err != nil { - return err - } - toBinary, err = rr.to.MarshalBinary() - if err != nil { - return err - } - err = rw.WriteUVariant(writer, uint64(len(toBinary))) - if err != nil { - return err - } - _, err = writer.Write(toBinary) + for _, data := range dataList { + err = varbin.Write(writer, binary.BigEndian, data) if err != nil { return err } diff --git a/common/stun/stun.go b/common/stun/stun.go new file mode 100644 index 0000000000..a4bb9d5cc8 --- /dev/null +++ b/common/stun/stun.go @@ -0,0 +1,612 @@ +package stun + +import ( + "context" + "crypto/rand" + "encoding/binary" + "fmt" + "net" + "net/netip" + "time" + + "github.com/sagernet/sing/common/bufio" + "github.com/sagernet/sing/common/bufio/deadline" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +const ( + DefaultServer = "stun.voipgate.com:3478" + + magicCookie = 0x2112A442 + headerSize = 20 + + bindingRequest = 0x0001 + bindingSuccessResponse = 0x0101 + bindingErrorResponse = 0x0111 + + attrMappedAddress = 0x0001 + attrChangeRequest = 0x0003 + attrErrorCode = 0x0009 + attrXORMappedAddress = 0x0020 + attrOtherAddress = 0x802c + + familyIPv4 = 0x01 + familyIPv6 = 0x02 + + changeIP = 0x04 + changePort = 0x02 + + defaultRTO = 500 * time.Millisecond + minRTO = 250 * time.Millisecond + maxRetransmit = 2 +) + +type Phase int32 + +const ( + PhaseBinding Phase = iota + PhaseNATMapping + PhaseNATFiltering + PhaseDone +) + +type NATMapping int32 + +const ( + NATMappingUnknown NATMapping = iota + _ // reserved + NATMappingEndpointIndependent + NATMappingAddressDependent + NATMappingAddressAndPortDependent +) + +func (m NATMapping) String() string { + switch m { + case NATMappingEndpointIndependent: + return "Endpoint Independent" + case NATMappingAddressDependent: + return "Address Dependent" + case NATMappingAddressAndPortDependent: + return "Address and Port Dependent" + default: + return "Unknown" + } +} + +type NATFiltering int32 + +const ( + NATFilteringUnknown NATFiltering = iota + NATFilteringEndpointIndependent + NATFilteringAddressDependent + NATFilteringAddressAndPortDependent +) + +func (f NATFiltering) String() string { + switch f { + case NATFilteringEndpointIndependent: + return "Endpoint Independent" + case NATFilteringAddressDependent: + return "Address Dependent" + case NATFilteringAddressAndPortDependent: + return "Address and Port Dependent" + default: + return "Unknown" + } +} + +type TransactionID [12]byte + +type Options struct { + Server string + Dialer N.Dialer + Context context.Context + OnProgress func(Progress) +} + +type Progress struct { + Phase Phase + ExternalAddr string + LatencyMs int32 + NATMapping NATMapping + NATFiltering NATFiltering +} + +type Result struct { + ExternalAddr string + LatencyMs int32 + NATMapping NATMapping + NATFiltering NATFiltering + NATTypeSupported bool +} + +type parsedResponse struct { + xorMappedAddr netip.AddrPort + mappedAddr netip.AddrPort + otherAddr netip.AddrPort +} + +func (r *parsedResponse) externalAddr() (netip.AddrPort, bool) { + if r.xorMappedAddr.IsValid() { + return r.xorMappedAddr, true + } + if r.mappedAddr.IsValid() { + return r.mappedAddr, true + } + return netip.AddrPort{}, false +} + +type stunAttribute struct { + typ uint16 + value []byte +} + +func newTransactionID() TransactionID { + var id TransactionID + _, _ = rand.Read(id[:]) + return id +} + +func buildBindingRequest(txID TransactionID, attrs ...stunAttribute) []byte { + attrLen := 0 + for _, attr := range attrs { + attrLen += 4 + len(attr.value) + paddingLen(len(attr.value)) + } + + buf := make([]byte, headerSize+attrLen) + binary.BigEndian.PutUint16(buf[0:2], bindingRequest) + binary.BigEndian.PutUint16(buf[2:4], uint16(attrLen)) + binary.BigEndian.PutUint32(buf[4:8], magicCookie) + copy(buf[8:20], txID[:]) + + offset := headerSize + for _, attr := range attrs { + binary.BigEndian.PutUint16(buf[offset:offset+2], attr.typ) + binary.BigEndian.PutUint16(buf[offset+2:offset+4], uint16(len(attr.value))) + copy(buf[offset+4:offset+4+len(attr.value)], attr.value) + offset += 4 + len(attr.value) + paddingLen(len(attr.value)) + } + + return buf +} + +func changeRequestAttr(flags byte) stunAttribute { + return stunAttribute{ + typ: attrChangeRequest, + value: []byte{0, 0, 0, flags}, + } +} + +func parseResponse(data []byte, expectedTxID TransactionID) (*parsedResponse, error) { + if len(data) < headerSize { + return nil, E.New("response too short") + } + + msgType := binary.BigEndian.Uint16(data[0:2]) + if msgType&0xC000 != 0 { + return nil, E.New("invalid STUN message: top 2 bits not zero") + } + + cookie := binary.BigEndian.Uint32(data[4:8]) + if cookie != magicCookie { + return nil, E.New("invalid magic cookie") + } + + var txID TransactionID + copy(txID[:], data[8:20]) + if txID != expectedTxID { + return nil, E.New("transaction ID mismatch") + } + + msgLen := int(binary.BigEndian.Uint16(data[2:4])) + if msgLen > len(data)-headerSize { + return nil, E.New("message length exceeds data") + } + + attrData := data[headerSize : headerSize+msgLen] + + if msgType == bindingErrorResponse { + return nil, parseErrorResponse(attrData) + } + if msgType != bindingSuccessResponse { + return nil, E.New("unexpected message type: ", fmt.Sprintf("0x%04x", msgType)) + } + + resp := &parsedResponse{} + offset := 0 + for offset+4 <= len(attrData) { + attrType := binary.BigEndian.Uint16(attrData[offset : offset+2]) + attrLen := int(binary.BigEndian.Uint16(attrData[offset+2 : offset+4])) + if offset+4+attrLen > len(attrData) { + break + } + attrValue := attrData[offset+4 : offset+4+attrLen] + + switch attrType { + case attrXORMappedAddress: + addr, err := parseXORMappedAddress(attrValue, txID) + if err == nil { + resp.xorMappedAddr = addr + } + case attrMappedAddress: + addr, err := parseMappedAddress(attrValue) + if err == nil { + resp.mappedAddr = addr + } + case attrOtherAddress: + addr, err := parseMappedAddress(attrValue) + if err == nil { + resp.otherAddr = addr + } + } + + offset += 4 + attrLen + paddingLen(attrLen) + } + + return resp, nil +} + +func parseErrorResponse(data []byte) error { + offset := 0 + for offset+4 <= len(data) { + attrType := binary.BigEndian.Uint16(data[offset : offset+2]) + attrLen := int(binary.BigEndian.Uint16(data[offset+2 : offset+4])) + if offset+4+attrLen > len(data) { + break + } + if attrType == attrErrorCode && attrLen >= 4 { + attrValue := data[offset+4 : offset+4+attrLen] + class := int(attrValue[2] & 0x07) + number := int(attrValue[3]) + code := class*100 + number + if attrLen > 4 { + return E.New("STUN error ", code, ": ", string(attrValue[4:])) + } + return E.New("STUN error ", code) + } + offset += 4 + attrLen + paddingLen(attrLen) + } + return E.New("STUN error response") +} + +func parseXORMappedAddress(data []byte, txID TransactionID) (netip.AddrPort, error) { + if len(data) < 4 { + return netip.AddrPort{}, E.New("XOR-MAPPED-ADDRESS too short") + } + + family := data[1] + xPort := binary.BigEndian.Uint16(data[2:4]) + port := xPort ^ uint16(magicCookie>>16) + + switch family { + case familyIPv4: + if len(data) < 8 { + return netip.AddrPort{}, E.New("XOR-MAPPED-ADDRESS IPv4 too short") + } + var ip [4]byte + binary.BigEndian.PutUint32(ip[:], binary.BigEndian.Uint32(data[4:8])^magicCookie) + return netip.AddrPortFrom(netip.AddrFrom4(ip), port), nil + case familyIPv6: + if len(data) < 20 { + return netip.AddrPort{}, E.New("XOR-MAPPED-ADDRESS IPv6 too short") + } + var ip [16]byte + var xorKey [16]byte + binary.BigEndian.PutUint32(xorKey[0:4], magicCookie) + copy(xorKey[4:16], txID[:]) + for i := range 16 { + ip[i] = data[4+i] ^ xorKey[i] + } + return netip.AddrPortFrom(netip.AddrFrom16(ip), port), nil + default: + return netip.AddrPort{}, E.New("unknown address family: ", family) + } +} + +func parseMappedAddress(data []byte) (netip.AddrPort, error) { + if len(data) < 4 { + return netip.AddrPort{}, E.New("MAPPED-ADDRESS too short") + } + + family := data[1] + port := binary.BigEndian.Uint16(data[2:4]) + + switch family { + case familyIPv4: + if len(data) < 8 { + return netip.AddrPort{}, E.New("MAPPED-ADDRESS IPv4 too short") + } + return netip.AddrPortFrom( + netip.AddrFrom4([4]byte{data[4], data[5], data[6], data[7]}), port, + ), nil + case familyIPv6: + if len(data) < 20 { + return netip.AddrPort{}, E.New("MAPPED-ADDRESS IPv6 too short") + } + var ip [16]byte + copy(ip[:], data[4:20]) + return netip.AddrPortFrom(netip.AddrFrom16(ip), port), nil + default: + return netip.AddrPort{}, E.New("unknown address family: ", family) + } +} + +func roundTrip(conn net.PacketConn, addr net.Addr, txID TransactionID, attrs []stunAttribute, rto time.Duration) (*parsedResponse, time.Duration, error) { + request := buildBindingRequest(txID, attrs...) + currentRTO := rto + retransmitCount := 0 + + sendTime := time.Now() + _, err := conn.WriteTo(request, addr) + if err != nil { + return nil, 0, E.Cause(err, "send STUN request") + } + + buf := make([]byte, 1024) + for { + err = conn.SetReadDeadline(sendTime.Add(currentRTO)) + if err != nil { + return nil, 0, E.Cause(err, "set read deadline") + } + + n, _, readErr := conn.ReadFrom(buf) + if readErr != nil { + if E.IsTimeout(readErr) && retransmitCount < maxRetransmit { + retransmitCount++ + currentRTO *= 2 + sendTime = time.Now() + _, err = conn.WriteTo(request, addr) + if err != nil { + return nil, 0, E.Cause(err, "retransmit STUN request") + } + continue + } + return nil, 0, E.Cause(readErr, "read STUN response") + } + + if n < headerSize || buf[0]&0xC0 != 0 || + binary.BigEndian.Uint32(buf[4:8]) != magicCookie { + continue + } + var receivedTxID TransactionID + copy(receivedTxID[:], buf[8:20]) + if receivedTxID != txID { + continue + } + + latency := time.Since(sendTime) + + resp, parseErr := parseResponse(buf[:n], txID) + if parseErr != nil { + return nil, 0, parseErr + } + + return resp, latency, nil + } +} + +func Run(options Options) (*Result, error) { + ctx := options.Context + if ctx == nil { + ctx = context.Background() + } + + server := options.Server + if server == "" { + server = DefaultServer + } + serverSocksaddr := M.ParseSocksaddr(server) + if serverSocksaddr.Port == 0 { + serverSocksaddr.Port = 3478 + } + + reportProgress := options.OnProgress + if reportProgress == nil { + reportProgress = func(Progress) {} + } + + var ( + packetConn net.PacketConn + serverAddr net.Addr + err error + ) + + if options.Dialer != nil { + packetConn, err = options.Dialer.ListenPacket(ctx, serverSocksaddr) + if err != nil { + return nil, E.Cause(err, "create UDP socket") + } + serverAddr = serverSocksaddr + } else { + serverUDPAddr, resolveErr := net.ResolveUDPAddr("udp", serverSocksaddr.String()) + if resolveErr != nil { + return nil, E.Cause(resolveErr, "resolve STUN server") + } + packetConn, err = net.ListenPacket("udp", "") + if err != nil { + return nil, E.Cause(err, "create UDP socket") + } + serverAddr = serverUDPAddr + } + defer func() { + _ = packetConn.Close() + }() + if deadline.NeedAdditionalReadDeadline(packetConn) { + packetConn = deadline.NewPacketConn(bufio.NewPacketConn(packetConn)) + } + + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + rto := defaultRTO + + // Phase 1: Binding + reportProgress(Progress{Phase: PhaseBinding}) + + txID := newTransactionID() + resp, latency, err := roundTrip(packetConn, serverAddr, txID, nil, rto) + if err != nil { + return nil, E.Cause(err, "binding request") + } + + rto = max(minRTO, 3*latency) + + externalAddr, ok := resp.externalAddr() + if !ok { + return nil, E.New("no mapped address in response") + } + + result := &Result{ + ExternalAddr: externalAddr.String(), + LatencyMs: int32(latency.Milliseconds()), + } + + reportProgress(Progress{ + Phase: PhaseBinding, + ExternalAddr: result.ExternalAddr, + LatencyMs: result.LatencyMs, + }) + + otherAddr := resp.otherAddr + if !otherAddr.IsValid() { + result.NATTypeSupported = false + reportProgress(Progress{ + Phase: PhaseDone, + ExternalAddr: result.ExternalAddr, + LatencyMs: result.LatencyMs, + }) + return result, nil + } + result.NATTypeSupported = true + + select { + case <-ctx.Done(): + return result, nil + default: + } + + // Phase 2: NAT Mapping Detection (RFC 5780 Section 4.3) + reportProgress(Progress{ + Phase: PhaseNATMapping, + ExternalAddr: result.ExternalAddr, + LatencyMs: result.LatencyMs, + }) + + result.NATMapping = detectNATMapping( + packetConn, serverSocksaddr.Port, externalAddr, otherAddr, rto, + ) + + reportProgress(Progress{ + Phase: PhaseNATMapping, + ExternalAddr: result.ExternalAddr, + LatencyMs: result.LatencyMs, + NATMapping: result.NATMapping, + }) + + select { + case <-ctx.Done(): + return result, nil + default: + } + + // Phase 3: NAT Filtering Detection (RFC 5780 Section 4.4) + reportProgress(Progress{ + Phase: PhaseNATFiltering, + ExternalAddr: result.ExternalAddr, + LatencyMs: result.LatencyMs, + NATMapping: result.NATMapping, + }) + + result.NATFiltering = detectNATFiltering(packetConn, serverAddr, rto) + + reportProgress(Progress{ + Phase: PhaseDone, + ExternalAddr: result.ExternalAddr, + LatencyMs: result.LatencyMs, + NATMapping: result.NATMapping, + NATFiltering: result.NATFiltering, + }) + + return result, nil +} + +func detectNATMapping( + conn net.PacketConn, + serverPort uint16, + externalAddr netip.AddrPort, + otherAddr netip.AddrPort, + rto time.Duration, +) NATMapping { + // Mapping Test II: Send to other_ip:server_port + testIIAddr := net.UDPAddrFromAddrPort( + netip.AddrPortFrom(otherAddr.Addr(), serverPort), + ) + txID2 := newTransactionID() + resp2, _, err := roundTrip(conn, testIIAddr, txID2, nil, rto) + if err != nil { + return NATMappingUnknown + } + + externalAddr2, ok := resp2.externalAddr() + if !ok { + return NATMappingUnknown + } + + if externalAddr == externalAddr2 { + return NATMappingEndpointIndependent + } + + // Mapping Test III: Send to other_ip:other_port + testIIIAddr := net.UDPAddrFromAddrPort(otherAddr) + txID3 := newTransactionID() + resp3, _, err := roundTrip(conn, testIIIAddr, txID3, nil, rto) + if err != nil { + return NATMappingUnknown + } + + externalAddr3, ok := resp3.externalAddr() + if !ok { + return NATMappingUnknown + } + + if externalAddr2 == externalAddr3 { + return NATMappingAddressDependent + } + return NATMappingAddressAndPortDependent +} + +func detectNATFiltering( + conn net.PacketConn, + serverAddr net.Addr, + rto time.Duration, +) NATFiltering { + // Filtering Test II: Request response from different IP and port + txID := newTransactionID() + _, _, err := roundTrip(conn, serverAddr, txID, + []stunAttribute{changeRequestAttr(changeIP | changePort)}, rto) + if err == nil { + return NATFilteringEndpointIndependent + } + + // Filtering Test III: Request response from different port only + txID = newTransactionID() + _, _, err = roundTrip(conn, serverAddr, txID, + []stunAttribute{changeRequestAttr(changePort)}, rto) + if err == nil { + return NATFilteringAddressDependent + } + + return NATFilteringAddressAndPortDependent +} + +func paddingLen(n int) int { + if n%4 == 0 { + return 0 + } + return 4 - n%4 +} diff --git a/common/tls/acme.go b/common/tls/acme.go index 08b24ed22c..93f293f6f2 100644 --- a/common/tls/acme.go +++ b/common/tls/acme.go @@ -5,18 +5,20 @@ package tls import ( "context" "crypto/tls" - "os" + "slices" "strings" "github.com/sagernet/sing-box/adapter" 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" "github.com/caddyserver/certmagic" + "github.com/libdns/acmedns" "github.com/libdns/alidns" "github.com/libdns/cloudflare" - "github.com/mholt/acmez/acme" + "github.com/mholt/acmez/v3/acme" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) @@ -37,7 +39,7 @@ func (w *acmeWrapper) Close() error { return nil } -func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) { +func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) { var acmeServer string switch options.Provider { case "", "letsencrypt": @@ -58,37 +60,55 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con } else { storage = certmagic.Default.Storage } + zapLogger := zap.New(zapcore.NewCore( + zapcore.NewConsoleEncoder(ACMEEncoderConfig()), + &ACMELogWriter{Logger: logger}, + zap.DebugLevel, + )) config := &certmagic.Config{ DefaultServerName: options.DefaultServerName, Storage: storage, - Logger: zap.New(zapcore.NewCore( - zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()), - os.Stderr, - zap.InfoLevel, - )), + Logger: zapLogger, } + profile := options.Profile + if profile == "" && acmeServer == certmagic.LetsEncryptProductionCA && slices.ContainsFunc(options.Domain, certmagic.SubjectIsIP) { + profile = "shortlived" + } + acmeConfig := certmagic.ACMEIssuer{ CA: acmeServer, Email: options.Email, Agreed: true, + Profile: profile, DisableHTTPChallenge: options.DisableHTTPChallenge, DisableTLSALPNChallenge: options.DisableTLSALPNChallenge, AltHTTPPort: int(options.AlternativeHTTPPort), AltTLSALPNPort: int(options.AlternativeTLSPort), - Logger: config.Logger, + Logger: zapLogger, } if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" { var solver certmagic.DNS01Solver switch dnsOptions.Provider { case C.DNSProviderAliDNS: solver.DNSProvider = &alidns.Provider{ - AccKeyID: dnsOptions.AliDNSOptions.AccessKeyID, - AccKeySecret: dnsOptions.AliDNSOptions.AccessKeySecret, - RegionID: dnsOptions.AliDNSOptions.RegionID, + CredentialInfo: alidns.CredentialInfo{ + AccessKeyID: dnsOptions.AliDNSOptions.AccessKeyID, + AccessKeySecret: dnsOptions.AliDNSOptions.AccessKeySecret, + RegionID: dnsOptions.AliDNSOptions.RegionID, + SecurityToken: dnsOptions.AliDNSOptions.SecurityToken, + }, } case C.DNSProviderCloudflare: solver.DNSProvider = &cloudflare.Provider{ - APIToken: dnsOptions.CloudflareOptions.APIToken, + APIToken: dnsOptions.CloudflareOptions.APIToken, + ZoneToken: dnsOptions.CloudflareOptions.ZoneToken, + } + case C.DNSProviderACMEDNS: + solver.DNSProvider = &acmedns.Provider{ + Username: dnsOptions.ACMEDNSOptions.Username, + Password: dnsOptions.ACMEDNSOptions.Password, + Subdomain: dnsOptions.ACMEDNSOptions.Subdomain, + ServerURL: dnsOptions.ACMEDNSOptions.ServerURL, } default: return nil, nil, E.New("unsupported ACME DNS01 provider type: " + dnsOptions.Provider) @@ -103,6 +123,7 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) { return config, nil }, + Logger: zapLogger, }) config = certmagic.New(cache, *config) var tlsConfig *tls.Config @@ -113,7 +134,7 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con } else { tlsConfig = &tls.Config{ GetCertificate: config.GetCertificate, - NextProtos: []string{ACMETLS1Protocol}, + NextProtos: []string{C.ACMETLS1Protocol}, } } return tlsConfig, &acmeWrapper{ctx: ctx, cfg: config, cache: cache, domain: options.Domain}, nil diff --git a/common/tls/acme_contstant.go b/common/tls/acme_contstant.go deleted file mode 100644 index c5cd2ff164..0000000000 --- a/common/tls/acme_contstant.go +++ /dev/null @@ -1,3 +0,0 @@ -package tls - -const ACMETLS1Protocol = "acme-tls/1" diff --git a/common/tls/acme_logger.go b/common/tls/acme_logger.go new file mode 100644 index 0000000000..cb3a1e3ce3 --- /dev/null +++ b/common/tls/acme_logger.go @@ -0,0 +1,41 @@ +package tls + +import ( + "strings" + + "github.com/sagernet/sing/common/logger" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type ACMELogWriter struct { + Logger logger.Logger +} + +func (w *ACMELogWriter) Write(p []byte) (n int, err error) { + logLine := strings.ReplaceAll(string(p), " ", ": ") + switch { + case strings.HasPrefix(logLine, "error: "): + w.Logger.Error(logLine[7:]) + case strings.HasPrefix(logLine, "warn: "): + w.Logger.Warn(logLine[6:]) + case strings.HasPrefix(logLine, "info: "): + w.Logger.Info(logLine[6:]) + case strings.HasPrefix(logLine, "debug: "): + w.Logger.Debug(logLine[7:]) + default: + w.Logger.Debug(logLine) + } + return len(p), nil +} + +func (w *ACMELogWriter) Sync() error { + return nil +} + +func ACMEEncoderConfig() zapcore.EncoderConfig { + config := zap.NewProductionEncoderConfig() + config.TimeKey = zapcore.OmitKey + return config +} diff --git a/common/tls/acme_stub.go b/common/tls/acme_stub.go index d97d05409d..53ced06d11 100644 --- a/common/tls/acme_stub.go +++ b/common/tls/acme_stub.go @@ -9,8 +9,9 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" ) -func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) { +func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) { return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`) } diff --git a/common/tls/apple_client.go b/common/tls/apple_client.go new file mode 100644 index 0000000000..441c29b34b --- /dev/null +++ b/common/tls/apple_client.go @@ -0,0 +1,44 @@ +//go:build darwin && cgo + +package tls + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/certificate" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/logger" +) + +const appleTLSEngineName = "Apple TLS engine" + +type appleClientConfig struct { + systemTLSConfig + userPEM []byte +} + +func (c *appleClientConfig) Clone() Config { + return &appleClientConfig{ + systemTLSConfig: c.systemTLSConfig.clone(), + userPEM: append([]byte(nil), c.userPEM...), + } +} + +func (c *appleClientConfig) resolveAnchors() (adapter.AppleAnchors, error) { + if len(c.userPEM) > 0 { + return certificate.NewAppleAnchors(c.userPEM) + } + return certificate.AcquireAnchors(nil, c.store), nil +} + +func newAppleClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions, allowEmptyServerName bool) (Config, error) { + base, validated, err := newSystemTLSConfig(ctx, serverAddress, options, allowEmptyServerName, appleTLSEngineName) + if err != nil { + return nil, err + } + return &appleClientConfig{ + systemTLSConfig: base, + userPEM: append([]byte(nil), validated.UserPEM...), + }, nil +} diff --git a/common/tls/apple_client_platform.go b/common/tls/apple_client_platform.go new file mode 100644 index 0000000000..9e2b7b53e0 --- /dev/null +++ b/common/tls/apple_client_platform.go @@ -0,0 +1,713 @@ +//go:build darwin && cgo + +package tls + +/* +#cgo CFLAGS: -x objective-c -fobjc-arc +#cgo LDFLAGS: -framework Foundation -framework Network -framework Security + +#include +#include "apple_client_platform_darwin.h" +*/ +import "C" + +import ( + "context" + "crypto/tls" + "crypto/x509" + "encoding/binary" + "io" + "math" + "net" + "os" + "runtime/cgo" + "strings" + "sync" + "time" + "unsafe" + + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + N "github.com/sagernet/sing/common/network" + + "golang.org/x/sys/unix" +) + +func (c *appleClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (Conn, error) { + tcpConn, ok := N.UnwrapReader(conn).(*net.TCPConn) + if !ok { + return nil, E.New("apple TLS: requires fd-backed TCP connection") + } + syscallConn, err := tcpConn.SyscallConn() + if err != nil { + return nil, E.Cause(err, "access raw connection") + } + + var dupFD int + controlErr := syscallConn.Control(func(fd uintptr) { + dupFD, err = unix.Dup(int(fd)) + }) + if controlErr != nil { + return nil, E.Cause(controlErr, "access raw connection") + } + if err != nil { + return nil, E.Cause(err, "duplicate raw connection") + } + + serverName := c.serverName + serverNamePtr := cStringOrNil(serverName) + defer cFree(serverNamePtr) + + alpn := strings.Join(c.nextProtos, "\n") + alpnPtr := cStringOrNil(alpn) + defer cFree(alpnPtr) + + anchors, err := c.resolveAnchors() + if err != nil { + return nil, err + } + var anchorsRef unsafe.Pointer + if anchors != nil { + anchorsRef = anchors.Ref() + } + + var ( + hasVerifyTime bool + verifyTimeUnixMilli int64 + ) + if c.timeFunc != nil { + hasVerifyTime = true + verifyTimeUnixMilli = c.timeFunc().UnixMilli() + } + + var errorPtr *C.char + client := C.box_apple_tls_client_create( + C.int(dupFD), + serverNamePtr, + alpnPtr, + C.size_t(len(alpn)), + C.uint16_t(c.minVersion), + C.uint16_t(c.maxVersion), + C.bool(c.insecure), + anchorsRef, + C.bool(c.anchorOnly), + C.bool(hasVerifyTime), + C.int64_t(verifyTimeUnixMilli), + &errorPtr, + ) + if anchors != nil { + anchors.Release() + } + if client == nil { + if errorPtr != nil { + defer C.free(unsafe.Pointer(errorPtr)) + return nil, E.New(C.GoString(errorPtr)) + } + return nil, E.New("apple TLS: create connection") + } + if err = waitAppleTLSClientReady(ctx, client); err != nil { + C.box_apple_tls_client_cancel(client) + C.box_apple_tls_client_free(client) + return nil, err + } + + var state C.box_apple_tls_state_t + stateOK := C.box_apple_tls_client_copy_state(client, &state, &errorPtr) + if !bool(stateOK) { + C.box_apple_tls_client_cancel(client) + C.box_apple_tls_client_free(client) + if errorPtr != nil { + defer C.free(unsafe.Pointer(errorPtr)) + return nil, E.New(C.GoString(errorPtr)) + } + return nil, E.New("apple TLS: read metadata") + } + defer C.box_apple_tls_state_free(&state) + + connectionState, rawCerts, err := parseAppleTLSState(&state) + if err != nil { + C.box_apple_tls_client_cancel(client) + C.box_apple_tls_client_free(client) + return nil, err + } + if len(c.certificatePublicKeySHA256) > 0 { + err = VerifyPublicKeySHA256(c.certificatePublicKeySHA256, rawCerts) + if err != nil { + C.box_apple_tls_client_cancel(client) + C.box_apple_tls_client_free(client) + return nil, err + } + } + + return &appleTLSConn{ + rawConn: conn, + client: client, + state: connectionState, + closed: make(chan struct{}), + }, nil +} + +const ( + appleTLSHandshakePollInterval = 100 * time.Millisecond + appleTLSWriteChunkSize = 32 * 1024 +) + +func waitAppleTLSClientReady(ctx context.Context, client *C.box_apple_tls_client_t) error { + for { + err := ctx.Err() + if err != nil { + C.box_apple_tls_client_cancel(client) + return err + } + + waitTimeout := appleTLSHandshakePollInterval + deadline, loaded := ctx.Deadline() + if loaded { + remaining := time.Until(deadline) + if remaining <= 0 { + C.box_apple_tls_client_cancel(client) + err = ctx.Err() + if err != nil { + return err + } + return context.DeadlineExceeded + } + if remaining < waitTimeout { + waitTimeout = remaining + } + } + + var errorPtr *C.char + waitResult := C.box_apple_tls_client_wait_ready(client, C.int(timeoutFromDuration(waitTimeout)), &errorPtr) + switch waitResult { + case 1: + return nil + case -2: + continue + case 0: + if errorPtr != nil { + defer C.free(unsafe.Pointer(errorPtr)) + return E.New(C.GoString(errorPtr)) + } + return E.New("apple TLS: handshake failed") + default: + return E.New("apple TLS: invalid handshake state") + } + } +} + +type appleTLSConn struct { + rawConn net.Conn + client *C.box_apple_tls_client_t + state tls.ConnectionState + + readAccess sync.Mutex + writeAccess sync.Mutex + stateAccess sync.RWMutex + closeOnce sync.Once + ioAccess sync.Mutex + ioGroup sync.WaitGroup + closed chan struct{} + readEOF bool + deadlineAccess sync.Mutex + readDeadline time.Time + writeDeadline time.Time + readTimedOut bool + writeTimedOut bool +} + +var ( + _ N.ExtendedConn = (*appleTLSConn)(nil) + _ N.ReadWaitCreator = (*appleTLSConn)(nil) +) + +func (c *appleTLSConn) Read(p []byte) (int, error) { + c.readAccess.Lock() + defer c.readAccess.Unlock() + if c.readEOF { + return 0, io.EOF + } + if len(p) == 0 { + return 0, nil + } + + return c.readIntoLocked(p) +} + +func (c *appleTLSConn) ReadBuffer(buffer *buf.Buffer) error { + c.readAccess.Lock() + defer c.readAccess.Unlock() + if buffer.IsFull() { + return io.ErrShortBuffer + } + startLen := buffer.Len() + n, err := c.readIntoLocked(buffer.FreeBytes()) + buffer.Truncate(startLen + n) + return err +} + +func (c *appleTLSConn) readIntoLocked(p []byte) (int, error) { + if c.readEOF { + return 0, io.EOF + } + if len(p) == 0 { + return 0, nil + } + + timeoutMs, err := c.prepareReadTimeout() + if err != nil { + return 0, err + } + + client, err := c.acquireClient() + if err != nil { + return 0, err + } + defer c.releaseClient() + + var eof C.bool + var errorPtr *C.char + n := C.box_apple_tls_client_read(client, unsafe.Pointer(&p[0]), C.size_t(len(p)), C.int(timeoutMs), &eof, &errorPtr) + switch { + case n == -2: + c.markReadTimedOut() + return 0, os.ErrDeadlineExceeded + case n >= 0: + if bool(eof) { + c.readEOF = true + if n == 0 { + return 0, io.EOF + } + } + return int(n), nil + default: + if errorPtr != nil { + defer C.free(unsafe.Pointer(errorPtr)) + if c.isClosed() { + return 0, net.ErrClosed + } + return 0, E.New(C.GoString(errorPtr)) + } + return 0, net.ErrClosed + } +} + +func (c *appleTLSConn) Write(p []byte) (int, error) { + c.writeAccess.Lock() + defer c.writeAccess.Unlock() + if len(p) == 0 { + return 0, nil + } + + client, err := c.acquireClient() + if err != nil { + return 0, err + } + defer c.releaseClient() + + deadline, err := c.prepareWriteDeadline() + if err != nil { + return 0, err + } + var written int + for written < len(p) { + timeoutMs, expired := deadlineTimeoutMs(deadline) + if expired { + C.box_apple_tls_client_cancel(client) + c.markWriteTimedOut() + return written, os.ErrDeadlineExceeded + } + chunkSize := min(len(p)-written, appleTLSWriteChunkSize) + chunk := p[written : written+chunkSize] + var errorPtr *C.char + n := C.box_apple_tls_client_write(client, unsafe.Pointer(&chunk[0]), C.size_t(len(chunk)), C.int(timeoutMs), &errorPtr) + switch { + case n == -2: + c.markWriteTimedOut() + return written, os.ErrDeadlineExceeded + case n >= 0: + written += int(n) + if int(n) != len(chunk) { + return written, io.ErrShortWrite + } + continue + } + return written, c.errorFromPointer(errorPtr) + } + return written, nil +} + +func (c *appleTLSConn) WriteBuffer(buffer *buf.Buffer) error { + defer buffer.Release() + _, err := c.Write(buffer.Bytes()) + return err +} + +func (c *appleTLSConn) CreateReadWaiter() (N.ReadWaiter, bool) { + return &appleTLSReadWaiter{ + conn: c, + results: make(chan *C.box_apple_tls_read_result_t, 1), + }, true +} + +func (c *appleTLSConn) Close() error { + var closeErr error + c.closeOnce.Do(func() { + close(c.closed) + C.box_apple_tls_client_cancel(c.client) + closeErr = c.rawConn.Close() + c.ioAccess.Lock() + c.ioGroup.Wait() + C.box_apple_tls_client_free(c.client) + c.client = nil + c.ioAccess.Unlock() + }) + return closeErr +} + +func (c *appleTLSConn) LocalAddr() net.Addr { + return c.rawConn.LocalAddr() +} + +func (c *appleTLSConn) RemoteAddr() net.Addr { + return c.rawConn.RemoteAddr() +} + +// SetDeadline installs deadlines for subsequent Read and Write calls. +// +// Deadlines only apply to subsequent Read or Write calls; an in-flight call +// does not observe later updates to its deadline. Callers that need to cancel +// an in-flight I/O must Close the connection instead. +// +// Once an active Read or Write trips its deadline, the underlying +// nw_connection is cancelled and the conn is no longer usable — callers must +// Close after a deadline error. +func (c *appleTLSConn) SetDeadline(t time.Time) error { + c.deadlineAccess.Lock() + c.readDeadline = t + c.writeDeadline = t + c.readTimedOut = false + c.writeTimedOut = false + c.deadlineAccess.Unlock() + return nil +} + +func (c *appleTLSConn) SetReadDeadline(t time.Time) error { + c.deadlineAccess.Lock() + c.readDeadline = t + c.readTimedOut = false + c.deadlineAccess.Unlock() + return nil +} + +func (c *appleTLSConn) SetWriteDeadline(t time.Time) error { + c.deadlineAccess.Lock() + c.writeDeadline = t + c.writeTimedOut = false + c.deadlineAccess.Unlock() + return nil +} + +func (c *appleTLSConn) prepareReadTimeout() (int, error) { + c.deadlineAccess.Lock() + defer c.deadlineAccess.Unlock() + if c.readTimedOut { + return 0, os.ErrDeadlineExceeded + } + timeoutMs, expired := deadlineTimeoutMs(c.readDeadline) + if expired { + c.readTimedOut = true + return 0, os.ErrDeadlineExceeded + } + return timeoutMs, nil +} + +func (c *appleTLSConn) prepareWriteDeadline() (time.Time, error) { + c.deadlineAccess.Lock() + defer c.deadlineAccess.Unlock() + if c.writeTimedOut { + return time.Time{}, os.ErrDeadlineExceeded + } + _, expired := deadlineTimeoutMs(c.writeDeadline) + if expired { + c.writeTimedOut = true + return time.Time{}, os.ErrDeadlineExceeded + } + return c.writeDeadline, nil +} + +func (c *appleTLSConn) markReadTimedOut() { + c.deadlineAccess.Lock() + c.readTimedOut = true + c.deadlineAccess.Unlock() +} + +func (c *appleTLSConn) markWriteTimedOut() { + c.deadlineAccess.Lock() + c.writeTimedOut = true + c.deadlineAccess.Unlock() +} + +func deadlineTimeoutMs(deadline time.Time) (int, bool) { + if deadline.IsZero() { + return -1, false + } + remaining := time.Until(deadline) + if remaining <= 0 { + return 0, true + } + return timeoutFromDuration(remaining), false +} + +func (c *appleTLSConn) isClosed() bool { + select { + case <-c.closed: + return true + default: + return false + } +} + +func (c *appleTLSConn) acquireClient() (*C.box_apple_tls_client_t, error) { + c.ioAccess.Lock() + defer c.ioAccess.Unlock() + if c.isClosed() { + return nil, net.ErrClosed + } + client := c.client + if client == nil { + return nil, net.ErrClosed + } + c.ioGroup.Add(1) + return client, nil +} + +func (c *appleTLSConn) releaseClient() { + c.ioGroup.Done() +} + +func (c *appleTLSConn) errorFromPointer(errorPtr *C.char) error { + if errorPtr != nil { + defer C.free(unsafe.Pointer(errorPtr)) + if c.isClosed() { + return net.ErrClosed + } + return E.New(C.GoString(errorPtr)) + } + return net.ErrClosed +} + +type appleTLSReadWaiter struct { + conn *appleTLSConn + options N.ReadWaitOptions + results chan *C.box_apple_tls_read_result_t +} + +var _ N.ReadWaiter = (*appleTLSReadWaiter)(nil) + +func (w *appleTLSReadWaiter) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy bool) { + w.options = options + if w.results == nil { + w.results = make(chan *C.box_apple_tls_read_result_t, 1) + } + return false +} + +func (w *appleTLSReadWaiter) WaitReadBuffer() (*buf.Buffer, error) { + c := w.conn + c.readAccess.Lock() + defer c.readAccess.Unlock() + if c.readEOF { + return nil, io.EOF + } + maximumLen := readWaitFreeLen(w.options) + if maximumLen <= 0 { + return nil, io.ErrShortBuffer + } + timeoutMs, err := c.prepareReadTimeout() + if err != nil { + return nil, err + } + client, err := c.acquireClient() + if err != nil { + return nil, err + } + defer c.releaseClient() + + handle := cgo.NewHandle(w) + defer handle.Delete() + var errorPtr *C.char + if !bool(C.box_apple_tls_client_read_async(client, C.size_t(maximumLen), C.uintptr_t(handle), &errorPtr)) { + return nil, c.errorFromPointer(errorPtr) + } + + var result *C.box_apple_tls_read_result_t + if timeoutMs >= 0 { + timer := time.NewTimer(time.Duration(timeoutMs) * time.Millisecond) + defer timer.Stop() + select { + case result = <-w.results: + case <-timer.C: + C.box_apple_tls_client_cancel(client) + result = <-w.results + if result != nil { + C.box_apple_tls_read_result_free(result) + } + c.markReadTimedOut() + return nil, os.ErrDeadlineExceeded + } + } else { + result = <-w.results + } + return c.readWaitResultToBuffer(result, w.options) +} + +func (c *appleTLSConn) readWaitResultToBuffer(result *C.box_apple_tls_read_result_t, options N.ReadWaitOptions) (*buf.Buffer, error) { + defer C.box_apple_tls_read_result_free(result) + buffer := options.NewBuffer() + if buffer.IsFull() { + buffer.Release() + return nil, io.ErrShortBuffer + } + startLen := buffer.Len() + var eof C.bool + var errorPtr *C.char + n := C.box_apple_tls_read_result_copy(result, unsafe.Pointer(&buffer.FreeBytes()[0]), C.size_t(buffer.FreeLen()), &eof, &errorPtr) + if n < 0 { + buffer.Release() + return nil, c.errorFromPointer(errorPtr) + } + if bool(eof) { + c.readEOF = true + if n == 0 { + buffer.Release() + return nil, io.EOF + } + } + if n == 0 { + buffer.Release() + return nil, io.ErrNoProgress + } + buffer.Truncate(startLen + int(n)) + options.PostReturn(buffer) + return buffer, nil +} + +func readWaitFreeLen(options N.ReadWaitOptions) int { + if options.IncreaseBuffer { + return 65535 - options.FrontHeadroom - options.RearHeadroom + } + if options.MTU > 0 { + return options.MTU + } + return buf.BufferSize - options.FrontHeadroom - options.RearHeadroom +} + +//export box_apple_tls_read_callback +func box_apple_tls_read_callback(callbackHandle C.uintptr_t, result *C.box_apple_tls_read_result_t) { + handle := cgo.Handle(callbackHandle) + waiter, ok := handle.Value().(*appleTLSReadWaiter) + if !ok { + C.box_apple_tls_read_result_free(result) + return + } + select { + case waiter.results <- result: + default: + C.box_apple_tls_read_result_free(result) + } +} + +func (c *appleTLSConn) NetConn() net.Conn { + return c.rawConn +} + +func (c *appleTLSConn) HandshakeContext(ctx context.Context) error { + return nil +} + +func (c *appleTLSConn) ConnectionState() ConnectionState { + c.stateAccess.RLock() + defer c.stateAccess.RUnlock() + return c.state +} + +func parseAppleTLSState(state *C.box_apple_tls_state_t) (tls.ConnectionState, [][]byte, error) { + rawCerts, peerCertificates, err := parseAppleCertChain(state.peer_cert_chain, state.peer_cert_chain_len) + if err != nil { + return tls.ConnectionState{}, nil, err + } + var negotiatedProtocol string + if state.alpn != nil { + negotiatedProtocol = C.GoString(state.alpn) + } + var serverName string + if state.server_name != nil { + serverName = C.GoString(state.server_name) + } + return tls.ConnectionState{ + Version: uint16(state.version), + HandshakeComplete: true, + CipherSuite: uint16(state.cipher_suite), + NegotiatedProtocol: negotiatedProtocol, + ServerName: serverName, + PeerCertificates: peerCertificates, + }, rawCerts, nil +} + +func parseAppleCertChain(chain *C.uint8_t, chainLen C.size_t) ([][]byte, []*x509.Certificate, error) { + if chain == nil || chainLen == 0 { + return nil, nil, nil + } + chainBytes := C.GoBytes(unsafe.Pointer(chain), C.int(chainLen)) + var ( + rawCerts [][]byte + peerCertificates []*x509.Certificate + ) + for len(chainBytes) >= 4 { + certificateLen := binary.BigEndian.Uint32(chainBytes[:4]) + chainBytes = chainBytes[4:] + if len(chainBytes) < int(certificateLen) { + return nil, nil, E.New("apple TLS: invalid certificate chain") + } + certificateData := append([]byte(nil), chainBytes[:certificateLen]...) + certificate, err := x509.ParseCertificate(certificateData) + if err != nil { + return nil, nil, E.Cause(err, "parse peer certificate") + } + rawCerts = append(rawCerts, certificateData) + peerCertificates = append(peerCertificates, certificate) + chainBytes = chainBytes[certificateLen:] + } + if len(chainBytes) != 0 { + return nil, nil, E.New("apple TLS: invalid certificate chain") + } + return rawCerts, peerCertificates, nil +} + +func timeoutFromDuration(timeout time.Duration) int { + if timeout <= 0 { + return 0 + } + timeoutMilliseconds := int64(timeout / time.Millisecond) + if timeout%time.Millisecond != 0 { + timeoutMilliseconds++ + } + if timeoutMilliseconds > math.MaxInt32 { + return math.MaxInt32 + } + return int(timeoutMilliseconds) +} + +func cStringOrNil(value string) *C.char { + if value == "" { + return nil + } + return C.CString(value) +} + +func cFree(pointer *C.char) { + if pointer != nil { + C.free(unsafe.Pointer(pointer)) + } +} diff --git a/common/tls/apple_client_platform_benchmark_test.go b/common/tls/apple_client_platform_benchmark_test.go new file mode 100644 index 0000000000..27fcf1532c --- /dev/null +++ b/common/tls/apple_client_platform_benchmark_test.go @@ -0,0 +1,278 @@ +//go:build darwin && cgo + +package tls + +import ( + "bytes" + stdtls "crypto/tls" + "crypto/x509" + "errors" + "io" + "net" + "testing" + + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/json/badoption" + N "github.com/sagernet/sing/common/network" +) + +const ( + appleTLSBenchmarkReadPayloadSize = 16 * 1024 + appleTLSBenchmarkWritePayloadSize = 48 * 1024 +) + +func BenchmarkAppleClientReadBuffer(b *testing.B) { + payload := bytes.Repeat([]byte{'r'}, appleTLSBenchmarkReadPayloadSize) + start := make(chan struct{}) + clientConn, serverResult := newAppleBenchmarkClientConn(b, func(conn *stdtls.Conn) error { + <-start + for range b.N { + if err := writeBenchmarkPayload(conn, payload); err != nil { + return err + } + } + return nil + }) + defer clientConn.Close() + + extendedConn := clientConn.(N.ExtendedConn) + b.ReportAllocs() + b.SetBytes(int64(len(payload))) + b.ReportMetric(float64(len(payload)), "payload_B") + b.ResetTimer() + close(start) + target := b.N * len(payload) + var received int + for received < target { + buffer := buf.NewSize(len(payload)) + err := extendedConn.ReadBuffer(buffer) + if err != nil { + buffer.Release() + b.Fatal(err) + } + received += buffer.Len() + buffer.Release() + } + b.StopTimer() + if err := <-serverResult; err != nil { + b.Fatal(err) + } +} + +func BenchmarkAppleClientReadWaiter(b *testing.B) { + payload := bytes.Repeat([]byte{'w'}, appleTLSBenchmarkReadPayloadSize) + start := make(chan struct{}) + clientConn, serverResult := newAppleBenchmarkClientConn(b, func(conn *stdtls.Conn) error { + <-start + for range b.N { + if err := writeBenchmarkPayload(conn, payload); err != nil { + return err + } + } + return nil + }) + defer clientConn.Close() + + readWaiter, ok := clientConn.(N.ReadWaitCreator).CreateReadWaiter() + if !ok { + b.Fatal("expected read waiter") + } + readWaiter.InitializeReadWaiter(N.ReadWaitOptions{ + MTU: appleTLSBenchmarkReadPayloadSize, + }) + b.ReportAllocs() + b.SetBytes(int64(len(payload))) + b.ReportMetric(float64(len(payload)), "payload_B") + b.ResetTimer() + close(start) + target := b.N * len(payload) + var received int + for received < target { + buffer, err := readWaiter.WaitReadBuffer() + if err != nil { + if errors.Is(err, io.ErrNoProgress) { + continue + } + b.Fatal(err) + } + received += buffer.Len() + buffer.Release() + } + b.StopTimer() + if err := <-serverResult; err != nil { + b.Fatal(err) + } +} + +func BenchmarkAppleClientWriteBuffer(b *testing.B) { + payload := bytes.Repeat([]byte{'x'}, appleTLSBenchmarkWritePayloadSize) + start := make(chan struct{}) + clientConn, serverResult := newAppleBenchmarkClientConn(b, func(conn *stdtls.Conn) error { + <-start + _, err := io.CopyN(io.Discard, conn, int64(b.N*len(payload))) + return err + }) + defer clientConn.Close() + + extendedConn := clientConn.(N.ExtendedConn) + b.ReportAllocs() + b.SetBytes(int64(len(payload))) + b.ReportMetric(float64(len(payload)), "payload_B") + b.ReportMetric(float64(appleTLSWriteChunkSize), "write_chunk_B") + b.ResetTimer() + close(start) + for range b.N { + buffer := buf.NewSize(len(payload)) + _, err := buffer.Write(payload) + if err != nil { + buffer.Release() + b.Fatal(err) + } + err = extendedConn.WriteBuffer(buffer) + if err != nil { + b.Fatal(err) + } + } + b.StopTimer() + if err := <-serverResult; err != nil { + b.Fatal(err) + } +} + +func BenchmarkStdlibClientReadBuffer(b *testing.B) { + payload := bytes.Repeat([]byte{'r'}, appleTLSBenchmarkReadPayloadSize) + start := make(chan struct{}) + clientConn, serverResult := newStdlibBenchmarkClientConn(b, func(conn *stdtls.Conn) error { + <-start + for range b.N { + if err := writeBenchmarkPayload(conn, payload); err != nil { + return err + } + } + return nil + }) + defer clientConn.Close() + + b.ReportAllocs() + b.SetBytes(int64(len(payload))) + b.ReportMetric(float64(len(payload)), "payload_B") + b.ResetTimer() + close(start) + target := b.N * len(payload) + var received int + for received < target { + buffer := buf.NewSize(len(payload)) + n, err := clientConn.Read(buffer.FreeBytes()) + if n > 0 { + buffer.Truncate(buffer.Len() + n) + } + received += buffer.Len() + buffer.Release() + if err != nil && received < target { + b.Fatal(err) + } + } + b.StopTimer() + if err := <-serverResult; err != nil { + b.Fatal(err) + } +} + +func BenchmarkStdlibClientWriteBuffer(b *testing.B) { + payload := bytes.Repeat([]byte{'x'}, appleTLSBenchmarkWritePayloadSize) + start := make(chan struct{}) + clientConn, serverResult := newStdlibBenchmarkClientConn(b, func(conn *stdtls.Conn) error { + <-start + _, err := io.CopyN(io.Discard, conn, int64(b.N*len(payload))) + return err + }) + defer clientConn.Close() + + b.ReportAllocs() + b.SetBytes(int64(len(payload))) + b.ReportMetric(float64(len(payload)), "payload_B") + b.ResetTimer() + close(start) + for range b.N { + buffer := buf.NewSize(len(payload)) + _, err := buffer.Write(payload) + if err != nil { + buffer.Release() + b.Fatal(err) + } + _, err = clientConn.Write(buffer.Bytes()) + buffer.Release() + if err != nil { + b.Fatal(err) + } + } + b.StopTimer() + if err := <-serverResult; err != nil { + b.Fatal(err) + } +} + +func newAppleBenchmarkClientConn(b *testing.B, handler func(*stdtls.Conn) error) (Conn, <-chan error) { + b.Helper() + + serverCertificate, serverCertificatePEM := newAppleTestCertificate(b, "localhost") + serverResult, serverAddress := startAppleTLSIOTestServer(b, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS12, + MaxVersion: stdtls.VersionTLS12, + }, handler) + + clientConn, err := newAppleTestClientConn(b, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: "apple", + ServerName: "localhost", + MinVersion: "1.2", + MaxVersion: "1.2", + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err != nil { + b.Fatal(err) + } + return clientConn, serverResult +} + +func newStdlibBenchmarkClientConn(b *testing.B, handler func(*stdtls.Conn) error) (*stdtls.Conn, <-chan error) { + b.Helper() + + serverCertificate, serverCertificatePEM := newAppleTestCertificate(b, "localhost") + serverResult, serverAddress := startAppleTLSIOTestServer(b, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS12, + MaxVersion: stdtls.VersionTLS12, + }, handler) + + roots := x509.NewCertPool() + if !roots.AppendCertsFromPEM([]byte(serverCertificatePEM)) { + b.Fatal("parse benchmark certificate") + } + dialer := &net.Dialer{ + Timeout: appleTLSTestTimeout, + } + clientConn, err := stdtls.DialWithDialer(dialer, "tcp", serverAddress, &stdtls.Config{ + ServerName: "localhost", + RootCAs: roots, + MinVersion: stdtls.VersionTLS12, + MaxVersion: stdtls.VersionTLS12, + }) + if err != nil { + b.Fatal(err) + } + return clientConn, serverResult +} + +func writeBenchmarkPayload(writer io.Writer, payload []byte) error { + for len(payload) > 0 { + n, err := writer.Write(payload) + if err != nil { + return err + } + payload = payload[n:] + } + return nil +} diff --git a/common/tls/apple_client_platform_darwin.h b/common/tls/apple_client_platform_darwin.h new file mode 100644 index 0000000000..43b3e031d8 --- /dev/null +++ b/common/tls/apple_client_platform_darwin.h @@ -0,0 +1,43 @@ +#include +#include +#include +#include + +typedef struct box_apple_tls_client box_apple_tls_client_t; +typedef struct box_apple_tls_read_result box_apple_tls_read_result_t; + +typedef struct box_apple_tls_state { + uint16_t version; + uint16_t cipher_suite; + char *alpn; + char *server_name; + uint8_t *peer_cert_chain; + size_t peer_cert_chain_len; +} box_apple_tls_state_t; + +box_apple_tls_client_t *box_apple_tls_client_create( + int connected_socket, + const char *server_name, + const char *alpn, + size_t alpn_len, + uint16_t min_version, + uint16_t max_version, + bool insecure, + void *anchors_cf, + bool anchor_only, + bool has_verify_time, + int64_t verify_time_unix_millis, + char **error_out +); + +int box_apple_tls_client_wait_ready(box_apple_tls_client_t *client, int timeout_msec, char **error_out); +void box_apple_tls_client_cancel(box_apple_tls_client_t *client); +void box_apple_tls_client_free(box_apple_tls_client_t *client); +ssize_t box_apple_tls_client_read(box_apple_tls_client_t *client, void *buffer, size_t buffer_len, int timeout_msec, bool *eof_out, char **error_out); +ssize_t box_apple_tls_client_write(box_apple_tls_client_t *client, const void *buffer, size_t buffer_len, int timeout_msec, char **error_out); +bool box_apple_tls_client_read_async(box_apple_tls_client_t *client, size_t maximum_len, uintptr_t callback_handle, char **error_out); +ssize_t box_apple_tls_read_result_copy(box_apple_tls_read_result_t *result, void *buffer, size_t buffer_len, bool *eof_out, char **error_out); +void box_apple_tls_read_result_free(box_apple_tls_read_result_t *result); +bool box_apple_tls_client_copy_state(box_apple_tls_client_t *client, box_apple_tls_state_t *state, char **error_out); +void box_apple_tls_state_free(box_apple_tls_state_t *state); +ssize_t box_apple_tls_copy_dispatch_data_for_test(const void *first, size_t first_len, const void *second, size_t second_len, void *buffer, size_t buffer_len, char **error_out); diff --git a/common/tls/apple_client_platform_darwin.m b/common/tls/apple_client_platform_darwin.m new file mode 100644 index 0000000000..f4d0eb6793 --- /dev/null +++ b/common/tls/apple_client_platform_darwin.m @@ -0,0 +1,759 @@ +#import "apple_client_platform_darwin.h" + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +typedef nw_connection_t _Nullable (*box_nw_connection_create_with_connected_socket_and_parameters_f)(int connected_socket, nw_parameters_t parameters); +typedef const char * _Nullable (*box_sec_protocol_metadata_string_accessor_f)(sec_protocol_metadata_t metadata); + +typedef struct box_apple_tls_client { + void *connection; + void *queue; + void *ready_semaphore; + void *anchors; + atomic_int ref_count; + atomic_bool ready; + atomic_bool ready_done; + char *ready_error; + box_apple_tls_state_t state; +} box_apple_tls_client_t; + +struct box_apple_tls_read_result { + void *content; + bool eof; + char *error; +}; + +extern void box_apple_tls_read_callback(uintptr_t callback_handle, box_apple_tls_read_result_t *result); + +static nw_connection_t box_apple_tls_connection(box_apple_tls_client_t *client) { + if (client == NULL || client->connection == NULL) { + return nil; + } + return (__bridge nw_connection_t)client->connection; +} + +static dispatch_queue_t box_apple_tls_client_queue(box_apple_tls_client_t *client) { + if (client == NULL || client->queue == NULL) { + return nil; + } + return (__bridge dispatch_queue_t)client->queue; +} + +static dispatch_semaphore_t box_apple_tls_ready_semaphore(box_apple_tls_client_t *client) { + if (client == NULL || client->ready_semaphore == NULL) { + return nil; + } + return (__bridge dispatch_semaphore_t)client->ready_semaphore; +} + +static NSArray *box_apple_tls_client_anchors(box_apple_tls_client_t *client) { + if (client == NULL || client->anchors == NULL) { + return nil; + } + return (__bridge NSArray *)client->anchors; +} + +static dispatch_data_t box_apple_tls_read_result_content(box_apple_tls_read_result_t *result) { + if (result == NULL || result->content == NULL) { + return nil; + } + return (__bridge dispatch_data_t)result->content; +} + +static void box_apple_tls_state_reset(box_apple_tls_state_t *state) { + if (state == NULL) { + return; + } + free(state->alpn); + free(state->server_name); + free(state->peer_cert_chain); + memset(state, 0, sizeof(box_apple_tls_state_t)); +} + +static void box_apple_tls_client_destroy(box_apple_tls_client_t *client) { + free(client->ready_error); + box_apple_tls_state_reset(&client->state); + if (client->anchors != NULL) { + CFRelease((CFTypeRef)client->anchors); + } + if (client->ready_semaphore != NULL) { + CFBridgingRelease(client->ready_semaphore); + } + if (client->connection != NULL) { + CFBridgingRelease(client->connection); + } + if (client->queue != NULL) { + CFBridgingRelease(client->queue); + } + free(client); +} + +static void box_apple_tls_client_release(box_apple_tls_client_t *client) { + if (client == NULL) { + return; + } + if (atomic_fetch_sub(&client->ref_count, 1) == 1) { + box_apple_tls_client_destroy(client); + } +} + +static void box_set_error_string(char **error_out, NSString *message) { + if (error_out == NULL || *error_out != NULL) { + return; + } + const char *utf8 = [message UTF8String]; + *error_out = strdup(utf8 != NULL ? utf8 : "unknown error"); +} + +static void box_set_error_message(char **error_out, const char *message) { + if (error_out == NULL || *error_out != NULL) { + return; + } + *error_out = strdup(message != NULL ? message : "unknown error"); +} + +static void box_set_error_from_nw_error(char **error_out, nw_error_t error) { + if (error == NULL) { + box_set_error_message(error_out, "unknown network error"); + return; + } + CFErrorRef cfError = nw_error_copy_cf_error(error); + if (cfError == NULL) { + box_set_error_message(error_out, "unknown network error"); + return; + } + NSString *description = [(__bridge NSError *)cfError description]; + box_set_error_string(error_out, description); + CFRelease(cfError); +} + +static ssize_t box_apple_tls_dispatch_data_copy(dispatch_data_t content, void *buffer, size_t buffer_len, char **error_out) { + if (content == nil) { + return 0; + } + size_t content_size = dispatch_data_get_size(content); + if (content_size == 0) { + return 0; + } + if (buffer == NULL) { + box_set_error_message(error_out, "apple TLS: read buffer unavailable"); + return -1; + } + __block size_t copied = 0; + __block bool overflow = false; + bool complete = dispatch_data_apply(content, ^bool(dispatch_data_t region, size_t offset, const void *region_buffer, size_t region_size) { + (void)region; + (void)offset; + if (region_size == 0) { + return true; + } + if (region_buffer == NULL || region_size > buffer_len - copied) { + overflow = true; + return false; + } + memcpy((uint8_t *)buffer + copied, region_buffer, region_size); + copied += region_size; + return true; + }); + if (!complete || overflow) { + box_set_error_message(error_out, "apple TLS: read buffer too small"); + return -1; + } + return (ssize_t)copied; +} + +ssize_t box_apple_tls_copy_dispatch_data_for_test(const void *first, size_t first_len, const void *second, size_t second_len, void *buffer, size_t buffer_len, char **error_out) { + @autoreleasepool { + dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0); + dispatch_data_t first_data = first_len > 0 ? dispatch_data_create(first, first_len, queue, DISPATCH_DATA_DESTRUCTOR_DEFAULT) : dispatch_data_empty; + dispatch_data_t second_data = second_len > 0 ? dispatch_data_create(second, second_len, queue, DISPATCH_DATA_DESTRUCTOR_DEFAULT) : dispatch_data_empty; + dispatch_data_t content = dispatch_data_create_concat(first_data, second_data); + return box_apple_tls_dispatch_data_copy(content, buffer, buffer_len, error_out); + } +} + +static char *box_apple_tls_metadata_copy_negotiated_protocol(sec_protocol_metadata_t metadata) { + static box_sec_protocol_metadata_string_accessor_f copy_fn; + static box_sec_protocol_metadata_string_accessor_f get_fn; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + copy_fn = (box_sec_protocol_metadata_string_accessor_f)dlsym(RTLD_DEFAULT, "sec_protocol_metadata_copy_negotiated_protocol"); + get_fn = (box_sec_protocol_metadata_string_accessor_f)dlsym(RTLD_DEFAULT, "sec_protocol_metadata_get_negotiated_protocol"); + }); + if (copy_fn != NULL) { + return (char *)copy_fn(metadata); + } + if (get_fn != NULL) { + const char *protocol = get_fn(metadata); + if (protocol != NULL) { + return strdup(protocol); + } + } + return NULL; +} + +static char *box_apple_tls_metadata_copy_server_name(sec_protocol_metadata_t metadata) { + static box_sec_protocol_metadata_string_accessor_f copy_fn; + static box_sec_protocol_metadata_string_accessor_f get_fn; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + copy_fn = (box_sec_protocol_metadata_string_accessor_f)dlsym(RTLD_DEFAULT, "sec_protocol_metadata_copy_server_name"); + get_fn = (box_sec_protocol_metadata_string_accessor_f)dlsym(RTLD_DEFAULT, "sec_protocol_metadata_get_server_name"); + }); + if (copy_fn != NULL) { + return (char *)copy_fn(metadata); + } + if (get_fn != NULL) { + const char *server_name = get_fn(metadata); + if (server_name != NULL) { + return strdup(server_name); + } + } + return NULL; +} + +static NSArray *box_split_lines(const char *content, size_t content_len) { + if (content == NULL || content_len == 0) { + return @[]; + } + NSString *string = [[NSString alloc] initWithBytes:content length:content_len encoding:NSUTF8StringEncoding]; + if (string == nil) { + return @[]; + } + NSMutableArray *lines = [NSMutableArray array]; + [string enumerateLinesUsingBlock:^(NSString *line, BOOL *stop) { + if (line.length > 0) { + [lines addObject:line]; + } + }]; + return lines; +} + +static bool box_evaluate_trust(sec_trust_t trust, NSArray *anchors, bool anchor_only, NSDate *verify_date) { + bool result = false; + SecTrustRef trustRef = sec_trust_copy_ref(trust); + if (trustRef == NULL) { + return false; + } + if (verify_date != nil && SecTrustSetVerifyDate(trustRef, (__bridge CFDateRef)verify_date) != errSecSuccess) { + CFRelease(trustRef); + return false; + } + if (anchors.count > 0 || anchor_only) { + CFMutableArrayRef anchorArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + for (id certificate in anchors) { + CFArrayAppendValue(anchorArray, (__bridge const void *)certificate); + } + SecTrustSetAnchorCertificates(trustRef, anchorArray); + SecTrustSetAnchorCertificatesOnly(trustRef, anchor_only); + CFRelease(anchorArray); + } + CFErrorRef error = NULL; + result = SecTrustEvaluateWithError(trustRef, &error); + if (error != NULL) { + CFRelease(error); + } + CFRelease(trustRef); + return result; +} + +static nw_connection_t box_apple_tls_create_connection(int connected_socket, nw_parameters_t parameters) { + static box_nw_connection_create_with_connected_socket_and_parameters_f create_fn; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + char name[] = "sretemarap_dna_tekcos_detcennoc_htiw_etaerc_noitcennoc_wn"; + for (size_t i = 0, j = sizeof(name) - 2; i < j; i++, j--) { + char t = name[i]; + name[i] = name[j]; + name[j] = t; + } + create_fn = (box_nw_connection_create_with_connected_socket_and_parameters_f)dlsym(RTLD_DEFAULT, name); + }); + if (create_fn == NULL) { + return nil; + } + return create_fn(connected_socket, parameters); +} + +static bool box_apple_tls_state_copy(const box_apple_tls_state_t *source, box_apple_tls_state_t *destination) { + memset(destination, 0, sizeof(box_apple_tls_state_t)); + destination->version = source->version; + destination->cipher_suite = source->cipher_suite; + if (source->alpn != NULL) { + destination->alpn = strdup(source->alpn); + if (destination->alpn == NULL) { + goto oom; + } + } + if (source->server_name != NULL) { + destination->server_name = strdup(source->server_name); + if (destination->server_name == NULL) { + goto oom; + } + } + if (source->peer_cert_chain_len > 0) { + destination->peer_cert_chain = malloc(source->peer_cert_chain_len); + if (destination->peer_cert_chain == NULL) { + goto oom; + } + memcpy(destination->peer_cert_chain, source->peer_cert_chain, source->peer_cert_chain_len); + destination->peer_cert_chain_len = source->peer_cert_chain_len; + } + return true; + +oom: + box_apple_tls_state_reset(destination); + return false; +} + +// Captures TLS negotiation results from the verify block. The sec_metadata +// exposed here is live for the duration of the handshake; the one retrieved +// after nw_connection_state_ready may return stale ALPN/server_name buffers. +static void box_apple_tls_state_load(sec_protocol_metadata_t sec_metadata, box_apple_tls_state_t *state) { + state->version = (uint16_t)sec_protocol_metadata_get_negotiated_tls_protocol_version(sec_metadata); + state->cipher_suite = (uint16_t)sec_protocol_metadata_get_negotiated_tls_ciphersuite(sec_metadata); + state->alpn = box_apple_tls_metadata_copy_negotiated_protocol(sec_metadata); + state->server_name = box_apple_tls_metadata_copy_server_name(sec_metadata); + + NSMutableData *chain_data = [NSMutableData data]; + sec_protocol_metadata_access_peer_certificate_chain(sec_metadata, ^(sec_certificate_t certificate) { + SecCertificateRef certificate_ref = sec_certificate_copy_ref(certificate); + if (certificate_ref == NULL) { + return; + } + CFDataRef certificate_data = SecCertificateCopyData(certificate_ref); + CFRelease(certificate_ref); + if (certificate_data == NULL) { + return; + } + uint32_t certificate_len = (uint32_t)CFDataGetLength(certificate_data); + uint32_t network_len = htonl(certificate_len); + [chain_data appendBytes:&network_len length:sizeof(network_len)]; + [chain_data appendBytes:CFDataGetBytePtr(certificate_data) length:certificate_len]; + CFRelease(certificate_data); + }); + if (chain_data.length > 0) { + state->peer_cert_chain = malloc(chain_data.length); + if (state->peer_cert_chain != NULL) { + memcpy(state->peer_cert_chain, chain_data.bytes, chain_data.length); + state->peer_cert_chain_len = chain_data.length; + } + } +} + +box_apple_tls_client_t *box_apple_tls_client_create( + int connected_socket, + const char *server_name, + const char *alpn, + size_t alpn_len, + uint16_t min_version, + uint16_t max_version, + bool insecure, + void *anchors_cf, + bool anchor_only, + bool has_verify_time, + int64_t verify_time_unix_millis, + char **error_out +) { + box_apple_tls_client_t *client = calloc(1, sizeof(box_apple_tls_client_t)); + if (client == NULL) { + close(connected_socket); + box_set_error_message(error_out, "apple TLS: out of memory"); + return NULL; + } + client->queue = (__bridge_retained void *)dispatch_queue_create("sing-box.apple-private-tls", DISPATCH_QUEUE_SERIAL); + client->ready_semaphore = (__bridge_retained void *)dispatch_semaphore_create(0); + atomic_init(&client->ref_count, 1); + atomic_init(&client->ready, false); + atomic_init(&client->ready_done, false); + if (anchors_cf != NULL) { + client->anchors = (void *)CFRetain(anchors_cf); + } + + NSArray *alpnList = box_split_lines(alpn, alpn_len); + NSDate *verifyDate = nil; + if (has_verify_time) { + verifyDate = [NSDate dateWithTimeIntervalSince1970:(NSTimeInterval)verify_time_unix_millis / 1000.0]; + } + nw_parameters_t parameters = nw_parameters_create_secure_tcp(^(nw_protocol_options_t tls_options) { + sec_protocol_options_t sec_options = nw_tls_copy_sec_protocol_options(tls_options); + if (min_version != 0) { + sec_protocol_options_set_min_tls_protocol_version(sec_options, (tls_protocol_version_t)min_version); + } + if (max_version != 0) { + sec_protocol_options_set_max_tls_protocol_version(sec_options, (tls_protocol_version_t)max_version); + } + if (server_name != NULL && server_name[0] != '\0') { + sec_protocol_options_set_tls_server_name(sec_options, server_name); + } + for (NSString *protocol in alpnList) { + sec_protocol_options_add_tls_application_protocol(sec_options, protocol.UTF8String); + } + sec_protocol_options_set_peer_authentication_required(sec_options, !insecure); + sec_protocol_options_set_verify_block(sec_options, ^(sec_protocol_metadata_t metadata, sec_trust_t trust, sec_protocol_verify_complete_t complete) { + if (client->state.version == 0) { + box_apple_tls_state_load(metadata, &client->state); + } + complete(insecure || box_evaluate_trust(trust, box_apple_tls_client_anchors(client), anchor_only, verifyDate)); + }, box_apple_tls_client_queue(client)); + }, NW_PARAMETERS_DEFAULT_CONFIGURATION); + + nw_connection_t connection = box_apple_tls_create_connection(connected_socket, parameters); + if (connection == NULL) { + close(connected_socket); + if (client->anchors != NULL) { + CFRelease((CFTypeRef)client->anchors); + } + if (client->ready_semaphore != NULL) { + CFBridgingRelease(client->ready_semaphore); + } + if (client->queue != NULL) { + CFBridgingRelease(client->queue); + } + free(client); + box_set_error_message(error_out, "apple TLS: failed to create connection"); + return NULL; + } + + client->connection = (__bridge_retained void *)connection; + atomic_fetch_add(&client->ref_count, 1); + + nw_connection_set_state_changed_handler(connection, ^(nw_connection_state_t state, nw_error_t error) { + switch (state) { + case nw_connection_state_ready: + if (!atomic_load(&client->ready_done)) { + if (client->state.version == 0) { + box_set_error_message(&client->ready_error, "apple TLS: metadata unavailable"); + } else { + atomic_store(&client->ready, true); + } + atomic_store(&client->ready_done, true); + dispatch_semaphore_signal(box_apple_tls_ready_semaphore(client)); + } + break; + case nw_connection_state_failed: + if (!atomic_load(&client->ready_done)) { + box_set_error_from_nw_error(&client->ready_error, error); + atomic_store(&client->ready_done, true); + dispatch_semaphore_signal(box_apple_tls_ready_semaphore(client)); + } + break; + case nw_connection_state_cancelled: + if (!atomic_load(&client->ready_done)) { + box_set_error_from_nw_error(&client->ready_error, error); + atomic_store(&client->ready_done, true); + dispatch_semaphore_signal(box_apple_tls_ready_semaphore(client)); + } + box_apple_tls_client_release(client); + break; + default: + break; + } + }); + nw_connection_set_queue(connection, box_apple_tls_client_queue(client)); + nw_connection_start(connection); + return client; +} + +int box_apple_tls_client_wait_ready(box_apple_tls_client_t *client, int timeout_msec, char **error_out) { + dispatch_semaphore_t ready_semaphore = box_apple_tls_ready_semaphore(client); + if (ready_semaphore == nil) { + box_set_error_message(error_out, "apple TLS: invalid client"); + return 0; + } + if (!atomic_load(&client->ready_done)) { + dispatch_time_t timeout = DISPATCH_TIME_FOREVER; + if (timeout_msec >= 0) { + timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)timeout_msec * NSEC_PER_MSEC); + } + long wait_result = dispatch_semaphore_wait(ready_semaphore, timeout); + if (wait_result != 0) { + return -2; + } + } + if (atomic_load(&client->ready)) { + return 1; + } + if (client->ready_error != NULL) { + if (error_out != NULL) { + *error_out = client->ready_error; + client->ready_error = NULL; + } else { + free(client->ready_error); + client->ready_error = NULL; + } + } else { + box_set_error_message(error_out, "apple TLS: handshake failed"); + } + return 0; +} + +void box_apple_tls_client_cancel(box_apple_tls_client_t *client) { + if (client == NULL) { + return; + } + nw_connection_t connection = box_apple_tls_connection(client); + if (connection != nil) { + nw_connection_cancel(connection); + } +} + +void box_apple_tls_client_free(box_apple_tls_client_t *client) { + if (client == NULL) { + return; + } + nw_connection_t connection = box_apple_tls_connection(client); + if (connection != nil) { + nw_connection_cancel(connection); + } + box_apple_tls_client_release(client); +} + +ssize_t box_apple_tls_client_read(box_apple_tls_client_t *client, void *buffer, size_t buffer_len, int timeout_msec, bool *eof_out, char **error_out) { + @autoreleasepool { + nw_connection_t connection = box_apple_tls_connection(client); + if (connection == nil) { + box_set_error_message(error_out, "apple TLS: invalid client"); + return -1; + } + + dispatch_semaphore_t read_semaphore = dispatch_semaphore_create(0); + __block size_t content_len = 0; + __block bool read_eof = false; + __block char *local_error = NULL; + + nw_connection_receive(connection, 1, (uint32_t)buffer_len, ^(dispatch_data_t content, nw_content_context_t context, bool is_complete, nw_error_t error) { + @autoreleasepool { + if (content != NULL) { + ssize_t copied = box_apple_tls_dispatch_data_copy(content, buffer, buffer_len, &local_error); + if (copied >= 0) { + content_len = (size_t)copied; + } + } + if (error != NULL && content_len == 0 && local_error == NULL) { + box_set_error_from_nw_error(&local_error, error); + } + if (is_complete && (context == NULL || nw_content_context_get_is_final(context))) { + read_eof = true; + } + dispatch_semaphore_signal(read_semaphore); + } + }); + + dispatch_time_t wait_deadline = DISPATCH_TIME_FOREVER; + if (timeout_msec >= 0) { + wait_deadline = dispatch_time(DISPATCH_TIME_NOW, (int64_t)timeout_msec * NSEC_PER_MSEC); + } + long wait_result = dispatch_semaphore_wait(read_semaphore, wait_deadline); + if (wait_result != 0) { + nw_connection_cancel(connection); + dispatch_semaphore_wait(read_semaphore, DISPATCH_TIME_FOREVER); + if (local_error != NULL) { + free(local_error); + local_error = NULL; + } + return -2; + } + if (local_error != NULL) { + if (error_out != NULL) { + *error_out = local_error; + } else { + free(local_error); + } + return -1; + } + if (eof_out != NULL) { + *eof_out = read_eof; + } + return (ssize_t)content_len; + } +} + +ssize_t box_apple_tls_client_write(box_apple_tls_client_t *client, const void *buffer, size_t buffer_len, int timeout_msec, char **error_out) { + @autoreleasepool { + nw_connection_t connection = box_apple_tls_connection(client); + if (connection == nil) { + box_set_error_message(error_out, "apple TLS: invalid client"); + return -1; + } + if (buffer_len == 0) { + return 0; + } + + void *content_copy = malloc(buffer_len); + if (content_copy == NULL) { + box_set_error_message(error_out, "apple TLS: out of memory"); + return -1; + } + dispatch_queue_t queue = box_apple_tls_client_queue(client); + if (queue == nil) { + free(content_copy); + box_set_error_message(error_out, "apple TLS: invalid client"); + return -1; + } + memcpy(content_copy, buffer, buffer_len); + dispatch_data_t content = dispatch_data_create(content_copy, buffer_len, queue, ^{ + free(content_copy); + }); + + dispatch_semaphore_t write_semaphore = dispatch_semaphore_create(0); + __block char *local_error = NULL; + + nw_connection_send(connection, content, NW_CONNECTION_DEFAULT_STREAM_CONTEXT, false, ^(nw_error_t error) { + @autoreleasepool { + if (error != NULL) { + box_set_error_from_nw_error(&local_error, error); + } + dispatch_semaphore_signal(write_semaphore); + } + }); + + dispatch_time_t wait_deadline = DISPATCH_TIME_FOREVER; + if (timeout_msec >= 0) { + wait_deadline = dispatch_time(DISPATCH_TIME_NOW, (int64_t)timeout_msec * NSEC_PER_MSEC); + } + long wait_result = dispatch_semaphore_wait(write_semaphore, wait_deadline); + if (wait_result != 0) { + nw_connection_cancel(connection); + dispatch_semaphore_wait(write_semaphore, DISPATCH_TIME_FOREVER); + if (local_error != NULL) { + free(local_error); + local_error = NULL; + } + return -2; + } + if (local_error != NULL) { + if (error_out != NULL) { + *error_out = local_error; + } else { + free(local_error); + } + return -1; + } + return (ssize_t)buffer_len; + } +} + +bool box_apple_tls_client_read_async(box_apple_tls_client_t *client, size_t maximum_len, uintptr_t callback_handle, char **error_out) { + @autoreleasepool { + nw_connection_t connection = box_apple_tls_connection(client); + if (connection == nil) { + box_set_error_message(error_out, "apple TLS: invalid client"); + return false; + } + if (maximum_len == 0) { + box_set_error_message(error_out, "apple TLS: empty read buffer"); + return false; + } + uint32_t receive_len = maximum_len > UINT32_MAX ? UINT32_MAX : (uint32_t)maximum_len; + nw_connection_receive(connection, 1, receive_len, ^(dispatch_data_t content, nw_content_context_t context, bool is_complete, nw_error_t error) { + @autoreleasepool { + box_apple_tls_read_result_t *result = calloc(1, sizeof(box_apple_tls_read_result_t)); + if (result == NULL) { + box_apple_tls_read_callback(callback_handle, NULL); + return; + } + size_t content_size = content != NULL ? dispatch_data_get_size(content) : 0; + if (content_size > 0) { + result->content = (__bridge_retained void *)content; + } + if (error != NULL && content_size == 0) { + box_set_error_from_nw_error(&result->error, error); + } + if (is_complete && (context == NULL || nw_content_context_get_is_final(context))) { + result->eof = true; + } + box_apple_tls_read_callback(callback_handle, result); + } + }); + return true; + } +} + +ssize_t box_apple_tls_read_result_copy(box_apple_tls_read_result_t *result, void *buffer, size_t buffer_len, bool *eof_out, char **error_out) { + @autoreleasepool { + if (result == NULL) { + box_set_error_message(error_out, "apple TLS: read result unavailable"); + return -1; + } + if (result->error != NULL) { + if (error_out != NULL) { + *error_out = result->error; + result->error = NULL; + } else { + free(result->error); + result->error = NULL; + } + return -1; + } + if (eof_out != NULL) { + *eof_out = result->eof; + } + dispatch_data_t content = box_apple_tls_read_result_content(result); + if (content == nil) { + return 0; + } + return box_apple_tls_dispatch_data_copy(content, buffer, buffer_len, error_out); + } +} + +void box_apple_tls_read_result_free(box_apple_tls_read_result_t *result) { + if (result == NULL) { + return; + } + free(result->error); + if (result->content != NULL) { + CFBridgingRelease(result->content); + } + free(result); +} + +bool box_apple_tls_client_copy_state(box_apple_tls_client_t *client, box_apple_tls_state_t *state, char **error_out) { + dispatch_queue_t queue = box_apple_tls_client_queue(client); + if (queue == nil || state == NULL) { + box_set_error_message(error_out, "apple TLS: invalid client"); + return false; + } + memset(state, 0, sizeof(box_apple_tls_state_t)); + __block bool copied = false; + __block char *local_error = NULL; + dispatch_sync(queue, ^{ + if (!atomic_load(&client->ready)) { + box_set_error_message(&local_error, "apple TLS: metadata unavailable"); + return; + } + if (!box_apple_tls_state_copy(&client->state, state)) { + box_set_error_message(&local_error, "apple TLS: out of memory"); + return; + } + copied = true; + }); + if (copied) { + return true; + } + if (local_error != NULL) { + if (error_out != NULL) { + *error_out = local_error; + } else { + free(local_error); + } + } + box_apple_tls_state_reset(state); + return false; +} + +void box_apple_tls_state_free(box_apple_tls_state_t *state) { + box_apple_tls_state_reset(state); +} diff --git a/common/tls/apple_client_platform_dispatch_test.go b/common/tls/apple_client_platform_dispatch_test.go new file mode 100644 index 0000000000..65261a925e --- /dev/null +++ b/common/tls/apple_client_platform_dispatch_test.go @@ -0,0 +1,50 @@ +//go:build darwin && cgo + +package tls + +import ( + "bytes" + "strings" + "testing" +) + +func TestAppleTLSDispatchDataCopySegments(t *testing.T) { + first := []byte("hello ") + second := []byte("world") + + buffer := make([]byte, len(first)+len(second)) + n, errorMessage := appleTLSCopyDispatchDataForTest(first, second, buffer) + if n < 0 { + t.Fatalf("copy failed: %s", errorMessage) + } + if int(n) != len(buffer) { + t.Fatalf("copied %d bytes, want %d", n, len(buffer)) + } + if !bytes.Equal(buffer, []byte("hello world")) { + t.Fatalf("unexpected copy result: %q", string(buffer)) + } +} + +func TestAppleTLSDispatchDataCopyRejectsSmallBuffer(t *testing.T) { + first := []byte("hello") + second := []byte("world") + + buffer := make([]byte, len(first)+len(second)-1) + n, errorMessage := appleTLSCopyDispatchDataForTest(first, second, buffer) + if n != -1 { + t.Fatalf("copied %d bytes, want error", n) + } + if !strings.Contains(errorMessage, "read buffer too small") { + t.Fatalf("unexpected error: %q", errorMessage) + } +} + +func TestAppleTLSDispatchDataCopyEmpty(t *testing.T) { + n, errorMessage := appleTLSCopyDispatchDataForTest(nil, nil, nil) + if n != 0 { + t.Fatalf("copied %d bytes, want 0", n) + } + if errorMessage != "" { + t.Fatalf("unexpected error: %q", errorMessage) + } +} diff --git a/common/tls/apple_client_platform_dispatch_testhelper_darwin.go b/common/tls/apple_client_platform_dispatch_testhelper_darwin.go new file mode 100644 index 0000000000..0d80d429d9 --- /dev/null +++ b/common/tls/apple_client_platform_dispatch_testhelper_darwin.go @@ -0,0 +1,44 @@ +//go:build darwin && cgo + +package tls + +/* +#include +#include "apple_client_platform_darwin.h" +*/ +import "C" + +import "unsafe" + +func appleTLSCopyDispatchDataForTest(first, second []byte, buffer []byte) (int, string) { + var firstPtr unsafe.Pointer + if len(first) > 0 { + firstPtr = C.CBytes(first) + defer C.free(firstPtr) + } + var secondPtr unsafe.Pointer + if len(second) > 0 { + secondPtr = C.CBytes(second) + defer C.free(secondPtr) + } + var bufferPtr unsafe.Pointer + if len(buffer) > 0 { + bufferPtr = unsafe.Pointer(&buffer[0]) + } + var errPtr *C.char + n := C.box_apple_tls_copy_dispatch_data_for_test( + firstPtr, + C.size_t(len(first)), + secondPtr, + C.size_t(len(second)), + bufferPtr, + C.size_t(len(buffer)), + &errPtr, + ) + if errPtr == nil { + return int(n), "" + } + errorMessage := C.GoString(errPtr) + C.free(unsafe.Pointer(errPtr)) + return int(n), errorMessage +} diff --git a/common/tls/apple_client_platform_test.go b/common/tls/apple_client_platform_test.go new file mode 100644 index 0000000000..c7b93a1ba1 --- /dev/null +++ b/common/tls/apple_client_platform_test.go @@ -0,0 +1,766 @@ +//go:build darwin && cgo + +package tls + +import ( + "bytes" + "context" + stdtls "crypto/tls" + "errors" + "io" + "net" + "os" + "testing" + "time" + + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/json/badoption" + "github.com/sagernet/sing/common/logger" + N "github.com/sagernet/sing/common/network" +) + +const appleTLSTestTimeout = 5 * time.Second + +const ( + appleTLSSuccessHandshakeLoops = 20 + appleTLSFailureRecoveryLoops = 10 +) + +type appleTLSServerResult struct { + state stdtls.ConnectionState + err error +} + +var ( + _ N.ExtendedConn = (*appleTLSConn)(nil) + _ N.ReadWaitCreator = (*appleTLSConn)(nil) +) + +func TestAppleClientHandshakeAppliesALPNAndVersion(t *testing.T) { + serverCertificate, serverCertificatePEM := newAppleTestCertificate(t, "localhost") + for index := range appleTLSSuccessHandshakeLoops { + serverResult, serverAddress := startAppleTLSTestServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS12, + MaxVersion: stdtls.VersionTLS12, + NextProtos: []string{"h2"}, + }) + + clientConn, err := newAppleTestClientConn(t, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: "apple", + ServerName: "localhost", + MinVersion: "1.2", + MaxVersion: "1.2", + ALPN: badoption.Listable[string]{"h2"}, + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err != nil { + t.Fatalf("iteration %d: %v", index, err) + } + + clientState := clientConn.ConnectionState() + if clientState.Version != stdtls.VersionTLS12 { + _ = clientConn.Close() + t.Fatalf("iteration %d: unexpected negotiated version: %x", index, clientState.Version) + } + if clientState.NegotiatedProtocol != "h2" { + _ = clientConn.Close() + t.Fatalf("iteration %d: unexpected negotiated protocol: %q", index, clientState.NegotiatedProtocol) + } + _ = clientConn.Close() + + result := <-serverResult + if result.err != nil { + t.Fatalf("iteration %d: %v", index, result.err) + } + if result.state.Version != stdtls.VersionTLS12 { + t.Fatalf("iteration %d: server negotiated unexpected version: %x", index, result.state.Version) + } + if result.state.NegotiatedProtocol != "h2" { + t.Fatalf("iteration %d: server negotiated unexpected protocol: %q", index, result.state.NegotiatedProtocol) + } + } +} + +func TestAppleClientHandshakeRejectsOpaqueConn(t *testing.T) { + clientConfig, err := NewClientWithOptions(ClientOptions{ + Context: context.Background(), + Logger: logger.NOP(), + Options: option.OutboundTLSOptions{ + Enabled: true, + Engine: "apple", + ServerName: "localhost", + Insecure: true, + }, + }) + if err != nil { + t.Fatal(err) + } + clientConn, serverConn := net.Pipe() + defer clientConn.Close() + defer serverConn.Close() + _, err = ClientHandshake(context.Background(), clientConn, clientConfig) + if err == nil { + t.Fatal("expected handshake to reject non-TCP connection") + } +} + +func TestAppleClientHandshakeRejectsVersionMismatch(t *testing.T) { + serverCertificate, serverCertificatePEM := newAppleTestCertificate(t, "localhost") + serverResult, serverAddress := startAppleTLSTestServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS13, + MaxVersion: stdtls.VersionTLS13, + }) + + clientConn, err := newAppleTestClientConn(t, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: "apple", + ServerName: "localhost", + MaxVersion: "1.2", + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err == nil { + clientConn.Close() + t.Fatal("expected version mismatch handshake to fail") + } + + if result := <-serverResult; result.err == nil { + t.Fatal("expected server handshake to fail on version mismatch") + } +} + +func TestAppleClientHandshakeRejectsServerNameMismatch(t *testing.T) { + serverCertificate, serverCertificatePEM := newAppleTestCertificate(t, "localhost") + serverResult, serverAddress := startAppleTLSTestServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + }) + + clientConn, err := newAppleTestClientConn(t, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: "apple", + ServerName: "example.com", + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err == nil { + clientConn.Close() + t.Fatal("expected server name mismatch handshake to fail") + } + + if result := <-serverResult; result.err == nil { + t.Fatal("expected server handshake to fail on server name mismatch") + } +} + +func TestAppleClientHandshakeRecoversAfterFailure(t *testing.T) { + serverCertificate, serverCertificatePEM := newAppleTestCertificate(t, "localhost") + testCases := []struct { + name string + serverConfig *stdtls.Config + clientOptions option.OutboundTLSOptions + }{ + { + name: "version mismatch", + serverConfig: &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS13, + MaxVersion: stdtls.VersionTLS13, + }, + clientOptions: option.OutboundTLSOptions{ + Enabled: true, + Engine: "apple", + ServerName: "localhost", + MaxVersion: "1.2", + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }, + }, + { + name: "server name mismatch", + serverConfig: &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + }, + clientOptions: option.OutboundTLSOptions{ + Enabled: true, + Engine: "apple", + ServerName: "example.com", + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }, + }, + } + successClientOptions := option.OutboundTLSOptions{ + Enabled: true, + Engine: "apple", + ServerName: "localhost", + MinVersion: "1.2", + MaxVersion: "1.2", + ALPN: badoption.Listable[string]{"h2"}, + Certificate: badoption.Listable[string]{serverCertificatePEM}, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + for index := range appleTLSFailureRecoveryLoops { + failedResult, failedAddress := startAppleTLSTestServer(t, testCase.serverConfig) + failedConn, err := newAppleTestClientConn(t, failedAddress, testCase.clientOptions) + if err == nil { + _ = failedConn.Close() + t.Fatalf("iteration %d: expected handshake failure", index) + } + if result := <-failedResult; result.err == nil { + t.Fatalf("iteration %d: expected server handshake failure", index) + } + + successResult, successAddress := startAppleTLSTestServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS12, + MaxVersion: stdtls.VersionTLS12, + NextProtos: []string{"h2"}, + }) + successConn, err := newAppleTestClientConn(t, successAddress, successClientOptions) + if err != nil { + t.Fatalf("iteration %d: follow-up handshake failed: %v", index, err) + } + clientState := successConn.ConnectionState() + if clientState.NegotiatedProtocol != "h2" { + _ = successConn.Close() + t.Fatalf("iteration %d: unexpected negotiated protocol after failure: %q", index, clientState.NegotiatedProtocol) + } + _ = successConn.Close() + + result := <-successResult + if result.err != nil { + t.Fatalf("iteration %d: follow-up server handshake failed: %v", index, result.err) + } + if result.state.NegotiatedProtocol != "h2" { + t.Fatalf("iteration %d: follow-up server negotiated unexpected protocol: %q", index, result.state.NegotiatedProtocol) + } + } + }) + } +} + +func TestAppleClientConfigCloneWithInlineCertificate(t *testing.T) { + serverCertificate, serverCertificatePEM := newAppleTestCertificate(t, "localhost") + clientConfig, err := NewClientWithOptions(ClientOptions{ + Context: context.Background(), + Logger: logger.NOP(), + Options: option.OutboundTLSOptions{ + Enabled: true, + Engine: "apple", + ServerName: "localhost", + MinVersion: "1.2", + MaxVersion: "1.2", + ALPN: badoption.Listable[string]{"h2", "http/1.1"}, + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }, + }) + if err != nil { + t.Fatal(err) + } + + clone := clientConfig.Clone() + clone.SetServerName("other") + clone.SetNextProtos([]string{"http/1.1"}) + if clientConfig.ServerName() == "other" { + t.Fatal("Clone shares server name with original") + } + nextProtos := clientConfig.NextProtos() + if len(nextProtos) != 2 || nextProtos[0] != "h2" || nextProtos[1] != "http/1.1" { + t.Fatalf("Clone shares ALPN slice with original: %v", nextProtos) + } + + for index := range appleTLSFailureRecoveryLoops { + serverResult, serverAddress := startAppleTLSTestServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS12, + MaxVersion: stdtls.VersionTLS12, + NextProtos: []string{"h2"}, + }) + + handshakeConfig := clientConfig.Clone() + handshakeConfig.SetNextProtos([]string{"h2"}) + clientConn, err := dialAppleTestClientConn(t, serverAddress, handshakeConfig) + if err != nil { + t.Fatalf("iteration %d: %v", index, err) + } + + clientState := clientConn.ConnectionState() + if clientState.NegotiatedProtocol != "h2" { + _ = clientConn.Close() + t.Fatalf("iteration %d: unexpected negotiated protocol: %q", index, clientState.NegotiatedProtocol) + } + _ = clientConn.Close() + + result := <-serverResult + if result.err != nil { + t.Fatalf("iteration %d: %v", index, result.err) + } + } +} + +func TestAppleClientReadBuffer(t *testing.T) { + serverCertificate, serverCertificatePEM := newAppleTestCertificate(t, "localhost") + payload := []byte("apple tls read buffer payload") + serverResult, serverAddress := startAppleTLSIOTestServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS12, + MaxVersion: stdtls.VersionTLS12, + }, func(conn *stdtls.Conn) error { + _, err := conn.Write(payload) + return err + }) + + clientConn, err := newAppleTestClientConn(t, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: "apple", + ServerName: "localhost", + MinVersion: "1.2", + MaxVersion: "1.2", + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err != nil { + t.Fatal(err) + } + defer clientConn.Close() + + extendedConn := clientConn.(N.ExtendedConn) + const ( + frontHeadroom = 17 + rearHeadroom = 19 + ) + buffer := buf.NewSize(frontHeadroom + len(payload) + rearHeadroom) + defer buffer.Release() + buffer.Resize(frontHeadroom, 0) + buffer.Reserve(rearHeadroom) + err = extendedConn.ReadBuffer(buffer) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(buffer.Bytes(), payload) { + t.Fatalf("unexpected payload: %q", buffer.Bytes()) + } + if buffer.Start() != frontHeadroom { + t.Fatalf("unexpected front headroom: %d", buffer.Start()) + } + if buffer.FreeLen() != 0 { + t.Fatalf("unexpected reserved free length before PostReturn: %d", buffer.FreeLen()) + } + buffer.OverCap(rearHeadroom) + if buffer.FreeLen() != rearHeadroom { + t.Fatalf("unexpected rear headroom after PostReturn: %d", buffer.FreeLen()) + } + + if err = <-serverResult; err != nil { + t.Fatal(err) + } +} + +func TestAppleClientWriteBuffer(t *testing.T) { + serverCertificate, serverCertificatePEM := newAppleTestCertificate(t, "localhost") + payload := bytes.Repeat([]byte("apple-write-buffer-"), 3000) + serverResult, serverAddress := startAppleTLSIOTestServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS12, + MaxVersion: stdtls.VersionTLS12, + }, func(conn *stdtls.Conn) error { + received := make([]byte, len(payload)) + _, err := io.ReadFull(conn, received) + if err != nil { + return err + } + if !bytes.Equal(received, payload) { + return errors.New("payload mismatch") + } + return nil + }) + + clientConn, err := newAppleTestClientConn(t, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: "apple", + ServerName: "localhost", + MinVersion: "1.2", + MaxVersion: "1.2", + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err != nil { + t.Fatal(err) + } + defer clientConn.Close() + + extendedConn := clientConn.(N.ExtendedConn) + buffer := buf.NewSize(len(payload)) + _, err = buffer.Write(payload) + if err != nil { + t.Fatal(err) + } + err = extendedConn.WriteBuffer(buffer) + if err != nil { + t.Fatal(err) + } + if buffer.RawCap() != 0 { + t.Fatalf("buffer was not released: raw cap %d", buffer.RawCap()) + } + if err = <-serverResult; err != nil { + t.Fatal(err) + } +} + +func TestAppleClientCreateReadWaiter(t *testing.T) { + serverCertificate, serverCertificatePEM := newAppleTestCertificate(t, "localhost") + payload := []byte("apple tls read waiter payload") + serverResult, serverAddress := startAppleTLSIOTestServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS12, + MaxVersion: stdtls.VersionTLS12, + }, func(conn *stdtls.Conn) error { + _, err := conn.Write(payload) + return err + }) + + clientConn, err := newAppleTestClientConn(t, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: "apple", + ServerName: "localhost", + MinVersion: "1.2", + MaxVersion: "1.2", + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err != nil { + t.Fatal(err) + } + defer clientConn.Close() + + readWaitCreator := clientConn.(N.ReadWaitCreator) + readWaiter, ok := readWaitCreator.CreateReadWaiter() + if !ok { + t.Fatal("expected read waiter") + } + const ( + frontHeadroom = 11 + rearHeadroom = 13 + ) + needCopy := readWaiter.InitializeReadWaiter(N.ReadWaitOptions{ + FrontHeadroom: frontHeadroom, + RearHeadroom: rearHeadroom, + MTU: len(payload), + }) + if needCopy { + t.Fatal("expected owned read waiter buffer") + } + buffer, err := readWaiter.WaitReadBuffer() + if err != nil { + t.Fatal(err) + } + defer buffer.Release() + if !bytes.Equal(buffer.Bytes(), payload) { + t.Fatalf("unexpected payload: %q", buffer.Bytes()) + } + if buffer.Start() != frontHeadroom { + t.Fatalf("unexpected front headroom: %d", buffer.Start()) + } + if buffer.FreeLen() != rearHeadroom { + t.Fatalf("unexpected rear headroom: %d", buffer.FreeLen()) + } + if buffer.Cap() != buffer.RawCap() { + t.Fatalf("capacity was not restored: cap=%d raw=%d", buffer.Cap(), buffer.RawCap()) + } + + if err = <-serverResult; err != nil { + t.Fatal(err) + } +} + +func TestAppleClientReadDeadline(t *testing.T) { + serverCertificate, serverCertificatePEM := newAppleTestCertificate(t, "localhost") + serverDone, serverAddress := startAppleTLSSilentServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS12, + MaxVersion: stdtls.VersionTLS12, + }) + + clientConn, err := newAppleTestClientConn(t, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: "apple", + ServerName: "localhost", + MinVersion: "1.2", + MaxVersion: "1.2", + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err != nil { + t.Fatal(err) + } + defer clientConn.Close() + defer close(serverDone) + + err = clientConn.SetReadDeadline(time.Now().Add(200 * time.Millisecond)) + if err != nil { + t.Fatalf("SetReadDeadline: %v", err) + } + + readDone := make(chan error, 1) + buffer := make([]byte, 64) + go func() { + _, readErr := clientConn.Read(buffer) + readDone <- readErr + }() + + select { + case readErr := <-readDone: + if !errors.Is(readErr, os.ErrDeadlineExceeded) { + t.Fatalf("expected os.ErrDeadlineExceeded, got %v", readErr) + } + case <-time.After(2 * time.Second): + t.Fatal("Read did not return within 2s after deadline") + } + + _, err = clientConn.Read(buffer) + if !errors.Is(err, os.ErrDeadlineExceeded) { + t.Fatalf("sticky deadline: expected os.ErrDeadlineExceeded, got %v", err) + } +} + +func TestAppleClientSetDeadlineClearsPreExpiredSticky(t *testing.T) { + serverCertificate, serverCertificatePEM := newAppleTestCertificate(t, "localhost") + serverDone, serverAddress := startAppleTLSSilentServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS12, + MaxVersion: stdtls.VersionTLS12, + }) + + clientConn, err := newAppleTestClientConn(t, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: "apple", + ServerName: "localhost", + MinVersion: "1.2", + MaxVersion: "1.2", + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err != nil { + t.Fatal(err) + } + defer clientConn.Close() + defer close(serverDone) + + err = clientConn.SetReadDeadline(time.Now().Add(-time.Second)) + if err != nil { + t.Fatalf("SetReadDeadline past: %v", err) + } + + // Pre-expired deadline trips sticky flag without cancelling nw_connection + // (prepareReadTimeout short-circuits before the C read is issued). + buffer := make([]byte, 64) + _, err = clientConn.Read(buffer) + if !errors.Is(err, os.ErrDeadlineExceeded) { + t.Fatalf("pre-expired: expected os.ErrDeadlineExceeded, got %v", err) + } + + err = clientConn.SetReadDeadline(time.Time{}) + if err != nil { + t.Fatalf("SetReadDeadline zero: %v", err) + } + + newDeadline := 300 * time.Millisecond + err = clientConn.SetReadDeadline(time.Now().Add(newDeadline)) + if err != nil { + t.Fatalf("SetReadDeadline future: %v", err) + } + + readStart := time.Now() + _, err = clientConn.Read(buffer) + readElapsed := time.Since(readStart) + if !errors.Is(err, os.ErrDeadlineExceeded) { + t.Fatalf("after clear: expected os.ErrDeadlineExceeded, got %v", err) + } + if readElapsed < newDeadline-50*time.Millisecond { + t.Fatalf("sticky flag was not cleared: Read returned after %v, expected ~%v", readElapsed, newDeadline) + } +} + +func startAppleTLSSilentServer(t *testing.T, tlsConfig *stdtls.Config) (chan<- struct{}, string) { + t.Helper() + + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + listener.Close() + }) + + if tcpListener, isTCP := listener.(*net.TCPListener); isTCP { + err = tcpListener.SetDeadline(time.Now().Add(appleTLSTestTimeout)) + if err != nil { + t.Fatal(err) + } + } + + done := make(chan struct{}) + go func() { + conn, acceptErr := listener.Accept() + if acceptErr != nil { + return + } + defer conn.Close() + handshakeErr := conn.SetDeadline(time.Now().Add(appleTLSTestTimeout)) + if handshakeErr != nil { + return + } + tlsConn := stdtls.Server(conn, tlsConfig) + defer tlsConn.Close() + handshakeErr = tlsConn.Handshake() + if handshakeErr != nil { + return + } + handshakeErr = conn.SetDeadline(time.Time{}) + if handshakeErr != nil { + return + } + <-done + }() + return done, listener.Addr().String() +} + +func startAppleTLSIOTestServer(t testing.TB, tlsConfig *stdtls.Config, handler func(*stdtls.Conn) error) (<-chan error, string) { + t.Helper() + + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + listener.Close() + }) + + if tcpListener, isTCP := listener.(*net.TCPListener); isTCP { + err = tcpListener.SetDeadline(time.Now().Add(appleTLSTestTimeout)) + if err != nil { + t.Fatal(err) + } + } + + result := make(chan error, 1) + go func() { + conn, err := listener.Accept() + if err != nil { + result <- err + return + } + defer conn.Close() + + err = conn.SetDeadline(time.Now().Add(appleTLSTestTimeout)) + if err != nil { + result <- err + return + } + + tlsConn := stdtls.Server(conn, tlsConfig) + defer tlsConn.Close() + err = tlsConn.Handshake() + if err != nil { + result <- err + return + } + result <- handler(tlsConn) + }() + return result, listener.Addr().String() +} + +func newAppleTestCertificate(t testing.TB, serverName string) (stdtls.Certificate, string) { + t.Helper() + + privateKeyPEM, certificatePEM, err := GenerateCertificate(nil, nil, time.Now, serverName, time.Now().Add(time.Hour)) + if err != nil { + t.Fatal(err) + } + certificate, err := stdtls.X509KeyPair(certificatePEM, privateKeyPEM) + if err != nil { + t.Fatal(err) + } + return certificate, string(certificatePEM) +} + +func startAppleTLSTestServer(t *testing.T, tlsConfig *stdtls.Config) (<-chan appleTLSServerResult, string) { + t.Helper() + + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + listener.Close() + }) + + if tcpListener, isTCP := listener.(*net.TCPListener); isTCP { + err = tcpListener.SetDeadline(time.Now().Add(appleTLSTestTimeout)) + if err != nil { + t.Fatal(err) + } + } + + result := make(chan appleTLSServerResult, 1) + go func() { + defer close(result) + + conn, err := listener.Accept() + if err != nil { + result <- appleTLSServerResult{err: err} + return + } + defer conn.Close() + + err = conn.SetDeadline(time.Now().Add(appleTLSTestTimeout)) + if err != nil { + result <- appleTLSServerResult{err: err} + return + } + + tlsConn := stdtls.Server(conn, tlsConfig) + defer tlsConn.Close() + + err = tlsConn.Handshake() + if err != nil { + result <- appleTLSServerResult{err: err} + return + } + + result <- appleTLSServerResult{state: tlsConn.ConnectionState()} + }() + + return result, listener.Addr().String() +} + +func newAppleTestClientConn(t testing.TB, serverAddress string, options option.OutboundTLSOptions) (Conn, error) { + t.Helper() + + clientConfig, err := NewClientWithOptions(ClientOptions{ + Context: context.Background(), + Logger: logger.NOP(), + ServerAddress: "", + Options: options, + }) + if err != nil { + return nil, err + } + return dialAppleTestClientConn(t, serverAddress, clientConfig) +} + +func dialAppleTestClientConn(t testing.TB, serverAddress string, clientConfig Config) (Conn, error) { + t.Helper() + + ctx, cancel := context.WithTimeout(context.Background(), appleTLSTestTimeout) + t.Cleanup(cancel) + + conn, err := net.DialTimeout("tcp", serverAddress, appleTLSTestTimeout) + if err != nil { + return nil, err + } + + tlsConn, err := ClientHandshake(ctx, conn, clientConfig) + if err != nil { + conn.Close() + return nil, err + } + return tlsConn, nil +} diff --git a/common/tls/apple_client_stub.go b/common/tls/apple_client_stub.go new file mode 100644 index 0000000000..33b7df47c5 --- /dev/null +++ b/common/tls/apple_client_stub.go @@ -0,0 +1,15 @@ +//go:build !darwin || !cgo + +package tls + +import ( + "context" + + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" +) + +func newAppleClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions, allowEmptyServerName bool) (Config, error) { + return nil, E.New("Apple TLS engine is not available on non-Apple platforms") +} diff --git a/common/tls/client.go b/common/tls/client.go index 4d6b0c540e..7b303687ca 100644 --- a/common/tls/client.go +++ b/common/tls/client.go @@ -2,47 +2,113 @@ package tls import ( "context" + "crypto/tls" + "errors" "net" "os" + "strings" - "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/badtls" + "github.com/sagernet/sing-box/common/tlsspoof" 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" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" aTLS "github.com/sagernet/sing/common/tls" ) -func NewDialerFromOptions(ctx context.Context, router adapter.Router, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) { +var errMissingServerName = E.New("missing server_name or insecure=true") + +func parseTLSSpoofOptions(serverName string, options option.OutboundTLSOptions) (string, tlsspoof.Method, error) { + spoof, method, err := tlsspoof.ParseOptions(options.Spoof, options.SpoofMethod) + if err != nil { + return "", 0, err + } + if spoof == "" { + return "", 0, nil + } + if options.DisableSNI || serverName == "" || M.ParseAddr(serverName).IsValid() { + return "", 0, E.New("`spoof` requires TLS ClientHello with SNI") + } + if strings.EqualFold(spoof, serverName) { + return "", 0, E.New("`spoof` must differ from `server_name`") + } + return spoof, method, nil +} + +func applyTLSSpoof(conn net.Conn, spoof string, method tlsspoof.Method) (net.Conn, error) { + if spoof == "" { + return conn, nil + } + return tlsspoof.NewConn(conn, method, spoof) +} + +func NewDialerFromOptions(ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) { if !options.Enabled { return dialer, nil } - config, err := NewClient(ctx, serverAddress, options) + config, err := NewClientWithOptions(ClientOptions{ + Context: ctx, + Logger: logger, + ServerAddress: serverAddress, + Options: options, + }) if err != nil { return nil, err } return NewDialer(dialer, config), nil } -func NewClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) { - if !options.Enabled { +func NewClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions) (Config, error) { + return NewClientWithOptions(ClientOptions{ + Context: ctx, + Logger: logger, + ServerAddress: serverAddress, + Options: options, + }) +} + +type ClientOptions struct { + Context context.Context + Logger logger.ContextLogger + ServerAddress string + Options option.OutboundTLSOptions + AllowEmptyServerName bool + KTLSCompatible bool +} + +func NewClientWithOptions(options ClientOptions) (Config, error) { + if !options.Options.Enabled { return nil, nil } - if options.ECH != nil && options.ECH.Enabled { - return NewECHClient(ctx, serverAddress, options) - } else if options.Reality != nil && options.Reality.Enabled { - return NewRealityClient(ctx, serverAddress, options) - } else if options.UTLS != nil && options.UTLS.Enabled { - return NewUTLSClient(ctx, serverAddress, options) - } else { - return NewSTDClient(ctx, serverAddress, options) + if !options.KTLSCompatible { + if options.Options.KernelTx { + options.Logger.Warn("enabling kTLS TX in current scenarios will definitely reduce performance, please checkout https://sing-box.sagernet.org/configuration/shared/tls/#kernel_tx") + } + } + if options.Options.KernelRx { + options.Logger.Warn("enabling kTLS RX will definitely reduce performance, please checkout https://sing-box.sagernet.org/configuration/shared/tls/#kernel_rx") } + switch options.Options.Engine { + case "", C.TLSEngineGo: + case C.TLSEngineApple: + return newAppleClient(options.Context, options.Logger, options.ServerAddress, options.Options, options.AllowEmptyServerName) + case C.TLSEngineWindows: + return newWindowsClient(options.Context, options.Logger, options.ServerAddress, options.Options, options.AllowEmptyServerName) + default: + return nil, E.New("unknown tls engine: ", options.Options.Engine) + } + if options.Options.Reality != nil && options.Options.Reality.Enabled { + return newRealityClient(options.Context, options.Logger, options.ServerAddress, options.Options, options.AllowEmptyServerName) + } else if options.Options.UTLS != nil && options.Options.UTLS.Enabled { + return newUTLSClient(options.Context, options.Logger, options.ServerAddress, options.Options, options.AllowEmptyServerName) + } + return newSTDClient(options.Context, options.Logger, options.ServerAddress, options.Options, options.AllowEmptyServerName) } func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) { - ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout) - defer cancel() tlsConn, err := aTLS.ClientHandshake(ctx, conn, config) if err != nil { return nil, err @@ -56,26 +122,55 @@ func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, e return tlsConn, nil } -type Dialer struct { +type Dialer interface { + N.Dialer + DialTLSContext(ctx context.Context, destination M.Socksaddr) (Conn, error) +} + +type defaultDialer struct { dialer N.Dialer config Config } -func NewDialer(dialer N.Dialer, config Config) N.Dialer { - return &Dialer{dialer, config} +func NewDialer(dialer N.Dialer, config Config) Dialer { + return &defaultDialer{dialer, config} } -func (d *Dialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - if network != N.NetworkTCP { +func (d *defaultDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + if N.NetworkName(network) != N.NetworkTCP { return nil, os.ErrInvalid } - conn, err := d.dialer.DialContext(ctx, network, destination) + return d.DialTLSContext(ctx, destination) +} + +func (d *defaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return nil, os.ErrInvalid +} + +func (d *defaultDialer) DialTLSContext(ctx context.Context, destination M.Socksaddr) (Conn, error) { + return d.dialContext(ctx, destination, true) +} + +func (d *defaultDialer) dialContext(ctx context.Context, destination M.Socksaddr, echRetry bool) (Conn, error) { + conn, err := d.dialer.DialContext(ctx, N.NetworkTCP, destination) + if err != nil { + return nil, err + } + tlsConn, err := aTLS.ClientHandshake(ctx, conn, d.config) if err != nil { + conn.Close() + var echErr *tls.ECHRejectionError + if echRetry && errors.As(err, &echErr) && len(echErr.RetryConfigList) > 0 { + if echConfig, isECH := d.config.(ECHCapableConfig); isECH { + echConfig.SetECHConfigList(echErr.RetryConfigList) + return d.dialContext(ctx, destination, false) + } + } return nil, err } - return ClientHandshake(ctx, conn, d.config) + return tlsConn, nil } -func (d *Dialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - return nil, os.ErrInvalid +func (d *defaultDialer) Upstream() any { + return d.dialer } diff --git a/common/tls/client_test.go b/common/tls/client_test.go new file mode 100644 index 0000000000..5bc939e29e --- /dev/null +++ b/common/tls/client_test.go @@ -0,0 +1,154 @@ +package tls + +import ( + "context" + "crypto/tls" + "net" + "testing" + + tf "github.com/sagernet/sing-box/common/tlsfragment" + "github.com/sagernet/sing-box/common/tlsspoof" + "github.com/sagernet/sing-box/option" + + "github.com/stretchr/testify/require" +) + +func TestParseTLSSpoofOptions_Disabled(t *testing.T) { + t.Parallel() + spoof, method, err := parseTLSSpoofOptions("example.com", option.OutboundTLSOptions{}) + require.NoError(t, err) + require.Empty(t, spoof) + require.Equal(t, tlsspoof.MethodWrongSequence, method) +} + +func TestParseTLSSpoofOptions_MethodWithoutSpoof(t *testing.T) { + t.Parallel() + _, _, err := parseTLSSpoofOptions("example.com", option.OutboundTLSOptions{ + SpoofMethod: tlsspoof.MethodNameWrongChecksum, + }) + require.Error(t, err) +} + +func TestParseTLSSpoofOptions_IPLiteralRejected(t *testing.T) { + t.Parallel() + _, _, err := parseTLSSpoofOptions("1.2.3.4", option.OutboundTLSOptions{ + Spoof: "example.com", + }) + require.Error(t, err) +} + +func TestParseTLSSpoofOptions_EmptyServerNameRejected(t *testing.T) { + t.Parallel() + _, _, err := parseTLSSpoofOptions("", option.OutboundTLSOptions{ + Spoof: "example.com", + }) + require.Error(t, err) +} + +func TestParseTLSSpoofOptions_DisableSNIRejected(t *testing.T) { + t.Parallel() + _, _, err := parseTLSSpoofOptions("example.com", option.OutboundTLSOptions{ + Spoof: "decoy.com", + DisableSNI: true, + }) + require.Error(t, err) +} + +// TestParseTLSSpoofOptions_RejectsSameSNI is the primary regression test for +// the "spoofed packet contains the original SNI" bug report: when a user +// configures spoof equal to server_name, the rewriter produces a byte-identical +// record, so the fake and real ClientHellos on the wire look the same. Reject +// at parse time. +func TestParseTLSSpoofOptions_RejectsSameSNI(t *testing.T) { + t.Parallel() + _, _, err := parseTLSSpoofOptions("example.com", option.OutboundTLSOptions{ + Spoof: "example.com", + }) + require.Error(t, err) + + _, _, err = parseTLSSpoofOptions("example.com", option.OutboundTLSOptions{ + Spoof: "EXAMPLE.com", + }) + require.Error(t, err, "comparison must be case-insensitive") +} + +func TestParseTLSSpoofOptions_UnknownMethodRejected(t *testing.T) { + t.Parallel() + _, _, err := parseTLSSpoofOptions("example.com", option.OutboundTLSOptions{ + Spoof: "decoy.com", + SpoofMethod: "nonsense", + }) + require.Error(t, err) +} + +func TestParseTLSSpoofOptions_DistinctSNIAccepted(t *testing.T) { + t.Parallel() + if !tlsspoof.PlatformSupported { + t.Skip("tlsspoof not supported on this platform") + } + spoof, method, err := parseTLSSpoofOptions("example.com", option.OutboundTLSOptions{ + Spoof: "decoy.com", + SpoofMethod: tlsspoof.MethodNameWrongSequence, + }) + require.NoError(t, err) + require.Equal(t, "decoy.com", spoof) + require.Equal(t, tlsspoof.MethodWrongSequence, method) +} + +// The following tests guard the wrap gate in STDClientConfig.Client(): +// tf.Conn must wrap the underlying connection whenever either `fragment` or +// `record_fragment` is set, so that TLS fragmentation coexists with features +// like tls_spoof that layer on top of tf.Conn. + +func newSTDClientConfigForGateTest(fragment, recordFragment bool) *STDClientConfig { + return &STDClientConfig{ + ctx: context.Background(), + config: &tls.Config{ServerName: "example.com", InsecureSkipVerify: true}, + fragment: fragment, + recordFragment: recordFragment, + } +} + +func TestSTDClient_Client_NoFragment_DoesNotWrap(t *testing.T) { + t.Parallel() + client, server := net.Pipe() + defer client.Close() + defer server.Close() + wrapped, err := newSTDClientConfigForGateTest(false, false).Client(client) + require.NoError(t, err) + _, isTF := wrapped.NetConn().(*tf.Conn) + require.False(t, isTF, "no fragment flags: must not wrap with tf.Conn") +} + +func TestSTDClient_Client_FragmentOnly_Wraps(t *testing.T) { + t.Parallel() + client, server := net.Pipe() + defer client.Close() + defer server.Close() + wrapped, err := newSTDClientConfigForGateTest(true, false).Client(client) + require.NoError(t, err) + _, isTF := wrapped.NetConn().(*tf.Conn) + require.True(t, isTF, "fragment=true: must wrap with tf.Conn") +} + +func TestSTDClient_Client_RecordFragmentOnly_Wraps(t *testing.T) { + t.Parallel() + client, server := net.Pipe() + defer client.Close() + defer server.Close() + wrapped, err := newSTDClientConfigForGateTest(false, true).Client(client) + require.NoError(t, err) + _, isTF := wrapped.NetConn().(*tf.Conn) + require.True(t, isTF, "record_fragment=true: must wrap with tf.Conn") +} + +func TestSTDClient_Client_BothFragment_Wraps(t *testing.T) { + t.Parallel() + client, server := net.Pipe() + defer client.Close() + defer server.Close() + wrapped, err := newSTDClientConfigForGateTest(true, true).Client(client) + require.NoError(t, err) + _, isTF := wrapped.NetConn().(*tf.Conn) + require.True(t, isTF, "both fragment flags: must wrap with tf.Conn") +} diff --git a/common/tls/config.go b/common/tls/config.go index 52d88af0d1..72bbd19419 100644 --- a/common/tls/config.go +++ b/common/tls/config.go @@ -18,6 +18,7 @@ type ( STDConfig = tls.Config STDConn = tls.Conn ConnectionState = tls.ConnectionState + CurveID = tls.CurveID ) func ParseTLSVersion(version string) (uint16, error) { diff --git a/common/tls/ech.go b/common/tls/ech.go new file mode 100644 index 0000000000..8c884cab61 --- /dev/null +++ b/common/tls/ech.go @@ -0,0 +1,208 @@ +//go:build go1.24 + +package tls + +import ( + "context" + "crypto/tls" + "encoding/base64" + "encoding/pem" + "net" + "os" + "strings" + "sync" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + aTLS "github.com/sagernet/sing/common/tls" + "github.com/sagernet/sing/service" + + mDNS "github.com/miekg/dns" + "golang.org/x/crypto/cryptobyte" +) + +func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, options option.OutboundTLSOptions) (Config, error) { + var echConfig []byte + if len(options.ECH.Config) > 0 { + echConfig = []byte(strings.Join(options.ECH.Config, "\n")) + } else if options.ECH.ConfigPath != "" { + content, err := os.ReadFile(options.ECH.ConfigPath) + if err != nil { + return nil, E.Cause(err, "read ECH config") + } + echConfig = content + } + //nolint:staticcheck + if options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled { + return nil, E.New("legacy ECH options are deprecated in sing-box 1.12.0 and removed in sing-box 1.13.0") + } + if len(echConfig) > 0 { + block, rest := pem.Decode(echConfig) + if block == nil || block.Type != "ECH CONFIGS" || len(rest) > 0 { + return nil, E.New("invalid ECH configs pem") + } + clientConfig.SetECHConfigList(block.Bytes) + return clientConfig, nil + } else { + return &ECHClientConfig{ + ECHCapableConfig: clientConfig, + dnsRouter: service.FromContext[adapter.DNSRouter](ctx), + queryServerName: options.ECH.QueryServerName, + }, nil + } +} + +func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions, tlsConfig *tls.Config, echKeyPath *string) error { + var echKey []byte + if len(options.ECH.Key) > 0 { + echKey = []byte(strings.Join(options.ECH.Key, "\n")) + } else if options.ECH.KeyPath != "" { + content, err := os.ReadFile(options.ECH.KeyPath) + if err != nil { + return E.Cause(err, "read ECH keys") + } + echKey = content + *echKeyPath = options.ECH.KeyPath + } else { + return E.New("missing ECH keys") + } + echKeys, err := parseECHKeys(echKey) + if err != nil { + return E.Cause(err, "parse ECH keys") + } + tlsConfig.EncryptedClientHelloKeys = echKeys + //nolint:staticcheck + if options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled { + return E.New("legacy ECH options are deprecated in sing-box 1.12.0 and removed in sing-box 1.13.0") + } + return nil +} + +func (c *STDServerConfig) setECHServerConfig(echKey []byte) error { + echKeys, err := parseECHKeys(echKey) + if err != nil { + return err + } + c.access.Lock() + config := c.config.Clone() + config.EncryptedClientHelloKeys = echKeys + c.config = config + c.access.Unlock() + return nil +} + +func parseECHKeys(echKey []byte) ([]tls.EncryptedClientHelloKey, error) { + block, _ := pem.Decode(echKey) + if block == nil || block.Type != "ECH KEYS" { + return nil, E.New("invalid ECH keys pem") + } + echKeys, err := UnmarshalECHKeys(block.Bytes) + if err != nil { + return nil, E.Cause(err, "parse ECH keys") + } + return echKeys, nil +} + +type ECHClientConfig struct { + ECHCapableConfig + access sync.Mutex + dnsRouter adapter.DNSRouter + queryServerName string + lastTTL time.Duration + lastUpdate time.Time +} + +func (s *ECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) { + tlsConn, err := s.fetchAndHandshake(ctx, conn) + if err != nil { + return nil, err + } + err = tlsConn.HandshakeContext(ctx) + if err != nil { + return nil, err + } + return tlsConn, nil +} + +func (s *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) { + s.access.Lock() + defer s.access.Unlock() + if len(s.ECHConfigList()) == 0 || s.lastTTL == 0 || time.Since(s.lastUpdate) > s.lastTTL { + queryServerName := s.queryServerName + if queryServerName == "" { + queryServerName = s.ServerName() + } + message := &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + RecursionDesired: true, + }, + Question: []mDNS.Question{ + { + Name: mDNS.Fqdn(queryServerName), + Qtype: mDNS.TypeHTTPS, + Qclass: mDNS.ClassINET, + }, + }, + } + response, err := s.dnsRouter.Exchange(ctx, message, adapter.DNSQueryOptions{}) + if err != nil { + return nil, E.Cause(err, "fetch ECH config list") + } + if response.Rcode != mDNS.RcodeSuccess { + return nil, E.Cause(dns.RcodeError(response.Rcode), "fetch ECH config list") + } + match: + for _, rr := range response.Answer { + switch resource := rr.(type) { + case *mDNS.HTTPS: + for _, value := range resource.Value { + if value.Key().String() == "ech" { + echConfigList, err := base64.StdEncoding.DecodeString(value.String()) + if err != nil { + return nil, E.Cause(err, "decode ECH config") + } + s.lastTTL = time.Duration(rr.Header().Ttl) * time.Second + s.lastUpdate = time.Now() + s.SetECHConfigList(echConfigList) + break match + } + } + } + } + if len(s.ECHConfigList()) == 0 { + return nil, E.New("no ECH config found in DNS records") + } + } + return s.Client(conn) +} + +func (s *ECHClientConfig) Clone() Config { + return &ECHClientConfig{ + ECHCapableConfig: s.ECHCapableConfig.Clone().(ECHCapableConfig), + dnsRouter: s.dnsRouter, + queryServerName: s.queryServerName, + lastUpdate: s.lastUpdate, + } +} + +func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) { + var keys []tls.EncryptedClientHelloKey + rawString := cryptobyte.String(raw) + for !rawString.Empty() { + var key tls.EncryptedClientHelloKey + if !rawString.ReadUint16LengthPrefixed((*cryptobyte.String)(&key.PrivateKey)) { + return nil, E.New("error parsing private key") + } + if !rawString.ReadUint16LengthPrefixed((*cryptobyte.String)(&key.Config)) { + return nil, E.New("error parsing config") + } + keys = append(keys, key) + } + if len(keys) == 0 { + return nil, E.New("empty ECH keys") + } + return keys, nil +} diff --git a/common/tls/ech_client.go b/common/tls/ech_client.go deleted file mode 100644 index 7f72b4d8aa..0000000000 --- a/common/tls/ech_client.go +++ /dev/null @@ -1,241 +0,0 @@ -//go:build with_ech - -package tls - -import ( - "context" - "crypto/tls" - "crypto/x509" - "encoding/base64" - "encoding/pem" - "net" - "net/netip" - "os" - "strings" - - cftls "github.com/sagernet/cloudflare-tls" - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-dns" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/ntp" - - mDNS "github.com/miekg/dns" -) - -type echClientConfig struct { - config *cftls.Config -} - -func (c *echClientConfig) ServerName() string { - return c.config.ServerName -} - -func (c *echClientConfig) SetServerName(serverName string) { - c.config.ServerName = serverName -} - -func (c *echClientConfig) NextProtos() []string { - return c.config.NextProtos -} - -func (c *echClientConfig) SetNextProtos(nextProto []string) { - c.config.NextProtos = nextProto -} - -func (c *echClientConfig) Config() (*STDConfig, error) { - return nil, E.New("unsupported usage for ECH") -} - -func (c *echClientConfig) Client(conn net.Conn) (Conn, error) { - return &echConnWrapper{cftls.Client(conn, c.config)}, nil -} - -func (c *echClientConfig) Clone() Config { - return &echClientConfig{ - config: c.config.Clone(), - } -} - -type echConnWrapper struct { - *cftls.Conn -} - -func (c *echConnWrapper) ConnectionState() tls.ConnectionState { - state := c.Conn.ConnectionState() - return tls.ConnectionState{ - Version: state.Version, - HandshakeComplete: state.HandshakeComplete, - DidResume: state.DidResume, - CipherSuite: state.CipherSuite, - NegotiatedProtocol: state.NegotiatedProtocol, - NegotiatedProtocolIsMutual: state.NegotiatedProtocolIsMutual, - ServerName: state.ServerName, - PeerCertificates: state.PeerCertificates, - VerifiedChains: state.VerifiedChains, - SignedCertificateTimestamps: state.SignedCertificateTimestamps, - OCSPResponse: state.OCSPResponse, - TLSUnique: state.TLSUnique, - } -} - -func (c *echConnWrapper) Upstream() any { - return c.Conn -} - -func NewECHClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) { - var serverName string - if options.ServerName != "" { - serverName = options.ServerName - } else if serverAddress != "" { - if _, err := netip.ParseAddr(serverName); err != nil { - serverName = serverAddress - } - } - if serverName == "" && !options.Insecure { - return nil, E.New("missing server_name or insecure=true") - } - - var tlsConfig cftls.Config - tlsConfig.Time = ntp.TimeFuncFromContext(ctx) - if options.DisableSNI { - tlsConfig.ServerName = "127.0.0.1" - } else { - tlsConfig.ServerName = serverName - } - if options.Insecure { - tlsConfig.InsecureSkipVerify = options.Insecure - } else if options.DisableSNI { - tlsConfig.InsecureSkipVerify = true - tlsConfig.VerifyConnection = func(state cftls.ConnectionState) error { - verifyOptions := x509.VerifyOptions{ - DNSName: serverName, - Intermediates: x509.NewCertPool(), - } - for _, cert := range state.PeerCertificates[1:] { - verifyOptions.Intermediates.AddCert(cert) - } - _, err := state.PeerCertificates[0].Verify(verifyOptions) - return err - } - } - if len(options.ALPN) > 0 { - tlsConfig.NextProtos = options.ALPN - } - if options.MinVersion != "" { - minVersion, err := ParseTLSVersion(options.MinVersion) - if err != nil { - return nil, E.Cause(err, "parse min_version") - } - tlsConfig.MinVersion = minVersion - } - if options.MaxVersion != "" { - maxVersion, err := ParseTLSVersion(options.MaxVersion) - if err != nil { - return nil, E.Cause(err, "parse max_version") - } - tlsConfig.MaxVersion = maxVersion - } - if options.CipherSuites != nil { - find: - for _, cipherSuite := range options.CipherSuites { - for _, tlsCipherSuite := range cftls.CipherSuites() { - if cipherSuite == tlsCipherSuite.Name { - tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID) - continue find - } - } - return nil, E.New("unknown cipher_suite: ", cipherSuite) - } - } - var certificate []byte - if len(options.Certificate) > 0 { - certificate = []byte(strings.Join(options.Certificate, "\n")) - } else if options.CertificatePath != "" { - content, err := os.ReadFile(options.CertificatePath) - if err != nil { - return nil, E.Cause(err, "read certificate") - } - certificate = content - } - if len(certificate) > 0 { - certPool := x509.NewCertPool() - if !certPool.AppendCertsFromPEM(certificate) { - return nil, E.New("failed to parse certificate:\n\n", certificate) - } - tlsConfig.RootCAs = certPool - } - - // ECH Config - - tlsConfig.ECHEnabled = true - tlsConfig.PQSignatureSchemesEnabled = options.ECH.PQSignatureSchemesEnabled - tlsConfig.DynamicRecordSizingDisabled = options.ECH.DynamicRecordSizingDisabled - - var echConfig []byte - if len(options.ECH.Config) > 0 { - echConfig = []byte(strings.Join(options.ECH.Config, "\n")) - } else if options.ECH.ConfigPath != "" { - content, err := os.ReadFile(options.ECH.ConfigPath) - if err != nil { - return nil, E.Cause(err, "read ECH config") - } - echConfig = content - } - - if len(echConfig) > 0 { - block, rest := pem.Decode(echConfig) - if block == nil || block.Type != "ECH CONFIGS" || len(rest) > 0 { - return nil, E.New("invalid ECH configs pem") - } - echConfigs, err := cftls.UnmarshalECHConfigs(block.Bytes) - if err != nil { - return nil, E.Cause(err, "parse ECH configs") - } - tlsConfig.ClientECHConfigs = echConfigs - } else { - tlsConfig.GetClientECHConfigs = fetchECHClientConfig(ctx) - } - return &echClientConfig{&tlsConfig}, nil -} - -func fetchECHClientConfig(ctx context.Context) func(_ context.Context, serverName string) ([]cftls.ECHConfig, error) { - return func(_ context.Context, serverName string) ([]cftls.ECHConfig, error) { - message := &mDNS.Msg{ - MsgHdr: mDNS.MsgHdr{ - RecursionDesired: true, - }, - Question: []mDNS.Question{ - { - Name: serverName + ".", - Qtype: mDNS.TypeHTTPS, - Qclass: mDNS.ClassINET, - }, - }, - } - response, err := adapter.RouterFromContext(ctx).Exchange(ctx, message) - if err != nil { - return nil, err - } - if response.Rcode != mDNS.RcodeSuccess { - return nil, dns.RCodeError(response.Rcode) - } - for _, rr := range response.Answer { - switch resource := rr.(type) { - case *mDNS.HTTPS: - for _, value := range resource.Value { - if value.Key().String() == "ech" { - echConfig, err := base64.StdEncoding.DecodeString(value.String()) - if err != nil { - return nil, E.Cause(err, "decode ECH config") - } - return cftls.UnmarshalECHConfigs(echConfig) - } - } - default: - return nil, E.New("unknown resource record type: ", resource.Header().Rrtype) - } - } - return nil, E.New("no ECH config found") - } -} diff --git a/common/tls/ech_keygen.go b/common/tls/ech_keygen.go deleted file mode 100644 index 1fea131c96..0000000000 --- a/common/tls/ech_keygen.go +++ /dev/null @@ -1,169 +0,0 @@ -//go:build with_ech - -package tls - -import ( - "bytes" - "encoding/binary" - "encoding/pem" - - cftls "github.com/sagernet/cloudflare-tls" - E "github.com/sagernet/sing/common/exceptions" - - "github.com/cloudflare/circl/hpke" - "github.com/cloudflare/circl/kem" -) - -func ECHKeygenDefault(serverName string, pqSignatureSchemesEnabled bool) (configPem string, keyPem string, err error) { - cipherSuites := []echCipherSuite{ - { - kdf: hpke.KDF_HKDF_SHA256, - aead: hpke.AEAD_AES128GCM, - }, { - kdf: hpke.KDF_HKDF_SHA256, - aead: hpke.AEAD_ChaCha20Poly1305, - }, - } - - keyConfig := []myECHKeyConfig{ - {id: 0, kem: hpke.KEM_X25519_HKDF_SHA256}, - } - if pqSignatureSchemesEnabled { - keyConfig = append(keyConfig, myECHKeyConfig{id: 1, kem: hpke.KEM_X25519_KYBER768_DRAFT00}) - } - - keyPairs, err := echKeygen(0xfe0d, serverName, keyConfig, cipherSuites) - if err != nil { - return - } - - var configBuffer bytes.Buffer - var totalLen uint16 - for _, keyPair := range keyPairs { - totalLen += uint16(len(keyPair.rawConf)) - } - binary.Write(&configBuffer, binary.BigEndian, totalLen) - for _, keyPair := range keyPairs { - configBuffer.Write(keyPair.rawConf) - } - - var keyBuffer bytes.Buffer - for _, keyPair := range keyPairs { - keyBuffer.Write(keyPair.rawKey) - } - - configPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBuffer.Bytes()})) - keyPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer.Bytes()})) - return -} - -type echKeyConfigPair struct { - id uint8 - key cftls.EXP_ECHKey - rawKey []byte - conf myECHKeyConfig - rawConf []byte -} - -type echCipherSuite struct { - kdf hpke.KDF - aead hpke.AEAD -} - -type myECHKeyConfig struct { - id uint8 - kem hpke.KEM - seed []byte -} - -func echKeygen(version uint16, serverName string, conf []myECHKeyConfig, suite []echCipherSuite) ([]echKeyConfigPair, error) { - be := binary.BigEndian - // prepare for future update - if version != 0xfe0d { - return nil, E.New("unsupported ECH version", version) - } - - suiteBuf := make([]byte, 0, len(suite)*4+2) - suiteBuf = be.AppendUint16(suiteBuf, uint16(len(suite))*4) - for _, s := range suite { - if !s.kdf.IsValid() || !s.aead.IsValid() { - return nil, E.New("invalid HPKE cipher suite") - } - suiteBuf = be.AppendUint16(suiteBuf, uint16(s.kdf)) - suiteBuf = be.AppendUint16(suiteBuf, uint16(s.aead)) - } - - pairs := []echKeyConfigPair{} - for _, c := range conf { - pair := echKeyConfigPair{} - pair.id = c.id - pair.conf = c - - if !c.kem.IsValid() { - return nil, E.New("invalid HPKE KEM") - } - - kpGenerator := c.kem.Scheme().GenerateKeyPair - if len(c.seed) > 0 { - kpGenerator = func() (kem.PublicKey, kem.PrivateKey, error) { - pub, sec := c.kem.Scheme().DeriveKeyPair(c.seed) - return pub, sec, nil - } - if len(c.seed) < c.kem.Scheme().PrivateKeySize() { - return nil, E.New("HPKE KEM seed too short") - } - } - - pub, sec, err := kpGenerator() - if err != nil { - return nil, E.Cause(err, "generate ECH config key pair") - } - b := []byte{} - b = be.AppendUint16(b, version) - b = be.AppendUint16(b, 0) // length field - // contents - // key config - b = append(b, c.id) - b = be.AppendUint16(b, uint16(c.kem)) - pubBuf, err := pub.MarshalBinary() - if err != nil { - return nil, E.Cause(err, "serialize ECH public key") - } - b = be.AppendUint16(b, uint16(len(pubBuf))) - b = append(b, pubBuf...) - - b = append(b, suiteBuf...) - // end key config - // max name len, not supported - b = append(b, 0) - // server name - b = append(b, byte(len(serverName))) - b = append(b, []byte(serverName)...) - // extensions, not supported - b = be.AppendUint16(b, 0) - - be.PutUint16(b[2:], uint16(len(b)-4)) - - pair.rawConf = b - - secBuf, err := sec.MarshalBinary() - sk := []byte{} - sk = be.AppendUint16(sk, uint16(len(secBuf))) - sk = append(sk, secBuf...) - sk = be.AppendUint16(sk, uint16(len(b))) - sk = append(sk, b...) - - cfECHKeys, err := cftls.EXP_UnmarshalECHKeys(sk) - if err != nil { - return nil, E.Cause(err, "bug: can't parse generated ECH server key") - } - if len(cfECHKeys) != 1 { - return nil, E.New("bug: unexpected server key count") - } - pair.key = cfECHKeys[0] - pair.rawKey = sk - - pairs = append(pairs, pair) - } - return pairs, nil -} diff --git a/common/tls/ech_quic.go b/common/tls/ech_quic.go deleted file mode 100644 index fef506dbb1..0000000000 --- a/common/tls/ech_quic.go +++ /dev/null @@ -1,55 +0,0 @@ -//go:build with_quic && with_ech - -package tls - -import ( - "context" - "net" - "net/http" - - "github.com/sagernet/cloudflare-tls" - "github.com/sagernet/quic-go/ech" - "github.com/sagernet/quic-go/http3_ech" - "github.com/sagernet/sing-quic" - M "github.com/sagernet/sing/common/metadata" -) - -var ( - _ qtls.Config = (*echClientConfig)(nil) - _ qtls.ServerConfig = (*echServerConfig)(nil) -) - -func (c *echClientConfig) Dial(ctx context.Context, conn net.PacketConn, addr net.Addr, config *quic.Config) (quic.Connection, error) { - return quic.Dial(ctx, conn, addr, c.config, config) -} - -func (c *echClientConfig) DialEarly(ctx context.Context, conn net.PacketConn, addr net.Addr, config *quic.Config) (quic.EarlyConnection, error) { - return quic.DialEarly(ctx, conn, addr, c.config, config) -} - -func (c *echClientConfig) CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config) http.RoundTripper { - return &http3.RoundTripper{ - TLSClientConfig: c.config, - QUICConfig: quicConfig, - Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { - quicConn, err := quic.DialEarly(ctx, conn, serverAddr.UDPAddr(), tlsCfg, cfg) - if err != nil { - return nil, err - } - *quicConnPtr = quicConn - return quicConn, nil - }, - } -} - -func (c *echServerConfig) Listen(conn net.PacketConn, config *quic.Config) (qtls.Listener, error) { - return quic.Listen(conn, c.config, config) -} - -func (c *echServerConfig) ListenEarly(conn net.PacketConn, config *quic.Config) (qtls.EarlyListener, error) { - return quic.ListenEarly(conn, c.config, config) -} - -func (c *echServerConfig) ConfigureHTTP3() { - http3.ConfigureTLSConfig(c.config) -} diff --git a/common/tls/ech_server.go b/common/tls/ech_server.go deleted file mode 100644 index 43ddd820ec..0000000000 --- a/common/tls/ech_server.go +++ /dev/null @@ -1,343 +0,0 @@ -//go:build with_ech - -package tls - -import ( - "context" - "crypto/tls" - "encoding/pem" - "net" - "os" - "strings" - - cftls "github.com/sagernet/cloudflare-tls" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/ntp" - - "github.com/fsnotify/fsnotify" -) - -type echServerConfig struct { - config *cftls.Config - logger log.Logger - certificate []byte - key []byte - certificatePath string - keyPath string - watcher *fsnotify.Watcher - echKeyPath string - echWatcher *fsnotify.Watcher -} - -func (c *echServerConfig) ServerName() string { - return c.config.ServerName -} - -func (c *echServerConfig) SetServerName(serverName string) { - c.config.ServerName = serverName -} - -func (c *echServerConfig) NextProtos() []string { - return c.config.NextProtos -} - -func (c *echServerConfig) SetNextProtos(nextProto []string) { - c.config.NextProtos = nextProto -} - -func (c *echServerConfig) Config() (*STDConfig, error) { - return nil, E.New("unsupported usage for ECH") -} - -func (c *echServerConfig) Client(conn net.Conn) (Conn, error) { - return &echConnWrapper{cftls.Client(conn, c.config)}, nil -} - -func (c *echServerConfig) Server(conn net.Conn) (Conn, error) { - return &echConnWrapper{cftls.Server(conn, c.config)}, nil -} - -func (c *echServerConfig) Clone() Config { - return &echServerConfig{ - config: c.config.Clone(), - } -} - -func (c *echServerConfig) Start() error { - if c.certificatePath != "" && c.keyPath != "" { - err := c.startWatcher() - if err != nil { - c.logger.Warn("create fsnotify watcher: ", err) - } - } - if c.echKeyPath != "" { - err := c.startECHWatcher() - if err != nil { - c.logger.Warn("create fsnotify watcher: ", err) - } - } - return nil -} - -func (c *echServerConfig) startWatcher() error { - watcher, err := fsnotify.NewWatcher() - if err != nil { - return err - } - if c.certificatePath != "" { - err = watcher.Add(c.certificatePath) - if err != nil { - return err - } - } - if c.keyPath != "" { - err = watcher.Add(c.keyPath) - if err != nil { - return err - } - } - c.watcher = watcher - go c.loopUpdate() - return nil -} - -func (c *echServerConfig) loopUpdate() { - for { - select { - case event, ok := <-c.watcher.Events: - if !ok { - return - } - if event.Op&fsnotify.Write != fsnotify.Write { - continue - } - err := c.reloadKeyPair() - if err != nil { - c.logger.Error(E.Cause(err, "reload TLS key pair")) - } - case err, ok := <-c.watcher.Errors: - if !ok { - return - } - c.logger.Error(E.Cause(err, "fsnotify error")) - } - } -} - -func (c *echServerConfig) reloadKeyPair() error { - if c.certificatePath != "" { - certificate, err := os.ReadFile(c.certificatePath) - if err != nil { - return E.Cause(err, "reload certificate from ", c.certificatePath) - } - c.certificate = certificate - } - if c.keyPath != "" { - key, err := os.ReadFile(c.keyPath) - if err != nil { - return E.Cause(err, "reload key from ", c.keyPath) - } - c.key = key - } - keyPair, err := cftls.X509KeyPair(c.certificate, c.key) - if err != nil { - return E.Cause(err, "reload key pair") - } - c.config.Certificates = []cftls.Certificate{keyPair} - c.logger.Info("reloaded TLS certificate") - return nil -} - -func (c *echServerConfig) startECHWatcher() error { - watcher, err := fsnotify.NewWatcher() - if err != nil { - return err - } - err = watcher.Add(c.echKeyPath) - if err != nil { - return err - } - c.echWatcher = watcher - go c.loopECHUpdate() - return nil -} - -func (c *echServerConfig) loopECHUpdate() { - for { - select { - case event, ok := <-c.echWatcher.Events: - if !ok { - return - } - if event.Op&fsnotify.Write != fsnotify.Write { - continue - } - err := c.reloadECHKey() - if err != nil { - c.logger.Error(E.Cause(err, "reload ECH key")) - } - case err, ok := <-c.echWatcher.Errors: - if !ok { - return - } - c.logger.Error(E.Cause(err, "fsnotify error")) - } - } -} - -func (c *echServerConfig) reloadECHKey() error { - echKeyContent, err := os.ReadFile(c.echKeyPath) - if err != nil { - return err - } - block, rest := pem.Decode(echKeyContent) - if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 { - return E.New("invalid ECH keys pem") - } - echKeys, err := cftls.EXP_UnmarshalECHKeys(block.Bytes) - if err != nil { - return E.Cause(err, "parse ECH keys") - } - echKeySet, err := cftls.EXP_NewECHKeySet(echKeys) - if err != nil { - return E.Cause(err, "create ECH key set") - } - c.config.ServerECHProvider = echKeySet - c.logger.Info("reloaded ECH keys") - return nil -} - -func (c *echServerConfig) Close() error { - var err error - if c.watcher != nil { - err = E.Append(err, c.watcher.Close(), func(err error) error { - return E.Cause(err, "close certificate watcher") - }) - } - if c.echWatcher != nil { - err = E.Append(err, c.echWatcher.Close(), func(err error) error { - return E.Cause(err, "close ECH key watcher") - }) - } - return err -} - -func NewECHServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) { - if !options.Enabled { - return nil, nil - } - var tlsConfig cftls.Config - if options.ACME != nil && len(options.ACME.Domain) > 0 { - return nil, E.New("acme is unavailable in ech") - } - tlsConfig.Time = ntp.TimeFuncFromContext(ctx) - if options.ServerName != "" { - tlsConfig.ServerName = options.ServerName - } - if len(options.ALPN) > 0 { - tlsConfig.NextProtos = append(options.ALPN, tlsConfig.NextProtos...) - } - if options.MinVersion != "" { - minVersion, err := ParseTLSVersion(options.MinVersion) - if err != nil { - return nil, E.Cause(err, "parse min_version") - } - tlsConfig.MinVersion = minVersion - } - if options.MaxVersion != "" { - maxVersion, err := ParseTLSVersion(options.MaxVersion) - if err != nil { - return nil, E.Cause(err, "parse max_version") - } - tlsConfig.MaxVersion = maxVersion - } - 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) - } - } - var certificate []byte - var key []byte - if len(options.Certificate) > 0 { - certificate = []byte(strings.Join(options.Certificate, "\n")) - } else if options.CertificatePath != "" { - content, err := os.ReadFile(options.CertificatePath) - if err != nil { - return nil, E.Cause(err, "read certificate") - } - certificate = content - } - if len(options.Key) > 0 { - key = []byte(strings.Join(options.Key, "\n")) - } else if options.KeyPath != "" { - content, err := os.ReadFile(options.KeyPath) - if err != nil { - return nil, E.Cause(err, "read key") - } - key = content - } - - if certificate == nil { - return nil, E.New("missing certificate") - } else if key == nil { - return nil, E.New("missing key") - } - - keyPair, err := cftls.X509KeyPair(certificate, key) - if err != nil { - return nil, E.Cause(err, "parse x509 key pair") - } - tlsConfig.Certificates = []cftls.Certificate{keyPair} - - var echKey []byte - if len(options.ECH.Key) > 0 { - echKey = []byte(strings.Join(options.ECH.Key, "\n")) - } else if options.KeyPath != "" { - content, err := os.ReadFile(options.ECH.KeyPath) - if err != nil { - return nil, E.Cause(err, "read ECH key") - } - echKey = content - } else { - return nil, E.New("missing ECH key") - } - - block, rest := pem.Decode(echKey) - if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 { - return nil, E.New("invalid ECH keys pem") - } - - echKeys, err := cftls.EXP_UnmarshalECHKeys(block.Bytes) - if err != nil { - return nil, E.Cause(err, "parse ECH keys") - } - - echKeySet, err := cftls.EXP_NewECHKeySet(echKeys) - if err != nil { - return nil, E.Cause(err, "create ECH key set") - } - - tlsConfig.ECHEnabled = true - tlsConfig.PQSignatureSchemesEnabled = options.ECH.PQSignatureSchemesEnabled - tlsConfig.DynamicRecordSizingDisabled = options.ECH.DynamicRecordSizingDisabled - tlsConfig.ServerECHProvider = echKeySet - - return &echServerConfig{ - config: &tlsConfig, - logger: logger, - certificate: certificate, - key: key, - certificatePath: options.CertificatePath, - keyPath: options.KeyPath, - echKeyPath: options.ECH.KeyPath, - }, nil -} diff --git a/common/tls/ech_shared.go b/common/tls/ech_shared.go new file mode 100644 index 0000000000..1cdfb7e138 --- /dev/null +++ b/common/tls/ech_shared.go @@ -0,0 +1,81 @@ +package tls + +import ( + "crypto/ecdh" + "crypto/rand" + "encoding/pem" + + "golang.org/x/crypto/cryptobyte" +) + +type ECHCapableConfig interface { + Config + ECHConfigList() []byte + SetECHConfigList([]byte) +} + +func ECHKeygenDefault(publicName string) (configPem string, keyPem string, err error) { + echKey, err := ecdh.X25519().GenerateKey(rand.Reader) + if err != nil { + return + } + echConfig, err := marshalECHConfig(0, echKey.PublicKey().Bytes(), publicName, 0) + if err != nil { + return + } + configBuilder := cryptobyte.NewBuilder(nil) + configBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) { + builder.AddBytes(echConfig) + }) + configBytes, err := configBuilder.Bytes() + if err != nil { + return + } + keyBuilder := cryptobyte.NewBuilder(nil) + keyBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) { + builder.AddBytes(echKey.Bytes()) + }) + keyBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) { + builder.AddBytes(echConfig) + }) + keyBytes, err := keyBuilder.Bytes() + if err != nil { + return + } + configPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBytes})) + keyPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBytes})) + return +} + +func marshalECHConfig(id uint8, pubKey []byte, publicName string, maxNameLen uint8) ([]byte, error) { + const extensionEncryptedClientHello = 0xfe0d + const DHKEM_X25519_HKDF_SHA256 = 0x0020 + const KDF_HKDF_SHA256 = 0x0001 + builder := cryptobyte.NewBuilder(nil) + builder.AddUint16(extensionEncryptedClientHello) + builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) { + builder.AddUint8(id) + + builder.AddUint16(DHKEM_X25519_HKDF_SHA256) // The only DHKEM we support + builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) { + builder.AddBytes(pubKey) + }) + builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) { + const ( + AEAD_AES_128_GCM = 0x0001 + AEAD_AES_256_GCM = 0x0002 + AEAD_ChaCha20Poly1305 = 0x0003 + ) + for _, aeadID := range []uint16{AEAD_AES_128_GCM, AEAD_AES_256_GCM, AEAD_ChaCha20Poly1305} { + builder.AddUint16(KDF_HKDF_SHA256) // The only KDF we support + builder.AddUint16(aeadID) + } + }) + builder.AddUint8(maxNameLen) + builder.AddUint8LengthPrefixed(func(builder *cryptobyte.Builder) { + builder.AddBytes([]byte(publicName)) + }) + builder.AddUint16(0) // extensions + }) + return builder.Bytes() +} diff --git a/common/tls/ech_stub.go b/common/tls/ech_stub.go deleted file mode 100644 index 4ee4272cf8..0000000000 --- a/common/tls/ech_stub.go +++ /dev/null @@ -1,25 +0,0 @@ -//go:build !with_ech - -package tls - -import ( - "context" - - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" -) - -var errECHNotIncluded = E.New(`ECH is not included in this build, rebuild with -tags with_ech`) - -func NewECHServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) { - return nil, errECHNotIncluded -} - -func NewECHClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) { - return nil, errECHNotIncluded -} - -func ECHKeygenDefault(host string, pqSignatureSchemesEnabled bool) (configPem string, keyPem string, err error) { - return "", "", errECHNotIncluded -} diff --git a/common/tls/ech_tag_stub.go b/common/tls/ech_tag_stub.go new file mode 100644 index 0000000000..1a9cbd56eb --- /dev/null +++ b/common/tls/ech_tag_stub.go @@ -0,0 +1,5 @@ +//go:build with_ech + +package tls + +var _ int = "Due to the migration to stdlib, the separate `with_ech` build tag has been deprecated and is no longer needed, please update your build configuration." diff --git a/common/tls/ktls.go b/common/tls/ktls.go new file mode 100644 index 0000000000..dd564f5348 --- /dev/null +++ b/common/tls/ktls.go @@ -0,0 +1,67 @@ +package tls + +import ( + "context" + "net" + + "github.com/sagernet/sing-box/common/ktls" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + aTLS "github.com/sagernet/sing/common/tls" +) + +type KTLSClientConfig struct { + Config + logger logger.ContextLogger + kernelTx, kernelRx bool +} + +func (w *KTLSClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) { + tlsConn, err := aTLS.ClientHandshake(ctx, conn, w.Config) + if err != nil { + return nil, err + } + kConn, err := ktls.NewConn(ctx, w.logger, tlsConn, w.kernelTx, w.kernelRx) + if err != nil { + tlsConn.Close() + return nil, E.Cause(err, "initialize kernel TLS") + } + return kConn, nil +} + +func (w *KTLSClientConfig) Clone() Config { + return &KTLSClientConfig{ + w.Config.Clone(), + w.logger, + w.kernelTx, + w.kernelRx, + } +} + +type KTlSServerConfig struct { + ServerConfig + logger logger.ContextLogger + kernelTx, kernelRx bool +} + +func (w *KTlSServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) { + tlsConn, err := aTLS.ServerHandshake(ctx, conn, w.ServerConfig) + if err != nil { + return nil, err + } + kConn, err := ktls.NewConn(ctx, w.logger, tlsConn, w.kernelTx, w.kernelRx) + if err != nil { + tlsConn.Close() + return nil, E.Cause(err, "initialize kernel TLS") + } + return kConn, nil +} + +func (w *KTlSServerConfig) Clone() Config { + return &KTlSServerConfig{ + w.ServerConfig.Clone().(ServerConfig), + w.logger, + w.kernelTx, + w.kernelRx, + } +} diff --git a/common/tls/masque_client.go b/common/tls/masque_client.go new file mode 100644 index 0000000000..e9e4028995 --- /dev/null +++ b/common/tls/masque_client.go @@ -0,0 +1,81 @@ +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: ctx, + config: &tlsConfig, + serverName: serverName, + fragment: options.Fragment, + fragmentFallbackDelay: time.Duration(options.FragmentFallbackDelay), + recordFragment: 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 +} diff --git a/common/tls/mkcert.go b/common/tls/mkcert.go index 1e71a76315..4e0ed102bb 100644 --- a/common/tls/mkcert.go +++ b/common/tls/mkcert.go @@ -11,8 +11,11 @@ import ( "time" ) -func GenerateCertificate(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) { - privateKeyPem, publicKeyPem, err := GenerateKeyPair(timeFunc, serverName, timeFunc().Add(time.Hour)) +func GenerateKeyPair(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string) (*tls.Certificate, error) { + if timeFunc == nil { + timeFunc = time.Now + } + privateKeyPem, publicKeyPem, err := GenerateCertificate(parent, parentKey, timeFunc, serverName, timeFunc().Add(time.Hour)) if err != nil { return nil, err } @@ -23,10 +26,7 @@ func GenerateCertificate(timeFunc func() time.Time, serverName string) (*tls.Cer return &certificate, err } -func GenerateKeyPair(timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) { - if timeFunc == nil { - timeFunc = time.Now - } +func GenerateCertificate(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) { key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return @@ -47,7 +47,11 @@ func GenerateKeyPair(timeFunc func() time.Time, serverName string, expire time.T }, DNSNames: []string{serverName}, } - publicDer, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key) + if parent == nil { + parent = template + parentKey = key + } + publicDer, err := x509.CreateCertificate(rand.Reader, template, parent, key.Public(), parentKey) if err != nil { return } diff --git a/common/tls/reality_client.go b/common/tls/reality_client.go index 59ecf86003..af64ef6f29 100644 --- a/common/tls/reality_client.go +++ b/common/tls/reality_client.go @@ -27,12 +27,17 @@ import ( "time" "unsafe" + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/debug" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + "github.com/sagernet/sing/common/ntp" aTLS "github.com/sagernet/sing/common/tls" - utls "github.com/sagernet/utls" + utls "github.com/metacubex/utls" "golang.org/x/crypto/hkdf" "golang.org/x/net/http2" ) @@ -40,17 +45,25 @@ import ( var _ ConfigCompat = (*RealityClientConfig)(nil) type RealityClientConfig struct { + ctx context.Context uClient *UTLSClientConfig publicKey []byte shortID [8]byte } -func NewRealityClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (*RealityClientConfig, error) { +func NewRealityClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions) (Config, error) { + return newRealityClient(ctx, logger, serverAddress, options, false) +} + +func newRealityClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions, allowEmptyServerName bool) (Config, error) { if options.UTLS == nil || !options.UTLS.Enabled { return nil, E.New("uTLS is required by reality client") } + if options.Spoof != "" || options.SpoofMethod != "" { + return nil, E.New("spoof is unsupported in reality") + } - uClient, err := NewUTLSClient(ctx, serverAddress, options) + uClient, err := newUTLSClient(ctx, logger, serverAddress, options, allowEmptyServerName) if err != nil { return nil, err } @@ -70,7 +83,20 @@ func NewRealityClient(ctx context.Context, serverAddress string, options option. if decodedLen > 8 { return nil, E.New("invalid short_id") } - return &RealityClientConfig{uClient, publicKey, shortID}, nil + + var config Config = &RealityClientConfig{ctx, uClient.(*UTLSClientConfig), publicKey, shortID} + 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 } func (e *RealityClientConfig) ServerName() string { @@ -89,7 +115,15 @@ func (e *RealityClientConfig) SetNextProtos(nextProto []string) { e.uClient.SetNextProtos(nextProto) } -func (e *RealityClientConfig) Config() (*STDConfig, error) { +func (e *RealityClientConfig) HandshakeTimeout() time.Duration { + return e.uClient.HandshakeTimeout() +} + +func (e *RealityClientConfig) SetHandshakeTimeout(timeout time.Duration) { + e.uClient.SetHandshakeTimeout(timeout) +} + +func (e *RealityClientConfig) STDConfig() (*STDConfig, error) { return nil, E.New("unsupported usage for reality") } @@ -111,6 +145,22 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn if err != nil { return nil, err } + for _, extension := range uConn.Extensions { + if ce, ok := extension.(*utls.SupportedCurvesExtension); ok { + ce.Curves = common.Filter(ce.Curves, func(curveID utls.CurveID) bool { + return curveID != utls.X25519MLKEM768 + }) + } + if ks, ok := extension.(*utls.KeyShareExtension); ok { + ks.KeyShares = common.Filter(ks.KeyShares, func(share utls.KeyShare) bool { + return share.Group != utls.X25519MLKEM768 + }) + } + } + err = uConn.BuildHandshakeState() + if err != nil { + return nil, err + } if len(uConfig.NextProtos) > 0 { for _, extension := range uConn.Extensions { @@ -145,9 +195,13 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn if err != nil { return nil, err } - ecdheKey := uConn.HandshakeState.State13.EcdheKey + keyShareKeys := uConn.HandshakeState.State13.KeyShareKeys + if keyShareKeys == nil { + return nil, E.New("nil KeyShareKeys") + } + ecdheKey := keyShareKeys.Ecdhe if ecdheKey == nil { - return nil, E.New("nil ecdhe_key") + return nil, E.New("nil ecdheKey") } authKey, err := ecdheKey.ECDH(publicKey) if err != nil { @@ -180,20 +234,24 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn } if !verifier.verified { - go realityClientFallback(uConn, e.uClient.ServerName(), e.uClient.id) + go realityClientFallback(e.ctx, uConn, e.uClient.ServerName(), e.uClient.id) return nil, E.New("reality verification failed") } - return &utlsConnWrapper{uConn}, nil + return &realityClientConnWrapper{uConn}, nil } -func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) { +func realityClientFallback(ctx context.Context, uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) { defer uConn.Close() client := &http.Client{ Transport: &http2.Transport{ DialTLSContext: func(ctx context.Context, network, addr string, config *tls.Config) (net.Conn, error) { return uConn, nil }, + TLSClientConfig: &tls.Config{ + Time: ntp.TimeFuncFromContext(ctx), + RootCAs: adapter.RootPoolFromContext(ctx), + }, }, } request, _ := http.NewRequest("GET", "https://"+serverName, nil) @@ -207,12 +265,9 @@ func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.C response.Body.Close() } -func (e *RealityClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) { - e.uClient.config.SessionIDGenerator = generator -} - func (e *RealityClientConfig) Clone() Config { return &RealityClientConfig{ + e.ctx, e.uClient.Clone().(*UTLSClientConfig), e.publicKey, e.shortID, @@ -227,8 +282,8 @@ type realityVerifier struct { } func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { - p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates") - certs := *(*([]*x509.Certificate))(unsafe.Pointer(uintptr(unsafe.Pointer(c.Conn)) + p.Offset)) + p, _ := reflect.TypeFor[utls.Conn]().FieldByName("peerCertificates") + certs := *(*([]*x509.Certificate))(unsafe.Add(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) @@ -249,3 +304,44 @@ func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChain } return nil } + +type realityClientConnWrapper struct { + *utls.UConn +} + +func (c *realityClientConnWrapper) ConnectionState() tls.ConnectionState { + state := c.Conn.ConnectionState() + //nolint:staticcheck + return tls.ConnectionState{ + Version: state.Version, + HandshakeComplete: state.HandshakeComplete, + DidResume: state.DidResume, + CipherSuite: state.CipherSuite, + NegotiatedProtocol: state.NegotiatedProtocol, + NegotiatedProtocolIsMutual: state.NegotiatedProtocolIsMutual, + ServerName: state.ServerName, + PeerCertificates: state.PeerCertificates, + VerifiedChains: state.VerifiedChains, + SignedCertificateTimestamps: state.SignedCertificateTimestamps, + OCSPResponse: state.OCSPResponse, + TLSUnique: state.TLSUnique, + } +} + +func (c *realityClientConnWrapper) Upstream() any { + return c.UConn +} + +// Due to low implementation quality, the reality server intercepted half close and caused memory leaks. +// We fixed it by calling Close() directly. +func (c *realityClientConnWrapper) CloseWrite() error { + return c.Close() +} + +func (c *realityClientConnWrapper) ReaderReplaceable() bool { + return true +} + +func (c *realityClientConnWrapper) WriterReplaceable() bool { + return true +} diff --git a/common/tls/reality_server.go b/common/tls/reality_server.go index a931879835..10d6061870 100644 --- a/common/tls/reality_server.go +++ b/common/tls/reality_server.go @@ -1,4 +1,4 @@ -//go:build with_reality_server +//go:build with_utls package tls @@ -7,30 +7,36 @@ import ( "crypto/tls" "encoding/base64" "encoding/hex" + "fmt" "net" "time" - "github.com/sagernet/reality" - "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/dialer" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common/debug" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ntp" + + utls "github.com/metacubex/utls" ) var _ ServerConfigCompat = (*RealityServerConfig)(nil) type RealityServerConfig struct { - config *reality.Config + config *utls.RealityConfig + handshakeTimeout time.Duration } -func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (*RealityServerConfig, error) { - var tlsConfig reality.Config +func NewRealityServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) { + var tlsConfig utls.RealityConfig + if options.CertificateProvider != nil { + return nil, E.New("certificate_provider is unavailable in reality") + } + //nolint:staticcheck if options.ACME != nil && len(options.ACME.Domain) > 0 { return nil, E.New("acme is unavailable in reality") } @@ -67,7 +73,10 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb return nil, E.New("unknown cipher_suite: ", cipherSuite) } } - if len(options.Certificate) > 0 || options.CertificatePath != "" { + if len(options.CurvePreferences) > 0 { + return nil, E.New("curve preferences is unavailable in reality") + } + if len(options.Certificate) > 0 || options.CertificatePath != "" || len(options.ClientCertificatePublicKeySHA256) > 0 { return nil, E.New("certificate is unavailable in reality") } if len(options.Key) > 0 || options.KeyPath != "" { @@ -75,6 +84,11 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb } tlsConfig.SessionTicketsDisabled = true + tlsConfig.Log = func(format string, v ...any) { + if logger != nil { + logger.Trace(fmt.Sprintf(format, v...)) + } + } tlsConfig.Type = N.NetworkTCP tlsConfig.Dest = options.Reality.Handshake.ServerOptions.Build().String() @@ -90,19 +104,23 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb tlsConfig.MaxTimeDiff = time.Duration(options.Reality.MaxTimeDifference) tlsConfig.ShortIds = make(map[[8]byte]bool) - for i, shortIDString := range options.Reality.ShortID { - var shortID [8]byte - decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString)) - if err != nil { - return nil, E.Cause(err, "decode short_id[", i, "]: ", shortIDString) - } - if decodedLen > 8 { - return nil, E.New("invalid short_id[", i, "]: ", shortIDString) + if len(options.Reality.ShortID) == 0 { + tlsConfig.ShortIds[[8]byte{0}] = true + } else { + for i, shortIDString := range options.Reality.ShortID { + var shortID [8]byte + decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString)) + if err != nil { + return nil, E.Cause(err, "decode short_id[", i, "]: ", shortIDString) + } + if decodedLen > 8 { + return nil, E.New("invalid short_id[", i, "]: ", shortIDString) + } + tlsConfig.ShortIds[shortID] = true } - tlsConfig.ShortIds[shortID] = true } - handshakeDialer, err := dialer.New(adapter.RouterFromContext(ctx), options.Reality.Handshake.DialerOptions) + handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions, options.Reality.Handshake.ServerIsDomain()) if err != nil { return nil, err } @@ -110,11 +128,31 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb return handshakeDialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) } - if debug.Enabled { - tlsConfig.Show = true + if options.ECH != nil && options.ECH.Enabled { + return nil, E.New("Reality is conflict with ECH") } - - return &RealityServerConfig{&tlsConfig}, nil + var handshakeTimeout time.Duration + if options.HandshakeTimeout > 0 { + handshakeTimeout = options.HandshakeTimeout.Build() + } else { + handshakeTimeout = C.TCPTimeout + } + var config ServerConfig = &RealityServerConfig{ + config: &tlsConfig, + handshakeTimeout: handshakeTimeout, + } + if options.KernelTx || options.KernelRx { + if !C.IsLinux { + return nil, E.New("kTLS is only supported on Linux") + } + config = &KTlSServerConfig{ + ServerConfig: config, + logger: logger, + kernelTx: options.KernelTx, + kernelRx: options.KernelRx, + } + } + return config, nil } func (c *RealityServerConfig) ServerName() string { @@ -133,7 +171,15 @@ func (c *RealityServerConfig) SetNextProtos(nextProto []string) { c.config.NextProtos = nextProto } -func (c *RealityServerConfig) Config() (*tls.Config, error) { +func (c *RealityServerConfig) HandshakeTimeout() time.Duration { + return c.handshakeTimeout +} + +func (c *RealityServerConfig) SetHandshakeTimeout(timeout time.Duration) { + c.handshakeTimeout = timeout +} + +func (c *RealityServerConfig) STDConfig() (*tls.Config, error) { return nil, E.New("unsupported usage for reality") } @@ -154,7 +200,7 @@ func (c *RealityServerConfig) Server(conn net.Conn) (Conn, error) { } func (c *RealityServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error) { - tlsConn, err := reality.Server(ctx, conn, c.config) + tlsConn, err := utls.RealityServer(ctx, conn, c.config) if err != nil { return nil, err } @@ -163,18 +209,20 @@ func (c *RealityServerConfig) ServerHandshake(ctx context.Context, conn net.Conn func (c *RealityServerConfig) Clone() Config { return &RealityServerConfig{ - config: c.config.Clone(), + config: c.config.Clone(), + handshakeTimeout: c.handshakeTimeout, } } var _ Conn = (*realityConnWrapper)(nil) type realityConnWrapper struct { - *reality.Conn + *utls.Conn } func (c *realityConnWrapper) ConnectionState() ConnectionState { state := c.Conn.ConnectionState() + //nolint:staticcheck return tls.ConnectionState{ Version: state.Version, HandshakeComplete: state.HandshakeComplete, @@ -194,3 +242,17 @@ func (c *realityConnWrapper) ConnectionState() ConnectionState { func (c *realityConnWrapper) Upstream() any { return c.Conn } + +// Due to low implementation quality, the reality server intercepted half close and caused memory leaks. +// We fixed it by calling Close() directly. +func (c *realityConnWrapper) CloseWrite() error { + return c.Close() +} + +func (c *realityConnWrapper) ReaderReplaceable() bool { + return true +} + +func (c *realityConnWrapper) WriterReplaceable() bool { + return true +} diff --git a/common/tls/reality_stub.go b/common/tls/reality_stub.go index 8d394f7b01..0feb2aac0a 100644 --- a/common/tls/reality_stub.go +++ b/common/tls/reality_stub.go @@ -1,15 +1,5 @@ -//go:build !with_reality_server +//go:build with_reality_server package tls -import ( - "context" - - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" -) - -func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) { - return nil, E.New(`reality server is not included in this build, rebuild with -tags with_reality_server`) -} +var _ int = "The separate `with_reality_server` build tag has been merged into `with_utls` and is no longer needed, please update your build configuration." diff --git a/common/tls/server.go b/common/tls/server.go index 6afd89d64d..8f4b3c38d0 100644 --- a/common/tls/server.go +++ b/common/tls/server.go @@ -12,22 +12,45 @@ import ( aTLS "github.com/sagernet/sing/common/tls" ) -func NewServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) { - if !options.Enabled { +type ServerOptions struct { + Context context.Context + Logger log.ContextLogger + Options option.InboundTLSOptions + KTLSCompatible bool +} + +func NewServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) { + return NewServerWithOptions(ServerOptions{ + Context: ctx, + Logger: logger, + Options: options, + }) +} + +func NewServerWithOptions(options ServerOptions) (ServerConfig, error) { + if !options.Options.Enabled { return nil, nil } - if options.ECH != nil && options.ECH.Enabled { - return NewECHServer(ctx, logger, options) - } else if options.Reality != nil && options.Reality.Enabled { - return NewRealityServer(ctx, logger, options) - } else { - return NewSTDServer(ctx, logger, options) + if !options.KTLSCompatible { + if options.Options.KernelTx { + options.Logger.Warn("enabling kTLS TX in current scenarios will definitely reduce performance, please checkout https://sing-box.sagernet.org/configuration/shared/tls/#kernel_tx") + } } + if options.Options.KernelRx { + options.Logger.Warn("enabling kTLS RX will definitely reduce performance, please checkout https://sing-box.sagernet.org/configuration/shared/tls/#kernel_rx") + } + if options.Options.Reality != nil && options.Options.Reality.Enabled { + return NewRealityServer(options.Context, options.Logger, options.Options) + } + return NewSTDServer(options.Context, options.Logger, options.Options) } func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) { - ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout) - defer cancel() + if config.HandshakeTimeout() == 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, C.TCPTimeout) + defer cancel() + } tlsConn, err := aTLS.ServerHandshake(ctx, conn, config) if err != nil { return nil, err diff --git a/common/tls/std_client.go b/common/tls/std_client.go index 90f51821e8..6531039604 100644 --- a/common/tls/std_client.go +++ b/common/tls/std_client.go @@ -1,85 +1,146 @@ package tls import ( + "bytes" "context" + "crypto/sha256" "crypto/tls" "crypto/x509" + "encoding/base64" "net" - "net/netip" "os" "strings" + "time" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/tlsfragment" + "github.com/sagernet/sing-box/common/tlsspoof" + 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" "github.com/sagernet/sing/common/ntp" ) type STDClientConfig struct { - config *tls.Config + ctx context.Context + config *tls.Config + serverName string + disableSNI bool + verifyServerName bool + handshakeTimeout time.Duration + fragment bool + fragmentFallbackDelay time.Duration + recordFragment bool + spoof string + spoofMethod tlsspoof.Method } -func (s *STDClientConfig) ServerName() string { - return s.config.ServerName +func (c *STDClientConfig) ServerName() string { + return c.serverName } -func (s *STDClientConfig) SetServerName(serverName string) { - s.config.ServerName = serverName +func (c *STDClientConfig) SetServerName(serverName string) { + c.serverName = serverName + if c.disableSNI { + c.config.ServerName = "" + if c.verifyServerName { + c.config.VerifyConnection = verifyConnection(c.config.RootCAs, c.config.Time, serverName) + } else { + c.config.VerifyConnection = nil + } + return + } + c.config.ServerName = serverName +} + +func (c *STDClientConfig) NextProtos() []string { + return c.config.NextProtos +} + +func (c *STDClientConfig) SetNextProtos(nextProto []string) { + c.config.NextProtos = nextProto +} + +func (c *STDClientConfig) HandshakeTimeout() time.Duration { + return c.handshakeTimeout } -func (s *STDClientConfig) NextProtos() []string { - return s.config.NextProtos +func (c *STDClientConfig) SetHandshakeTimeout(timeout time.Duration) { + c.handshakeTimeout = timeout } -func (s *STDClientConfig) SetNextProtos(nextProto []string) { - s.config.NextProtos = nextProto +func (c *STDClientConfig) STDConfig() (*STDConfig, error) { + return c.config, nil } -func (s *STDClientConfig) Config() (*STDConfig, error) { - return s.config, nil +func (c *STDClientConfig) Client(conn net.Conn) (Conn, error) { + if c.fragment || c.recordFragment { + conn = tf.NewConn(conn, c.ctx, c.fragment, c.recordFragment, c.fragmentFallbackDelay) + } + conn, err := applyTLSSpoof(conn, c.spoof, c.spoofMethod) + if err != nil { + return nil, err + } + return tls.Client(conn, c.config), nil } -func (s *STDClientConfig) Client(conn net.Conn) (Conn, error) { - return tls.Client(conn, s.config), nil +func (c *STDClientConfig) Clone() Config { + cloned := &STDClientConfig{ + ctx: c.ctx, + config: c.config.Clone(), + serverName: c.serverName, + disableSNI: c.disableSNI, + verifyServerName: c.verifyServerName, + handshakeTimeout: c.handshakeTimeout, + fragment: c.fragment, + fragmentFallbackDelay: c.fragmentFallbackDelay, + recordFragment: c.recordFragment, + spoof: c.spoof, + spoofMethod: c.spoofMethod, + } + cloned.SetServerName(cloned.serverName) + return cloned } -func (s *STDClientConfig) Clone() Config { - return &STDClientConfig{s.config.Clone()} +func (c *STDClientConfig) ECHConfigList() []byte { + return c.config.EncryptedClientHelloConfigList } -func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) { +func (c *STDClientConfig) SetECHConfigList(EncryptedClientHelloConfigList []byte) { + c.config.EncryptedClientHelloConfigList = EncryptedClientHelloConfigList +} + +func NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions) (Config, error) { + return newSTDClient(ctx, logger, serverAddress, options, false) +} + +func newSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions, allowEmptyServerName bool) (Config, error) { var serverName string if options.ServerName != "" { serverName = options.ServerName } else if serverAddress != "" { - if _, err := netip.ParseAddr(serverName); err != nil { - serverName = serverAddress - } + serverName = serverAddress } - if serverName == "" && !options.Insecure { - return nil, E.New("missing server_name or insecure=true") + if serverName == "" && !options.Insecure && !allowEmptyServerName { + return nil, errMissingServerName } var tlsConfig tls.Config tlsConfig.Time = ntp.TimeFuncFromContext(ctx) - if options.DisableSNI { - tlsConfig.ServerName = "127.0.0.1" - } else { - tlsConfig.ServerName = serverName - } + tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx) if options.Insecure { tlsConfig.InsecureSkipVerify = options.Insecure } else if options.DisableSNI { tlsConfig.InsecureSkipVerify = true - tlsConfig.VerifyConnection = func(state tls.ConnectionState) error { - verifyOptions := x509.VerifyOptions{ - DNSName: serverName, - Intermediates: x509.NewCertPool(), - } - for _, cert := range state.PeerCertificates[1:] { - verifyOptions.Intermediates.AddCert(cert) - } - _, err := state.PeerCertificates[0].Verify(verifyOptions) - return err + } + if len(options.CertificatePublicKeySHA256) > 0 { + if len(options.Certificate) > 0 || options.CertificatePath != "" { + return nil, E.New("certificate_public_key_sha256 is conflict with certificate or certificate_path") + } + tlsConfig.InsecureSkipVerify = true + tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + return VerifyPublicKeySHA256(options.CertificatePublicKeySHA256, rawCerts) } } if len(options.ALPN) > 0 { @@ -111,6 +172,9 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb return nil, E.New("unknown cipher_suite: ", cipherSuite) } } + for _, curve := range options.CurvePreferences { + tlsConfig.CurvePreferences = append(tlsConfig.CurvePreferences, tls.CurveID(curve)) + } var certificate []byte if len(options.Certificate) > 0 { certificate = []byte(strings.Join(options.Certificate, "\n")) @@ -128,5 +192,104 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb } tlsConfig.RootCAs = certPool } - return &STDClientConfig{&tlsConfig}, nil + var clientCertificate []byte + if len(options.ClientCertificate) > 0 { + clientCertificate = []byte(strings.Join(options.ClientCertificate, "\n")) + } else if options.ClientCertificatePath != "" { + content, err := os.ReadFile(options.ClientCertificatePath) + if err != nil { + return nil, E.Cause(err, "read client certificate") + } + clientCertificate = content + } + var clientKey []byte + if len(options.ClientKey) > 0 { + clientKey = []byte(strings.Join(options.ClientKey, "\n")) + } else if options.ClientKeyPath != "" { + content, err := os.ReadFile(options.ClientKeyPath) + if err != nil { + return nil, E.Cause(err, "read client key") + } + clientKey = content + } + if len(clientCertificate) > 0 && len(clientKey) > 0 { + keyPair, err := tls.X509KeyPair(clientCertificate, clientKey) + if err != nil { + return nil, E.Cause(err, "parse client x509 key pair") + } + tlsConfig.Certificates = []tls.Certificate{keyPair} + } else if len(clientCertificate) > 0 || len(clientKey) > 0 { + return nil, E.New("client certificate and client key must be provided together") + } + var handshakeTimeout time.Duration + if options.HandshakeTimeout > 0 { + handshakeTimeout = options.HandshakeTimeout.Build() + } else { + handshakeTimeout = C.TCPTimeout + } + spoof, spoofMethod, err := parseTLSSpoofOptions(serverName, options) + if err != nil { + return nil, err + } + var config Config = &STDClientConfig{ + ctx: ctx, + config: &tlsConfig, + serverName: serverName, + disableSNI: options.DisableSNI, + verifyServerName: options.DisableSNI && !options.Insecure, + handshakeTimeout: handshakeTimeout, + fragment: options.Fragment, + fragmentFallbackDelay: time.Duration(options.FragmentFallbackDelay), + recordFragment: options.RecordFragment, + spoof: spoof, + spoofMethod: spoofMethod, + } + config.SetServerName(serverName) + if options.ECH != nil && options.ECH.Enabled { + var err error + config, err = parseECHClientConfig(ctx, config.(ECHCapableConfig), options) + if err != nil { + return nil, err + } + } + 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 +} + +func verifyConnection(rootCAs *x509.CertPool, timeFunc func() time.Time, serverName string) func(state tls.ConnectionState) error { + return func(state tls.ConnectionState) error { + if serverName == "" { + return errMissingServerName + } + return verifySystemTLSPeer(rootCAs, serverName, timeFunc, state.PeerCertificates) + } +} + +func VerifyPublicKeySHA256(knownHashValues [][]byte, rawCerts [][]byte) error { + leafCertificate, err := x509.ParseCertificate(rawCerts[0]) + if err != nil { + return E.Cause(err, "failed to parse leaf certificate") + } + + pubKeyBytes, err := x509.MarshalPKIXPublicKey(leafCertificate.PublicKey) + if err != nil { + return E.Cause(err, "failed to marshal public key") + } + hashValue := sha256.Sum256(pubKeyBytes) + for _, value := range knownHashValues { + if bytes.Equal(value, hashValue[:]) { + return nil + } + } + return E.New("unrecognized remote public key: ", base64.StdEncoding.EncodeToString(hashValue[:])) } diff --git a/common/tls/std_server.go b/common/tls/std_server.go index 7184bdb36b..a99b327579 100644 --- a/common/tls/std_server.go +++ b/common/tls/std_server.go @@ -3,58 +3,168 @@ package tls import ( "context" "crypto/tls" + "crypto/x509" "net" "os" "strings" + "sync" + "time" + "github.com/sagernet/fswatch" "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/ntp" - - "github.com/fsnotify/fsnotify" + "github.com/sagernet/sing/service" ) var errInsecureUnused = E.New("tls: insecure unused") +type managedCertificateProvider interface { + adapter.CertificateProvider + adapter.SimpleLifecycle +} + +type sharedCertificateProvider struct { + tag string + manager adapter.CertificateProviderManager + provider adapter.CertificateProviderService +} + +func (p *sharedCertificateProvider) Start() error { + provider, found := p.manager.Get(p.tag) + if !found { + return E.New("certificate provider not found: ", p.tag) + } + p.provider = provider + return nil +} + +func (p *sharedCertificateProvider) Close() error { + return nil +} + +func (p *sharedCertificateProvider) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { + return p.provider.GetCertificate(hello) +} + +func (p *sharedCertificateProvider) GetACMENextProtos() []string { + return getACMENextProtos(p.provider) +} + +type inlineCertificateProvider struct { + provider adapter.CertificateProviderService +} + +func (p *inlineCertificateProvider) Start() error { + for _, stage := range adapter.ListStartStages { + err := adapter.LegacyStart(p.provider, stage) + if err != nil { + return err + } + } + return nil +} + +func (p *inlineCertificateProvider) Close() error { + return p.provider.Close() +} + +func (p *inlineCertificateProvider) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { + return p.provider.GetCertificate(hello) +} + +func (p *inlineCertificateProvider) GetACMENextProtos() []string { + return getACMENextProtos(p.provider) +} + +func getACMENextProtos(provider adapter.CertificateProvider) []string { + if acmeProvider, isACME := provider.(adapter.ACMECertificateProvider); isACME { + return acmeProvider.GetACMENextProtos() + } + return nil +} + type STDServerConfig struct { - config *tls.Config - logger log.Logger - acmeService adapter.Service - certificate []byte - key []byte - certificatePath string - keyPath string - watcher *fsnotify.Watcher + access sync.RWMutex + config *tls.Config + handshakeTimeout time.Duration + logger log.Logger + certificateProvider managedCertificateProvider + acmeService adapter.SimpleLifecycle + certificate []byte + key []byte + certificatePath string + keyPath string + clientCertificatePath []string + echKeyPath string + watcher *fswatch.Watcher } func (c *STDServerConfig) ServerName() string { + c.access.RLock() + defer c.access.RUnlock() return c.config.ServerName } func (c *STDServerConfig) SetServerName(serverName string) { - c.config.ServerName = serverName + c.access.Lock() + defer c.access.Unlock() + config := c.config.Clone() + config.ServerName = serverName + c.config = config } func (c *STDServerConfig) NextProtos() []string { - if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol { + c.access.RLock() + defer c.access.RUnlock() + if c.hasACMEALPN() && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == C.ACMETLS1Protocol { return c.config.NextProtos[1:] - } else { - return c.config.NextProtos } + return c.config.NextProtos } func (c *STDServerConfig) SetNextProtos(nextProto []string) { - if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol { - c.config.NextProtos = append(c.config.NextProtos[:1], nextProto...) + c.access.Lock() + defer c.access.Unlock() + config := c.config.Clone() + if c.hasACMEALPN() && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == C.ACMETLS1Protocol { + config.NextProtos = append(c.config.NextProtos[:1], nextProto...) } else { - c.config.NextProtos = nextProto + config.NextProtos = nextProto } + c.config = config } -func (c *STDServerConfig) Config() (*STDConfig, error) { +func (c *STDServerConfig) HandshakeTimeout() time.Duration { + c.access.RLock() + defer c.access.RUnlock() + return c.handshakeTimeout +} + +func (c *STDServerConfig) SetHandshakeTimeout(timeout time.Duration) { + c.access.Lock() + defer c.access.Unlock() + c.handshakeTimeout = timeout +} + +func (c *STDServerConfig) hasACMEALPN() bool { + if c.acmeService != nil { + return true + } + if c.certificateProvider != nil { + if acmeProvider, isACME := c.certificateProvider.(adapter.ACMECertificateProvider); isACME { + return len(acmeProvider.GetACMENextProtos()) > 0 + } + } + return false +} + +func (c *STDServerConfig) STDConfig() (*STDConfig, error) { return c.config, nil } @@ -68,114 +178,179 @@ func (c *STDServerConfig) Server(conn net.Conn) (Conn, error) { func (c *STDServerConfig) Clone() Config { return &STDServerConfig{ - config: c.config.Clone(), + config: c.config.Clone(), + handshakeTimeout: c.handshakeTimeout, } } func (c *STDServerConfig) Start() error { - if c.acmeService != nil { - return c.acmeService.Start() - } else { - if c.certificatePath == "" && c.keyPath == "" { - return nil + if c.certificateProvider != nil { + err := c.certificateProvider.Start() + if err != nil { + return err } - err := c.startWatcher() + if acmeProvider, isACME := c.certificateProvider.(adapter.ACMECertificateProvider); isACME { + nextProtos := acmeProvider.GetACMENextProtos() + if len(nextProtos) > 0 { + c.access.Lock() + config := c.config.Clone() + mergedNextProtos := append([]string{}, nextProtos...) + for _, nextProto := range config.NextProtos { + if !common.Contains(mergedNextProtos, nextProto) { + mergedNextProtos = append(mergedNextProtos, nextProto) + } + } + config.NextProtos = mergedNextProtos + c.config = config + c.access.Unlock() + } + } + } + if c.acmeService != nil { + err := c.acmeService.Start() if err != nil { - c.logger.Warn("create fsnotify watcher: ", err) + return err } - return nil } + err := c.startWatcher() + if err != nil { + c.logger.Warn("create fsnotify watcher: ", err) + } + return nil } func (c *STDServerConfig) startWatcher() error { - watcher, err := fsnotify.NewWatcher() - if err != nil { - return err - } + var watchPath []string if c.certificatePath != "" { - err = watcher.Add(c.certificatePath) - if err != nil { - return err - } + watchPath = append(watchPath, c.certificatePath) } if c.keyPath != "" { - err = watcher.Add(c.keyPath) - if err != nil { - return err - } + watchPath = append(watchPath, c.keyPath) + } + if c.echKeyPath != "" { + watchPath = append(watchPath, c.echKeyPath) + } + if len(c.clientCertificatePath) > 0 { + watchPath = append(watchPath, c.clientCertificatePath...) + } + if len(watchPath) == 0 { + return nil + } + watcher, err := fswatch.NewWatcher(fswatch.Options{ + Path: watchPath, + Callback: func(path string) { + err := c.certificateUpdated(path) + if err != nil { + c.logger.Error(E.Cause(err, "reload certificate")) + } + }, + }) + if err != nil { + return err + } + err = watcher.Start() + if err != nil { + return err } c.watcher = watcher - go c.loopUpdate() return nil } -func (c *STDServerConfig) loopUpdate() { - for { - select { - case event, ok := <-c.watcher.Events: - if !ok { - return +func (c *STDServerConfig) certificateUpdated(path string) error { + if path == c.certificatePath || path == c.keyPath { + switch path { + case c.certificatePath: + certificate, err := os.ReadFile(c.certificatePath) + if err != nil { + return E.Cause(err, "reload certificate from ", c.certificatePath) } - if event.Op&fsnotify.Write != fsnotify.Write { - continue + c.certificate = certificate + case c.keyPath: + key, err := os.ReadFile(c.keyPath) + if err != nil { + return E.Cause(err, "reload key from ", c.keyPath) } - err := c.reloadKeyPair() + c.key = key + } + keyPair, err := tls.X509KeyPair(c.certificate, c.key) + if err != nil { + return E.Cause(err, "reload key pair") + } + c.access.Lock() + config := c.config.Clone() + config.Certificates = []tls.Certificate{keyPair} + c.config = config + c.access.Unlock() + c.logger.Info("reloaded TLS certificate") + } else if common.Contains(c.clientCertificatePath, path) { + clientCertificateCA := x509.NewCertPool() + var reloaded bool + for _, certPath := range c.clientCertificatePath { + content, err := os.ReadFile(certPath) if err != nil { - c.logger.Error(E.Cause(err, "reload TLS key pair")) + c.logger.Error(E.Cause(err, "reload certificate from ", c.clientCertificatePath)) + continue } - case err, ok := <-c.watcher.Errors: - if !ok { - return + if !clientCertificateCA.AppendCertsFromPEM(content) { + c.logger.Error(E.New("invalid client certificate file: ", certPath)) + continue } - c.logger.Error(E.Cause(err, "fsnotify error")) + reloaded = true } - } -} - -func (c *STDServerConfig) reloadKeyPair() error { - if c.certificatePath != "" { - certificate, err := os.ReadFile(c.certificatePath) + if !reloaded { + return E.New("client certificates is empty") + } + c.access.Lock() + config := c.config.Clone() + config.ClientCAs = clientCertificateCA + c.config = config + c.access.Unlock() + c.logger.Info("reloaded client certificates") + } else if path == c.echKeyPath { + echKey, err := os.ReadFile(c.echKeyPath) if err != nil { - return E.Cause(err, "reload certificate from ", c.certificatePath) + return E.Cause(err, "reload ECH keys from ", c.echKeyPath) } - c.certificate = certificate - } - if c.keyPath != "" { - key, err := os.ReadFile(c.keyPath) + err = c.setECHServerConfig(echKey) if err != nil { - return E.Cause(err, "reload key from ", c.keyPath) + return err } - c.key = key - } - keyPair, err := tls.X509KeyPair(c.certificate, c.key) - if err != nil { - return E.Cause(err, "reload key pair") + c.logger.Info("reloaded ECH keys") } - c.config.Certificates = []tls.Certificate{keyPair} - c.logger.Info("reloaded TLS certificate") return nil } func (c *STDServerConfig) Close() error { - if c.acmeService != nil { - return c.acmeService.Close() - } - if c.watcher != nil { - return c.watcher.Close() - } - return nil + return common.Close(c.certificateProvider, c.acmeService, common.PtrOrNil(c.watcher)) } -func NewSTDServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) { +func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) { if !options.Enabled { return nil, nil } + //nolint:staticcheck + if options.CertificateProvider != nil && options.ACME != nil { + return nil, E.New("certificate_provider and acme are mutually exclusive") + } var tlsConfig *tls.Config - var acmeService adapter.Service + var certificateProvider managedCertificateProvider + var acmeService adapter.SimpleLifecycle var err error - if options.ACME != nil && len(options.ACME.Domain) > 0 { + if options.CertificateProvider != nil { + certificateProvider, err = newCertificateProvider(ctx, logger, options.CertificateProvider) + if err != nil { + return nil, err + } + tlsConfig = &tls.Config{ + GetCertificate: certificateProvider.GetCertificate, + } + if options.Insecure { + return nil, errInsecureUnused + } + } else if options.ACME != nil && len(options.ACME.Domain) > 0 { //nolint:staticcheck + deprecated.Report(ctx, deprecated.OptionInlineACME) //nolint:staticcheck - tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME)) + tlsConfig, acmeService, err = startACME(ctx, logger, common.PtrValueOrDefault(options.ACME)) if err != nil { return nil, err } @@ -218,9 +393,15 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound return nil, E.New("unknown cipher_suite: ", cipherSuite) } } - var certificate []byte - var key []byte - if acmeService == nil { + for _, curveID := range options.CurvePreferences { + tlsConfig.CurvePreferences = append(tlsConfig.CurvePreferences, tls.CurveID(curveID)) + } + tlsConfig.ClientAuth = tls.ClientAuthType(options.ClientAuthentication) + var ( + certificate []byte + key []byte + ) + if certificateProvider == nil && acmeService == nil { if len(options.Certificate) > 0 { certificate = []byte(strings.Join(options.Certificate, "\n")) } else if options.CertificatePath != "" { @@ -240,8 +421,12 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound key = content } if certificate == nil && key == nil && options.Insecure { + timeFunc := ntp.TimeFuncFromContext(ctx) + if timeFunc == nil { + timeFunc = time.Now + } tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) { - return GenerateCertificate(ntp.TimeFuncFromContext(ctx), info.ServerName) + return GenerateKeyPair(nil, nil, timeFunc, info.ServerName) } } else { if certificate == nil { @@ -257,13 +442,110 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound tlsConfig.Certificates = []tls.Certificate{keyPair} } } - return &STDServerConfig{ - config: tlsConfig, - logger: logger, - acmeService: acmeService, - certificate: certificate, - key: key, - certificatePath: options.CertificatePath, - keyPath: options.KeyPath, + if len(options.ClientCertificate) > 0 || len(options.ClientCertificatePath) > 0 { + if tlsConfig.ClientAuth == tls.NoClientCert { + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + } + } + if tlsConfig.ClientAuth == tls.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tls.RequireAndVerifyClientCert { + if len(options.ClientCertificate) > 0 { + clientCertificateCA := x509.NewCertPool() + if !clientCertificateCA.AppendCertsFromPEM([]byte(strings.Join(options.ClientCertificate, "\n"))) { + return nil, E.New("invalid client certificate strings") + } + tlsConfig.ClientCAs = clientCertificateCA + } else if len(options.ClientCertificatePath) > 0 { + clientCertificateCA := x509.NewCertPool() + for _, path := range options.ClientCertificatePath { + content, err := os.ReadFile(path) + if err != nil { + return nil, E.Cause(err, "read client certificate from ", path) + } + if !clientCertificateCA.AppendCertsFromPEM(content) { + return nil, E.New("invalid client certificate file: ", path) + } + } + tlsConfig.ClientCAs = clientCertificateCA + } else if len(options.ClientCertificatePublicKeySHA256) > 0 { + switch tlsConfig.ClientAuth { + case tls.RequireAndVerifyClientCert: + tlsConfig.ClientAuth = tls.RequireAnyClientCert + case tls.VerifyClientCertIfGiven: + tlsConfig.ClientAuth = tls.RequestClientCert + } + tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + return VerifyPublicKeySHA256(options.ClientCertificatePublicKeySHA256, rawCerts) + } + } else { + return nil, E.New("missing client_certificate, client_certificate_path or client_certificate_public_key_sha256 for client authentication") + } + } + var echKeyPath string + if options.ECH != nil && options.ECH.Enabled { + err = parseECHServerConfig(ctx, options, tlsConfig, &echKeyPath) + if err != nil { + return nil, err + } + } + var handshakeTimeout time.Duration + if options.HandshakeTimeout > 0 { + handshakeTimeout = options.HandshakeTimeout.Build() + } else { + handshakeTimeout = C.TCPTimeout + } + serverConfig := &STDServerConfig{ + config: tlsConfig, + handshakeTimeout: handshakeTimeout, + logger: logger, + certificateProvider: certificateProvider, + acmeService: acmeService, + certificate: certificate, + key: key, + certificatePath: options.CertificatePath, + clientCertificatePath: options.ClientCertificatePath, + keyPath: options.KeyPath, + echKeyPath: echKeyPath, + } + serverConfig.config.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) { + serverConfig.access.RLock() + defer serverConfig.access.RUnlock() + return serverConfig.config, nil + } + var config ServerConfig = serverConfig + if options.KernelTx || options.KernelRx { + if !C.IsLinux { + return nil, E.New("kTLS is only supported on Linux") + } + config = &KTlSServerConfig{ + ServerConfig: config, + logger: logger, + kernelTx: options.KernelTx, + kernelRx: options.KernelRx, + } + } + return config, nil +} + +func newCertificateProvider(ctx context.Context, logger log.ContextLogger, options *option.CertificateProviderOptions) (managedCertificateProvider, error) { + if options.IsShared() { + manager := service.FromContext[adapter.CertificateProviderManager](ctx) + if manager == nil { + return nil, E.New("missing certificate provider manager in context") + } + return &sharedCertificateProvider{ + tag: options.Tag, + manager: manager, + }, nil + } + registry := service.FromContext[adapter.CertificateProviderRegistry](ctx) + if registry == nil { + return nil, E.New("missing certificate provider registry in context") + } + provider, err := registry.Create(ctx, logger, "", options.Type, options.Options) + if err != nil { + return nil, E.Cause(err, "create inline certificate provider") + } + return &inlineCertificateProvider{ + provider: provider, }, nil } diff --git a/common/tls/system_client.go b/common/tls/system_client.go new file mode 100644 index 0000000000..ade2bd3636 --- /dev/null +++ b/common/tls/system_client.go @@ -0,0 +1,123 @@ +package tls + +import ( + "context" + "crypto/x509" + "os" + "strings" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/service" +) + +type SystemTLSValidated struct { + MinVersion uint16 + MaxVersion uint16 + UserPEM []byte + Exclusive bool + Store adapter.CertificateStore +} + +func ValidateSystemTLSOptions(ctx context.Context, options option.OutboundTLSOptions, engineName string) (SystemTLSValidated, error) { + if options.Reality != nil && options.Reality.Enabled { + return SystemTLSValidated{}, E.New("reality is unsupported in ", engineName) + } + if options.UTLS != nil && options.UTLS.Enabled { + return SystemTLSValidated{}, E.New("utls is unsupported in ", engineName) + } + if options.ECH != nil && options.ECH.Enabled { + return SystemTLSValidated{}, E.New("ech is unsupported in ", engineName) + } + if options.DisableSNI { + return SystemTLSValidated{}, E.New("disable_sni is unsupported in ", engineName) + } + if len(options.CipherSuites) > 0 { + return SystemTLSValidated{}, E.New("cipher_suites is unsupported in ", engineName) + } + if len(options.CurvePreferences) > 0 { + return SystemTLSValidated{}, E.New("curve_preferences is unsupported in ", engineName) + } + if len(options.ClientCertificate) > 0 || options.ClientCertificatePath != "" || len(options.ClientKey) > 0 || options.ClientKeyPath != "" { + return SystemTLSValidated{}, E.New("client certificate is unsupported in ", engineName) + } + if options.Fragment || options.RecordFragment { + return SystemTLSValidated{}, E.New("tls fragment is unsupported in ", engineName) + } + if options.KernelTx || options.KernelRx { + return SystemTLSValidated{}, E.New("ktls is unsupported in ", engineName) + } + if options.Spoof != "" || options.SpoofMethod != "" { + return SystemTLSValidated{}, E.New("spoof is unsupported in ", engineName) + } + if len(options.CertificatePublicKeySHA256) > 0 && (len(options.Certificate) > 0 || options.CertificatePath != "") { + return SystemTLSValidated{}, E.New("certificate_public_key_sha256 is conflict with certificate or certificate_path") + } + var minVersion uint16 + if options.MinVersion != "" { + parsed, err := ParseTLSVersion(options.MinVersion) + if err != nil { + return SystemTLSValidated{}, E.Cause(err, "parse min_version") + } + minVersion = parsed + } + var maxVersion uint16 + if options.MaxVersion != "" { + parsed, err := ParseTLSVersion(options.MaxVersion) + if err != nil { + return SystemTLSValidated{}, E.Cause(err, "parse max_version") + } + maxVersion = parsed + } + userPEM, exclusive, store, err := resolveSystemAnchors(ctx, options) + if err != nil { + return SystemTLSValidated{}, err + } + return SystemTLSValidated{ + MinVersion: minVersion, + MaxVersion: maxVersion, + UserPEM: userPEM, + Exclusive: exclusive, + Store: store, + }, nil +} + +func resolveSystemAnchors(ctx context.Context, options option.OutboundTLSOptions) ([]byte, bool, adapter.CertificateStore, error) { + if len(options.Certificate) > 0 { + return []byte(strings.Join(options.Certificate, "\n")), true, nil, nil + } + if options.CertificatePath != "" { + content, err := os.ReadFile(options.CertificatePath) + if err != nil { + return nil, false, nil, E.Cause(err, "read certificate") + } + return content, true, nil, nil + } + store := service.FromContext[adapter.CertificateStore](ctx) + if store == nil { + return nil, false, nil, nil + } + return nil, store.ExclusiveAnchors(), store, nil +} + +func verifySystemTLSPeer(roots *x509.CertPool, serverName string, timeFunc func() time.Time, peerCertificates []*x509.Certificate) error { + if len(peerCertificates) == 0 { + return E.New("no peer certificates") + } + intermediates := x509.NewCertPool() + for _, cert := range peerCertificates[1:] { + intermediates.AddCert(cert) + } + verifyOptions := x509.VerifyOptions{ + Roots: roots, + Intermediates: intermediates, + DNSName: serverName, + } + if timeFunc != nil { + verifyOptions.CurrentTime = timeFunc() + } + _, err := peerCertificates[0].Verify(verifyOptions) + return err +} diff --git a/common/tls/system_client_engine.go b/common/tls/system_client_engine.go new file mode 100644 index 0000000000..5fc89959fc --- /dev/null +++ b/common/tls/system_client_engine.go @@ -0,0 +1,108 @@ +//go:build (darwin && cgo) || windows + +package tls + +import ( + "context" + "net" + "os" + "time" + + "github.com/sagernet/sing-box/adapter" + 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/ntp" +) + +type systemTLSConfig struct { + serverName string + nextProtos []string + handshakeTimeout time.Duration + minVersion uint16 + maxVersion uint16 + insecure bool + anchorOnly bool + certificatePublicKeySHA256 [][]byte + timeFunc func() time.Time + store adapter.CertificateStore +} + +func (c *systemTLSConfig) ServerName() string { + return c.serverName +} + +func (c *systemTLSConfig) SetServerName(serverName string) { + c.serverName = serverName +} + +func (c *systemTLSConfig) NextProtos() []string { + return c.nextProtos +} + +func (c *systemTLSConfig) SetNextProtos(nextProto []string) { + c.nextProtos = append([]string(nil), nextProto...) +} + +func (c *systemTLSConfig) HandshakeTimeout() time.Duration { + return c.handshakeTimeout +} + +func (c *systemTLSConfig) SetHandshakeTimeout(timeout time.Duration) { + c.handshakeTimeout = timeout +} + +func (c *systemTLSConfig) STDConfig() (*STDConfig, error) { + return nil, E.New("STDConfig is unsupported for the system TLS engine") +} + +func (c *systemTLSConfig) Client(conn net.Conn) (Conn, error) { + return nil, os.ErrInvalid +} + +func (c *systemTLSConfig) clone() systemTLSConfig { + return systemTLSConfig{ + serverName: c.serverName, + nextProtos: append([]string(nil), c.nextProtos...), + handshakeTimeout: c.handshakeTimeout, + minVersion: c.minVersion, + maxVersion: c.maxVersion, + insecure: c.insecure, + anchorOnly: c.anchorOnly, + certificatePublicKeySHA256: append([][]byte(nil), c.certificatePublicKeySHA256...), + timeFunc: c.timeFunc, + store: c.store, + } +} + +func newSystemTLSConfig(ctx context.Context, serverAddress string, options option.OutboundTLSOptions, allowEmptyServerName bool, engineName string) (systemTLSConfig, SystemTLSValidated, error) { + validated, err := ValidateSystemTLSOptions(ctx, options, engineName) + if err != nil { + return systemTLSConfig{}, SystemTLSValidated{}, err + } + var serverName string + if options.ServerName != "" { + serverName = options.ServerName + } else if serverAddress != "" { + serverName = serverAddress + } + if serverName == "" && !options.Insecure && !allowEmptyServerName { + return systemTLSConfig{}, SystemTLSValidated{}, errMissingServerName + } + handshakeTimeout := C.TCPTimeout + if options.HandshakeTimeout > 0 { + handshakeTimeout = options.HandshakeTimeout.Build() + } + return systemTLSConfig{ + serverName: serverName, + nextProtos: append([]string(nil), options.ALPN...), + handshakeTimeout: handshakeTimeout, + minVersion: validated.MinVersion, + maxVersion: validated.MaxVersion, + insecure: options.Insecure || len(options.CertificatePublicKeySHA256) > 0, + anchorOnly: validated.Exclusive, + certificatePublicKeySHA256: append([][]byte(nil), options.CertificatePublicKeySHA256...), + timeFunc: ntp.TimeFuncFromContext(ctx), + store: validated.Store, + }, validated, nil +} diff --git a/common/tls/time_wrapper.go b/common/tls/time_wrapper.go new file mode 100644 index 0000000000..5cbecedc7a --- /dev/null +++ b/common/tls/time_wrapper.go @@ -0,0 +1,25 @@ +package tls + +import ( + "time" + + "github.com/sagernet/sing/common/ntp" +) + +type TimeServiceWrapper struct { + ntp.TimeService +} + +func (w *TimeServiceWrapper) TimeFunc() func() time.Time { + return func() time.Time { + if w.TimeService != nil { + return w.TimeService.TimeFunc()() + } else { + return time.Now() + } + } +} + +func (w *TimeServiceWrapper) Upstream() any { + return w.TimeService +} diff --git a/common/tls/utls_client.go b/common/tls/utls_client.go index 71ce8a4e0d..1cc41554fa 100644 --- a/common/tls/utls_client.go +++ b/common/tls/utls_client.go @@ -8,59 +8,120 @@ import ( "crypto/x509" "math/rand" "net" - "net/netip" "os" "strings" + "time" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/tlsfragment" + "github.com/sagernet/sing-box/common/tlsspoof" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/ntp" - utls "github.com/sagernet/utls" + utls "github.com/metacubex/utls" "golang.org/x/net/http2" ) type UTLSClientConfig struct { - config *utls.Config - id utls.ClientHelloID + ctx context.Context + config *utls.Config + serverName string + disableSNI bool + verifyServerName bool + handshakeTimeout time.Duration + id utls.ClientHelloID + fragment bool + fragmentFallbackDelay time.Duration + recordFragment bool + spoof string + spoofMethod tlsspoof.Method } -func (e *UTLSClientConfig) ServerName() string { - return e.config.ServerName +func (c *UTLSClientConfig) ServerName() string { + return c.serverName } -func (e *UTLSClientConfig) SetServerName(serverName string) { - e.config.ServerName = serverName +func (c *UTLSClientConfig) SetServerName(serverName string) { + c.serverName = serverName + if c.disableSNI { + c.config.ServerName = "" + if c.verifyServerName { + c.config.InsecureServerNameToVerify = serverName + } else { + c.config.InsecureServerNameToVerify = "" + } + return + } + c.config.ServerName = serverName } -func (e *UTLSClientConfig) NextProtos() []string { - return e.config.NextProtos +func (c *UTLSClientConfig) NextProtos() []string { + return c.config.NextProtos } -func (e *UTLSClientConfig) SetNextProtos(nextProto []string) { +func (c *UTLSClientConfig) SetNextProtos(nextProto []string) { if len(nextProto) == 1 && nextProto[0] == http2.NextProtoTLS { nextProto = append(nextProto, "http/1.1") } - e.config.NextProtos = nextProto + c.config.NextProtos = nextProto +} + +func (c *UTLSClientConfig) HandshakeTimeout() time.Duration { + return c.handshakeTimeout } -func (e *UTLSClientConfig) Config() (*STDConfig, error) { +func (c *UTLSClientConfig) SetHandshakeTimeout(timeout time.Duration) { + c.handshakeTimeout = timeout +} + +func (c *UTLSClientConfig) STDConfig() (*STDConfig, error) { return nil, E.New("unsupported usage for uTLS") } -func (e *UTLSClientConfig) Client(conn net.Conn) (Conn, error) { - return &utlsALPNWrapper{utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, e.config.NextProtos}, nil +func (c *UTLSClientConfig) Client(conn net.Conn) (Conn, error) { + if c.fragment || c.recordFragment { + conn = tf.NewConn(conn, c.ctx, c.fragment, c.recordFragment, c.fragmentFallbackDelay) + } + conn, err := applyTLSSpoof(conn, c.spoof, c.spoofMethod) + if err != nil { + return nil, err + } + return &utlsALPNWrapper{utlsConnWrapper{utls.UClient(conn, c.config.Clone(), c.id)}, c.config.NextProtos}, nil } -func (e *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) { - e.config.SessionIDGenerator = generator +func (c *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) { + c.config.SessionIDGenerator = generator } -func (e *UTLSClientConfig) Clone() Config { - return &UTLSClientConfig{ - config: e.config.Clone(), - id: e.id, +func (c *UTLSClientConfig) Clone() Config { + cloned := &UTLSClientConfig{ + ctx: c.ctx, + config: c.config.Clone(), + serverName: c.serverName, + disableSNI: c.disableSNI, + verifyServerName: c.verifyServerName, + handshakeTimeout: c.handshakeTimeout, + id: c.id, + fragment: c.fragment, + fragmentFallbackDelay: c.fragmentFallbackDelay, + recordFragment: c.recordFragment, + spoof: c.spoof, + spoofMethod: c.spoofMethod, } + cloned.SetServerName(cloned.serverName) + return cloned +} + +func (c *UTLSClientConfig) ECHConfigList() []byte { + return c.config.EncryptedClientHelloConfigList +} + +func (c *UTLSClientConfig) SetECHConfigList(EncryptedClientHelloConfigList []byte) { + c.config.EncryptedClientHelloConfigList = EncryptedClientHelloConfigList } type utlsConnWrapper struct { @@ -69,6 +130,7 @@ type utlsConnWrapper struct { func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState { state := c.Conn.ConnectionState() + //nolint:staticcheck return tls.ConnectionState{ Version: state.Version, HandshakeComplete: state.HandshakeComplete, @@ -89,6 +151,14 @@ func (c *utlsConnWrapper) Upstream() any { return c.UConn } +func (c *utlsConnWrapper) ReaderReplaceable() bool { + return true +} + +func (c *utlsConnWrapper) WriterReplaceable() bool { + return true +} + type utlsALPNWrapper struct { utlsConnWrapper nextProtocols []string @@ -114,30 +184,39 @@ func (c *utlsALPNWrapper) HandshakeContext(ctx context.Context) error { return c.UConn.HandshakeContext(ctx) } -func NewUTLSClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (*UTLSClientConfig, error) { +func NewUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions) (Config, error) { + return newUTLSClient(ctx, logger, serverAddress, options, false) +} + +func newUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions, allowEmptyServerName bool) (Config, error) { var serverName string if options.ServerName != "" { serverName = options.ServerName } else if serverAddress != "" { - if _, err := netip.ParseAddr(serverName); err != nil { - serverName = serverAddress - } + serverName = serverAddress } - if serverName == "" && !options.Insecure { - return nil, E.New("missing server_name or insecure=true") + if serverName == "" && !options.Insecure && !allowEmptyServerName { + return nil, errMissingServerName } var tlsConfig utls.Config tlsConfig.Time = ntp.TimeFuncFromContext(ctx) - if options.DisableSNI { - tlsConfig.ServerName = "127.0.0.1" - } else { - tlsConfig.ServerName = serverName - } + tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx) if options.Insecure { tlsConfig.InsecureSkipVerify = options.Insecure } else if options.DisableSNI { - return nil, E.New("disable_sni is unsupported in uTLS") + if options.Reality != nil && options.Reality.Enabled { + return nil, E.New("disable_sni is unsupported in reality") + } + } + if len(options.CertificatePublicKeySHA256) > 0 { + if len(options.Certificate) > 0 || options.CertificatePath != "" { + return nil, E.New("certificate_public_key_sha256 is conflict with certificate or certificate_path") + } + tlsConfig.InsecureSkipVerify = true + tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + return VerifyPublicKeySHA256(options.CertificatePublicKeySHA256, rawCerts) + } } if len(options.ALPN) > 0 { tlsConfig.NextProtos = options.ALPN @@ -185,11 +264,85 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out } tlsConfig.RootCAs = certPool } + var clientCertificate []byte + if len(options.ClientCertificate) > 0 { + clientCertificate = []byte(strings.Join(options.ClientCertificate, "\n")) + } else if options.ClientCertificatePath != "" { + content, err := os.ReadFile(options.ClientCertificatePath) + if err != nil { + return nil, E.Cause(err, "read client certificate") + } + clientCertificate = content + } + var clientKey []byte + if len(options.ClientKey) > 0 { + clientKey = []byte(strings.Join(options.ClientKey, "\n")) + } else if options.ClientKeyPath != "" { + content, err := os.ReadFile(options.ClientKeyPath) + if err != nil { + return nil, E.Cause(err, "read client key") + } + clientKey = content + } + if len(clientCertificate) > 0 && len(clientKey) > 0 { + keyPair, err := utls.X509KeyPair(clientCertificate, clientKey) + if err != nil { + return nil, E.Cause(err, "parse client x509 key pair") + } + tlsConfig.Certificates = []utls.Certificate{keyPair} + } else if len(clientCertificate) > 0 || len(clientKey) > 0 { + return nil, E.New("client certificate and client key must be provided together") + } + var handshakeTimeout time.Duration + if options.HandshakeTimeout > 0 { + handshakeTimeout = options.HandshakeTimeout.Build() + } else { + handshakeTimeout = C.TCPTimeout + } + spoof, spoofMethod, err := parseTLSSpoofOptions(serverName, options) + if err != nil { + return nil, err + } id, err := uTLSClientHelloID(options.UTLS.Fingerprint) if err != nil { return nil, err } - return &UTLSClientConfig{&tlsConfig, id}, nil + var config Config = &UTLSClientConfig{ + ctx: ctx, + config: &tlsConfig, + serverName: serverName, + disableSNI: options.DisableSNI, + verifyServerName: options.DisableSNI && !options.Insecure, + handshakeTimeout: handshakeTimeout, + id: id, + fragment: options.Fragment, + fragmentFallbackDelay: time.Duration(options.FragmentFallbackDelay), + recordFragment: options.RecordFragment, + spoof: spoof, + spoofMethod: spoofMethod, + } + config.SetServerName(serverName) + if options.ECH != nil && options.ECH.Enabled { + if options.Reality != nil && options.Reality.Enabled { + return nil, E.New("Reality is conflict with ECH") + } + config, err = parseECHClientConfig(ctx, config.(ECHCapableConfig), options) + if err != nil { + return nil, err + } + } + if (options.KernelRx || options.KernelTx) && !common.PtrValueOrDefault(options.Reality).Enabled { + 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 } var ( @@ -217,18 +370,10 @@ func init() { func uTLSClientHelloID(name string) (utls.ClientHelloID, error) { switch name { + case "chrome_psk", "chrome_psk_shuffle", "chrome_padding_psk_shuffle", "chrome_pq", "chrome_pq_psk": + fallthrough case "chrome", "": return utls.HelloChrome_Auto, nil - case "chrome_psk": - return utls.HelloChrome_100_PSK, nil - case "chrome_psk_shuffle": - return utls.HelloChrome_112_PSK_Shuf, nil - case "chrome_padding_psk_shuffle": - return utls.HelloChrome_114_Padding_PSK_Shuf, nil - case "chrome_pq": - return utls.HelloChrome_115_PQ, nil - case "chrome_pq_psk": - return utls.HelloChrome_115_PQ_PSK, nil case "firefox": return utls.HelloFirefox_Auto, nil case "edge": diff --git a/common/tls/utls_client_test.go b/common/tls/utls_client_test.go new file mode 100644 index 0000000000..48c1e327ec --- /dev/null +++ b/common/tls/utls_client_test.go @@ -0,0 +1,73 @@ +//go:build with_utls + +package tls + +import ( + "context" + "net" + "testing" + + tf "github.com/sagernet/sing-box/common/tlsfragment" + + utls "github.com/metacubex/utls" + "github.com/stretchr/testify/require" +) + +// Guards the wrap gate in UTLSClientConfig.Client(): tf.Conn must wrap the +// underlying connection whenever either `fragment` or `record_fragment` is +// set. Mirrors the STDClientConfig gate tests to keep both code paths in +// lockstep. + +func newUTLSClientConfigForGateTest(fragment, recordFragment bool) *UTLSClientConfig { + return &UTLSClientConfig{ + ctx: context.Background(), + config: &utls.Config{ServerName: "example.com", InsecureSkipVerify: true}, + id: utls.HelloChrome_Auto, + fragment: fragment, + recordFragment: recordFragment, + } +} + +func TestUTLSClient_Client_NoFragment_DoesNotWrap(t *testing.T) { + t.Parallel() + client, server := net.Pipe() + defer client.Close() + defer server.Close() + wrapped, err := newUTLSClientConfigForGateTest(false, false).Client(client) + require.NoError(t, err) + _, isTF := wrapped.NetConn().(*tf.Conn) + require.False(t, isTF, "no fragment flags: must not wrap with tf.Conn") +} + +func TestUTLSClient_Client_FragmentOnly_Wraps(t *testing.T) { + t.Parallel() + client, server := net.Pipe() + defer client.Close() + defer server.Close() + wrapped, err := newUTLSClientConfigForGateTest(true, false).Client(client) + require.NoError(t, err) + _, isTF := wrapped.NetConn().(*tf.Conn) + require.True(t, isTF, "fragment=true: must wrap with tf.Conn") +} + +func TestUTLSClient_Client_RecordFragmentOnly_Wraps(t *testing.T) { + t.Parallel() + client, server := net.Pipe() + defer client.Close() + defer server.Close() + wrapped, err := newUTLSClientConfigForGateTest(false, true).Client(client) + require.NoError(t, err) + _, isTF := wrapped.NetConn().(*tf.Conn) + require.True(t, isTF, "record_fragment=true: must wrap with tf.Conn") +} + +func TestUTLSClient_Client_BothFragment_Wraps(t *testing.T) { + t.Parallel() + client, server := net.Pipe() + defer client.Close() + defer server.Close() + wrapped, err := newUTLSClientConfigForGateTest(true, true).Client(client) + require.NoError(t, err) + _, isTF := wrapped.NetConn().(*tf.Conn) + require.True(t, isTF, "both fragment flags: must wrap with tf.Conn") +} diff --git a/common/tls/utls_stub.go b/common/tls/utls_stub.go index d015611aa4..67fac20983 100644 --- a/common/tls/utls_stub.go +++ b/common/tls/utls_stub.go @@ -5,14 +5,28 @@ package tls import ( "context" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" ) -func NewUTLSClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) { +func NewUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions) (Config, error) { + return newUTLSClient(ctx, logger, serverAddress, options, false) +} + +func newUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions, allowEmptyServerName bool) (Config, error) { return nil, E.New(`uTLS is not included in this build, rebuild with -tags with_utls`) } -func NewRealityClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) { - return nil, E.New(`uTLS, which is required by reality client is not included in this build, rebuild with -tags with_utls`) +func NewRealityClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions) (Config, error) { + return newRealityClient(ctx, logger, serverAddress, options, false) +} + +func newRealityClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions, allowEmptyServerName bool) (Config, error) { + return nil, E.New(`uTLS, which is required by reality is not included in this build, rebuild with -tags with_utls`) +} + +func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) { + return nil, E.New(`uTLS, which is required by reality is not included in this build, rebuild with -tags with_utls`) } diff --git a/common/tls/windows_client.go b/common/tls/windows_client.go new file mode 100644 index 0000000000..9a865c0aa1 --- /dev/null +++ b/common/tls/windows_client.go @@ -0,0 +1,848 @@ +//go:build windows + +package tls + +import ( + "context" + "crypto/tls" + "crypto/x509" + "errors" + "io" + "net" + "os" + "sync" + "sync/atomic" + "time" + + "github.com/sagernet/sing-box/common/schannel" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + N "github.com/sagernet/sing/common/network" +) + +const ( + windowsTLSEngineName = "Windows TLS engine" + handshakeReadChunkSize = 8192 + readScratchSize = 16 * 1024 + readWaitCiphertextChunkSize = 4096 +) + +type windowsClientConfig struct { + systemTLSConfig + userRoots *x509.CertPool +} + +func (c *windowsClientConfig) Clone() Config { + return &windowsClientConfig{ + systemTLSConfig: c.systemTLSConfig.clone(), + userRoots: c.userRoots, + } +} + +func newWindowsClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions, allowEmptyServerName bool) (Config, error) { + err := schannel.CheckPlatform() + if err != nil { + return nil, err + } + base, validated, err := newSystemTLSConfig(ctx, serverAddress, options, allowEmptyServerName, windowsTLSEngineName) + if err != nil { + return nil, err + } + var userRoots *x509.CertPool + if len(validated.UserPEM) > 0 { + userRoots = x509.NewCertPool() + if !userRoots.AppendCertsFromPEM(validated.UserPEM) { + return nil, E.New("parse certificate PEM") + } + } + return &windowsClientConfig{ + systemTLSConfig: base, + userRoots: userRoots, + }, nil +} + +func (c *windowsClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (Conn, error) { + deadline, hasDeadline := ctx.Deadline() + if hasDeadline { + deadlineErr := conn.SetDeadline(deadline) + if deadlineErr != nil { + return nil, E.Cause(deadlineErr, "set handshake deadline") + } + defer conn.SetDeadline(time.Time{}) + } + + client, err := schannel.NewClientContext(c.minVersion, c.maxVersion, c.serverName, c.nextProtos) + if err != nil { + return nil, err + } + + handshakeOK := false + defer func() { + if !handshakeOK { + client.Close() + } + }() + + stopCancel := installHandshakeCancel(ctx, conn) + defer stopCancel() + + scratch := make([]byte, handshakeReadChunkSize) + leftover, err := driveHandshake(ctx, conn, client, scratch) + if err != nil { + return nil, err + } + state, rawCerts, err := buildConnectionState(c.serverName, client) + if err != nil { + return nil, err + } + err = c.verifyPeerCertificates(state.PeerCertificates) + if err != nil { + return nil, err + } + if len(c.certificatePublicKeySHA256) > 0 { + err = VerifyPublicKeySHA256(c.certificatePublicKeySHA256, rawCerts) + if err != nil { + return nil, err + } + } + header, trailer, maxMessage, err := client.StreamSizes() + if err != nil { + return nil, err + } + + handshakeOK = true + tlsConn := &windowsTLSConn{ + rawConn: conn, + client: client, + state: state, + header: header, + trailer: trailer, + maxMessage: maxMessage, + cipher: leftover, + } + return tlsConn, nil +} + +func driveHandshake(ctx context.Context, conn net.Conn, client *schannel.ClientContext, scratch []byte) ([]byte, error) { + readMore := func() ([]byte, error) { + more, err := readTLSRaw(conn, scratch, true) + if err != nil { + return nil, handshakeIOError(ctx, err, "read handshake") + } + return more, nil + } + writeOut := func(data []byte) error { + _, err := conn.Write(data) + if err != nil { + return handshakeIOError(ctx, err, "write handshake") + } + return nil + } + leftover, err := driveSteps(nil, client.Step, readMore, writeOut) + if err != nil { + return nil, E.Cause(err, "tls handshake") + } + return leftover, nil +} + +func driveSteps( + initial []byte, + step func([]byte) (schannel.StepResult, error), + readMore func() ([]byte, error), + writeOut func([]byte) error, +) ([]byte, error) { + buffer := initial + for { + result, stepErr := step(buffer) + if stepErr != nil { + return nil, stepErr + } + if len(result.Output) > 0 { + writeErr := writeOut(result.Output) + if writeErr != nil { + return nil, writeErr + } + } + if result.Incomplete { + // readMore reuses scratch storage, so keep the buffered handshake + // bytes in stable memory before the next read overwrites them. + buffer = append([]byte(nil), buffer...) + more, readErr := readMore() + if readErr != nil { + return nil, readErr + } + buffer = append(buffer, more...) + continue + } + if result.Consumed > len(buffer) { + return nil, E.New("schannel: Consumed > input length") + } + buffer = buffer[result.Consumed:] + if result.Done { + return buffer, nil + } + if len(buffer) == 0 { + more, readErr := readMore() + if readErr != nil { + return nil, readErr + } + buffer = append(buffer, more...) + } + } +} + +// installHandshakeCancel unblocks an in-flight read/write by forcing an +// immediate deadline on conn when ctx is cancelled. The returned cleanup +// waits for a racing cancel to finish and clears the forced deadline. +func installHandshakeCancel(ctx context.Context, conn net.Conn) func() { + var fired atomic.Bool + done := make(chan struct{}) + stop := context.AfterFunc(ctx, func() { + defer close(done) + fired.Store(true) + _ = conn.SetDeadline(time.Now()) + }) + return func() { + if stop() { + return + } + <-done + if fired.Load() { + _ = conn.SetDeadline(time.Time{}) + } + } +} + +func handshakeIOError(ctx context.Context, err error, message string) error { + ctxErr := ctx.Err() + if ctxErr != nil && isTimeoutError(err) { + return ctxErr + } + return E.Cause(err, message) +} + +func readTLSRaw(conn net.Conn, scratch []byte, requireMore bool) ([]byte, error) { + n, err := conn.Read(scratch) + if n > 0 { + return scratch[:n], nil + } + if err != nil { + if requireMore && errors.Is(err, io.EOF) { + return nil, io.ErrUnexpectedEOF + } + return nil, err + } + return nil, io.ErrUnexpectedEOF +} + +func isTimeoutError(err error) bool { + if errors.Is(err, os.ErrDeadlineExceeded) { + return true + } + var netErr net.Error + return errors.As(err, &netErr) && netErr.Timeout() +} + +func buildConnectionState(serverName string, client *schannel.ClientContext) (tls.ConnectionState, [][]byte, error) { + version, cipherSuite, err := client.ConnectionInfo() + if err != nil { + return tls.ConnectionState{}, nil, err + } + alpn, err := client.ApplicationProtocol() + if err != nil { + return tls.ConnectionState{}, nil, err + } + rawCerts, err := client.RemoteCertificateChain() + if err != nil { + return tls.ConnectionState{}, nil, err + } + peerCertificates := make([]*x509.Certificate, 0, len(rawCerts)) + for index, der := range rawCerts { + cert, parseErr := x509.ParseCertificate(der) + if parseErr != nil { + return tls.ConnectionState{}, nil, E.Cause(parseErr, "parse peer certificate ", index) + } + peerCertificates = append(peerCertificates, cert) + } + return tls.ConnectionState{ + Version: version, + HandshakeComplete: true, + CipherSuite: cipherSuite, + NegotiatedProtocol: alpn, + ServerName: serverName, + PeerCertificates: peerCertificates, + }, rawCerts, nil +} + +func (c *windowsClientConfig) verifyPeerCertificates(peerCertificates []*x509.Certificate) error { + if c.insecure { + return nil + } + var roots *x509.CertPool + switch { + case c.userRoots != nil: + roots = c.userRoots + case c.store != nil: + roots = c.store.Pool() + } + return verifySystemTLSPeer(roots, c.serverName, c.timeFunc, peerCertificates) +} + +type windowsTLSConn struct { + rawConn net.Conn + client *schannel.ClientContext + state tls.ConnectionState + header uint32 + trailer uint32 + maxMessage uint32 + + readAccess sync.Mutex + writeAccess sync.Mutex + contextAccess sync.RWMutex + + writeState sync.Mutex + writeStateOnce sync.Once + writeReady *sync.Cond + postHandshake bool + writeActive bool + + cipher []byte + plain []byte + readScratch []byte + writeScratch []byte + readEOF bool + + deadlineAccess sync.Mutex + readDeadline time.Time + writeDeadline time.Time + closed atomic.Bool +} + +var ( + _ N.ExtendedConn = (*windowsTLSConn)(nil) + _ N.ReadWaitCreator = (*windowsTLSConn)(nil) +) + +type ( + windowsTLSAppendCipherFunc func(requireMore bool) error + windowsTLSReadRawFunc func(requireMore bool) ([]byte, error) +) + +func (c *windowsTLSConn) Read(p []byte) (int, error) { + c.readAccess.Lock() + defer c.readAccess.Unlock() + if len(p) == 0 { + return 0, nil + } + if c.isClosed() { + return 0, net.ErrClosed + } + return c.readIntoLocked(p, c.appendRaw, c.readRaw) +} + +func (c *windowsTLSConn) ReadBuffer(buffer *buf.Buffer) error { + c.readAccess.Lock() + defer c.readAccess.Unlock() + if buffer.IsFull() { + return io.ErrShortBuffer + } + if c.isClosed() { + return net.ErrClosed + } + startLen := buffer.Len() + n, err := c.readIntoLocked(buffer.FreeBytes(), c.appendRaw, c.readRaw) + buffer.Truncate(startLen + n) + return err +} + +func (c *windowsTLSConn) readIntoLocked(p []byte, appendCipher windowsTLSAppendCipherFunc, readRaw windowsTLSReadRawFunc) (int, error) { + plaintext, err := c.readPlaintextLocked(appendCipher, readRaw) + if err != nil { + return 0, err + } + n := copy(p, plaintext) + if n < len(plaintext) { + c.plain = append([]byte(nil), plaintext[n:]...) + } + return n, nil +} + +func (c *windowsTLSConn) readPlaintextLocked(appendCipher windowsTLSAppendCipherFunc, readRaw windowsTLSReadRawFunc) ([]byte, error) { + if len(c.plain) > 0 { + plaintext := c.plain + c.plain = nil + return plaintext, nil + } + if c.readEOF { + return nil, io.EOF + } + + cleanup, err := c.applyReadDeadline() + if err != nil { + return nil, err + } + defer cleanup() + + for { + if len(c.cipher) > 0 { + result, decryptErr := c.decrypt(c.cipher) + if decryptErr != nil { + return nil, decryptErr + } + if result.Expired { + c.readEOF = true + return nil, io.EOF + } + if !result.Incomplete { + plaintext := result.Plaintext + if result.Renegotiate && len(plaintext) > 0 { + plaintext = append([]byte(nil), plaintext...) + } + nextCipher := c.cipher[result.ConsumedTotal:] + if len(result.RenegotiateToken) > 0 { + nextCipher = result.RenegotiateToken + } + c.cipher = nextCipher + if len(c.cipher) == 0 { + c.cipher = nil + } + if result.Renegotiate { + postErr := c.drivePostHandshake(readRaw) + if postErr != nil { + return nil, postErr + } + } + if len(plaintext) > 0 { + return plaintext, nil + } + continue + } + } + err = appendCipher(len(c.cipher) > 0) + if err != nil { + return nil, err + } + } +} + +func (c *windowsTLSConn) drivePostHandshake(readRaw windowsTLSReadRawFunc) error { + initial := c.cipher + c.cipher = nil + err := c.beginPostHandshakeWrite() + if err != nil { + return err + } + defer c.finishPostHandshakeWrite() + c.contextAccess.Lock() + if c.client == nil { + c.contextAccess.Unlock() + return net.ErrClosed + } + writeFailed := false + readMore := func() ([]byte, error) { + more, err := readRaw(true) + if err != nil { + return nil, E.Cause(err, "tls post-handshake read") + } + return more, nil + } + writeOut := func(data []byte) error { + err := c.writePostHandshakeReplyLocked(data) + if err != nil { + writeFailed = true + return E.Cause(err, "tls post-handshake write") + } + return nil + } + leftover, err := driveSteps(initial, c.client.PostHandshake, readMore, writeOut) + c.contextAccess.Unlock() + if err != nil { + if writeFailed { + _ = c.Close() + } + return E.Cause(err, "tls post-handshake") + } + if len(leftover) > 0 { + c.cipher = leftover + } + return nil +} + +func (c *windowsTLSConn) writePostHandshakeReplyLocked(data []byte) error { + c.deadlineAccess.Lock() + deadline := c.readDeadline + c.deadlineAccess.Unlock() + cleanup, err := c.applyDeadline(deadline, c.rawConn.SetWriteDeadline) + if err != nil { + return err + } + defer cleanup() + _, err = c.rawConn.Write(data) + return err +} + +func (c *windowsTLSConn) decrypt(input []byte) (schannel.DecryptResult, error) { + c.contextAccess.RLock() + defer c.contextAccess.RUnlock() + if c.client == nil { + return schannel.DecryptResult{}, net.ErrClosed + } + return c.client.Decrypt(input) +} + +func (c *windowsTLSConn) encrypt(plaintext []byte) ([]byte, error) { + c.contextAccess.RLock() + defer c.contextAccess.RUnlock() + if c.client == nil { + return nil, net.ErrClosed + } + if c.writeScratch == nil { + c.writeScratch = make([]byte, int(c.header)+int(c.maxMessage)+int(c.trailer)) + } + return c.client.Encrypt(c.header, c.trailer, plaintext, c.writeScratch) +} + +func (c *windowsTLSConn) readRaw(requireMore bool) ([]byte, error) { + if c.readScratch == nil { + c.readScratch = make([]byte, readScratchSize) + } + return readTLSRaw(c.rawConn, c.readScratch, requireMore) +} + +func (c *windowsTLSConn) appendRaw(requireMore bool) error { + more, err := c.readRaw(requireMore) + if err != nil { + return err + } + c.cipher = append(c.cipher, more...) + return nil +} + +func (c *windowsTLSConn) Write(p []byte) (int, error) { + err := c.beginWrite() + if err != nil { + return 0, err + } + defer c.finishWrite() + if len(p) == 0 { + return 0, nil + } + if c.isClosed() { + return 0, net.ErrClosed + } + + cleanup, err := c.applyWriteDeadline() + if err != nil { + return 0, err + } + defer cleanup() + + total := 0 + chunkSize := int(c.maxMessage) + for len(p) > 0 { + chunk := p + if len(chunk) > chunkSize { + chunk = chunk[:chunkSize] + } + encrypted, encryptErr := c.encrypt(chunk) + if encryptErr != nil { + if errors.Is(encryptErr, net.ErrClosed) { + return total, net.ErrClosed + } + return total, E.Cause(encryptErr, "tls encrypt") + } + _, writeErr := c.rawConn.Write(encrypted) + if writeErr != nil { + _ = c.Close() + return total, E.Cause(writeErr, "tls write") + } + total += len(chunk) + p = p[len(chunk):] + } + return total, nil +} + +func (c *windowsTLSConn) WriteBuffer(buffer *buf.Buffer) error { + defer buffer.Release() + _, err := c.Write(buffer.Bytes()) + return err +} + +func (c *windowsTLSConn) CreateReadWaiter() (N.ReadWaiter, bool) { + rawWaiter, ok := bufio.CreateReadWaiter(c.rawConn) + if !ok { + return nil, false + } + return &windowsTLSReadWaiter{ + conn: c, + rawWaiter: rawWaiter, + }, true +} + +func (c *windowsTLSConn) Close() error { + if !c.closed.CompareAndSwap(false, true) { + return nil + } + ready := c.writeCondition() + c.writeState.Lock() + ready.Broadcast() + c.writeState.Unlock() + closeErr := c.rawConn.Close() + c.contextAccess.Lock() + if c.client != nil { + c.client.Close() + c.client = nil + } + c.contextAccess.Unlock() + return closeErr +} + +func (c *windowsTLSConn) LocalAddr() net.Addr { + return c.rawConn.LocalAddr() +} + +func (c *windowsTLSConn) RemoteAddr() net.Addr { + return c.rawConn.RemoteAddr() +} + +func (c *windowsTLSConn) SetDeadline(t time.Time) error { + c.deadlineAccess.Lock() + defer c.deadlineAccess.Unlock() + err := c.rawConn.SetDeadline(t) + if err != nil { + return err + } + c.readDeadline = t + c.writeDeadline = t + return nil +} + +func (c *windowsTLSConn) SetReadDeadline(t time.Time) error { + c.deadlineAccess.Lock() + defer c.deadlineAccess.Unlock() + err := c.rawConn.SetReadDeadline(t) + if err != nil { + return err + } + c.readDeadline = t + return nil +} + +func (c *windowsTLSConn) SetWriteDeadline(t time.Time) error { + c.deadlineAccess.Lock() + defer c.deadlineAccess.Unlock() + err := c.rawConn.SetWriteDeadline(t) + if err != nil { + return err + } + c.writeDeadline = t + return nil +} + +func (c *windowsTLSConn) NetConn() net.Conn { + return c.rawConn +} + +func (c *windowsTLSConn) HandshakeContext(ctx context.Context) error { + return nil +} + +func (c *windowsTLSConn) ConnectionState() ConnectionState { + return c.state +} + +func (c *windowsTLSConn) applyReadDeadline() (func(), error) { + c.deadlineAccess.Lock() + deadline := c.readDeadline + c.deadlineAccess.Unlock() + return c.applyDeadline(deadline, c.rawConn.SetReadDeadline) +} + +func (c *windowsTLSConn) applyWriteDeadline() (func(), error) { + c.deadlineAccess.Lock() + deadline := c.writeDeadline + c.deadlineAccess.Unlock() + return c.applyDeadline(deadline, c.rawConn.SetWriteDeadline) +} + +func (c *windowsTLSConn) applyDeadline(deadline time.Time, set func(time.Time) error) (func(), error) { + if deadline.IsZero() { + return func() {}, nil + } + if !deadline.After(time.Now()) { + return nil, os.ErrDeadlineExceeded + } + err := set(deadline) + if err != nil { + return nil, err + } + return func() { _ = set(time.Time{}) }, nil +} + +func (c *windowsTLSConn) beginWrite() error { + ready := c.writeCondition() + c.writeState.Lock() + for c.postHandshake || c.writeActive { + if c.closed.Load() { + c.writeState.Unlock() + return net.ErrClosed + } + ready.Wait() + } + c.writeActive = true + c.writeState.Unlock() + + c.writeAccess.Lock() + if c.closed.Load() { + c.writeAccess.Unlock() + c.writeState.Lock() + c.writeActive = false + ready.Broadcast() + c.writeState.Unlock() + return net.ErrClosed + } + return nil +} + +func (c *windowsTLSConn) finishWrite() { + c.writeAccess.Unlock() + ready := c.writeCondition() + c.writeState.Lock() + c.writeActive = false + ready.Broadcast() + c.writeState.Unlock() +} + +func (c *windowsTLSConn) beginPostHandshakeWrite() error { + ready := c.writeCondition() + c.writeState.Lock() + c.postHandshake = true + for c.writeActive { + if c.closed.Load() { + c.postHandshake = false + ready.Broadcast() + c.writeState.Unlock() + return net.ErrClosed + } + ready.Wait() + } + c.writeActive = true + c.writeState.Unlock() + + c.writeAccess.Lock() + if c.closed.Load() { + c.writeAccess.Unlock() + c.writeState.Lock() + c.writeActive = false + c.postHandshake = false + ready.Broadcast() + c.writeState.Unlock() + return net.ErrClosed + } + return nil +} + +func (c *windowsTLSConn) finishPostHandshakeWrite() { + c.writeAccess.Unlock() + ready := c.writeCondition() + c.writeState.Lock() + c.writeActive = false + c.postHandshake = false + ready.Broadcast() + c.writeState.Unlock() +} + +func (c *windowsTLSConn) writeCondition() *sync.Cond { + c.writeStateOnce.Do(func() { + c.writeReady = sync.NewCond(&c.writeState) + }) + return c.writeReady +} + +func (c *windowsTLSConn) isClosed() bool { + return c.closed.Load() +} + +type windowsTLSReadWaiter struct { + conn *windowsTLSConn + rawWaiter N.ReadWaiter + options N.ReadWaitOptions +} + +var _ N.ReadWaiter = (*windowsTLSReadWaiter)(nil) + +func (w *windowsTLSReadWaiter) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy bool) { + w.options = options + w.rawWaiter.InitializeReadWaiter(N.ReadWaitOptions{ + MTU: readWaitCiphertextChunkSize, + }) + return false +} + +func (w *windowsTLSReadWaiter) WaitReadBuffer() (*buf.Buffer, error) { + c := w.conn + c.readAccess.Lock() + defer c.readAccess.Unlock() + if c.isClosed() { + return nil, net.ErrClosed + } + plaintext, err := c.readPlaintextLocked(w.appendRaw, w.readRaw) + if err != nil { + return nil, err + } + buffer := w.options.NewBuffer() + n, writeErr := buffer.Write(plaintext) + if writeErr != nil { + buffer.Release() + return nil, writeErr + } + if n == 0 { + buffer.Release() + return nil, io.ErrShortBuffer + } + if n < len(plaintext) { + c.plain = append([]byte(nil), plaintext[n:]...) + } + w.options.PostReturn(buffer) + return buffer, nil +} + +func (w *windowsTLSReadWaiter) appendRaw(requireMore bool) error { + rawBuffer, err := w.readRawBuffer(requireMore) + if err != nil { + return err + } + w.conn.cipher = append(w.conn.cipher, rawBuffer.Bytes()...) + rawBuffer.Release() + return nil +} + +func (w *windowsTLSReadWaiter) readRaw(requireMore bool) ([]byte, error) { + rawBuffer, err := w.readRawBuffer(requireMore) + if err != nil { + return nil, err + } + data := append([]byte(nil), rawBuffer.Bytes()...) + rawBuffer.Release() + return data, nil +} + +func (w *windowsTLSReadWaiter) readRawBuffer(requireMore bool) (*buf.Buffer, error) { + rawBuffer, err := w.rawWaiter.WaitReadBuffer() + if err != nil { + if requireMore && errors.Is(err, io.EOF) { + return nil, io.ErrUnexpectedEOF + } + return nil, err + } + if rawBuffer == nil || rawBuffer.Len() == 0 { + if rawBuffer != nil { + rawBuffer.Release() + } + return nil, io.ErrUnexpectedEOF + } + return rawBuffer, nil +} diff --git a/common/tls/windows_client_stub.go b/common/tls/windows_client_stub.go new file mode 100644 index 0000000000..7ad506a725 --- /dev/null +++ b/common/tls/windows_client_stub.go @@ -0,0 +1,15 @@ +//go:build !windows + +package tls + +import ( + "context" + + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" +) + +func newWindowsClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions, allowEmptyServerName bool) (Config, error) { + return nil, E.New("Windows TLS engine is not available on non-Windows platforms") +} diff --git a/common/tls/windows_client_test.go b/common/tls/windows_client_test.go new file mode 100644 index 0000000000..755aea59de --- /dev/null +++ b/common/tls/windows_client_test.go @@ -0,0 +1,2505 @@ +//go:build windows + +package tls + +import ( + "bytes" + "context" + "crypto/sha256" + stdtls "crypto/tls" + "crypto/x509" + "errors" + "io" + "net" + "os" + "strings" + "sync" + "testing" + "time" + + "github.com/sagernet/sing-box/common/schannel" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + "github.com/sagernet/sing/common/json/badoption" + "github.com/sagernet/sing/common/logger" + N "github.com/sagernet/sing/common/network" +) + +const windowsTLSTestTimeout = 5 * time.Second + +var ( + _ N.ExtendedConn = (*windowsTLSConn)(nil) + _ N.ReadWaitCreator = (*windowsTLSConn)(nil) +) + +func newTestWindowsTLSConn(rawConn net.Conn) *windowsTLSConn { + return &windowsTLSConn{rawConn: rawConn} +} + +// writePostHandshakeReply wraps writePostHandshakeReplyLocked with the +// writeAccess locking and auto-close behavior that drivePostHandshake +// composes from beginPostHandshakeWrite/finishPostHandshakeWrite plus the +// writeFailed → Close branch. +// Kept here as a test seam. +func (c *windowsTLSConn) writePostHandshakeReply(data []byte) error { + c.writeAccess.Lock() + defer c.writeAccess.Unlock() + err := c.writePostHandshakeReplyLocked(data) + if err != nil { + _ = c.Close() + } + return err +} + +type windowsTLSServerResult struct { + state stdtls.ConnectionState + err error +} + +type windowsTestDeadlineConn struct { + access sync.Mutex + readCalled chan struct{} + writeCalled chan struct{} + readCalledOnce sync.Once + writeCalledOnce sync.Once + readDeadline time.Time + writeDeadline time.Time + readDeadlines []time.Time + writeDeadlines []time.Time +} + +type windowsTestWriteGateConn struct { + writeCalled chan struct{} + releaseWrite chan struct{} +} + +type windowsTestIOConn struct { + access sync.Mutex + readErr error + writeErr error + writeN int + writeCalls int + closed bool +} + +func (c *windowsTestDeadlineConn) Read(_ []byte) (int, error) { + if c.readCalled != nil { + c.readCalledOnce.Do(func() { + close(c.readCalled) + }) + } + for { + c.access.Lock() + deadline := c.readDeadline + c.access.Unlock() + if deadline.IsZero() { + time.Sleep(5 * time.Millisecond) + continue + } + if !deadline.After(time.Now()) { + return 0, os.ErrDeadlineExceeded + } + time.Sleep(time.Until(deadline)) + return 0, os.ErrDeadlineExceeded + } +} + +func (c *windowsTestDeadlineConn) Write(_ []byte) (int, error) { + if c.writeCalled != nil { + c.writeCalledOnce.Do(func() { + close(c.writeCalled) + }) + } + for { + c.access.Lock() + deadline := c.writeDeadline + c.access.Unlock() + if deadline.IsZero() { + time.Sleep(5 * time.Millisecond) + continue + } + if !deadline.After(time.Now()) { + return 0, os.ErrDeadlineExceeded + } + time.Sleep(time.Until(deadline)) + return 0, os.ErrDeadlineExceeded + } +} + +func (c *windowsTestDeadlineConn) Close() error { + return nil +} + +func (c *windowsTestDeadlineConn) LocalAddr() net.Addr { + return windowsTestAddr("local") +} + +func (c *windowsTestDeadlineConn) RemoteAddr() net.Addr { + return windowsTestAddr("remote") +} + +func (c *windowsTestDeadlineConn) SetDeadline(t time.Time) error { + c.access.Lock() + c.readDeadline = t + c.writeDeadline = t + c.readDeadlines = append(c.readDeadlines, t) + c.writeDeadlines = append(c.writeDeadlines, t) + c.access.Unlock() + return nil +} + +func (c *windowsTestDeadlineConn) SetReadDeadline(t time.Time) error { + c.access.Lock() + c.readDeadline = t + c.readDeadlines = append(c.readDeadlines, t) + c.access.Unlock() + return nil +} + +func (c *windowsTestDeadlineConn) SetWriteDeadline(t time.Time) error { + c.access.Lock() + c.writeDeadline = t + c.writeDeadlines = append(c.writeDeadlines, t) + c.access.Unlock() + return nil +} + +func (c *windowsTestDeadlineConn) recordedWriteDeadlines() []time.Time { + c.access.Lock() + defer c.access.Unlock() + return append([]time.Time(nil), c.writeDeadlines...) +} + +func (c *windowsTestDeadlineConn) recordedReadDeadlines() []time.Time { + c.access.Lock() + defer c.access.Unlock() + return append([]time.Time(nil), c.readDeadlines...) +} + +func (c *windowsTestWriteGateConn) Read(_ []byte) (int, error) { + return 0, io.EOF +} + +func (c *windowsTestWriteGateConn) Write(p []byte) (int, error) { + close(c.writeCalled) + <-c.releaseWrite + return len(p), nil +} + +func (c *windowsTestWriteGateConn) Close() error { + return nil +} + +func (c *windowsTestWriteGateConn) LocalAddr() net.Addr { + return windowsTestAddr("local") +} + +func (c *windowsTestWriteGateConn) RemoteAddr() net.Addr { + return windowsTestAddr("remote") +} + +func (c *windowsTestWriteGateConn) SetDeadline(time.Time) error { + return nil +} + +func (c *windowsTestWriteGateConn) SetReadDeadline(time.Time) error { + return nil +} + +func (c *windowsTestWriteGateConn) SetWriteDeadline(time.Time) error { + return nil +} + +func (c *windowsTestIOConn) Read(_ []byte) (int, error) { + return 0, c.readErr +} + +func (c *windowsTestIOConn) Write(p []byte) (int, error) { + c.access.Lock() + defer c.access.Unlock() + c.writeCalls++ + if c.writeErr == nil { + return len(p), nil + } + n := c.writeN + if n <= 0 || n > len(p) { + n = 0 + } + return n, c.writeErr +} + +func (c *windowsTestIOConn) Close() error { + c.access.Lock() + c.closed = true + c.access.Unlock() + return nil +} + +func (c *windowsTestIOConn) LocalAddr() net.Addr { + return windowsTestAddr("local") +} + +func (c *windowsTestIOConn) RemoteAddr() net.Addr { + return windowsTestAddr("remote") +} + +func (c *windowsTestIOConn) SetDeadline(time.Time) error { + return nil +} + +func (c *windowsTestIOConn) SetReadDeadline(time.Time) error { + return nil +} + +func (c *windowsTestIOConn) SetWriteDeadline(time.Time) error { + return nil +} + +func (c *windowsTestIOConn) isClosed() bool { + c.access.Lock() + defer c.access.Unlock() + return c.closed +} + +func (c *windowsTestIOConn) totalWriteCalls() int { + c.access.Lock() + defer c.access.Unlock() + return c.writeCalls +} + +type windowsTestAddr string + +func (a windowsTestAddr) Network() string { + return "test" +} + +func (a windowsTestAddr) String() string { + return string(a) +} + +type windowsOpaqueConn struct { + net.Conn +} + +func TestWindowsClientHandshakeTLS12(t *testing.T) { + serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost") + serverResult, serverAddress := startWindowsTLSTestServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS12, + MaxVersion: stdtls.VersionTLS12, + NextProtos: []string{"h2"}, + }) + + clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + MinVersion: "1.2", + MaxVersion: "1.2", + ALPN: badoption.Listable[string]{"h2"}, + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err != nil { + t.Fatal(err) + } + defer clientConn.Close() + + clientState := clientConn.ConnectionState() + if clientState.Version != stdtls.VersionTLS12 { + t.Fatalf("unexpected negotiated version: %x", clientState.Version) + } + if clientState.NegotiatedProtocol != "h2" { + t.Fatalf("unexpected negotiated protocol: %q", clientState.NegotiatedProtocol) + } + if !clientState.HandshakeComplete { + t.Fatal("HandshakeComplete is false") + } + if len(clientState.PeerCertificates) == 0 { + t.Fatal("no peer certificates") + } + + result := <-serverResult + if result.err != nil { + t.Fatal(result.err) + } + if result.state.Version != stdtls.VersionTLS12 { + t.Fatalf("server negotiated unexpected version: %x", result.state.Version) + } + if result.state.NegotiatedProtocol != "h2" { + t.Fatalf("server negotiated unexpected protocol: %q", result.state.NegotiatedProtocol) + } +} + +func TestWindowsClientHandshakeWrappedConn(t *testing.T) { + serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost") + serverResult, serverAddress := startWindowsTLSTestServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS12, + MaxVersion: stdtls.VersionTLS12, + }) + + ctx, cancel := context.WithTimeout(context.Background(), windowsTLSTestTimeout) + t.Cleanup(cancel) + + clientConfig, err := NewClientWithOptions(ClientOptions{ + Context: ctx, + Logger: logger.NOP(), + Options: option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + MinVersion: "1.2", + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }, + }) + if err != nil { + t.Fatal(err) + } + rawConn, err := net.DialTimeout(N.NetworkTCP, serverAddress, windowsTLSTestTimeout) + if err != nil { + t.Fatal(err) + } + tlsConn, err := ClientHandshake(ctx, windowsOpaqueConn{Conn: rawConn}, clientConfig) + if err != nil { + rawConn.Close() + t.Fatal(err) + } + _ = tlsConn.Close() + + result := <-serverResult + if result.err != nil { + t.Fatal(result.err) + } + if result.state.Version != stdtls.VersionTLS12 { + t.Fatalf("server negotiated unexpected version: %x", result.state.Version) + } +} + +func TestWindowsClientHandshakeTLS13(t *testing.T) { + serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost") + serverResult, serverAddress := startWindowsTLSTestServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS13, + MaxVersion: stdtls.VersionTLS13, + NextProtos: []string{"h2"}, + }) + + clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + MinVersion: "1.3", + ALPN: badoption.Listable[string]{"h2"}, + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err != nil { + t.Fatal(err) + } + defer clientConn.Close() + + clientState := clientConn.ConnectionState() + if clientState.Version != stdtls.VersionTLS13 { + t.Fatalf("expected TLS 1.3, got %x", clientState.Version) + } + if clientState.NegotiatedProtocol != "h2" { + t.Fatalf("expected negotiated protocol h2, got %q", clientState.NegotiatedProtocol) + } + + result := <-serverResult + if result.err != nil { + t.Fatal(result.err) + } + if result.state.Version != stdtls.VersionTLS13 { + t.Fatalf("server negotiated unexpected version: %x", result.state.Version) + } +} + +func TestWindowsClientHandshakeALPNNoOverlap(t *testing.T) { + serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost") + _, serverAddress := startWindowsTLSTestServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS12, + MaxVersion: stdtls.VersionTLS12, + NextProtos: []string{"http/1.1"}, + }) + + clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + MinVersion: "1.2", + MaxVersion: "1.2", + ALPN: badoption.Listable[string]{"h2"}, + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + // Go's TLS server returns a TLS alert when the client advertises ALPN but + // the server has no overlap. The handshake fails. + if err == nil { + _ = clientConn.Close() + t.Fatal("expected handshake to fail with no ALPN overlap") + } +} + +func TestWindowsClientHandshakeMultipleALPN(t *testing.T) { + serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost") + _, serverAddress := startWindowsTLSTestServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS12, + NextProtos: []string{"h2", "http/1.1"}, + }) + + clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + MinVersion: "1.2", + ALPN: badoption.Listable[string]{"spdy/3", "h2"}, + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err != nil { + t.Fatal(err) + } + defer clientConn.Close() + + // Schannel follows the standard selection: first protocol offered by + // the client that the server also supports. Here: spdy/3 is not in the + // server list but h2 is, so h2 wins. + if got := clientConn.ConnectionState().NegotiatedProtocol; got != "h2" { + t.Fatalf("expected h2, got %q", got) + } +} + +func TestWindowsClientHandshakeRejectsVersionMismatch(t *testing.T) { + serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost") + serverResult, serverAddress := startWindowsTLSTestServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS13, + MaxVersion: stdtls.VersionTLS13, + }) + + clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + MaxVersion: "1.2", + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err == nil { + clientConn.Close() + t.Fatal("expected version mismatch handshake to fail") + } + + result := <-serverResult + if result.err == nil { + t.Fatal("expected server handshake to fail on version mismatch") + } +} + +func TestWindowsClientHandshakeRejectsServerNameMismatch(t *testing.T) { + serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost") + _, serverAddress := startWindowsTLSTestServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + }) + + clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "example.com", + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err == nil { + clientConn.Close() + t.Fatal("expected server name mismatch handshake to fail") + } +} + +func TestWindowsClientHandshakeRejectsUntrustedCA(t *testing.T) { + serverCertificate, _ := newWindowsTestCertificate(t, "localhost") + _, serverAddress := startWindowsTLSTestServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + }) + + clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + }) + if err == nil { + clientConn.Close() + t.Fatal("expected untrusted CA handshake to fail") + } +} + +func TestWindowsClientHandshakeInsecureSkipsValidation(t *testing.T) { + serverCertificate, _ := newWindowsTestCertificate(t, "localhost") + _, serverAddress := startWindowsTLSTestServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + }) + + // Server name mismatch but insecure=true → handshake succeeds. + clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "example.com", + Insecure: true, + }) + if err != nil { + t.Fatal(err) + } + defer clientConn.Close() + + if !clientConn.ConnectionState().HandshakeComplete { + t.Fatal("expected handshake to complete with insecure=true") + } +} + +func TestWindowsClientHandshakeHonorsPublicKeyPinSuccess(t *testing.T) { + serverCertificate, _ := newWindowsTestCertificate(t, "localhost") + pin := publicKeyPin(t, serverCertificate.Leaf) + _, serverAddress := startWindowsTLSTestServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + }) + + clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + CertificatePublicKeySHA256: [][]byte{pin}, + }) + if err != nil { + t.Fatal(err) + } + defer clientConn.Close() +} + +func TestWindowsClientHandshakeHonorsPublicKeyPinFailure(t *testing.T) { + serverCertificate, _ := newWindowsTestCertificate(t, "localhost") + wrongPin := sha256.Sum256([]byte("not the public key")) + _, serverAddress := startWindowsTLSTestServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + }) + + clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + CertificatePublicKeySHA256: [][]byte{wrongPin[:]}, + }) + if err == nil { + clientConn.Close() + t.Fatal("expected public-key pin mismatch to fail") + } +} + +func TestWindowsClientHandshakeContextCancellation(t *testing.T) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { listener.Close() }) + + clientHelloRead := make(chan struct{}, 1) + serverDone := make(chan struct{}) + defer close(serverDone) + go func() { + c, acceptErr := listener.Accept() + if acceptErr != nil { + return + } + defer c.Close() + buffer := make([]byte, 8192) + n, readErr := c.Read(buffer) + if n > 0 { + clientHelloRead <- struct{}{} + } + if readErr != nil { + return + } + <-serverDone + }() + + _, serverCertificatePEM := newWindowsTestCertificate(t, "localhost") + + ctx, cancel := context.WithCancel(context.Background()) + clientConfig, err := NewClientWithOptions(ClientOptions{ + Context: ctx, + Logger: logger.NOP(), + Options: option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }, + }) + if err != nil { + t.Fatal(err) + } + + conn, err := net.DialTimeout("tcp", listener.Addr().String(), windowsTLSTestTimeout) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + + handshakeDone := make(chan error, 1) + go func() { + _, err := ClientHandshake(ctx, conn, clientConfig) + handshakeDone <- err + }() + + select { + case <-clientHelloRead: + case <-time.After(2 * time.Second): + t.Fatal("server did not receive the client hello") + } + + cancel() + + select { + case err := <-handshakeDone: + if !errors.Is(err, context.Canceled) { + t.Fatalf("expected context.Canceled, got %v", err) + } + case <-time.After(2 * time.Second): + t.Fatal("handshake did not return after cancellation") + } +} + +func TestWindowsClientHandshakeTimeout(t *testing.T) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { listener.Close() }) + + // Accept but never respond. + go func() { + c, acceptErr := listener.Accept() + if acceptErr != nil { + return + } + defer c.Close() + time.Sleep(3 * time.Second) + }() + + _, serverCertificatePEM := newWindowsTestCertificate(t, "localhost") + + ctx, cancel := context.WithTimeout(context.Background(), windowsTLSTestTimeout) + defer cancel() + + clientConfig, err := NewClientWithOptions(ClientOptions{ + Context: ctx, + Logger: logger.NOP(), + Options: option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + HandshakeTimeout: badoption.Duration(300 * time.Millisecond), + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }, + }) + if err != nil { + t.Fatal(err) + } + + conn, err := net.DialTimeout("tcp", listener.Addr().String(), windowsTLSTestTimeout) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + + start := time.Now() + _, err = ClientHandshake(ctx, conn, clientConfig) + elapsed := time.Since(start) + if err == nil { + t.Fatal("expected handshake to time out") + } + if elapsed > 2*time.Second { + t.Fatalf("handshake took %v, expected ~300ms timeout", elapsed) + } +} + +func TestWindowsClientRoundtrip(t *testing.T) { + clientConn, serverDone := startWindowsEchoServer(t, stdtls.VersionTLS12) + defer clientConn.Close() + + _, err := clientConn.Write([]byte("ping")) + if err != nil { + t.Fatalf("write: %v", err) + } + reply := make([]byte, 4) + _, err = io.ReadFull(clientConn, reply) + if err != nil { + t.Fatalf("read: %v", err) + } + if string(reply) != "ping" { + t.Fatalf("unexpected reply: %q", string(reply)) + } + + clientConn.Close() + <-serverDone +} + +func TestWindowsClientRoundtripTLS13(t *testing.T) { + clientConn, serverDone := startWindowsEchoServer(t, stdtls.VersionTLS13) + defer clientConn.Close() + + payload := []byte("hello tls 1.3") + _, err := clientConn.Write(payload) + if err != nil { + t.Fatalf("write: %v", err) + } + reply := make([]byte, len(payload)) + _, err = io.ReadFull(clientConn, reply) + if err != nil { + t.Fatalf("read: %v", err) + } + if !bytes.Equal(payload, reply) { + t.Fatalf("unexpected reply: %q", string(reply)) + } + + clientConn.Close() + <-serverDone +} + +func TestWindowsClientReadBuffer(t *testing.T) { + payload := []byte("windows tls read buffer payload") + clientConn, serverErr := startWindowsPayloadServer(t, stdtls.VersionTLS12, payload) + defer clientConn.Close() + + const ( + frontHeadroom = 8 + rearHeadroom = 8 + ) + buffer := buf.NewSize(len(payload) + frontHeadroom + rearHeadroom) + defer buffer.Release() + buffer.Resize(frontHeadroom, 0) + buffer.Reserve(rearHeadroom) + + err := clientConn.ReadBuffer(buffer) + if err != nil { + t.Fatalf("ReadBuffer: %v", err) + } + if buffer.Start() != frontHeadroom { + t.Fatalf("expected front headroom %d, got %d", frontHeadroom, buffer.Start()) + } + if !bytes.Equal(buffer.Bytes(), payload) { + t.Fatalf("unexpected payload: %q", string(buffer.Bytes())) + } + if err = <-serverErr; err != nil { + t.Fatal(err) + } +} + +func TestWindowsClientWriteBuffer(t *testing.T) { + clientConn, serverDone := startWindowsEchoEngineServer(t, stdtls.VersionTLS12) + defer clientConn.Close() + + payload := []byte("windows tls write buffer payload") + buffer := buf.NewSize(len(payload)) + _, err := buffer.Write(payload) + if err != nil { + t.Fatal(err) + } + + err = clientConn.WriteBuffer(buffer) + if err != nil { + t.Fatalf("WriteBuffer: %v", err) + } + if buffer.RawCap() != 0 { + t.Fatalf("expected WriteBuffer to release buffer, raw cap %d", buffer.RawCap()) + } + + reply := make([]byte, len(payload)) + _, err = io.ReadFull(clientConn, reply) + if err != nil { + t.Fatalf("read echo: %v", err) + } + if !bytes.Equal(reply, payload) { + t.Fatalf("unexpected echo: %q", string(reply)) + } + + clientConn.Close() + <-serverDone +} + +func TestWindowsClientCreateReadWaiter(t *testing.T) { + payload := []byte("windows tls read waiter payload") + clientConn, serverErr := startWindowsPayloadServer(t, stdtls.VersionTLS12, payload) + defer clientConn.Close() + + readWaiter, created := bufio.CreateReadWaiter(clientConn) + if !created { + t.Fatal("expected read waiter") + } + readWaiter.InitializeReadWaiter(N.ReadWaitOptions{ + FrontHeadroom: 7, + RearHeadroom: 5, + MTU: len(payload), + }) + + buffer, err := readWaiter.WaitReadBuffer() + if err != nil { + t.Fatalf("WaitReadBuffer: %v", err) + } + defer buffer.Release() + if buffer.Start() != 7 { + t.Fatalf("expected front headroom 7, got %d", buffer.Start()) + } + if buffer.FreeLen() < 5 { + t.Fatalf("expected rear headroom at least 5, got %d", buffer.FreeLen()) + } + if !bytes.Equal(buffer.Bytes(), payload) { + t.Fatalf("unexpected payload: %q", string(buffer.Bytes())) + } + if err = <-serverErr; err != nil { + t.Fatal(err) + } +} + +func TestWindowsClientCreateReadWaiterFallback(t *testing.T) { + tlsConn := newTestWindowsTLSConn(&windowsTestIOConn{}) + _, created := tlsConn.CreateReadWaiter() + if created { + t.Fatal("expected read waiter fallback") + } +} + +func TestWindowsClientTLS13PostHandshakeConcurrentWrite(t *testing.T) { + serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost") + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { listener.Close() }) + + const payloadSize = 4 << 20 + const prefixSize = 32 << 10 + reply := []byte("tls13 post-handshake reply") + + serverErr := make(chan error, 1) + prefixRead := make(chan struct{}) + go func() { + conn, acceptErr := listener.Accept() + if acceptErr != nil { + serverErr <- acceptErr + return + } + defer conn.Close() + + tlsConn := stdtls.Server(conn, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS13, + MaxVersion: stdtls.VersionTLS13, + }) + defer tlsConn.Close() + + err := tlsConn.SetDeadline(time.Now().Add(2 * windowsTLSTestTimeout)) + if err != nil { + serverErr <- err + return + } + err = tlsConn.Handshake() + if err != nil { + serverErr <- err + return + } + prefix := make([]byte, prefixSize) + _, err = io.ReadFull(tlsConn, prefix) + if err != nil { + serverErr <- err + return + } + close(prefixRead) + _, err = tlsConn.Write(reply) + if err != nil { + serverErr <- err + return + } + _, err = io.Copy(io.Discard, io.LimitReader(tlsConn, int64(payloadSize-prefixSize))) + if err != nil { + serverErr <- err + return + } + serverErr <- nil + }() + + clientConn, err := newWindowsTestClientConn(t, listener.Addr().String(), option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + MinVersion: "1.3", + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err != nil { + t.Fatal(err) + } + defer clientConn.Close() + + payload := make([]byte, payloadSize) + for index := range payload { + payload[index] = byte(index % 251) + } + writeDone := make(chan error, 1) + go func() { + _, err := clientConn.Write(payload) + writeDone <- err + }() + + select { + case <-prefixRead: + case <-time.After(2 * time.Second): + t.Fatal("server did not observe the client write") + } + + replyBuffer := make([]byte, len(reply)) + _, err = io.ReadFull(clientConn, replyBuffer) + if err != nil { + t.Fatalf("read: %v", err) + } + if !bytes.Equal(reply, replyBuffer) { + t.Fatalf("unexpected reply: %q", string(replyBuffer)) + } + + writeErr := <-writeDone + if writeErr != nil { + t.Fatalf("write: %v", writeErr) + } + serverErrValue := <-serverErr + if serverErrValue != nil { + t.Fatal(serverErrValue) + } +} + +func TestWindowsClientLargeMessage(t *testing.T) { + clientConn, serverDone := startWindowsEchoServer(t, stdtls.VersionTLS12) + defer clientConn.Close() + + // 1 MiB exercises multiple TLS records and the chunking logic. + // Writes must run concurrently with reads to avoid TCP-buffer deadlock. + payload := make([]byte, 1<<20) + for index := range payload { + payload[index] = byte(index % 251) + } + writeErr := make(chan error, 1) + go func() { + _, err := clientConn.Write(payload) + writeErr <- err + }() + + reply := make([]byte, len(payload)) + _, err := io.ReadFull(clientConn, reply) + if err != nil { + t.Fatalf("read: %v", err) + } + writeResult := <-writeErr + if writeResult != nil { + t.Fatalf("write: %v", writeResult) + } + if !bytes.Equal(payload, reply) { + t.Fatal("payload mismatch after round-trip") + } + + clientConn.Close() + <-serverDone +} + +func TestWindowsClientFullDuplexLargePayload(t *testing.T) { + serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost") + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { listener.Close() }) + + const payloadSize = 2 << 20 + clientPayload := make([]byte, payloadSize) + serverPayload := make([]byte, payloadSize) + for index := range clientPayload { + clientPayload[index] = byte(index % 251) + serverPayload[index] = byte((index + 97) % 251) + } + + serverErr := make(chan error, 1) + go func() { + conn, acceptErr := listener.Accept() + if acceptErr != nil { + serverErr <- acceptErr + return + } + defer conn.Close() + + tlsConn := stdtls.Server(conn, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS12, + MaxVersion: stdtls.VersionTLS12, + }) + defer tlsConn.Close() + err := tlsConn.SetDeadline(time.Now().Add(2 * windowsTLSTestTimeout)) + if err != nil { + serverErr <- err + return + } + err = tlsConn.Handshake() + if err != nil { + serverErr <- err + return + } + + readDone := make(chan error, 1) + writeDone := make(chan error, 1) + go func() { + received := make([]byte, len(clientPayload)) + _, readErr := io.ReadFull(tlsConn, received) + if readErr == nil && !bytes.Equal(received, clientPayload) { + readErr = errors.New("client payload mismatch") + } + readDone <- readErr + }() + go func() { + _, writeErr := tlsConn.Write(serverPayload) + writeDone <- writeErr + }() + if readErr := <-readDone; readErr != nil { + serverErr <- readErr + return + } + serverErr <- <-writeDone + }() + + clientConn, err := newWindowsTestEngineConn(t, listener.Addr().String(), option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + MinVersion: "1.2", + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err != nil { + t.Fatal(err) + } + defer clientConn.Close() + + writeDone := make(chan error, 1) + go func() { + n, writeErr := clientConn.Write(clientPayload) + if writeErr == nil && n != len(clientPayload) { + writeErr = io.ErrShortWrite + } + writeDone <- writeErr + }() + + reply := make([]byte, len(serverPayload)) + _, err = io.ReadFull(clientConn, reply) + if err != nil { + t.Fatalf("read: %v", err) + } + if !bytes.Equal(reply, serverPayload) { + t.Fatal("server payload mismatch") + } + if writeErr := <-writeDone; writeErr != nil { + t.Fatalf("write: %v", writeErr) + } + if err = <-serverErr; err != nil { + t.Fatal(err) + } +} + +func TestWindowsClientMultipleRoundtrips(t *testing.T) { + clientConn, serverDone := startWindowsEchoServer(t, stdtls.VersionTLS12) + defer clientConn.Close() + + for i := range 100 { + payload := []byte("msg" + string(rune('A'+(i%26)))) + _, err := clientConn.Write(payload) + if err != nil { + t.Fatalf("write %d: %v", i, err) + } + reply := make([]byte, len(payload)) + _, err = io.ReadFull(clientConn, reply) + if err != nil { + t.Fatalf("read %d: %v", i, err) + } + if !bytes.Equal(payload, reply) { + t.Fatalf("iteration %d: expected %q got %q", i, payload, reply) + } + } + + clientConn.Close() + <-serverDone +} + +func TestWindowsClientConcurrentReadWrite(t *testing.T) { + clientConn, serverDone := startWindowsEchoServer(t, stdtls.VersionTLS12) + defer clientConn.Close() + + const messageCount = 200 + const messageSize = 64 + payloads := make([][]byte, messageCount) + for i := range payloads { + buffer := make([]byte, messageSize) + for j := range buffer { + buffer[j] = byte(i + j) + } + payloads[i] = buffer + } + + readErr := make(chan error, 1) + readBack := make(chan []byte, messageCount) + go func() { + for range messageCount { + reply := make([]byte, messageSize) + _, err := io.ReadFull(clientConn, reply) + if err != nil { + readErr <- err + return + } + readBack <- reply + } + readErr <- nil + }() + + for i, payload := range payloads { + _, err := clientConn.Write(payload) + if err != nil { + t.Fatalf("write %d: %v", i, err) + } + } + + readResult := <-readErr + if readResult != nil { + t.Fatal(readResult) + } + for i := range messageCount { + got := <-readBack + if !bytes.Equal(payloads[i], got) { + t.Fatalf("iteration %d: payload mismatch", i) + } + } + + clientConn.Close() + <-serverDone +} + +func TestWindowsClientServerCloseReturnsEOF(t *testing.T) { + serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost") + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { listener.Close() }) + + done := make(chan struct{}) + go func() { + defer close(done) + conn, acceptErr := listener.Accept() + if acceptErr != nil { + return + } + defer conn.Close() + tlsConn := stdtls.Server(conn, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS12, + MaxVersion: stdtls.VersionTLS12, + }) + _ = tlsConn.Handshake() + // Send close_notify then exit. + _ = tlsConn.Close() + }() + + clientConn, err := newWindowsTestClientConn(t, listener.Addr().String(), option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + MinVersion: "1.2", + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err != nil { + t.Fatal(err) + } + defer clientConn.Close() + + buffer := make([]byte, 16) + _, err = clientConn.Read(buffer) + if !errors.Is(err, io.EOF) { + t.Fatalf("expected io.EOF, got %v", err) + } + <-done +} + +func TestWindowsClientCloseUnblocksRead(t *testing.T) { + serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost") + _, serverAddress := startWindowsTLSSilentServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS12, + }) + + clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + MinVersion: "1.2", + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err != nil { + t.Fatal(err) + } + defer clientConn.Close() + + readDone := make(chan error, 1) + go func() { + buffer := make([]byte, 16) + _, err := clientConn.Read(buffer) + readDone <- err + }() + + time.Sleep(100 * time.Millisecond) + clientConn.Close() + + select { + case err := <-readDone: + if err == nil { + t.Fatal("expected Read to return an error after Close") + } + case <-time.After(2 * time.Second): + t.Fatal("Read did not return within 2s after Close") + } +} + +func TestWindowsClientReadAfterCloseReturnsError(t *testing.T) { + clientConn, serverDone := startWindowsEchoServer(t, stdtls.VersionTLS12) + clientConn.Close() + <-serverDone + + buffer := make([]byte, 16) + _, err := clientConn.Read(buffer) + if err == nil { + t.Fatal("expected Read after Close to return error") + } +} + +func TestWindowsClientReadAfterCloseDoesNotServeBufferedPlaintext(t *testing.T) { + serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost") + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { listener.Close() }) + + serverDone := make(chan struct{}) + serverErr := make(chan error, 1) + payload := bytes.Repeat([]byte("buffered plaintext "), 32) + go func() { + conn, acceptErr := listener.Accept() + if acceptErr != nil { + serverErr <- acceptErr + return + } + defer conn.Close() + + tlsConn := stdtls.Server(conn, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS12, + MaxVersion: stdtls.VersionTLS12, + }) + defer tlsConn.Close() + + err := tlsConn.SetDeadline(time.Now().Add(windowsTLSTestTimeout)) + if err != nil { + serverErr <- err + return + } + err = tlsConn.Handshake() + if err != nil { + serverErr <- err + return + } + _, err = tlsConn.Write(payload) + if err != nil { + serverErr <- err + return + } + <-serverDone + serverErr <- nil + }() + + clientConn, err := newWindowsTestClientConn(t, listener.Addr().String(), option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + MinVersion: "1.2", + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err != nil { + t.Fatal(err) + } + + buffer := make([]byte, 8) + n, err := clientConn.Read(buffer) + if err != nil { + t.Fatalf("first read: %v", err) + } + if n != len(buffer) { + t.Fatalf("expected first read to fill the buffer, got %d", n) + } + + clientConn.Close() + close(serverDone) + + _, err = clientConn.Read(make([]byte, len(payload))) + if !errors.Is(err, net.ErrClosed) { + t.Fatalf("expected net.ErrClosed, got %v", err) + } + serverErrValue := <-serverErr + if serverErrValue != nil { + t.Fatal(serverErrValue) + } +} + +func TestWindowsClientWriteAfterCloseReturnsError(t *testing.T) { + clientConn, serverDone := startWindowsEchoServer(t, stdtls.VersionTLS12) + clientConn.Close() + <-serverDone + + _, err := clientConn.Write([]byte("after close")) + if err == nil { + t.Fatal("expected Write after Close to return error") + } +} + +func TestWindowsClientReadDeadline(t *testing.T) { + serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost") + serverDone, serverAddress := startWindowsTLSSilentServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS12, + MaxVersion: stdtls.VersionTLS12, + }) + + clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + MinVersion: "1.2", + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err != nil { + t.Fatal(err) + } + defer clientConn.Close() + defer close(serverDone) + + err = clientConn.SetReadDeadline(time.Now().Add(200 * time.Millisecond)) + if err != nil { + t.Fatalf("SetReadDeadline: %v", err) + } + + readDone := make(chan error, 1) + buffer := make([]byte, 64) + go func() { + _, readErr := clientConn.Read(buffer) + readDone <- readErr + }() + + select { + case readErr := <-readDone: + if !errors.Is(readErr, os.ErrDeadlineExceeded) { + t.Fatalf("expected os.ErrDeadlineExceeded, got %v", readErr) + } + case <-time.After(2 * time.Second): + t.Fatal("Read did not return within 2s after deadline") + } +} + +func TestWindowsClientSetReadDeadlinePreExpired(t *testing.T) { + serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost") + serverDone, serverAddress := startWindowsTLSSilentServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS12, + MaxVersion: stdtls.VersionTLS12, + }) + + clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + MinVersion: "1.2", + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err != nil { + t.Fatal(err) + } + defer clientConn.Close() + defer close(serverDone) + + err = clientConn.SetReadDeadline(time.Now().Add(-time.Second)) + if err != nil { + t.Fatalf("SetReadDeadline past: %v", err) + } + + buffer := make([]byte, 16) + _, err = clientConn.Read(buffer) + if !errors.Is(err, os.ErrDeadlineExceeded) { + t.Fatalf("expected os.ErrDeadlineExceeded, got %v", err) + } + + // Clearing the deadline must restore normal blocking behaviour. + err = clientConn.SetReadDeadline(time.Time{}) + if err != nil { + t.Fatalf("SetReadDeadline zero: %v", err) + } + err = clientConn.SetReadDeadline(time.Now().Add(200 * time.Millisecond)) + if err != nil { + t.Fatalf("SetReadDeadline future: %v", err) + } + start := time.Now() + _, err = clientConn.Read(buffer) + elapsed := time.Since(start) + if !errors.Is(err, os.ErrDeadlineExceeded) { + t.Fatalf("expected os.ErrDeadlineExceeded after re-arm, got %v", err) + } + if elapsed < 150*time.Millisecond { + t.Fatalf("Read returned too fast (%v), pre-expired flag leaked", elapsed) + } +} + +func TestWindowsClientSetDeadlinePropagatesToRawConn(t *testing.T) { + rawConn := &windowsTestDeadlineConn{} + tlsConn := newTestWindowsTLSConn(rawConn) + + deadline := time.Now().Add(time.Second) + err := tlsConn.SetDeadline(deadline) + if err != nil { + t.Fatalf("SetDeadline: %v", err) + } + + readDeadlines := rawConn.recordedReadDeadlines() + if len(readDeadlines) != 1 { + t.Fatalf("expected 1 read deadline update, got %d", len(readDeadlines)) + } + if !readDeadlines[0].Equal(deadline) { + t.Fatalf("expected read deadline %v, got %v", deadline, readDeadlines[0]) + } + + writeDeadlines := rawConn.recordedWriteDeadlines() + if len(writeDeadlines) != 1 { + t.Fatalf("expected 1 write deadline update, got %d", len(writeDeadlines)) + } + if !writeDeadlines[0].Equal(deadline) { + t.Fatalf("expected write deadline %v, got %v", deadline, writeDeadlines[0]) + } +} + +func TestWindowsClientSetReadDeadlineCancelsBlockedRead(t *testing.T) { + rawConn := &windowsTestDeadlineConn{ + readCalled: make(chan struct{}), + } + tlsConn := newTestWindowsTLSConn(rawConn) + tlsConn.readScratch = make([]byte, 16) + + readErrCh := make(chan error, 1) + go func() { + _, err := tlsConn.Read(make([]byte, 1)) + readErrCh <- err + }() + + select { + case <-rawConn.readCalled: + case <-time.After(time.Second): + t.Fatal("Read did not reach the raw connection") + } + + deadline := time.Now().Add(150 * time.Millisecond) + err := tlsConn.SetReadDeadline(deadline) + if err != nil { + t.Fatalf("SetReadDeadline: %v", err) + } + + select { + case err = <-readErrCh: + if !errors.Is(err, os.ErrDeadlineExceeded) { + t.Fatalf("expected os.ErrDeadlineExceeded, got %v", err) + } + case <-time.After(2 * time.Second): + t.Fatal("Read did not return after SetReadDeadline") + } + + readDeadlines := rawConn.recordedReadDeadlines() + if len(readDeadlines) != 1 { + t.Fatalf("expected 1 read deadline update, got %d", len(readDeadlines)) + } + if !readDeadlines[0].Equal(deadline) { + t.Fatalf("expected read deadline %v, got %v", deadline, readDeadlines[0]) + } +} + +func TestWindowsClientSetWriteDeadlineCancelsBlockedWrite(t *testing.T) { + serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost") + serverDone, serverAddress := startWindowsTLSSilentServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS12, + MaxVersion: stdtls.VersionTLS12, + }) + defer close(serverDone) + + tlsConn, err := newWindowsTestEngineConn(t, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + MinVersion: "1.2", + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err != nil { + t.Fatal(err) + } + + originalRawConn := tlsConn.rawConn + rawConn := &windowsTestDeadlineConn{ + writeCalled: make(chan struct{}), + } + tlsConn.rawConn = rawConn + t.Cleanup(func() { + _ = originalRawConn.Close() + _ = tlsConn.Close() + }) + + writeErrCh := make(chan error, 1) + go func() { + _, err := tlsConn.Write([]byte("ping")) + writeErrCh <- err + }() + + select { + case <-rawConn.writeCalled: + case <-time.After(time.Second): + t.Fatal("Write did not reach the raw connection") + } + + deadline := time.Now().Add(150 * time.Millisecond) + err = tlsConn.SetWriteDeadline(deadline) + if err != nil { + t.Fatalf("SetWriteDeadline: %v", err) + } + + select { + case err = <-writeErrCh: + if !errors.Is(err, os.ErrDeadlineExceeded) { + t.Fatalf("expected os.ErrDeadlineExceeded, got %v", err) + } + case <-time.After(2 * time.Second): + t.Fatal("Write did not return after SetWriteDeadline") + } + + writeDeadlines := rawConn.recordedWriteDeadlines() + if len(writeDeadlines) != 1 { + t.Fatalf("expected 1 write deadline update, got %d", len(writeDeadlines)) + } + if !writeDeadlines[0].Equal(deadline) { + t.Fatalf("expected write deadline %v, got %v", deadline, writeDeadlines[0]) + } +} + +func TestWindowsClientPostHandshakeReplyUsesReadDeadline(t *testing.T) { + rawConn := &windowsTestDeadlineConn{} + tlsConn := newTestWindowsTLSConn(rawConn) + + readDeadline := time.Now().Add(150 * time.Millisecond) + err := tlsConn.SetReadDeadline(readDeadline) + if err != nil { + t.Fatalf("SetReadDeadline: %v", err) + } + + start := time.Now() + err = tlsConn.writePostHandshakeReply([]byte("reply")) + elapsed := time.Since(start) + if !errors.Is(err, os.ErrDeadlineExceeded) { + t.Fatalf("expected os.ErrDeadlineExceeded, got %v", err) + } + if elapsed < 100*time.Millisecond { + t.Fatalf("post-handshake write returned too fast: %v", elapsed) + } + + deadlines := rawConn.recordedWriteDeadlines() + if len(deadlines) != 2 { + t.Fatalf("expected 2 write deadline updates, got %d", len(deadlines)) + } + if !deadlines[0].Equal(readDeadline) { + t.Fatalf("expected first write deadline %v, got %v", readDeadline, deadlines[0]) + } + if !deadlines[1].IsZero() { + t.Fatalf("expected write deadline cleanup, got %v", deadlines[1]) + } +} + +func TestWindowsClientPostHandshakeReplyPreExpiredReadDeadline(t *testing.T) { + rawConn := &windowsTestDeadlineConn{} + tlsConn := newTestWindowsTLSConn(rawConn) + + err := tlsConn.SetReadDeadline(time.Now().Add(-time.Second)) + if err != nil { + t.Fatalf("SetReadDeadline: %v", err) + } + + start := time.Now() + err = tlsConn.writePostHandshakeReply([]byte("reply")) + elapsed := time.Since(start) + if !errors.Is(err, os.ErrDeadlineExceeded) { + t.Fatalf("expected os.ErrDeadlineExceeded, got %v", err) + } + if elapsed > 50*time.Millisecond { + t.Fatalf("pre-expired post-handshake write returned too slowly: %v", elapsed) + } + + deadlines := rawConn.recordedWriteDeadlines() + if len(deadlines) != 0 { + t.Fatalf("expected no write deadline update for pre-expired read deadline, got %d", len(deadlines)) + } +} + +func TestDriveStepsPreservesBufferedHandshakeBytes(t *testing.T) { + scratch := make([]byte, 8) + copy(scratch, "abc") + + readCalls := 0 + stepCalls := 0 + leftover, err := driveSteps( + scratch[:3], + func(input []byte) (schannel.StepResult, error) { + stepCalls++ + switch stepCalls { + case 1: + if string(input) != "abc" { + t.Fatalf("first step input = %q, want %q", input, "abc") + } + return schannel.StepResult{Incomplete: true}, nil + case 2: + if string(input) != "abcdef" { + t.Fatalf("second step input = %q, want %q", input, "abcdef") + } + return schannel.StepResult{Consumed: len(input), Done: true}, nil + default: + t.Fatalf("unexpected step call %d", stepCalls) + return schannel.StepResult{}, nil + } + }, + func() ([]byte, error) { + readCalls++ + copy(scratch, "def") + return scratch[:3], nil + }, + func([]byte) error { return nil }, + ) + if err != nil { + t.Fatal(err) + } + if readCalls != 1 { + t.Fatalf("readMore called %d times, want 1", readCalls) + } + if len(leftover) != 0 { + t.Fatalf("leftover = %q, want empty", leftover) + } +} + +func TestWindowsTLSRawReadEOFAtRecordBoundary(t *testing.T) { + rawConn := &windowsTestIOConn{readErr: io.EOF} + _, err := readTLSRaw(rawConn, make([]byte, 16), false) + if !errors.Is(err, io.EOF) { + t.Fatalf("expected io.EOF, got %v", err) + } +} + +func TestWindowsTLSRawReadEOFWithPendingRecord(t *testing.T) { + rawConn := &windowsTestIOConn{readErr: io.EOF} + _, err := readTLSRaw(rawConn, make([]byte, 16), true) + if !errors.Is(err, io.ErrUnexpectedEOF) { + t.Fatalf("expected io.ErrUnexpectedEOF, got %v", err) + } +} + +func TestWindowsClientPostHandshakeReplyWaitsForWriteAccess(t *testing.T) { + rawConn := &windowsTestWriteGateConn{ + writeCalled: make(chan struct{}), + releaseWrite: make(chan struct{}), + } + tlsConn := newTestWindowsTLSConn(rawConn) + + tlsConn.writeAccess.Lock() + errCh := make(chan error, 1) + go func() { + errCh <- tlsConn.writePostHandshakeReply([]byte("reply")) + }() + + select { + case <-rawConn.writeCalled: + t.Fatal("post-handshake write bypassed writeAccess") + case <-time.After(100 * time.Millisecond): + } + + tlsConn.writeAccess.Unlock() + + select { + case <-rawConn.writeCalled: + case <-time.After(time.Second): + t.Fatal("post-handshake write did not resume after writeAccess release") + } + + close(rawConn.releaseWrite) + err := <-errCh + if err != nil { + t.Fatal(err) + } +} + +func TestWindowsClientPostHandshakeWritePreemptsNewWrite(t *testing.T) { + tlsConn := newTestWindowsTLSConn(&windowsTestIOConn{}) + err := tlsConn.beginWrite() + if err != nil { + t.Fatal(err) + } + + postHandshakeReady := make(chan error, 1) + go func() { + postHandshakeReady <- tlsConn.beginPostHandshakeWrite() + }() + + deadline := time.After(time.Second) + for { + tlsConn.writeState.Lock() + pending := tlsConn.postHandshake + tlsConn.writeState.Unlock() + if pending { + break + } + select { + case <-deadline: + t.Fatal("post-handshake write did not become pending") + default: + time.Sleep(time.Millisecond) + } + } + + writeReady := make(chan error, 1) + go func() { + writeReady <- tlsConn.beginWrite() + }() + + tlsConn.finishWrite() + + select { + case err = <-postHandshakeReady: + if err != nil { + t.Fatal(err) + } + case err = <-writeReady: + t.Fatalf("new write preempted post-handshake write: %v", err) + case <-time.After(time.Second): + t.Fatal("post-handshake write did not resume") + } + + select { + case err = <-writeReady: + t.Fatalf("new write acquired before post-handshake finished: %v", err) + case <-time.After(100 * time.Millisecond): + } + + tlsConn.finishPostHandshakeWrite() + select { + case err = <-writeReady: + if err != nil { + t.Fatal(err) + } + case <-time.After(time.Second): + t.Fatal("new write did not resume after post-handshake write") + } + tlsConn.finishWrite() +} + +func TestWindowsClientPostHandshakeReplyErrorClosesConn(t *testing.T) { + rawConn := &windowsTestIOConn{ + writeErr: os.ErrDeadlineExceeded, + writeN: 1, + } + tlsConn := newTestWindowsTLSConn(rawConn) + + err := tlsConn.writePostHandshakeReply([]byte("reply")) + if !errors.Is(err, os.ErrDeadlineExceeded) { + t.Fatalf("expected os.ErrDeadlineExceeded, got %v", err) + } + if !rawConn.isClosed() { + t.Fatal("expected raw conn to be closed") + } + if !tlsConn.isClosed() { + t.Fatal("expected tls conn to be closed") + } +} + +func TestWindowsClientWriteErrorClosesConn(t *testing.T) { + serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost") + serverDone, serverAddress := startWindowsTLSSilentServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS12, + MaxVersion: stdtls.VersionTLS12, + }) + defer close(serverDone) + + tlsConn, err := newWindowsTestEngineConn(t, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + MinVersion: "1.2", + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err != nil { + t.Fatal(err) + } + + originalRawConn := tlsConn.rawConn + rawConn := &windowsTestIOConn{ + writeErr: os.ErrDeadlineExceeded, + writeN: 1, + } + tlsConn.rawConn = rawConn + t.Cleanup(func() { + _ = originalRawConn.Close() + _ = tlsConn.Close() + }) + + _, err = tlsConn.Write([]byte("ping")) + if !errors.Is(err, os.ErrDeadlineExceeded) { + t.Fatalf("expected os.ErrDeadlineExceeded, got %v", err) + } + if !rawConn.isClosed() { + t.Fatal("expected raw conn to be closed") + } + if !tlsConn.isClosed() { + t.Fatal("expected tls conn to be closed") + } + + _, err = tlsConn.Write([]byte("again")) + if !errors.Is(err, net.ErrClosed) { + t.Fatalf("expected net.ErrClosed on second write, got %v", err) + } + if rawConn.totalWriteCalls() != 1 { + t.Fatalf("expected exactly 1 raw write, got %d", rawConn.totalWriteCalls()) + } + + _, err = tlsConn.Read(make([]byte, 1)) + if !errors.Is(err, net.ErrClosed) { + t.Fatalf("expected net.ErrClosed on read after write failure, got %v", err) + } +} + +func TestWindowsClientConnectionStateFields(t *testing.T) { + serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost") + _, serverAddress := startWindowsTLSTestServer(t, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: stdtls.VersionTLS12, + NextProtos: []string{"h2"}, + }) + + clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + MinVersion: "1.2", + ALPN: badoption.Listable[string]{"h2"}, + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err != nil { + t.Fatal(err) + } + defer clientConn.Close() + + state := clientConn.ConnectionState() + if state.ServerName != "localhost" { + t.Errorf("ServerName: expected localhost, got %q", state.ServerName) + } + if state.NegotiatedProtocol != "h2" { + t.Errorf("NegotiatedProtocol: expected h2, got %q", state.NegotiatedProtocol) + } + if !state.HandshakeComplete { + t.Error("HandshakeComplete: expected true") + } + if state.Version < stdtls.VersionTLS12 || state.Version > stdtls.VersionTLS13 { + t.Errorf("Version: expected TLS 1.2–1.3, got %x", state.Version) + } + if len(state.PeerCertificates) == 0 { + t.Fatal("PeerCertificates: expected at least one certificate") + } + // CipherSuite may be 0 when the Schannel name does not map to a Go + // constant; just ensure it's consistent with the protocol. + if state.Version == stdtls.VersionTLS13 && state.CipherSuite != 0 { + switch state.CipherSuite { + case stdtls.TLS_AES_128_GCM_SHA256, stdtls.TLS_AES_256_GCM_SHA384, stdtls.TLS_CHACHA20_POLY1305_SHA256: + default: + t.Errorf("unexpected TLS 1.3 cipher suite: %x", state.CipherSuite) + } + } +} + +func TestWindowsClientNetConnReturnsUnderlying(t *testing.T) { + clientConn, serverDone := startWindowsEchoServer(t, stdtls.VersionTLS12) + defer func() { <-serverDone }() + defer clientConn.Close() + + underlying := clientConn.NetConn() + if _, isTCP := underlying.(*net.TCPConn); !isTCP { + t.Fatalf("NetConn returned %T, expected *net.TCPConn", underlying) + } +} + +func TestNewWindowsClientMissingServerName(t *testing.T) { + _, err := NewClientWithOptions(ClientOptions{ + Context: context.Background(), + Logger: logger.NOP(), + Options: option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + }, + }) + if err == nil { + t.Fatal("expected missing server_name error") + } +} + +func TestNewWindowsClientInsecureAllowsMissingServerName(t *testing.T) { + _, err := NewClientWithOptions(ClientOptions{ + Context: context.Background(), + Logger: logger.NOP(), + Options: option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + Insecure: true, + }, + }) + if err != nil { + t.Fatal(err) + } +} + +func TestWindowsClientConfigSTDConfigReturnsError(t *testing.T) { + config, err := NewClientWithOptions(ClientOptions{ + Context: context.Background(), + Logger: logger.NOP(), + Options: option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + }, + }) + if err != nil { + t.Fatal(err) + } + _, err = config.STDConfig() + if err == nil { + t.Fatal("expected STDConfig() to return error for Windows engine") + } + if !strings.Contains(err.Error(), "system TLS engine") { + t.Fatalf("expected error to name the engine, got %q", err.Error()) + } +} + +func TestWindowsClientConfigClientReturnsErrInvalid(t *testing.T) { + config, err := NewClientWithOptions(ClientOptions{ + Context: context.Background(), + Logger: logger.NOP(), + Options: option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + }, + }) + if err != nil { + t.Fatal(err) + } + _, err = config.Client(nil) + if !errors.Is(err, os.ErrInvalid) { + t.Fatalf("expected os.ErrInvalid, got %v", err) + } +} + +func TestWindowsClientConfigClone(t *testing.T) { + config, err := NewClientWithOptions(ClientOptions{ + Context: context.Background(), + Logger: logger.NOP(), + Options: option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + ALPN: badoption.Listable[string]{"h2", "http/1.1"}, + }, + }) + if err != nil { + t.Fatal(err) + } + + clone := config.Clone() + + // Mutating the clone must not affect the original. + clone.SetServerName("other") + clone.SetNextProtos([]string{"h3"}) + if config.ServerName() == "other" { + t.Error("Clone shares server name with original") + } + if len(config.NextProtos()) != 2 { + t.Error("Clone shares ALPN slice with original") + } +} + +func TestValidateWindowsTLSOptionsRejections(t *testing.T) { + cases := []struct { + name string + options option.OutboundTLSOptions + needle string + }{ + {"reality", option.OutboundTLSOptions{ + Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x", + Reality: &option.OutboundRealityOptions{Enabled: true, ShortID: "abc"}, + }, "reality"}, + {"utls", option.OutboundTLSOptions{ + Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x", + UTLS: &option.OutboundUTLSOptions{Enabled: true}, + }, "utls"}, + {"ech", option.OutboundTLSOptions{ + Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x", + ECH: &option.OutboundECHOptions{Enabled: true}, + }, "ech"}, + {"disable_sni", option.OutboundTLSOptions{ + Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x", + DisableSNI: true, + }, "disable_sni"}, + {"cipher_suites", option.OutboundTLSOptions{ + Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x", + CipherSuites: []string{"TLS_AES_128_GCM_SHA256"}, + }, "cipher_suites"}, + {"curve_preferences", option.OutboundTLSOptions{ + Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x", + CurvePreferences: []option.CurvePreference{option.CurvePreference(29)}, + }, "curve_preferences"}, + {"client_certificate", option.OutboundTLSOptions{ + Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x", + ClientCertificate: badoption.Listable[string]{"pem"}, + }, "client certificate"}, + {"fragment", option.OutboundTLSOptions{ + Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x", + Fragment: true, + }, "tls fragment"}, + {"record_fragment", option.OutboundTLSOptions{ + Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x", + RecordFragment: true, + }, "tls fragment"}, + {"kernel_tx", option.OutboundTLSOptions{ + Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x", + KernelTx: true, + }, "ktls"}, + {"kernel_rx", option.OutboundTLSOptions{ + Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x", + KernelRx: true, + }, "ktls"}, + {"spoof", option.OutboundTLSOptions{ + Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x", + Spoof: "decoy.example", + }, "spoof"}, + {"pin_and_cert_conflict", option.OutboundTLSOptions{ + Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x", + Certificate: badoption.Listable[string]{"-----BEGIN CERTIFICATE-----"}, + CertificatePublicKeySHA256: [][]byte{make([]byte, 32)}, + }, "certificate_public_key_sha256"}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + _, err := NewClientWithOptions(ClientOptions{ + Context: context.Background(), + Logger: logger.NOP(), + Options: tc.options, + }) + if err == nil { + t.Fatalf("expected error containing %q, got nil", tc.needle) + } + if !strings.Contains(strings.ToLower(err.Error()), strings.ToLower(tc.needle)) { + t.Fatalf("expected error to contain %q, got %q", tc.needle, err.Error()) + } + }) + } +} + +func startWindowsTLSSilentServer(t *testing.T, tlsConfig *stdtls.Config) (chan<- struct{}, string) { + t.Helper() + + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { listener.Close() }) + + if tcpListener, isTCP := listener.(*net.TCPListener); isTCP { + err = tcpListener.SetDeadline(time.Now().Add(windowsTLSTestTimeout)) + if err != nil { + t.Fatal(err) + } + } + + done := make(chan struct{}) + go func() { + conn, acceptErr := listener.Accept() + if acceptErr != nil { + return + } + defer conn.Close() + deadlineErr := conn.SetDeadline(time.Now().Add(windowsTLSTestTimeout)) + if deadlineErr != nil { + return + } + tlsConn := stdtls.Server(conn, tlsConfig) + defer tlsConn.Close() + handshakeErr := tlsConn.Handshake() + if handshakeErr != nil { + return + } + handshakeErr = conn.SetDeadline(time.Time{}) + if handshakeErr != nil { + return + } + <-done + }() + return done, listener.Addr().String() +} + +// sharedWindowsTestCertificate caches a localhost certificate so the RSA key +// generation runs once per test binary instead of once per test. +var sharedWindowsTestCertificate = sync.OnceValues(func() (stdtls.Certificate, string) { + return generateWindowsTestCertificate("localhost") +}) + +func newWindowsTestCertificate(t *testing.T, serverName string) (stdtls.Certificate, string) { + t.Helper() + if serverName == "localhost" { + return sharedWindowsTestCertificate() + } + return generateWindowsTestCertificate(serverName) +} + +func generateWindowsTestCertificate(serverName string) (stdtls.Certificate, string) { + privateKeyPEM, certificatePEM, err := GenerateCertificate(nil, nil, time.Now, serverName, time.Now().Add(time.Hour)) + if err != nil { + panic(err) + } + certificate, err := stdtls.X509KeyPair(certificatePEM, privateKeyPEM) + if err != nil { + panic(err) + } + leaf, err := x509.ParseCertificate(certificate.Certificate[0]) + if err != nil { + panic(err) + } + certificate.Leaf = leaf + return certificate, string(certificatePEM) +} + +func publicKeyPin(t *testing.T, cert *x509.Certificate) []byte { + t.Helper() + pub, err := x509.MarshalPKIXPublicKey(cert.PublicKey) + if err != nil { + t.Fatal(err) + } + sum := sha256.Sum256(pub) + return sum[:] +} + +func startWindowsTLSTestServer(t *testing.T, tlsConfig *stdtls.Config) (<-chan windowsTLSServerResult, string) { + t.Helper() + + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { listener.Close() }) + + if tcpListener, isTCP := listener.(*net.TCPListener); isTCP { + err = tcpListener.SetDeadline(time.Now().Add(windowsTLSTestTimeout)) + if err != nil { + t.Fatal(err) + } + } + + result := make(chan windowsTLSServerResult, 1) + go func() { + defer close(result) + + conn, err := listener.Accept() + if err != nil { + result <- windowsTLSServerResult{err: err} + return + } + defer conn.Close() + + err = conn.SetDeadline(time.Now().Add(windowsTLSTestTimeout)) + if err != nil { + result <- windowsTLSServerResult{err: err} + return + } + + tlsConn := stdtls.Server(conn, tlsConfig) + defer tlsConn.Close() + + err = tlsConn.Handshake() + if err != nil { + result <- windowsTLSServerResult{err: err} + return + } + + result <- windowsTLSServerResult{state: tlsConn.ConnectionState()} + }() + + return result, listener.Addr().String() +} + +func newWindowsTestClientConn(t *testing.T, serverAddress string, options option.OutboundTLSOptions) (Conn, error) { + t.Helper() + + ctx, cancel := context.WithTimeout(context.Background(), windowsTLSTestTimeout) + t.Cleanup(cancel) + + clientConfig, err := NewClientWithOptions(ClientOptions{ + Context: ctx, + Logger: logger.NOP(), + ServerAddress: "", + Options: options, + }) + if err != nil { + return nil, err + } + + conn, err := net.DialTimeout("tcp", serverAddress, windowsTLSTestTimeout) + if err != nil { + return nil, err + } + + tlsConn, err := ClientHandshake(ctx, conn, clientConfig) + if err != nil { + conn.Close() + return nil, err + } + return tlsConn, nil +} + +func newWindowsTestEngineConn(t *testing.T, serverAddress string, options option.OutboundTLSOptions) (*windowsTLSConn, error) { + t.Helper() + + ctx, cancel := context.WithTimeout(context.Background(), windowsTLSTestTimeout) + t.Cleanup(cancel) + + clientConfig, err := NewClientWithOptions(ClientOptions{ + Context: ctx, + Logger: logger.NOP(), + ServerAddress: "", + Options: options, + }) + if err != nil { + return nil, err + } + + engineConfig, ok := clientConfig.(*windowsClientConfig) + if !ok { + return nil, errors.New("unexpected windows config type") + } + + conn, err := net.DialTimeout("tcp", serverAddress, windowsTLSTestTimeout) + if err != nil { + return nil, err + } + + tlsConn, err := engineConfig.ClientHandshake(ctx, conn) + if err != nil { + conn.Close() + return nil, err + } + + engineConn, ok := tlsConn.(*windowsTLSConn) + if !ok { + tlsConn.Close() + return nil, errors.New("unexpected windows conn type") + } + return engineConn, nil +} + +func startWindowsPayloadServer(t *testing.T, minVersion uint16, payload []byte) (*windowsTLSConn, <-chan error) { + t.Helper() + + serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost") + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { listener.Close() }) + + serverErr := make(chan error, 1) + go func() { + conn, acceptErr := listener.Accept() + if acceptErr != nil { + serverErr <- acceptErr + return + } + defer conn.Close() + + tlsConn := stdtls.Server(conn, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: minVersion, + MaxVersion: minVersion, + }) + defer tlsConn.Close() + + handshakeErr := tlsConn.Handshake() + if handshakeErr != nil { + serverErr <- handshakeErr + return + } + _, writeErr := tlsConn.Write(payload) + serverErr <- writeErr + }() + + version := "1.2" + if minVersion == stdtls.VersionTLS13 { + version = "1.3" + } + clientConn, err := newWindowsTestEngineConn(t, listener.Addr().String(), option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + MinVersion: version, + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err != nil { + t.Fatal(err) + } + return clientConn, serverErr +} + +// startWindowsEchoServer brings up a TLS echo server with a self-signed cert +// and dials an engine client against it. The returned channel closes after +// the server goroutine exits. +func startWindowsEchoServer(t *testing.T, minVersion uint16) (Conn, <-chan struct{}) { + t.Helper() + + serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost") + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { listener.Close() }) + + done := make(chan struct{}) + go func() { + defer close(done) + conn, acceptErr := listener.Accept() + if acceptErr != nil { + return + } + defer conn.Close() + + tlsConn := stdtls.Server(conn, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: minVersion, + MaxVersion: minVersion, + }) + defer tlsConn.Close() + + handshakeErr := tlsConn.Handshake() + if handshakeErr != nil { + return + } + + buffer := make([]byte, 32*1024) + for { + n, readErr := tlsConn.Read(buffer) + if n > 0 { + _, writeErr := tlsConn.Write(buffer[:n]) + if writeErr != nil { + return + } + } + if readErr != nil { + return + } + } + }() + + version := "1.2" + if minVersion == stdtls.VersionTLS13 { + version = "1.3" + } + clientConn, err := newWindowsTestClientConn(t, listener.Addr().String(), option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + MinVersion: version, + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err != nil { + t.Fatal(err) + } + return clientConn, done +} + +func startWindowsEchoEngineServer(t *testing.T, minVersion uint16) (*windowsTLSConn, <-chan struct{}) { + t.Helper() + + serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost") + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { listener.Close() }) + + done := make(chan struct{}) + go func() { + defer close(done) + conn, acceptErr := listener.Accept() + if acceptErr != nil { + return + } + defer conn.Close() + + tlsConn := stdtls.Server(conn, &stdtls.Config{ + Certificates: []stdtls.Certificate{serverCertificate}, + MinVersion: minVersion, + MaxVersion: minVersion, + }) + defer tlsConn.Close() + + handshakeErr := tlsConn.Handshake() + if handshakeErr != nil { + return + } + + buffer := make([]byte, 32*1024) + for { + n, readErr := tlsConn.Read(buffer) + if n > 0 { + _, writeErr := tlsConn.Write(buffer[:n]) + if writeErr != nil { + return + } + } + if readErr != nil { + return + } + } + }() + + version := "1.2" + if minVersion == stdtls.VersionTLS13 { + version = "1.3" + } + clientConn, err := newWindowsTestEngineConn(t, listener.Addr().String(), option.OutboundTLSOptions{ + Enabled: true, + Engine: C.TLSEngineWindows, + ServerName: "localhost", + MinVersion: version, + Certificate: badoption.Listable[string]{serverCertificatePEM}, + }) + if err != nil { + t.Fatal(err) + } + return clientConn, done +} diff --git a/common/tlsfragment/conn.go b/common/tlsfragment/conn.go new file mode 100644 index 0000000000..040663bd2f --- /dev/null +++ b/common/tlsfragment/conn.go @@ -0,0 +1,146 @@ +package tf + +import ( + "bytes" + "context" + "encoding/binary" + "math/rand" + "net" + "strings" + "time" + + C "github.com/sagernet/sing-box/constant" + N "github.com/sagernet/sing/common/network" + + "golang.org/x/net/publicsuffix" +) + +type Conn struct { + net.Conn + tcpConn *net.TCPConn + ctx context.Context + firstPacketWritten bool + splitPacket bool + splitRecord bool + fallbackDelay time.Duration +} + +func NewConn(conn net.Conn, ctx context.Context, splitPacket bool, splitRecord bool, fallbackDelay time.Duration) *Conn { + if fallbackDelay == 0 { + fallbackDelay = C.TLSFragmentFallbackDelay + } + tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn) + return &Conn{ + Conn: conn, + tcpConn: tcpConn, + ctx: ctx, + splitPacket: splitPacket, + splitRecord: splitRecord, + fallbackDelay: fallbackDelay, + } +} + +func (c *Conn) Write(b []byte) (n int, err error) { + if !c.firstPacketWritten { + defer func() { + c.firstPacketWritten = true + }() + serverName := IndexTLSServerName(b) + if serverName != nil { + if c.splitPacket { + if c.tcpConn != nil { + err = c.tcpConn.SetNoDelay(true) + if err != nil { + return + } + } + } + splits := strings.Split(serverName.ServerName, ".") + currentIndex := serverName.Index + if publicSuffix := publicsuffix.List.PublicSuffix(serverName.ServerName); publicSuffix != "" { + splits = splits[:len(splits)-strings.Count(serverName.ServerName, ".")] + } + if len(splits) > 1 && splits[0] == "..." { + currentIndex += len(splits[0]) + 1 + splits = splits[1:] + } + var splitIndexes []int + for i, split := range splits { + splitAt := rand.Intn(len(split)) + splitIndexes = append(splitIndexes, currentIndex+splitAt) + currentIndex += len(split) + if i != len(splits)-1 { + currentIndex++ + } + } + var buffer bytes.Buffer + for i := 0; i <= len(splitIndexes); i++ { + var payload []byte + if i == 0 { + payload = b[:splitIndexes[i]] + if c.splitRecord { + payload = payload[recordLayerHeaderLen:] + } + } else if i == len(splitIndexes) { + payload = b[splitIndexes[i-1]:] + } else { + payload = b[splitIndexes[i-1]:splitIndexes[i]] + } + if c.splitRecord { + if c.splitPacket { + buffer.Reset() + } + payloadLen := uint16(len(payload)) + buffer.Write(b[:3]) + binary.Write(&buffer, binary.BigEndian, payloadLen) + buffer.Write(payload) + if c.splitPacket { + payload = buffer.Bytes() + } + } + if c.splitPacket { + if c.tcpConn != nil && i != len(splitIndexes) { + err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay) + if err != nil { + return + } + } else { + _, err = c.Conn.Write(payload) + if err != nil { + return + } + if i != len(splitIndexes) { + time.Sleep(c.fallbackDelay) + } + } + } + } + if c.splitRecord && !c.splitPacket { + _, err = c.Conn.Write(buffer.Bytes()) + if err != nil { + return + } + } + if c.tcpConn != nil { + err = c.tcpConn.SetNoDelay(false) + if err != nil { + return + } + } + return len(b), nil + } + } + return c.Conn.Write(b) +} + +func (c *Conn) ReaderReplaceable() bool { + return true +} + +func (c *Conn) WriterReplaceable() bool { + return c.firstPacketWritten +} + +func (c *Conn) Upstream() any { + return c.Conn +} diff --git a/common/tlsfragment/conn_test.go b/common/tlsfragment/conn_test.go new file mode 100644 index 0000000000..b7ade87303 --- /dev/null +++ b/common/tlsfragment/conn_test.go @@ -0,0 +1,42 @@ +package tf_test + +import ( + "context" + "crypto/tls" + "net" + "testing" + + tf "github.com/sagernet/sing-box/common/tlsfragment" + + "github.com/stretchr/testify/require" +) + +func TestTLSFragment(t *testing.T) { + t.Parallel() + tcpConn, err := net.Dial("tcp", "1.1.1.1:443") + require.NoError(t, err) + tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, false, 0), &tls.Config{ + ServerName: "www.cloudflare.com", + }) + require.NoError(t, tlsConn.Handshake()) +} + +func TestTLSRecordFragment(t *testing.T) { + t.Parallel() + tcpConn, err := net.Dial("tcp", "1.1.1.1:443") + require.NoError(t, err) + tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), false, true, 0), &tls.Config{ + ServerName: "www.cloudflare.com", + }) + require.NoError(t, tlsConn.Handshake()) +} + +func TestTLS2Fragment(t *testing.T) { + t.Parallel() + tcpConn, err := net.Dial("tcp", "1.1.1.1:443") + require.NoError(t, err) + tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, true, 0), &tls.Config{ + ServerName: "www.cloudflare.com", + }) + require.NoError(t, tlsConn.Handshake()) +} diff --git a/common/tlsfragment/index.go b/common/tlsfragment/index.go new file mode 100644 index 0000000000..83e4bcbc11 --- /dev/null +++ b/common/tlsfragment/index.go @@ -0,0 +1,136 @@ +package tf + +import ( + "encoding/binary" +) + +const ( + recordLayerHeaderLen int = 5 + handshakeHeaderLen int = 6 + randomDataLen int = 32 + sessionIDHeaderLen int = 1 + cipherSuiteHeaderLen int = 2 + compressMethodHeaderLen int = 1 + extensionsHeaderLen int = 2 + extensionHeaderLen int = 4 + sniExtensionHeaderLen int = 5 + contentType uint8 = 22 + handshakeType uint8 = 1 + sniExtensionType uint16 = 0 + sniNameDNSHostnameType uint8 = 0 + tlsVersionBitmask uint16 = 0xFFFC + tls13 uint16 = 0x0304 +) + +type MyServerName struct { + Index int + Length int + ServerName string + ExtensionsListLengthIndex int +} + +func IndexTLSServerName(payload []byte) *MyServerName { + if len(payload) < recordLayerHeaderLen || payload[0] != contentType { + return nil + } + segmentLen := binary.BigEndian.Uint16(payload[3:5]) + if len(payload) < recordLayerHeaderLen+int(segmentLen) { + return nil + } + serverName := indexTLSServerNameFromHandshake(payload[recordLayerHeaderLen:]) + if serverName == nil { + return nil + } + serverName.Index += recordLayerHeaderLen + serverName.ExtensionsListLengthIndex += recordLayerHeaderLen + return serverName +} + +func indexTLSServerNameFromHandshake(handshake []byte) *MyServerName { + if len(handshake) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen { + return nil + } + if handshake[0] != handshakeType { + return nil + } + handshakeLen := uint32(handshake[1])<<16 | uint32(handshake[2])<<8 | uint32(handshake[3]) + if len(handshake[4:]) != int(handshakeLen) { + return nil + } + tlsVersion := uint16(handshake[4])<<8 | uint16(handshake[5]) + if tlsVersion&tlsVersionBitmask != 0x0300 && tlsVersion != tls13 { + return nil + } + sessionIDLen := handshake[38] + currentIndex := handshakeHeaderLen + randomDataLen + sessionIDHeaderLen + int(sessionIDLen) + if len(handshake) < currentIndex { + return nil + } + cipherSuites := handshake[currentIndex:] + if len(cipherSuites) < cipherSuiteHeaderLen { + return nil + } + csLen := uint16(cipherSuites[0])<<8 | uint16(cipherSuites[1]) + if len(cipherSuites) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen { + return nil + } + compressMethodLen := uint16(cipherSuites[cipherSuiteHeaderLen+int(csLen)]) + currentIndex += cipherSuiteHeaderLen + int(csLen) + compressMethodHeaderLen + int(compressMethodLen) + if len(handshake) < currentIndex { + return nil + } + serverName := indexTLSServerNameFromExtensions(handshake[currentIndex:]) + if serverName == nil { + return nil + } + serverName.Index += currentIndex + serverName.ExtensionsListLengthIndex = currentIndex + return serverName +} + +func indexTLSServerNameFromExtensions(exs []byte) *MyServerName { + if len(exs) == 0 { + return nil + } + if len(exs) < extensionsHeaderLen { + return nil + } + exsLen := uint16(exs[0])<<8 | uint16(exs[1]) + exs = exs[extensionsHeaderLen:] + if len(exs) < int(exsLen) { + return nil + } + for currentIndex := extensionsHeaderLen; len(exs) > 0; { + if len(exs) < extensionHeaderLen { + return nil + } + exType := uint16(exs[0])<<8 | uint16(exs[1]) + exLen := uint16(exs[2])<<8 | uint16(exs[3]) + if len(exs) < extensionHeaderLen+int(exLen) { + return nil + } + sex := exs[extensionHeaderLen : extensionHeaderLen+int(exLen)] + + switch exType { + case sniExtensionType: + if len(sex) < sniExtensionHeaderLen { + return nil + } + sniType := sex[2] + if sniType != sniNameDNSHostnameType { + return nil + } + sniLen := uint16(sex[3])<<8 | uint16(sex[4]) + sex = sex[sniExtensionHeaderLen:] + + return &MyServerName{ + Index: currentIndex + extensionHeaderLen + sniExtensionHeaderLen, + Length: int(sniLen), + ServerName: string(sex), + } + } + exs = exs[4+exLen:] + currentIndex += 4 + int(exLen) + } + return nil +} diff --git a/common/tlsfragment/index_test.go b/common/tlsfragment/index_test.go new file mode 100644 index 0000000000..5086d6c7a8 --- /dev/null +++ b/common/tlsfragment/index_test.go @@ -0,0 +1,20 @@ +package tf_test + +import ( + "encoding/hex" + "testing" + + "github.com/sagernet/sing-box/common/tlsfragment" + + "github.com/stretchr/testify/require" +) + +func TestIndexTLSServerName(t *testing.T) { + t.Parallel() + payload, err := hex.DecodeString("16030105f8010005f403036e35de7389a679c54029cf452611f2211c70d9ac3897271de589ab6155f8e4ab20637d225f1ef969ad87ed78bfb9d171300bcb1703b6f314ccefb964f79b7d0961002a0a0a130213031301c02cc02bcca9c030c02fcca8c00ac009c014c013009d009c0035002fc008c012000a01000581baba00000000000f000d00000a6769746875622e636f6d00170000ff01000100000a000e000c3a3a11ec001d001700180019000b000201000010000e000c02683208687474702f312e31000500050100000000000d00160014040308040401050308050805050108060601020100120000003304ef04ed3a3a00010011ec04c0aeb2250c092a3463161cccb29d9183331a424964248579507ed23a180b0ceab2a5f5d9ce41547e497a89055471ea572867ba3a1fc3c9e45025274a20f60c6b60e62476b6afed0403af59ab83660ef4112ae20386a602010d0a5d454c0ed34c84ed4423e750213e6a2baab1bf9c4367a6007ab40a33d95220c2dcaa44f257024a5626b545db0510f4311b1a60714154909c6a61fdfca011fb2626d657aeb6070bf078508babe3b584555013e34acc56198ed4663742b3155a664a9901794c4586820a7dc162c01827291f3792e1237f801a8d1ef096013c181c4a58d2f6859ba75022d18cc4418bd4f351d5c18f83a58857d05af860c4b9ac018a5b63f17184e591532c6bc2cf2215d4a282c8a8a4f6f7aee110422c8bc9ebd3b1d609c568523aaae555db320e6c269473d87af38c256cbb9febc20aea6380c32a8916f7a373c8b1e37554e3260bf6621f6b804ee80b3c516b1d01985bf4c603b6daa9a5991de6a7a29f3a7122b8afb843a7660110fce62b43c615f5bcc2db688ba012649c0952b0a2c031e732d2b454c6b2968683cb8d244be2c9a7fa163222979eaf92722b92b862d81a3d94450c2b60c318421ebb4307c42d1f0473592a5c30e42039cc68cda9721e61aa63f49def17c15221680ed444896340133bbee67556f56b9f9d78a4df715f926a12add0cc9c862e46ea8b7316ae468282c18601b2771c9c9322f982228cf93effaacd3f80cbd12bce5fc36f56e2a3caf91e578a5fae00c9b23a8ed1a66764f4433c3628a70b8f0a6196adc60a4cb4226f07ba4c6b363fe9065563bfc1347452946386bab488686e837ab979c64f9047417fca635fe1bb4f074f256cc8af837c7b455e280426547755af90a61640169ef180aea3a77e662bb6dac1b6c3696027129b1a5edf495314e9c7f4b6110e16378ec893fa24642330a40aba1a85326101acb97c620fd8d71389e69eaed7bdb01bbe1fd428d66191150c7b2cd1ad4257391676a82ba8ce07fb2667c3b289f159003a7c7bc31d361b7b7f49a802961739d950dfcc0fa1c7abce5abdd2245101da391151490862028110465950b9e9c03d08a90998ab83267838d2e74a0593bc81f74cdf734519a05b351c0e5488c68dd810e6e9142ccc1e2f4a7f464297eb340e27acc6b9d64e12e38cce8492b3d939140b5a9e149a75597f10a23874c84323a07cdd657274378f887c85c4259b9c04cd33ba58ed630ef2a744f8e19dd34843dff331d2a6be7e2332c599289cd248a611c73d7481cd4a9bd43449a3836f14b2af18a1739e17999e4c67e85cc5bcecabb14185e5bcaff3c96098f03dc5aba819f29587758f49f940585354a2a780830528d68ccd166920dadcaa25cab5fc1907272a826aba3f08bc6b88757776812ecb6c7cec69a223ec0a13a7b62a2349a0f63ed7a27a3b15ba21d71fe6864ec6e089ae17cadd433fa3138f7ee24353c11365818f8fc34f43a05542d18efaac24bfccc1f748a0cc1a67ad379468b76fd34973dba785f5c91d618333cd810fe0700d1bbc8422029782628070a624c52c5309a4a64d625b11f8033ab28df34a1add297517fcc06b92b6817b3c5144438cf260867c57bde68c8c4b82e6a135ef676a52fbae5708002a404e6189a60e2836de565ad1b29e3819e5ed49f6810bcb28e1bd6de57306f94b79d9dae1cc4624d2a068499beef81cd5fe4b76dcbfff2a2008001d002001976128c6d5a934533f28b9914d2480aab2a8c1ab03d212529ce8b27640a716002d00020101002b000706caca03040303001b00030200015a5a000100") + require.NoError(t, err) + serverName := tf.IndexTLSServerName(payload) + require.NotNil(t, serverName) + require.Equal(t, serverName.ServerName, string(payload[serverName.Index:serverName.Index+serverName.Length])) + require.Equal(t, "github.com", serverName.ServerName) +} diff --git a/common/tlsfragment/wait_darwin.go b/common/tlsfragment/wait_darwin.go new file mode 100644 index 0000000000..90c65ba2cd --- /dev/null +++ b/common/tlsfragment/wait_darwin.go @@ -0,0 +1,93 @@ +package tf + +import ( + "context" + "net" + "time" + + "github.com/sagernet/sing/common/control" + + "golang.org/x/sys/unix" +) + +/* +const tcpMaxNotifyAck = 10 + +type tcpNotifyAckID uint32 + + type tcpNotifyAckComplete struct { + NotifyPending uint32 + NotifyCompleteCount uint32 + NotifyCompleteID [tcpMaxNotifyAck]tcpNotifyAckID + } + +var sizeOfTCPNotifyAckComplete = int(unsafe.Sizeof(tcpNotifyAckComplete{})) + + func getsockoptTCPNotifyAckComplete(fd, level, opt int) (*tcpNotifyAckComplete, error) { + var value tcpNotifyAckComplete + vallen := uint32(sizeOfTCPNotifyAckComplete) + err := getsockopt(fd, level, opt, unsafe.Pointer(&value), &vallen) + return &value, err + } + +//go:linkname getsockopt golang.org/x/sys/unix.getsockopt +func getsockopt(s int, level int, name int, val unsafe.Pointer, vallen *uint32) error + + func waitAck(ctx context.Context, conn *net.TCPConn, _ time.Duration) error { + const TCP_NOTIFY_ACKNOWLEDGEMENT = 0x212 + return control.Conn(conn, func(fd uintptr) error { + err := unix.SetsockoptInt(int(fd), unix.IPPROTO_TCP, TCP_NOTIFY_ACKNOWLEDGEMENT, 1) + if err != nil { + if errors.Is(err, unix.EINVAL) { + return waitAckFallback(ctx, conn, 0) + } + return err + } + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + var ackComplete *tcpNotifyAckComplete + ackComplete, err = getsockoptTCPNotifyAckComplete(int(fd), unix.IPPROTO_TCP, TCP_NOTIFY_ACKNOWLEDGEMENT) + if err != nil { + return err + } + if ackComplete.NotifyPending == 0 { + return nil + } + time.Sleep(10 * time.Millisecond) + } + }) + } +*/ + +func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error { + _, err := conn.Write(payload) + if err != nil { + return err + } + return control.Conn(conn, func(fd uintptr) error { + start := time.Now() + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + unacked, err := unix.GetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_NWRITE) + if err != nil { + return err + } + if unacked == 0 { + if time.Since(start) <= 20*time.Millisecond { + // under transparent proxy + time.Sleep(fallbackDelay) + } + return nil + } + time.Sleep(10 * time.Millisecond) + } + }) +} diff --git a/common/tlsfragment/wait_linux.go b/common/tlsfragment/wait_linux.go new file mode 100644 index 0000000000..517d6ea5ec --- /dev/null +++ b/common/tlsfragment/wait_linux.go @@ -0,0 +1,40 @@ +package tf + +import ( + "context" + "net" + "time" + + "github.com/sagernet/sing/common/control" + + "golang.org/x/sys/unix" +) + +func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error { + _, err := conn.Write(payload) + if err != nil { + return err + } + return control.Conn(conn, func(fd uintptr) error { + start := time.Now() + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + tcpInfo, err := unix.GetsockoptTCPInfo(int(fd), unix.IPPROTO_TCP, unix.TCP_INFO) + if err != nil { + return err + } + if tcpInfo.Unacked == 0 { + if time.Since(start) <= 20*time.Millisecond { + // under transparent proxy + time.Sleep(fallbackDelay) + } + return nil + } + time.Sleep(10 * time.Millisecond) + } + }) +} diff --git a/common/tlsfragment/wait_stub.go b/common/tlsfragment/wait_stub.go new file mode 100644 index 0000000000..6a1cd88942 --- /dev/null +++ b/common/tlsfragment/wait_stub.go @@ -0,0 +1,18 @@ +//go:build !(linux || darwin || windows) + +package tf + +import ( + "context" + "net" + "time" +) + +func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error { + _, err := conn.Write(payload) + if err != nil { + return err + } + time.Sleep(fallbackDelay) + return nil +} diff --git a/common/tlsfragment/wait_windows.go b/common/tlsfragment/wait_windows.go new file mode 100644 index 0000000000..49706ca574 --- /dev/null +++ b/common/tlsfragment/wait_windows.go @@ -0,0 +1,31 @@ +package tf + +import ( + "context" + "errors" + "net" + "time" + + "github.com/sagernet/sing/common/winiphlpapi" + + "golang.org/x/sys/windows" +) + +func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error { + start := time.Now() + err := winiphlpapi.WriteAndWaitAck(ctx, conn, payload) + if err != nil { + if errors.Is(err, windows.ERROR_ACCESS_DENIED) { + if _, err := conn.Write(payload); err != nil { + return err + } + time.Sleep(fallbackDelay) + return nil + } + return err + } + if time.Since(start) <= 20*time.Millisecond { + time.Sleep(fallbackDelay) + } + return nil +} diff --git a/common/tlsspoof/README.md b/common/tlsspoof/README.md new file mode 100644 index 0000000000..de684e15cd --- /dev/null +++ b/common/tlsspoof/README.md @@ -0,0 +1,3 @@ +# tls spoof + +idea from https://github.com/therealaleph/sni-spoofing-rust diff --git a/common/tlsspoof/client_hello.go b/common/tlsspoof/client_hello.go new file mode 100644 index 0000000000..abdfa31753 --- /dev/null +++ b/common/tlsspoof/client_hello.go @@ -0,0 +1,37 @@ +package tlsspoof + +import ( + "bytes" + "context" + "crypto/tls" + + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" +) + +// buildFakeClientHello drives crypto/tls against a write-only in-memory conn +// to capture a generated ClientHello. CurvePreferences pins classical groups +// to suppress Go's default X25519MLKEM768 hybrid key share; without this the +// post-quantum public key alone (~1184 bytes) pushes the record past one MSS, +// and middleboxes do not reassemble fragmented ClientHellos. The handshake +// error is discarded because the stub conn's Read returns immediately. +func buildFakeClientHello(sni string) ([]byte, error) { + if sni == "" { + return nil, E.New("empty sni") + } + var buf bytes.Buffer + tlsConn := tls.Client(bufio.NewWriteOnlyConn(&buf), &tls.Config{ + ServerName: sni, + // Order matches what browsers advertised before post-quantum. + CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256, tls.CurveP384}, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS13, + NextProtos: []string{"h2", "http/1.1"}, + InsecureSkipVerify: true, + }) + _ = tlsConn.HandshakeContext(context.Background()) + if buf.Len() == 0 { + return nil, E.New("tls ClientHello not produced") + } + return buf.Bytes(), nil +} diff --git a/common/tlsspoof/client_hello_test.go b/common/tlsspoof/client_hello_test.go new file mode 100644 index 0000000000..3eb7a2e040 --- /dev/null +++ b/common/tlsspoof/client_hello_test.go @@ -0,0 +1,83 @@ +package tlsspoof + +import ( + "bytes" + "encoding/binary" + "strings" + "testing" + + tf "github.com/sagernet/sing-box/common/tlsfragment" + + "github.com/stretchr/testify/require" +) + +// x25519MLKEM768 is the IANA code point for the post-quantum hybrid named +// group (0x11EC). The fake ClientHello must never carry it — its 1184-byte +// key share is the reason kernel-generated ClientHellos exceed one MSS, and +// the reason this builder has to force CurvePreferences. +const x25519MLKEM768 uint16 = 0x11EC + +func TestBuildFakeClientHello_ParsesWithSNI(t *testing.T) { + t.Parallel() + record, err := buildFakeClientHello("example.com") + require.NoError(t, err) + + serverName := tf.IndexTLSServerName(record) + require.NotNil(t, serverName, "output must parse as a ClientHello") + require.Equal(t, "example.com", serverName.ServerName) + + recordLen := binary.BigEndian.Uint16(record[3:5]) + require.Equal(t, len(record)-5, int(recordLen), + "record length header must match on-wire record size") + handshakeLen := int(record[6])<<16 | int(record[7])<<8 | int(record[8]) + require.Equal(t, len(record)-5-4, handshakeLen, + "handshake length header must match handshake body size") +} + +// TestBuildFakeClientHello_FitsOneSegment is the regression guard for the +// whole point of the rewrite: the fake must never need fragmenting on a +// standard 1500-byte path MTU. 1200 leaves ~260 bytes for IP+TCP headers and +// a generous safety margin — the X25519MLKEM768 ClientHello this replaces +// hit ~1400+. +func TestBuildFakeClientHello_FitsOneSegment(t *testing.T) { + t.Parallel() + for _, sni := range []string{"a.io", "example.com", strings.Repeat("a", 253)} { + record, err := buildFakeClientHello(sni) + require.NoError(t, err, "sni=%q", sni) + require.Less(t, len(record), 1200, "sni=%q built %d bytes", sni, len(record)) + } +} + +// TestBuildFakeClientHello_NoPostQuantumKeyShare catches regressions that +// would accidentally pull an X25519MLKEM768 key share (the reason the prior +// implementation had to fragment) back into the fake — e.g. if CurvePreferences +// stopped being respected by a future Go version. +func TestBuildFakeClientHello_NoPostQuantumKeyShare(t *testing.T) { + t.Parallel() + record, err := buildFakeClientHello("example.com") + require.NoError(t, err) + + var needle [2]byte + binary.BigEndian.PutUint16(needle[:], x25519MLKEM768) + require.False(t, bytes.Contains(record, needle[:]), + "output must not contain the X25519MLKEM768 code point (0x%04x)", x25519MLKEM768) +} + +// TestBuildFakeClientHello_RandomizesPerCall ensures crypto/tls generates a +// fresh random + session_id + key_share on every call, as required to avoid +// trivial fingerprinting of the spoof. +func TestBuildFakeClientHello_RandomizesPerCall(t *testing.T) { + t.Parallel() + first, err := buildFakeClientHello("example.com") + require.NoError(t, err) + second, err := buildFakeClientHello("example.com") + require.NoError(t, err) + require.NotEqual(t, first, second, + "repeated calls must produce distinct bytes (random/session_id/key_share must vary)") +} + +func TestBuildFakeClientHello_RejectsEmpty(t *testing.T) { + t.Parallel() + _, err := buildFakeClientHello("") + require.Error(t, err) +} diff --git a/common/tlsspoof/conn_test.go b/common/tlsspoof/conn_test.go new file mode 100644 index 0000000000..b41cf54753 --- /dev/null +++ b/common/tlsspoof/conn_test.go @@ -0,0 +1,369 @@ +package tlsspoof + +import ( + "bytes" + "context" + "encoding/binary" + "encoding/hex" + "io" + "net" + "testing" + "time" + + tf "github.com/sagernet/sing-box/common/tlsfragment" + + "github.com/stretchr/testify/require" +) + +// realClientHello is a captured Chrome ClientHello for github.com. Tests that +// stack tlsspoof.Conn on top of tf.Conn still need a parseable payload to +// exercise the fragment transform. +const realClientHello = "16030105f8010005f403036e35de7389a679c54029cf452611f2211c70d9ac3897271de589ab6155f8e4ab20637d225f1ef969ad87ed78bfb9d171300bcb1703b6f314ccefb964f79b7d0961002a0a0a130213031301c02cc02bcca9c030c02fcca8c00ac009c014c013009d009c0035002fc008c012000a01000581baba00000000000f000d00000a6769746875622e636f6d00170000ff01000100000a000e000c3a3a11ec001d001700180019000b000201000010000e000c02683208687474702f312e31000500050100000000000d00160014040308040401050308050805050108060601020100120000003304ef04ed3a3a00010011ec04c0aeb2250c092a3463161cccb29d9183331a424964248579507ed23a180b0ceab2a5f5d9ce41547e497a89055471ea572867ba3a1fc3c9e45025274a20f60c6b60e62476b6afed0403af59ab83660ef4112ae20386a602010d0a5d454c0ed34c84ed4423e750213e6a2baab1bf9c4367a6007ab40a33d95220c2dcaa44f257024a5626b545db0510f4311b1a60714154909c6a61fdfca011fb2626d657aeb6070bf078508babe3b584555013e34acc56198ed4663742b3155a664a9901794c4586820a7dc162c01827291f3792e1237f801a8d1ef096013c181c4a58d2f6859ba75022d18cc4418bd4f351d5c18f83a58857d05af860c4b9ac018a5b63f17184e591532c6bc2cf2215d4a282c8a8a4f6f7aee110422c8bc9ebd3b1d609c568523aaae555db320e6c269473d87af38c256cbb9febc20aea6380c32a8916f7a373c8b1e37554e3260bf6621f6b804ee80b3c516b1d01985bf4c603b6daa9a5991de6a7a29f3a7122b8afb843a7660110fce62b43c615f5bcc2db688ba012649c0952b0a2c031e732d2b454c6b2968683cb8d244be2c9a7fa163222979eaf92722b92b862d81a3d94450c2b60c318421ebb4307c42d1f0473592a5c30e42039cc68cda9721e61aa63f49def17c15221680ed444896340133bbee67556f56b9f9d78a4df715f926a12add0cc9c862e46ea8b7316ae468282c18601b2771c9c9322f982228cf93effaacd3f80cbd12bce5fc36f56e2a3caf91e578a5fae00c9b23a8ed1a66764f4433c3628a70b8f0a6196adc60a4cb4226f07ba4c6b363fe9065563bfc1347452946386bab488686e837ab979c64f9047417fca635fe1bb4f074f256cc8af837c7b455e280426547755af90a61640169ef180aea3a77e662bb6dac1b6c3696027129b1a5edf495314e9c7f4b6110e16378ec893fa24642330a40aba1a85326101acb97c620fd8d71389e69eaed7bdb01bbe1fd428d66191150c7b2cd1ad4257391676a82ba8ce07fb2667c3b289f159003a7c7bc31d361b7b7f49a802961739d950dfcc0fa1c7abce5abdd2245101da391151490862028110465950b9e9c03d08a90998ab83267838d2e74a0593bc81f74cdf734519a05b351c0e5488c68dd810e6e9142ccc1e2f4a7f464297eb340e27acc6b9d64e12e38cce8492b3d939140b5a9e149a75597f10a23874c84323a07cdd657274378f887c85c4259b9c04cd33ba58ed630ef2a744f8e19dd34843dff331d2a6be7e2332c599289cd248a611c73d7481cd4a9bd43449a3836f14b2af18a1739e17999e4c67e85cc5bcecabb14185e5bcaff3c96098f03dc5aba819f29587758f49f940585354a2a780830528d68ccd166920dadcaa25cab5fc1907272a826aba3f08bc6b88757776812ecb6c7cec69a223ec0a13a7b62a2349a0f63ed7a27a3b15ba21d71fe6864ec6e089ae17cadd433fa3138f7ee24353c11365818f8fc34f43a05542d18efaac24bfccc1f748a0cc1a67ad379468b76fd34973dba785f5c91d618333cd810fe0700d1bbc8422029782628070a624c52c5309a4a64d625b11f8033ab28df34a1add297517fcc06b92b6817b3c5144438cf260867c57bde68c8c4b82e6a135ef676a52fbae5708002a404e6189a60e2836de565ad1b29e3819e5ed49f6810bcb28e1bd6de57306f94b79d9dae1cc4624d2a068499beef81cd5fe4b76dcbfff2a2008001d002001976128c6d5a934533f28b9914d2480aab2a8c1ab03d212529ce8b27640a716002d00020101002b000706caca03040303001b00030200015a5a000100" + +func decodeClientHello(t *testing.T) []byte { + t.Helper() + payload, err := hex.DecodeString(realClientHello) + require.NoError(t, err) + return payload +} + +type fakeSpoofer struct { + injected [][]byte + err error + closeErr error +} + +func (f *fakeSpoofer) Inject(payload []byte) error { + if f.err != nil { + return f.err + } + f.injected = append(f.injected, append([]byte(nil), payload...)) + return nil +} + +func (f *fakeSpoofer) Close() error { + return f.closeErr +} + +func readAll(t *testing.T, conn net.Conn) []byte { + t.Helper() + data, err := io.ReadAll(conn) + require.NoError(t, err) + return data +} + +func TestConn_Write_InjectsThenForwards(t *testing.T) { + t.Parallel() + payload := decodeClientHello(t) + + client, server := net.Pipe() + spoofer := &fakeSpoofer{} + wrapped, err := newConn(client, spoofer, "letsencrypt.org") + require.NoError(t, err) + + serverRead := make(chan []byte, 1) + go func() { + serverRead <- readAll(t, server) + }() + + n, err := wrapped.Write(payload) + require.NoError(t, err) + require.Equal(t, len(payload), n) + require.NoError(t, wrapped.Close()) + + forwarded := <-serverRead + require.Equal(t, payload, forwarded, "underlying conn must receive the real ClientHello unchanged") + require.Len(t, spoofer.injected, 1) + + injected := spoofer.injected[0] + serverName := tf.IndexTLSServerName(injected) + require.NotNil(t, serverName, "injected payload must parse as ClientHello") + require.Equal(t, "letsencrypt.org", serverName.ServerName) +} + +func TestConn_Write_SecondWriteDoesNotInject(t *testing.T) { + t.Parallel() + payload := decodeClientHello(t) + + client, server := net.Pipe() + spoofer := &fakeSpoofer{} + wrapped, err := newConn(client, spoofer, "letsencrypt.org") + require.NoError(t, err) + + serverRead := make(chan []byte, 1) + go func() { + serverRead <- readAll(t, server) + }() + + _, err = wrapped.Write(payload) + require.NoError(t, err) + _, err = wrapped.Write([]byte("second")) + require.NoError(t, err) + require.NoError(t, wrapped.Close()) + + forwarded := <-serverRead + require.Equal(t, append(append([]byte(nil), payload...), []byte("second")...), forwarded) + require.Len(t, spoofer.injected, 1) +} + +// TestConn_Write_SurfacesCloseError guards against the defer pattern silently +// dropping the spoofer's Close() error on the success path. +func TestConn_Write_SurfacesCloseError(t *testing.T) { + t.Parallel() + + client, server := net.Pipe() + defer client.Close() + defer server.Close() + spoofer := &fakeSpoofer{closeErr: errSpoofClose} + wrapped, err := newConn(client, spoofer, "letsencrypt.org") + require.NoError(t, err) + + go func() { _, _ = io.ReadAll(server) }() + + _, err = wrapped.Write([]byte("trigger inject")) + require.ErrorIs(t, err, errSpoofClose, + "Close() error must be wrapped into Write's return") +} + +func TestConn_NewConn_RejectsEmptySNI(t *testing.T) { + t.Parallel() + client, server := net.Pipe() + defer client.Close() + defer server.Close() + _, err := newConn(client, &fakeSpoofer{}, "") + require.Error(t, err, "empty SNI must fail at construction") +} + +var errSpoofClose = errTest("spoof-close-failed") + +type errTest string + +func (e errTest) Error() string { return string(e) } + +// recordingConn intercepts each Write call so tests can assert how many +// downstream writes occurred and in what order with respect to spoof +// injection. It does not implement WithUpstream, so tf.Conn's +// N.UnwrapReader(conn).(*net.TCPConn) returns nil and fragment-mode falls +// back to its plain Write + time.Sleep path — which is what we want to +// exercise over a net.Pipe. +type recordingConn struct { + net.Conn + writes [][]byte + timeline *[]string +} + +func (c *recordingConn) Write(p []byte) (int, error) { + c.writes = append(c.writes, append([]byte(nil), p...)) + if c.timeline != nil { + *c.timeline = append(*c.timeline, "write") + } + return c.Conn.Write(p) +} + +type tlsRecord struct { + contentType byte + payload []byte +} + +func parseTLSRecords(t *testing.T, data []byte) []tlsRecord { + t.Helper() + var records []tlsRecord + for len(data) > 0 { + require.GreaterOrEqual(t, len(data), 5, "record header incomplete") + recordLen := int(binary.BigEndian.Uint16(data[3:5])) + require.GreaterOrEqual(t, len(data), 5+recordLen, "record payload truncated") + records = append(records, tlsRecord{ + contentType: data[0], + payload: append([]byte(nil), data[5:5+recordLen]...), + }) + data = data[5+recordLen:] + } + return records +} + +// TestConn_StackedWithRecordFragment mirrors the wrapping order that +// STDClientConfig.Client() produces when record_fragment is enabled: +// tls.Client → tlsspoof.Conn → tf.Conn → raw conn. +// Asserts the decoy is injected and the real handshake arrives split into +// multiple TLS records whose payloads reassemble to the original. +func TestConn_StackedWithRecordFragment(t *testing.T) { + t.Parallel() + payload := decodeClientHello(t) + + client, server := net.Pipe() + defer server.Close() + + fragConn := tf.NewConn(client, context.Background(), false, true, time.Millisecond) + spoofer := &fakeSpoofer{} + wrapped, err := newConn(fragConn, spoofer, "letsencrypt.org") + require.NoError(t, err) + + serverRead := make(chan []byte, 1) + go func() { serverRead <- readAll(t, server) }() + + _, err = wrapped.Write(payload) + require.NoError(t, err) + require.NoError(t, wrapped.Close()) + forwarded := <-serverRead + + require.Len(t, spoofer.injected, 1, "spoof must inject exactly once") + injected := tf.IndexTLSServerName(spoofer.injected[0]) + require.NotNil(t, injected, "injected payload must parse as ClientHello") + require.Equal(t, "letsencrypt.org", injected.ServerName) + + records := parseTLSRecords(t, forwarded) + require.Greater(t, len(records), 1, "record_fragment must produce multiple records") + var reassembled []byte + for _, r := range records { + require.Equal(t, byte(0x16), r.contentType, "all records must be handshake") + reassembled = append(reassembled, r.payload...) + } + require.Equal(t, payload[5:], reassembled, "record payloads must reassemble to original handshake") +} + +// TestConn_StackedWithPacketFragment is the primary regression test for the +// fragment-only gate fix in STDClientConfig.Client(). It verifies that +// packet-level fragmentation combined with spoof produces: +// - one spoof injection carrying the decoy SNI, +// - multiple separate writes to the underlying conn, +// - an unmodified byte stream when those writes are concatenated +// (no extra record framing). +func TestConn_StackedWithPacketFragment(t *testing.T) { + t.Parallel() + payload := decodeClientHello(t) + + client, server := net.Pipe() + defer server.Close() + + rc := &recordingConn{Conn: client} + fragConn := tf.NewConn(rc, context.Background(), true, false, time.Millisecond) + spoofer := &fakeSpoofer{} + wrapped, err := newConn(fragConn, spoofer, "letsencrypt.org") + require.NoError(t, err) + + serverRead := make(chan []byte, 1) + go func() { serverRead <- readAll(t, server) }() + + _, err = wrapped.Write(payload) + require.NoError(t, err) + require.NoError(t, wrapped.Close()) + forwarded := <-serverRead + + require.Len(t, spoofer.injected, 1, "spoof must inject exactly once") + injected := tf.IndexTLSServerName(spoofer.injected[0]) + require.NotNil(t, injected) + require.Equal(t, "letsencrypt.org", injected.ServerName) + + require.Greater(t, len(rc.writes), 1, "fragment must split the ClientHello into multiple writes") + require.Equal(t, payload, bytes.Join(rc.writes, nil), + "concatenated writes must equal original bytes (no extra framing)") + require.Equal(t, payload, forwarded) +} + +// TestConn_StackedWithBothFragment exercises the combination that produces +// the strongest obfuscation: each chunk becomes its own TLS record and its +// own TCP write. +func TestConn_StackedWithBothFragment(t *testing.T) { + t.Parallel() + payload := decodeClientHello(t) + + client, server := net.Pipe() + defer server.Close() + + rc := &recordingConn{Conn: client} + fragConn := tf.NewConn(rc, context.Background(), true, true, time.Millisecond) + spoofer := &fakeSpoofer{} + wrapped, err := newConn(fragConn, spoofer, "letsencrypt.org") + require.NoError(t, err) + + serverRead := make(chan []byte, 1) + go func() { serverRead <- readAll(t, server) }() + + _, err = wrapped.Write(payload) + require.NoError(t, err) + require.NoError(t, wrapped.Close()) + forwarded := <-serverRead + + require.Len(t, spoofer.injected, 1) + injected := tf.IndexTLSServerName(spoofer.injected[0]) + require.NotNil(t, injected) + require.Equal(t, "letsencrypt.org", injected.ServerName) + + require.Greater(t, len(rc.writes), 1, "split-packet must produce multiple writes") + records := parseTLSRecords(t, forwarded) + require.Greater(t, len(records), 1, "split-record must produce multiple records") + var reassembled []byte + for _, r := range records { + require.Equal(t, byte(0x16), r.contentType) + reassembled = append(reassembled, r.payload...) + } + require.Equal(t, payload[5:], reassembled, + "record payloads must reassemble to the original handshake") +} + +// trackingSpoofer adds the spoof injection to a shared event timeline so +// TestConn_StackedInjectionOrder can prove the decoy precedes the first +// downstream write. +type trackingSpoofer struct { + injected [][]byte + timeline *[]string +} + +func (s *trackingSpoofer) Inject(payload []byte) error { + s.injected = append(s.injected, append([]byte(nil), payload...)) + *s.timeline = append(*s.timeline, "inject") + return nil +} + +func (s *trackingSpoofer) Close() error { return nil } + +// TestConn_StackedInjectionOrder asserts the documented wire order: the +// decoy injection happens before any write reaches the underlying conn. +func TestConn_StackedInjectionOrder(t *testing.T) { + t.Parallel() + payload := decodeClientHello(t) + + client, server := net.Pipe() + defer server.Close() + + var timeline []string + rc := &recordingConn{Conn: client, timeline: &timeline} + fragConn := tf.NewConn(rc, context.Background(), true, true, time.Millisecond) + spoofer := &trackingSpoofer{timeline: &timeline} + wrapped, err := newConn(fragConn, spoofer, "letsencrypt.org") + require.NoError(t, err) + + serverRead := make(chan []byte, 1) + go func() { serverRead <- readAll(t, server) }() + + _, err = wrapped.Write(payload) + require.NoError(t, err) + require.NoError(t, wrapped.Close()) + <-serverRead + + require.NotEmpty(t, timeline) + require.Equal(t, "inject", timeline[0], "decoy must be injected before any downstream write") + require.Contains(t, timeline[1:], "write", "at least one downstream write must follow the inject") +} + +func TestParseMethod(t *testing.T) { + t.Parallel() + cases := map[string]struct { + want Method + ok bool + }{ + "": {MethodWrongSequence, true}, + "wrong-sequence": {MethodWrongSequence, true}, + "wrong-checksum": {MethodWrongChecksum, true}, + "nonsense": {0, false}, + } + for input, expected := range cases { + m, err := ParseMethod(input) + if !expected.ok { + require.Error(t, err, "input=%q", input) + continue + } + require.NoError(t, err, "input=%q", input) + require.Equal(t, expected.want, m, "input=%q", input) + } +} diff --git a/common/tlsspoof/endpoints.go b/common/tlsspoof/endpoints.go new file mode 100644 index 0000000000..2cc8df1225 --- /dev/null +++ b/common/tlsspoof/endpoints.go @@ -0,0 +1,31 @@ +//go:build linux || darwin || (windows && (amd64 || 386)) + +package tlsspoof + +import ( + "net" + "net/netip" + + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" +) + +// The returned addresses are v4-unmapped and share the same family. +func tcpEndpoints(conn net.Conn) (*net.TCPConn, netip.AddrPort, netip.AddrPort, error) { + tcpConn, isTCP := common.Cast[*net.TCPConn](conn) + if !isTCP { + return nil, netip.AddrPort{}, netip.AddrPort{}, E.New("tls_spoof: underlying conn is not *net.TCPConn") + } + local := M.AddrPortFromNet(tcpConn.LocalAddr()) + remote := M.AddrPortFromNet(tcpConn.RemoteAddr()) + if !local.IsValid() || !remote.IsValid() { + return nil, netip.AddrPort{}, netip.AddrPort{}, E.New("tls_spoof: invalid conn address") + } + local = netip.AddrPortFrom(local.Addr().Unmap(), local.Port()) + remote = netip.AddrPortFrom(remote.Addr().Unmap(), remote.Port()) + if local.Addr().Is4() != remote.Addr().Is4() { + return nil, netip.AddrPort{}, netip.AddrPort{}, E.New("tls_spoof: local/remote address family mismatch") + } + return tcpConn, local, remote, nil +} diff --git a/common/tlsspoof/integration_darwin_test.go b/common/tlsspoof/integration_darwin_test.go new file mode 100644 index 0000000000..60a933e5f9 --- /dev/null +++ b/common/tlsspoof/integration_darwin_test.go @@ -0,0 +1,5 @@ +//go:build darwin + +package tlsspoof + +const loopbackInterface = "lo0" diff --git a/common/tlsspoof/integration_linux_test.go b/common/tlsspoof/integration_linux_test.go new file mode 100644 index 0000000000..3294c272e5 --- /dev/null +++ b/common/tlsspoof/integration_linux_test.go @@ -0,0 +1,5 @@ +//go:build linux + +package tlsspoof + +const loopbackInterface = "lo" diff --git a/common/tlsspoof/integration_test.go b/common/tlsspoof/integration_test.go new file mode 100644 index 0000000000..23a83ff174 --- /dev/null +++ b/common/tlsspoof/integration_test.go @@ -0,0 +1,141 @@ +//go:build linux || darwin + +package tlsspoof + +import ( + "bufio" + "context" + "fmt" + "io" + "net" + "os" + "os/exec" + "strings" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func requireRoot(t *testing.T) { + t.Helper() + if os.Geteuid() != 0 { + t.Skip("integration test requires root; re-run with `go test -exec sudo`") + } +} + +func tcpdumpObserver(t *testing.T, iface string, port uint16, needle string, do func(), wait time.Duration) bool { + t.Helper() + return tcpdumpObserverMulti(t, iface, port, []string{needle}, do, wait)[needle] +} + +// tcpdumpObserverMulti captures tcpdump output while do() executes and reports +// which of the provided needles were observed in the raw ASCII dump. Use this +// to assert that distinct payloads (e.g. fake vs real ClientHello) are both on +// the wire. +func tcpdumpObserverMulti(t *testing.T, iface string, port uint16, needles []string, do func(), wait time.Duration) map[string]bool { + t.Helper() + ctx, cancel := context.WithTimeout(context.Background(), wait) + defer cancel() + cmd := exec.CommandContext(ctx, "tcpdump", "-i", iface, "-n", "-A", "-l", + "-s", "4096", fmt.Sprintf("tcp and port %d", port)) + cmd.Cancel = func() error { + return cmd.Process.Signal(os.Interrupt) + } + stdout, err := cmd.StdoutPipe() + require.NoError(t, err) + stderr, err := cmd.StderrPipe() + require.NoError(t, err) + require.NoError(t, cmd.Start()) + t.Cleanup(func() { + _ = cmd.Process.Signal(os.Interrupt) + _ = cmd.Wait() + }) + + ready := make(chan struct{}) + go func() { + scanner := bufio.NewScanner(stderr) + for scanner.Scan() { + if strings.Contains(scanner.Text(), "listening on") { + close(ready) + io.Copy(io.Discard, stderr) + return + } + } + }() + + select { + case <-ready: + case <-time.After(2 * time.Second): + t.Fatal("tcpdump did not attach within 2s") + } + + var access sync.Mutex + found := make(map[string]bool, len(needles)) + readerDone := make(chan struct{}) + go func() { + defer close(readerDone) + scanner := bufio.NewScanner(stdout) + scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024) + for scanner.Scan() { + line := scanner.Text() + access.Lock() + for _, needle := range needles { + if !found[needle] && strings.Contains(line, needle) { + found[needle] = true + } + } + access.Unlock() + } + }() + + do() + + time.Sleep(200 * time.Millisecond) + _ = cmd.Process.Signal(os.Interrupt) + <-readerDone + access.Lock() + defer access.Unlock() + result := make(map[string]bool, len(needles)) + for _, needle := range needles { + result[needle] = found[needle] + } + return result +} + +func dialLocalEchoServer(t *testing.T) (client net.Conn, serverPort uint16) { + return dialLocalEchoServerFamily(t, "tcp4", "127.0.0.1:0") +} + +func dialLocalEchoServerIPv6(t *testing.T) (client net.Conn, serverPort uint16) { + return dialLocalEchoServerFamily(t, "tcp6", "[::1]:0") +} + +func dialLocalEchoServerFamily(t *testing.T, network, address string) (client net.Conn, serverPort uint16) { + t.Helper() + listener, err := net.Listen(network, address) + require.NoError(t, err) + + accepted := make(chan net.Conn, 1) + go func() { + c, err := listener.Accept() + if err == nil { + accepted <- c + } + close(accepted) + }() + addr := listener.Addr().(*net.TCPAddr) + client, err = net.Dial(network, addr.String()) + require.NoError(t, err) + server := <-accepted + require.NotNil(t, server) + + go io.Copy(io.Discard, server) + t.Cleanup(func() { + client.Close() + server.Close() + listener.Close() + }) + return client, uint16(addr.Port) +} diff --git a/common/tlsspoof/integration_tls_test.go b/common/tlsspoof/integration_tls_test.go new file mode 100644 index 0000000000..d179c3841c --- /dev/null +++ b/common/tlsspoof/integration_tls_test.go @@ -0,0 +1,118 @@ +//go:build linux || darwin + +package tlsspoof + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "io" + "math/big" + "net" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +// generateSelfSignedCert returns a TLS certificate valid for the given SAN. +func generateSelfSignedCert(t *testing.T, commonName string, sans ...string) tls.Certificate { + t.Helper() + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) + require.NoError(t, err) + template := x509.Certificate{ + SerialNumber: serial, + Subject: pkix.Name{CommonName: commonName}, + NotBefore: time.Now().Add(-time.Hour), + NotAfter: time.Now().Add(time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + DNSNames: sans, + } + der, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + require.NoError(t, err) + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}) + keyDER, err := x509.MarshalPKCS8PrivateKey(priv) + require.NoError(t, err) + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}) + cert, err := tls.X509KeyPair(certPEM, keyPEM) + require.NoError(t, err) + return cert +} + +// TestIntegrationConn_RealTLSHandshake drives a real crypto/tls ClientHello +// through the spoofer and asserts the on-wire fake packet carries the fake SNI +// while the server receives the real SNI. This exercises the full +// `tls.Client(wrapped, config).Handshake()` path rather than a static hex +// payload, matching what user-facing code hits. +func TestIntegrationConn_RealTLSHandshake(t *testing.T) { + requireRoot(t) + const realSNI = "real.test" + const fakeSNI = "fake.test" + + serverCert := generateSelfSignedCert(t, realSNI, realSNI) + tlsConfig := &tls.Config{Certificates: []tls.Certificate{serverCert}} + + listener, err := tls.Listen("tcp4", "127.0.0.1:0", tlsConfig) + require.NoError(t, err) + t.Cleanup(func() { listener.Close() }) + + serverSNI := make(chan string, 1) + go func() { + conn, err := listener.Accept() + if err != nil { + return + } + defer conn.Close() + tlsConn := conn.(*tls.Conn) + _ = tlsConn.SetDeadline(time.Now().Add(3 * time.Second)) + if handshakeErr := tlsConn.Handshake(); handshakeErr != nil { + serverSNI <- "handshake-error:" + handshakeErr.Error() + return + } + serverSNI <- tlsConn.ConnectionState().ServerName + _, _ = io.Copy(io.Discard, conn) + }() + + addr := listener.Addr().(*net.TCPAddr) + serverPort := uint16(addr.Port) + raw, err := net.Dial("tcp4", addr.String()) + require.NoError(t, err) + t.Cleanup(func() { raw.Close() }) + + wrapped, err := NewConn(raw, MethodWrongSequence, fakeSNI) + require.NoError(t, err) + + clientConfig := &tls.Config{ + ServerName: realSNI, + InsecureSkipVerify: true, + } + tlsClient := tls.Client(wrapped, clientConfig) + t.Cleanup(func() { tlsClient.Close() }) + + seen := tcpdumpObserverMulti(t, loopbackInterface, serverPort, + []string{realSNI, fakeSNI}, func() { + _ = tlsClient.SetDeadline(time.Now().Add(3 * time.Second)) + err := tlsClient.Handshake() + require.NoError(t, err, "TLS handshake must succeed (wrong-sequence fake is dropped by peer)") + }, 4*time.Second) + + require.True(t, seen[realSNI], + "real ClientHello on the wire must contain original SNI %q", realSNI) + require.True(t, seen[fakeSNI], + "fake ClientHello on the wire must contain fake SNI %q", fakeSNI) + + select { + case sniOnServer := <-serverSNI: + require.Equal(t, realSNI, sniOnServer, + "TLS server must see the real SNI (fake packet dropped by peer TCP stack)") + case <-time.After(3 * time.Second): + t.Fatal("TLS server did not complete handshake") + } +} diff --git a/common/tlsspoof/integration_unix_test.go b/common/tlsspoof/integration_unix_test.go new file mode 100644 index 0000000000..6b262ff71b --- /dev/null +++ b/common/tlsspoof/integration_unix_test.go @@ -0,0 +1,185 @@ +//go:build linux || darwin + +package tlsspoof + +import ( + "encoding/hex" + "io" + "net" + "runtime" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestIntegrationSpoofer(t *testing.T) { + requireRoot(t) + methods := []struct { + name string + method Method + }{ + {"WrongChecksum", MethodWrongChecksum}, + {"WrongSequence", MethodWrongSequence}, + {"WrongAcknowledgment", MethodWrongAcknowledgment}, + {"WrongMD5Sig", MethodWrongMD5Sig}, + {"WrongTimestamp", MethodWrongTimestamp}, + } + families := []struct { + name string + dial func(*testing.T) (net.Conn, uint16) + }{ + {"IPv4", dialLocalEchoServer}, + {"IPv6", dialLocalEchoServerIPv6}, + } + for _, family := range families { + for _, tc := range methods { + t.Run(family.name+"/"+tc.name, func(t *testing.T) { + if tc.method == MethodWrongTimestamp && runtime.GOOS == "darwin" { + t.Skip("wrong-timestamp is not supported on macOS") + } + client, serverPort := family.dial(t) + spoofer, err := newRawSpoofer(client, tc.method) + require.NoError(t, err) + defer spoofer.Close() + + fake, err := buildFakeClientHello("letsencrypt.org") + require.NoError(t, err) + + captured := tcpdumpObserver(t, loopbackInterface, serverPort, "letsencrypt.org", func() { + require.NoError(t, spoofer.Inject(fake)) + }, 3*time.Second) + require.True(t, captured, "injected fake ClientHello must be observable on loopback") + }) + } + } +} + +// Loopback bypasses TCP checksum validation, so wrong-sequence is used instead. +func TestIntegrationConn_InjectsThenForwardsRealCH(t *testing.T) { + requireRoot(t) + runInjectsThenForwardsRealCH(t, "tcp4", "127.0.0.1:0") +} + +func TestIntegrationConn_IPv6_InjectsThenForwardsRealCH(t *testing.T) { + requireRoot(t) + runInjectsThenForwardsRealCH(t, "tcp6", "[::1]:0") +} + +// TestIntegrationConn_FakeAndRealHaveDistinctSNIs asserts that the on-wire fake +// packet carries the fake SNI (letsencrypt.org) AND the real packet still +// carries the original SNI (github.com). If the builder regresses to producing +// empty or mismatched bytes, the fake-SNI needle will be missing. +func TestIntegrationConn_FakeAndRealHaveDistinctSNIs(t *testing.T) { + requireRoot(t) + runFakeAndRealHaveDistinctSNIs(t, "tcp4", "127.0.0.1:0", "letsencrypt.org") +} + +func TestIntegrationConn_IPv6_FakeAndRealHaveDistinctSNIs(t *testing.T) { + requireRoot(t) + runFakeAndRealHaveDistinctSNIs(t, "tcp6", "[::1]:0", "letsencrypt.org") +} + +func runFakeAndRealHaveDistinctSNIs(t *testing.T, network, address, fakeSNI string) { + t.Helper() + const originalSNI = "github.com" + require.NotEqual(t, originalSNI, fakeSNI) + + listener, err := net.Listen(network, address) + require.NoError(t, err) + + serverReceived := make(chan []byte, 1) + go func() { + conn, err := listener.Accept() + if err != nil { + return + } + defer conn.Close() + _ = conn.SetReadDeadline(time.Now().Add(2 * time.Second)) + got, _ := io.ReadAll(conn) + serverReceived <- got + }() + + addr := listener.Addr().(*net.TCPAddr) + serverPort := uint16(addr.Port) + client, err := net.Dial(network, addr.String()) + require.NoError(t, err) + t.Cleanup(func() { + client.Close() + listener.Close() + }) + + wrapped, err := NewConn(client, MethodWrongSequence, fakeSNI) + require.NoError(t, err) + + payload, err := hex.DecodeString(realClientHello) + require.NoError(t, err) + + seen := tcpdumpObserverMulti(t, loopbackInterface, serverPort, + []string{originalSNI, fakeSNI}, func() { + n, err := wrapped.Write(payload) + require.NoError(t, err) + require.Equal(t, len(payload), n) + }, 3*time.Second) + require.True(t, seen[originalSNI], + "real ClientHello must carry original SNI %q on the wire", originalSNI) + require.True(t, seen[fakeSNI], + "fake ClientHello must carry fake SNI %q on the wire", fakeSNI) + + _ = wrapped.Close() + select { + case got := <-serverReceived: + require.Equal(t, payload, got, + "server must receive real ClientHello unchanged (wrong-sequence fake must be dropped)") + case <-time.After(2 * time.Second): + t.Fatal("echo server did not receive real ClientHello") + } +} + +func runInjectsThenForwardsRealCH(t *testing.T, network, address string) { + t.Helper() + listener, err := net.Listen(network, address) + require.NoError(t, err) + + serverReceived := make(chan []byte, 1) + go func() { + conn, err := listener.Accept() + if err != nil { + return + } + defer conn.Close() + _ = conn.SetReadDeadline(time.Now().Add(2 * time.Second)) + got, _ := io.ReadAll(conn) + serverReceived <- got + }() + + addr := listener.Addr().(*net.TCPAddr) + serverPort := uint16(addr.Port) + client, err := net.Dial(network, addr.String()) + require.NoError(t, err) + t.Cleanup(func() { + client.Close() + listener.Close() + }) + + wrapped, err := NewConn(client, MethodWrongSequence, "letsencrypt.org") + require.NoError(t, err) + + payload, err := hex.DecodeString(realClientHello) + require.NoError(t, err) + + captured := tcpdumpObserver(t, loopbackInterface, serverPort, "letsencrypt.org", func() { + n, err := wrapped.Write(payload) + require.NoError(t, err) + require.Equal(t, len(payload), n) + }, 3*time.Second) + require.True(t, captured, "fake ClientHello with letsencrypt.org SNI must be on the wire") + + _ = wrapped.Close() + select { + case got := <-serverReceived: + require.Equal(t, payload, got, "server must receive real ClientHello unchanged (wrong-sequence fake must be dropped)") + case <-time.After(2 * time.Second): + t.Fatal("echo server did not receive real ClientHello") + } +} diff --git a/common/tlsspoof/integration_windows_test.go b/common/tlsspoof/integration_windows_test.go new file mode 100644 index 0000000000..b0461a31b2 --- /dev/null +++ b/common/tlsspoof/integration_windows_test.go @@ -0,0 +1,138 @@ +//go:build windows && (amd64 || 386) + +package tlsspoof + +import ( + "encoding/hex" + "io" + "net" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func newSpoofer(t *testing.T, conn net.Conn, method Method) rawSpoofer { + t.Helper() + s, err := newRawSpoofer(conn, method) + require.NoError(t, err) + return s +} + +// Basic lifecycle: opening a spoofer against a live TCP conn installs +// the driver, spawns run(), then shuts down cleanly without ever +// injecting. Exercises the close path that cancels an in-flight Recv. +func TestIntegrationSpooferOpenClose(t *testing.T) { + listener, err := net.Listen("tcp4", "127.0.0.1:0") + require.NoError(t, err) + t.Cleanup(func() { listener.Close() }) + + accepted := make(chan net.Conn, 1) + go func() { + c, _ := listener.Accept() + accepted <- c + }() + client, err := net.Dial("tcp4", listener.Addr().String()) + require.NoError(t, err) + t.Cleanup(func() { client.Close() }) + server := <-accepted + t.Cleanup(func() { + if server != nil { + server.Close() + } + }) + + spoofer := newSpoofer(t, client, MethodWrongSequence) + require.NoError(t, spoofer.Close()) +} + +// End-to-end: Conn.Write injects a fake ClientHello with a fresh SNI, then +// forwards the real ClientHello. With wrong-sequence, the fake lands before +// the connection's send-next sequence — the peer TCP stack treats it as +// already-received and only surfaces the real bytes to the echo server. +func TestIntegrationConnInjectsThenForwardsRealCH(t *testing.T) { + listener, err := net.Listen("tcp4", "127.0.0.1:0") + require.NoError(t, err) + t.Cleanup(func() { listener.Close() }) + + serverReceived := make(chan []byte, 1) + go func() { + conn, acceptErr := listener.Accept() + if acceptErr != nil { + return + } + defer conn.Close() + _ = conn.SetReadDeadline(time.Now().Add(5 * time.Second)) + got, _ := io.ReadAll(conn) + serverReceived <- got + }() + + client, err := net.Dial("tcp4", listener.Addr().String()) + require.NoError(t, err) + t.Cleanup(func() { client.Close() }) + + wrapped, err := NewConn(client, MethodWrongSequence, "letsencrypt.org") + require.NoError(t, err) + + payload, err := hex.DecodeString(realClientHello) + require.NoError(t, err) + + n, err := wrapped.Write(payload) + require.NoError(t, err) + require.Equal(t, len(payload), n) + _ = wrapped.Close() + + select { + case got := <-serverReceived: + require.Equal(t, payload, got, + "server must receive real ClientHello unchanged (wrong-sequence fake must be dropped)") + case <-time.After(5 * time.Second): + t.Fatal("echo server did not receive real ClientHello within 5s") + } +} + +// Inject before any kernel payload: stages the fake, then Write flushes +// the real CH. Same terminal expectation as the Conn variant but via the +// raw spoofer primitive directly. +func TestIntegrationSpooferInjectThenWrite(t *testing.T) { + listener, err := net.Listen("tcp4", "127.0.0.1:0") + require.NoError(t, err) + t.Cleanup(func() { listener.Close() }) + + serverReceived := make(chan []byte, 1) + go func() { + conn, acceptErr := listener.Accept() + if acceptErr != nil { + return + } + defer conn.Close() + _ = conn.SetReadDeadline(time.Now().Add(5 * time.Second)) + got, _ := io.ReadAll(conn) + serverReceived <- got + }() + + client, err := net.Dial("tcp4", listener.Addr().String()) + require.NoError(t, err) + t.Cleanup(func() { client.Close() }) + + spoofer := newSpoofer(t, client, MethodWrongSequence) + t.Cleanup(func() { spoofer.Close() }) + + fake, err := buildFakeClientHello("letsencrypt.org") + require.NoError(t, err) + require.NoError(t, spoofer.Inject(fake)) + + payload, err := hex.DecodeString(realClientHello) + require.NoError(t, err) + n, err := client.Write(payload) + require.NoError(t, err) + require.Equal(t, len(payload), n) + _ = client.Close() + + select { + case got := <-serverReceived: + require.Equal(t, payload, got) + case <-time.After(5 * time.Second): + t.Fatal("echo server did not receive real ClientHello within 5s") + } +} diff --git a/common/tlsspoof/packet.go b/common/tlsspoof/packet.go new file mode 100644 index 0000000000..2b0210c070 --- /dev/null +++ b/common/tlsspoof/packet.go @@ -0,0 +1,177 @@ +//go:build linux || darwin || (windows && (amd64 || 386)) + +package tlsspoof + +import ( + "encoding/binary" + "net/netip" + + "github.com/sagernet/sing-tun/gtcpip/checksum" + "github.com/sagernet/sing-tun/gtcpip/header" + E "github.com/sagernet/sing/common/exceptions" +) + +const ( + defaultTTL uint8 = 64 + defaultWindowSize uint16 = 0xFFFF + tcpHeaderLen = header.TCPMinimumSize + + tcpOptionMD5Signature = 19 + tcpOptionMD5SignatureLength = 18 + tcpTimestampBackdate = 3600000 +) + +type spoofPacketInfo struct { + seqNum uint32 + ackNum uint32 + corrupt bool + options []byte +} + +func buildTCPSegment( + src netip.AddrPort, + dst netip.AddrPort, + packetInfo spoofPacketInfo, + payload []byte, +) []byte { + if src.Addr().Is4() != dst.Addr().Is4() { + panic("tlsspoof: mixed IPv4/IPv6 address family") + } + var ( + frame []byte + ipHeaderLen int + ) + ipPayloadLen := tcpHeaderLen + len(packetInfo.options) + len(payload) + if src.Addr().Is4() { + ipHeaderLen = header.IPv4MinimumSize + frame = make([]byte, ipHeaderLen+ipPayloadLen) + ip := header.IPv4(frame[:ipHeaderLen]) + ip.Encode(&header.IPv4Fields{ + TotalLength: uint16(len(frame)), + ID: 0, + TTL: defaultTTL, + Protocol: uint8(header.TCPProtocolNumber), + SrcAddr: src.Addr(), + DstAddr: dst.Addr(), + }) + ip.SetChecksum(^ip.CalculateChecksum()) + } else { + ipHeaderLen = header.IPv6MinimumSize + frame = make([]byte, ipHeaderLen+ipPayloadLen) + ip := header.IPv6(frame[:ipHeaderLen]) + ip.Encode(&header.IPv6Fields{ + PayloadLength: uint16(ipPayloadLen), + TransportProtocol: header.TCPProtocolNumber, + HopLimit: defaultTTL, + SrcAddr: src.Addr(), + DstAddr: dst.Addr(), + }) + } + encodeTCP(frame, ipHeaderLen, src, dst, packetInfo, payload) + return frame +} + +func encodeTCP(frame []byte, ipHeaderLen int, src, dst netip.AddrPort, packetInfo spoofPacketInfo, payload []byte) { + tcp := header.TCP(frame[ipHeaderLen:]) + copy(frame[ipHeaderLen+tcpHeaderLen:], packetInfo.options) + optionsLen := len(packetInfo.options) + copy(frame[ipHeaderLen+tcpHeaderLen+optionsLen:], payload) + tcp.Encode(&header.TCPFields{ + SrcPort: src.Port(), + DstPort: dst.Port(), + SeqNum: packetInfo.seqNum, + AckNum: packetInfo.ackNum, + DataOffset: uint8(tcpHeaderLen + optionsLen), + Flags: header.TCPFlagAck | header.TCPFlagPsh, + WindowSize: defaultWindowSize, + }) + applyTCPChecksum(tcp, src.Addr(), dst.Addr(), payload, packetInfo.corrupt) +} + +func buildSpoofFrame(method Method, src, dst netip.AddrPort, sendNext, receiveNext, timestamp uint32, tcpOptions, payload []byte) ([]byte, error) { + packetInfo, err := resolveSpoofPacketInfo(method, sendNext, receiveNext, timestamp, tcpOptions, payload) + if err != nil { + return nil, err + } + return buildTCPSegment(src, dst, packetInfo, payload), nil +} + +func resolveSpoofPacketInfo(method Method, sendNext, receiveNext, timestamp uint32, tcpOptions, payload []byte) (spoofPacketInfo, error) { + packetInfo := spoofPacketInfo{seqNum: sendNext, ackNum: receiveNext} + switch method { + case MethodWrongSequence: + packetInfo.seqNum = sendNext - uint32(len(payload)) + case MethodWrongChecksum: + packetInfo.corrupt = true + case MethodWrongAcknowledgment: + packetInfo.ackNum = receiveNext - uint32(defaultWindowSize/2) + case MethodWrongMD5Sig: + packetInfo.options = buildMD5SignatureOptions() + case MethodWrongTimestamp: + packetInfo.options = buildWrongTimestampOptions(timestamp, tcpOptions) + default: + return packetInfo, E.New("tls_spoof: unknown method ", method) + } + return packetInfo, nil +} + +func buildMD5SignatureOptions() []byte { + options := make([]byte, tcpOptionMD5SignatureLength+2) + options[0] = tcpOptionMD5Signature + options[1] = tcpOptionMD5SignatureLength + return options +} + +func buildWrongTimestampOptions(timestamp uint32, tcpOptions []byte) []byte { + spoofedTimestamp := timestamp + if spoofedTimestamp > tcpTimestampBackdate { + spoofedTimestamp -= tcpTimestampBackdate + } else { + spoofedTimestamp = 0 + } + if rewriteTCPOptionTimestamp(tcpOptions, spoofedTimestamp) { + return tcpOptions + } + options := make([]byte, header.TCPOptionTSLength+2) + header.EncodeTSOption(spoofedTimestamp, 0, options) + return options +} + +// rewriteTCPOptionTimestamp finds the TS option in tcpOptions and writes +// timestamp into its TSVal field in place. The caller must own tcpOptions +// (parseTCPPacket already returns a private copy on Windows). +func rewriteTCPOptionTimestamp(tcpOptions []byte, timestamp uint32) bool { + for i := 0; i < len(tcpOptions); { + switch tcpOptions[i] { + case header.TCPOptionEOL: + return false + case header.TCPOptionNOP: + i++ + continue + } + if i+1 >= len(tcpOptions) { + return false + } + optionLen := int(tcpOptions[i+1]) + if optionLen < 2 || i+optionLen > len(tcpOptions) { + return false + } + if tcpOptions[i] == header.TCPOptionTS && optionLen == header.TCPOptionTSLength { + binary.BigEndian.PutUint32(tcpOptions[i+2:], timestamp) + return true + } + i += optionLen + } + return false +} + +func applyTCPChecksum(tcp header.TCP, srcAddr, dstAddr netip.Addr, payload []byte, corrupt bool) { + tcpLen := int(tcp.DataOffset()) + len(payload) + pseudo := header.PseudoHeaderChecksum(header.TCPProtocolNumber, srcAddr.AsSlice(), dstAddr.AsSlice(), uint16(tcpLen)) + payloadChecksum := checksum.Checksum(payload, 0) + tcpChecksum := ^tcp.CalculateChecksum(checksum.Combine(pseudo, payloadChecksum)) + if corrupt { + tcpChecksum ^= 0xFFFF + } + tcp.SetChecksum(tcpChecksum) +} diff --git a/common/tlsspoof/packet_darwin.go b/common/tlsspoof/packet_darwin.go new file mode 100644 index 0000000000..4d12bd37ef --- /dev/null +++ b/common/tlsspoof/packet_darwin.go @@ -0,0 +1,15 @@ +package tlsspoof + +import "net/netip" + +// buildSpoofTCPSegment returns a TCP segment without an IP header, for +// platforms where the kernel synthesises the IP header (darwin IPv6). +func buildSpoofTCPSegment(method Method, src, dst netip.AddrPort, sendNext, receiveNext, timestamp uint32, payload []byte) ([]byte, error) { + packetInfo, err := resolveSpoofPacketInfo(method, sendNext, receiveNext, timestamp, nil, payload) + if err != nil { + return nil, err + } + segment := make([]byte, tcpHeaderLen+len(packetInfo.options)+len(payload)) + encodeTCP(segment, 0, src, dst, packetInfo, payload) + return segment, nil +} diff --git a/common/tlsspoof/packet_test.go b/common/tlsspoof/packet_test.go new file mode 100644 index 0000000000..5c6d5b6be4 --- /dev/null +++ b/common/tlsspoof/packet_test.go @@ -0,0 +1,136 @@ +package tlsspoof + +import ( + "net/netip" + "testing" + + "github.com/sagernet/sing-tun/gtcpip" + "github.com/sagernet/sing-tun/gtcpip/checksum" + "github.com/sagernet/sing-tun/gtcpip/header" + + "github.com/stretchr/testify/require" +) + +func TestBuildTCPSegment_IPv4_ValidChecksum(t *testing.T) { + t.Parallel() + src := netip.MustParseAddrPort("10.0.0.1:54321") + dst := netip.MustParseAddrPort("1.2.3.4:443") + payload := []byte("fake-client-hello") + frame := buildTCPSegment(src, dst, 100_000, 200_000, payload, false) + + ip := header.IPv4(frame[:header.IPv4MinimumSize]) + require.True(t, ip.IsChecksumValid()) + + tcp := header.TCP(frame[header.IPv4MinimumSize:]) + payloadChecksum := checksum.Checksum(payload, 0) + require.True(t, tcp.IsChecksumValid( + tcpip.AddrFrom4(src.Addr().As4()), + tcpip.AddrFrom4(dst.Addr().As4()), + payloadChecksum, + uint16(len(payload)), + )) +} + +func TestBuildTCPSegment_IPv4_CorruptChecksum(t *testing.T) { + t.Parallel() + src := netip.MustParseAddrPort("10.0.0.1:54321") + dst := netip.MustParseAddrPort("1.2.3.4:443") + payload := []byte("fake-client-hello") + frame := buildTCPSegment(src, dst, 100_000, 200_000, payload, true) + + tcp := header.TCP(frame[header.IPv4MinimumSize:]) + payloadChecksum := checksum.Checksum(payload, 0) + require.False(t, tcp.IsChecksumValid( + tcpip.AddrFrom4(src.Addr().As4()), + tcpip.AddrFrom4(dst.Addr().As4()), + payloadChecksum, + uint16(len(payload)), + )) + // IP checksum must still be valid so the router forwards the packet. + require.True(t, header.IPv4(frame[:header.IPv4MinimumSize]).IsChecksumValid()) +} + +func TestBuildTCPSegment_IPv6_ValidChecksum(t *testing.T) { + t.Parallel() + src := netip.MustParseAddrPort("[fe80::1]:54321") + dst := netip.MustParseAddrPort("[2606:4700::1]:443") + payload := []byte("fake-client-hello") + frame := buildTCPSegment(src, dst, 0xDEADBEEF, 0x12345678, payload, false) + + tcp := header.TCP(frame[header.IPv6MinimumSize:]) + payloadChecksum := checksum.Checksum(payload, 0) + require.True(t, tcp.IsChecksumValid( + tcpip.AddrFrom16(src.Addr().As16()), + tcpip.AddrFrom16(dst.Addr().As16()), + payloadChecksum, + uint16(len(payload)), + )) +} + +func TestBuildTCPSegment_MixedFamilyPanics(t *testing.T) { + t.Parallel() + src := netip.MustParseAddrPort("10.0.0.1:54321") + dst := netip.MustParseAddrPort("[2606:4700::1]:443") + require.Panics(t, func() { + buildTCPSegment(src, dst, 0, 0, nil, false) + }) +} + +func TestBuildSpoofFrame_WrongSequence(t *testing.T) { + t.Parallel() + src := netip.MustParseAddrPort("10.0.0.1:54321") + dst := netip.MustParseAddrPort("1.2.3.4:443") + payload := []byte("fake-client-hello") + const sendNext uint32 = 10_000 + frame, err := buildSpoofFrame(MethodWrongSequence, src, dst, sendNext, 20_000, payload) + require.NoError(t, err) + + tcp := header.TCP(frame[header.IPv4MinimumSize:]) + require.Equal(t, sendNext-uint32(len(payload)), tcp.SequenceNumber(), + "wrong-sequence places the fake at sendNext-len(payload)") + require.True(t, tcp.Flags().Contains(header.TCPFlagAck|header.TCPFlagPsh)) + + // Checksum must still be valid — only the sequence number is wrong. + payloadChecksum := checksum.Checksum(payload, 0) + require.True(t, tcp.IsChecksumValid( + tcpip.AddrFrom4(src.Addr().As4()), + tcpip.AddrFrom4(dst.Addr().As4()), + payloadChecksum, + uint16(len(payload)), + )) +} + +func TestBuildSpoofFrame_WrongChecksum(t *testing.T) { + t.Parallel() + src := netip.MustParseAddrPort("10.0.0.1:54321") + dst := netip.MustParseAddrPort("1.2.3.4:443") + payload := []byte("fake-client-hello") + const sendNext uint32 = 5_000 + frame, err := buildSpoofFrame(MethodWrongChecksum, src, dst, sendNext, 20_000, payload) + require.NoError(t, err) + + tcp := header.TCP(frame[header.IPv4MinimumSize:]) + require.Equal(t, sendNext, tcp.SequenceNumber(), + "wrong-checksum keeps the real sequence number") + + payloadChecksum := checksum.Checksum(payload, 0) + require.False(t, tcp.IsChecksumValid( + tcpip.AddrFrom4(src.Addr().As4()), + tcpip.AddrFrom4(dst.Addr().As4()), + payloadChecksum, + uint16(len(payload)), + )) + require.True(t, header.IPv4(frame[:header.IPv4MinimumSize]).IsChecksumValid(), + "IPv4 checksum must remain valid so the router forwards the packet") +} + +func TestBuildSpoofTCPSegment_EncodesWithoutIPHeader(t *testing.T) { + t.Parallel() + src := netip.MustParseAddrPort("[fe80::1]:54321") + dst := netip.MustParseAddrPort("[2606:4700::1]:443") + payload := []byte("fake-client-hello") + segment, err := buildSpoofTCPSegment(MethodWrongSequence, src, dst, 1000, 2000, payload) + require.NoError(t, err) + require.Equal(t, tcpHeaderLen+len(payload), len(segment), + "segment must be TCP header + payload, no IP header") +} diff --git a/common/tlsspoof/raw_darwin.go b/common/tlsspoof/raw_darwin.go new file mode 100644 index 0000000000..6f1dd96b6e --- /dev/null +++ b/common/tlsspoof/raw_darwin.go @@ -0,0 +1,199 @@ +package tlsspoof + +import ( + "encoding/binary" + "net" + "net/netip" + "strconv" + "strings" + "sync" + "syscall" + + "github.com/sagernet/sing-tun/gtcpip/header" + E "github.com/sagernet/sing/common/exceptions" + + "golang.org/x/sys/unix" +) + +const PlatformSupported = true + +// Offsets into xinpcb_n within each net.inet.tcp.pcblist_n record, identical +// to the values used by common/process/searcher_darwin_shared.go. +const ( + darwinXinpgenSize = 24 + darwinXsocketOffset = 104 + darwinXinpcbForeignPort = 16 + darwinXinpcbLocalPort = 18 + darwinXinpcbVFlag = 44 + darwinXinpcbForeignAddr = 48 + darwinXinpcbLocalAddr = 64 + darwinXinpcbIPv4Offset = 12 + + darwinTCPExtraSize = 208 + + darwinXtcpcbSndNxtOffset = 56 + darwinXtcpcbRcvNxtOffset = 80 +) + +// darwinStructSize returns the size of xinpcb_n for the running Darwin kernel. +// Darwin 22 (macOS 13 Ventura) grew the struct from 384 to 408 bytes; there is +// no ABI-stable way to read it, so we key off the kernel version. +var darwinStructSize = sync.OnceValues(func() (int, error) { + value, err := syscall.Sysctl("kern.osrelease") + if err != nil { + return 0, E.Cause(err, "sysctl kern.osrelease") + } + major, _, ok := strings.Cut(value, ".") + if !ok { + return 0, E.New("unexpected kern.osrelease format: ", value) + } + n, err := strconv.ParseInt(major, 10, 64) + if err != nil { + return 0, E.Cause(err, "parse kern.osrelease major version: ", value) + } + if n >= 22 { + return 408, nil + } + return 384, nil +}) + +type darwinSpoofer struct { + method Method + src netip.AddrPort + dst netip.AddrPort + rawFD int + rawSockAddr unix.Sockaddr + sendNext uint32 + receiveNext uint32 +} + +func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { + if method == MethodWrongTimestamp { + return nil, E.New("tls_spoof: wrong-timestamp is not supported on macOS") + } + _, src, dst, err := tcpEndpoints(conn) + if err != nil { + return nil, err + } + fd, sockaddr, err := openDarwinRawSocket(src, dst) + if err != nil { + return nil, err + } + sendNext, receiveNext, err := readDarwinTCPSequence(src, dst) + if err != nil { + unix.Close(fd) + return nil, err + } + return &darwinSpoofer{ + method: method, + src: src, + dst: dst, + rawFD: fd, + rawSockAddr: sockaddr, + sendNext: sendNext, + receiveNext: receiveNext, + }, nil +} + +// readDarwinTCPSequence scans net.inet.tcp.pcblist_n for the PCB that matches +// src -> dst and returns (snd_nxt, rcv_nxt). These live in xtcpcb_n at the end +// of each record; see darwin-xnu bsd/netinet/in_pcblist.c:get_pcblist_n. +func readDarwinTCPSequence(src, dst netip.AddrPort) (uint32, uint32, error) { + buffer, err := unix.SysctlRaw("net.inet.tcp.pcblist_n") + if err != nil { + return 0, 0, E.Cause(err, "sysctl net.inet.tcp.pcblist_n") + } + structSize, err := darwinStructSize() + if err != nil { + return 0, 0, err + } + itemSize := structSize + darwinTCPExtraSize + for i := darwinXinpgenSize; i+itemSize <= len(buffer); i += itemSize { + inpcb := buffer[i : i+darwinXsocketOffset] + xtcpcb := buffer[i+structSize : i+itemSize] + localPort := binary.BigEndian.Uint16(inpcb[darwinXinpcbLocalPort : darwinXinpcbLocalPort+2]) + remotePort := binary.BigEndian.Uint16(inpcb[darwinXinpcbForeignPort : darwinXinpcbForeignPort+2]) + if localPort != src.Port() || remotePort != dst.Port() { + continue + } + versionFlag := inpcb[darwinXinpcbVFlag] + var localAddr, remoteAddr netip.Addr + switch { + case versionFlag&0x1 != 0: + localAddr = netip.AddrFrom4([4]byte(inpcb[darwinXinpcbLocalAddr+darwinXinpcbIPv4Offset : darwinXinpcbLocalAddr+darwinXinpcbIPv4Offset+4])) + remoteAddr = netip.AddrFrom4([4]byte(inpcb[darwinXinpcbForeignAddr+darwinXinpcbIPv4Offset : darwinXinpcbForeignAddr+darwinXinpcbIPv4Offset+4])) + case versionFlag&0x2 != 0: + localAddr = netip.AddrFrom16([16]byte(inpcb[darwinXinpcbLocalAddr : darwinXinpcbLocalAddr+16])) + remoteAddr = netip.AddrFrom16([16]byte(inpcb[darwinXinpcbForeignAddr : darwinXinpcbForeignAddr+16])) + default: + continue + } + if localAddr.Unmap() != src.Addr() || remoteAddr.Unmap() != dst.Addr() { + continue + } + sendNext := binary.NativeEndian.Uint32(xtcpcb[darwinXtcpcbSndNxtOffset : darwinXtcpcbSndNxtOffset+4]) + receiveNext := binary.NativeEndian.Uint32(xtcpcb[darwinXtcpcbRcvNxtOffset : darwinXtcpcbRcvNxtOffset+4]) + return sendNext, receiveNext, nil + } + return 0, 0, E.New("tls_spoof: connection ", src, "->", dst, " not found in pcblist_n") +} + +func openDarwinRawSocket(src, dst netip.AddrPort) (int, unix.Sockaddr, error) { + if dst.Addr().Is4() { + return openIPv4RawSocket(dst) + } + // macOS does not accept IPV6_HDRINCL on AF_INET6 SOCK_RAW IPPROTO_TCP + // sockets, so the kernel builds the IPv6 header itself. Bind to the real + // connection's source address so in6_selectsrc returns it, and rely on + // in6p_cksum defaulting to -1 so the user-supplied TCP checksum is + // preserved (including deliberately corrupted ones). + fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) + if err != nil { + return -1, nil, E.Cause(err, "open AF_INET6 SOCK_RAW") + } + err = unix.Bind(fd, &unix.SockaddrInet6{Addr: src.Addr().As16()}) + if err != nil { + unix.Close(fd) + return -1, nil, E.Cause(err, "bind AF_INET6 SOCK_RAW") + } + sockaddr := &unix.SockaddrInet6{Port: int(dst.Port()), Addr: dst.Addr().As16()} + return fd, sockaddr, nil +} + +func (s *darwinSpoofer) Inject(payload []byte) error { + if !s.src.Addr().Is4() { + segment, err := buildSpoofTCPSegment(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, payload) + if err != nil { + return err + } + err = unix.Sendto(s.rawFD, segment, 0, s.rawSockAddr) + if err != nil { + return E.Cause(err, "sendto raw socket") + } + return nil + } + frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, 0, nil, payload) + if err != nil { + return err + } + // Darwin inherits the historical BSD quirk: with IP_HDRINCL the kernel + // expects ip_len and ip_off in host byte order, not network byte order. + // Apple's rip_output swaps them back before transmission. + ip := header.IPv4(frame) + ip.SetTotalLengthDarwinRaw(ip.TotalLength()) + ip.SetFlagsFragmentOffsetDarwinRaw(ip.Flags(), ip.FragmentOffset()) + err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) + if err != nil { + return E.Cause(err, "sendto raw socket") + } + return nil +} + +func (s *darwinSpoofer) Close() error { + if s.rawFD < 0 { + return nil + } + err := unix.Close(s.rawFD) + s.rawFD = -1 + return err +} diff --git a/common/tlsspoof/raw_linux.go b/common/tlsspoof/raw_linux.go new file mode 100644 index 0000000000..3e4761147e --- /dev/null +++ b/common/tlsspoof/raw_linux.go @@ -0,0 +1,149 @@ +package tlsspoof + +import ( + "net" + "net/netip" + + "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" + + "golang.org/x/sys/unix" +) + +const PlatformSupported = true + +const ( + // Values of enum { TCP_NO_QUEUE, TCP_RECV_QUEUE, TCP_SEND_QUEUE } from + // include/net/tcp.h; not exported by golang.org/x/sys/unix. + tcpRecvQueue = 1 + tcpSendQueue = 2 +) + +type linuxSpoofer struct { + method Method + src netip.AddrPort + dst netip.AddrPort + rawFD int + rawSockAddr unix.Sockaddr + sendNext uint32 + receiveNext uint32 + timestamp uint32 +} + +func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { + tcpConn, src, dst, err := tcpEndpoints(conn) + if err != nil { + return nil, err + } + fd, sockaddr, err := openLinuxRawSocket(dst) + if err != nil { + return nil, err + } + spoofer := &linuxSpoofer{ + method: method, + src: src, + dst: dst, + rawFD: fd, + rawSockAddr: sockaddr, + } + err = spoofer.loadSequenceNumbers(tcpConn) + if err != nil { + unix.Close(fd) + return nil, err + } + return spoofer, nil +} + +func openLinuxRawSocket(dst netip.AddrPort) (int, unix.Sockaddr, error) { + if dst.Addr().Is4() { + return openIPv4RawSocket(dst) + } + fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP) + if err != nil { + return -1, nil, E.Cause(err, "open AF_INET6 SOCK_RAW") + } + err = unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_HDRINCL, 1) + if err != nil { + unix.Close(fd) + return -1, nil, E.Cause(err, "set IPV6_HDRINCL") + } + // Linux raw IPv6 sockets interpret sin6_port as a nexthdr protocol number + // (see raw(7)); any value other than 0 or the socket's IPPROTO_TCP causes + // sendto to fail with EINVAL. The destination is already encoded in the + // user-supplied IPv6 header under IPV6_HDRINCL. + sockaddr := &unix.SockaddrInet6{Addr: dst.Addr().As16()} + return fd, sockaddr, nil +} + +// loadSequenceNumbers puts the socket briefly into TCP_REPAIR mode to read +// snd_nxt and rcv_nxt from the kernel. TCP_REPAIR requires CAP_NET_ADMIN; +// callers must run as root or grant both CAP_NET_RAW and CAP_NET_ADMIN. +// +// If the TCP_REPAIR_OFF revert fails, the socket would stay in TCP_REPAIR +// state and subsequent Write() calls would silently buffer instead of sending. +// Surface that error so callers can abort. +func (s *linuxSpoofer) loadSequenceNumbers(tcpConn *net.TCPConn) error { + return control.Conn(tcpConn, func(raw uintptr) (err error) { + fd := int(raw) + + if s.method == MethodWrongTimestamp { + timestamp, err := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_TIMESTAMP) + if err != nil { + return E.Cause(err, "read timestamp") + } + s.timestamp = uint32(timestamp) + } + + err = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR, unix.TCP_REPAIR_ON) + if err != nil { + return E.Cause(err, "enter TCP_REPAIR (need CAP_NET_ADMIN)") + } + defer func() { + offErr := unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR, unix.TCP_REPAIR_OFF) + if err == nil && offErr != nil { + err = E.Cause(offErr, "leave TCP_REPAIR") + } + }() + + err = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR_QUEUE, tcpSendQueue) + if err != nil { + return E.Cause(err, "select TCP_SEND_QUEUE") + } + sendSequence, err := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_QUEUE_SEQ) + if err != nil { + return E.Cause(err, "read send queue sequence") + } + err = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_REPAIR_QUEUE, tcpRecvQueue) + if err != nil { + return E.Cause(err, "select TCP_RECV_QUEUE") + } + receiveSequence, err := unix.GetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_QUEUE_SEQ) + if err != nil { + return E.Cause(err, "read recv queue sequence") + } + s.sendNext = uint32(sendSequence) + s.receiveNext = uint32(receiveSequence) + return nil + }) +} + +func (s *linuxSpoofer) Inject(payload []byte) error { + frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, s.timestamp, nil, payload) + if err != nil { + return err + } + err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr) + if err != nil { + return E.Cause(err, "sendto raw socket") + } + return nil +} + +func (s *linuxSpoofer) Close() error { + if s.rawFD < 0 { + return nil + } + err := unix.Close(s.rawFD) + s.rawFD = -1 + return err +} diff --git a/common/tlsspoof/raw_stub.go b/common/tlsspoof/raw_stub.go new file mode 100644 index 0000000000..7edf2441a6 --- /dev/null +++ b/common/tlsspoof/raw_stub.go @@ -0,0 +1,15 @@ +//go:build !linux && !darwin && !(windows && (amd64 || 386)) + +package tlsspoof + +import ( + "net" + + E "github.com/sagernet/sing/common/exceptions" +) + +const PlatformSupported = false + +func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { + return nil, E.New("tls_spoof: unsupported platform") +} diff --git a/common/tlsspoof/raw_unix.go b/common/tlsspoof/raw_unix.go new file mode 100644 index 0000000000..7ab1d44a27 --- /dev/null +++ b/common/tlsspoof/raw_unix.go @@ -0,0 +1,26 @@ +//go:build linux || darwin + +package tlsspoof + +import ( + "net/netip" + + E "github.com/sagernet/sing/common/exceptions" + + "golang.org/x/sys/unix" +) + +func openIPv4RawSocket(dst netip.AddrPort) (int, unix.Sockaddr, error) { + fd, err := unix.Socket(unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_TCP) + if err != nil { + return -1, nil, E.Cause(err, "open AF_INET SOCK_RAW") + } + err = unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_HDRINCL, 1) + if err != nil { + unix.Close(fd) + return -1, nil, E.Cause(err, "set IP_HDRINCL") + } + sockaddr := &unix.SockaddrInet4{Port: int(dst.Port())} + sockaddr.Addr = dst.Addr().As4() + return fd, sockaddr, nil +} diff --git a/common/tlsspoof/raw_windows.go b/common/tlsspoof/raw_windows.go new file mode 100644 index 0000000000..d7d8a2682a --- /dev/null +++ b/common/tlsspoof/raw_windows.go @@ -0,0 +1,237 @@ +//go:build windows && (amd64 || 386) + +package tlsspoof + +import ( + "errors" + "net" + "net/netip" + "slices" + "sync" + "sync/atomic" + "time" + + "github.com/sagernet/sing-box/common/windivert" + "github.com/sagernet/sing-tun/gtcpip/header" + E "github.com/sagernet/sing/common/exceptions" + + "golang.org/x/sys/windows" +) + +const PlatformSupported = true + +// closeGracePeriod caps how long Close() waits for the divert goroutine to +// observe the kernel-emitted real ClientHello and perform the reorder +// (fake → real). In practice this completes in microseconds; the cap +// bounds the pathological case where the kernel buffers the packet. +const closeGracePeriod = 2 * time.Second + +// windowsSpoofer uses a single WinDivert handle for both capture and +// injection. Sequential Send() calls on one handle traverse one driver queue, +// so the fake provably precedes the released real on the wire — a guarantee +// two separate handles cannot make because cross-handle order depends on the +// scheduler. +type windowsSpoofer struct { + method Method + src, dst netip.AddrPort + divertH *windivert.Handle + + fakeReady chan []byte // buffered(1): staged by Inject + done chan struct{} // closed by run() on exit + closeOnce sync.Once + runErr atomic.Pointer[error] +} + +func newRawSpoofer(conn net.Conn, method Method) (rawSpoofer, error) { + _, src, dst, err := tcpEndpoints(conn) + if err != nil { + return nil, err + } + filter, err := windivert.OutboundTCP(src, dst) + if err != nil { + return nil, err + } + divertH, err := windivert.Open(filter, windivert.LayerNetwork, 0, 0) + if err != nil { + return nil, E.Cause(err, "tls_spoof: open WinDivert") + } + s := &windowsSpoofer{ + method: method, + src: src, + dst: dst, + divertH: divertH, + fakeReady: make(chan []byte, 1), + done: make(chan struct{}), + } + go s.run() + return s, nil +} + +func (s *windowsSpoofer) Inject(payload []byte) error { + select { + case s.fakeReady <- payload: + return nil + case <-s.done: + if p := s.runErr.Load(); p != nil { + return *p + } + return E.New("tls_spoof: spoofer closed before Inject") + } +} + +func (s *windowsSpoofer) Close() error { + s.closeOnce.Do(func() { + // Give run() a grace window to finish handling the real packet. + select { + case <-s.done: + case <-time.After(closeGracePeriod): + // Force Recv() to return by closing the divert handle. + s.divertH.Close() + <-s.done + } + }) + if p := s.runErr.Load(); p != nil { + return *p + } + return nil +} + +func (s *windowsSpoofer) recordErr(err error) { s.runErr.Store(&err) } + +func (s *windowsSpoofer) run() { + defer close(s.done) + defer s.divertH.Close() + + buf := make([]byte, windivert.MTUMax) + for { + n, addr, err := s.divertH.Recv(buf) + if err != nil { + if errors.Is(err, windows.ERROR_OPERATION_ABORTED) || + errors.Is(err, windows.ERROR_NO_DATA) { + return + } + s.recordErr(E.Cause(err, "windivert recv")) + return + } + pkt := buf[:n] + seq, ack, tcpOptions, payloadLen, ok := parseTCPPacket(pkt, addr.IPv6()) + if !ok { + // Our filter is OutboundTCP(src, dst); a non-TCP or truncated + // match means driver state is suspect. Re-inject so the kernel + // still sees the byte stream, then abort — continuing would risk + // reordering against an unknown reference point. + _, sendErr := s.divertH.Send(pkt, &addr) + if sendErr != nil { + s.recordErr(E.Cause(sendErr, "windivert re-inject malformed")) + return + } + s.recordErr(E.New("windivert received malformed packet matching spoof filter")) + return + } + if payloadLen == 0 { + // Handshake ACK, keepalive, FIN — pass through unchanged. + _, err := s.divertH.Send(pkt, &addr) + if err != nil { + s.recordErr(E.Cause(err, "windivert re-inject empty")) + return + } + continue + } + + // Non-empty outbound TCP payload = the real ClientHello. + var fake []byte + select { + case fake = <-s.fakeReady: + default: + // Inject() not yet called — pass through and keep observing. + _, err := s.divertH.Send(pkt, &addr) + if err != nil { + s.recordErr(E.Cause(err, "windivert re-inject early data")) + return + } + continue + } + + var timestamp uint32 + if parsed := header.ParseTCPOptions(tcpOptions); parsed.TS { + timestamp = parsed.TSVal + } + frame, err := buildSpoofFrame(s.method, s.src, s.dst, seq, ack, timestamp, tcpOptions, fake) + if err != nil { + s.recordErr(err) + return + } + fakeAddr := addr // inherit Outbound, IfIdx + // buildSpoofFrame emits ready-to-wire bytes. The driver recomputes + // checksums on Send when TCPChecksum/IPChecksum are 0 — which would + // overwrite the intentionally corrupt checksum in WrongChecksum mode. + // Force both to 1 to keep our bytes intact. + fakeAddr.SetIPChecksum(true) + fakeAddr.SetTCPChecksum(true) + _, err = s.divertH.Send(frame, &fakeAddr) + if err != nil { + s.recordErr(E.Cause(err, "windivert inject fake")) + return + } + _, err = s.divertH.Send(pkt, &addr) + if err != nil { + s.recordErr(E.Cause(err, "windivert re-inject real")) + return + } + return // single-shot reorder complete + } +} + +func parseTCPPacket(pkt []byte, isV6 bool) (seq, ack uint32, options []byte, payloadLen int, ok bool) { + if isV6 { + if len(pkt) < header.IPv6MinimumSize+header.TCPMinimumSize { + return 0, 0, nil, 0, false + } + ip := header.IPv6(pkt) + if ip.TransportProtocol() != header.TCPProtocolNumber { + return 0, 0, nil, 0, false + } + tcp := header.TCP(pkt[header.IPv6MinimumSize:]) + tcpHdr := int(tcp.DataOffset()) + if tcpHdr < header.TCPMinimumSize || header.IPv6MinimumSize+tcpHdr > len(pkt) { + return 0, 0, nil, 0, false + } + total := header.IPv6MinimumSize + int(ip.PayloadLength()) + if total == header.IPv6MinimumSize || total > len(pkt) { + total = len(pkt) + } + if total < header.IPv6MinimumSize+tcpHdr { + return 0, 0, nil, 0, false + } + return tcp.SequenceNumber(), tcp.AckNumber(), slices.Clone(tcp.Options()), + total - header.IPv6MinimumSize - tcpHdr, true + } + if len(pkt) < header.IPv4MinimumSize+header.TCPMinimumSize { + return 0, 0, nil, 0, false + } + ip := header.IPv4(pkt) + if ip.Protocol() != uint8(header.TCPProtocolNumber) { + return 0, 0, nil, 0, false + } + ihl := int(ip.HeaderLength()) + // ihl+TCPMinimumSize guards the TCP-header field reads below; without + // this, an IPv4 packet with options (ihl>20) against a 40-byte buffer + // reads past the TCP slice when calling DataOffset. + if ihl < header.IPv4MinimumSize || ihl+header.TCPMinimumSize > len(pkt) { + return 0, 0, nil, 0, false + } + tcp := header.TCP(pkt[ihl:]) + tcpHdr := int(tcp.DataOffset()) + if tcpHdr < header.TCPMinimumSize || ihl+tcpHdr > len(pkt) { + return 0, 0, nil, 0, false + } + total := int(ip.TotalLength()) + if total == 0 || total > len(pkt) { + total = len(pkt) + } + if total < ihl+tcpHdr { + return 0, 0, nil, 0, false + } + return tcp.SequenceNumber(), tcp.AckNumber(), slices.Clone(tcp.Options()), + total - ihl - tcpHdr, true +} diff --git a/common/tlsspoof/raw_windows_test.go b/common/tlsspoof/raw_windows_test.go new file mode 100644 index 0000000000..58566b8759 --- /dev/null +++ b/common/tlsspoof/raw_windows_test.go @@ -0,0 +1,112 @@ +//go:build windows && (amd64 || 386) + +package tlsspoof + +import ( + "net/netip" + "testing" + + "github.com/sagernet/sing-tun/gtcpip/header" + + "github.com/stretchr/testify/require" +) + +func TestParseTCPFieldsIPv4Valid(t *testing.T) { + t.Parallel() + src := netip.MustParseAddrPort("10.0.0.1:54321") + dst := netip.MustParseAddrPort("1.2.3.4:443") + payload := []byte("hello") + frame := buildTCPSegment(src, dst, 1000, 2000, payload, false) + + seq, ack, payloadLen, ok := parseTCPFields(frame, false) + require.True(t, ok) + require.Equal(t, uint32(1000), seq) + require.Equal(t, uint32(2000), ack) + require.Equal(t, len(payload), payloadLen) +} + +func TestParseTCPFieldsIPv4NoPayload(t *testing.T) { + t.Parallel() + src := netip.MustParseAddrPort("10.0.0.1:54321") + dst := netip.MustParseAddrPort("1.2.3.4:443") + frame := buildTCPSegment(src, dst, 42, 100, nil, false) + + seq, ack, payloadLen, ok := parseTCPFields(frame, false) + require.True(t, ok) + require.Equal(t, uint32(42), seq) + require.Equal(t, uint32(100), ack) + require.Equal(t, 0, payloadLen) +} + +func TestParseTCPFieldsIPv6Valid(t *testing.T) { + t.Parallel() + src := netip.MustParseAddrPort("[fe80::1]:54321") + dst := netip.MustParseAddrPort("[2606:4700::1]:443") + payload := []byte("hello-v6") + frame := buildTCPSegment(src, dst, 0xDEADBEEF, 0x12345678, payload, false) + + seq, ack, payloadLen, ok := parseTCPFields(frame, true) + require.True(t, ok) + require.Equal(t, uint32(0xDEADBEEF), seq) + require.Equal(t, uint32(0x12345678), ack) + require.Equal(t, len(payload), payloadLen) +} + +func TestParseTCPFieldsIPv4TooShort(t *testing.T) { + t.Parallel() + _, _, _, ok := parseTCPFields(make([]byte, header.IPv4MinimumSize+header.TCPMinimumSize-1), false) + require.False(t, ok) +} + +func TestParseTCPFieldsIPv6TooShort(t *testing.T) { + t.Parallel() + _, _, _, ok := parseTCPFields(make([]byte, header.IPv6MinimumSize+header.TCPMinimumSize-1), true) + require.False(t, ok) +} + +// buildTCPSegment only produces TCP; a UDP packet hitting parseTCPFields +// (for example from a mis-specified filter) must be rejected. +func TestParseTCPFieldsIPv4WrongProtocol(t *testing.T) { + t.Parallel() + frame := make([]byte, header.IPv4MinimumSize+header.TCPMinimumSize) + ip := header.IPv4(frame[:header.IPv4MinimumSize]) + ip.Encode(&header.IPv4Fields{ + TotalLength: uint16(len(frame)), + TTL: 64, + Protocol: 17, // UDP + SrcAddr: netip.MustParseAddr("10.0.0.1"), + DstAddr: netip.MustParseAddr("10.0.0.2"), + }) + _, _, _, ok := parseTCPFields(frame, false) + require.False(t, ok) +} + +func TestParseTCPFieldsIPv6WrongProtocol(t *testing.T) { + t.Parallel() + frame := make([]byte, header.IPv6MinimumSize+header.TCPMinimumSize) + ip := header.IPv6(frame[:header.IPv6MinimumSize]) + ip.Encode(&header.IPv6Fields{ + PayloadLength: header.TCPMinimumSize, + TransportProtocol: 17, // UDP + HopLimit: 64, + SrcAddr: netip.MustParseAddr("fe80::1"), + DstAddr: netip.MustParseAddr("fe80::2"), + }) + _, _, _, ok := parseTCPFields(frame, true) + require.False(t, ok) +} + +// ihl > 20 must not read past the TCP slice. Build an IPv4 packet with +// options header but truncate so ihl*4 + TCPMinimumSize exceeds len. +func TestParseTCPFieldsIPv4OptionsOverflow(t *testing.T) { + t.Parallel() + // Start with a valid IPv4+TCP frame, then lie about the header length. + src := netip.MustParseAddrPort("10.0.0.1:1") + dst := netip.MustParseAddrPort("10.0.0.2:2") + frame := buildTCPSegment(src, dst, 0, 0, []byte("x"), false) + ip := header.IPv4(frame[:header.IPv4MinimumSize]) + // ihl=15 → 60 bytes of IP header claimed, but buffer only has 20. + ip.SetHeaderLength(60) + _, _, _, ok := parseTCPFields(frame, false) + require.False(t, ok) +} diff --git a/common/tlsspoof/spoof.go b/common/tlsspoof/spoof.go new file mode 100644 index 0000000000..e36d39e1f0 --- /dev/null +++ b/common/tlsspoof/spoof.go @@ -0,0 +1,149 @@ +package tlsspoof + +import ( + "net" + + E "github.com/sagernet/sing/common/exceptions" +) + +type Method int + +const ( + MethodWrongSequence Method = iota + MethodWrongChecksum + MethodWrongAcknowledgment + MethodWrongMD5Sig + MethodWrongTimestamp +) + +const ( + MethodNameWrongSequence = "wrong-sequence" + MethodNameWrongChecksum = "wrong-checksum" + MethodNameWrongAcknowledgment = "wrong-ack" + MethodNameWrongMD5Sig = "wrong-md5" + MethodNameWrongTimestamp = "wrong-timestamp" +) + +func ParseOptions(spoof, method string) (string, Method, error) { + if spoof == "" { + if method != "" { + return "", 0, E.New("spoof_method requires spoof") + } + return "", 0, nil + } + if !PlatformSupported { + return "", 0, E.New("tls_spoof is not supported on this platform") + } + parsedMethod, err := ParseMethod(method) + if err != nil { + return "", 0, err + } + return spoof, parsedMethod, nil +} + +func ParseMethod(s string) (Method, error) { + switch s { + case "", MethodNameWrongSequence: + return MethodWrongSequence, nil + case MethodNameWrongChecksum: + return MethodWrongChecksum, nil + case MethodNameWrongAcknowledgment: + return MethodWrongAcknowledgment, nil + case MethodNameWrongMD5Sig: + return MethodWrongMD5Sig, nil + case MethodNameWrongTimestamp: + return MethodWrongTimestamp, nil + default: + return 0, E.New("tls_spoof: unknown method: ", s) + } +} + +func (m Method) String() string { + switch m { + case MethodWrongSequence: + return MethodNameWrongSequence + case MethodWrongChecksum: + return MethodNameWrongChecksum + case MethodWrongAcknowledgment: + return MethodNameWrongAcknowledgment + case MethodWrongMD5Sig: + return MethodNameWrongMD5Sig + case MethodWrongTimestamp: + return MethodNameWrongTimestamp + default: + return "unknown" + } +} + +type rawSpoofer interface { + Inject(payload []byte) error + Close() error +} + +type Conn struct { + net.Conn + spoofer rawSpoofer + fakeHello []byte + injected bool +} + +func NewConn(conn net.Conn, method Method, fakeSNI string) (*Conn, error) { + spoofer, err := newRawSpoofer(conn, method) + if err != nil { + return nil, err + } + result, err := newConn(conn, spoofer, fakeSNI) + if err != nil { + spoofer.Close() + return nil, err + } + return result, nil +} + +func newConn(conn net.Conn, spoofer rawSpoofer, fakeSNI string) (*Conn, error) { + fakeHello, err := buildFakeClientHello(fakeSNI) + if err != nil { + return nil, E.Cause(err, "tls_spoof: build fake ClientHello") + } + return &Conn{ + Conn: conn, + spoofer: spoofer, + fakeHello: fakeHello, + }, nil +} + +func (c *Conn) Write(b []byte) (n int, err error) { + if c.injected { + return c.Conn.Write(b) + } + defer func() { + closeErr := c.spoofer.Close() + if err == nil && closeErr != nil { + err = E.Cause(closeErr, "tls_spoof: close spoofer") + } + }() + err = c.spoofer.Inject(c.fakeHello) + if err != nil { + return 0, E.Cause(err, "tls_spoof: inject") + } + c.injected = true + return c.Conn.Write(b) +} + +func (c *Conn) Close() error { + return E.Append(c.Conn.Close(), c.spoofer.Close(), func(e error) error { + return E.Cause(e, "tls_spoof: close spoofer") + }) +} + +func (c *Conn) ReaderReplaceable() bool { + return true +} + +func (c *Conn) WriterReplaceable() bool { + return c.injected +} + +func (c *Conn) Upstream() any { + return c.Conn +} diff --git a/common/tlsspoof/testdata_test.go b/common/tlsspoof/testdata_test.go new file mode 100644 index 0000000000..2596ebf619 --- /dev/null +++ b/common/tlsspoof/testdata_test.go @@ -0,0 +1,6 @@ +//go:build linux || darwin || (windows && (amd64 || 386)) + +package tlsspoof + +// realClientHello is a captured Chrome ClientHello for github.com. +const realClientHello = "16030105f8010005f403036e35de7389a679c54029cf452611f2211c70d9ac3897271de589ab6155f8e4ab20637d225f1ef969ad87ed78bfb9d171300bcb1703b6f314ccefb964f79b7d0961002a0a0a130213031301c02cc02bcca9c030c02fcca8c00ac009c014c013009d009c0035002fc008c012000a01000581baba00000000000f000d00000a6769746875622e636f6d00170000ff01000100000a000e000c3a3a11ec001d001700180019000b000201000010000e000c02683208687474702f312e31000500050100000000000d00160014040308040401050308050805050108060601020100120000003304ef04ed3a3a00010011ec04c0aeb2250c092a3463161cccb29d9183331a424964248579507ed23a180b0ceab2a5f5d9ce41547e497a89055471ea572867ba3a1fc3c9e45025274a20f60c6b60e62476b6afed0403af59ab83660ef4112ae20386a602010d0a5d454c0ed34c84ed4423e750213e6a2baab1bf9c4367a6007ab40a33d95220c2dcaa44f257024a5626b545db0510f4311b1a60714154909c6a61fdfca011fb2626d657aeb6070bf078508babe3b584555013e34acc56198ed4663742b3155a664a9901794c4586820a7dc162c01827291f3792e1237f801a8d1ef096013c181c4a58d2f6859ba75022d18cc4418bd4f351d5c18f83a58857d05af860c4b9ac018a5b63f17184e591532c6bc2cf2215d4a282c8a8a4f6f7aee110422c8bc9ebd3b1d609c568523aaae555db320e6c269473d87af38c256cbb9febc20aea6380c32a8916f7a373c8b1e37554e3260bf6621f6b804ee80b3c516b1d01985bf4c603b6daa9a5991de6a7a29f3a7122b8afb843a7660110fce62b43c615f5bcc2db688ba012649c0952b0a2c031e732d2b454c6b2968683cb8d244be2c9a7fa163222979eaf92722b92b862d81a3d94450c2b60c318421ebb4307c42d1f0473592a5c30e42039cc68cda9721e61aa63f49def17c15221680ed444896340133bbee67556f56b9f9d78a4df715f926a12add0cc9c862e46ea8b7316ae468282c18601b2771c9c9322f982228cf93effaacd3f80cbd12bce5fc36f56e2a3caf91e578a5fae00c9b23a8ed1a66764f4433c3628a70b8f0a6196adc60a4cb4226f07ba4c6b363fe9065563bfc1347452946386bab488686e837ab979c64f9047417fca635fe1bb4f074f256cc8af837c7b455e280426547755af90a61640169ef180aea3a77e662bb6dac1b6c3696027129b1a5edf495314e9c7f4b6110e16378ec893fa24642330a40aba1a85326101acb97c620fd8d71389e69eaed7bdb01bbe1fd428d66191150c7b2cd1ad4257391676a82ba8ce07fb2667c3b289f159003a7c7bc31d361b7b7f49a802961739d950dfcc0fa1c7abce5abdd2245101da391151490862028110465950b9e9c03d08a90998ab83267838d2e74a0593bc81f74cdf734519a05b351c0e5488c68dd810e6e9142ccc1e2f4a7f464297eb340e27acc6b9d64e12e38cce8492b3d939140b5a9e149a75597f10a23874c84323a07cdd657274378f887c85c4259b9c04cd33ba58ed630ef2a744f8e19dd34843dff331d2a6be7e2332c599289cd248a611c73d7481cd4a9bd43449a3836f14b2af18a1739e17999e4c67e85cc5bcecabb14185e5bcaff3c96098f03dc5aba819f29587758f49f940585354a2a780830528d68ccd166920dadcaa25cab5fc1907272a826aba3f08bc6b88757776812ecb6c7cec69a223ec0a13a7b62a2349a0f63ed7a27a3b15ba21d71fe6864ec6e089ae17cadd433fa3138f7ee24353c11365818f8fc34f43a05542d18efaac24bfccc1f748a0cc1a67ad379468b76fd34973dba785f5c91d618333cd810fe0700d1bbc8422029782628070a624c52c5309a4a64d625b11f8033ab28df34a1add297517fcc06b92b6817b3c5144438cf260867c57bde68c8c4b82e6a135ef676a52fbae5708002a404e6189a60e2836de565ad1b29e3819e5ed49f6810bcb28e1bd6de57306f94b79d9dae1cc4624d2a068499beef81cd5fe4b76dcbfff2a2008001d002001976128c6d5a934533f28b9914d2480aab2a8c1ab03d212529ce8b27640a716002d00020101002b000706caca03040303001b00030200015a5a000100" diff --git a/common/uot/router.go b/common/uot/router.go index fb2d23d557..98c6d608f3 100644 --- a/common/uot/router.go +++ b/common/uot/router.go @@ -13,14 +13,14 @@ import ( "github.com/sagernet/sing/common/uot" ) -var _ adapter.ConnectionRouter = (*Router)(nil) +var _ adapter.ConnectionRouterEx = (*Router)(nil) type Router struct { - router adapter.ConnectionRouter + router adapter.ConnectionRouterEx logger logger.ContextLogger } -func NewRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) *Router { +func NewRouter(router adapter.ConnectionRouterEx, logger logger.ContextLogger) *Router { return &Router{router, logger} } @@ -51,3 +51,36 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { return r.router.RoutePacketConnection(ctx, conn, metadata) } + +func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + switch metadata.Destination.Fqdn { + case uot.MagicAddress: + request, err := uot.ReadRequest(conn) + if err != nil { + err = E.Cause(err, "UoT read request") + r.logger.ErrorContext(ctx, "process connection from ", metadata.Source, ": ", err) + N.CloseOnHandshakeFailure(conn, onClose, err) + return + } + if request.IsConnect { + r.logger.InfoContext(ctx, "inbound UoT connect connection to ", request.Destination) + } else { + r.logger.InfoContext(ctx, "inbound UoT connection to ", request.Destination) + } + metadata.Domain = metadata.Destination.Fqdn + metadata.Destination = request.Destination + r.router.RoutePacketConnectionEx(ctx, uot.NewConn(conn, *request), metadata, onClose) + return + case uot.LegacyMagicAddress: + r.logger.InfoContext(ctx, "inbound legacy UoT connection") + metadata.Domain = metadata.Destination.Fqdn + metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()} + r.RoutePacketConnectionEx(ctx, uot.NewConn(conn, uot.Request{}), metadata, onClose) + return + } + r.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + r.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/common/urltest/context.go b/common/urltest/context.go new file mode 100644 index 0000000000..002ff26553 --- /dev/null +++ b/common/urltest/context.go @@ -0,0 +1,13 @@ +package urltest + +import "context" + +type contextKeyIsUnifiedDelay struct{} + +func ContextWithIsUnifiedDelay(ctx context.Context) context.Context { + return context.WithValue(ctx, contextKeyIsUnifiedDelay{}, true) +} + +func IsUnifiedDelayFromContext(ctx context.Context) bool { + return ctx.Value(contextKeyIsUnifiedDelay{}) != nil +} diff --git a/common/urltest/urltest.go b/common/urltest/urltest.go index 001f2e15a6..5e53d9912f 100644 --- a/common/urltest/urltest.go +++ b/common/urltest/urltest.go @@ -2,39 +2,41 @@ package urltest import ( "context" + "crypto/tls" + "fmt" "net" "net/http" "net/url" "sync" "time" - "github.com/sagernet/sing/common" + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/ntp" + "github.com/sagernet/sing/common/observable" ) -type History struct { - Time time.Time `json:"time"` - Delay uint16 `json:"delay"` -} +var _ adapter.URLTestHistoryStorage = (*HistoryStorage)(nil) type HistoryStorage struct { access sync.RWMutex - delayHistory map[string]*History - updateHook chan<- struct{} + delayHistory map[string]*adapter.URLTestHistory + updateHook *observable.Subscriber[struct{}] } func NewHistoryStorage() *HistoryStorage { return &HistoryStorage{ - delayHistory: make(map[string]*History), + delayHistory: make(map[string]*adapter.URLTestHistory), } } -func (s *HistoryStorage) SetHook(hook chan<- struct{}) { +func (s *HistoryStorage) SetHook(hook *observable.Subscriber[struct{}]) { s.updateHook = hook } -func (s *HistoryStorage) LoadURLTestHistory(tag string) *History { +func (s *HistoryStorage) LoadURLTestHistory(tag string) *adapter.URLTestHistory { if s == nil { return nil } @@ -44,15 +46,40 @@ func (s *HistoryStorage) LoadURLTestHistory(tag string) *History { } func (s *HistoryStorage) DeleteURLTestHistory(tag string) { + s.StoreURLTestHistory(tag, &adapter.URLTestHistory{ + Delay: 65535, + Time: time.Now(), + }) + // s.access.Lock() + // // delete(s.delayHistory, tag) + // s.access.Unlock() + // s.notifyUpdated() +} + +func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTestHistory) *adapter.URLTestHistory { s.access.Lock() - delete(s.delayHistory, tag) + if old, ok := s.delayHistory[tag]; ok && history != nil { + old.Delay = history.Delay + old.Time = history.Time + if history.IpInfo != nil { + old.IpInfo = history.IpInfo + } + } else { + s.delayHistory[tag] = history + } + history = s.delayHistory[tag] s.access.Unlock() s.notifyUpdated() + return history } -func (s *HistoryStorage) StoreURLTestHistory(tag string, history *History) { +func (s *HistoryStorage) AddOnlyIpToHistory(tag string, history *adapter.URLTestHistory) { s.access.Lock() - s.delayHistory[tag] = history + if old, ok := s.delayHistory[tag]; ok && history != nil { + old.IpInfo = history.IpInfo + } else { + s.delayHistory[tag] = history + } s.access.Unlock() s.notifyUpdated() } @@ -60,19 +87,22 @@ func (s *HistoryStorage) StoreURLTestHistory(tag string, history *History) { func (s *HistoryStorage) notifyUpdated() { updateHook := s.updateHook if updateHook != nil { - select { - case updateHook <- struct{}{}: - default: - } + updateHook.Emit(struct{}{}) } } func (s *HistoryStorage) Close() error { + s.access.Lock() + defer s.access.Unlock() s.updateHook = nil return nil } func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) { + if detour == nil { + err = fmt.Errorf("urltest dialer is nil") + return + } if link == "" { link = "https://www.gstatic.com/generate_204" } @@ -97,29 +127,60 @@ func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err e return } defer instance.Close() - if earlyConn, isEarlyConn := common.Cast[N.EarlyConn](instance); isEarlyConn && earlyConn.NeedHandshake() { + if N.NeedHandshakeForWrite(instance) { start = time.Now() } req, err := http.NewRequest(http.MethodHead, link, nil) if err != nil { return } + select { + case <-ctx.Done(): + return + default: + } client := http.Client{ Transport: &http.Transport{ DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { return instance, nil }, + TLSClientConfig: &tls.Config{ + Time: ntp.TimeFuncFromContext(ctx), + RootCAs: adapter.RootPoolFromContext(ctx), + }, }, CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, + Timeout: C.TCPTimeout, } defer client.CloseIdleConnections() + select { + case <-ctx.Done(): + return + default: + } resp, err := client.Do(req.WithContext(ctx)) if err != nil { return } resp.Body.Close() + t = uint16(time.Since(start) / time.Millisecond) + + if IsUnifiedDelayFromContext(ctx) { + select { + case <-ctx.Done(): + return + default: + } + second := time.Now() + resp, err = client.Do(req) + if err != nil { + return + } + resp.Body.Close() + t = uint16(time.Since(second) / time.Millisecond) //to avid timeout in the second call + } return } diff --git a/common/vision/hook.go b/common/vision/hook.go new file mode 100644 index 0000000000..8a63cb7f2a --- /dev/null +++ b/common/vision/hook.go @@ -0,0 +1,25 @@ +package vision + +import ( + "context" + "net" +) + +type Hook func(net.Conn) + +type hookKey struct{} + +func WithHook(ctx context.Context, hook Hook) context.Context { + if hook == nil { + return ctx + } + return context.WithValue(ctx, hookKey{}, hook) +} + +func HookFromContext(ctx context.Context) (Hook, bool) { + if ctx == nil { + return nil, false + } + hook, ok := ctx.Value(hookKey{}).(Hook) + return hook, ok +} diff --git a/common/windivert/address_test.go b/common/windivert/address_test.go new file mode 100644 index 0000000000..bfc995589d --- /dev/null +++ b/common/windivert/address_test.go @@ -0,0 +1,53 @@ +package windivert + +import ( + "testing" + "unsafe" + + "github.com/stretchr/testify/require" +) + +func TestAddressSize(t *testing.T) { + t.Parallel() + require.Equal(t, uintptr(80), unsafe.Sizeof(Address{})) +} + +func TestAddressIPv6(t *testing.T) { + t.Parallel() + var addr Address + require.False(t, addr.IPv6()) + addr.bits = 1 << addrBitIPv6 + require.True(t, addr.IPv6()) +} + +func TestAddressSetIPChecksum(t *testing.T) { + t.Parallel() + var addr Address + addr.SetIPChecksum(true) + require.Equal(t, uint32(1< + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. + +============================================================================== + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + +============================================================================== + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + diff --git a/common/windivert/assets/WinDivert32.sys b/common/windivert/assets/WinDivert32.sys new file mode 100644 index 0000000000..d06738cbb7 Binary files /dev/null and b/common/windivert/assets/WinDivert32.sys differ diff --git a/common/windivert/assets/WinDivert64.sys b/common/windivert/assets/WinDivert64.sys new file mode 100644 index 0000000000..218ccaf423 Binary files /dev/null and b/common/windivert/assets/WinDivert64.sys differ diff --git a/common/windivert/assets_386.go b/common/windivert/assets_386.go new file mode 100644 index 0000000000..0cbf35ed5c --- /dev/null +++ b/common/windivert/assets_386.go @@ -0,0 +1,14 @@ +//go:build windows && 386 + +package windivert + +import _ "embed" + +//go:embed assets/WinDivert32.sys +var sysBytes []byte + +func assetFiles() []assetFile { + return []assetFile{{"WinDivert32.sys", sysBytes}} +} + +func driverSysName() string { return "WinDivert32.sys" } diff --git a/common/windivert/assets_amd64.go b/common/windivert/assets_amd64.go new file mode 100644 index 0000000000..2c9fb6c6ad --- /dev/null +++ b/common/windivert/assets_amd64.go @@ -0,0 +1,14 @@ +//go:build windows && amd64 + +package windivert + +import _ "embed" + +//go:embed assets/WinDivert64.sys +var sysBytes []byte + +func assetFiles() []assetFile { + return []assetFile{{"WinDivert64.sys", sysBytes}} +} + +func driverSysName() string { return "WinDivert64.sys" } diff --git a/common/windivert/assets_unsupported.go b/common/windivert/assets_unsupported.go new file mode 100644 index 0000000000..04698953fa --- /dev/null +++ b/common/windivert/assets_unsupported.go @@ -0,0 +1,7 @@ +//go:build windows && !amd64 && !386 + +package windivert + +func assetFiles() []assetFile { return nil } + +func driverSysName() string { return "" } diff --git a/common/windivert/driver_windows.go b/common/windivert/driver_windows.go new file mode 100644 index 0000000000..d6bc59f893 --- /dev/null +++ b/common/windivert/driver_windows.go @@ -0,0 +1,212 @@ +//go:build windows + +package windivert + +import ( + "errors" + "os" + "path/filepath" + "runtime" + "strconv" + "sync" + + E "github.com/sagernet/sing/common/exceptions" + + "golang.org/x/sys/windows" +) + +const ( + driverServiceName = "WinDivert" + driverDeviceName = `\\.\WinDivert` +) + +var ( + driverOnce sync.Once + driverErr error + // driverDevName is ASCII-safe and must be available before ensureDriver + // so Open can try CreateFile first and only install on FILE_NOT_FOUND. + driverDevName, _ = windows.UTF16PtrFromString(driverDeviceName) +) + +// Requires SeLoadDriverPrivilege (Administrator). Running the 386 build +// under WOW64 on a 64-bit kernel is rejected — use the amd64 build. +func ensureDriver() error { + driverOnce.Do(func() { + driverErr = installDriver() + }) + return driverErr +} + +func installDriver() error { + if runtime.GOARCH == "386" { + var isWow64 bool + err := windows.IsWow64Process(windows.CurrentProcess(), &isWow64) + if err == nil && isWow64 { + return E.New("windivert: 386 build detected running under WOW64 on a 64-bit kernel; use the amd64 build") + } + } + + dir, err := ensureExtracted() + if err != nil { + return err + } + sysPath := filepath.Join(dir, driverSysName()) + sysPathW, err := windows.UTF16PtrFromString(sysPath) + if err != nil { + return E.Cause(err, "windivert: utf16 driver path") + } + + // Serialize driver install across concurrent processes. + mutexName, _ := windows.UTF16PtrFromString("WinDivertDriverInstallMutex") + mutex, err := windows.CreateMutex(nil, false, mutexName) + if err != nil { + return E.Cause(err, "windivert: create install mutex") + } + defer windows.CloseHandle(mutex) + _, err = windows.WaitForSingleObject(mutex, windows.INFINITE) + if err != nil { + return E.Cause(err, "windivert: wait install mutex") + } + defer windows.ReleaseMutex(mutex) + + manager, err := windows.OpenSCManager(nil, nil, windows.SC_MANAGER_ALL_ACCESS) + if err != nil { + return E.Cause(err, "windivert: open SCM") + } + defer windows.CloseServiceHandle(manager) + + serviceNameW, _ := windows.UTF16PtrFromString(driverServiceName) + service, err := windows.OpenService(manager, serviceNameW, windows.SERVICE_ALL_ACCESS) + if err != nil { + service, err = windows.CreateService( + manager, + serviceNameW, + serviceNameW, + windows.SERVICE_ALL_ACCESS, + windows.SERVICE_KERNEL_DRIVER, + windows.SERVICE_DEMAND_START, + windows.SERVICE_ERROR_NORMAL, + sysPathW, + nil, nil, nil, nil, nil, + ) + if err != nil { + if errors.Is(err, windows.ERROR_SERVICE_EXISTS) { + service, err = windows.OpenService(manager, serviceNameW, windows.SERVICE_ALL_ACCESS) + } + if err != nil { + return wrapDriverInstallError(err) + } + } + } + defer windows.CloseServiceHandle(service) + + err = windows.StartService(service, 0, nil) + if err != nil && errors.Is(err, windows.ERROR_SERVICE_DISABLED) { + // A prior process called DeleteService on a still-running kernel + // driver: SCM marks the record for deletion and flips START_TYPE + // to DISABLED until the last handle closes. Re-enable so we can + // start it instead of waiting for a reboot. + err = windows.ChangeServiceConfig( + service, + windows.SERVICE_NO_CHANGE, + windows.SERVICE_DEMAND_START, + windows.SERVICE_NO_CHANGE, + nil, nil, nil, nil, nil, nil, nil, + ) + if err != nil { + return E.Cause(err, "windivert: re-enable disabled service") + } + err = windows.StartService(service, 0, nil) + } + if err == nil { + // Mark for deletion so the driver unregisters when the last handle + // closes or on next reboot. Matches the upstream DLL's behavior: + // only the process that actually started the service takes on the + // cleanup responsibility. If another process already started it, + // we leave DeleteService to them. + _ = windows.DeleteService(service) + } else if !errors.Is(err, windows.ERROR_SERVICE_ALREADY_RUNNING) { + return E.Cause(err, "windivert: start service") + } + return nil +} + +func wrapDriverInstallError(err error) error { + if errors.Is(err, windows.ERROR_ACCESS_DENIED) { + return E.Cause(err, "windivert: installing the kernel driver requires Administrator privileges") + } + return E.Cause(err, "windivert: create service") +} + +type assetFile struct { + name string + data []byte +} + +var ( + extractOnce sync.Once + extractErr error + extractDir string +) + +// The on-disk copy is protected by Windows Authenticode signature +// enforcement, which rejects any tampered .sys at StartService time. +func ensureExtracted() (string, error) { + extractOnce.Do(func() { + extractDir, extractErr = extractImpl() + }) + return extractDir, extractErr +} + +func extractImpl() (string, error) { + files := assetFiles() + if len(files) == 0 { + return "", E.New("windivert: unsupported architecture ", runtime.GOARCH) + } + + base, err := os.UserCacheDir() + if err != nil { + return "", E.Cause(err, "windivert: locate user cache dir") + } + dir := filepath.Join(base, "sing-box", "windivert", "v"+AssetVersion) + err = os.MkdirAll(dir, 0o755) + if err != nil { + return "", E.Cause(err, "windivert: mkdir ", dir) + } + + for _, asset := range files { + err = ensureAsset(dir, asset) + if err != nil { + return "", err + } + } + return dir, nil +} + +// Concurrent sing-box processes race on os.Rename (atomic on NTFS); +// whichever wins creates the final file. Writers that lose the race +// silently discard their temp copy. +func ensureAsset(dir string, asset assetFile) error { + target := filepath.Join(dir, asset.name) + _, err := os.Stat(target) + if err == nil { + return nil + } + if !os.IsNotExist(err) { + return E.Cause(err, "windivert: stat ", asset.name) + } + tmp := target + ".tmp-" + strconv.Itoa(os.Getpid()) + err = os.WriteFile(tmp, asset.data, 0o644) + if err != nil { + return E.Cause(err, "windivert: write ", asset.name) + } + err = os.Rename(tmp, target) + if err != nil { + os.Remove(tmp) + if _, statErr := os.Stat(target); statErr == nil { + return nil + } + return E.Cause(err, "windivert: rename ", asset.name) + } + return nil +} diff --git a/common/windivert/filter.go b/common/windivert/filter.go new file mode 100644 index 0000000000..5c8fb5adcd --- /dev/null +++ b/common/windivert/filter.go @@ -0,0 +1,182 @@ +package windivert + +import ( + "encoding/binary" + "net/netip" + + E "github.com/sagernet/sing/common/exceptions" +) + +// WINDIVERT_FILTER VM instruction layout (24 bytes, #pragma pack(1)): +// +// word 0 (LE): field:11 | test:5 | success:16 +// word 1 (LE): failure:16 | neg:1 | reserved:15 +// words 2..5: arg[4] (native-endian uint32 each) +// +// The driver walks this as a decision tree: evaluate the test at inst i; +// on success jump to success; on failure jump to failure. Continuations +// 0x7FFE and 0x7FFF are ACCEPT and REJECT terminals. +const ( + filterInstBytes = 24 + filterMaxInsts = 256 + + fieldZero = 0 + fieldOutbound = 2 + fieldIP = 5 + fieldIPv6 = 6 + fieldTCP = 8 + fieldIPSrcAddr = 21 + fieldIPDstAddr = 22 + fieldIPv6SrcAddr = 28 + fieldIPv6DstAddr = 29 + fieldTCPSrcPort = 38 + fieldTCPDstPort = 39 + + testEQ = 0 + + resultAccept uint16 = 0x7FFE + resultReject uint16 = 0x7FFF +) + +// Filter flags passed to IOCTL_WINDIVERT_STARTUP alongside the compiled +// filter. These tell the driver what *kinds* of packets the filter might +// match, used as a kernel-side fast-reject. +const ( + filterFlagOutbound uint64 = 0x0020 + filterFlagIP uint64 = 0x0040 + filterFlagIPv6 uint64 = 0x0080 +) + +type filterInst struct { + field uint16 // 11 bits used + test uint8 // 5 bits used + success uint16 + failure uint16 + neg bool + arg [4]uint32 +} + +// Filter is a typed specification of packets to capture. It replaces +// WinDivert's filter string language. +// +// Zero value = "reject all" (match nothing), suitable for send-only handles. +type Filter struct { + insts []filterInst + flags uint64 // filter flags for STARTUP ioctl +} + +// reject returns a filter that matches no packet. The empty insts slice +// is encoded as a single rejecting instruction by encode(). +func reject() *Filter { + return &Filter{} +} + +// OutboundTCP returns a filter matching outbound TCP packets on the given +// 5-tuple. Both addresses must share an address family (IPv4 or IPv6). +func OutboundTCP(src, dst netip.AddrPort) (*Filter, error) { + if !src.IsValid() || !dst.IsValid() { + return nil, E.New("windivert: filter: invalid address port") + } + if src.Addr().Is4() != dst.Addr().Is4() { + return nil, E.New("windivert: filter: mixed IPv4/IPv6") + } + f := &Filter{ + flags: filterFlagOutbound, + } + // Insts chain as AND: each test's failure = REJECT, success = next inst. + // The final inst's success = ACCEPT. + f.add(fieldOutbound, testEQ, argUint32(1)) + if src.Addr().Is4() { + f.flags |= filterFlagIP + f.add(fieldIP, testEQ, argUint32(1)) + f.add(fieldTCP, testEQ, argUint32(1)) + f.add(fieldIPSrcAddr, testEQ, argIPv4(src.Addr())) + f.add(fieldIPDstAddr, testEQ, argIPv4(dst.Addr())) + } else { + f.flags |= filterFlagIPv6 + f.add(fieldIPv6, testEQ, argUint32(1)) + f.add(fieldTCP, testEQ, argUint32(1)) + f.add(fieldIPv6SrcAddr, testEQ, argIPv6(src.Addr())) + f.add(fieldIPv6DstAddr, testEQ, argIPv6(dst.Addr())) + } + f.add(fieldTCPSrcPort, testEQ, argUint32(uint32(src.Port()))) + f.add(fieldTCPDstPort, testEQ, argUint32(uint32(dst.Port()))) + return f, nil +} + +func (f *Filter) add(field uint16, test uint8, arg [4]uint32) { + f.insts = append(f.insts, filterInst{field: field, test: test, arg: arg}) +} + +func argUint32(v uint32) [4]uint32 { return [4]uint32{v, 0, 0, 0} } + +// argIPv4 encodes an IPv4 address for IP_SRCADDR/IP_DSTADDR. The driver +// compares against an IPv4-mapped-IPv6 form: {host_order_u32, 0x0000FFFF, +// 0, 0} (see sys/windivert.c windivert_get_ipv4_addr and the IPv4_SRCADDR +// val-word construction). Omitting the 0x0000FFFF marker causes the EQ +// test to fail for every packet. +func argIPv4(addr netip.Addr) [4]uint32 { + b := addr.As4() + return [4]uint32{binary.BigEndian.Uint32(b[:]), 0x0000FFFF, 0, 0} +} + +// argIPv6 encodes an IPv6 address for IPV6_SRCADDR/IPV6_DSTADDR. The +// driver stores the address as four host-order uint32s in REVERSED word +// order: val[0]=low (bytes 12..15), val[3]=high (bytes 0..3). See +// sys/windivert.c windivert_outbound_network_v6_classify val-word +// construction. +func argIPv6(addr netip.Addr) [4]uint32 { + b := addr.As16() + return [4]uint32{ + binary.BigEndian.Uint32(b[12:16]), + binary.BigEndian.Uint32(b[8:12]), + binary.BigEndian.Uint32(b[4:8]), + binary.BigEndian.Uint32(b[0:4]), + } +} + +// encode serializes the Filter to the on-wire WINDIVERT_FILTER[] format +// plus the filter_flags for STARTUP ioctl. +func (f *Filter) encode() ([]byte, uint64, error) { + if len(f.insts) == 0 { + // "Reject all" — one instruction, ZERO == 0 is always true, but we + // invert by setting both success and failure to REJECT. + return encodeInst(filterInst{ + field: fieldZero, + test: testEQ, + success: resultReject, + failure: resultReject, + }), 0, nil + } + if len(f.insts) > filterMaxInsts-1 { + return nil, 0, E.New("windivert: filter too long") + } + buf := make([]byte, 0, filterInstBytes*len(f.insts)) + for i, inst := range f.insts { + if i == len(f.insts)-1 { + inst.success = resultAccept + } else { + inst.success = uint16(i + 1) + } + inst.failure = resultReject + buf = append(buf, encodeInst(inst)...) + } + return buf, f.flags, nil +} + +func encodeInst(inst filterInst) []byte { + out := make([]byte, filterInstBytes) + word0 := uint32(inst.field&0x7FF) | uint32(inst.test&0x1F)<<11 | + uint32(inst.success)<<16 + word1 := uint32(inst.failure) + if inst.neg { + word1 |= 1 << 16 + } + binary.LittleEndian.PutUint32(out[0:4], word0) + binary.LittleEndian.PutUint32(out[4:8], word1) + binary.LittleEndian.PutUint32(out[8:12], inst.arg[0]) + binary.LittleEndian.PutUint32(out[12:16], inst.arg[1]) + binary.LittleEndian.PutUint32(out[16:20], inst.arg[2]) + binary.LittleEndian.PutUint32(out[20:24], inst.arg[3]) + return out +} diff --git a/common/windivert/filter_test.go b/common/windivert/filter_test.go new file mode 100644 index 0000000000..babac3e86a --- /dev/null +++ b/common/windivert/filter_test.go @@ -0,0 +1,140 @@ +package windivert + +import ( + "encoding/binary" + "net/netip" + "testing" +) + +func TestRejectFilter(t *testing.T) { + t.Parallel() + bin, flags, err := reject().encode() + if err != nil { + t.Fatal(err) + } + if len(bin) != filterInstBytes { + t.Fatalf("reject filter len: got %d, want %d", len(bin), filterInstBytes) + } + if flags != 0 { + t.Fatalf("reject filter flags: got %x, want 0", flags) + } + // word0: field=ZERO=0, test=EQ=0, success=REJECT=0x7FFF + word0 := binary.LittleEndian.Uint32(bin[0:4]) + if word0 != uint32(resultReject)<<16 { + t.Fatalf("reject word0 = %08x", word0) + } + // word1: failure=REJECT + word1 := binary.LittleEndian.Uint32(bin[4:8]) + if word1 != uint32(resultReject) { + t.Fatalf("reject word1 = %08x", word1) + } +} + +func TestOutboundTCPFilterIPv4(t *testing.T) { + t.Parallel() + src := netip.MustParseAddrPort("10.1.2.3:54321") + dst := netip.MustParseAddrPort("1.2.3.4:443") + f, err := OutboundTCP(src, dst) + if err != nil { + t.Fatal(err) + } + bin, flags, err := f.encode() + if err != nil { + t.Fatal(err) + } + if want := filterFlagOutbound | filterFlagIP; flags != want { + t.Fatalf("flags: got %x, want %x", flags, want) + } + // 7 instructions: OUTBOUND, IP, TCP, IP_SRCADDR, IP_DSTADDR, TCP_SRCPORT, TCP_DSTPORT + const wantInsts = 7 + if len(bin) != wantInsts*filterInstBytes { + t.Fatalf("instruction count: got %d, want %d", len(bin)/filterInstBytes, wantInsts) + } + + // Inst 0: OUTBOUND == 1, success=1, failure=REJECT + checkInst(t, bin[0*filterInstBytes:], 0, fieldOutbound, testEQ, 1, resultReject, 1) + // Inst 1: IP == 1, success=2 + checkInst(t, bin[1*filterInstBytes:], 1, fieldIP, testEQ, 2, resultReject, 1) + // Inst 2: TCP == 1, success=3 + checkInst(t, bin[2*filterInstBytes:], 2, fieldTCP, testEQ, 3, resultReject, 1) + // Inst 3: IP_SRCADDR == 10.1.2.3 (host-order uint32 = 0x0A010203, arg[1]=0x0000FFFF marker) + checkInst(t, bin[3*filterInstBytes:], 3, fieldIPSrcAddr, testEQ, 4, resultReject, 0x0A010203) + checkArg1(t, bin[3*filterInstBytes:], 3, 0x0000FFFF) + // Inst 4: IP_DSTADDR == 1.2.3.4 + checkInst(t, bin[4*filterInstBytes:], 4, fieldIPDstAddr, testEQ, 5, resultReject, 0x01020304) + checkArg1(t, bin[4*filterInstBytes:], 4, 0x0000FFFF) + // Inst 5: TCP_SRCPORT == 54321 + checkInst(t, bin[5*filterInstBytes:], 5, fieldTCPSrcPort, testEQ, 6, resultReject, 54321) + // Last inst 6: TCP_DSTPORT == 443, success=ACCEPT + checkInst(t, bin[6*filterInstBytes:], 6, fieldTCPDstPort, testEQ, resultAccept, resultReject, 443) +} + +func TestOutboundTCPFilterIPv6(t *testing.T) { + t.Parallel() + src := netip.MustParseAddrPort("[2001:db8::1]:54321") + dst := netip.MustParseAddrPort("[2001:db8::2]:443") + f, err := OutboundTCP(src, dst) + if err != nil { + t.Fatal(err) + } + bin, flags, err := f.encode() + if err != nil { + t.Fatal(err) + } + if want := filterFlagOutbound | filterFlagIPv6; flags != want { + t.Fatalf("flags: got %x, want %x", flags, want) + } + // Inst 3: IPv6_SRCADDR. The driver stores the address in reversed + // word order: arg[0]=low (bytes 12..15)=1, arg[3]=high (bytes 0..3)=0x20010db8. + off := 3 * filterInstBytes + a0 := binary.LittleEndian.Uint32(bin[off+8:]) + a1 := binary.LittleEndian.Uint32(bin[off+12:]) + a2 := binary.LittleEndian.Uint32(bin[off+16:]) + a3 := binary.LittleEndian.Uint32(bin[off+20:]) + if a0 != 1 || a1 != 0 || a2 != 0 || a3 != 0x20010db8 { + t.Fatalf("ipv6 src arg=[%08x %08x %08x %08x], want [1 0 0 0x20010db8]", a0, a1, a2, a3) + } +} + +func TestOutboundTCPFilterMixedFamily(t *testing.T) { + t.Parallel() + src := netip.MustParseAddrPort("10.0.0.1:1234") + dst := netip.MustParseAddrPort("[2001:db8::1]:443") + if _, err := OutboundTCP(src, dst); err == nil { + t.Fatal("expected error for mixed families") + } +} + +func checkArg1(t *testing.T, raw []byte, idx int, arg1 uint32) { + t.Helper() + got := binary.LittleEndian.Uint32(raw[12:16]) + if got != arg1 { + t.Errorf("inst %d arg[1]: got %08x, want %08x", idx, got, arg1) + } +} + +func checkInst(t *testing.T, raw []byte, idx int, field uint16, test uint8, success, failure uint16, arg0 uint32) { + t.Helper() + word0 := binary.LittleEndian.Uint32(raw[0:4]) + word1 := binary.LittleEndian.Uint32(raw[4:8]) + a0 := binary.LittleEndian.Uint32(raw[8:12]) + gotField := uint16(word0 & 0x7FF) + gotTest := uint8((word0 >> 11) & 0x1F) + gotSuccess := uint16(word0 >> 16) + gotFailure := uint16(word1 & 0xFFFF) + if gotField != field { + t.Errorf("inst %d field: got %d, want %d", idx, gotField, field) + } + if gotTest != test { + t.Errorf("inst %d test: got %d, want %d", idx, gotTest, test) + } + if gotSuccess != success { + t.Errorf("inst %d success: got %d, want %d", idx, gotSuccess, success) + } + if gotFailure != failure { + t.Errorf("inst %d failure: got %d, want %d", idx, gotFailure, failure) + } + if a0 != arg0 { + t.Errorf("inst %d arg[0]: got %08x, want %08x", idx, a0, arg0) + } +} diff --git a/common/windivert/handle_windows.go b/common/windivert/handle_windows.go new file mode 100644 index 0000000000..1d7aebfdef --- /dev/null +++ b/common/windivert/handle_windows.go @@ -0,0 +1,324 @@ +//go:build windows + +package windivert + +import ( + "encoding/binary" + "errors" + "runtime" + "sync" + "unsafe" + + E "github.com/sagernet/sing/common/exceptions" + + "golang.org/x/sys/windows" +) + +// Handle owns a WinDivert kernel device handle plus a private event for +// overlapped I/O. Methods on *Handle are not safe for concurrent use +// across goroutines (there is a single shared event per Handle). +// +// addr is a per-Handle Address buffer the IOCTL struct embeds a pointer +// to. It lives on the heap (as a field of a heap-allocated Handle) so +// the pointer value stored as bytes in the ioctl buffer remains valid +// across stack growth between buildIoctl* and the DeviceIoControl +// syscall — stack-local Address values are not safe for this pattern +// because Go's escape analysis does not see the pointer through the +// unsafe.Pointer → uintptr → bytes conversion. +type Handle struct { + device windows.Handle + event windows.Handle + closing sync.Once + closeErr error + addr Address +} + +// Filter may be nil for "reject all", suitable for send-only handles. +// Requires Administrator on first call per process (installs the kernel +// driver via SCM); subsequent calls reuse the running driver. +func Open(filter *Filter, layer Layer, priority int16, flags Flag) (*Handle, error) { + err := validateOpenArgs(layer, priority, flags) + if err != nil { + return nil, err + } + if filter == nil { + filter = reject() + } + filterBin, filterFlags, err := filter.encode() + if err != nil { + return nil, err + } + device, err := openDevice() + if err != nil { + if !errors.Is(err, windows.ERROR_FILE_NOT_FOUND) && + !errors.Is(err, windows.ERROR_PATH_NOT_FOUND) { + if errors.Is(err, windows.ERROR_ACCESS_DENIED) { + return nil, E.Cause(err, "windivert: open device (administrator required)") + } + return nil, E.Cause(err, "windivert: open device") + } + // Device node missing: kernel driver not loaded. Install + retry. + // Matches WinDivertOpen's lazy-install path; avoids racing StartService + // against a still-loaded driver whose SCM record is marked for deletion. + err = ensureDriver() + if err != nil { + return nil, err + } + device, err = openDevice() + if err != nil { + if errors.Is(err, windows.ERROR_ACCESS_DENIED) { + return nil, E.Cause(err, "windivert: open device (administrator required)") + } + return nil, E.Cause(err, "windivert: open device") + } + } + event, err := windows.CreateEvent(nil, 1, 0, nil) // manual reset, unsignaled + if err != nil { + windows.CloseHandle(device) + return nil, E.Cause(err, "windivert: create event") + } + h := &Handle{device: device, event: event} + + err = h.initialize(layer, priority, flags) + if err != nil { + h.Close() + return nil, err + } + err = h.startup(filterBin, filterFlags) + if err != nil { + h.Close() + return nil, err + } + return h, nil +} + +func openDevice() (windows.Handle, error) { + return windows.CreateFile( + driverDevName, + windows.GENERIC_READ|windows.GENERIC_WRITE, + 0, nil, + windows.OPEN_EXISTING, + windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_OVERLAPPED, + 0, + ) +} + +func validateOpenArgs(layer Layer, priority int16, flags Flag) error { + if layer != LayerNetwork { + return E.New("windivert: invalid layer ", uint32(layer)) + } + if priority < PriorityLowest || priority > PriorityHighest { + return E.New("windivert: priority out of range") + } + const supportedFlags = FlagSniff | FlagSendOnly + if flags&^supportedFlags != 0 { + return E.New("windivert: unknown flag bits") + } + if flags&FlagSniff != 0 && flags&FlagSendOnly != 0 { + return E.New("windivert: FlagSniff and FlagSendOnly are mutually exclusive") + } + return nil +} + +func (h *Handle) initialize(layer Layer, priority int16, flags Flag) error { + in := buildIoctlInitialize(layer, priority, flags) + // WINDIVERT_VERSION is a 64-byte packed struct; only the first 20 + // bytes (magic, major, minor, bits) carry data, the rest is reserved. + var outBuf [versionStructSize]byte + binary.LittleEndian.PutUint64(outBuf[0:8], magicDLL) + binary.LittleEndian.PutUint32(outBuf[8:12], versionMajor) + binary.LittleEndian.PutUint32(outBuf[12:16], versionMinor) + binary.LittleEndian.PutUint32(outBuf[16:20], uint32(unsafe.Sizeof(uintptr(0))*8)) + _, err := doIoctl(h.device, ioctlInitialize, in[:], outBuf[:], h.event) + if err != nil { + return E.Cause(err, "windivert: initialize ioctl") + } + gotMagic := binary.LittleEndian.Uint64(outBuf[0:8]) + if gotMagic != magicSYS { + return E.New("windivert: driver magic mismatch (got ", gotMagic, ")") + } + gotMajor := binary.LittleEndian.Uint32(outBuf[8:12]) + if gotMajor < versionMajor { + gotMinor := binary.LittleEndian.Uint32(outBuf[12:16]) + return E.New("windivert: driver version too old: ", gotMajor, ".", gotMinor) + } + return nil +} + +func (h *Handle) startup(filterBin []byte, filterFlags uint64) error { + in := buildIoctlStartup(filterFlags) + _, err := doIoctl(h.device, ioctlStartup, in[:], filterBin, h.event) + if err != nil { + return E.Cause(err, "windivert: startup ioctl") + } + return nil +} + +// If the handle is closed mid-Recv the error wraps ERROR_OPERATION_ABORTED. +func (h *Handle) Recv(buf []byte) (int, Address, error) { + if len(buf) == 0 { + return 0, Address{}, E.New("windivert: recv: zero-length buffer") + } + h.addr = Address{} + in := buildIoctlRecv(&h.addr) + n, err := doIoctl(h.device, ioctlRecv, in[:], buf, h.event) + runtime.KeepAlive(h) + if err != nil { + return 0, Address{}, err + } + return int(n), h.addr, nil +} + +// The address's Outbound flag controls whether the packet is sent toward +// the wire (outbound=true) or delivered up the stack (outbound=false). +// IfIdx and SubIfIdx can stay zero — the driver uses the routing table +// when IfIdx=0. +func (h *Handle) Send(packet []byte, addr *Address) (int, error) { + if len(packet) == 0 { + return 0, E.New("windivert: send: empty packet") + } + if addr == nil { + return 0, E.New("windivert: send: nil address") + } + h.addr = *addr + in := buildIoctlSend(&h.addr) + n, err := doIoctl(h.device, ioctlSend, in[:], packet, h.event) + runtime.KeepAlive(h) + if err != nil { + return 0, err + } + return int(n), nil +} + +// Idempotent. Aborts any in-flight I/O on the handle. +func (h *Handle) Close() error { + h.closing.Do(func() { + var errs []error + if h.device != 0 { + err := windows.CloseHandle(h.device) + if err != nil { + errs = append(errs, err) + } + h.device = 0 + } + if h.event != 0 { + err := windows.CloseHandle(h.event) + if err != nil { + errs = append(errs, err) + } + h.event = 0 + } + h.closeErr = E.Errors(errs...) + }) + return h.closeErr +} + +// IOCTL codes from windivert_device.h. CTL_CODE macro layout: +// +// (DeviceType << 16) | (Access << 14) | (Function << 2) | Method +const ( + fileDeviceNetwork uint32 = 0x12 + accessReadWrite uint32 = 3 // FILE_READ_DATA | FILE_WRITE_DATA + accessRead uint32 = 1 + + methodInDirect uint32 = 1 + methodOutDirect uint32 = 2 +) + +func ctlCode(deviceType, access, function, method uint32) uint32 { + return (deviceType << 16) | (access << 14) | (function << 2) | method +} + +var ( + ioctlInitialize = ctlCode(fileDeviceNetwork, accessReadWrite, 0x921, methodOutDirect) + ioctlStartup = ctlCode(fileDeviceNetwork, accessReadWrite, 0x922, methodInDirect) + ioctlRecv = ctlCode(fileDeviceNetwork, accessRead, 0x923, methodOutDirect) + ioctlSend = ctlCode(fileDeviceNetwork, accessReadWrite, 0x924, methodInDirect) +) + +// Magic numbers exchanged during INITIALIZE. DLL sends magicDLL in the +// version struct; driver returns magicSYS on success. +const ( + magicDLL uint64 = 0x4C4C447669645724 // "$WdivDLL" in LE bytes + magicSYS uint64 = 0x5359537669645723 // "#WdivSYS" in LE bytes +) + +const ( + versionMajor uint32 = 2 + versionMinor uint32 = 2 +) + +// Size of the WINDIVERT_IOCTL union on wire (packed). +const ioctlSize = 16 + +// Size of WINDIVERT_VERSION on wire (packed). Only the first 20 bytes +// carry data; the rest is reserved zero padding. +const versionStructSize = 64 + +// doIoctl performs a single synchronous (blocking) overlapped +// DeviceIoControl. The handle is opened with FILE_FLAG_OVERLAPPED so +// DeviceIoControl returns ERROR_IO_PENDING; we then wait for completion +// via GetOverlappedResult. Event is passed in so callers can reuse it +// across calls on the same handle (avoids per-call CreateEvent). +func doIoctl(handle windows.Handle, code uint32, in []byte, out []byte, event windows.Handle) (uint32, error) { + var overlapped windows.Overlapped + overlapped.HEvent = event + _ = windows.ResetEvent(event) + + var inPtr *byte + var inLen uint32 + if len(in) > 0 { + inPtr = &in[0] + inLen = uint32(len(in)) + } + var outPtr *byte + var outLen uint32 + if len(out) > 0 { + outPtr = &out[0] + outLen = uint32(len(out)) + } + var returned uint32 + err := windows.DeviceIoControl(handle, code, inPtr, inLen, outPtr, outLen, &returned, &overlapped) + if err != nil && !errors.Is(err, windows.ERROR_IO_PENDING) { + return 0, err + } + err = windows.GetOverlappedResult(handle, &overlapped, &returned, true) + if err != nil { + return 0, err + } + return returned, nil +} + +func buildIoctlInitialize(layer Layer, priority int16, flags Flag) [ioctlSize]byte { + var buf [ioctlSize]byte + binary.LittleEndian.PutUint32(buf[0:4], uint32(layer)) + // The driver expects priority + WINDIVERT_PRIORITY_HIGHEST (30000) so + // the low range maps to non-negative integers. + binary.LittleEndian.PutUint32(buf[4:8], uint32(int32(priority)+int32(PriorityHighest))) + binary.LittleEndian.PutUint64(buf[8:16], uint64(flags)) + return buf +} + +func buildIoctlStartup(filterFlags uint64) [ioctlSize]byte { + var buf [ioctlSize]byte + binary.LittleEndian.PutUint64(buf[0:8], filterFlags) + return buf +} + +// buildIoctlRecv packs a user-space pointer to a WINDIVERT_ADDRESS into +// the ioctl struct. The driver dereferences it to write the address for +// the received packet. Caller must keep the Address alive via +// runtime.KeepAlive. +func buildIoctlRecv(addr *Address) [ioctlSize]byte { + var buf [ioctlSize]byte + binary.LittleEndian.PutUint64(buf[0:8], uint64(uintptr(unsafe.Pointer(addr)))) + binary.LittleEndian.PutUint64(buf[8:16], 0) + return buf +} + +func buildIoctlSend(addr *Address) [ioctlSize]byte { + var buf [ioctlSize]byte + binary.LittleEndian.PutUint64(buf[0:8], uint64(uintptr(unsafe.Pointer(addr)))) + binary.LittleEndian.PutUint64(buf[8:16], uint64(unsafe.Sizeof(Address{}))) + return buf +} diff --git a/common/windivert/handle_windows_test.go b/common/windivert/handle_windows_test.go new file mode 100644 index 0000000000..73dfbb166a --- /dev/null +++ b/common/windivert/handle_windows_test.go @@ -0,0 +1,109 @@ +//go:build windows + +package windivert + +import ( + "encoding/binary" + "testing" + "unsafe" + + "github.com/stretchr/testify/require" +) + +// CTL_CODE macro from Windows DDK: +// +// (DeviceType<<16) | (Access<<14) | (Function<<2) | Method +func TestCtlCodeMatchesDDK(t *testing.T) { + t.Parallel() + // FILE_DEVICE_NETWORK=0x12, FILE_READ_DATA|FILE_WRITE_DATA=3, METHOD_OUT_DIRECT=2 + require.Equal(t, uint32(0x12E486), ctlCode(0x12, 3, 0x921, 2)) + // FILE_READ_DATA=1, METHOD_OUT_DIRECT=2 + require.Equal(t, uint32(0x12648E), ctlCode(0x12, 1, 0x923, 2)) +} + +// Baked-in against windivert_device.h @ v2.2.2. A mismatch here means the +// kernel will reject every ioctl with ERROR_INVALID_FUNCTION. +func TestIoctlCodesMatchUpstream(t *testing.T) { + t.Parallel() + require.Equal(t, uint32(0x12E486), ioctlInitialize) + require.Equal(t, uint32(0x12E489), ioctlStartup) + require.Equal(t, uint32(0x12648E), ioctlRecv) + require.Equal(t, uint32(0x12E491), ioctlSend) +} + +func TestBuildIoctlInitialize(t *testing.T) { + t.Parallel() + buf := buildIoctlInitialize(LayerNetwork, 100, FlagSendOnly) + require.Equal(t, uint32(LayerNetwork), binary.LittleEndian.Uint32(buf[0:4])) + // Driver expects priority+PriorityHighest(30000) so the range is non-negative. + require.Equal(t, uint32(30100), binary.LittleEndian.Uint32(buf[4:8])) + require.Equal(t, uint64(FlagSendOnly), binary.LittleEndian.Uint64(buf[8:16])) +} + +func TestBuildIoctlInitializePriorityRange(t *testing.T) { + t.Parallel() + lowest := buildIoctlInitialize(LayerNetwork, PriorityLowest, 0) + require.Equal(t, uint32(0), binary.LittleEndian.Uint32(lowest[4:8])) + highest := buildIoctlInitialize(LayerNetwork, PriorityHighest, 0) + require.Equal(t, uint32(60000), binary.LittleEndian.Uint32(highest[4:8])) + zero := buildIoctlInitialize(LayerNetwork, 0, 0) + require.Equal(t, uint32(30000), binary.LittleEndian.Uint32(zero[4:8])) +} + +func TestBuildIoctlStartup(t *testing.T) { + t.Parallel() + flags := filterFlagOutbound | filterFlagIP + buf := buildIoctlStartup(flags) + require.Equal(t, flags, binary.LittleEndian.Uint64(buf[0:8])) + // The second quad-word is unused for STARTUP. + require.Equal(t, uint64(0), binary.LittleEndian.Uint64(buf[8:16])) +} + +func TestBuildIoctlRecvEmbedsAddressPointer(t *testing.T) { + t.Parallel() + addr := &Address{Timestamp: 0xCAFEBABE} + buf := buildIoctlRecv(addr) + require.Equal(t, uint64(uintptr(unsafe.Pointer(addr))), + binary.LittleEndian.Uint64(buf[0:8])) + // RECV does not carry an address length; driver writes full Address back. + require.Equal(t, uint64(0), binary.LittleEndian.Uint64(buf[8:16])) +} + +func TestBuildIoctlSendEmbedsAddressPointerAndSize(t *testing.T) { + t.Parallel() + addr := &Address{} + buf := buildIoctlSend(addr) + require.Equal(t, uint64(uintptr(unsafe.Pointer(addr))), + binary.LittleEndian.Uint64(buf[0:8])) + require.Equal(t, uint64(unsafe.Sizeof(Address{})), + binary.LittleEndian.Uint64(buf[8:16])) + require.Equal(t, uint64(80), binary.LittleEndian.Uint64(buf[8:16])) +} + +func TestValidateOpenArgsLayer(t *testing.T) { + t.Parallel() + require.NoError(t, validateOpenArgs(LayerNetwork, 0, 0)) + require.Error(t, validateOpenArgs(Layer(1), 0, 0)) + require.Error(t, validateOpenArgs(Layer(42), 0, 0)) +} + +func TestValidateOpenArgsPriorityBounds(t *testing.T) { + t.Parallel() + require.NoError(t, validateOpenArgs(LayerNetwork, PriorityHighest, 0)) + require.NoError(t, validateOpenArgs(LayerNetwork, PriorityLowest, 0)) + require.NoError(t, validateOpenArgs(LayerNetwork, 0, 0)) + require.Error(t, validateOpenArgs(LayerNetwork, PriorityHighest+1, 0)) + require.Error(t, validateOpenArgs(LayerNetwork, PriorityLowest-1, 0)) +} + +func TestValidateOpenArgsFlags(t *testing.T) { + t.Parallel() + require.NoError(t, validateOpenArgs(LayerNetwork, 0, 0)) + require.NoError(t, validateOpenArgs(LayerNetwork, 0, FlagSendOnly)) + require.NoError(t, validateOpenArgs(LayerNetwork, 0, FlagSniff)) + // Sniff and send-only describe contradictory handle roles. + require.Error(t, validateOpenArgs(LayerNetwork, 0, FlagSniff|FlagSendOnly)) + // Unknown flag bits must be rejected to surface caller mistakes early. + require.Error(t, validateOpenArgs(LayerNetwork, 0, Flag(0x10))) + require.Error(t, validateOpenArgs(LayerNetwork, 0, FlagSendOnly|Flag(0x10))) +} diff --git a/common/windivert/integration_windows_test.go b/common/windivert/integration_windows_test.go new file mode 100644 index 0000000000..1b4ce958cc --- /dev/null +++ b/common/windivert/integration_windows_test.go @@ -0,0 +1,88 @@ +//go:build windows + +package windivert + +import ( + "errors" + "net/netip" + "testing" + "time" + + "github.com/stretchr/testify/require" + "golang.org/x/sys/windows" +) + +func openHandle(t *testing.T, filter *Filter, flags Flag) *Handle { + t.Helper() + h, err := Open(filter, LayerNetwork, 0, flags) + require.NoError(t, err) + return h +} + +// A send-only handle installs+opens the driver but does not attach a +// receive filter, so it exercises the full driver-install path without +// diverting any live traffic on the host. +func TestIntegrationOpenSendOnly(t *testing.T) { + h := openHandle(t, nil, FlagSendOnly) + require.NoError(t, h.Close()) +} + +// Close is idempotent per the doc contract. +func TestIntegrationCloseTwice(t *testing.T) { + h := openHandle(t, nil, FlagSendOnly) + require.NoError(t, h.Close()) + require.NoError(t, h.Close()) +} + +// Recv must unblock when the handle is closed concurrently. Without this, +// the spoofer's run goroutine could deadlock on shutdown. +func TestIntegrationRecvAbortsOnClose(t *testing.T) { + // A filter no live traffic will match, so Recv blocks indefinitely + // until Close aborts the overlapped I/O. + filter, err := OutboundTCP( + netip.MustParseAddrPort("10.255.255.254:1"), + netip.MustParseAddrPort("10.255.255.253:2"), + ) + require.NoError(t, err) + h := openHandle(t, filter, 0) + + errCh := make(chan error, 1) + go func() { + buf := make([]byte, MTUMax) + _, _, recvErr := h.Recv(buf) + errCh <- recvErr + }() + + // Let Recv reach the blocking DeviceIoControl before Close races in. + time.Sleep(200 * time.Millisecond) + require.NoError(t, h.Close()) + + select { + case err := <-errCh: + require.Error(t, err) + require.True(t, errors.Is(err, windows.ERROR_OPERATION_ABORTED), + "Recv should return ERROR_OPERATION_ABORTED, got %v", err) + case <-time.After(3 * time.Second): + t.Fatal("Recv did not unblock within 3s after Close") + } +} + +// Two concurrent Open calls must both succeed: the first wins the driver +// install race, the second reuses the already-running service. +func TestIntegrationConcurrentOpen(t *testing.T) { + errCh := make(chan error, 2) + handles := make(chan *Handle, 2) + for range 2 { + go func() { + h, err := Open(nil, LayerNetwork, 0, FlagSendOnly) + handles <- h + errCh <- err + }() + } + for range 2 { + err := <-errCh + h := <-handles + require.NoError(t, err) + require.NoError(t, h.Close()) + } +} diff --git a/common/windivert/windivert.go b/common/windivert/windivert.go new file mode 100644 index 0000000000..619e41b611 --- /dev/null +++ b/common/windivert/windivert.go @@ -0,0 +1,78 @@ +// Package windivert provides a pure-Go binding to the WinDivert kernel +// driver on Windows (amd64 and 386). User-mode WinDivert calls are +// reimplemented in Go; only the signed kernel driver is embedded as an +// asset, since SCM-installed drivers must live on disk and their +// Authenticode signature forbids modification. +// +// Administrator is required for the first Open in a process so SCM can +// load the driver. Upstream: https://github.com/basil00/WinDivert v2.2.2, +// redistributed under its LGPL v3 option; see assets/LICENSE.txt. +package windivert + +import "unsafe" + +const AssetVersion = "2.2.2" + +// MTUMax is WINDIVERT_MTU_MAX from windivert.h (40 + 0xFFFF). Suitable as +// a single-packet receive buffer size. +const MTUMax = 40 + 0xFFFF + +type Layer uint32 + +const LayerNetwork Layer = 0 + +type Flag uint64 + +const ( + // FlagSniff opens a passive observer: the driver copies matching packets + // to userspace without removing them from the network stack. Send is not + // required (and not allowed) on a sniffing handle. + FlagSniff Flag = 0x0001 + // FlagSendOnly opens a write-only injection handle; Recv is not allowed. + FlagSendOnly Flag = 0x0008 +) + +const ( + PriorityHighest int16 = 30000 + PriorityLowest int16 = -30000 +) + +// Address mirrors WINDIVERT_ADDRESS from windivert.h (80 bytes, +// little-endian on both amd64 and 386): +// +// 0: INT64 Timestamp +// 8: UINT32 bitfield: Layer:8 | Event:8 | flags | Reserved1:8 +// 12: UINT32 Reserved2 +// 16: 64 bytes union (WINDIVERT_DATA_NETWORK / FLOW / SOCKET / REFLECT) +type Address struct { + Timestamp int64 + bits uint32 + Reserved2 uint32 + _ [64]byte +} + +var _ [80]byte = [unsafe.Sizeof(Address{})]byte{} + +// Bit positions inside the Address's packed flags word. +const ( + addrBitIPv6 = 20 + addrBitIPChecksum = 21 + addrBitTCPChecksum = 22 +) + +func getFlagBit(bits uint32, pos uint) bool { return bits&(1<= Size { + buf = buf[:Size] + } else { + buf = make([]byte, Size) + } + + return &Buffer{ + v: buf, + } +} + +// NewExisted creates a standard size Buffer with an existed bytearray, managed. +func NewExisted(b []byte) *Buffer { + if cap(b) < Size { + panic("Invalid buffer") + } + + oLen := len(b) + if oLen < Size { + b = b[:Size] + } + + return &Buffer{ + v: b, + end: int32(oLen), + } +} + +// FromBytes creates a Buffer with an existed bytearray, unmanaged. +func FromBytes(b []byte) *Buffer { + return &Buffer{ + v: b, + end: int32(len(b)), + ownership: unmanaged, + } +} + +// StackNew creates a new Buffer object on stack, managed. +// This method is for buffers that is released in the same function. +func StackNew() Buffer { + buf := pool.Get().([]byte) + if cap(buf) >= Size { + buf = buf[:Size] + } else { + buf = make([]byte, Size) + } + + return Buffer{ + v: buf, + } +} + +// NewWithSize creates a Buffer with 0 length and capacity with at least the given size, bytespool's. +func NewWithSize(size int32) *Buffer { + return &Buffer{ + v: bytespool.Alloc(size), + ownership: bytespools, + } +} + +// Release recycles the buffer into an internal buffer pool. +func (b *Buffer) Release() { + if b == nil || b.v == nil || b.ownership == unmanaged { + return + } + + p := b.v + b.v = nil + b.Clear() + + switch b.ownership { + case managed: + if cap(p) == Size { + pool.Put(p) + } + case bytespools: + bytespool.Free(p) + } + b.UDP = nil +} + +// Clear clears the content of the buffer, results an empty buffer with +// Len() = 0. +func (b *Buffer) Clear() { + b.start = 0 + b.end = 0 +} + +// Byte returns the bytes at index. +func (b *Buffer) Byte(index int32) byte { + return b.v[b.start+index] +} + +// SetByte sets the byte value at index. +func (b *Buffer) SetByte(index int32, value byte) { + b.v[b.start+index] = value +} + +// Bytes returns the content bytes of this Buffer. +func (b *Buffer) Bytes() []byte { + return b.v[b.start:b.end] +} + +// Extend increases the buffer size by n bytes, and returns the extended part. +// It panics if result size is larger than size of this buffer. +func (b *Buffer) Extend(n int32) []byte { + end := b.end + n + if end > int32(len(b.v)) { + panic("extending out of bound") + } + ext := b.v[b.end:end] + b.end = end + clear(ext) + return ext +} + +// BytesRange returns a slice of this buffer with given from and to boundary. +func (b *Buffer) BytesRange(from, to int32) []byte { + if from < 0 { + from += b.Len() + } + if to < 0 { + to += b.Len() + } + return b.v[b.start+from : b.start+to] +} + +// BytesFrom returns a slice of this Buffer starting from the given position. +func (b *Buffer) BytesFrom(from int32) []byte { + if from < 0 { + from += b.Len() + } + return b.v[b.start+from : b.end] +} + +// BytesTo returns a slice of this Buffer from start to the given position. +func (b *Buffer) BytesTo(to int32) []byte { + if to < 0 { + to += b.Len() + } + if to < 0 { + to = 0 + } + return b.v[b.start : b.start+to] +} + +// Check makes sure that 0 <= b.start <= b.end. +func (b *Buffer) Check() { + if b.start < 0 { + b.start = 0 + } + if b.end < 0 { + b.end = 0 + } + if b.start > b.end { + b.start = b.end + } +} + +// Resize cuts the buffer at the given position. +func (b *Buffer) Resize(from, to int32) { + oldEnd := b.end + if from < 0 { + from += b.Len() + } + if to < 0 { + to += b.Len() + } + if to < from { + panic("Invalid slice") + } + b.end = b.start + to + b.start += from + b.Check() + if b.end > oldEnd { + clear(b.v[oldEnd:b.end]) + } +} + +// Advance cuts the buffer at the given position. +func (b *Buffer) Advance(from int32) { + if from < 0 { + from += b.Len() + } + b.start += from + b.Check() +} + +// Len returns the length of the buffer content. +func (b *Buffer) Len() int32 { + if b == nil { + return 0 + } + return b.end - b.start +} + +// Cap returns the capacity of the buffer content. +func (b *Buffer) Cap() int32 { + if b == nil { + return 0 + } + return int32(len(b.v)) +} + +// Available returns the available capacity of the buffer content. +func (b *Buffer) Available() int32 { + if b == nil { + return 0 + } + return int32(len(b.v)) - b.end +} + +// IsEmpty returns true if the buffer is empty. +func (b *Buffer) IsEmpty() bool { + return b.Len() == 0 +} + +// IsFull returns true if the buffer has no more room to grow. +func (b *Buffer) IsFull() bool { + return b != nil && b.end == int32(len(b.v)) +} + +// Write implements Write method in io.Writer. +func (b *Buffer) Write(data []byte) (int, error) { + nBytes := copy(b.v[b.end:], data) + b.end += int32(nBytes) + if nBytes < len(data) { + return nBytes, ErrBufferFull + } + return nBytes, nil +} + +// WriteByte writes a single byte into the buffer. +func (b *Buffer) WriteByte(v byte) error { + if b.IsFull() { + return ErrBufferFull + } + b.v[b.end] = v + b.end++ + return nil +} + +// WriteString implements io.StringWriter. +func (b *Buffer) WriteString(s string) (int, error) { + return b.Write([]byte(s)) +} + +// ReadByte implements io.ByteReader +func (b *Buffer) ReadByte() (byte, error) { + if b.start == b.end { + return 0, io.EOF + } + + nb := b.v[b.start] + b.start++ + return nb, nil +} + +// ReadBytes implements bufio.Reader.ReadBytes +func (b *Buffer) ReadBytes(length int32) ([]byte, error) { + if b.end-b.start < length { + return nil, io.EOF + } + + nb := b.v[b.start : b.start+length] + b.start += length + return nb, nil +} + +// Read implements io.Reader.Read(). +func (b *Buffer) Read(data []byte) (int, error) { + if b.Len() == 0 { + return 0, io.EOF + } + nBytes := copy(data, b.v[b.start:b.end]) + if int32(nBytes) == b.Len() { + b.Clear() + } else { + b.start += int32(nBytes) + } + return nBytes, nil +} + +// ReadFrom implements io.ReaderFrom. +func (b *Buffer) ReadFrom(reader io.Reader) (int64, error) { + n, err := reader.Read(b.v[b.end:]) + b.end += int32(n) + return int64(n), err +} + +// ReadFullFrom reads exact size of bytes from given reader, or until error occurs. +func (b *Buffer) ReadFullFrom(reader io.Reader, size int32) (int64, error) { + end := b.end + size + if end > int32(len(b.v)) { + v := end + return 0, E.New("out of bound: ", v) + } + n, err := io.ReadFull(reader, b.v[b.end:end]) + b.end += int32(n) + return int64(n), err +} + +// String returns the string form of this Buffer. +func (b *Buffer) String() string { + return string(b.Bytes()) +} diff --git a/common/xray/buf/copy.go b/common/xray/buf/copy.go new file mode 100644 index 0000000000..efab0e175a --- /dev/null +++ b/common/xray/buf/copy.go @@ -0,0 +1,124 @@ +package buf + +import ( + "io" + "time" + + "github.com/sagernet/sing-box/common/xray/errors" + "github.com/sagernet/sing-box/common/xray/signal" + E "github.com/sagernet/sing/common/exceptions" +) + +type dataHandler func(MultiBuffer) + +type copyHandler struct { + onData []dataHandler +} + +// SizeCounter is for counting bytes copied by Copy(). +type SizeCounter struct { + Size int64 +} + +// CopyOption is an option for copying data. +type CopyOption func(*copyHandler) + +// UpdateActivity is a CopyOption to update activity on each data copy operation. +func UpdateActivity(timer signal.ActivityUpdater) CopyOption { + return func(handler *copyHandler) { + handler.onData = append(handler.onData, func(MultiBuffer) { + timer.Update() + }) + } +} + +// CountSize is a CopyOption that sums the total size of data copied into the given SizeCounter. +func CountSize(sc *SizeCounter) CopyOption { + return func(handler *copyHandler) { + handler.onData = append(handler.onData, func(b MultiBuffer) { + sc.Size += int64(b.Len()) + }) + } +} + +type readError struct { + error +} + +func (e readError) Error() string { + return e.error.Error() +} + +func (e readError) Unwrap() error { + return e.error +} + +// IsReadError returns true if the error in Copy() comes from reading. +func IsReadError(err error) bool { + _, ok := err.(readError) + return ok +} + +type writeError struct { + error +} + +func (e writeError) Error() string { + return e.error.Error() +} + +func (e writeError) Unwrap() error { + return e.error +} + +// IsWriteError returns true if the error in Copy() comes from writing. +func IsWriteError(err error) bool { + _, ok := err.(writeError) + return ok +} + +func copyInternal(reader Reader, writer Writer, handler *copyHandler) error { + for { + buffer, err := reader.ReadMultiBuffer() + if !buffer.IsEmpty() { + for _, handler := range handler.onData { + handler(buffer) + } + + if werr := writer.WriteMultiBuffer(buffer); werr != nil { + return writeError{werr} + } + } + + if err != nil { + return readError{err} + } + } +} + +// Copy dumps all payload from reader to writer or stops when an error occurs. It returns nil when EOF. +func Copy(reader Reader, writer Writer, options ...CopyOption) error { + var handler copyHandler + for _, option := range options { + option(&handler) + } + err := copyInternal(reader, writer, &handler) + if err != nil && errors.Cause(err) != io.EOF { + return err + } + return nil +} + +var ErrNotTimeoutReader = E.New("not a TimeoutReader") + +func CopyOnceTimeout(reader Reader, writer Writer, timeout time.Duration) error { + timeoutReader, ok := reader.(TimeoutReader) + if !ok { + return ErrNotTimeoutReader + } + mb, err := timeoutReader.ReadMultiBufferTimeout(timeout) + if err != nil { + return err + } + return writer.WriteMultiBuffer(mb) +} diff --git a/common/xray/buf/io.go b/common/xray/buf/io.go new file mode 100644 index 0000000000..5b7826e0de --- /dev/null +++ b/common/xray/buf/io.go @@ -0,0 +1,127 @@ +package buf + +import ( + "io" + "net" + "syscall" + "time" + + "github.com/sagernet/sing-box/common/xray/stat" + "github.com/sagernet/sing-box/common/xray/stats" + E "github.com/sagernet/sing/common/exceptions" +) + +// Reader extends io.Reader with MultiBuffer. +type Reader interface { + // ReadMultiBuffer reads content from underlying reader, and put it into a MultiBuffer. + ReadMultiBuffer() (MultiBuffer, error) +} + +// ErrReadTimeout is an error that happens with IO timeout. +var ErrReadTimeout = E.New("IO timeout") + +// TimeoutReader is a reader that returns error if Read() operation takes longer than the given timeout. +type TimeoutReader interface { + Reader + ReadMultiBufferTimeout(time.Duration) (MultiBuffer, error) +} + +// Writer extends io.Writer with MultiBuffer. +type Writer interface { + // WriteMultiBuffer writes a MultiBuffer into underlying writer. + WriteMultiBuffer(MultiBuffer) error +} + +// WriteAllBytes ensures all bytes are written into the given writer. +func WriteAllBytes(writer io.Writer, payload []byte, c stats.Counter) error { + wc := 0 + defer func() { + if c != nil { + c.Add(int64(wc)) + } + }() + + for len(payload) > 0 { + n, err := writer.Write(payload) + wc += n + if err != nil { + return err + } + payload = payload[n:] + } + return nil +} + +func isPacketReader(reader io.Reader) bool { + _, ok := reader.(net.PacketConn) + return ok +} + +// NewReader creates a new Reader. +// The Reader instance doesn't take the ownership of reader. +func NewReader(reader io.Reader) Reader { + if mr, ok := reader.(Reader); ok { + return mr + } + + if isPacketReader(reader) { + return &PacketReader{ + Reader: reader, + } + } + + return &SingleReader{ + Reader: reader, + } +} + +// NewPacketReader creates a new PacketReader based on the given reader. +func NewPacketReader(reader io.Reader) Reader { + if mr, ok := reader.(Reader); ok { + return mr + } + + return &PacketReader{ + Reader: reader, + } +} + +func isPacketWriter(writer io.Writer) bool { + if _, ok := writer.(net.PacketConn); ok { + return true + } + + // If the writer doesn't implement syscall.Conn, it is probably not a TCP connection. + if _, ok := writer.(syscall.Conn); !ok { + return true + } + return false +} + +// NewWriter creates a new Writer. +func NewWriter(writer io.Writer) Writer { + if mw, ok := writer.(Writer); ok { + return mw + } + + iConn := writer + if statConn, ok := writer.(*stat.CounterConnection); ok { + iConn = statConn.Connection + } + + if isPacketWriter(iConn) { + return &SequentialWriter{ + Writer: writer, + } + } + + var counter stats.Counter + + if statConn, ok := writer.(*stat.CounterConnection); ok { + counter = statConn.WriteCounter + } + return &BufferToBytesWriter{ + Writer: iConn, + counter: counter, + } +} diff --git a/common/xray/buf/multi_buffer.go b/common/xray/buf/multi_buffer.go new file mode 100644 index 0000000000..44dd499c00 --- /dev/null +++ b/common/xray/buf/multi_buffer.go @@ -0,0 +1,310 @@ +package buf + +import ( + "io" + + "github.com/sagernet/sing-box/common/xray" + "github.com/sagernet/sing-box/common/xray/errors" + "github.com/sagernet/sing-box/common/xray/serial" +) + +// ReadAllToBytes reads all content from the reader into a byte array, until EOF. +func ReadAllToBytes(reader io.Reader) ([]byte, error) { + mb, err := ReadFrom(reader) + if err != nil { + return nil, err + } + if mb.Len() == 0 { + return nil, nil + } + b := make([]byte, mb.Len()) + mb, _ = SplitBytes(mb, b) + ReleaseMulti(mb) + return b, nil +} + +// MultiBuffer is a list of Buffers. The order of Buffer matters. +type MultiBuffer []*Buffer + +// MergeMulti merges content from src to dest, and returns the new address of dest and src +func MergeMulti(dest MultiBuffer, src MultiBuffer) (MultiBuffer, MultiBuffer) { + dest = append(dest, src...) + for idx := range src { + src[idx] = nil + } + return dest, src[:0] +} + +// 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) + src = src[nBytes:] + } + + for len(src) > 0 { + b := New() + nBytes, _ := b.Write(src) + src = src[nBytes:] + dest = append(dest, b) + } + + return dest +} + +// ReleaseMulti releases all content of the MultiBuffer, and returns an empty MultiBuffer. +func ReleaseMulti(mb MultiBuffer) MultiBuffer { + for i := range mb { + mb[i].Release() + mb[i] = nil + } + return mb[:0] +} + +// Copy copied the beginning part of the MultiBuffer into the given byte array. +func (mb MultiBuffer) Copy(b []byte) int { + total := 0 + for _, bb := range mb { + nBytes := copy(b[total:], bb.Bytes()) + total += nBytes + if int32(nBytes) < bb.Len() { + break + } + } + return total +} + +// ReadFrom reads all content from reader until EOF. +func ReadFrom(reader io.Reader) (MultiBuffer, error) { + mb := make(MultiBuffer, 0, 16) + for { + b := New() + _, err := b.ReadFullFrom(reader, Size) + if b.IsEmpty() { + b.Release() + } else { + mb = append(mb, b) + } + if err != nil { + if errors.Cause(err) == io.EOF || errors.Cause(err) == io.ErrUnexpectedEOF { + return mb, nil + } + return mb, err + } + } +} + +// SplitBytes splits the given amount of bytes from the beginning of the MultiBuffer. +// It returns the new address of MultiBuffer leftover, and number of bytes written into the input byte slice. +func SplitBytes(mb MultiBuffer, b []byte) (MultiBuffer, int) { + totalBytes := 0 + endIndex := -1 + for i := range mb { + pBuffer := mb[i] + nBytes, _ := pBuffer.Read(b) + totalBytes += nBytes + b = b[nBytes:] + if !pBuffer.IsEmpty() { + endIndex = i + break + } + pBuffer.Release() + mb[i] = nil + } + + if endIndex == -1 { + mb = mb[:0] + } else { + mb = mb[endIndex:] + } + + return mb, totalBytes +} + +// SplitFirstBytes splits the first buffer from MultiBuffer, and then copy its content into the given slice. +func SplitFirstBytes(mb MultiBuffer, p []byte) (MultiBuffer, int) { + mb, b := SplitFirst(mb) + if b == nil { + return mb, 0 + } + n := copy(p, b.Bytes()) + b.Release() + return mb, n +} + +// Compact returns another MultiBuffer by merging all content of the given one together. +func Compact(mb MultiBuffer) MultiBuffer { + if len(mb) == 0 { + return mb + } + + mb2 := make(MultiBuffer, 0, len(mb)) + last := mb[0] + + for i := 1; i < len(mb); i++ { + curr := mb[i] + if curr.Len() > last.Available() { + mb2 = append(mb2, last) + last = curr + } else { + common.Must2(last.ReadFrom(curr)) + curr.Release() + } + } + + mb2 = append(mb2, last) + return mb2 +} + +// SplitFirst splits the first Buffer from the beginning of the MultiBuffer. +func SplitFirst(mb MultiBuffer) (MultiBuffer, *Buffer) { + if len(mb) == 0 { + return mb, nil + } + + b := mb[0] + mb[0] = nil + mb = mb[1:] + return mb, b +} + +// SplitSize splits the beginning of the MultiBuffer into another one, for at most size bytes. +func SplitSize(mb MultiBuffer, size int32) (MultiBuffer, MultiBuffer) { + if len(mb) == 0 { + return mb, nil + } + + if mb[0].Len() > size { + b := New() + copy(b.Extend(size), mb[0].BytesTo(size)) + mb[0].Advance(size) + return mb, MultiBuffer{b} + } + + totalBytes := int32(0) + var r MultiBuffer + endIndex := -1 + for i := range mb { + if totalBytes+mb[i].Len() > size { + endIndex = i + break + } + totalBytes += mb[i].Len() + r = append(r, mb[i]) + mb[i] = nil + } + if endIndex == -1 { + // To reuse mb array + mb = mb[:0] + } else { + mb = mb[endIndex:] + } + return mb, r +} + +// SplitMulti splits the beginning of the MultiBuffer into first one, the index i and after into second one +func SplitMulti(mb MultiBuffer, i int) (MultiBuffer, MultiBuffer) { + mb2 := make(MultiBuffer, 0, len(mb)) + if i < len(mb) && i >= 0 { + mb2 = append(mb2, mb[i:]...) + for j := i; j < len(mb); j++ { + mb[j] = nil + } + mb = mb[:i] + } + return mb, mb2 +} + +// WriteMultiBuffer writes all buffers from the MultiBuffer to the Writer one by one, and return error if any, with leftover MultiBuffer. +func WriteMultiBuffer(writer io.Writer, mb MultiBuffer) (MultiBuffer, error) { + for { + mb2, b := SplitFirst(mb) + mb = mb2 + if b == nil { + break + } + + _, err := writer.Write(b.Bytes()) + b.Release() + if err != nil { + return mb, err + } + } + + return nil, nil +} + +// Len returns the total number of bytes in the MultiBuffer. +func (mb MultiBuffer) Len() int32 { + if mb == nil { + return 0 + } + + size := int32(0) + for _, b := range mb { + size += b.Len() + } + return size +} + +// IsEmpty returns true if the MultiBuffer has no content. +func (mb MultiBuffer) IsEmpty() bool { + for _, b := range mb { + if !b.IsEmpty() { + return false + } + } + return true +} + +// String returns the content of the MultiBuffer in string. +func (mb MultiBuffer) String() string { + v := make([]interface{}, len(mb)) + for i, b := range mb { + v[i] = b + } + return serial.Concat(v...) +} + +// MultiBufferContainer is a ReadWriteCloser wrapper over MultiBuffer. +type MultiBufferContainer struct { + MultiBuffer +} + +// Read implements io.Reader. +func (c *MultiBufferContainer) Read(b []byte) (int, error) { + if c.MultiBuffer.IsEmpty() { + return 0, io.EOF + } + + mb, nBytes := SplitBytes(c.MultiBuffer, b) + c.MultiBuffer = mb + return nBytes, nil +} + +// ReadMultiBuffer implements Reader. +func (c *MultiBufferContainer) ReadMultiBuffer() (MultiBuffer, error) { + mb := c.MultiBuffer + c.MultiBuffer = nil + return mb, nil +} + +// Write implements io.Writer. +func (c *MultiBufferContainer) Write(b []byte) (int, error) { + c.MultiBuffer = MergeBytes(c.MultiBuffer, b) + return len(b), nil +} + +// WriteMultiBuffer implements Writer. +func (c *MultiBufferContainer) WriteMultiBuffer(b MultiBuffer) error { + mb, _ := MergeMulti(c.MultiBuffer, b) + c.MultiBuffer = mb + return nil +} + +// Close implements io.Closer. +func (c *MultiBufferContainer) Close() error { + c.MultiBuffer = ReleaseMulti(c.MultiBuffer) + return nil +} diff --git a/common/xray/buf/override.go b/common/xray/buf/override.go new file mode 100644 index 0000000000..2032854f6f --- /dev/null +++ b/common/xray/buf/override.go @@ -0,0 +1,38 @@ +package buf + +import ( + "github.com/sagernet/sing-box/common/xray/net" +) + +type EndpointOverrideReader struct { + Reader + Dest net.Address + OriginalDest net.Address +} + +func (r *EndpointOverrideReader) ReadMultiBuffer() (MultiBuffer, error) { + mb, err := r.Reader.ReadMultiBuffer() + if err == nil { + for _, b := range mb { + if b.UDP != nil && b.UDP.Address == r.OriginalDest { + b.UDP.Address = r.Dest + } + } + } + return mb, err +} + +type EndpointOverrideWriter struct { + Writer + Dest net.Address + OriginalDest net.Address +} + +func (w *EndpointOverrideWriter) WriteMultiBuffer(mb MultiBuffer) error { + for _, b := range mb { + if b.UDP != nil && b.UDP.Address == w.Dest { + b.UDP.Address = w.OriginalDest + } + } + return w.Writer.WriteMultiBuffer(mb) +} diff --git a/common/xray/buf/reader.go b/common/xray/buf/reader.go new file mode 100644 index 0000000000..17b2e49191 --- /dev/null +++ b/common/xray/buf/reader.go @@ -0,0 +1,175 @@ +package buf + +import ( + "io" + + "github.com/sagernet/sing-box/common/xray" + "github.com/sagernet/sing-box/common/xray/errors" + E "github.com/sagernet/sing/common/exceptions" +) + +func readOneUDP(r io.Reader) (*Buffer, error) { + b := New() + for i := 0; i < 64; i++ { + _, err := b.ReadFrom(r) + if !b.IsEmpty() { + return b, nil + } + if err != nil { + b.Release() + return nil, err + } + } + + b.Release() + return nil, E.New("Reader returns too many empty payloads.") +} + +// ReadBuffer reads a Buffer from the given reader. +func ReadBuffer(r io.Reader) (*Buffer, error) { + b := New() + n, err := b.ReadFrom(r) + if n > 0 { + return b, err + } + b.Release() + return nil, err +} + +// BufferedReader is a Reader that keeps its internal buffer. +type BufferedReader struct { + // Reader is the underlying reader to be read from + Reader Reader + // Buffer is the internal buffer to be read from first + Buffer MultiBuffer + // Splitter is a function to read bytes from MultiBuffer + Splitter func(MultiBuffer, []byte) (MultiBuffer, int) +} + +// BufferedBytes returns the number of bytes that is cached in this reader. +func (r *BufferedReader) BufferedBytes() int32 { + return r.Buffer.Len() +} + +// ReadByte implements io.ByteReader. +func (r *BufferedReader) ReadByte() (byte, error) { + var b [1]byte + _, err := r.Read(b[:]) + return b[0], err +} + +// Read implements io.Reader. It reads from internal buffer first (if available) and then reads from the underlying reader. +func (r *BufferedReader) Read(b []byte) (int, error) { + spliter := r.Splitter + if spliter == nil { + spliter = SplitBytes + } + + if !r.Buffer.IsEmpty() { + buffer, nBytes := spliter(r.Buffer, b) + r.Buffer = buffer + if r.Buffer.IsEmpty() { + r.Buffer = nil + } + return nBytes, nil + } + + mb, err := r.Reader.ReadMultiBuffer() + if err != nil { + return 0, err + } + + mb, nBytes := spliter(mb, b) + if !mb.IsEmpty() { + r.Buffer = mb + } + return nBytes, nil +} + +// ReadMultiBuffer implements Reader. +func (r *BufferedReader) ReadMultiBuffer() (MultiBuffer, error) { + if !r.Buffer.IsEmpty() { + mb := r.Buffer + r.Buffer = nil + return mb, nil + } + + return r.Reader.ReadMultiBuffer() +} + +// ReadAtMost returns a MultiBuffer with at most size. +func (r *BufferedReader) ReadAtMost(size int32) (MultiBuffer, error) { + if r.Buffer.IsEmpty() { + mb, err := r.Reader.ReadMultiBuffer() + if mb.IsEmpty() && err != nil { + return nil, err + } + r.Buffer = mb + } + + rb, mb := SplitSize(r.Buffer, size) + r.Buffer = rb + if r.Buffer.IsEmpty() { + r.Buffer = nil + } + return mb, nil +} + +func (r *BufferedReader) writeToInternal(writer io.Writer) (int64, error) { + mbWriter := NewWriter(writer) + var sc SizeCounter + if r.Buffer != nil { + sc.Size = int64(r.Buffer.Len()) + if err := mbWriter.WriteMultiBuffer(r.Buffer); err != nil { + return 0, err + } + r.Buffer = nil + } + + err := Copy(r.Reader, mbWriter, CountSize(&sc)) + return sc.Size, err +} + +// WriteTo implements io.WriterTo. +func (r *BufferedReader) WriteTo(writer io.Writer) (int64, error) { + nBytes, err := r.writeToInternal(writer) + if errors.Cause(err) == io.EOF { + return nBytes, nil + } + return nBytes, err +} + +// Interrupt implements common.Interruptible. +func (r *BufferedReader) Interrupt() { + common.Interrupt(r.Reader) +} + +// Close implements io.Closer. +func (r *BufferedReader) Close() error { + return common.Close(r.Reader) +} + +// SingleReader is a Reader that read one Buffer every time. +type SingleReader struct { + io.Reader +} + +// ReadMultiBuffer implements Reader. +func (r *SingleReader) ReadMultiBuffer() (MultiBuffer, error) { + b, err := ReadBuffer(r.Reader) + return MultiBuffer{b}, err +} + +// PacketReader is a Reader that read one Buffer every time. +type PacketReader struct { + io.Reader +} + +// ReadMultiBuffer implements Reader. +func (r *PacketReader) ReadMultiBuffer() (MultiBuffer, error) { + b, err := readOneUDP(r.Reader) + if err != nil { + return nil, err + } + return MultiBuffer{b}, nil +} diff --git a/common/xray/buf/writer.go b/common/xray/buf/writer.go new file mode 100644 index 0000000000..c76316c576 --- /dev/null +++ b/common/xray/buf/writer.go @@ -0,0 +1,284 @@ +package buf + +import ( + "io" + "net" + "sync" + + "github.com/sagernet/sing-box/common/xray" + "github.com/sagernet/sing-box/common/xray/errors" + "github.com/sagernet/sing-box/common/xray/stats" +) + +// BufferToBytesWriter is a Writer that writes alloc.Buffer into underlying writer. +type BufferToBytesWriter struct { + io.Writer + + counter stats.Counter + cache [][]byte +} + +// WriteMultiBuffer implements Writer. This method takes ownership of the given buffer. +func (w *BufferToBytesWriter) WriteMultiBuffer(mb MultiBuffer) error { + defer ReleaseMulti(mb) + + size := mb.Len() + if size == 0 { + return nil + } + + if len(mb) == 1 { + return WriteAllBytes(w.Writer, mb[0].Bytes(), w.counter) + } + + if cap(w.cache) < len(mb) { + w.cache = make([][]byte, 0, len(mb)) + } + + bs := w.cache + for _, b := range mb { + bs = append(bs, b.Bytes()) + } + + defer func() { + for idx := range bs { + bs[idx] = nil + } + }() + + nb := net.Buffers(bs) + wc := int64(0) + defer func() { + if w.counter != nil { + w.counter.Add(wc) + } + }() + for size > 0 { + n, err := nb.WriteTo(w.Writer) + wc += n + if err != nil { + return err + } + size -= int32(n) + } + + return nil +} + +// ReadFrom implements io.ReaderFrom. +func (w *BufferToBytesWriter) ReadFrom(reader io.Reader) (int64, error) { + var sc SizeCounter + err := Copy(NewReader(reader), w, CountSize(&sc)) + return sc.Size, err +} + +// BufferedWriter is a Writer with internal buffer. +type BufferedWriter struct { + sync.Mutex + writer Writer + buffer *Buffer + buffered bool + flushNext bool +} + +// NewBufferedWriter creates a new BufferedWriter. +func NewBufferedWriter(writer Writer) *BufferedWriter { + return &BufferedWriter{ + writer: writer, + buffer: New(), + buffered: true, + } +} + +// WriteByte implements io.ByteWriter. +func (w *BufferedWriter) WriteByte(c byte) error { + return common.Error2(w.Write([]byte{c})) +} + +// Write implements io.Writer. +func (w *BufferedWriter) Write(b []byte) (int, error) { + if len(b) == 0 { + return 0, nil + } + + w.Lock() + defer w.Unlock() + + if !w.buffered { + if writer, ok := w.writer.(io.Writer); ok { + return writer.Write(b) + } + } + + totalBytes := 0 + for len(b) > 0 { + if w.buffer == nil { + w.buffer = New() + } + + nBytes, err := w.buffer.Write(b) + totalBytes += nBytes + if err != nil { + return totalBytes, err + } + if !w.buffered || w.buffer.IsFull() { + if err := w.flushInternal(); err != nil { + return totalBytes, err + } + } + b = b[nBytes:] + } + + return totalBytes, nil +} + +// WriteMultiBuffer implements Writer. It takes ownership of the given MultiBuffer. +func (w *BufferedWriter) WriteMultiBuffer(b MultiBuffer) error { + if b.IsEmpty() { + return nil + } + + w.Lock() + defer w.Unlock() + + if !w.buffered { + return w.writer.WriteMultiBuffer(b) + } + + reader := MultiBufferContainer{ + MultiBuffer: b, + } + defer reader.Close() + + for !reader.MultiBuffer.IsEmpty() { + if w.buffer == nil { + w.buffer = New() + } + common.Must2(w.buffer.ReadFrom(&reader)) + if w.buffer.IsFull() { + if err := w.flushInternal(); err != nil { + return err + } + } + } + + if w.flushNext { + w.buffered = false + w.flushNext = false + return w.flushInternal() + } + + return nil +} + +// Flush flushes buffered content into underlying writer. +func (w *BufferedWriter) Flush() error { + w.Lock() + defer w.Unlock() + + return w.flushInternal() +} + +func (w *BufferedWriter) flushInternal() error { + if w.buffer.IsEmpty() { + return nil + } + + b := w.buffer + w.buffer = nil + + if writer, ok := w.writer.(io.Writer); ok { + err := WriteAllBytes(writer, b.Bytes(), nil) + b.Release() + return err + } + + return w.writer.WriteMultiBuffer(MultiBuffer{b}) +} + +// SetBuffered sets whether the internal buffer is used. If set to false, Flush() will be called to clear the buffer. +func (w *BufferedWriter) SetBuffered(f bool) error { + w.Lock() + defer w.Unlock() + + w.buffered = f + if !f { + return w.flushInternal() + } + return nil +} + +// SetFlushNext will wait the next WriteMultiBuffer to flush and set buffered = false +func (w *BufferedWriter) SetFlushNext() { + w.Lock() + defer w.Unlock() + w.flushNext = true +} + +// ReadFrom implements io.ReaderFrom. +func (w *BufferedWriter) ReadFrom(reader io.Reader) (int64, error) { + if err := w.SetBuffered(false); err != nil { + return 0, err + } + + var sc SizeCounter + err := Copy(NewReader(reader), w, CountSize(&sc)) + return sc.Size, err +} + +// Close implements io.Closable. +func (w *BufferedWriter) Close() error { + if err := w.Flush(); err != nil { + return err + } + return common.Close(w.writer) +} + +// SequentialWriter is a Writer that writes MultiBuffer sequentially into the underlying io.Writer. +type SequentialWriter struct { + io.Writer +} + +// WriteMultiBuffer implements Writer. +func (w *SequentialWriter) WriteMultiBuffer(mb MultiBuffer) error { + mb, err := WriteMultiBuffer(w.Writer, mb) + ReleaseMulti(mb) + return err +} + +type noOpWriter byte + +func (noOpWriter) WriteMultiBuffer(b MultiBuffer) error { + ReleaseMulti(b) + return nil +} + +func (noOpWriter) Write(b []byte) (int, error) { + return len(b), nil +} + +func (noOpWriter) ReadFrom(reader io.Reader) (int64, error) { + b := New() + defer b.Release() + + totalBytes := int64(0) + for { + b.Clear() + _, err := b.ReadFrom(reader) + totalBytes += int64(b.Len()) + if err != nil { + if errors.Cause(err) == io.EOF { + return totalBytes, nil + } + return totalBytes, err + } + } +} + +var ( + // Discard is a Writer that swallows all contents written in. + Discard Writer = noOpWriter(0) + + // DiscardBytes is an io.Writer that swallows all contents written in. + DiscardBytes io.Writer = noOpWriter(0) +) diff --git a/common/xray/bytespool/pool.go b/common/xray/bytespool/pool.go new file mode 100644 index 0000000000..6f632d5241 --- /dev/null +++ b/common/xray/bytespool/pool.go @@ -0,0 +1,72 @@ +package bytespool + +import "sync" + +func createAllocFunc(size int32) func() interface{} { + return func() interface{} { + return make([]byte, size) + } +} + +// The following parameters controls the size of buffer pools. +// There are numPools pools. Starting from 2k size, the size of each pool is sizeMulti of the previous one. +// Package buf is guaranteed to not use buffers larger than the largest pool. +// Other packets may use larger buffers. +const ( + numPools = 4 + sizeMulti = 4 +) + +var ( + pool [numPools]sync.Pool + poolSize [numPools]int32 +) + +func init() { + size := int32(2048) + for i := 0; i < numPools; i++ { + pool[i] = sync.Pool{ + New: createAllocFunc(size), + } + poolSize[i] = size + size *= sizeMulti + } +} + +// GetPool returns a sync.Pool that generates bytes array with at least the given size. +// It may return nil if no such pool exists. +// +// xray:api:stable +func GetPool(size int32) *sync.Pool { + for idx, ps := range poolSize { + if size <= ps { + return &pool[idx] + } + } + return nil +} + +// Alloc returns a byte slice with at least the given size. Minimum size of returned slice is 2048. +// +// xray:api:stable +func Alloc(size int32) []byte { + pool := GetPool(size) + if pool != nil { + return pool.Get().([]byte) + } + return make([]byte, size) +} + +// Free puts a byte slice into the internal pool. +// +// xray:api:stable +func Free(b []byte) { + size := int32(cap(b)) + b = b[0:cap(b)] + for i := numPools - 1; i >= 0; i-- { + if size >= poolSize[i] { + pool[i].Put(b) + return + } + } +} diff --git a/common/xray/common.go b/common/xray/common.go new file mode 100644 index 0000000000..266f57ef92 --- /dev/null +++ b/common/xray/common.go @@ -0,0 +1,32 @@ +package common + +import "reflect" + +// Must panics if err is not nil. +func Must(err error) { + if err != nil { + panic(err) + } +} + +// Must2 panics if the second parameter is not nil, otherwise returns the first parameter. +func Must2(v interface{}, err error) interface{} { + Must(err) + return v +} + +// Error2 returns the err from the 2nd parameter. +func Error2(v interface{}, err error) error { + return err +} + +// CloseIfExists call obj.Close() if obj is not nil. +func CloseIfExists(obj any) error { + if obj != nil { + v := reflect.ValueOf(obj) + if !v.IsNil() { + return Close(obj) + } + } + return nil +} diff --git a/common/xray/cpuid/cpuid.go b/common/xray/cpuid/cpuid.go new file mode 100644 index 0000000000..60938fe3f5 --- /dev/null +++ b/common/xray/cpuid/cpuid.go @@ -0,0 +1,18 @@ +package cpuid + +import ( + "runtime" + + "golang.org/x/sys/cpu" +) + +var ( + // Keep in sync with crypto/tls/cipher_suites.go. + hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ + hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL + hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasGHASH + hasGCMAsmPPC64 = runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" + + // HasAESGCM indicates whether the CPU has AES-GCM hardware acceleration. + HasAESGCM = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X || hasGCMAsmPPC64 +) diff --git a/common/xray/crypto/crypto.go b/common/xray/crypto/crypto.go new file mode 100644 index 0000000000..4356d41255 --- /dev/null +++ b/common/xray/crypto/crypto.go @@ -0,0 +1,17 @@ +package crypto + +import ( + "crypto/rand" + "math/big" +) + +func RandBetween(from int64, to int64) int64 { + if from == to { + return from + } + if from > to { + from, to = to, from + } + bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from)) + return from + bigInt.Int64() +} diff --git a/common/xray/errors/errors.go b/common/xray/errors/errors.go new file mode 100644 index 0000000000..54af7cbe1d --- /dev/null +++ b/common/xray/errors/errors.go @@ -0,0 +1,25 @@ +package errors + +type hasInnerError interface { + // Unwrap returns the underlying error of this one. + Unwrap() error +} + +func Cause(err error) error { + if err == nil { + return nil + } +L: + for { + switch inner := err.(type) { + case hasInnerError: + if inner.Unwrap() == nil { + break L + } + err = inner.Unwrap() + default: + break L + } + } + return err +} diff --git a/common/xray/interfaces.go b/common/xray/interfaces.go new file mode 100644 index 0000000000..428c3388f7 --- /dev/null +++ b/common/xray/interfaces.go @@ -0,0 +1,52 @@ +package common + +// Closable is the interface for objects that can release its resources. +// +// xray:api:beta +type Closable interface { + // Close release all resources used by this object, including goroutines. + Close() error +} + +// Interruptible is an interface for objects that can be stopped before its completion. +// +// xray:api:beta +type Interruptible interface { + Interrupt() +} + +// Close closes the obj if it is a Closable. +// +// xray:api:beta +func Close(obj interface{}) error { + if c, ok := obj.(Closable); ok { + return c.Close() + } + return nil +} + +// Interrupt calls Interrupt() if object implements Interruptible interface, or Close() if the object implements Closable interface. +// +// xray:api:beta +func Interrupt(obj interface{}) error { + if c, ok := obj.(Interruptible); ok { + c.Interrupt() + return nil + } + return Close(obj) +} + +// Runnable is the interface for objects that can start to work and stop on demand. +type Runnable interface { + // Start starts the runnable object. Upon the method returning nil, the object begins to function properly. + Start() error + + Closable +} + +// HasType is the interface for objects that knows its type. +type HasType interface { + // Type returns the type of the object. + // Usually it returns (*Type)(nil) of the object. + Type() interface{} +} diff --git a/common/xray/json/badoption/range.go b/common/xray/json/badoption/range.go new file mode 100644 index 0000000000..056a8106a1 --- /dev/null +++ b/common/xray/json/badoption/range.go @@ -0,0 +1,76 @@ +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 == 0 && c.To == 0 { + return json.Marshal("") + } + 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 + + if err := json.Unmarshal(content, &stringValue); err == nil { + + parts := strings.Split(stringValue, "-") + if stringValue == "" { + rangeValue.From, rangeValue.To = 0, 0 + } else 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 intValue int + if err := json.Unmarshal(content, &intValue); err == nil { + rangeValue.From, rangeValue.To = int32(intValue), int32(intValue) + } else if err := json.Unmarshal(content, &rangeValue); 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) Rand() int32 { + return int32(crypto.RandBetween(int64(c.From), int64(c.To))) +} diff --git a/common/xray/net/address.go b/common/xray/net/address.go new file mode 100644 index 0000000000..7a99c8adc9 --- /dev/null +++ b/common/xray/net/address.go @@ -0,0 +1,181 @@ +package net + +import ( + "bytes" + "net" + "strings" +) + +var ( + // LocalHostIP is a constant value for localhost IP in IPv4. + LocalHostIP = IPAddress([]byte{127, 0, 0, 1}) + + // AnyIP is a constant value for any IP in IPv4. + AnyIP = IPAddress([]byte{0, 0, 0, 0}) + + // LocalHostDomain is a constant value for localhost domain. + LocalHostDomain = DomainAddress("localhost") + + // LocalHostIPv6 is a constant value for localhost IP in IPv6. + LocalHostIPv6 = IPAddress([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}) + + // AnyIPv6 is a constant value for any IP in IPv6. + AnyIPv6 = IPAddress([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) +) + +// AddressFamily is the type of address. +type AddressFamily byte + +const ( + // AddressFamilyIPv4 represents address as IPv4 + AddressFamilyIPv4 = AddressFamily(0) + + // AddressFamilyIPv6 represents address as IPv6 + AddressFamilyIPv6 = AddressFamily(1) + + // AddressFamilyDomain represents address as Domain + AddressFamilyDomain = AddressFamily(2) +) + +// IsIPv4 returns true if current AddressFamily is IPv4. +func (af AddressFamily) IsIPv4() bool { + return af == AddressFamilyIPv4 +} + +// IsIPv6 returns true if current AddressFamily is IPv6. +func (af AddressFamily) IsIPv6() bool { + return af == AddressFamilyIPv6 +} + +// IsIP returns true if current AddressFamily is IPv6 or IPv4. +func (af AddressFamily) IsIP() bool { + return af == AddressFamilyIPv4 || af == AddressFamilyIPv6 +} + +// IsDomain returns true if current AddressFamily is Domain. +func (af AddressFamily) IsDomain() bool { + return af == AddressFamilyDomain +} + +// Address represents a network address to be communicated with. It may be an IP address or domain +// address, not both. This interface doesn't resolve IP address for a given domain. +type Address interface { + IP() net.IP // IP of this Address + Domain() string // Domain of this Address + Family() AddressFamily + + String() string // String representation of this Address +} + +func isAlphaNum(c byte) bool { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') +} + +// ParseAddress parses a string into an Address. The return value will be an IPAddress when +// the string is in the form of IPv4 or IPv6 address, or a DomainAddress otherwise. +func ParseAddress(addr string) Address { + // Handle IPv6 address in form as "[2001:4860:0:2001::68]" + lenAddr := len(addr) + if lenAddr > 0 && addr[0] == '[' && addr[lenAddr-1] == ']' { + addr = addr[1 : lenAddr-1] + lenAddr -= 2 + } + + if lenAddr > 0 && (!isAlphaNum(addr[0]) || !isAlphaNum(addr[len(addr)-1])) { + addr = strings.TrimSpace(addr) + } + + ip := net.ParseIP(addr) + if ip != nil { + return IPAddress(ip) + } + return DomainAddress(addr) +} + +var bytes0 = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + +// IPAddress creates an Address with given IP. +func IPAddress(ip []byte) Address { + switch len(ip) { + case net.IPv4len: + var addr ipv4Address = [4]byte{ip[0], ip[1], ip[2], ip[3]} + return addr + case net.IPv6len: + if bytes.Equal(ip[:10], bytes0) && ip[10] == 0xff && ip[11] == 0xff { + return IPAddress(ip[12:16]) + } + var addr ipv6Address = [16]byte{ + ip[0], ip[1], ip[2], ip[3], + ip[4], ip[5], ip[6], ip[7], + ip[8], ip[9], ip[10], ip[11], + ip[12], ip[13], ip[14], ip[15], + } + return addr + default: + return nil + } +} + +// DomainAddress creates an Address with given domain. +// This is an internal function that forcibly converts a string to domain. +// It's mainly used in test files and mux. +// Unless you have a specific reason, use net.ParseAddress instead, +// as this function does not check whether the input is an IP address. +// Otherwise, you will get strange results like domain: 1.1.1.1 +func DomainAddress(domain string) Address { + return domainAddress(domain) +} + +type ipv4Address [4]byte + +func (a ipv4Address) IP() net.IP { + return net.IP(a[:]) +} + +func (ipv4Address) Domain() string { + panic("Calling Domain() on an IPv4Address.") +} + +func (ipv4Address) Family() AddressFamily { + return AddressFamilyIPv4 +} + +func (a ipv4Address) String() string { + return a.IP().String() +} + +type ipv6Address [16]byte + +func (a ipv6Address) IP() net.IP { + return net.IP(a[:]) +} + +func (ipv6Address) Domain() string { + panic("Calling Domain() on an IPv6Address.") +} + +func (ipv6Address) Family() AddressFamily { + return AddressFamilyIPv6 +} + +func (a ipv6Address) String() string { + return "[" + a.IP().String() + "]" +} + +type domainAddress string + +func (domainAddress) IP() net.IP { + panic("Calling IP() on a DomainAddress.") +} + +func (a domainAddress) Domain() string { + return string(a) +} + +func (domainAddress) Family() AddressFamily { + return AddressFamilyDomain +} + +func (a domainAddress) String() string { + return a.Domain() +} diff --git a/common/xray/net/destination.go b/common/xray/net/destination.go new file mode 100644 index 0000000000..561c7726ba --- /dev/null +++ b/common/xray/net/destination.go @@ -0,0 +1,146 @@ +package net + +import ( + "net" + "strings" +) + +// Destination represents a network destination including address and protocol (tcp / udp). +type Destination struct { + Address Address + Port Port + Network Network +} + +// DestinationFromAddr generates a Destination from a net address. +func DestinationFromAddr(addr net.Addr) Destination { + switch addr := addr.(type) { + case *net.TCPAddr: + return TCPDestination(IPAddress(addr.IP), Port(addr.Port)) + case *net.UDPAddr: + return UDPDestination(IPAddress(addr.IP), Port(addr.Port)) + case *net.UnixAddr: + return UnixDestination(DomainAddress(addr.Name)) + default: + panic("Net: Unknown address type.") + } +} + +// ParseDestination converts a destination from its string presentation. +func ParseDestination(dest string) (Destination, error) { + d := Destination{ + Address: AnyIP, + Port: Port(0), + } + if strings.HasPrefix(dest, "tcp:") { + d.Network = Network_TCP + dest = dest[4:] + } else if strings.HasPrefix(dest, "udp:") { + d.Network = Network_UDP + dest = dest[4:] + } else if strings.HasPrefix(dest, "unix:") { + d = UnixDestination(DomainAddress(dest[5:])) + return d, nil + } + + hstr, pstr, err := SplitHostPort(dest) + if err != nil { + return d, err + } + if len(hstr) > 0 { + d.Address = ParseAddress(hstr) + } + if len(pstr) > 0 { + port, err := PortFromString(pstr) + if err != nil { + return d, err + } + d.Port = port + } + return d, nil +} + +// TCPDestination creates a TCP destination with given address +func TCPDestination(address Address, port Port) Destination { + return Destination{ + Network: Network_TCP, + Address: address, + Port: port, + } +} + +// UDPDestination creates a UDP destination with given address +func UDPDestination(address Address, port Port) Destination { + return Destination{ + Network: Network_UDP, + Address: address, + Port: port, + } +} + +// UnixDestination creates a Unix destination with given address +func UnixDestination(address Address) Destination { + return Destination{ + Network: Network_UNIX, + Address: address, + } +} + +// NetAddr returns the network address in this Destination in string form. +func (d Destination) NetAddr() string { + addr := "" + if d.Network == Network_TCP || d.Network == Network_UDP { + addr = d.Address.String() + ":" + d.Port.String() + } else if d.Network == Network_UNIX { + addr = d.Address.String() + } + return addr +} + +// RawNetAddr converts a net.Addr from its Destination presentation. +func (d Destination) RawNetAddr() net.Addr { + var addr net.Addr + switch d.Network { + case Network_TCP: + if d.Address.Family().IsIP() { + addr = &net.TCPAddr{ + IP: d.Address.IP(), + Port: int(d.Port), + } + } + case Network_UDP: + if d.Address.Family().IsIP() { + addr = &net.UDPAddr{ + IP: d.Address.IP(), + Port: int(d.Port), + } + } + case Network_UNIX: + if d.Address.Family().IsDomain() { + addr = &net.UnixAddr{ + Name: d.Address.String(), + Net: d.Network.SystemString(), + } + } + } + return addr +} + +// String returns the strings form of this Destination. +func (d Destination) String() string { + prefix := "unknown:" + switch d.Network { + case Network_TCP: + prefix = "tcp:" + case Network_UDP: + prefix = "udp:" + case Network_UNIX: + prefix = "unix:" + } + return prefix + d.NetAddr() +} + +// IsValid returns true if this Destination is valid. +func (d Destination) IsValid() bool { + return d.Network != Network_Unknown +} diff --git a/common/xray/net/net.go b/common/xray/net/net.go new file mode 100644 index 0000000000..9bc049d98a --- /dev/null +++ b/common/xray/net/net.go @@ -0,0 +1,13 @@ +package net + +import "time" + +// defines the maximum time an idle TCP session can survive in the tunnel, so +// it should be consistent across HTTP versions and with other transports. +const ConnIdleTimeout = 300 * time.Second + +// consistent with quic-go +const QuicgoH3KeepAlivePeriod = 10 * time.Second + +// consistent with chrome +const ChromeH2KeepAlivePeriod = 45 * time.Second diff --git a/common/xray/net/network.go b/common/xray/net/network.go new file mode 100644 index 0000000000..356d7fdab8 --- /dev/null +++ b/common/xray/net/network.go @@ -0,0 +1,33 @@ +package net + +type Network int32 + +const ( + Network_Unknown Network = 0 + Network_TCP Network = 2 + Network_UDP Network = 3 + Network_UNIX Network = 4 +) + +func (n Network) SystemString() string { + switch n { + case Network_TCP: + return "tcp" + case Network_UDP: + return "udp" + case Network_UNIX: + return "unix" + default: + return "unknown" + } +} + +// HasNetwork returns true if the network list has a certain network. +func HasNetwork(list []Network, network Network) bool { + for _, value := range list { + if value == network { + return true + } + } + return false +} diff --git a/common/xray/net/port.go b/common/xray/net/port.go new file mode 100644 index 0000000000..6d42c40b6f --- /dev/null +++ b/common/xray/net/port.go @@ -0,0 +1,55 @@ +package net + +import ( + "encoding/binary" + "strconv" + + E "github.com/sagernet/sing/common/exceptions" +) + +// Port represents a network port in TCP and UDP protocol. +type Port uint16 + +// PortFromBytes converts a byte array to a Port, assuming bytes are in big endian order. +// @unsafe Caller must ensure that the byte array has at least 2 elements. +func PortFromBytes(port []byte) Port { + return Port(binary.BigEndian.Uint16(port)) +} + +// PortFromInt converts an integer to a Port. +// @error when the integer is not positive or larger then 65535 +func PortFromInt(val uint32) (Port, error) { + if val > 65535 { + return Port(0), E.New("invalid port range: ", val) + } + return Port(val), nil +} + +// PortFromString converts a string to a Port. +// @error when the string is not an integer or the integral value is a not a valid Port. +func PortFromString(s string) (Port, error) { + val, err := strconv.ParseUint(s, 10, 32) + if err != nil { + return Port(0), E.New("invalid port range: ", s) + } + return PortFromInt(uint32(val)) +} + +// Value return the corresponding uint16 value of a Port. +func (p Port) Value() uint16 { + return uint16(p) +} + +// String returns the string presentation of a Port. +func (p Port) String() string { + return strconv.Itoa(int(p)) +} + +type MemoryPortRange struct { + From Port + To Port +} + +func (r MemoryPortRange) Contains(port Port) bool { + return r.From <= port && port <= r.To +} diff --git a/common/xray/net/system.go b/common/xray/net/system.go new file mode 100644 index 0000000000..7e1c4b01a3 --- /dev/null +++ b/common/xray/net/system.go @@ -0,0 +1,84 @@ +package net + +import "net" + +// DialTCP is an alias of net.DialTCP. +var ( + DialTCP = net.DialTCP + DialUDP = net.DialUDP + DialUnix = net.DialUnix + Dial = net.Dial +) + +type ListenConfig = net.ListenConfig + +var ( + Listen = net.Listen + ListenTCP = net.ListenTCP + ListenUDP = net.ListenUDP + ListenUnix = net.ListenUnix +) + +var LookupIP = net.LookupIP + +var FileConn = net.FileConn + +// ParseIP is an alias of net.ParseIP +var ParseIP = net.ParseIP + +var SplitHostPort = net.SplitHostPort + +var CIDRMask = net.CIDRMask + +type ( + Addr = net.Addr + Conn = net.Conn + PacketConn = net.PacketConn +) + +type ( + TCPAddr = net.TCPAddr + TCPConn = net.TCPConn +) + +type ( + UDPAddr = net.UDPAddr + UDPConn = net.UDPConn +) + +type ( + UnixAddr = net.UnixAddr + UnixConn = net.UnixConn +) + +// IP is an alias for net.IP. +type ( + IP = net.IP + IPMask = net.IPMask + IPNet = net.IPNet +) + +const ( + IPv4len = net.IPv4len + IPv6len = net.IPv6len +) + +type ( + Error = net.Error + AddrError = net.AddrError +) + +type ( + Dialer = net.Dialer + Listener = net.Listener + TCPListener = net.TCPListener + UnixListener = net.UnixListener +) + +var ( + ResolveTCPAddr = net.ResolveTCPAddr + ResolveUDPAddr = net.ResolveUDPAddr + ResolveUnixAddr = net.ResolveUnixAddr +) + +type Resolver = net.Resolver diff --git a/common/xray/pipe/impl.go b/common/xray/pipe/impl.go new file mode 100644 index 0000000000..7b2a97a80b --- /dev/null +++ b/common/xray/pipe/impl.go @@ -0,0 +1,209 @@ +package pipe + +import ( + "errors" + "io" + "sync" + "time" + + "github.com/sagernet/sing-box/common/xray" + "github.com/sagernet/sing-box/common/xray/buf" + "github.com/sagernet/sing-box/common/xray/signal" + "github.com/sagernet/sing-box/common/xray/signal/done" +) + +type state byte + +const ( + open state = iota + closed + errord +) + +type pipeOption struct { + limit int32 // maximum buffer size in bytes + discardOverflow bool +} + +func (o *pipeOption) isFull(curSize int32) bool { + return o.limit >= 0 && curSize > o.limit +} + +type pipe struct { + sync.Mutex + data buf.MultiBuffer + readSignal *signal.Notifier + writeSignal *signal.Notifier + done *done.Instance + errChan chan error + option pipeOption + state state +} + +var ( + errBufferFull = errors.New("buffer full") + errSlowDown = errors.New("slow down") +) + +func (p *pipe) Len() int32 { + data := p.data + if data == nil { + return 0 + } + return data.Len() +} + +func (p *pipe) getState(forRead bool) error { + switch p.state { + case open: + if !forRead && p.option.isFull(p.data.Len()) { + return errBufferFull + } + return nil + case closed: + if !forRead { + return io.ErrClosedPipe + } + if !p.data.IsEmpty() { + return nil + } + return io.EOF + case errord: + return io.ErrClosedPipe + default: + panic("impossible case") + } +} + +func (p *pipe) readMultiBufferInternal() (buf.MultiBuffer, error) { + p.Lock() + defer p.Unlock() + + if err := p.getState(true); err != nil { + return nil, err + } + + data := p.data + p.data = nil + return data, nil +} + +func (p *pipe) ReadMultiBuffer() (buf.MultiBuffer, error) { + for { + data, err := p.readMultiBufferInternal() + if data != nil || err != nil { + p.writeSignal.Signal() + return data, err + } + + select { + case <-p.readSignal.Wait(): + case <-p.done.Wait(): + case err = <-p.errChan: + return nil, err + } + } +} + +func (p *pipe) ReadMultiBufferTimeout(d time.Duration) (buf.MultiBuffer, error) { + timer := time.NewTimer(d) + defer timer.Stop() + + for { + data, err := p.readMultiBufferInternal() + if data != nil || err != nil { + p.writeSignal.Signal() + return data, err + } + + select { + case <-p.readSignal.Wait(): + case <-p.done.Wait(): + case <-timer.C: + return nil, buf.ErrReadTimeout + } + } +} + +func (p *pipe) writeMultiBufferInternal(mb buf.MultiBuffer) error { + p.Lock() + defer p.Unlock() + + if err := p.getState(false); err != nil { + return err + } + + if p.data == nil { + p.data = mb + } else { + p.data, _ = buf.MergeMulti(p.data, mb) + } + return nil +} + +func (p *pipe) WriteMultiBuffer(mb buf.MultiBuffer) error { + if mb.IsEmpty() { + return nil + } + + for { + err := p.writeMultiBufferInternal(mb) + if err == nil { + p.readSignal.Signal() + return nil + } + + if err == errBufferFull { + if p.option.discardOverflow { + buf.ReleaseMulti(mb) + return nil + } + select { + case <-p.writeSignal.Wait(): + continue + case <-p.done.Wait(): + buf.ReleaseMulti(mb) + return io.ErrClosedPipe + } + } + + buf.ReleaseMulti(mb) + p.readSignal.Signal() + return err + } +} + +func (p *pipe) Close() error { + p.Lock() + defer p.Unlock() + + if p.state == closed || p.state == errord { + return nil + } + + p.state = closed + common.Must(p.done.Close()) + return nil +} + +// Interrupt implements common.Interruptible. +func (p *pipe) Interrupt() { + p.Lock() + defer p.Unlock() + + if !p.data.IsEmpty() { + buf.ReleaseMulti(p.data) + p.data = nil + if p.state == closed { + p.state = errord + } + } + + if p.state == closed || p.state == errord { + return + } + + p.state = errord + + common.Must(p.done.Close()) +} diff --git a/common/xray/pipe/pipe.go b/common/xray/pipe/pipe.go new file mode 100644 index 0000000000..dad266788c --- /dev/null +++ b/common/xray/pipe/pipe.go @@ -0,0 +1,53 @@ +package pipe + +import ( + "github.com/sagernet/sing-box/common/xray/signal" + "github.com/sagernet/sing-box/common/xray/signal/done" +) + +// Option for creating new Pipes. +type Option func(*pipeOption) + +// WithoutSizeLimit returns an Option for Pipe to have no size limit. +func WithoutSizeLimit() Option { + return func(opt *pipeOption) { + opt.limit = -1 + } +} + +// WithSizeLimit returns an Option for Pipe to have the given size limit. +func WithSizeLimit(limit int32) Option { + return func(opt *pipeOption) { + opt.limit = limit + } +} + +// DiscardOverflow returns an Option for Pipe to discard writes if full. +func DiscardOverflow() Option { + return func(opt *pipeOption) { + opt.discardOverflow = true + } +} + +// New creates a new Reader and Writer that connects to each other. +func New(opts ...Option) (*Reader, *Writer) { + p := &pipe{ + readSignal: signal.NewNotifier(), + writeSignal: signal.NewNotifier(), + done: done.New(), + errChan: make(chan error, 1), + option: pipeOption{ + limit: -1, + }, + } + + for _, opt := range opts { + opt(&(p.option)) + } + + return &Reader{ + pipe: p, + }, &Writer{ + pipe: p, + } +} diff --git a/common/xray/pipe/reader.go b/common/xray/pipe/reader.go new file mode 100644 index 0000000000..cafe444a49 --- /dev/null +++ b/common/xray/pipe/reader.go @@ -0,0 +1,41 @@ +package pipe + +import ( + "time" + + "github.com/sagernet/sing-box/common/xray/buf" +) + +// Reader is a buf.Reader that reads content from a pipe. +type Reader struct { + pipe *pipe +} + +// ReadMultiBuffer implements buf.Reader. +func (r *Reader) ReadMultiBuffer() (buf.MultiBuffer, error) { + return r.pipe.ReadMultiBuffer() +} + +// ReadMultiBufferTimeout reads content from a pipe within the given duration, or returns buf.ErrTimeout otherwise. +func (r *Reader) ReadMultiBufferTimeout(d time.Duration) (buf.MultiBuffer, error) { + return r.pipe.ReadMultiBufferTimeout(d) +} + +// Interrupt implements common.Interruptible. +func (r *Reader) Interrupt() { + r.pipe.Interrupt() +} + +// ReturnAnError makes ReadMultiBuffer return an error, only once. +func (r *Reader) ReturnAnError(err error) { + r.pipe.errChan <- err +} + +// Recover catches an error set by ReturnAnError, if exists. +func (r *Reader) Recover() (err error) { + select { + case err = <-r.pipe.errChan: + default: + } + return +} diff --git a/common/xray/pipe/writer.go b/common/xray/pipe/writer.go new file mode 100644 index 0000000000..153126357c --- /dev/null +++ b/common/xray/pipe/writer.go @@ -0,0 +1,29 @@ +package pipe + +import ( + "github.com/sagernet/sing-box/common/xray/buf" +) + +// Writer is a buf.Writer that writes data into a pipe. +type Writer struct { + pipe *pipe +} + +// WriteMultiBuffer implements buf.Writer. +func (w *Writer) WriteMultiBuffer(mb buf.MultiBuffer) error { + return w.pipe.WriteMultiBuffer(mb) +} + +// Close implements io.Closer. After the pipe is closed, writing to the pipe will return io.ErrClosedPipe, while reading will return io.EOF. +func (w *Writer) Close() error { + return w.pipe.Close() +} + +func (w *Writer) Len() int32 { + return w.pipe.Len() +} + +// Interrupt implements common.Interruptible. +func (w *Writer) Interrupt() { + w.pipe.Interrupt() +} diff --git a/common/xray/serial/serial.go b/common/xray/serial/serial.go new file mode 100644 index 0000000000..9d8cb4caf6 --- /dev/null +++ b/common/xray/serial/serial.go @@ -0,0 +1,29 @@ +package serial + +import ( + "encoding/binary" + "io" +) + +// ReadUint16 reads first two bytes from the reader, and then converts them to an uint16 value. +func ReadUint16(reader io.Reader) (uint16, error) { + var b [2]byte + if _, err := io.ReadFull(reader, b[:]); err != nil { + return 0, err + } + return binary.BigEndian.Uint16(b[:]), nil +} + +// WriteUint16 writes an uint16 value into writer. +func WriteUint16(writer io.Writer, value uint16) (int, error) { + var b [2]byte + binary.BigEndian.PutUint16(b[:], value) + return writer.Write(b[:]) +} + +// WriteUint64 writes an uint64 value into writer. +func WriteUint64(writer io.Writer, value uint64) (int, error) { + var b [8]byte + binary.BigEndian.PutUint64(b[:], value) + return writer.Write(b[:]) +} diff --git a/common/xray/serial/string.go b/common/xray/serial/string.go new file mode 100644 index 0000000000..8fdcd7f61f --- /dev/null +++ b/common/xray/serial/string.go @@ -0,0 +1,35 @@ +package serial + +import ( + "fmt" + "strings" +) + +// ToString serializes an arbitrary value into string. +func ToString(v interface{}) string { + if v == nil { + return "" + } + + switch value := v.(type) { + case string: + return value + case *string: + return *value + case fmt.Stringer: + return value.String() + case error: + return value.Error() + default: + return fmt.Sprintf("%+v", value) + } +} + +// Concat concatenates all input into a single string. +func Concat(v ...interface{}) string { + builder := strings.Builder{} + for _, value := range v { + builder.WriteString(ToString(value)) + } + return builder.String() +} diff --git a/common/xray/signal/done/done.go b/common/xray/signal/done/done.go new file mode 100644 index 0000000000..5fe0cfedeb --- /dev/null +++ b/common/xray/signal/done/done.go @@ -0,0 +1,41 @@ +package done + +import ( + "sync" +) + +type Instance struct { + once sync.Once + c chan struct{} +} + +// New returns a new Done. +func New() *Instance { + return &Instance{ + c: make(chan struct{}), + } +} + +// Done returns true if Close() is called. +func (d *Instance) Done() bool { + select { + case <-d.c: + return true + default: + return false + } +} + +// Wait returns a channel for waiting for done. +func (d *Instance) Wait() <-chan struct{} { + return d.c +} + +// Close marks this Done 'done'. This method may be called multiple times. +// All calls after the first call have no effect. +func (d *Instance) Close() error { + d.once.Do(func() { + close(d.c) + }) + return nil +} diff --git a/common/xray/signal/notifier.go b/common/xray/signal/notifier.go new file mode 100644 index 0000000000..19836e54f9 --- /dev/null +++ b/common/xray/signal/notifier.go @@ -0,0 +1,26 @@ +package signal + +// Notifier is a utility for notifying changes. The change producer may notify changes multiple time, and the consumer may get notified asynchronously. +type Notifier struct { + c chan struct{} +} + +// NewNotifier creates a new Notifier. +func NewNotifier() *Notifier { + return &Notifier{ + c: make(chan struct{}, 1), + } +} + +// Signal signals a change, usually by producer. This method never blocks. +func (n *Notifier) Signal() { + select { + case n.c <- struct{}{}: + default: + } +} + +// Wait returns a channel for waiting for changes. The returned channel never gets closed. +func (n *Notifier) Wait() <-chan struct{} { + return n.c +} diff --git a/common/xray/signal/pubsub/pubsub.go b/common/xray/signal/pubsub/pubsub.go new file mode 100644 index 0000000000..6de453e852 --- /dev/null +++ b/common/xray/signal/pubsub/pubsub.go @@ -0,0 +1,105 @@ +package pubsub + +import ( + "errors" + "sync" + "time" + + "github.com/sagernet/sing-box/common/xray" + "github.com/sagernet/sing-box/common/xray/signal/done" + "github.com/sagernet/sing-box/common/xray/task" +) + +type Subscriber struct { + buffer chan interface{} + done *done.Instance +} + +func (s *Subscriber) push(msg interface{}) { + select { + case s.buffer <- msg: + default: + } +} + +func (s *Subscriber) Wait() <-chan interface{} { + return s.buffer +} + +func (s *Subscriber) Close() error { + return s.done.Close() +} + +func (s *Subscriber) IsClosed() bool { + return s.done.Done() +} + +type Service struct { + sync.RWMutex + subs map[string][]*Subscriber + ctask *task.Periodic +} + +func NewService() *Service { + s := &Service{ + subs: make(map[string][]*Subscriber), + } + s.ctask = &task.Periodic{ + Execute: s.Cleanup, + Interval: time.Second * 30, + } + return s +} + +// Cleanup cleans up internal caches of subscribers. +// Visible for testing only. +func (s *Service) Cleanup() error { + s.Lock() + defer s.Unlock() + + if len(s.subs) == 0 { + return errors.New("nothing to do") + } + + for name, subs := range s.subs { + newSub := make([]*Subscriber, 0, len(s.subs)) + for _, sub := range subs { + if !sub.IsClosed() { + newSub = append(newSub, sub) + } + } + if len(newSub) == 0 { + delete(s.subs, name) + } else { + s.subs[name] = newSub + } + } + + if len(s.subs) == 0 { + s.subs = make(map[string][]*Subscriber) + } + return nil +} + +func (s *Service) Subscribe(name string) *Subscriber { + sub := &Subscriber{ + buffer: make(chan interface{}, 16), + done: done.New(), + } + s.Lock() + s.subs[name] = append(s.subs[name], sub) + s.Unlock() + common.Must(s.ctask.Start()) + return sub +} + +func (s *Service) Publish(name string, message interface{}) { + s.RLock() + defer s.RUnlock() + + for _, sub := range s.subs[name] { + if !sub.IsClosed() { + sub.push(message) + } + } +} diff --git a/common/xray/signal/semaphore/semaphore.go b/common/xray/signal/semaphore/semaphore.go new file mode 100644 index 0000000000..8696b148bd --- /dev/null +++ b/common/xray/signal/semaphore/semaphore.go @@ -0,0 +1,27 @@ +package semaphore + +// Instance is an implementation of semaphore. +type Instance struct { + token chan struct{} +} + +// New create a new Semaphore with n permits. +func New(n int) *Instance { + s := &Instance{ + token: make(chan struct{}, n), + } + for i := 0; i < n; i++ { + s.token <- struct{}{} + } + return s +} + +// Wait returns a channel for acquiring a permit. +func (s *Instance) Wait() <-chan struct{} { + return s.token +} + +// Signal releases a permit into the semaphore. +func (s *Instance) Signal() { + s.token <- struct{}{} +} diff --git a/common/xray/signal/timer.go b/common/xray/signal/timer.go new file mode 100644 index 0000000000..52961bf56e --- /dev/null +++ b/common/xray/signal/timer.go @@ -0,0 +1,85 @@ +package signal + +import ( + "context" + "sync" + "sync/atomic" + "time" + + "github.com/sagernet/sing-box/common/xray" + "github.com/sagernet/sing-box/common/xray/task" +) + +type ActivityUpdater interface { + Update() +} + +type ActivityTimer struct { + mu sync.RWMutex + updated chan struct{} + checkTask *task.Periodic + onTimeout func() + consumed atomic.Bool + once sync.Once +} + +func (t *ActivityTimer) Update() { + select { + case t.updated <- struct{}{}: + default: + } +} + +func (t *ActivityTimer) check() error { + select { + case <-t.updated: + default: + t.finish() + } + return nil +} + +func (t *ActivityTimer) finish() { + t.once.Do(func() { + t.consumed.Store(true) + t.mu.Lock() + defer t.mu.Unlock() + + common.CloseIfExists(t.checkTask) + t.onTimeout() + }) +} + +func (t *ActivityTimer) SetTimeout(timeout time.Duration) { + if t.consumed.Load() { + return + } + if timeout == 0 { + t.finish() + return + } + + t.mu.Lock() + defer t.mu.Unlock() + // double check, just in case + if t.consumed.Load() { + return + } + newCheckTask := &task.Periodic{ + Interval: timeout, + Execute: t.check, + } + common.CloseIfExists(t.checkTask) + t.checkTask = newCheckTask + t.Update() + common.Must(newCheckTask.Start()) +} + +func CancelAfterInactivity(ctx context.Context, cancel context.CancelFunc, timeout time.Duration) *ActivityTimer { + timer := &ActivityTimer{ + updated: make(chan struct{}, 1), + onTimeout: cancel, + } + timer.SetTimeout(timeout) + return timer +} diff --git a/common/xray/stat/connection.go b/common/xray/stat/connection.go new file mode 100644 index 0000000000..463db62d5c --- /dev/null +++ b/common/xray/stat/connection.go @@ -0,0 +1,34 @@ +package stat + +import ( + "net" + + "github.com/sagernet/sing-box/common/xray/stats" +) + +type Connection interface { + net.Conn +} + +type CounterConnection struct { + Connection + ReadCounter stats.Counter + WriteCounter stats.Counter +} + +func (c *CounterConnection) Read(b []byte) (int, error) { + nBytes, err := c.Connection.Read(b) + if c.ReadCounter != nil { + c.ReadCounter.Add(int64(nBytes)) + } + + return nBytes, err +} + +func (c *CounterConnection) Write(b []byte) (int, error) { + nBytes, err := c.Connection.Write(b) + if c.WriteCounter != nil { + c.WriteCounter.Add(int64(nBytes)) + } + return nBytes, err +} diff --git a/common/xray/stats/stats.go b/common/xray/stats/stats.go new file mode 100644 index 0000000000..70bbd0269d --- /dev/null +++ b/common/xray/stats/stats.go @@ -0,0 +1,13 @@ +package stats + +// Counter is the interface for stats counters. +// +// xray:api:stable +type Counter interface { + // Value is the current value of the counter. + Value() int64 + // Set sets a new value to the counter, and returns the previous one. + Set(int64) int64 + // Add adds a value to the current counter value, and returns the previous value. + Add(int64) int64 +} diff --git a/common/xray/task/common.go b/common/xray/task/common.go new file mode 100644 index 0000000000..11078fb991 --- /dev/null +++ b/common/xray/task/common.go @@ -0,0 +1,10 @@ +package task + +import "github.com/sagernet/sing-box/common/xray" + +// Close returns a func() that closes v. +func Close(v interface{}) func() error { + return func() error { + return common.Close(v) + } +} diff --git a/common/xray/task/periodic.go b/common/xray/task/periodic.go new file mode 100644 index 0000000000..6abe41ae0b --- /dev/null +++ b/common/xray/task/periodic.go @@ -0,0 +1,85 @@ +package task + +import ( + "sync" + "time" +) + +// Periodic is a task that runs periodically. +type Periodic struct { + // Interval of the task being run + Interval time.Duration + // Execute is the task function + Execute func() error + + access sync.Mutex + timer *time.Timer + running bool +} + +func (t *Periodic) hasClosed() bool { + t.access.Lock() + defer t.access.Unlock() + + return !t.running +} + +func (t *Periodic) checkedExecute() error { + if t.hasClosed() { + return nil + } + + if err := t.Execute(); err != nil { + t.access.Lock() + t.running = false + t.access.Unlock() + return err + } + + t.access.Lock() + defer t.access.Unlock() + + if !t.running { + return nil + } + + t.timer = time.AfterFunc(t.Interval, func() { + t.checkedExecute() + }) + + return nil +} + +// Start implements common.Runnable. +func (t *Periodic) Start() error { + t.access.Lock() + if t.running { + t.access.Unlock() + return nil + } + t.running = true + t.access.Unlock() + + if err := t.checkedExecute(); err != nil { + t.access.Lock() + t.running = false + t.access.Unlock() + return err + } + + return nil +} + +// Close implements common.Closable. +func (t *Periodic) Close() error { + t.access.Lock() + defer t.access.Unlock() + + t.running = false + if t.timer != nil { + t.timer.Stop() + t.timer = nil + } + + return nil +} diff --git a/common/xray/task/task.go b/common/xray/task/task.go new file mode 100644 index 0000000000..a153839249 --- /dev/null +++ b/common/xray/task/task.go @@ -0,0 +1,64 @@ +package task + +import ( + "context" + + "github.com/sagernet/sing-box/common/xray/signal/semaphore" +) + +// OnSuccess executes g() after f() returns nil. +func OnSuccess(f func() error, g func() error) func() error { + return func() error { + if err := f(); err != nil { + return err + } + return g() + } +} + +// Run executes a list of tasks in parallel, returns the first error encountered or nil if all tasks pass. +func Run(ctx context.Context, tasks ...func() error) error { + n := len(tasks) + s := semaphore.New(n) + done := make(chan error, 1) + + for _, task := range tasks { + <-s.Wait() + go func(f func() error) { + err := f() + if err == nil { + s.Signal() + return + } + + select { + case done <- err: + default: + } + }(task) + } + + /* + if altctx := ctx.Value("altctx"); altctx != nil { + ctx = altctx.(context.Context) + } + */ + + for i := 0; i < n; i++ { + select { + case err := <-done: + return err + case <-ctx.Done(): + return ctx.Err() + case <-s.Wait(): + } + } + + /* + if cancel := ctx.Value("cancel"); cancel != nil { + cancel.(context.CancelFunc)() + } + */ + + return nil +} diff --git a/common/xray/utils/browser.go b/common/xray/utils/browser.go new file mode 100644 index 0000000000..91209f4bda --- /dev/null +++ b/common/xray/utils/browser.go @@ -0,0 +1,28 @@ +package utils + +import ( + "math/rand" + "strconv" + "time" + + "github.com/klauspost/cpuid/v2" +) + +func ChromeVersion() int { + // Use only CPU info as seed for PRNG + seed := int64(cpuid.CPU.Family + cpuid.CPU.Model + cpuid.CPU.PhysicalCores + cpuid.CPU.LogicalCores + cpuid.CPU.CacheLine) + rng := rand.New(rand.NewSource(seed)) + // Start from Chrome 144 released on 2026.1.13 + releaseDate := time.Date(2026, 1, 13, 0, 0, 0, 0, time.UTC) + version := 144 + now := time.Now() + // Each version has random 25-45 day interval + for releaseDate.Before(now) { + releaseDate = releaseDate.AddDate(0, 0, rng.Intn(21)+25) + version++ + } + return version - 1 +} + +// ChromeUA provides default browser User-Agent based on CPU-seeded PRNG. +var ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(ChromeVersion()) + ".0.0.0 Safari/537.36" diff --git a/common/xray/utils/padding.go b/common/xray/utils/padding.go new file mode 100644 index 0000000000..fe95ba9a13 --- /dev/null +++ b/common/xray/utils/padding.go @@ -0,0 +1,24 @@ +package utils + +import ( + "math/rand/v2" +) + +var ( + // 8 ÷ (397/62) + h2packCorrectionFactor = 1.2493702770780857 + base62TotalCharsNum = 62 + base62Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +) + +// H2Base62Pad generates a base62 padding string for HTTP/2 header +// The total len will be slightly longer than the input to match the length after h2(h3 also) header huffman encoding +func H2Base62Pad[T int32 | int64 | int](expectedLen T) string { + actualLenFloat := float64(expectedLen) * h2packCorrectionFactor + actualLen := int(actualLenFloat) + result := make([]byte, actualLen) + for i := range actualLen { + result[i] = base62Chars[rand.N(base62TotalCharsNum)] + } + return string(result) +} diff --git a/common/xray/uuid/uuid.go b/common/xray/uuid/uuid.go new file mode 100644 index 0000000000..31c7b531a4 --- /dev/null +++ b/common/xray/uuid/uuid.go @@ -0,0 +1,105 @@ +package uuid + +import ( + "bytes" + "crypto/rand" + "crypto/sha1" + "encoding/hex" + + "github.com/sagernet/sing-box/common/xray" + E "github.com/sagernet/sing/common/exceptions" +) + +var byteGroups = []int{8, 4, 4, 4, 12} + +type UUID [16]byte + +// String returns the string representation of this UUID. +func (u *UUID) String() string { + bytes := u.Bytes() + result := hex.EncodeToString(bytes[0 : byteGroups[0]/2]) + start := byteGroups[0] / 2 + for i := 1; i < len(byteGroups); i++ { + nBytes := byteGroups[i] / 2 + result += "-" + result += hex.EncodeToString(bytes[start : start+nBytes]) + start += nBytes + } + return result +} + +// Bytes returns the bytes representation of this UUID. +func (u *UUID) Bytes() []byte { + return u[:] +} + +// Equals returns true if this UUID equals another UUID by value. +func (u *UUID) Equals(another *UUID) bool { + if u == nil && another == nil { + return true + } + if u == nil || another == nil { + return false + } + return bytes.Equal(u.Bytes(), another.Bytes()) +} + +// New creates a UUID with random value. +func New() UUID { + var uuid UUID + common.Must2(rand.Read(uuid.Bytes())) + uuid[6] = (uuid[6] & 0x0f) | (4 << 4) + uuid[8] = (uuid[8]&(0xff>>2) | (0x02 << 6)) + return uuid +} + +// ParseBytes converts a UUID in byte form to object. +func ParseBytes(b []byte) (UUID, error) { + var uuid UUID + if len(b) != 16 { + return uuid, E.New("invalid UUID: ", b) + } + copy(uuid[:], b) + return uuid, nil +} + +// ParseString converts a UUID in string form to object. +func ParseString(str string) (UUID, error) { + var uuid UUID + + text := []byte(str) + if l := len(text); l < 32 || l > 36 { + if l == 0 || l > 30 { + return uuid, E.New("invalid UUID: ", str) + } + h := sha1.New() + h.Write(uuid[:]) + h.Write(text) + u := h.Sum(nil)[:16] + u[6] = (u[6] & 0x0f) | (5 << 4) + u[8] = (u[8]&(0xff>>2) | (0x02 << 6)) + copy(uuid[:], u) + return uuid, nil + } + + b := uuid.Bytes() + + for _, byteGroup := range byteGroups { + if len(text) > 0 && text[0] == '-' { + text = text[1:] + } + + if len(text) < byteGroup { + return uuid, E.New("invalid UUID: ", str) + } + + if _, err := hex.Decode(b[:byteGroup/2], text[:byteGroup]); err != nil { + return uuid, err + } + + text = text[byteGroup:] + b = b[byteGroup/2:] + } + + return uuid, nil +} diff --git a/constant/certificate.go b/constant/certificate.go new file mode 100644 index 0000000000..05ab161687 --- /dev/null +++ b/constant/certificate.go @@ -0,0 +1,8 @@ +package constant + +const ( + CertificateStoreSystem = "system" + CertificateStoreMozilla = "mozilla" + CertificateStoreChrome = "chrome" + CertificateStoreNone = "none" +) diff --git a/constant/dhcp.go b/constant/dhcp.go index 1d9792a2cc..bdabd06ec7 100644 --- a/constant/dhcp.go +++ b/constant/dhcp.go @@ -4,5 +4,5 @@ import "time" const ( DHCPTTL = time.Hour - DHCPTimeout = time.Minute + DHCPTimeout = 5 * time.Second ) diff --git a/constant/dns.go b/constant/dns.go index 3907b8c15e..22b7967bf2 100644 --- a/constant/dns.go +++ b/constant/dns.go @@ -1,6 +1,39 @@ package constant +const ( + DefaultDNSTTL = 600 +) + +type DomainStrategy = uint8 + +const ( + DomainStrategyAsIS DomainStrategy = iota + DomainStrategyPreferIPv4 + DomainStrategyPreferIPv6 + DomainStrategyIPv4Only + DomainStrategyIPv6Only +) + +const ( + DNSTypeLegacy = "legacy" + DNSTypeUDP = "udp" + DNSTypeTCP = "tcp" + DNSTypeTLS = "tls" + DNSTypeHTTPS = "https" + DNSTypeQUIC = "quic" + DNSTypeHTTP3 = "h3" + DNSTypeLocal = "local" + DNSTypeHosts = "hosts" + DNSTypeFakeIP = "fakeip" + DNSTypeDHCP = "dhcp" + DNSTypeMDNS = "mdns" + DNSTypeMulti = "multi" //H + DNSTypeSDNS = "sdns" //H + DNSTypeTailscale = "tailscale" +) + const ( DNSProviderAliDNS = "alidns" DNSProviderCloudflare = "cloudflare" + DNSProviderACMEDNS = "acmedns" ) diff --git a/constant/hysteria2.go b/constant/hysteria2.go new file mode 100644 index 0000000000..2d6e6ee8b3 --- /dev/null +++ b/constant/hysteria2.go @@ -0,0 +1,12 @@ +package constant + +const ( + Hysteria2ObfsTypeSalamander = "salamander" + Hysteria2ObfsTypeGecko = "gecko" +) + +const ( + Hysterai2MasqueradeTypeFile = "file" + Hysterai2MasqueradeTypeProxy = "proxy" + Hysterai2MasqueradeTypeString = "string" +) diff --git a/constant/network.go b/constant/network.go new file mode 100644 index 0000000000..88a1dd815f --- /dev/null +++ b/constant/network.go @@ -0,0 +1,58 @@ +package constant + +import ( + "github.com/sagernet/sing/common" + F "github.com/sagernet/sing/common/format" +) + +type InterfaceType uint8 + +const ( + InterfaceTypeWIFI InterfaceType = iota + InterfaceTypeCellular + InterfaceTypeEthernet + InterfaceTypeOther +) + +var ( + interfaceTypeToString = map[InterfaceType]string{ + InterfaceTypeWIFI: "wifi", + InterfaceTypeCellular: "cellular", + InterfaceTypeEthernet: "ethernet", + InterfaceTypeOther: "other", + } + StringToInterfaceType = common.ReverseMap(interfaceTypeToString) +) + +func (t InterfaceType) String() string { + name, loaded := interfaceTypeToString[t] + if !loaded { + return F.ToString(int(t)) + } + return name +} + +type NetworkStrategy uint8 + +const ( + NetworkStrategyDefault NetworkStrategy = iota + NetworkStrategyFallback + NetworkStrategyHybrid +) + +var ( + networkStrategyToString = map[NetworkStrategy]string{ + NetworkStrategyDefault: "default", + NetworkStrategyFallback: "fallback", + NetworkStrategyHybrid: "hybrid", + } + StringToNetworkStrategy = common.ReverseMap(networkStrategyToString) +) + +func (s NetworkStrategy) String() string { + name, loaded := networkStrategyToString[s] + if !loaded { + return F.ToString(int(s)) + } + return name +} diff --git a/constant/os.go b/constant/os.go index 63c6c41cd2..6142767c9c 100644 --- a/constant/os.go +++ b/constant/os.go @@ -6,7 +6,7 @@ import ( const IsAndroid = goos.IsAndroid == 1 -const IsDarwin = goos.IsDarwin == 1 +const IsDarwin = goos.IsDarwin == 1 || goos.IsIos == 1 const IsDragonfly = goos.IsDragonfly == 1 diff --git a/constant/path.go b/constant/path.go index 98acacdc37..ea2aad3e23 100644 --- a/constant/path.go +++ b/constant/path.go @@ -13,14 +13,14 @@ var resourcePaths []string func FindPath(name string) (string, bool) { name = os.ExpandEnv(name) - if rw.FileExists(name) { + if rw.IsFile(name) { return name, true } for _, dir := range resourcePaths { - if path := filepath.Join(dir, dirName, name); rw.FileExists(path) { + if path := filepath.Join(dir, dirName, name); rw.IsFile(path) { return path, true } - if path := filepath.Join(dir, name); rw.FileExists(path) { + if path := filepath.Join(dir, name); rw.IsFile(path) { return path, true } } diff --git a/constant/protocol.go b/constant/protocol.go index 810c79ec99..dbe16e51c4 100644 --- a/constant/protocol.go +++ b/constant/protocol.go @@ -1,9 +1,22 @@ package constant const ( - ProtocolTLS = "tls" - ProtocolHTTP = "http" - ProtocolQUIC = "quic" - ProtocolDNS = "dns" - ProtocolSTUN = "stun" + ProtocolTLS = "tls" + ProtocolHTTP = "http" + ProtocolQUIC = "quic" + ProtocolDNS = "dns" + ProtocolSTUN = "stun" + ProtocolBitTorrent = "bittorrent" + ProtocolDTLS = "dtls" + ProtocolSSH = "ssh" + ProtocolRDP = "rdp" + ProtocolNTP = "ntp" +) + +const ( + ClientChromium = "chromium" + ClientSafari = "safari" + ClientFirefox = "firefox" + ClientQUICGo = "quic-go" + ClientUnknown = "unknown" ) diff --git a/constant/proxy.go b/constant/proxy.go index 1e9baee298..d674af09b7 100644 --- a/constant/proxy.go +++ b/constant/proxy.go @@ -1,28 +1,57 @@ package constant const ( - TypeTun = "tun" - TypeRedirect = "redirect" - TypeTProxy = "tproxy" - TypeDirect = "direct" - TypeBlock = "block" - TypeDNS = "dns" - TypeSOCKS = "socks" - TypeHTTP = "http" - TypeMixed = "mixed" - TypeShadowsocks = "shadowsocks" - TypeVMess = "vmess" - TypeTrojan = "trojan" - TypeNaive = "naive" - TypeWireGuard = "wireguard" - TypeHysteria = "hysteria" - TypeTor = "tor" - TypeSSH = "ssh" - TypeShadowTLS = "shadowtls" - TypeShadowsocksR = "shadowsocksr" - TypeVLESS = "vless" - TypeTUIC = "tuic" - TypeHysteria2 = "hysteria2" + TypeTun = "tun" + TypeRedirect = "redirect" + TypeTProxy = "tproxy" + TypeDirect = "direct" + TypeBlock = "block" + TypeDNS = "dns" + TypeSOCKS = "socks" + TypeHTTP = "http" + TypeMixed = "mixed" + TypeShadowsocks = "shadowsocks" + TypeVMess = "vmess" + TypeTrojan = "trojan" + TypeNaive = "naive" + TypeWireGuard = "wireguard" + TypeWARP = "warp" + TypeHysteria = "hysteria" + TypeTor = "tor" + TypeSSH = "ssh" + TypeShadowTLS = "shadowtls" + TypeMieru = "mieru" + TypeAnyTLS = "anytls" + TypeSnell = "snell" + TypeMASQUE = "masque" + TypeShadowsocksR = "shadowsocksr" + TypeVLESS = "vless" + TypeTUIC = "tuic" + TypeHysteria2 = "hysteria2" + TypeHysteriaRealm = "hysteria-realm" + TypePsiphon = "psiphon" + TypeTunnelClient = "tunnel_client" + TypeTunnelServer = "tunnel_server" + TypeTailscale = "tailscale" + TypeCloudflared = "cloudflared" + TypeDERP = "derp" + TypeResolved = "resolved" + TypeSSMAPI = "ssm-api" + TypeCCM = "ccm" + TypeOCM = "ocm" + TypeOOMKiller = "oom-killer" + TypeACME = "acme" + TypeCloudflareOriginCA = "cloudflare-origin-ca" + + TypeHInvalidConfig = "hinvalid" //H + TypeXray = "xray" //H + TypeCustom = "custom" //H + TypeAwg = "awg" //H + TypeBalancer = "balancer" //H + TypeDNSTT = "dnstt" //H + TypeGooseRelay = "gooserelay" //H + TypeFirebaseTunnel = "firebasetunnel" //H + TypeSmartDNSPool = "smart_dns_pool" //H — local recursive-resolver pool with AIMD throttling + recovery probing (github.com/hiddify/hmrd_multi_resolver_dns) ) const ( @@ -32,6 +61,12 @@ const ( func ProxyDisplayName(proxyType string) string { switch proxyType { + case TypeTun: + return "TUN" + case TypeRedirect: + return "Redirect" + case TypeTProxy: + return "TProxy" case TypeDirect: return "Direct" case TypeBlock: @@ -42,6 +77,8 @@ func ProxyDisplayName(proxyType string) string { return "SOCKS" case TypeHTTP: return "HTTP" + case TypeMixed: + return "Mixed" case TypeShadowsocks: return "Shadowsocks" case TypeVMess: @@ -52,6 +89,8 @@ func ProxyDisplayName(proxyType string) string { return "Naive" case TypeWireGuard: return "WireGuard" + case TypeWARP: + return "WARP" case TypeHysteria: return "Hysteria" case TypeTor: @@ -68,11 +107,47 @@ func ProxyDisplayName(proxyType string) string { return "TUIC" case TypeHysteria2: return "Hysteria2" + case TypeMieru: + return "Mieru" + case TypeAnyTLS: + return "AnyTLS" + case TypeSnell: + return "Snell" + case TypeMASQUE: + return "MASQUE" + case TypePsiphon: + return "Psiphon" + case TypeTailscale: + return "Tailscale" + case TypeCloudflared: + return "Cloudflared" case TypeSelector: return "Selector" case TypeURLTest: return "URLTest" + case TypeHInvalidConfig: + return "Invalid" + case TypeXray: + return "xray" + case TypeCustom: + return "custom" + case TypeTunnelClient: + return "Tunnel Client" + case TypeTunnelServer: + return "Tunnel Server" + case TypeAwg: + return "Awg" + case TypeBalancer: + return "Balancer" + case TypeDNSTT: + return "DNSTT" + case TypeGooseRelay: + return "GooseRelay" + case TypeFirebaseTunnel: + return "Firebase Tunnel" default: return "Unknown" } } + +const DefaultBrowserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36" diff --git a/constant/rule.go b/constant/rule.go index 5a8eaf127f..efd4a2d32d 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -11,9 +11,38 @@ const ( ) const ( + RuleSetTypeInline = "inline" RuleSetTypeLocal = "local" RuleSetTypeRemote = "remote" - RuleSetVersion1 = 1 RuleSetFormatSource = "source" RuleSetFormatBinary = "binary" ) + +const ( + RuleSetVersion1 = 1 + iota + RuleSetVersion2 + RuleSetVersion3 + RuleSetVersion4 + RuleSetVersion5 + RuleSetVersionCurrent = RuleSetVersion5 +) + +const ( + RuleActionTypeRoute = "route" + RuleActionTypeRouteOptions = "route-options" + RuleActionTypeEvaluate = "evaluate" + RuleActionTypeRespond = "respond" + RuleActionTypeDirect = "direct" + RuleActionTypeBypass = "bypass" + RuleActionTypeReject = "reject" + RuleActionTypeHijackDNS = "hijack-dns" + RuleActionTypeSniff = "sniff" + RuleActionTypeResolve = "resolve" + RuleActionTypePredefined = "predefined" +) + +const ( + RuleActionRejectMethodDefault = "default" + RuleActionRejectMethodDrop = "drop" + RuleActionRejectMethodReply = "reply" +) diff --git a/constant/timeout.go b/constant/timeout.go index b270a0500d..e1bc7ccdf2 100644 --- a/constant/timeout.go +++ b/constant/timeout.go @@ -3,13 +3,12 @@ package constant import "time" const ( - TCPKeepAliveInitial = 10 * time.Minute + TCPKeepAliveInitial = 5 * time.Minute TCPKeepAliveInterval = 75 * time.Second - TCPTimeout = 5 * time.Second + TCPConnectTimeout = 5 * time.Second + TCPTimeout = 15 * time.Second ReadPayloadTimeout = 300 * time.Millisecond DNSTimeout = 10 * time.Second - QUICTimeout = 30 * time.Second - STUNTimeout = 15 * time.Second UDPTimeout = 5 * time.Minute DefaultURLTestInterval = 3 * time.Minute DefaultURLTestIdleTimeout = 30 * time.Minute @@ -17,4 +16,20 @@ const ( StopTimeout = 5 * time.Second FatalStopTimeout = 10 * time.Second FakeIPMetadataSaveInterval = 10 * time.Second + TLSFragmentFallbackDelay = 500 * time.Millisecond ) + +var PortProtocols = map[uint16]string{ + 53: ProtocolDNS, + 123: ProtocolNTP, + 3478: ProtocolSTUN, + 443: ProtocolQUIC, +} + +var ProtocolTimeouts = map[string]time.Duration{ + ProtocolDNS: 10 * time.Second, + ProtocolNTP: 10 * time.Second, + ProtocolSTUN: 10 * time.Second, + ProtocolQUIC: 30 * time.Second, + ProtocolDTLS: 30 * time.Second, +} diff --git a/constant/tls.go b/constant/tls.go new file mode 100644 index 0000000000..fdd813b758 --- /dev/null +++ b/constant/tls.go @@ -0,0 +1,10 @@ +package constant + +const ACMETLS1Protocol = "acme-tls/1" + +const ( + TLSEngineDefault = "" + TLSEngineGo = "go" + TLSEngineApple = "apple" + TLSEngineWindows = "windows" +) diff --git a/constant/v2ray.go b/constant/v2ray.go index c3089a6cf6..1811df5f26 100644 --- a/constant/v2ray.go +++ b/constant/v2ray.go @@ -6,4 +6,5 @@ const ( V2RayTransportTypeQUIC = "quic" V2RayTransportTypeGRPC = "grpc" V2RayTransportTypeHTTPUpgrade = "httpupgrade" + V2RayTransportTypeXHTTP = "xhttp" ) diff --git a/constant/warp.go b/constant/warp.go new file mode 100644 index 0000000000..038ce346ea --- /dev/null +++ b/constant/warp.go @@ -0,0 +1,20 @@ +package constant + +type WARPConfig struct { + PrivateKey string `json:"private_key"` + 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"` +} diff --git a/daemon/deprecated.go b/daemon/deprecated.go new file mode 100644 index 0000000000..6f23db9991 --- /dev/null +++ b/daemon/deprecated.go @@ -0,0 +1,29 @@ +package daemon + +import ( + "sync" + + "github.com/sagernet/sing-box/experimental/deprecated" + "github.com/sagernet/sing/common" +) + +var _ deprecated.Manager = (*deprecatedManager)(nil) + +type deprecatedManager struct { + access sync.Mutex + notes []deprecated.Note +} + +func (m *deprecatedManager) ReportDeprecated(feature deprecated.Note) { + m.access.Lock() + defer m.access.Unlock() + m.notes = common.Uniq(append(m.notes, feature)) +} + +func (m *deprecatedManager) Get() []deprecated.Note { + m.access.Lock() + defer m.access.Unlock() + notes := m.notes + m.notes = nil + return notes +} diff --git a/daemon/instance.go b/daemon/instance.go new file mode 100644 index 0000000000..1cbe50195a --- /dev/null +++ b/daemon/instance.go @@ -0,0 +1,177 @@ +package daemon + +import ( + "bytes" + "context" + + box "github.com/sagernet/sing-box" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/urltest" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/experimental/deprecated" + "github.com/sagernet/sing-box/include" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/service" + "github.com/sagernet/sing/service/pause" +) + +type Instance struct { + ctx context.Context + cancel context.CancelFunc + instance *box.Box + connectionManager adapter.ConnectionManager + clashServer adapter.ClashServer + cacheFile adapter.CacheFile + pauseManager pause.Manager + urlTestHistoryStorage *urltest.HistoryStorage +} + +func (s *StartedService) CheckConfig(configContent string) error { + options, err := parseConfig(s.ctx, configContent) + if err != nil { + return err + } + ctx, cancel := context.WithCancel(s.ctx) + defer cancel() + instance, err := box.New(box.Options{ + Context: ctx, + Options: options, + }) + if err == nil { + instance.Close() + } + return err +} + +func (s *StartedService) FormatConfig(configContent string) (string, error) { + options, err := parseConfig(s.ctx, configContent) + if err != nil { + return "", err + } + var buffer bytes.Buffer + encoder := json.NewEncoder(&buffer) + encoder.SetIndent("", " ") + err = encoder.Encode(options) + if err != nil { + return "", err + } + return buffer.String(), nil +} + +type OverrideOptions struct { + AutoRedirect bool + IncludePackage []string + ExcludePackage []string +} + +func (s *StartedService) newInstance(profileContent string, overrideOptions *OverrideOptions) (*Instance, error) { + ctx := service.ExtendContext(s.ctx) + service.MustRegister[deprecated.Manager](ctx, new(deprecatedManager)) + // ctx, cancel := context.WithCancel(include.Context(ctx)) + options, err := parseConfig(ctx, profileContent) + if err != nil { + // cancel() + return nil, err + } + return s.newInstanceOptions(options, overrideOptions) +} +func (s *StartedService) newInstanceOptions(options option.Options, overrideOptions *OverrideOptions) (*Instance, error) { + ctx := s.ctx + ctx, cancel := context.WithCancel(include.Context(ctx)) + if overrideOptions != nil { + for _, inbound := range options.Inbounds { + if tunInboundOptions, isTUN := inbound.Options.(*option.TunInboundOptions); isTUN { + tunInboundOptions.AutoRedirect = overrideOptions.AutoRedirect + tunInboundOptions.IncludePackage = append(tunInboundOptions.IncludePackage, overrideOptions.IncludePackage...) + tunInboundOptions.ExcludePackage = append(tunInboundOptions.ExcludePackage, overrideOptions.ExcludePackage...) + break + } + } + } + if s.oomKillerEnabled { + if !common.Any(options.Services, func(it option.Service) bool { + return it.Type == C.TypeOOMKiller + }) { + oomOptions := &option.OOMKillerServiceOptions{ + KillerDisabled: s.oomKillerDisabled, + MemoryLimitOverride: s.oomMemoryLimit, + } + options.Services = append(options.Services, option.Service{ + Type: C.TypeOOMKiller, + Options: oomOptions, + }) + } + } + urlTestHistoryStorage := urltest.NewHistoryStorage() + ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage) + i := &Instance{ + ctx: ctx, + cancel: cancel, + urlTestHistoryStorage: urlTestHistoryStorage, + } + boxInstance, err := box.New(box.Options{ + Context: ctx, + Options: options, + PlatformLogWriter: s, + }) + if err != nil { + cancel() + return nil, err + } + experimentalOptions := common.PtrValueOrDefault(options.Experimental) + if experimentalOptions.UnifiedDelay != nil && experimentalOptions.UnifiedDelay.Enabled { + ctx = urltest.ContextWithIsUnifiedDelay(ctx) + } + i.instance = boxInstance + i.connectionManager = service.FromContext[adapter.ConnectionManager](ctx) + i.clashServer = service.FromContext[adapter.ClashServer](ctx) + i.pauseManager = service.FromContext[pause.Manager](ctx) + i.cacheFile = service.FromContext[adapter.CacheFile](ctx) + log.SetStdLogger(boxInstance.LogFactory().Logger()) + return i, nil +} + +func (i *Instance) Start() error { + return i.instance.Start() +} + +func (i *Instance) Close() error { + i.cancel() + i.urlTestHistoryStorage.Close() + return i.instance.Close() +} + +func (i *Instance) Box() *box.Box { + return i.instance +} + +func (i *Instance) PauseManager() pause.Manager { + return i.pauseManager +} + +func parseConfig(ctx context.Context, configContent string) (option.Options, error) { + options, err := json.UnmarshalExtendedContext[option.Options](ctx, []byte(configContent)) + if err != nil { + return option.Options{}, E.Cause(err, "decode config") + } + return options, nil +} + +func (i *Instance) UrlTestHistory() *urltest.HistoryStorage { + return i.urlTestHistoryStorage +} + +func (i *Instance) Context() context.Context { + return i.ctx +} + +func (i *Instance) CacheFile() adapter.CacheFile { + return i.cacheFile +} +func (i *Instance) ClashServer() adapter.ClashServer { + return i.clashServer +} diff --git a/daemon/platform.go b/daemon/platform.go new file mode 100644 index 0000000000..3d24fc2592 --- /dev/null +++ b/daemon/platform.go @@ -0,0 +1,11 @@ +package daemon + +type PlatformHandler interface { + ServiceStop() error + ServiceReload() error + SystemProxyStatus() (*SystemProxyStatus, error) + SetSystemProxyEnabled(enabled bool) error + TriggerNativeCrash() error + WriteDebugMessage(message string) + ConnectSSHAgent() (int32, error) +} diff --git a/daemon/started_service.go b/daemon/started_service.go new file mode 100644 index 0000000000..8f13cda564 --- /dev/null +++ b/daemon/started_service.go @@ -0,0 +1,1552 @@ +package daemon + +import ( + "context" + "os" + "runtime" + "sync" + "time" + "unsafe" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/networkquality" + "github.com/sagernet/sing-box/common/stun" + "github.com/sagernet/sing-box/common/urltest" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/experimental/clashapi" + "github.com/sagernet/sing-box/experimental/clashapi/trafficontrol" + "github.com/sagernet/sing-box/experimental/deprecated" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/protocol/group" + "github.com/sagernet/sing-box/service/oomkiller" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/batch" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/memory" + "github.com/sagernet/sing/common/observable" + "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service" + + "github.com/gofrs/uuid/v5" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" +) + +var _ StartedServiceServer = (*StartedService)(nil) + +type StartedService struct { + ctx context.Context + // platform adapter.PlatformInterface + handler PlatformHandler + debug bool + logMaxLines int + oomKillerEnabled bool + oomKillerDisabled bool + oomMemoryLimit uint64 + // workingDirectory string + // tempDirectory string + // userID int + // groupID int + // systemProxyEnabled bool + serviceAccess sync.RWMutex + serviceStatus *ServiceStatus + serviceStatusSubscriber *observable.Subscriber[*ServiceStatus] + serviceStatusObserver *observable.Observer[*ServiceStatus] + logAccess sync.RWMutex + logLines list.List[*log.Entry] + logSubscriber *observable.Subscriber[*log.Entry] + logObserver *observable.Observer[*log.Entry] + instance *Instance + startedAt time.Time + urlTestSubscriber *observable.Subscriber[struct{}] + urlTestObserver *observable.Observer[struct{}] + urlTestHistoryStorage *urltest.HistoryStorage + clashModeSubscriber *observable.Subscriber[struct{}] + clashModeObserver *observable.Observer[struct{}] + + connectionEventSubscriber *observable.Subscriber[trafficontrol.ConnectionEvent] + connectionEventObserver *observable.Observer[trafficontrol.ConnectionEvent] + + extraServices []adapter.LifecycleService //H +} + +type ServiceOptions struct { + Context context.Context + // Platform adapter.PlatformInterface + Handler PlatformHandler + Debug bool + LogMaxLines int + OOMKillerEnabled bool + OOMKillerDisabled bool + OOMMemoryLimit uint64 + // WorkingDirectory string + // TempDirectory string + // UserID int + // GroupID int + // SystemProxyEnabled bool + ExtraServices []adapter.LifecycleService //H +} + +func NewStartedService(options ServiceOptions) *StartedService { + s := &StartedService{ + ctx: options.Context, + // platform: options.Platform, + handler: options.Handler, + debug: options.Debug, + logMaxLines: options.LogMaxLines, + oomKillerEnabled: options.OOMKillerEnabled, + oomKillerDisabled: options.OOMKillerDisabled, + oomMemoryLimit: options.OOMMemoryLimit, + // workingDirectory: options.WorkingDirectory, + // tempDirectory: options.TempDirectory, + // userID: options.UserID, + // groupID: options.GroupID, + // systemProxyEnabled: options.SystemProxyEnabled, + serviceStatus: &ServiceStatus{Status: ServiceStatus_IDLE}, + serviceStatusSubscriber: observable.NewSubscriber[*ServiceStatus](4), + logSubscriber: observable.NewSubscriber[*log.Entry](128), + urlTestSubscriber: observable.NewSubscriber[struct{}](1), + urlTestHistoryStorage: urltest.NewHistoryStorage(), + clashModeSubscriber: observable.NewSubscriber[struct{}](1), + connectionEventSubscriber: observable.NewSubscriber[trafficontrol.ConnectionEvent](256), + extraServices: options.ExtraServices, + } + s.serviceStatusObserver = observable.NewObserver(s.serviceStatusSubscriber, 2) + s.logObserver = observable.NewObserver(s.logSubscriber, 64) + s.urlTestObserver = observable.NewObserver(s.urlTestSubscriber, 1) + s.clashModeObserver = observable.NewObserver(s.clashModeSubscriber, 1) + s.connectionEventObserver = observable.NewObserver(s.connectionEventSubscriber, 64) + return s +} + +func (s *StartedService) resetLogs() { + s.logAccess.Lock() + s.logLines = list.List[*log.Entry]{} + s.logAccess.Unlock() + s.logSubscriber.Emit(nil) +} + +func (s *StartedService) updateStatus(newStatus ServiceStatus_Type) { + statusObject := &ServiceStatus{Status: newStatus} + s.serviceStatusSubscriber.Emit(statusObject) + s.serviceStatus = statusObject +} + +func (s *StartedService) updateStatusError(err error) error { + statusObject := &ServiceStatus{Status: ServiceStatus_FATAL, ErrorMessage: err.Error()} + s.serviceStatusSubscriber.Emit(statusObject) + s.serviceStatus = statusObject + s.serviceAccess.Unlock() + return err +} + +func (s *StartedService) waitForStarted(ctx context.Context) error { + s.serviceAccess.RLock() + currentStatus := s.serviceStatus.Status + s.serviceAccess.RUnlock() + + switch currentStatus { + case ServiceStatus_STARTED: + return nil + case ServiceStatus_STARTING: + default: + return os.ErrInvalid + } + + subscription, done, err := s.serviceStatusObserver.Subscribe() + if err != nil { + return err + } + defer s.serviceStatusObserver.UnSubscribe(subscription) + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-s.ctx.Done(): + return s.ctx.Err() + case status := <-subscription: + switch status.Status { + case ServiceStatus_STARTED: + return nil + case ServiceStatus_FATAL: + return E.New(status.ErrorMessage) + case ServiceStatus_IDLE, ServiceStatus_STOPPING: + return os.ErrInvalid + } + case <-done: + return os.ErrClosed + } + } +} +func (s *StartedService) StartOrReloadService(profileContent string, options *OverrideOptions) error { + return s.startOrReloadServiceImp(nil, profileContent, options) +} +func (s *StartedService) StartOrReloadServiceOptions(profileOptions option.Options) error { + return s.startOrReloadServiceImp(&profileOptions, "", nil) +} +func (s *StartedService) startOrReloadServiceImp(profileOptions *option.Options, profileContent string, options *OverrideOptions) error { + s.serviceAccess.Lock() + switch s.serviceStatus.Status { + case ServiceStatus_IDLE, ServiceStatus_STARTED, ServiceStatus_STARTING, ServiceStatus_FATAL: + default: + s.serviceAccess.Unlock() + return os.ErrInvalid + } + oldInstance := s.instance + if oldInstance != nil { + s.updateStatus(ServiceStatus_STOPPING) + s.serviceAccess.Unlock() + _ = oldInstance.Close() + s.serviceAccess.Lock() + } + s.updateStatus(ServiceStatus_STARTING) + s.resetLogs() + var err error + var instance *Instance + if profileContent == "" && profileOptions != nil { + instance, err = s.newInstanceOptions(*profileOptions, options) + } else { + instance, err = s.newInstance(profileContent, options) + + } + if err != nil { + return s.updateStatusError(err) + } + s.instance = instance + for _, extraService := range s.extraServices { + s.instance.Box().AddService(extraService) + } + instance.urlTestHistoryStorage.SetHook(s.urlTestSubscriber) + if instance.clashServer != nil { + instance.clashServer.SetModeUpdateHook(s.clashModeSubscriber) + instance.clashServer.(*clashapi.Server).TrafficManager().SetEventHook(s.connectionEventSubscriber) + } + s.serviceAccess.Unlock() + err = instance.Start() + s.serviceAccess.Lock() + if s.serviceStatus.Status != ServiceStatus_STARTING { + s.serviceAccess.Unlock() + return nil + } + if err != nil { + return s.updateStatusError(err) + } + s.startedAt = time.Now() + s.updateStatus(ServiceStatus_STARTED) + s.serviceAccess.Unlock() + runtime.GC() + return nil +} + +func (s *StartedService) Close() { + s.serviceStatusSubscriber.Close() + s.logSubscriber.Close() + s.urlTestSubscriber.Close() + s.clashModeSubscriber.Close() + s.connectionEventSubscriber.Close() +} + +func (s *StartedService) CloseService() error { + s.serviceAccess.Lock() + switch s.serviceStatus.Status { + case ServiceStatus_STARTING, ServiceStatus_STARTED: + default: + s.serviceAccess.Unlock() + return os.ErrInvalid + } + s.updateStatus(ServiceStatus_STOPPING) + instance := s.instance + s.instance = nil + if instance != nil { + err := instance.Close() + if err != nil { + return s.updateStatusError(err) + } + } + s.startedAt = time.Time{} + s.updateStatus(ServiceStatus_IDLE) + s.serviceAccess.Unlock() + runtime.GC() + return nil +} + +func (s *StartedService) SetError(err error) { + s.serviceAccess.Lock() + s.updateStatusError(err) + s.WriteMessage(log.LevelError, err.Error()) +} + +func (s *StartedService) StopService(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { + err := s.handler.ServiceStop() + if err != nil { + return nil, err + } + return &emptypb.Empty{}, nil +} + +func (s *StartedService) ReloadService(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { + err := s.handler.ServiceReload() + if err != nil { + return nil, err + } + return &emptypb.Empty{}, nil +} + +func (s *StartedService) SubscribeServiceStatus(empty *emptypb.Empty, server grpc.ServerStreamingServer[ServiceStatus]) error { + subscription, done, err := s.serviceStatusObserver.Subscribe() + if err != nil { + return err + } + defer s.serviceStatusObserver.UnSubscribe(subscription) + err = server.Send(s.serviceStatus) + if err != nil { + return err + } + for { + select { + case <-s.ctx.Done(): + return s.ctx.Err() + case <-server.Context().Done(): + return server.Context().Err() + case newStatus := <-subscription: + err = server.Send(newStatus) + if err != nil { + return err + } + case <-done: + return nil + } + } +} + +func (s *StartedService) SubscribeLog(empty *emptypb.Empty, server grpc.ServerStreamingServer[Log]) error { + var savedLines []*log.Entry + s.logAccess.Lock() + savedLines = make([]*log.Entry, 0, s.logLines.Len()) + for element := s.logLines.Front(); element != nil; element = element.Next() { + savedLines = append(savedLines, element.Value) + } + subscription, done, err := s.logObserver.Subscribe() + s.logAccess.Unlock() + if err != nil { + return err + } + defer s.logObserver.UnSubscribe(subscription) + err = server.Send(&Log{ + Messages: common.Map(savedLines, func(it *log.Entry) *Log_Message { + return &Log_Message{ + Level: LogLevel(it.Level), + Message: it.Message, + } + }), + Reset_: true, + }) + if err != nil { + return err + } + for { + select { + case <-s.ctx.Done(): + return s.ctx.Err() + case <-server.Context().Done(): + return server.Context().Err() + case message := <-subscription: + var rawMessage Log + if message == nil { + rawMessage.Reset_ = true + } else { + rawMessage.Messages = append(rawMessage.Messages, &Log_Message{ + Level: LogLevel(message.Level), + Message: message.Message, + }) + } + fetch: + for { + select { + case message = <-subscription: + if message == nil { + rawMessage.Messages = nil + rawMessage.Reset_ = true + } else { + rawMessage.Messages = append(rawMessage.Messages, &Log_Message{ + Level: LogLevel(message.Level), + Message: message.Message, + }) + } + default: + break fetch + } + } + err = server.Send(&rawMessage) + if err != nil { + return err + } + case <-done: + return nil + } + } +} + +func (s *StartedService) GetDefaultLogLevel(ctx context.Context, empty *emptypb.Empty) (*DefaultLogLevel, error) { + s.serviceAccess.RLock() + switch s.serviceStatus.Status { + case ServiceStatus_STARTING, ServiceStatus_STARTED: + default: + s.serviceAccess.RUnlock() + return nil, os.ErrInvalid + } + logLevel := s.instance.instance.LogFactory().Level() + s.serviceAccess.RUnlock() + return &DefaultLogLevel{Level: LogLevel(logLevel)}, nil +} + +func (s *StartedService) ClearLogs(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { + s.resetLogs() + return &emptypb.Empty{}, nil +} + +func (s *StartedService) SubscribeStatus(request *SubscribeStatusRequest, server grpc.ServerStreamingServer[Status]) error { + interval := time.Duration(request.Interval) + if interval <= 0 { + interval = time.Second // Default to 1 second + } + ticker := time.NewTicker(interval) + defer ticker.Stop() + status := s.readStatus() + uploadTotal := status.UplinkTotal + downloadTotal := status.DownlinkTotal + for { + err := server.Send(status) + if err != nil { + return err + } + select { + case <-s.ctx.Done(): + return s.ctx.Err() + case <-server.Context().Done(): + return server.Context().Err() + case <-ticker.C: + } + status = s.readStatus() + upload := status.UplinkTotal - uploadTotal + download := status.DownlinkTotal - downloadTotal + uploadTotal = status.UplinkTotal + downloadTotal = status.DownlinkTotal + status.Uplink = upload + status.Downlink = download + } +} + +func (s *StartedService) ReadStatus() *Status { + return s.readStatus() +} +func (s *StartedService) readStatus() *Status { + var status Status + status.Memory = memory.Total() + status.Goroutines = int32(runtime.NumGoroutine()) + s.serviceAccess.RLock() + nowService := s.instance + s.serviceAccess.RUnlock() + if nowService != nil && nowService.connectionManager != nil { + status.ConnectionsOut = int32(nowService.connectionManager.Count()) + } + if nowService != nil { + if clashServer := nowService.clashServer; clashServer != nil { + status.TrafficAvailable = true + trafficManager := clashServer.(*clashapi.Server).TrafficManager() + status.UplinkTotal, status.DownlinkTotal = trafficManager.Total() + status.ConnectionsIn = int32(trafficManager.ConnectionsLen()) + } + } + return &status +} + +func (s *StartedService) SubscribeGroups(empty *emptypb.Empty, server grpc.ServerStreamingServer[Groups]) error { + err := s.waitForStarted(server.Context()) + if err != nil { + return err + } + subscription, done, err := s.urlTestObserver.Subscribe() + if err != nil { + return err + } + defer s.urlTestObserver.UnSubscribe(subscription) + for { + s.serviceAccess.RLock() + if s.serviceStatus.Status != ServiceStatus_STARTED { + s.serviceAccess.RUnlock() + return os.ErrInvalid + } + groups := s.readGroups() + s.serviceAccess.RUnlock() + err = server.Send(groups) + if err != nil { + return err + } + select { + case <-subscription: + case <-s.ctx.Done(): + return s.ctx.Err() + case <-server.Context().Done(): + return server.Context().Err() + case <-done: + return nil + } + } +} + +func (s *StartedService) readGroups() *Groups { + historyStorage := s.instance.urlTestHistoryStorage + boxService := s.instance + outbounds := boxService.instance.Outbound().Outbounds() + var iGroups []adapter.OutboundGroup + for _, it := range outbounds { + if group, isGroup := it.(adapter.OutboundGroup); isGroup { + iGroups = append(iGroups, group) + } + } + var gs Groups + for _, iGroup := range iGroups { + var g Group + g.Tag = iGroup.Tag() + g.Type = iGroup.Type() + _, g.Selectable = iGroup.(*group.Selector) + g.Selected = iGroup.Now() + if boxService.cacheFile != nil { + if isExpand, loaded := boxService.cacheFile.LoadGroupExpand(g.Tag); loaded { + g.IsExpand = isExpand + } + } + + for _, itemTag := range iGroup.All() { + itemOutbound, isLoaded := boxService.instance.Outbound().Outbound(itemTag) + if !isLoaded { + continue + } + + var item GroupItem + item.Tag = itemTag + item.Type = itemOutbound.Type() + if history := historyStorage.LoadURLTestHistory(adapter.OutboundTag(itemOutbound)); history != nil { + item.UrlTestTime = history.Time.Unix() + item.UrlTestDelay = int32(history.Delay) + } + g.Items = append(g.Items, &item) + } + if len(g.Items) < 2 { + continue + } + gs.Group = append(gs.Group, &g) + } + return &gs +} + +func (s *StartedService) GetClashModeStatus(ctx context.Context, empty *emptypb.Empty) (*ClashModeStatus, error) { + s.serviceAccess.RLock() + if s.serviceStatus.Status != ServiceStatus_STARTED { + s.serviceAccess.RUnlock() + return nil, os.ErrInvalid + } + clashServer := s.instance.clashServer + s.serviceAccess.RUnlock() + if clashServer == nil { + return nil, os.ErrInvalid + } + return &ClashModeStatus{ + ModeList: clashServer.ModeList(), + CurrentMode: clashServer.Mode(), + }, nil +} + +func (s *StartedService) SubscribeClashMode(empty *emptypb.Empty, server grpc.ServerStreamingServer[ClashMode]) error { + err := s.waitForStarted(server.Context()) + if err != nil { + return err + } + subscription, done, err := s.clashModeObserver.Subscribe() + if err != nil { + return err + } + defer s.clashModeObserver.UnSubscribe(subscription) + for { + s.serviceAccess.RLock() + if s.serviceStatus.Status != ServiceStatus_STARTED { + s.serviceAccess.RUnlock() + return os.ErrInvalid + } + message := &ClashMode{Mode: s.instance.clashServer.Mode()} + s.serviceAccess.RUnlock() + err = server.Send(message) + if err != nil { + return err + } + select { + case <-subscription: + case <-s.ctx.Done(): + return s.ctx.Err() + case <-server.Context().Done(): + return server.Context().Err() + case <-done: + return nil + } + } +} + +func (s *StartedService) SetClashMode(ctx context.Context, request *ClashMode) (*emptypb.Empty, error) { + s.serviceAccess.RLock() + if s.serviceStatus.Status != ServiceStatus_STARTED { + s.serviceAccess.RUnlock() + return nil, os.ErrInvalid + } + clashServer := s.instance.clashServer + s.serviceAccess.RUnlock() + clashServer.(*clashapi.Server).SetMode(request.Mode) + return &emptypb.Empty{}, nil +} + +func (s *StartedService) URLTest(ctx context.Context, request *URLTestRequest) (*emptypb.Empty, error) { + s.serviceAccess.RLock() + if s.serviceStatus.Status != ServiceStatus_STARTED { + s.serviceAccess.RUnlock() + return nil, os.ErrInvalid + } + boxService := s.instance + s.serviceAccess.RUnlock() + groupTag := request.OutboundTag + abstractOutboundGroup, isLoaded := boxService.instance.Outbound().Outbound(groupTag) + if !isLoaded { + return nil, E.New("outbound group not found: ", groupTag) + } + outboundGroup, isOutboundGroup := abstractOutboundGroup.(adapter.OutboundGroup) + if !isOutboundGroup { + return nil, E.New("outbound is not a group: ", groupTag) + } + urlTest, isURLTest := abstractOutboundGroup.(*group.URLTest) + if isURLTest { + go urlTest.CheckOutbounds() + } else { + historyStorage := boxService.urlTestHistoryStorage + + outbounds := common.Filter(common.Map(outboundGroup.All(), func(it string) adapter.Outbound { + itOutbound, _ := boxService.instance.Outbound().Outbound(it) + return itOutbound + }), func(it adapter.Outbound) bool { + if it == nil { + return false + } + _, isGroup := it.(adapter.OutboundGroup) + if isGroup { + return false + } + return true + }) + b, _ := batch.New(boxService.ctx, batch.WithConcurrencyNum[any](10)) + for _, detour := range outbounds { + outboundToTest := detour + outboundTag := outboundToTest.Tag() + b.Go(outboundTag, func() (any, error) { + t, err := urltest.URLTest(boxService.ctx, "", outboundToTest) + if err != nil { + historyStorage.DeleteURLTestHistory(outboundTag) + } else { + historyStorage.StoreURLTestHistory(outboundTag, &adapter.URLTestHistory{ + Time: time.Now(), + Delay: t, + }) + } + return nil, nil + }) + } + } + return &emptypb.Empty{}, nil +} + +func (s *StartedService) SelectOutbound(ctx context.Context, request *SelectOutboundRequest) (*emptypb.Empty, error) { + s.serviceAccess.RLock() + switch s.serviceStatus.Status { + case ServiceStatus_STARTING, ServiceStatus_STARTED: + default: + s.serviceAccess.RUnlock() + return nil, os.ErrInvalid + } + boxService := s.instance.instance + s.serviceAccess.RUnlock() + outboundGroup, isLoaded := boxService.Outbound().Outbound(request.GroupTag) + if !isLoaded { + return nil, E.New("selector not found: ", request.GroupTag) + } + selector, isSelector := outboundGroup.(*group.Selector) + if !isSelector { + return nil, E.New("outbound is not a selector: ", request.GroupTag) + } + if !selector.SelectOutbound(request.OutboundTag) { + return nil, E.New("outbound not found in selector: ", request.OutboundTag) + } + s.urlTestObserver.Emit(struct{}{}) + return &emptypb.Empty{}, nil +} + +func (s *StartedService) SetGroupExpand(ctx context.Context, request *SetGroupExpandRequest) (*emptypb.Empty, error) { + s.serviceAccess.RLock() + switch s.serviceStatus.Status { + case ServiceStatus_STARTING, ServiceStatus_STARTED: + default: + s.serviceAccess.RUnlock() + return nil, os.ErrInvalid + } + boxService := s.instance + s.serviceAccess.RUnlock() + if boxService.cacheFile != nil { + err := boxService.cacheFile.StoreGroupExpand(request.GroupTag, request.IsExpand) + if err != nil { + return nil, err + } + } + return &emptypb.Empty{}, nil +} + +func (s *StartedService) GetSystemProxyStatus(ctx context.Context, empty *emptypb.Empty) (*SystemProxyStatus, error) { + return s.handler.SystemProxyStatus() +} + +func (s *StartedService) SetSystemProxyEnabled(ctx context.Context, request *SetSystemProxyEnabledRequest) (*emptypb.Empty, error) { + err := s.handler.SetSystemProxyEnabled(request.Enabled) + if err != nil { + return nil, err + } + return &emptypb.Empty{}, nil +} + +func (s *StartedService) TriggerDebugCrash(ctx context.Context, request *DebugCrashRequest) (*emptypb.Empty, error) { + if !s.debug { + return nil, status.Error(codes.PermissionDenied, "debug crash trigger unavailable") + } + if request == nil { + return nil, status.Error(codes.InvalidArgument, "missing debug crash request") + } + switch request.Type { + case DebugCrashRequest_GO: + time.AfterFunc(200*time.Millisecond, func() { + *(*int)(unsafe.Pointer(uintptr(0))) = 0 + }) + case DebugCrashRequest_NATIVE: + err := s.handler.TriggerNativeCrash() + if err != nil { + return nil, err + } + default: + return nil, status.Error(codes.InvalidArgument, "unknown debug crash type") + } + return &emptypb.Empty{}, nil +} + +func (s *StartedService) TriggerOOMReport(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) { + instance := s.Instance() + if instance == nil { + return nil, status.Error(codes.FailedPrecondition, "service not started") + } + reporter := service.FromContext[oomkiller.OOMReporter](instance.ctx) + if reporter == nil { + return nil, status.Error(codes.Unavailable, "OOM reporter not available") + } + return &emptypb.Empty{}, reporter.WriteReport(memory.Total()) +} + +func (s *StartedService) SubscribeConnections(request *SubscribeConnectionsRequest, server grpc.ServerStreamingServer[ConnectionEvents]) error { + err := s.waitForStarted(server.Context()) + if err != nil { + return err + } + s.serviceAccess.RLock() + boxService := s.instance + s.serviceAccess.RUnlock() + + if boxService.clashServer == nil { + return E.New("clash server not available") + } + + trafficManager := boxService.clashServer.(*clashapi.Server).TrafficManager() + + subscription, done, err := s.connectionEventObserver.Subscribe() + if err != nil { + return err + } + defer s.connectionEventObserver.UnSubscribe(subscription) + + connectionSnapshots := make(map[uuid.UUID]connectionSnapshot) + initialEvents := s.buildInitialConnectionState(trafficManager, connectionSnapshots) + err = server.Send(&ConnectionEvents{ + Events: initialEvents, + Reset_: true, + }) + if err != nil { + return err + } + + interval := time.Duration(request.Interval) + if interval <= 0 { + interval = time.Second + } + ticker := time.NewTicker(interval) + defer ticker.Stop() + + for { + select { + case <-s.ctx.Done(): + return s.ctx.Err() + case <-server.Context().Done(): + return server.Context().Err() + case <-done: + return nil + + case event := <-subscription: + var pendingEvents []*ConnectionEvent + if protoEvent := s.applyConnectionEvent(event, connectionSnapshots); protoEvent != nil { + pendingEvents = append(pendingEvents, protoEvent) + } + drain: + for { + select { + case event = <-subscription: + if protoEvent := s.applyConnectionEvent(event, connectionSnapshots); protoEvent != nil { + pendingEvents = append(pendingEvents, protoEvent) + } + default: + break drain + } + } + if len(pendingEvents) > 0 { + err = server.Send(&ConnectionEvents{Events: pendingEvents}) + if err != nil { + return err + } + } + + case <-ticker.C: + protoEvents := s.buildTrafficUpdates(trafficManager, connectionSnapshots) + if len(protoEvents) == 0 { + continue + } + err = server.Send(&ConnectionEvents{Events: protoEvents}) + if err != nil { + return err + } + } + } +} + +type connectionSnapshot struct { + uplink int64 + downlink int64 + hadTraffic bool +} + +func (s *StartedService) buildInitialConnectionState(manager *trafficontrol.Manager, snapshots map[uuid.UUID]connectionSnapshot) []*ConnectionEvent { + var events []*ConnectionEvent + + for _, metadata := range manager.Connections() { + events = append(events, &ConnectionEvent{ + Type: ConnectionEventType_CONNECTION_EVENT_NEW, + Id: metadata.ID.String(), + Connection: buildConnectionProto(metadata), + }) + snapshots[metadata.ID] = connectionSnapshot{ + uplink: metadata.Upload.Load(), + downlink: metadata.Download.Load(), + } + } + + for _, metadata := range manager.ClosedConnections() { + conn := buildConnectionProto(metadata) + conn.ClosedAt = metadata.ClosedAt.UnixMilli() + events = append(events, &ConnectionEvent{ + Type: ConnectionEventType_CONNECTION_EVENT_NEW, + Id: metadata.ID.String(), + Connection: conn, + }) + } + + return events +} + +func (s *StartedService) applyConnectionEvent(event trafficontrol.ConnectionEvent, snapshots map[uuid.UUID]connectionSnapshot) *ConnectionEvent { + switch event.Type { + case trafficontrol.ConnectionEventNew: + if _, exists := snapshots[event.ID]; exists { + return nil + } + snapshots[event.ID] = connectionSnapshot{ + uplink: event.Metadata.Upload.Load(), + downlink: event.Metadata.Download.Load(), + } + return &ConnectionEvent{ + Type: ConnectionEventType_CONNECTION_EVENT_NEW, + Id: event.ID.String(), + Connection: buildConnectionProto(event.Metadata), + } + case trafficontrol.ConnectionEventClosed: + delete(snapshots, event.ID) + protoEvent := &ConnectionEvent{ + Type: ConnectionEventType_CONNECTION_EVENT_CLOSED, + Id: event.ID.String(), + } + closedAt := event.ClosedAt + if closedAt.IsZero() && !event.Metadata.ClosedAt.IsZero() { + closedAt = event.Metadata.ClosedAt + } + if closedAt.IsZero() { + closedAt = time.Now() + } + protoEvent.ClosedAt = closedAt.UnixMilli() + if event.Metadata.ID != uuid.Nil { + conn := buildConnectionProto(event.Metadata) + conn.ClosedAt = protoEvent.ClosedAt + protoEvent.Connection = conn + } + return protoEvent + default: + return nil + } +} + +func (s *StartedService) buildTrafficUpdates(manager *trafficontrol.Manager, snapshots map[uuid.UUID]connectionSnapshot) []*ConnectionEvent { + activeConnections := manager.Connections() + activeIndex := make(map[uuid.UUID]*trafficontrol.TrackerMetadata, len(activeConnections)) + var events []*ConnectionEvent + + for _, metadata := range activeConnections { + activeIndex[metadata.ID] = metadata + currentUpload := metadata.Upload.Load() + currentDownload := metadata.Download.Load() + snapshot, exists := snapshots[metadata.ID] + if !exists { + snapshots[metadata.ID] = connectionSnapshot{ + uplink: currentUpload, + downlink: currentDownload, + } + events = append(events, &ConnectionEvent{ + Type: ConnectionEventType_CONNECTION_EVENT_NEW, + Id: metadata.ID.String(), + Connection: buildConnectionProto(metadata), + }) + continue + } + uplinkDelta := currentUpload - snapshot.uplink + downlinkDelta := currentDownload - snapshot.downlink + if uplinkDelta < 0 || downlinkDelta < 0 { + if snapshot.hadTraffic { + events = append(events, &ConnectionEvent{ + Type: ConnectionEventType_CONNECTION_EVENT_UPDATE, + Id: metadata.ID.String(), + UplinkDelta: 0, + DownlinkDelta: 0, + }) + } + snapshot.uplink = currentUpload + snapshot.downlink = currentDownload + snapshot.hadTraffic = false + snapshots[metadata.ID] = snapshot + continue + } + if uplinkDelta > 0 || downlinkDelta > 0 { + snapshot.uplink = currentUpload + snapshot.downlink = currentDownload + snapshot.hadTraffic = true + snapshots[metadata.ID] = snapshot + events = append(events, &ConnectionEvent{ + Type: ConnectionEventType_CONNECTION_EVENT_UPDATE, + Id: metadata.ID.String(), + UplinkDelta: uplinkDelta, + DownlinkDelta: downlinkDelta, + }) + continue + } + if snapshot.hadTraffic { + snapshot.uplink = currentUpload + snapshot.downlink = currentDownload + snapshot.hadTraffic = false + snapshots[metadata.ID] = snapshot + events = append(events, &ConnectionEvent{ + Type: ConnectionEventType_CONNECTION_EVENT_UPDATE, + Id: metadata.ID.String(), + UplinkDelta: 0, + DownlinkDelta: 0, + }) + } + } + + var closedIndex map[uuid.UUID]*trafficontrol.TrackerMetadata + for id := range snapshots { + if _, exists := activeIndex[id]; exists { + continue + } + if closedIndex == nil { + closedIndex = make(map[uuid.UUID]*trafficontrol.TrackerMetadata) + for _, metadata := range manager.ClosedConnections() { + closedIndex[metadata.ID] = metadata + } + } + closedAt := time.Now() + var conn *Connection + if metadata, ok := closedIndex[id]; ok { + if !metadata.ClosedAt.IsZero() { + closedAt = metadata.ClosedAt + } + conn = buildConnectionProto(metadata) + conn.ClosedAt = closedAt.UnixMilli() + } + events = append(events, &ConnectionEvent{ + Type: ConnectionEventType_CONNECTION_EVENT_CLOSED, + Id: id.String(), + ClosedAt: closedAt.UnixMilli(), + Connection: conn, + }) + delete(snapshots, id) + } + + return events +} + +func buildConnectionProto(metadata *trafficontrol.TrackerMetadata) *Connection { + var rule string + if metadata.Rule != nil { + rule = metadata.Rule.String() + } + uplinkTotal := metadata.Upload.Load() + downlinkTotal := metadata.Download.Load() + var processInfo *ProcessInfo + if metadata.Metadata.ProcessInfo != nil { + processInfo = &ProcessInfo{ + ProcessId: metadata.Metadata.ProcessInfo.ProcessID, + UserId: metadata.Metadata.ProcessInfo.UserId, + UserName: metadata.Metadata.ProcessInfo.UserName, + ProcessPath: metadata.Metadata.ProcessInfo.ProcessPath, + PackageNames: metadata.Metadata.ProcessInfo.AndroidPackageNames, + } + } + return &Connection{ + Id: metadata.ID.String(), + Inbound: metadata.Metadata.Inbound, + InboundType: metadata.Metadata.InboundType, + IpVersion: int32(metadata.Metadata.IPVersion), + Network: metadata.Metadata.Network, + Source: metadata.Metadata.Source.String(), + Destination: metadata.Metadata.Destination.String(), + Domain: metadata.Metadata.Domain, + Protocol: metadata.Metadata.Protocol, + User: metadata.Metadata.User, + FromOutbound: metadata.Metadata.Outbound, + CreatedAt: metadata.CreatedAt.UnixMilli(), + UplinkTotal: uplinkTotal, + DownlinkTotal: downlinkTotal, + Rule: rule, + Outbound: metadata.Outbound, + OutboundType: metadata.OutboundType, + ChainList: metadata.Chain, + ProcessInfo: processInfo, + } +} + +func (s *StartedService) CloseConnection(ctx context.Context, request *CloseConnectionRequest) (*emptypb.Empty, error) { + s.serviceAccess.RLock() + switch s.serviceStatus.Status { + case ServiceStatus_STARTING, ServiceStatus_STARTED: + default: + s.serviceAccess.RUnlock() + return nil, os.ErrInvalid + } + boxService := s.instance + s.serviceAccess.RUnlock() + targetConn := boxService.clashServer.(*clashapi.Server).TrafficManager().Connection(uuid.FromStringOrNil(request.Id)) + if targetConn != nil { + targetConn.Close() + } + return &emptypb.Empty{}, nil +} + +func (s *StartedService) CloseAllConnections(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { + s.serviceAccess.RLock() + nowService := s.instance + s.serviceAccess.RUnlock() + if nowService != nil && nowService.connectionManager != nil { + nowService.connectionManager.CloseAll() + } + return &emptypb.Empty{}, nil +} + +func (s *StartedService) GetDeprecatedWarnings(ctx context.Context, empty *emptypb.Empty) (*DeprecatedWarnings, error) { + s.serviceAccess.RLock() + if s.serviceStatus.Status != ServiceStatus_STARTED { + s.serviceAccess.RUnlock() + return nil, os.ErrInvalid + } + boxService := s.instance + s.serviceAccess.RUnlock() + notes := service.FromContext[deprecated.Manager](boxService.ctx).(*deprecatedManager).Get() + return &DeprecatedWarnings{ + Warnings: common.Map(notes, func(it deprecated.Note) *DeprecatedWarning { + return &DeprecatedWarning{ + Message: it.Message(), + Impending: it.Impending(), + MigrationLink: it.MigrationLink, + Description: it.Description, + DeprecatedVersion: it.DeprecatedVersion, + ScheduledVersion: it.ScheduledVersion, + } + }), + }, nil +} + +func (s *StartedService) GetStartedAt(ctx context.Context, empty *emptypb.Empty) (*StartedAt, error) { + s.serviceAccess.RLock() + defer s.serviceAccess.RUnlock() + return &StartedAt{StartedAt: s.startedAt.UnixMilli()}, nil +} + +func (s *StartedService) SubscribeOutbounds(_ *emptypb.Empty, server grpc.ServerStreamingServer[OutboundList]) error { + err := s.waitForStarted(server.Context()) + if err != nil { + return err + } + subscription, done, err := s.urlTestObserver.Subscribe() + if err != nil { + return err + } + defer s.urlTestObserver.UnSubscribe(subscription) + for { + s.serviceAccess.RLock() + if s.serviceStatus.Status != ServiceStatus_STARTED { + s.serviceAccess.RUnlock() + return os.ErrInvalid + } + boxService := s.instance + s.serviceAccess.RUnlock() + historyStorage := boxService.urlTestHistoryStorage + var list OutboundList + for _, ob := range boxService.instance.Outbound().Outbounds() { + item := &GroupItem{ + Tag: ob.Tag(), + Type: ob.Type(), + } + if history := historyStorage.LoadURLTestHistory(adapter.OutboundTag(ob)); history != nil { + item.UrlTestTime = history.Time.Unix() + item.UrlTestDelay = int32(history.Delay) + } + list.Outbounds = append(list.Outbounds, item) + } + for _, ep := range boxService.instance.Endpoint().Endpoints() { + item := &GroupItem{ + Tag: ep.Tag(), + Type: ep.Type(), + } + if history := historyStorage.LoadURLTestHistory(adapter.OutboundTag(ep)); history != nil { + item.UrlTestTime = history.Time.Unix() + item.UrlTestDelay = int32(history.Delay) + } + list.Outbounds = append(list.Outbounds, item) + } + err = server.Send(&list) + if err != nil { + return err + } + select { + case <-subscription: + case <-s.ctx.Done(): + return s.ctx.Err() + case <-server.Context().Done(): + return server.Context().Err() + case <-done: + return nil + } + } +} + +func resolveOutbound(instance *Instance, tag string) (adapter.Outbound, error) { + if tag == "" { + return instance.instance.Outbound().Default(), nil + } + outbound, loaded := instance.instance.Outbound().Outbound(tag) + if !loaded { + return nil, E.New("outbound not found: ", tag) + } + return outbound, nil +} + +func resolveTailscaleEndpoint(instance *Instance, tag string) (adapter.Endpoint, error) { + endpointManager := service.FromContext[adapter.EndpointManager](instance.ctx) + endpoint, loaded := endpointManager.Get(tag) + if !loaded { + return nil, E.New("endpoint not found: ", tag) + } + if endpoint.Type() != C.TypeTailscale { + return nil, E.New("endpoint is not Tailscale: ", tag) + } + return endpoint, nil +} + +func (s *StartedService) StartNetworkQualityTest( + request *NetworkQualityTestRequest, + server grpc.ServerStreamingServer[NetworkQualityTestProgress], +) error { + err := s.waitForStarted(server.Context()) + if err != nil { + return err + } + s.serviceAccess.RLock() + boxService := s.instance + s.serviceAccess.RUnlock() + + outbound, err := resolveOutbound(boxService, request.OutboundTag) + if err != nil { + return err + } + + resolvedDialer := dialer.NewResolveDialer(boxService.ctx, outbound, true, "", adapter.DNSQueryOptions{}, 0) + httpClient := networkquality.NewHTTPClient(resolvedDialer) + defer httpClient.CloseIdleConnections() + + measurementClientFactory, err := networkquality.NewOptionalHTTP3Factory(resolvedDialer, request.Http3) + if err != nil { + return err + } + + result, nqErr := networkquality.Run(networkquality.Options{ + ConfigURL: request.ConfigURL, + HTTPClient: httpClient, + NewMeasurementClient: measurementClientFactory, + Serial: request.Serial, + MaxRuntime: time.Duration(request.MaxRuntimeSeconds) * time.Second, + Context: server.Context(), + OnProgress: func(p networkquality.Progress) { + _ = server.Send(&NetworkQualityTestProgress{ + Phase: int32(p.Phase), + DownloadCapacity: p.DownloadCapacity, + UploadCapacity: p.UploadCapacity, + DownloadRPM: p.DownloadRPM, + UploadRPM: p.UploadRPM, + IdleLatencyMs: p.IdleLatencyMs, + ElapsedMs: p.ElapsedMs, + DownloadCapacityAccuracy: int32(p.DownloadCapacityAccuracy), + UploadCapacityAccuracy: int32(p.UploadCapacityAccuracy), + DownloadRPMAccuracy: int32(p.DownloadRPMAccuracy), + UploadRPMAccuracy: int32(p.UploadRPMAccuracy), + }) + }, + }) + if nqErr != nil { + return server.Send(&NetworkQualityTestProgress{ + IsFinal: true, + Error: nqErr.Error(), + }) + } + return server.Send(&NetworkQualityTestProgress{ + Phase: int32(networkquality.PhaseDone), + DownloadCapacity: result.DownloadCapacity, + UploadCapacity: result.UploadCapacity, + DownloadRPM: result.DownloadRPM, + UploadRPM: result.UploadRPM, + IdleLatencyMs: result.IdleLatencyMs, + IsFinal: true, + DownloadCapacityAccuracy: int32(result.DownloadCapacityAccuracy), + UploadCapacityAccuracy: int32(result.UploadCapacityAccuracy), + DownloadRPMAccuracy: int32(result.DownloadRPMAccuracy), + UploadRPMAccuracy: int32(result.UploadRPMAccuracy), + }) +} + +func (s *StartedService) StartSTUNTest( + request *STUNTestRequest, + server grpc.ServerStreamingServer[STUNTestProgress], +) error { + err := s.waitForStarted(server.Context()) + if err != nil { + return err + } + s.serviceAccess.RLock() + boxService := s.instance + s.serviceAccess.RUnlock() + + outbound, err := resolveOutbound(boxService, request.OutboundTag) + if err != nil { + return err + } + + resolvedDialer := dialer.NewResolveDialer(boxService.ctx, outbound, true, "", adapter.DNSQueryOptions{}, 0) + + result, stunErr := stun.Run(stun.Options{ + Server: request.Server, + Dialer: resolvedDialer, + Context: server.Context(), + OnProgress: func(p stun.Progress) { + _ = server.Send(&STUNTestProgress{ + Phase: int32(p.Phase), + ExternalAddr: p.ExternalAddr, + LatencyMs: p.LatencyMs, + NatMapping: int32(p.NATMapping), + NatFiltering: int32(p.NATFiltering), + }) + }, + }) + if stunErr != nil { + return server.Send(&STUNTestProgress{ + IsFinal: true, + Error: stunErr.Error(), + }) + } + return server.Send(&STUNTestProgress{ + Phase: int32(stun.PhaseDone), + ExternalAddr: result.ExternalAddr, + LatencyMs: result.LatencyMs, + NatMapping: int32(result.NATMapping), + NatFiltering: int32(result.NATFiltering), + IsFinal: true, + NatTypeSupported: result.NATTypeSupported, + }) +} + +func (s *StartedService) SubscribeTailscaleStatus( + _ *emptypb.Empty, + server grpc.ServerStreamingServer[TailscaleStatusUpdate], +) error { + err := s.waitForStarted(server.Context()) + if err != nil { + return err + } + s.serviceAccess.RLock() + boxService := s.instance + s.serviceAccess.RUnlock() + + endpointManager := service.FromContext[adapter.EndpointManager](boxService.ctx) + if endpointManager == nil { + return status.Error(codes.FailedPrecondition, "endpoint manager not available") + } + + type tailscaleEndpoint struct { + tag string + provider adapter.TailscaleEndpoint + } + var endpoints []tailscaleEndpoint + for _, endpoint := range endpointManager.Endpoints() { + if endpoint.Type() != C.TypeTailscale { + continue + } + provider, loaded := endpoint.(adapter.TailscaleEndpoint) + if !loaded { + continue + } + endpoints = append(endpoints, tailscaleEndpoint{ + tag: endpoint.Tag(), + provider: provider, + }) + } + if len(endpoints) == 0 { + return status.Error(codes.NotFound, "no Tailscale endpoint found") + } + + type taggedStatus struct { + tag string + status *adapter.TailscaleEndpointStatus + } + updates := make(chan taggedStatus, len(endpoints)) + ctx, cancel := context.WithCancel(server.Context()) + defer cancel() + + var waitGroup sync.WaitGroup + for _, endpoint := range endpoints { + waitGroup.Add(1) + go func(tag string, provider adapter.TailscaleEndpoint) { + defer waitGroup.Done() + _ = provider.SubscribeTailscaleStatus(ctx, func(endpointStatus *adapter.TailscaleEndpointStatus) { + select { + case updates <- taggedStatus{tag: tag, status: endpointStatus}: + case <-ctx.Done(): + } + }) + }(endpoint.tag, endpoint.provider) + } + + go func() { + waitGroup.Wait() + close(updates) + }() + + var tags []string + statuses := make(map[string]*adapter.TailscaleEndpointStatus, len(endpoints)) + for update := range updates { + if _, exists := statuses[update.tag]; !exists { + tags = append(tags, update.tag) + } + statuses[update.tag] = update.status + protoEndpoints := make([]*TailscaleEndpointStatus, 0, len(statuses)) + for _, tag := range tags { + protoEndpoints = append(protoEndpoints, tailscaleEndpointStatusToProto(tag, statuses[tag])) + } + sendErr := server.Send(&TailscaleStatusUpdate{ + Endpoints: protoEndpoints, + }) + if sendErr != nil { + return sendErr + } + } + return nil +} + +func tailscaleEndpointStatusToProto(tag string, s *adapter.TailscaleEndpointStatus) *TailscaleEndpointStatus { + userGroups := make([]*TailscaleUserGroup, len(s.UserGroups)) + for i, group := range s.UserGroups { + peers := make([]*TailscalePeer, len(group.Peers)) + for j, peer := range group.Peers { + peers[j] = tailscalePeerToProto(peer) + } + userGroups[i] = &TailscaleUserGroup{ + UserID: group.UserID, + LoginName: group.LoginName, + DisplayName: group.DisplayName, + ProfilePicURL: group.ProfilePicURL, + Peers: peers, + } + } + result := &TailscaleEndpointStatus{ + EndpointTag: tag, + BackendState: s.BackendState, + AuthURL: s.AuthURL, + NetworkName: s.NetworkName, + MagicDNSSuffix: s.MagicDNSSuffix, + UserGroups: userGroups, + } + if s.Self != nil { + result.Self = tailscalePeerToProto(s.Self) + } + if s.ExitNode != nil { + result.ExitNode = tailscalePeerToProto(s.ExitNode) + } + return result +} + +func tailscalePeerToProto(peer *adapter.TailscalePeer) *TailscalePeer { + return &TailscalePeer{ + StableID: peer.StableID, + HostName: peer.HostName, + DnsName: peer.DNSName, + Os: peer.OS, + TailscaleIPs: peer.TailscaleIPs, + SshHostKeys: peer.SSHHostKeys, + Online: peer.Online, + ExitNode: peer.ExitNode, + ExitNodeOption: peer.ExitNodeOption, + ShareeNode: peer.ShareeNode, + Expired: peer.Expired, + Active: peer.Active, + RxBytes: peer.RxBytes, + TxBytes: peer.TxBytes, + KeyExpiry: peer.KeyExpiry, + LastSeen: peer.LastSeen, + } +} + +func (s *StartedService) StartTailscalePing( + request *TailscalePingRequest, + server grpc.ServerStreamingServer[TailscalePingResponse], +) error { + err := s.waitForStarted(server.Context()) + if err != nil { + return err + } + s.serviceAccess.RLock() + boxService := s.instance + s.serviceAccess.RUnlock() + + var provider adapter.TailscaleEndpoint + if request.EndpointTag != "" { + endpoint, err := resolveTailscaleEndpoint(boxService, request.EndpointTag) + if err != nil { + return err + } + pingProvider, loaded := endpoint.(adapter.TailscaleEndpoint) + if !loaded { + return status.Error(codes.FailedPrecondition, "endpoint does not support ping") + } + provider = pingProvider + } else { + endpointManager := service.FromContext[adapter.EndpointManager](boxService.ctx) + if endpointManager == nil { + return status.Error(codes.FailedPrecondition, "endpoint manager not available") + } + for _, endpoint := range endpointManager.Endpoints() { + if endpoint.Type() != C.TypeTailscale { + continue + } + pingProvider, loaded := endpoint.(adapter.TailscaleEndpoint) + if loaded { + provider = pingProvider + break + } + } + if provider == nil { + return status.Error(codes.NotFound, "no Tailscale endpoint found") + } + } + + return provider.StartTailscalePing(server.Context(), request.PeerIP, func(result *adapter.TailscalePingResult) { + _ = server.Send(&TailscalePingResponse{ + LatencyMs: result.LatencyMs, + IsDirect: result.IsDirect, + Endpoint: result.Endpoint, + DerpRegionID: result.DERPRegionID, + DerpRegionCode: result.DERPRegionCode, + Error: result.Error, + }) + }) +} + +func (s *StartedService) SetTailscaleExitNode(ctx context.Context, request *SetTailscaleExitNodeRequest) (*emptypb.Empty, error) { + err := s.waitForStarted(ctx) + if err != nil { + return nil, err + } + s.serviceAccess.RLock() + boxService := s.instance + s.serviceAccess.RUnlock() + + endpoint, err := resolveTailscaleEndpoint(boxService, request.EndpointTag) + if err != nil { + return nil, err + } + tsEndpoint, loaded := endpoint.(adapter.TailscaleEndpoint) + if !loaded { + return nil, status.Error(codes.FailedPrecondition, "endpoint does not support tailscale") + } + err = tsEndpoint.SetTailscaleExitNode(ctx, request.StableID) + if err != nil { + return nil, err + } + return &emptypb.Empty{}, nil +} + +func (s *StartedService) mustEmbedUnimplementedStartedServiceServer() { +} + +func (s *StartedService) WriteMessage(level log.Level, message string) { + item := &log.Entry{Level: level, Message: message} + s.logAccess.Lock() + s.logLines.PushBack(item) + if s.logLines.Len() > s.logMaxLines { + s.logLines.Remove(s.logLines.Front()) + } + s.logAccess.Unlock() + s.logSubscriber.Emit(item) + if s.debug { + s.handler.WriteDebugMessage(message) + } +} + +func (s *StartedService) Instance() *Instance { + s.serviceAccess.RLock() + defer s.serviceAccess.RUnlock() + return s.instance +} diff --git a/daemon/started_service.pb.go b/daemon/started_service.pb.go new file mode 100644 index 0000000000..3eb41c842f --- /dev/null +++ b/daemon/started_service.pb.go @@ -0,0 +1,4074 @@ +package daemon + +import ( + reflect "reflect" + sync "sync" + unsafe "unsafe" + + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type LogLevel int32 + +const ( + LogLevel_PANIC LogLevel = 0 + LogLevel_FATAL LogLevel = 1 + LogLevel_ERROR LogLevel = 2 + LogLevel_WARN LogLevel = 3 + LogLevel_INFO LogLevel = 4 + LogLevel_DEBUG LogLevel = 5 + LogLevel_TRACE LogLevel = 6 +) + +// Enum value maps for LogLevel. +var ( + LogLevel_name = map[int32]string{ + 0: "PANIC", + 1: "FATAL", + 2: "ERROR", + 3: "WARN", + 4: "INFO", + 5: "DEBUG", + 6: "TRACE", + } + LogLevel_value = map[string]int32{ + "PANIC": 0, + "FATAL": 1, + "ERROR": 2, + "WARN": 3, + "INFO": 4, + "DEBUG": 5, + "TRACE": 6, + } +) + +func (x LogLevel) Enum() *LogLevel { + p := new(LogLevel) + *p = x + return p +} + +func (x LogLevel) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (LogLevel) Descriptor() protoreflect.EnumDescriptor { + return file_daemon_started_service_proto_enumTypes[0].Descriptor() +} + +func (LogLevel) Type() protoreflect.EnumType { + return &file_daemon_started_service_proto_enumTypes[0] +} + +func (x LogLevel) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use LogLevel.Descriptor instead. +func (LogLevel) EnumDescriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{0} +} + +type ConnectionEventType int32 + +const ( + ConnectionEventType_CONNECTION_EVENT_NEW ConnectionEventType = 0 + ConnectionEventType_CONNECTION_EVENT_UPDATE ConnectionEventType = 1 + ConnectionEventType_CONNECTION_EVENT_CLOSED ConnectionEventType = 2 +) + +// Enum value maps for ConnectionEventType. +var ( + ConnectionEventType_name = map[int32]string{ + 0: "CONNECTION_EVENT_NEW", + 1: "CONNECTION_EVENT_UPDATE", + 2: "CONNECTION_EVENT_CLOSED", + } + ConnectionEventType_value = map[string]int32{ + "CONNECTION_EVENT_NEW": 0, + "CONNECTION_EVENT_UPDATE": 1, + "CONNECTION_EVENT_CLOSED": 2, + } +) + +func (x ConnectionEventType) Enum() *ConnectionEventType { + p := new(ConnectionEventType) + *p = x + return p +} + +func (x ConnectionEventType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ConnectionEventType) Descriptor() protoreflect.EnumDescriptor { + return file_daemon_started_service_proto_enumTypes[1].Descriptor() +} + +func (ConnectionEventType) Type() protoreflect.EnumType { + return &file_daemon_started_service_proto_enumTypes[1] +} + +func (x ConnectionEventType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ConnectionEventType.Descriptor instead. +func (ConnectionEventType) EnumDescriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{1} +} + +type ServiceStatus_Type int32 + +const ( + ServiceStatus_IDLE ServiceStatus_Type = 0 + ServiceStatus_STARTING ServiceStatus_Type = 1 + ServiceStatus_STARTED ServiceStatus_Type = 2 + ServiceStatus_STOPPING ServiceStatus_Type = 3 + ServiceStatus_FATAL ServiceStatus_Type = 4 +) + +// Enum value maps for ServiceStatus_Type. +var ( + ServiceStatus_Type_name = map[int32]string{ + 0: "IDLE", + 1: "STARTING", + 2: "STARTED", + 3: "STOPPING", + 4: "FATAL", + } + ServiceStatus_Type_value = map[string]int32{ + "IDLE": 0, + "STARTING": 1, + "STARTED": 2, + "STOPPING": 3, + "FATAL": 4, + } +) + +func (x ServiceStatus_Type) Enum() *ServiceStatus_Type { + p := new(ServiceStatus_Type) + *p = x + return p +} + +func (x ServiceStatus_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ServiceStatus_Type) Descriptor() protoreflect.EnumDescriptor { + return file_daemon_started_service_proto_enumTypes[2].Descriptor() +} + +func (ServiceStatus_Type) Type() protoreflect.EnumType { + return &file_daemon_started_service_proto_enumTypes[2] +} + +func (x ServiceStatus_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ServiceStatus_Type.Descriptor instead. +func (ServiceStatus_Type) EnumDescriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{0, 0} +} + +type DebugCrashRequest_Type int32 + +const ( + DebugCrashRequest_GO DebugCrashRequest_Type = 0 + DebugCrashRequest_NATIVE DebugCrashRequest_Type = 1 +) + +// Enum value maps for DebugCrashRequest_Type. +var ( + DebugCrashRequest_Type_name = map[int32]string{ + 0: "GO", + 1: "NATIVE", + } + DebugCrashRequest_Type_value = map[string]int32{ + "GO": 0, + "NATIVE": 1, + } +) + +func (x DebugCrashRequest_Type) Enum() *DebugCrashRequest_Type { + p := new(DebugCrashRequest_Type) + *p = x + return p +} + +func (x DebugCrashRequest_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (DebugCrashRequest_Type) Descriptor() protoreflect.EnumDescriptor { + return file_daemon_started_service_proto_enumTypes[3].Descriptor() +} + +func (DebugCrashRequest_Type) Type() protoreflect.EnumType { + return &file_daemon_started_service_proto_enumTypes[3] +} + +func (x DebugCrashRequest_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use DebugCrashRequest_Type.Descriptor instead. +func (DebugCrashRequest_Type) EnumDescriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{16, 0} +} + +type ServiceStatus struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status ServiceStatus_Type `protobuf:"varint,1,opt,name=status,proto3,enum=daemon.ServiceStatus_Type" json:"status,omitempty"` + ErrorMessage string `protobuf:"bytes,2,opt,name=errorMessage,proto3" json:"errorMessage,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ServiceStatus) Reset() { + *x = ServiceStatus{} + mi := &file_daemon_started_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ServiceStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ServiceStatus) ProtoMessage() {} + +func (x *ServiceStatus) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ServiceStatus.ProtoReflect.Descriptor instead. +func (*ServiceStatus) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{0} +} + +func (x *ServiceStatus) GetStatus() ServiceStatus_Type { + if x != nil { + return x.Status + } + return ServiceStatus_IDLE +} + +func (x *ServiceStatus) GetErrorMessage() string { + if x != nil { + return x.ErrorMessage + } + return "" +} + +type ReloadServiceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + NewProfileContent string `protobuf:"bytes,1,opt,name=newProfileContent,proto3" json:"newProfileContent,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReloadServiceRequest) Reset() { + *x = ReloadServiceRequest{} + mi := &file_daemon_started_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReloadServiceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReloadServiceRequest) ProtoMessage() {} + +func (x *ReloadServiceRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReloadServiceRequest.ProtoReflect.Descriptor instead. +func (*ReloadServiceRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{1} +} + +func (x *ReloadServiceRequest) GetNewProfileContent() string { + if x != nil { + return x.NewProfileContent + } + return "" +} + +type SubscribeStatusRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Interval int64 `protobuf:"varint,1,opt,name=interval,proto3" json:"interval,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubscribeStatusRequest) Reset() { + *x = SubscribeStatusRequest{} + mi := &file_daemon_started_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubscribeStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubscribeStatusRequest) ProtoMessage() {} + +func (x *SubscribeStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubscribeStatusRequest.ProtoReflect.Descriptor instead. +func (*SubscribeStatusRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{2} +} + +func (x *SubscribeStatusRequest) GetInterval() int64 { + if x != nil { + return x.Interval + } + return 0 +} + +type Log struct { + state protoimpl.MessageState `protogen:"open.v1"` + Messages []*Log_Message `protobuf:"bytes,1,rep,name=messages,proto3" json:"messages,omitempty"` + Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Log) Reset() { + *x = Log{} + mi := &file_daemon_started_service_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Log) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Log) ProtoMessage() {} + +func (x *Log) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Log.ProtoReflect.Descriptor instead. +func (*Log) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{3} +} + +func (x *Log) GetMessages() []*Log_Message { + if x != nil { + return x.Messages + } + return nil +} + +func (x *Log) GetReset_() bool { + if x != nil { + return x.Reset_ + } + return false +} + +type DefaultLogLevel struct { + state protoimpl.MessageState `protogen:"open.v1"` + Level LogLevel `protobuf:"varint,1,opt,name=level,proto3,enum=daemon.LogLevel" json:"level,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DefaultLogLevel) Reset() { + *x = DefaultLogLevel{} + mi := &file_daemon_started_service_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DefaultLogLevel) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DefaultLogLevel) ProtoMessage() {} + +func (x *DefaultLogLevel) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DefaultLogLevel.ProtoReflect.Descriptor instead. +func (*DefaultLogLevel) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{4} +} + +func (x *DefaultLogLevel) GetLevel() LogLevel { + if x != nil { + return x.Level + } + return LogLevel_PANIC +} + +type Status struct { + state protoimpl.MessageState `protogen:"open.v1"` + Memory uint64 `protobuf:"varint,1,opt,name=memory,proto3" json:"memory,omitempty"` + Goroutines int32 `protobuf:"varint,2,opt,name=goroutines,proto3" json:"goroutines,omitempty"` + ConnectionsIn int32 `protobuf:"varint,3,opt,name=connectionsIn,proto3" json:"connectionsIn,omitempty"` + ConnectionsOut int32 `protobuf:"varint,4,opt,name=connectionsOut,proto3" json:"connectionsOut,omitempty"` + TrafficAvailable bool `protobuf:"varint,5,opt,name=trafficAvailable,proto3" json:"trafficAvailable,omitempty"` + Uplink int64 `protobuf:"varint,6,opt,name=uplink,proto3" json:"uplink,omitempty"` + Downlink int64 `protobuf:"varint,7,opt,name=downlink,proto3" json:"downlink,omitempty"` + UplinkTotal int64 `protobuf:"varint,8,opt,name=uplinkTotal,proto3" json:"uplinkTotal,omitempty"` + DownlinkTotal int64 `protobuf:"varint,9,opt,name=downlinkTotal,proto3" json:"downlinkTotal,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Status) Reset() { + *x = Status{} + mi := &file_daemon_started_service_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Status) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Status) ProtoMessage() {} + +func (x *Status) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Status.ProtoReflect.Descriptor instead. +func (*Status) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{5} +} + +func (x *Status) GetMemory() uint64 { + if x != nil { + return x.Memory + } + return 0 +} + +func (x *Status) GetGoroutines() int32 { + if x != nil { + return x.Goroutines + } + return 0 +} + +func (x *Status) GetConnectionsIn() int32 { + if x != nil { + return x.ConnectionsIn + } + return 0 +} + +func (x *Status) GetConnectionsOut() int32 { + if x != nil { + return x.ConnectionsOut + } + return 0 +} + +func (x *Status) GetTrafficAvailable() bool { + if x != nil { + return x.TrafficAvailable + } + return false +} + +func (x *Status) GetUplink() int64 { + if x != nil { + return x.Uplink + } + return 0 +} + +func (x *Status) GetDownlink() int64 { + if x != nil { + return x.Downlink + } + return 0 +} + +func (x *Status) GetUplinkTotal() int64 { + if x != nil { + return x.UplinkTotal + } + return 0 +} + +func (x *Status) GetDownlinkTotal() int64 { + if x != nil { + return x.DownlinkTotal + } + return 0 +} + +type Groups struct { + state protoimpl.MessageState `protogen:"open.v1"` + Group []*Group `protobuf:"bytes,1,rep,name=group,proto3" json:"group,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Groups) Reset() { + *x = Groups{} + mi := &file_daemon_started_service_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Groups) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Groups) ProtoMessage() {} + +func (x *Groups) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Groups.ProtoReflect.Descriptor instead. +func (*Groups) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{6} +} + +func (x *Groups) GetGroup() []*Group { + if x != nil { + return x.Group + } + return nil +} + +type Group struct { + state protoimpl.MessageState `protogen:"open.v1"` + Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` + Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` + Selectable bool `protobuf:"varint,3,opt,name=selectable,proto3" json:"selectable,omitempty"` + Selected string `protobuf:"bytes,4,opt,name=selected,proto3" json:"selected,omitempty"` + IsExpand bool `protobuf:"varint,5,opt,name=isExpand,proto3" json:"isExpand,omitempty"` + Items []*GroupItem `protobuf:"bytes,6,rep,name=items,proto3" json:"items,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Group) Reset() { + *x = Group{} + mi := &file_daemon_started_service_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Group) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Group) ProtoMessage() {} + +func (x *Group) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Group.ProtoReflect.Descriptor instead. +func (*Group) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{7} +} + +func (x *Group) GetTag() string { + if x != nil { + return x.Tag + } + return "" +} + +func (x *Group) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *Group) GetSelectable() bool { + if x != nil { + return x.Selectable + } + return false +} + +func (x *Group) GetSelected() string { + if x != nil { + return x.Selected + } + return "" +} + +func (x *Group) GetIsExpand() bool { + if x != nil { + return x.IsExpand + } + return false +} + +func (x *Group) GetItems() []*GroupItem { + if x != nil { + return x.Items + } + return nil +} + +type GroupItem struct { + state protoimpl.MessageState `protogen:"open.v1"` + Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` + Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` + UrlTestTime int64 `protobuf:"varint,3,opt,name=urlTestTime,proto3" json:"urlTestTime,omitempty"` + UrlTestDelay int32 `protobuf:"varint,4,opt,name=urlTestDelay,proto3" json:"urlTestDelay,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GroupItem) Reset() { + *x = GroupItem{} + mi := &file_daemon_started_service_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GroupItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GroupItem) ProtoMessage() {} + +func (x *GroupItem) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GroupItem.ProtoReflect.Descriptor instead. +func (*GroupItem) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{8} +} + +func (x *GroupItem) GetTag() string { + if x != nil { + return x.Tag + } + return "" +} + +func (x *GroupItem) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *GroupItem) GetUrlTestTime() int64 { + if x != nil { + return x.UrlTestTime + } + return 0 +} + +func (x *GroupItem) GetUrlTestDelay() int32 { + if x != nil { + return x.UrlTestDelay + } + return 0 +} + +type URLTestRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + OutboundTag string `protobuf:"bytes,1,opt,name=outboundTag,proto3" json:"outboundTag,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *URLTestRequest) Reset() { + *x = URLTestRequest{} + mi := &file_daemon_started_service_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *URLTestRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*URLTestRequest) ProtoMessage() {} + +func (x *URLTestRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use URLTestRequest.ProtoReflect.Descriptor instead. +func (*URLTestRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{9} +} + +func (x *URLTestRequest) GetOutboundTag() string { + if x != nil { + return x.OutboundTag + } + return "" +} + +type SelectOutboundRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + GroupTag string `protobuf:"bytes,1,opt,name=groupTag,proto3" json:"groupTag,omitempty"` + OutboundTag string `protobuf:"bytes,2,opt,name=outboundTag,proto3" json:"outboundTag,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SelectOutboundRequest) Reset() { + *x = SelectOutboundRequest{} + mi := &file_daemon_started_service_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SelectOutboundRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SelectOutboundRequest) ProtoMessage() {} + +func (x *SelectOutboundRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SelectOutboundRequest.ProtoReflect.Descriptor instead. +func (*SelectOutboundRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{10} +} + +func (x *SelectOutboundRequest) GetGroupTag() string { + if x != nil { + return x.GroupTag + } + return "" +} + +func (x *SelectOutboundRequest) GetOutboundTag() string { + if x != nil { + return x.OutboundTag + } + return "" +} + +type SetGroupExpandRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + GroupTag string `protobuf:"bytes,1,opt,name=groupTag,proto3" json:"groupTag,omitempty"` + IsExpand bool `protobuf:"varint,2,opt,name=isExpand,proto3" json:"isExpand,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetGroupExpandRequest) Reset() { + *x = SetGroupExpandRequest{} + mi := &file_daemon_started_service_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetGroupExpandRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetGroupExpandRequest) ProtoMessage() {} + +func (x *SetGroupExpandRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetGroupExpandRequest.ProtoReflect.Descriptor instead. +func (*SetGroupExpandRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{11} +} + +func (x *SetGroupExpandRequest) GetGroupTag() string { + if x != nil { + return x.GroupTag + } + return "" +} + +func (x *SetGroupExpandRequest) GetIsExpand() bool { + if x != nil { + return x.IsExpand + } + return false +} + +type ClashMode struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mode string `protobuf:"bytes,3,opt,name=mode,proto3" json:"mode,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClashMode) Reset() { + *x = ClashMode{} + mi := &file_daemon_started_service_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClashMode) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClashMode) ProtoMessage() {} + +func (x *ClashMode) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClashMode.ProtoReflect.Descriptor instead. +func (*ClashMode) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{12} +} + +func (x *ClashMode) GetMode() string { + if x != nil { + return x.Mode + } + return "" +} + +type ClashModeStatus struct { + state protoimpl.MessageState `protogen:"open.v1"` + ModeList []string `protobuf:"bytes,1,rep,name=modeList,proto3" json:"modeList,omitempty"` + CurrentMode string `protobuf:"bytes,2,opt,name=currentMode,proto3" json:"currentMode,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClashModeStatus) Reset() { + *x = ClashModeStatus{} + mi := &file_daemon_started_service_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClashModeStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClashModeStatus) ProtoMessage() {} + +func (x *ClashModeStatus) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClashModeStatus.ProtoReflect.Descriptor instead. +func (*ClashModeStatus) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{13} +} + +func (x *ClashModeStatus) GetModeList() []string { + if x != nil { + return x.ModeList + } + return nil +} + +func (x *ClashModeStatus) GetCurrentMode() string { + if x != nil { + return x.CurrentMode + } + return "" +} + +type SystemProxyStatus struct { + state protoimpl.MessageState `protogen:"open.v1"` + Available bool `protobuf:"varint,1,opt,name=available,proto3" json:"available,omitempty"` + Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SystemProxyStatus) Reset() { + *x = SystemProxyStatus{} + mi := &file_daemon_started_service_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SystemProxyStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SystemProxyStatus) ProtoMessage() {} + +func (x *SystemProxyStatus) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SystemProxyStatus.ProtoReflect.Descriptor instead. +func (*SystemProxyStatus) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{14} +} + +func (x *SystemProxyStatus) GetAvailable() bool { + if x != nil { + return x.Available + } + return false +} + +func (x *SystemProxyStatus) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +type SetSystemProxyEnabledRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetSystemProxyEnabledRequest) Reset() { + *x = SetSystemProxyEnabledRequest{} + mi := &file_daemon_started_service_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetSystemProxyEnabledRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetSystemProxyEnabledRequest) ProtoMessage() {} + +func (x *SetSystemProxyEnabledRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetSystemProxyEnabledRequest.ProtoReflect.Descriptor instead. +func (*SetSystemProxyEnabledRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{15} +} + +func (x *SetSystemProxyEnabledRequest) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +type DebugCrashRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Type DebugCrashRequest_Type `protobuf:"varint,1,opt,name=type,proto3,enum=daemon.DebugCrashRequest_Type" json:"type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DebugCrashRequest) Reset() { + *x = DebugCrashRequest{} + mi := &file_daemon_started_service_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DebugCrashRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DebugCrashRequest) ProtoMessage() {} + +func (x *DebugCrashRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DebugCrashRequest.ProtoReflect.Descriptor instead. +func (*DebugCrashRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{16} +} + +func (x *DebugCrashRequest) GetType() DebugCrashRequest_Type { + if x != nil { + return x.Type + } + return DebugCrashRequest_GO +} + +type SubscribeConnectionsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Interval int64 `protobuf:"varint,1,opt,name=interval,proto3" json:"interval,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubscribeConnectionsRequest) Reset() { + *x = SubscribeConnectionsRequest{} + mi := &file_daemon_started_service_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubscribeConnectionsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubscribeConnectionsRequest) ProtoMessage() {} + +func (x *SubscribeConnectionsRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubscribeConnectionsRequest.ProtoReflect.Descriptor instead. +func (*SubscribeConnectionsRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{17} +} + +func (x *SubscribeConnectionsRequest) GetInterval() int64 { + if x != nil { + return x.Interval + } + return 0 +} + +type ConnectionEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + Type ConnectionEventType `protobuf:"varint,1,opt,name=type,proto3,enum=daemon.ConnectionEventType" json:"type,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + Connection *Connection `protobuf:"bytes,3,opt,name=connection,proto3" json:"connection,omitempty"` + UplinkDelta int64 `protobuf:"varint,4,opt,name=uplinkDelta,proto3" json:"uplinkDelta,omitempty"` + DownlinkDelta int64 `protobuf:"varint,5,opt,name=downlinkDelta,proto3" json:"downlinkDelta,omitempty"` + ClosedAt int64 `protobuf:"varint,6,opt,name=closedAt,proto3" json:"closedAt,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConnectionEvent) Reset() { + *x = ConnectionEvent{} + mi := &file_daemon_started_service_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConnectionEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectionEvent) ProtoMessage() {} + +func (x *ConnectionEvent) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectionEvent.ProtoReflect.Descriptor instead. +func (*ConnectionEvent) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{18} +} + +func (x *ConnectionEvent) GetType() ConnectionEventType { + if x != nil { + return x.Type + } + return ConnectionEventType_CONNECTION_EVENT_NEW +} + +func (x *ConnectionEvent) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ConnectionEvent) GetConnection() *Connection { + if x != nil { + return x.Connection + } + return nil +} + +func (x *ConnectionEvent) GetUplinkDelta() int64 { + if x != nil { + return x.UplinkDelta + } + return 0 +} + +func (x *ConnectionEvent) GetDownlinkDelta() int64 { + if x != nil { + return x.DownlinkDelta + } + return 0 +} + +func (x *ConnectionEvent) GetClosedAt() int64 { + if x != nil { + return x.ClosedAt + } + return 0 +} + +type ConnectionEvents struct { + state protoimpl.MessageState `protogen:"open.v1"` + Events []*ConnectionEvent `protobuf:"bytes,1,rep,name=events,proto3" json:"events,omitempty"` + Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConnectionEvents) Reset() { + *x = ConnectionEvents{} + mi := &file_daemon_started_service_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConnectionEvents) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectionEvents) ProtoMessage() {} + +func (x *ConnectionEvents) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectionEvents.ProtoReflect.Descriptor instead. +func (*ConnectionEvents) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{19} +} + +func (x *ConnectionEvents) GetEvents() []*ConnectionEvent { + if x != nil { + return x.Events + } + return nil +} + +func (x *ConnectionEvents) GetReset_() bool { + if x != nil { + return x.Reset_ + } + return false +} + +type Connection struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Inbound string `protobuf:"bytes,2,opt,name=inbound,proto3" json:"inbound,omitempty"` + InboundType string `protobuf:"bytes,3,opt,name=inboundType,proto3" json:"inboundType,omitempty"` + IpVersion int32 `protobuf:"varint,4,opt,name=ipVersion,proto3" json:"ipVersion,omitempty"` + Network string `protobuf:"bytes,5,opt,name=network,proto3" json:"network,omitempty"` + Source string `protobuf:"bytes,6,opt,name=source,proto3" json:"source,omitempty"` + Destination string `protobuf:"bytes,7,opt,name=destination,proto3" json:"destination,omitempty"` + Domain string `protobuf:"bytes,8,opt,name=domain,proto3" json:"domain,omitempty"` + Protocol string `protobuf:"bytes,9,opt,name=protocol,proto3" json:"protocol,omitempty"` + User string `protobuf:"bytes,10,opt,name=user,proto3" json:"user,omitempty"` + FromOutbound string `protobuf:"bytes,11,opt,name=fromOutbound,proto3" json:"fromOutbound,omitempty"` + CreatedAt int64 `protobuf:"varint,12,opt,name=createdAt,proto3" json:"createdAt,omitempty"` + ClosedAt int64 `protobuf:"varint,13,opt,name=closedAt,proto3" json:"closedAt,omitempty"` + Uplink int64 `protobuf:"varint,14,opt,name=uplink,proto3" json:"uplink,omitempty"` + Downlink int64 `protobuf:"varint,15,opt,name=downlink,proto3" json:"downlink,omitempty"` + UplinkTotal int64 `protobuf:"varint,16,opt,name=uplinkTotal,proto3" json:"uplinkTotal,omitempty"` + DownlinkTotal int64 `protobuf:"varint,17,opt,name=downlinkTotal,proto3" json:"downlinkTotal,omitempty"` + Rule string `protobuf:"bytes,18,opt,name=rule,proto3" json:"rule,omitempty"` + Outbound string `protobuf:"bytes,19,opt,name=outbound,proto3" json:"outbound,omitempty"` + OutboundType string `protobuf:"bytes,20,opt,name=outboundType,proto3" json:"outboundType,omitempty"` + ChainList []string `protobuf:"bytes,21,rep,name=chainList,proto3" json:"chainList,omitempty"` + ProcessInfo *ProcessInfo `protobuf:"bytes,22,opt,name=processInfo,proto3" json:"processInfo,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Connection) Reset() { + *x = Connection{} + mi := &file_daemon_started_service_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Connection) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Connection) ProtoMessage() {} + +func (x *Connection) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Connection.ProtoReflect.Descriptor instead. +func (*Connection) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{20} +} + +func (x *Connection) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Connection) GetInbound() string { + if x != nil { + return x.Inbound + } + return "" +} + +func (x *Connection) GetInboundType() string { + if x != nil { + return x.InboundType + } + return "" +} + +func (x *Connection) GetIpVersion() int32 { + if x != nil { + return x.IpVersion + } + return 0 +} + +func (x *Connection) GetNetwork() string { + if x != nil { + return x.Network + } + return "" +} + +func (x *Connection) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +func (x *Connection) GetDestination() string { + if x != nil { + return x.Destination + } + return "" +} + +func (x *Connection) GetDomain() string { + if x != nil { + return x.Domain + } + return "" +} + +func (x *Connection) GetProtocol() string { + if x != nil { + return x.Protocol + } + return "" +} + +func (x *Connection) GetUser() string { + if x != nil { + return x.User + } + return "" +} + +func (x *Connection) GetFromOutbound() string { + if x != nil { + return x.FromOutbound + } + return "" +} + +func (x *Connection) GetCreatedAt() int64 { + if x != nil { + return x.CreatedAt + } + return 0 +} + +func (x *Connection) GetClosedAt() int64 { + if x != nil { + return x.ClosedAt + } + return 0 +} + +func (x *Connection) GetUplink() int64 { + if x != nil { + return x.Uplink + } + return 0 +} + +func (x *Connection) GetDownlink() int64 { + if x != nil { + return x.Downlink + } + return 0 +} + +func (x *Connection) GetUplinkTotal() int64 { + if x != nil { + return x.UplinkTotal + } + return 0 +} + +func (x *Connection) GetDownlinkTotal() int64 { + if x != nil { + return x.DownlinkTotal + } + return 0 +} + +func (x *Connection) GetRule() string { + if x != nil { + return x.Rule + } + return "" +} + +func (x *Connection) GetOutbound() string { + if x != nil { + return x.Outbound + } + return "" +} + +func (x *Connection) GetOutboundType() string { + if x != nil { + return x.OutboundType + } + return "" +} + +func (x *Connection) GetChainList() []string { + if x != nil { + return x.ChainList + } + return nil +} + +func (x *Connection) GetProcessInfo() *ProcessInfo { + if x != nil { + return x.ProcessInfo + } + return nil +} + +type ProcessInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + ProcessId uint32 `protobuf:"varint,1,opt,name=processId,proto3" json:"processId,omitempty"` + UserId int32 `protobuf:"varint,2,opt,name=userId,proto3" json:"userId,omitempty"` + UserName string `protobuf:"bytes,3,opt,name=userName,proto3" json:"userName,omitempty"` + ProcessPath string `protobuf:"bytes,4,opt,name=processPath,proto3" json:"processPath,omitempty"` + PackageNames []string `protobuf:"bytes,5,rep,name=packageNames,proto3" json:"packageNames,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProcessInfo) Reset() { + *x = ProcessInfo{} + mi := &file_daemon_started_service_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProcessInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProcessInfo) ProtoMessage() {} + +func (x *ProcessInfo) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProcessInfo.ProtoReflect.Descriptor instead. +func (*ProcessInfo) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{21} +} + +func (x *ProcessInfo) GetProcessId() uint32 { + if x != nil { + return x.ProcessId + } + return 0 +} + +func (x *ProcessInfo) GetUserId() int32 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *ProcessInfo) GetUserName() string { + if x != nil { + return x.UserName + } + return "" +} + +func (x *ProcessInfo) GetProcessPath() string { + if x != nil { + return x.ProcessPath + } + return "" +} + +func (x *ProcessInfo) GetPackageNames() []string { + if x != nil { + return x.PackageNames + } + return nil +} + +type CloseConnectionRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CloseConnectionRequest) Reset() { + *x = CloseConnectionRequest{} + mi := &file_daemon_started_service_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CloseConnectionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CloseConnectionRequest) ProtoMessage() {} + +func (x *CloseConnectionRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CloseConnectionRequest.ProtoReflect.Descriptor instead. +func (*CloseConnectionRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{22} +} + +func (x *CloseConnectionRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type DeprecatedWarnings struct { + state protoimpl.MessageState `protogen:"open.v1"` + Warnings []*DeprecatedWarning `protobuf:"bytes,1,rep,name=warnings,proto3" json:"warnings,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeprecatedWarnings) Reset() { + *x = DeprecatedWarnings{} + mi := &file_daemon_started_service_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeprecatedWarnings) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeprecatedWarnings) ProtoMessage() {} + +func (x *DeprecatedWarnings) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeprecatedWarnings.ProtoReflect.Descriptor instead. +func (*DeprecatedWarnings) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{23} +} + +func (x *DeprecatedWarnings) GetWarnings() []*DeprecatedWarning { + if x != nil { + return x.Warnings + } + return nil +} + +type DeprecatedWarning struct { + state protoimpl.MessageState `protogen:"open.v1"` + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + Impending bool `protobuf:"varint,2,opt,name=impending,proto3" json:"impending,omitempty"` + MigrationLink string `protobuf:"bytes,3,opt,name=migrationLink,proto3" json:"migrationLink,omitempty"` + Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` + DeprecatedVersion string `protobuf:"bytes,5,opt,name=deprecatedVersion,proto3" json:"deprecatedVersion,omitempty"` + ScheduledVersion string `protobuf:"bytes,6,opt,name=scheduledVersion,proto3" json:"scheduledVersion,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeprecatedWarning) Reset() { + *x = DeprecatedWarning{} + mi := &file_daemon_started_service_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeprecatedWarning) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeprecatedWarning) ProtoMessage() {} + +func (x *DeprecatedWarning) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeprecatedWarning.ProtoReflect.Descriptor instead. +func (*DeprecatedWarning) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{24} +} + +func (x *DeprecatedWarning) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *DeprecatedWarning) GetImpending() bool { + if x != nil { + return x.Impending + } + return false +} + +func (x *DeprecatedWarning) GetMigrationLink() string { + if x != nil { + return x.MigrationLink + } + return "" +} + +func (x *DeprecatedWarning) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *DeprecatedWarning) GetDeprecatedVersion() string { + if x != nil { + return x.DeprecatedVersion + } + return "" +} + +func (x *DeprecatedWarning) GetScheduledVersion() string { + if x != nil { + return x.ScheduledVersion + } + return "" +} + +type StartedAt struct { + state protoimpl.MessageState `protogen:"open.v1"` + StartedAt int64 `protobuf:"varint,1,opt,name=startedAt,proto3" json:"startedAt,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StartedAt) Reset() { + *x = StartedAt{} + mi := &file_daemon_started_service_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StartedAt) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StartedAt) ProtoMessage() {} + +func (x *StartedAt) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StartedAt.ProtoReflect.Descriptor instead. +func (*StartedAt) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{25} +} + +func (x *StartedAt) GetStartedAt() int64 { + if x != nil { + return x.StartedAt + } + return 0 +} + +type OutboundList struct { + state protoimpl.MessageState `protogen:"open.v1"` + Outbounds []*GroupItem `protobuf:"bytes,1,rep,name=outbounds,proto3" json:"outbounds,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OutboundList) Reset() { + *x = OutboundList{} + mi := &file_daemon_started_service_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OutboundList) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OutboundList) ProtoMessage() {} + +func (x *OutboundList) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OutboundList.ProtoReflect.Descriptor instead. +func (*OutboundList) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{26} +} + +func (x *OutboundList) GetOutbounds() []*GroupItem { + if x != nil { + return x.Outbounds + } + return nil +} + +type NetworkQualityTestRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ConfigURL string `protobuf:"bytes,1,opt,name=configURL,proto3" json:"configURL,omitempty"` + OutboundTag string `protobuf:"bytes,2,opt,name=outboundTag,proto3" json:"outboundTag,omitempty"` + Serial bool `protobuf:"varint,3,opt,name=serial,proto3" json:"serial,omitempty"` + MaxRuntimeSeconds int32 `protobuf:"varint,4,opt,name=maxRuntimeSeconds,proto3" json:"maxRuntimeSeconds,omitempty"` + Http3 bool `protobuf:"varint,5,opt,name=http3,proto3" json:"http3,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetworkQualityTestRequest) Reset() { + *x = NetworkQualityTestRequest{} + mi := &file_daemon_started_service_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetworkQualityTestRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetworkQualityTestRequest) ProtoMessage() {} + +func (x *NetworkQualityTestRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NetworkQualityTestRequest.ProtoReflect.Descriptor instead. +func (*NetworkQualityTestRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{27} +} + +func (x *NetworkQualityTestRequest) GetConfigURL() string { + if x != nil { + return x.ConfigURL + } + return "" +} + +func (x *NetworkQualityTestRequest) GetOutboundTag() string { + if x != nil { + return x.OutboundTag + } + return "" +} + +func (x *NetworkQualityTestRequest) GetSerial() bool { + if x != nil { + return x.Serial + } + return false +} + +func (x *NetworkQualityTestRequest) GetMaxRuntimeSeconds() int32 { + if x != nil { + return x.MaxRuntimeSeconds + } + return 0 +} + +func (x *NetworkQualityTestRequest) GetHttp3() bool { + if x != nil { + return x.Http3 + } + return false +} + +type NetworkQualityTestProgress struct { + state protoimpl.MessageState `protogen:"open.v1"` + Phase int32 `protobuf:"varint,1,opt,name=phase,proto3" json:"phase,omitempty"` + DownloadCapacity int64 `protobuf:"varint,2,opt,name=downloadCapacity,proto3" json:"downloadCapacity,omitempty"` + UploadCapacity int64 `protobuf:"varint,3,opt,name=uploadCapacity,proto3" json:"uploadCapacity,omitempty"` + DownloadRPM int32 `protobuf:"varint,4,opt,name=downloadRPM,proto3" json:"downloadRPM,omitempty"` + UploadRPM int32 `protobuf:"varint,5,opt,name=uploadRPM,proto3" json:"uploadRPM,omitempty"` + IdleLatencyMs int32 `protobuf:"varint,6,opt,name=idleLatencyMs,proto3" json:"idleLatencyMs,omitempty"` + ElapsedMs int64 `protobuf:"varint,7,opt,name=elapsedMs,proto3" json:"elapsedMs,omitempty"` + IsFinal bool `protobuf:"varint,8,opt,name=isFinal,proto3" json:"isFinal,omitempty"` + Error string `protobuf:"bytes,9,opt,name=error,proto3" json:"error,omitempty"` + DownloadCapacityAccuracy int32 `protobuf:"varint,10,opt,name=downloadCapacityAccuracy,proto3" json:"downloadCapacityAccuracy,omitempty"` + UploadCapacityAccuracy int32 `protobuf:"varint,11,opt,name=uploadCapacityAccuracy,proto3" json:"uploadCapacityAccuracy,omitempty"` + DownloadRPMAccuracy int32 `protobuf:"varint,12,opt,name=downloadRPMAccuracy,proto3" json:"downloadRPMAccuracy,omitempty"` + UploadRPMAccuracy int32 `protobuf:"varint,13,opt,name=uploadRPMAccuracy,proto3" json:"uploadRPMAccuracy,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetworkQualityTestProgress) Reset() { + *x = NetworkQualityTestProgress{} + mi := &file_daemon_started_service_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetworkQualityTestProgress) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetworkQualityTestProgress) ProtoMessage() {} + +func (x *NetworkQualityTestProgress) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[28] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NetworkQualityTestProgress.ProtoReflect.Descriptor instead. +func (*NetworkQualityTestProgress) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{28} +} + +func (x *NetworkQualityTestProgress) GetPhase() int32 { + if x != nil { + return x.Phase + } + return 0 +} + +func (x *NetworkQualityTestProgress) GetDownloadCapacity() int64 { + if x != nil { + return x.DownloadCapacity + } + return 0 +} + +func (x *NetworkQualityTestProgress) GetUploadCapacity() int64 { + if x != nil { + return x.UploadCapacity + } + return 0 +} + +func (x *NetworkQualityTestProgress) GetDownloadRPM() int32 { + if x != nil { + return x.DownloadRPM + } + return 0 +} + +func (x *NetworkQualityTestProgress) GetUploadRPM() int32 { + if x != nil { + return x.UploadRPM + } + return 0 +} + +func (x *NetworkQualityTestProgress) GetIdleLatencyMs() int32 { + if x != nil { + return x.IdleLatencyMs + } + return 0 +} + +func (x *NetworkQualityTestProgress) GetElapsedMs() int64 { + if x != nil { + return x.ElapsedMs + } + return 0 +} + +func (x *NetworkQualityTestProgress) GetIsFinal() bool { + if x != nil { + return x.IsFinal + } + return false +} + +func (x *NetworkQualityTestProgress) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +func (x *NetworkQualityTestProgress) GetDownloadCapacityAccuracy() int32 { + if x != nil { + return x.DownloadCapacityAccuracy + } + return 0 +} + +func (x *NetworkQualityTestProgress) GetUploadCapacityAccuracy() int32 { + if x != nil { + return x.UploadCapacityAccuracy + } + return 0 +} + +func (x *NetworkQualityTestProgress) GetDownloadRPMAccuracy() int32 { + if x != nil { + return x.DownloadRPMAccuracy + } + return 0 +} + +func (x *NetworkQualityTestProgress) GetUploadRPMAccuracy() int32 { + if x != nil { + return x.UploadRPMAccuracy + } + return 0 +} + +type STUNTestRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Server string `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"` + OutboundTag string `protobuf:"bytes,2,opt,name=outboundTag,proto3" json:"outboundTag,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *STUNTestRequest) Reset() { + *x = STUNTestRequest{} + mi := &file_daemon_started_service_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *STUNTestRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*STUNTestRequest) ProtoMessage() {} + +func (x *STUNTestRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[29] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use STUNTestRequest.ProtoReflect.Descriptor instead. +func (*STUNTestRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{29} +} + +func (x *STUNTestRequest) GetServer() string { + if x != nil { + return x.Server + } + return "" +} + +func (x *STUNTestRequest) GetOutboundTag() string { + if x != nil { + return x.OutboundTag + } + return "" +} + +type STUNTestProgress struct { + state protoimpl.MessageState `protogen:"open.v1"` + Phase int32 `protobuf:"varint,1,opt,name=phase,proto3" json:"phase,omitempty"` + ExternalAddr string `protobuf:"bytes,2,opt,name=externalAddr,proto3" json:"externalAddr,omitempty"` + LatencyMs int32 `protobuf:"varint,3,opt,name=latencyMs,proto3" json:"latencyMs,omitempty"` + NatMapping int32 `protobuf:"varint,4,opt,name=natMapping,proto3" json:"natMapping,omitempty"` + NatFiltering int32 `protobuf:"varint,5,opt,name=natFiltering,proto3" json:"natFiltering,omitempty"` + IsFinal bool `protobuf:"varint,6,opt,name=isFinal,proto3" json:"isFinal,omitempty"` + Error string `protobuf:"bytes,7,opt,name=error,proto3" json:"error,omitempty"` + NatTypeSupported bool `protobuf:"varint,8,opt,name=natTypeSupported,proto3" json:"natTypeSupported,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *STUNTestProgress) Reset() { + *x = STUNTestProgress{} + mi := &file_daemon_started_service_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *STUNTestProgress) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*STUNTestProgress) ProtoMessage() {} + +func (x *STUNTestProgress) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[30] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use STUNTestProgress.ProtoReflect.Descriptor instead. +func (*STUNTestProgress) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{30} +} + +func (x *STUNTestProgress) GetPhase() int32 { + if x != nil { + return x.Phase + } + return 0 +} + +func (x *STUNTestProgress) GetExternalAddr() string { + if x != nil { + return x.ExternalAddr + } + return "" +} + +func (x *STUNTestProgress) GetLatencyMs() int32 { + if x != nil { + return x.LatencyMs + } + return 0 +} + +func (x *STUNTestProgress) GetNatMapping() int32 { + if x != nil { + return x.NatMapping + } + return 0 +} + +func (x *STUNTestProgress) GetNatFiltering() int32 { + if x != nil { + return x.NatFiltering + } + return 0 +} + +func (x *STUNTestProgress) GetIsFinal() bool { + if x != nil { + return x.IsFinal + } + return false +} + +func (x *STUNTestProgress) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +func (x *STUNTestProgress) GetNatTypeSupported() bool { + if x != nil { + return x.NatTypeSupported + } + return false +} + +type TailscaleStatusUpdate struct { + state protoimpl.MessageState `protogen:"open.v1"` + Endpoints []*TailscaleEndpointStatus `protobuf:"bytes,1,rep,name=endpoints,proto3" json:"endpoints,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TailscaleStatusUpdate) Reset() { + *x = TailscaleStatusUpdate{} + mi := &file_daemon_started_service_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TailscaleStatusUpdate) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TailscaleStatusUpdate) ProtoMessage() {} + +func (x *TailscaleStatusUpdate) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[31] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TailscaleStatusUpdate.ProtoReflect.Descriptor instead. +func (*TailscaleStatusUpdate) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{31} +} + +func (x *TailscaleStatusUpdate) GetEndpoints() []*TailscaleEndpointStatus { + if x != nil { + return x.Endpoints + } + return nil +} + +type TailscaleEndpointStatus struct { + state protoimpl.MessageState `protogen:"open.v1"` + EndpointTag string `protobuf:"bytes,1,opt,name=endpointTag,proto3" json:"endpointTag,omitempty"` + BackendState string `protobuf:"bytes,2,opt,name=backendState,proto3" json:"backendState,omitempty"` + AuthURL string `protobuf:"bytes,3,opt,name=authURL,proto3" json:"authURL,omitempty"` + NetworkName string `protobuf:"bytes,4,opt,name=networkName,proto3" json:"networkName,omitempty"` + MagicDNSSuffix string `protobuf:"bytes,5,opt,name=magicDNSSuffix,proto3" json:"magicDNSSuffix,omitempty"` + Self *TailscalePeer `protobuf:"bytes,6,opt,name=self,proto3" json:"self,omitempty"` + UserGroups []*TailscaleUserGroup `protobuf:"bytes,7,rep,name=userGroups,proto3" json:"userGroups,omitempty"` + ExitNode *TailscalePeer `protobuf:"bytes,8,opt,name=exitNode,proto3" json:"exitNode,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TailscaleEndpointStatus) Reset() { + *x = TailscaleEndpointStatus{} + mi := &file_daemon_started_service_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TailscaleEndpointStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TailscaleEndpointStatus) ProtoMessage() {} + +func (x *TailscaleEndpointStatus) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[32] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TailscaleEndpointStatus.ProtoReflect.Descriptor instead. +func (*TailscaleEndpointStatus) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{32} +} + +func (x *TailscaleEndpointStatus) GetEndpointTag() string { + if x != nil { + return x.EndpointTag + } + return "" +} + +func (x *TailscaleEndpointStatus) GetBackendState() string { + if x != nil { + return x.BackendState + } + return "" +} + +func (x *TailscaleEndpointStatus) GetAuthURL() string { + if x != nil { + return x.AuthURL + } + return "" +} + +func (x *TailscaleEndpointStatus) GetNetworkName() string { + if x != nil { + return x.NetworkName + } + return "" +} + +func (x *TailscaleEndpointStatus) GetMagicDNSSuffix() string { + if x != nil { + return x.MagicDNSSuffix + } + return "" +} + +func (x *TailscaleEndpointStatus) GetSelf() *TailscalePeer { + if x != nil { + return x.Self + } + return nil +} + +func (x *TailscaleEndpointStatus) GetUserGroups() []*TailscaleUserGroup { + if x != nil { + return x.UserGroups + } + return nil +} + +func (x *TailscaleEndpointStatus) GetExitNode() *TailscalePeer { + if x != nil { + return x.ExitNode + } + return nil +} + +type TailscaleUserGroup struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserID int64 `protobuf:"varint,1,opt,name=userID,proto3" json:"userID,omitempty"` + LoginName string `protobuf:"bytes,2,opt,name=loginName,proto3" json:"loginName,omitempty"` + DisplayName string `protobuf:"bytes,3,opt,name=displayName,proto3" json:"displayName,omitempty"` + ProfilePicURL string `protobuf:"bytes,4,opt,name=profilePicURL,proto3" json:"profilePicURL,omitempty"` + Peers []*TailscalePeer `protobuf:"bytes,5,rep,name=peers,proto3" json:"peers,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TailscaleUserGroup) Reset() { + *x = TailscaleUserGroup{} + mi := &file_daemon_started_service_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TailscaleUserGroup) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TailscaleUserGroup) ProtoMessage() {} + +func (x *TailscaleUserGroup) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[33] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TailscaleUserGroup.ProtoReflect.Descriptor instead. +func (*TailscaleUserGroup) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{33} +} + +func (x *TailscaleUserGroup) GetUserID() int64 { + if x != nil { + return x.UserID + } + return 0 +} + +func (x *TailscaleUserGroup) GetLoginName() string { + if x != nil { + return x.LoginName + } + return "" +} + +func (x *TailscaleUserGroup) GetDisplayName() string { + if x != nil { + return x.DisplayName + } + return "" +} + +func (x *TailscaleUserGroup) GetProfilePicURL() string { + if x != nil { + return x.ProfilePicURL + } + return "" +} + +func (x *TailscaleUserGroup) GetPeers() []*TailscalePeer { + if x != nil { + return x.Peers + } + return nil +} + +type TailscalePeer struct { + state protoimpl.MessageState `protogen:"open.v1"` + HostName string `protobuf:"bytes,1,opt,name=hostName,proto3" json:"hostName,omitempty"` + DnsName string `protobuf:"bytes,2,opt,name=dnsName,proto3" json:"dnsName,omitempty"` + Os string `protobuf:"bytes,3,opt,name=os,proto3" json:"os,omitempty"` + TailscaleIPs []string `protobuf:"bytes,4,rep,name=tailscaleIPs,proto3" json:"tailscaleIPs,omitempty"` + Online bool `protobuf:"varint,5,opt,name=online,proto3" json:"online,omitempty"` + ExitNode bool `protobuf:"varint,6,opt,name=exitNode,proto3" json:"exitNode,omitempty"` + ExitNodeOption bool `protobuf:"varint,7,opt,name=exitNodeOption,proto3" json:"exitNodeOption,omitempty"` + Active bool `protobuf:"varint,8,opt,name=active,proto3" json:"active,omitempty"` + RxBytes int64 `protobuf:"varint,9,opt,name=rxBytes,proto3" json:"rxBytes,omitempty"` + TxBytes int64 `protobuf:"varint,10,opt,name=txBytes,proto3" json:"txBytes,omitempty"` + KeyExpiry int64 `protobuf:"varint,11,opt,name=keyExpiry,proto3" json:"keyExpiry,omitempty"` + StableID string `protobuf:"bytes,12,opt,name=stableID,proto3" json:"stableID,omitempty"` + Expired bool `protobuf:"varint,13,opt,name=expired,proto3" json:"expired,omitempty"` + SshHostKeys []string `protobuf:"bytes,14,rep,name=sshHostKeys,proto3" json:"sshHostKeys,omitempty"` + ShareeNode bool `protobuf:"varint,15,opt,name=shareeNode,proto3" json:"shareeNode,omitempty"` + LastSeen int64 `protobuf:"varint,16,opt,name=lastSeen,proto3" json:"lastSeen,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TailscalePeer) Reset() { + *x = TailscalePeer{} + mi := &file_daemon_started_service_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TailscalePeer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TailscalePeer) ProtoMessage() {} + +func (x *TailscalePeer) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[34] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TailscalePeer.ProtoReflect.Descriptor instead. +func (*TailscalePeer) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{34} +} + +func (x *TailscalePeer) GetHostName() string { + if x != nil { + return x.HostName + } + return "" +} + +func (x *TailscalePeer) GetDnsName() string { + if x != nil { + return x.DnsName + } + return "" +} + +func (x *TailscalePeer) GetOs() string { + if x != nil { + return x.Os + } + return "" +} + +func (x *TailscalePeer) GetTailscaleIPs() []string { + if x != nil { + return x.TailscaleIPs + } + return nil +} + +func (x *TailscalePeer) GetOnline() bool { + if x != nil { + return x.Online + } + return false +} + +func (x *TailscalePeer) GetExitNode() bool { + if x != nil { + return x.ExitNode + } + return false +} + +func (x *TailscalePeer) GetExitNodeOption() bool { + if x != nil { + return x.ExitNodeOption + } + return false +} + +func (x *TailscalePeer) GetActive() bool { + if x != nil { + return x.Active + } + return false +} + +func (x *TailscalePeer) GetRxBytes() int64 { + if x != nil { + return x.RxBytes + } + return 0 +} + +func (x *TailscalePeer) GetTxBytes() int64 { + if x != nil { + return x.TxBytes + } + return 0 +} + +func (x *TailscalePeer) GetKeyExpiry() int64 { + if x != nil { + return x.KeyExpiry + } + return 0 +} + +func (x *TailscalePeer) GetStableID() string { + if x != nil { + return x.StableID + } + return "" +} + +func (x *TailscalePeer) GetExpired() bool { + if x != nil { + return x.Expired + } + return false +} + +func (x *TailscalePeer) GetSshHostKeys() []string { + if x != nil { + return x.SshHostKeys + } + return nil +} + +func (x *TailscalePeer) GetShareeNode() bool { + if x != nil { + return x.ShareeNode + } + return false +} + +func (x *TailscalePeer) GetLastSeen() int64 { + if x != nil { + return x.LastSeen + } + return 0 +} + +type TailscalePingRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + EndpointTag string `protobuf:"bytes,1,opt,name=endpointTag,proto3" json:"endpointTag,omitempty"` + PeerIP string `protobuf:"bytes,2,opt,name=peerIP,proto3" json:"peerIP,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TailscalePingRequest) Reset() { + *x = TailscalePingRequest{} + mi := &file_daemon_started_service_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TailscalePingRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TailscalePingRequest) ProtoMessage() {} + +func (x *TailscalePingRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[35] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TailscalePingRequest.ProtoReflect.Descriptor instead. +func (*TailscalePingRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{35} +} + +func (x *TailscalePingRequest) GetEndpointTag() string { + if x != nil { + return x.EndpointTag + } + return "" +} + +func (x *TailscalePingRequest) GetPeerIP() string { + if x != nil { + return x.PeerIP + } + return "" +} + +type TailscalePingResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + LatencyMs float64 `protobuf:"fixed64,1,opt,name=latencyMs,proto3" json:"latencyMs,omitempty"` + IsDirect bool `protobuf:"varint,2,opt,name=isDirect,proto3" json:"isDirect,omitempty"` + Endpoint string `protobuf:"bytes,3,opt,name=endpoint,proto3" json:"endpoint,omitempty"` + DerpRegionID int32 `protobuf:"varint,4,opt,name=derpRegionID,proto3" json:"derpRegionID,omitempty"` + DerpRegionCode string `protobuf:"bytes,5,opt,name=derpRegionCode,proto3" json:"derpRegionCode,omitempty"` + Error string `protobuf:"bytes,6,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TailscalePingResponse) Reset() { + *x = TailscalePingResponse{} + mi := &file_daemon_started_service_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TailscalePingResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TailscalePingResponse) ProtoMessage() {} + +func (x *TailscalePingResponse) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[36] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TailscalePingResponse.ProtoReflect.Descriptor instead. +func (*TailscalePingResponse) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{36} +} + +func (x *TailscalePingResponse) GetLatencyMs() float64 { + if x != nil { + return x.LatencyMs + } + return 0 +} + +func (x *TailscalePingResponse) GetIsDirect() bool { + if x != nil { + return x.IsDirect + } + return false +} + +func (x *TailscalePingResponse) GetEndpoint() string { + if x != nil { + return x.Endpoint + } + return "" +} + +func (x *TailscalePingResponse) GetDerpRegionID() int32 { + if x != nil { + return x.DerpRegionID + } + return 0 +} + +func (x *TailscalePingResponse) GetDerpRegionCode() string { + if x != nil { + return x.DerpRegionCode + } + return "" +} + +func (x *TailscalePingResponse) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type SetTailscaleExitNodeRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + EndpointTag string `protobuf:"bytes,1,opt,name=endpointTag,proto3" json:"endpointTag,omitempty"` + StableID string `protobuf:"bytes,2,opt,name=stableID,proto3" json:"stableID,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetTailscaleExitNodeRequest) Reset() { + *x = SetTailscaleExitNodeRequest{} + mi := &file_daemon_started_service_proto_msgTypes[37] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetTailscaleExitNodeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetTailscaleExitNodeRequest) ProtoMessage() {} + +func (x *SetTailscaleExitNodeRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[37] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetTailscaleExitNodeRequest.ProtoReflect.Descriptor instead. +func (*SetTailscaleExitNodeRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{37} +} + +func (x *SetTailscaleExitNodeRequest) GetEndpointTag() string { + if x != nil { + return x.EndpointTag + } + return "" +} + +func (x *SetTailscaleExitNodeRequest) GetStableID() string { + if x != nil { + return x.StableID + } + return "" +} + +type TailscaleSSHClientMessage struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Message: + // + // *TailscaleSSHClientMessage_Start + // *TailscaleSSHClientMessage_Input + // *TailscaleSSHClientMessage_Resize + Message isTailscaleSSHClientMessage_Message `protobuf_oneof:"message"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TailscaleSSHClientMessage) Reset() { + *x = TailscaleSSHClientMessage{} + mi := &file_daemon_started_service_proto_msgTypes[38] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TailscaleSSHClientMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TailscaleSSHClientMessage) ProtoMessage() {} + +func (x *TailscaleSSHClientMessage) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[38] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TailscaleSSHClientMessage.ProtoReflect.Descriptor instead. +func (*TailscaleSSHClientMessage) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{38} +} + +func (x *TailscaleSSHClientMessage) GetMessage() isTailscaleSSHClientMessage_Message { + if x != nil { + return x.Message + } + return nil +} + +func (x *TailscaleSSHClientMessage) GetStart() *TailscaleSSHStart { + if x != nil { + if x, ok := x.Message.(*TailscaleSSHClientMessage_Start); ok { + return x.Start + } + } + return nil +} + +func (x *TailscaleSSHClientMessage) GetInput() *TailscaleSSHInput { + if x != nil { + if x, ok := x.Message.(*TailscaleSSHClientMessage_Input); ok { + return x.Input + } + } + return nil +} + +func (x *TailscaleSSHClientMessage) GetResize() *TailscaleSSHResize { + if x != nil { + if x, ok := x.Message.(*TailscaleSSHClientMessage_Resize); ok { + return x.Resize + } + } + return nil +} + +type isTailscaleSSHClientMessage_Message interface { + isTailscaleSSHClientMessage_Message() +} + +type TailscaleSSHClientMessage_Start struct { + Start *TailscaleSSHStart `protobuf:"bytes,1,opt,name=start,proto3,oneof"` +} + +type TailscaleSSHClientMessage_Input struct { + Input *TailscaleSSHInput `protobuf:"bytes,2,opt,name=input,proto3,oneof"` +} + +type TailscaleSSHClientMessage_Resize struct { + Resize *TailscaleSSHResize `protobuf:"bytes,3,opt,name=resize,proto3,oneof"` +} + +func (*TailscaleSSHClientMessage_Start) isTailscaleSSHClientMessage_Message() {} + +func (*TailscaleSSHClientMessage_Input) isTailscaleSSHClientMessage_Message() {} + +func (*TailscaleSSHClientMessage_Resize) isTailscaleSSHClientMessage_Message() {} + +type TailscaleSSHStart struct { + state protoimpl.MessageState `protogen:"open.v1"` + EndpointTag string `protobuf:"bytes,1,opt,name=endpointTag,proto3" json:"endpointTag,omitempty"` + PeerAddress string `protobuf:"bytes,2,opt,name=peerAddress,proto3" json:"peerAddress,omitempty"` + Username string `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"` + TerminalType string `protobuf:"bytes,4,opt,name=terminalType,proto3" json:"terminalType,omitempty"` + Columns int32 `protobuf:"varint,5,opt,name=columns,proto3" json:"columns,omitempty"` + Rows int32 `protobuf:"varint,6,opt,name=rows,proto3" json:"rows,omitempty"` + WidthPixels int32 `protobuf:"varint,7,opt,name=widthPixels,proto3" json:"widthPixels,omitempty"` + HeightPixels int32 `protobuf:"varint,8,opt,name=heightPixels,proto3" json:"heightPixels,omitempty"` + HostKeys []string `protobuf:"bytes,9,rep,name=hostKeys,proto3" json:"hostKeys,omitempty"` + ForwardAgent bool `protobuf:"varint,10,opt,name=forward_agent,json=forwardAgent,proto3" json:"forward_agent,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TailscaleSSHStart) Reset() { + *x = TailscaleSSHStart{} + mi := &file_daemon_started_service_proto_msgTypes[39] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TailscaleSSHStart) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TailscaleSSHStart) ProtoMessage() {} + +func (x *TailscaleSSHStart) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[39] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TailscaleSSHStart.ProtoReflect.Descriptor instead. +func (*TailscaleSSHStart) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{39} +} + +func (x *TailscaleSSHStart) GetEndpointTag() string { + if x != nil { + return x.EndpointTag + } + return "" +} + +func (x *TailscaleSSHStart) GetPeerAddress() string { + if x != nil { + return x.PeerAddress + } + return "" +} + +func (x *TailscaleSSHStart) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *TailscaleSSHStart) GetTerminalType() string { + if x != nil { + return x.TerminalType + } + return "" +} + +func (x *TailscaleSSHStart) GetColumns() int32 { + if x != nil { + return x.Columns + } + return 0 +} + +func (x *TailscaleSSHStart) GetRows() int32 { + if x != nil { + return x.Rows + } + return 0 +} + +func (x *TailscaleSSHStart) GetWidthPixels() int32 { + if x != nil { + return x.WidthPixels + } + return 0 +} + +func (x *TailscaleSSHStart) GetHeightPixels() int32 { + if x != nil { + return x.HeightPixels + } + return 0 +} + +func (x *TailscaleSSHStart) GetHostKeys() []string { + if x != nil { + return x.HostKeys + } + return nil +} + +func (x *TailscaleSSHStart) GetForwardAgent() bool { + if x != nil { + return x.ForwardAgent + } + return false +} + +type TailscaleSSHInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TailscaleSSHInput) Reset() { + *x = TailscaleSSHInput{} + mi := &file_daemon_started_service_proto_msgTypes[40] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TailscaleSSHInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TailscaleSSHInput) ProtoMessage() {} + +func (x *TailscaleSSHInput) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[40] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TailscaleSSHInput.ProtoReflect.Descriptor instead. +func (*TailscaleSSHInput) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{40} +} + +func (x *TailscaleSSHInput) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +type TailscaleSSHResize struct { + state protoimpl.MessageState `protogen:"open.v1"` + Columns int32 `protobuf:"varint,1,opt,name=columns,proto3" json:"columns,omitempty"` + Rows int32 `protobuf:"varint,2,opt,name=rows,proto3" json:"rows,omitempty"` + WidthPixels int32 `protobuf:"varint,3,opt,name=widthPixels,proto3" json:"widthPixels,omitempty"` + HeightPixels int32 `protobuf:"varint,4,opt,name=heightPixels,proto3" json:"heightPixels,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TailscaleSSHResize) Reset() { + *x = TailscaleSSHResize{} + mi := &file_daemon_started_service_proto_msgTypes[41] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TailscaleSSHResize) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TailscaleSSHResize) ProtoMessage() {} + +func (x *TailscaleSSHResize) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[41] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TailscaleSSHResize.ProtoReflect.Descriptor instead. +func (*TailscaleSSHResize) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{41} +} + +func (x *TailscaleSSHResize) GetColumns() int32 { + if x != nil { + return x.Columns + } + return 0 +} + +func (x *TailscaleSSHResize) GetRows() int32 { + if x != nil { + return x.Rows + } + return 0 +} + +func (x *TailscaleSSHResize) GetWidthPixels() int32 { + if x != nil { + return x.WidthPixels + } + return 0 +} + +func (x *TailscaleSSHResize) GetHeightPixels() int32 { + if x != nil { + return x.HeightPixels + } + return 0 +} + +type TailscaleSSHServerMessage struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Message: + // + // *TailscaleSSHServerMessage_AuthBanner + // *TailscaleSSHServerMessage_Ready + // *TailscaleSSHServerMessage_Output + // *TailscaleSSHServerMessage_Exit + // *TailscaleSSHServerMessage_Error + Message isTailscaleSSHServerMessage_Message `protobuf_oneof:"message"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TailscaleSSHServerMessage) Reset() { + *x = TailscaleSSHServerMessage{} + mi := &file_daemon_started_service_proto_msgTypes[42] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TailscaleSSHServerMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TailscaleSSHServerMessage) ProtoMessage() {} + +func (x *TailscaleSSHServerMessage) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[42] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TailscaleSSHServerMessage.ProtoReflect.Descriptor instead. +func (*TailscaleSSHServerMessage) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{42} +} + +func (x *TailscaleSSHServerMessage) GetMessage() isTailscaleSSHServerMessage_Message { + if x != nil { + return x.Message + } + return nil +} + +func (x *TailscaleSSHServerMessage) GetAuthBanner() *TailscaleSSHAuthBanner { + if x != nil { + if x, ok := x.Message.(*TailscaleSSHServerMessage_AuthBanner); ok { + return x.AuthBanner + } + } + return nil +} + +func (x *TailscaleSSHServerMessage) GetReady() *TailscaleSSHReady { + if x != nil { + if x, ok := x.Message.(*TailscaleSSHServerMessage_Ready); ok { + return x.Ready + } + } + return nil +} + +func (x *TailscaleSSHServerMessage) GetOutput() *TailscaleSSHOutput { + if x != nil { + if x, ok := x.Message.(*TailscaleSSHServerMessage_Output); ok { + return x.Output + } + } + return nil +} + +func (x *TailscaleSSHServerMessage) GetExit() *TailscaleSSHExit { + if x != nil { + if x, ok := x.Message.(*TailscaleSSHServerMessage_Exit); ok { + return x.Exit + } + } + return nil +} + +func (x *TailscaleSSHServerMessage) GetError() *TailscaleSSHError { + if x != nil { + if x, ok := x.Message.(*TailscaleSSHServerMessage_Error); ok { + return x.Error + } + } + return nil +} + +type isTailscaleSSHServerMessage_Message interface { + isTailscaleSSHServerMessage_Message() +} + +type TailscaleSSHServerMessage_AuthBanner struct { + AuthBanner *TailscaleSSHAuthBanner `protobuf:"bytes,1,opt,name=authBanner,proto3,oneof"` +} + +type TailscaleSSHServerMessage_Ready struct { + Ready *TailscaleSSHReady `protobuf:"bytes,2,opt,name=ready,proto3,oneof"` +} + +type TailscaleSSHServerMessage_Output struct { + Output *TailscaleSSHOutput `protobuf:"bytes,3,opt,name=output,proto3,oneof"` +} + +type TailscaleSSHServerMessage_Exit struct { + Exit *TailscaleSSHExit `protobuf:"bytes,4,opt,name=exit,proto3,oneof"` +} + +type TailscaleSSHServerMessage_Error struct { + Error *TailscaleSSHError `protobuf:"bytes,5,opt,name=error,proto3,oneof"` +} + +func (*TailscaleSSHServerMessage_AuthBanner) isTailscaleSSHServerMessage_Message() {} + +func (*TailscaleSSHServerMessage_Ready) isTailscaleSSHServerMessage_Message() {} + +func (*TailscaleSSHServerMessage_Output) isTailscaleSSHServerMessage_Message() {} + +func (*TailscaleSSHServerMessage_Exit) isTailscaleSSHServerMessage_Message() {} + +func (*TailscaleSSHServerMessage_Error) isTailscaleSSHServerMessage_Message() {} + +type TailscaleSSHAuthBanner struct { + state protoimpl.MessageState `protogen:"open.v1"` + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TailscaleSSHAuthBanner) Reset() { + *x = TailscaleSSHAuthBanner{} + mi := &file_daemon_started_service_proto_msgTypes[43] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TailscaleSSHAuthBanner) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TailscaleSSHAuthBanner) ProtoMessage() {} + +func (x *TailscaleSSHAuthBanner) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[43] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TailscaleSSHAuthBanner.ProtoReflect.Descriptor instead. +func (*TailscaleSSHAuthBanner) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{43} +} + +func (x *TailscaleSSHAuthBanner) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type TailscaleSSHReady struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TailscaleSSHReady) Reset() { + *x = TailscaleSSHReady{} + mi := &file_daemon_started_service_proto_msgTypes[44] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TailscaleSSHReady) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TailscaleSSHReady) ProtoMessage() {} + +func (x *TailscaleSSHReady) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[44] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TailscaleSSHReady.ProtoReflect.Descriptor instead. +func (*TailscaleSSHReady) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{44} +} + +type TailscaleSSHOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TailscaleSSHOutput) Reset() { + *x = TailscaleSSHOutput{} + mi := &file_daemon_started_service_proto_msgTypes[45] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TailscaleSSHOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TailscaleSSHOutput) ProtoMessage() {} + +func (x *TailscaleSSHOutput) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[45] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TailscaleSSHOutput.ProtoReflect.Descriptor instead. +func (*TailscaleSSHOutput) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{45} +} + +func (x *TailscaleSSHOutput) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +type TailscaleSSHExit struct { + state protoimpl.MessageState `protogen:"open.v1"` + ExitCode int32 `protobuf:"varint,1,opt,name=exitCode,proto3" json:"exitCode,omitempty"` + Signal string `protobuf:"bytes,2,opt,name=signal,proto3" json:"signal,omitempty"` + ErrorMessage string `protobuf:"bytes,3,opt,name=errorMessage,proto3" json:"errorMessage,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TailscaleSSHExit) Reset() { + *x = TailscaleSSHExit{} + mi := &file_daemon_started_service_proto_msgTypes[46] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TailscaleSSHExit) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TailscaleSSHExit) ProtoMessage() {} + +func (x *TailscaleSSHExit) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[46] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TailscaleSSHExit.ProtoReflect.Descriptor instead. +func (*TailscaleSSHExit) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{46} +} + +func (x *TailscaleSSHExit) GetExitCode() int32 { + if x != nil { + return x.ExitCode + } + return 0 +} + +func (x *TailscaleSSHExit) GetSignal() string { + if x != nil { + return x.Signal + } + return "" +} + +func (x *TailscaleSSHExit) GetErrorMessage() string { + if x != nil { + return x.ErrorMessage + } + return "" +} + +type TailscaleSSHError struct { + state protoimpl.MessageState `protogen:"open.v1"` + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TailscaleSSHError) Reset() { + *x = TailscaleSSHError{} + mi := &file_daemon_started_service_proto_msgTypes[47] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TailscaleSSHError) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TailscaleSSHError) ProtoMessage() {} + +func (x *TailscaleSSHError) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[47] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TailscaleSSHError.ProtoReflect.Descriptor instead. +func (*TailscaleSSHError) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{47} +} + +func (x *TailscaleSSHError) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type Log_Message struct { + state protoimpl.MessageState `protogen:"open.v1"` + Level LogLevel `protobuf:"varint,1,opt,name=level,proto3,enum=daemon.LogLevel" json:"level,omitempty"` + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Log_Message) Reset() { + *x = Log_Message{} + mi := &file_daemon_started_service_proto_msgTypes[48] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Log_Message) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Log_Message) ProtoMessage() {} + +func (x *Log_Message) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[48] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Log_Message.ProtoReflect.Descriptor instead. +func (*Log_Message) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{3, 0} +} + +func (x *Log_Message) GetLevel() LogLevel { + if x != nil { + return x.Level + } + return LogLevel_PANIC +} + +func (x *Log_Message) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +var File_daemon_started_service_proto protoreflect.FileDescriptor + +const file_daemon_started_service_proto_rawDesc = "" + + "\n" + + "\x1cdaemon/started_service.proto\x12\x06daemon\x1a\x1bgoogle/protobuf/empty.proto\"\xad\x01\n" + + "\rServiceStatus\x122\n" + + "\x06status\x18\x01 \x01(\x0e2\x1a.daemon.ServiceStatus.TypeR\x06status\x12\"\n" + + "\ferrorMessage\x18\x02 \x01(\tR\ferrorMessage\"D\n" + + "\x04Type\x12\b\n" + + "\x04IDLE\x10\x00\x12\f\n" + + "\bSTARTING\x10\x01\x12\v\n" + + "\aSTARTED\x10\x02\x12\f\n" + + "\bSTOPPING\x10\x03\x12\t\n" + + "\x05FATAL\x10\x04\"D\n" + + "\x14ReloadServiceRequest\x12,\n" + + "\x11newProfileContent\x18\x01 \x01(\tR\x11newProfileContent\"4\n" + + "\x16SubscribeStatusRequest\x12\x1a\n" + + "\binterval\x18\x01 \x01(\x03R\binterval\"\x99\x01\n" + + "\x03Log\x12/\n" + + "\bmessages\x18\x01 \x03(\v2\x13.daemon.Log.MessageR\bmessages\x12\x14\n" + + "\x05reset\x18\x02 \x01(\bR\x05reset\x1aK\n" + + "\aMessage\x12&\n" + + "\x05level\x18\x01 \x01(\x0e2\x10.daemon.LogLevelR\x05level\x12\x18\n" + + "\amessage\x18\x02 \x01(\tR\amessage\"9\n" + + "\x0fDefaultLogLevel\x12&\n" + + "\x05level\x18\x01 \x01(\x0e2\x10.daemon.LogLevelR\x05level\"\xb6\x02\n" + + "\x06Status\x12\x16\n" + + "\x06memory\x18\x01 \x01(\x04R\x06memory\x12\x1e\n" + + "\n" + + "goroutines\x18\x02 \x01(\x05R\n" + + "goroutines\x12$\n" + + "\rconnectionsIn\x18\x03 \x01(\x05R\rconnectionsIn\x12&\n" + + "\x0econnectionsOut\x18\x04 \x01(\x05R\x0econnectionsOut\x12*\n" + + "\x10trafficAvailable\x18\x05 \x01(\bR\x10trafficAvailable\x12\x16\n" + + "\x06uplink\x18\x06 \x01(\x03R\x06uplink\x12\x1a\n" + + "\bdownlink\x18\a \x01(\x03R\bdownlink\x12 \n" + + "\vuplinkTotal\x18\b \x01(\x03R\vuplinkTotal\x12$\n" + + "\rdownlinkTotal\x18\t \x01(\x03R\rdownlinkTotal\"-\n" + + "\x06Groups\x12#\n" + + "\x05group\x18\x01 \x03(\v2\r.daemon.GroupR\x05group\"\xae\x01\n" + + "\x05Group\x12\x10\n" + + "\x03tag\x18\x01 \x01(\tR\x03tag\x12\x12\n" + + "\x04type\x18\x02 \x01(\tR\x04type\x12\x1e\n" + + "\n" + + "selectable\x18\x03 \x01(\bR\n" + + "selectable\x12\x1a\n" + + "\bselected\x18\x04 \x01(\tR\bselected\x12\x1a\n" + + "\bisExpand\x18\x05 \x01(\bR\bisExpand\x12'\n" + + "\x05items\x18\x06 \x03(\v2\x11.daemon.GroupItemR\x05items\"w\n" + + "\tGroupItem\x12\x10\n" + + "\x03tag\x18\x01 \x01(\tR\x03tag\x12\x12\n" + + "\x04type\x18\x02 \x01(\tR\x04type\x12 \n" + + "\vurlTestTime\x18\x03 \x01(\x03R\vurlTestTime\x12\"\n" + + "\furlTestDelay\x18\x04 \x01(\x05R\furlTestDelay\"2\n" + + "\x0eURLTestRequest\x12 \n" + + "\voutboundTag\x18\x01 \x01(\tR\voutboundTag\"U\n" + + "\x15SelectOutboundRequest\x12\x1a\n" + + "\bgroupTag\x18\x01 \x01(\tR\bgroupTag\x12 \n" + + "\voutboundTag\x18\x02 \x01(\tR\voutboundTag\"O\n" + + "\x15SetGroupExpandRequest\x12\x1a\n" + + "\bgroupTag\x18\x01 \x01(\tR\bgroupTag\x12\x1a\n" + + "\bisExpand\x18\x02 \x01(\bR\bisExpand\"\x1f\n" + + "\tClashMode\x12\x12\n" + + "\x04mode\x18\x03 \x01(\tR\x04mode\"O\n" + + "\x0fClashModeStatus\x12\x1a\n" + + "\bmodeList\x18\x01 \x03(\tR\bmodeList\x12 \n" + + "\vcurrentMode\x18\x02 \x01(\tR\vcurrentMode\"K\n" + + "\x11SystemProxyStatus\x12\x1c\n" + + "\tavailable\x18\x01 \x01(\bR\tavailable\x12\x18\n" + + "\aenabled\x18\x02 \x01(\bR\aenabled\"8\n" + + "\x1cSetSystemProxyEnabledRequest\x12\x18\n" + + "\aenabled\x18\x01 \x01(\bR\aenabled\"c\n" + + "\x11DebugCrashRequest\x122\n" + + "\x04type\x18\x01 \x01(\x0e2\x1e.daemon.DebugCrashRequest.TypeR\x04type\"\x1a\n" + + "\x04Type\x12\x06\n" + + "\x02GO\x10\x00\x12\n" + + "\n" + + "\x06NATIVE\x10\x01\"9\n" + + "\x1bSubscribeConnectionsRequest\x12\x1a\n" + + "\binterval\x18\x01 \x01(\x03R\binterval\"\xea\x01\n" + + "\x0fConnectionEvent\x12/\n" + + "\x04type\x18\x01 \x01(\x0e2\x1b.daemon.ConnectionEventTypeR\x04type\x12\x0e\n" + + "\x02id\x18\x02 \x01(\tR\x02id\x122\n" + + "\n" + + "connection\x18\x03 \x01(\v2\x12.daemon.ConnectionR\n" + + "connection\x12 \n" + + "\vuplinkDelta\x18\x04 \x01(\x03R\vuplinkDelta\x12$\n" + + "\rdownlinkDelta\x18\x05 \x01(\x03R\rdownlinkDelta\x12\x1a\n" + + "\bclosedAt\x18\x06 \x01(\x03R\bclosedAt\"Y\n" + + "\x10ConnectionEvents\x12/\n" + + "\x06events\x18\x01 \x03(\v2\x17.daemon.ConnectionEventR\x06events\x12\x14\n" + + "\x05reset\x18\x02 \x01(\bR\x05reset\"\x95\x05\n" + + "\n" + + "Connection\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" + + "\ainbound\x18\x02 \x01(\tR\ainbound\x12 \n" + + "\vinboundType\x18\x03 \x01(\tR\vinboundType\x12\x1c\n" + + "\tipVersion\x18\x04 \x01(\x05R\tipVersion\x12\x18\n" + + "\anetwork\x18\x05 \x01(\tR\anetwork\x12\x16\n" + + "\x06source\x18\x06 \x01(\tR\x06source\x12 \n" + + "\vdestination\x18\a \x01(\tR\vdestination\x12\x16\n" + + "\x06domain\x18\b \x01(\tR\x06domain\x12\x1a\n" + + "\bprotocol\x18\t \x01(\tR\bprotocol\x12\x12\n" + + "\x04user\x18\n" + + " \x01(\tR\x04user\x12\"\n" + + "\ffromOutbound\x18\v \x01(\tR\ffromOutbound\x12\x1c\n" + + "\tcreatedAt\x18\f \x01(\x03R\tcreatedAt\x12\x1a\n" + + "\bclosedAt\x18\r \x01(\x03R\bclosedAt\x12\x16\n" + + "\x06uplink\x18\x0e \x01(\x03R\x06uplink\x12\x1a\n" + + "\bdownlink\x18\x0f \x01(\x03R\bdownlink\x12 \n" + + "\vuplinkTotal\x18\x10 \x01(\x03R\vuplinkTotal\x12$\n" + + "\rdownlinkTotal\x18\x11 \x01(\x03R\rdownlinkTotal\x12\x12\n" + + "\x04rule\x18\x12 \x01(\tR\x04rule\x12\x1a\n" + + "\boutbound\x18\x13 \x01(\tR\boutbound\x12\"\n" + + "\foutboundType\x18\x14 \x01(\tR\foutboundType\x12\x1c\n" + + "\tchainList\x18\x15 \x03(\tR\tchainList\x125\n" + + "\vprocessInfo\x18\x16 \x01(\v2\x13.daemon.ProcessInfoR\vprocessInfo\"\xa5\x01\n" + + "\vProcessInfo\x12\x1c\n" + + "\tprocessId\x18\x01 \x01(\rR\tprocessId\x12\x16\n" + + "\x06userId\x18\x02 \x01(\x05R\x06userId\x12\x1a\n" + + "\buserName\x18\x03 \x01(\tR\buserName\x12 \n" + + "\vprocessPath\x18\x04 \x01(\tR\vprocessPath\x12\"\n" + + "\fpackageNames\x18\x05 \x03(\tR\fpackageNames\"(\n" + + "\x16CloseConnectionRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\"K\n" + + "\x12DeprecatedWarnings\x125\n" + + "\bwarnings\x18\x01 \x03(\v2\x19.daemon.DeprecatedWarningR\bwarnings\"\xed\x01\n" + + "\x11DeprecatedWarning\x12\x18\n" + + "\amessage\x18\x01 \x01(\tR\amessage\x12\x1c\n" + + "\timpending\x18\x02 \x01(\bR\timpending\x12$\n" + + "\rmigrationLink\x18\x03 \x01(\tR\rmigrationLink\x12 \n" + + "\vdescription\x18\x04 \x01(\tR\vdescription\x12,\n" + + "\x11deprecatedVersion\x18\x05 \x01(\tR\x11deprecatedVersion\x12*\n" + + "\x10scheduledVersion\x18\x06 \x01(\tR\x10scheduledVersion\")\n" + + "\tStartedAt\x12\x1c\n" + + "\tstartedAt\x18\x01 \x01(\x03R\tstartedAt\"?\n" + + "\fOutboundList\x12/\n" + + "\toutbounds\x18\x01 \x03(\v2\x11.daemon.GroupItemR\toutbounds\"\xb7\x01\n" + + "\x19NetworkQualityTestRequest\x12\x1c\n" + + "\tconfigURL\x18\x01 \x01(\tR\tconfigURL\x12 \n" + + "\voutboundTag\x18\x02 \x01(\tR\voutboundTag\x12\x16\n" + + "\x06serial\x18\x03 \x01(\bR\x06serial\x12,\n" + + "\x11maxRuntimeSeconds\x18\x04 \x01(\x05R\x11maxRuntimeSeconds\x12\x14\n" + + "\x05http3\x18\x05 \x01(\bR\x05http3\"\x8e\x04\n" + + "\x1aNetworkQualityTestProgress\x12\x14\n" + + "\x05phase\x18\x01 \x01(\x05R\x05phase\x12*\n" + + "\x10downloadCapacity\x18\x02 \x01(\x03R\x10downloadCapacity\x12&\n" + + "\x0euploadCapacity\x18\x03 \x01(\x03R\x0euploadCapacity\x12 \n" + + "\vdownloadRPM\x18\x04 \x01(\x05R\vdownloadRPM\x12\x1c\n" + + "\tuploadRPM\x18\x05 \x01(\x05R\tuploadRPM\x12$\n" + + "\ridleLatencyMs\x18\x06 \x01(\x05R\ridleLatencyMs\x12\x1c\n" + + "\telapsedMs\x18\a \x01(\x03R\telapsedMs\x12\x18\n" + + "\aisFinal\x18\b \x01(\bR\aisFinal\x12\x14\n" + + "\x05error\x18\t \x01(\tR\x05error\x12:\n" + + "\x18downloadCapacityAccuracy\x18\n" + + " \x01(\x05R\x18downloadCapacityAccuracy\x126\n" + + "\x16uploadCapacityAccuracy\x18\v \x01(\x05R\x16uploadCapacityAccuracy\x120\n" + + "\x13downloadRPMAccuracy\x18\f \x01(\x05R\x13downloadRPMAccuracy\x12,\n" + + "\x11uploadRPMAccuracy\x18\r \x01(\x05R\x11uploadRPMAccuracy\"K\n" + + "\x0fSTUNTestRequest\x12\x16\n" + + "\x06server\x18\x01 \x01(\tR\x06server\x12 \n" + + "\voutboundTag\x18\x02 \x01(\tR\voutboundTag\"\x8a\x02\n" + + "\x10STUNTestProgress\x12\x14\n" + + "\x05phase\x18\x01 \x01(\x05R\x05phase\x12\"\n" + + "\fexternalAddr\x18\x02 \x01(\tR\fexternalAddr\x12\x1c\n" + + "\tlatencyMs\x18\x03 \x01(\x05R\tlatencyMs\x12\x1e\n" + + "\n" + + "natMapping\x18\x04 \x01(\x05R\n" + + "natMapping\x12\"\n" + + "\fnatFiltering\x18\x05 \x01(\x05R\fnatFiltering\x12\x18\n" + + "\aisFinal\x18\x06 \x01(\bR\aisFinal\x12\x14\n" + + "\x05error\x18\a \x01(\tR\x05error\x12*\n" + + "\x10natTypeSupported\x18\b \x01(\bR\x10natTypeSupported\"V\n" + + "\x15TailscaleStatusUpdate\x12=\n" + + "\tendpoints\x18\x01 \x03(\v2\x1f.daemon.TailscaleEndpointStatusR\tendpoints\"\xdd\x02\n" + + "\x17TailscaleEndpointStatus\x12 \n" + + "\vendpointTag\x18\x01 \x01(\tR\vendpointTag\x12\"\n" + + "\fbackendState\x18\x02 \x01(\tR\fbackendState\x12\x18\n" + + "\aauthURL\x18\x03 \x01(\tR\aauthURL\x12 \n" + + "\vnetworkName\x18\x04 \x01(\tR\vnetworkName\x12&\n" + + "\x0emagicDNSSuffix\x18\x05 \x01(\tR\x0emagicDNSSuffix\x12)\n" + + "\x04self\x18\x06 \x01(\v2\x15.daemon.TailscalePeerR\x04self\x12:\n" + + "\n" + + "userGroups\x18\a \x03(\v2\x1a.daemon.TailscaleUserGroupR\n" + + "userGroups\x121\n" + + "\bexitNode\x18\b \x01(\v2\x15.daemon.TailscalePeerR\bexitNode\"\xbf\x01\n" + + "\x12TailscaleUserGroup\x12\x16\n" + + "\x06userID\x18\x01 \x01(\x03R\x06userID\x12\x1c\n" + + "\tloginName\x18\x02 \x01(\tR\tloginName\x12 \n" + + "\vdisplayName\x18\x03 \x01(\tR\vdisplayName\x12$\n" + + "\rprofilePicURL\x18\x04 \x01(\tR\rprofilePicURL\x12+\n" + + "\x05peers\x18\x05 \x03(\v2\x15.daemon.TailscalePeerR\x05peers\"\xd3\x03\n" + + "\rTailscalePeer\x12\x1a\n" + + "\bhostName\x18\x01 \x01(\tR\bhostName\x12\x18\n" + + "\adnsName\x18\x02 \x01(\tR\adnsName\x12\x0e\n" + + "\x02os\x18\x03 \x01(\tR\x02os\x12\"\n" + + "\ftailscaleIPs\x18\x04 \x03(\tR\ftailscaleIPs\x12\x16\n" + + "\x06online\x18\x05 \x01(\bR\x06online\x12\x1a\n" + + "\bexitNode\x18\x06 \x01(\bR\bexitNode\x12&\n" + + "\x0eexitNodeOption\x18\a \x01(\bR\x0eexitNodeOption\x12\x16\n" + + "\x06active\x18\b \x01(\bR\x06active\x12\x18\n" + + "\arxBytes\x18\t \x01(\x03R\arxBytes\x12\x18\n" + + "\atxBytes\x18\n" + + " \x01(\x03R\atxBytes\x12\x1c\n" + + "\tkeyExpiry\x18\v \x01(\x03R\tkeyExpiry\x12\x1a\n" + + "\bstableID\x18\f \x01(\tR\bstableID\x12\x18\n" + + "\aexpired\x18\r \x01(\bR\aexpired\x12 \n" + + "\vsshHostKeys\x18\x0e \x03(\tR\vsshHostKeys\x12\x1e\n" + + "\n" + + "shareeNode\x18\x0f \x01(\bR\n" + + "shareeNode\x12\x1a\n" + + "\blastSeen\x18\x10 \x01(\x03R\blastSeen\"P\n" + + "\x14TailscalePingRequest\x12 \n" + + "\vendpointTag\x18\x01 \x01(\tR\vendpointTag\x12\x16\n" + + "\x06peerIP\x18\x02 \x01(\tR\x06peerIP\"\xcf\x01\n" + + "\x15TailscalePingResponse\x12\x1c\n" + + "\tlatencyMs\x18\x01 \x01(\x01R\tlatencyMs\x12\x1a\n" + + "\bisDirect\x18\x02 \x01(\bR\bisDirect\x12\x1a\n" + + "\bendpoint\x18\x03 \x01(\tR\bendpoint\x12\"\n" + + "\fderpRegionID\x18\x04 \x01(\x05R\fderpRegionID\x12&\n" + + "\x0ederpRegionCode\x18\x05 \x01(\tR\x0ederpRegionCode\x12\x14\n" + + "\x05error\x18\x06 \x01(\tR\x05error\"[\n" + + "\x1bSetTailscaleExitNodeRequest\x12 \n" + + "\vendpointTag\x18\x01 \x01(\tR\vendpointTag\x12\x1a\n" + + "\bstableID\x18\x02 \x01(\tR\bstableID\"\xc2\x01\n" + + "\x19TailscaleSSHClientMessage\x121\n" + + "\x05start\x18\x01 \x01(\v2\x19.daemon.TailscaleSSHStartH\x00R\x05start\x121\n" + + "\x05input\x18\x02 \x01(\v2\x19.daemon.TailscaleSSHInputH\x00R\x05input\x124\n" + + "\x06resize\x18\x03 \x01(\v2\x1a.daemon.TailscaleSSHResizeH\x00R\x06resizeB\t\n" + + "\amessage\"\xcc\x02\n" + + "\x11TailscaleSSHStart\x12 \n" + + "\vendpointTag\x18\x01 \x01(\tR\vendpointTag\x12 \n" + + "\vpeerAddress\x18\x02 \x01(\tR\vpeerAddress\x12\x1a\n" + + "\busername\x18\x03 \x01(\tR\busername\x12\"\n" + + "\fterminalType\x18\x04 \x01(\tR\fterminalType\x12\x18\n" + + "\acolumns\x18\x05 \x01(\x05R\acolumns\x12\x12\n" + + "\x04rows\x18\x06 \x01(\x05R\x04rows\x12 \n" + + "\vwidthPixels\x18\a \x01(\x05R\vwidthPixels\x12\"\n" + + "\fheightPixels\x18\b \x01(\x05R\fheightPixels\x12\x1a\n" + + "\bhostKeys\x18\t \x03(\tR\bhostKeys\x12#\n" + + "\rforward_agent\x18\n" + + " \x01(\bR\fforwardAgent\"'\n" + + "\x11TailscaleSSHInput\x12\x12\n" + + "\x04data\x18\x01 \x01(\fR\x04data\"\x88\x01\n" + + "\x12TailscaleSSHResize\x12\x18\n" + + "\acolumns\x18\x01 \x01(\x05R\acolumns\x12\x12\n" + + "\x04rows\x18\x02 \x01(\x05R\x04rows\x12 \n" + + "\vwidthPixels\x18\x03 \x01(\x05R\vwidthPixels\x12\"\n" + + "\fheightPixels\x18\x04 \x01(\x05R\fheightPixels\"\xb4\x02\n" + + "\x19TailscaleSSHServerMessage\x12@\n" + + "\n" + + "authBanner\x18\x01 \x01(\v2\x1e.daemon.TailscaleSSHAuthBannerH\x00R\n" + + "authBanner\x121\n" + + "\x05ready\x18\x02 \x01(\v2\x19.daemon.TailscaleSSHReadyH\x00R\x05ready\x124\n" + + "\x06output\x18\x03 \x01(\v2\x1a.daemon.TailscaleSSHOutputH\x00R\x06output\x12.\n" + + "\x04exit\x18\x04 \x01(\v2\x18.daemon.TailscaleSSHExitH\x00R\x04exit\x121\n" + + "\x05error\x18\x05 \x01(\v2\x19.daemon.TailscaleSSHErrorH\x00R\x05errorB\t\n" + + "\amessage\"2\n" + + "\x16TailscaleSSHAuthBanner\x12\x18\n" + + "\amessage\x18\x01 \x01(\tR\amessage\"\x13\n" + + "\x11TailscaleSSHReady\"(\n" + + "\x12TailscaleSSHOutput\x12\x12\n" + + "\x04data\x18\x01 \x01(\fR\x04data\"j\n" + + "\x10TailscaleSSHExit\x12\x1a\n" + + "\bexitCode\x18\x01 \x01(\x05R\bexitCode\x12\x16\n" + + "\x06signal\x18\x02 \x01(\tR\x06signal\x12\"\n" + + "\ferrorMessage\x18\x03 \x01(\tR\ferrorMessage\"-\n" + + "\x11TailscaleSSHError\x12\x18\n" + + "\amessage\x18\x01 \x01(\tR\amessage*U\n" + + "\bLogLevel\x12\t\n" + + "\x05PANIC\x10\x00\x12\t\n" + + "\x05FATAL\x10\x01\x12\t\n" + + "\x05ERROR\x10\x02\x12\b\n" + + "\x04WARN\x10\x03\x12\b\n" + + "\x04INFO\x10\x04\x12\t\n" + + "\x05DEBUG\x10\x05\x12\t\n" + + "\x05TRACE\x10\x06*i\n" + + "\x13ConnectionEventType\x12\x18\n" + + "\x14CONNECTION_EVENT_NEW\x10\x00\x12\x1b\n" + + "\x17CONNECTION_EVENT_UPDATE\x10\x01\x12\x1b\n" + + "\x17CONNECTION_EVENT_CLOSED\x10\x022\xd8\x11\n" + + "\x0eStartedService\x12=\n" + + "\vStopService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12?\n" + + "\rReloadService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12K\n" + + "\x16SubscribeServiceStatus\x12\x16.google.protobuf.Empty\x1a\x15.daemon.ServiceStatus\"\x000\x01\x127\n" + + "\fSubscribeLog\x12\x16.google.protobuf.Empty\x1a\v.daemon.Log\"\x000\x01\x12G\n" + + "\x12GetDefaultLogLevel\x12\x16.google.protobuf.Empty\x1a\x17.daemon.DefaultLogLevel\"\x00\x12=\n" + + "\tClearLogs\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12E\n" + + "\x0fSubscribeStatus\x12\x1e.daemon.SubscribeStatusRequest\x1a\x0e.daemon.Status\"\x000\x01\x12=\n" + + "\x0fSubscribeGroups\x12\x16.google.protobuf.Empty\x1a\x0e.daemon.Groups\"\x000\x01\x12G\n" + + "\x12GetClashModeStatus\x12\x16.google.protobuf.Empty\x1a\x17.daemon.ClashModeStatus\"\x00\x12C\n" + + "\x12SubscribeClashMode\x12\x16.google.protobuf.Empty\x1a\x11.daemon.ClashMode\"\x000\x01\x12;\n" + + "\fSetClashMode\x12\x11.daemon.ClashMode\x1a\x16.google.protobuf.Empty\"\x00\x12;\n" + + "\aURLTest\x12\x16.daemon.URLTestRequest\x1a\x16.google.protobuf.Empty\"\x00\x12I\n" + + "\x0eSelectOutbound\x12\x1d.daemon.SelectOutboundRequest\x1a\x16.google.protobuf.Empty\"\x00\x12I\n" + + "\x0eSetGroupExpand\x12\x1d.daemon.SetGroupExpandRequest\x1a\x16.google.protobuf.Empty\"\x00\x12K\n" + + "\x14GetSystemProxyStatus\x12\x16.google.protobuf.Empty\x1a\x19.daemon.SystemProxyStatus\"\x00\x12W\n" + + "\x15SetSystemProxyEnabled\x12$.daemon.SetSystemProxyEnabledRequest\x1a\x16.google.protobuf.Empty\"\x00\x12H\n" + + "\x11TriggerDebugCrash\x12\x19.daemon.DebugCrashRequest\x1a\x16.google.protobuf.Empty\"\x00\x12D\n" + + "\x10TriggerOOMReport\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12Y\n" + + "\x14SubscribeConnections\x12#.daemon.SubscribeConnectionsRequest\x1a\x18.daemon.ConnectionEvents\"\x000\x01\x12K\n" + + "\x0fCloseConnection\x12\x1e.daemon.CloseConnectionRequest\x1a\x16.google.protobuf.Empty\"\x00\x12G\n" + + "\x13CloseAllConnections\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12M\n" + + "\x15GetDeprecatedWarnings\x12\x16.google.protobuf.Empty\x1a\x1a.daemon.DeprecatedWarnings\"\x00\x12;\n" + + "\fGetStartedAt\x12\x16.google.protobuf.Empty\x1a\x11.daemon.StartedAt\"\x00\x12F\n" + + "\x12SubscribeOutbounds\x12\x16.google.protobuf.Empty\x1a\x14.daemon.OutboundList\"\x000\x01\x12d\n" + + "\x17StartNetworkQualityTest\x12!.daemon.NetworkQualityTestRequest\x1a\".daemon.NetworkQualityTestProgress\"\x000\x01\x12F\n" + + "\rStartSTUNTest\x12\x17.daemon.STUNTestRequest\x1a\x18.daemon.STUNTestProgress\"\x000\x01\x12U\n" + + "\x18SubscribeTailscaleStatus\x12\x16.google.protobuf.Empty\x1a\x1d.daemon.TailscaleStatusUpdate\"\x000\x01\x12U\n" + + "\x12StartTailscalePing\x12\x1c.daemon.TailscalePingRequest\x1a\x1d.daemon.TailscalePingResponse\"\x000\x01\x12U\n" + + "\x14SetTailscaleExitNode\x12#.daemon.SetTailscaleExitNodeRequest\x1a\x16.google.protobuf.Empty\"\x00\x12f\n" + + "\x18StartTailscaleSSHSession\x12!.daemon.TailscaleSSHClientMessage\x1a!.daemon.TailscaleSSHServerMessage\"\x00(\x010\x01B%Z#github.com/sagernet/sing-box/daemonb\x06proto3" + +var ( + file_daemon_started_service_proto_rawDescOnce sync.Once + file_daemon_started_service_proto_rawDescData []byte +) + +func file_daemon_started_service_proto_rawDescGZIP() []byte { + file_daemon_started_service_proto_rawDescOnce.Do(func() { + file_daemon_started_service_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_daemon_started_service_proto_rawDesc), len(file_daemon_started_service_proto_rawDesc))) + }) + return file_daemon_started_service_proto_rawDescData +} + +var ( + file_daemon_started_service_proto_enumTypes = make([]protoimpl.EnumInfo, 4) + file_daemon_started_service_proto_msgTypes = make([]protoimpl.MessageInfo, 49) + file_daemon_started_service_proto_goTypes = []any{ + (LogLevel)(0), // 0: daemon.LogLevel + (ConnectionEventType)(0), // 1: daemon.ConnectionEventType + (ServiceStatus_Type)(0), // 2: daemon.ServiceStatus.Type + (DebugCrashRequest_Type)(0), // 3: daemon.DebugCrashRequest.Type + (*ServiceStatus)(nil), // 4: daemon.ServiceStatus + (*ReloadServiceRequest)(nil), // 5: daemon.ReloadServiceRequest + (*SubscribeStatusRequest)(nil), // 6: daemon.SubscribeStatusRequest + (*Log)(nil), // 7: daemon.Log + (*DefaultLogLevel)(nil), // 8: daemon.DefaultLogLevel + (*Status)(nil), // 9: daemon.Status + (*Groups)(nil), // 10: daemon.Groups + (*Group)(nil), // 11: daemon.Group + (*GroupItem)(nil), // 12: daemon.GroupItem + (*URLTestRequest)(nil), // 13: daemon.URLTestRequest + (*SelectOutboundRequest)(nil), // 14: daemon.SelectOutboundRequest + (*SetGroupExpandRequest)(nil), // 15: daemon.SetGroupExpandRequest + (*ClashMode)(nil), // 16: daemon.ClashMode + (*ClashModeStatus)(nil), // 17: daemon.ClashModeStatus + (*SystemProxyStatus)(nil), // 18: daemon.SystemProxyStatus + (*SetSystemProxyEnabledRequest)(nil), // 19: daemon.SetSystemProxyEnabledRequest + (*DebugCrashRequest)(nil), // 20: daemon.DebugCrashRequest + (*SubscribeConnectionsRequest)(nil), // 21: daemon.SubscribeConnectionsRequest + (*ConnectionEvent)(nil), // 22: daemon.ConnectionEvent + (*ConnectionEvents)(nil), // 23: daemon.ConnectionEvents + (*Connection)(nil), // 24: daemon.Connection + (*ProcessInfo)(nil), // 25: daemon.ProcessInfo + (*CloseConnectionRequest)(nil), // 26: daemon.CloseConnectionRequest + (*DeprecatedWarnings)(nil), // 27: daemon.DeprecatedWarnings + (*DeprecatedWarning)(nil), // 28: daemon.DeprecatedWarning + (*StartedAt)(nil), // 29: daemon.StartedAt + (*OutboundList)(nil), // 30: daemon.OutboundList + (*NetworkQualityTestRequest)(nil), // 31: daemon.NetworkQualityTestRequest + (*NetworkQualityTestProgress)(nil), // 32: daemon.NetworkQualityTestProgress + (*STUNTestRequest)(nil), // 33: daemon.STUNTestRequest + (*STUNTestProgress)(nil), // 34: daemon.STUNTestProgress + (*TailscaleStatusUpdate)(nil), // 35: daemon.TailscaleStatusUpdate + (*TailscaleEndpointStatus)(nil), // 36: daemon.TailscaleEndpointStatus + (*TailscaleUserGroup)(nil), // 37: daemon.TailscaleUserGroup + (*TailscalePeer)(nil), // 38: daemon.TailscalePeer + (*TailscalePingRequest)(nil), // 39: daemon.TailscalePingRequest + (*TailscalePingResponse)(nil), // 40: daemon.TailscalePingResponse + (*SetTailscaleExitNodeRequest)(nil), // 41: daemon.SetTailscaleExitNodeRequest + (*TailscaleSSHClientMessage)(nil), // 42: daemon.TailscaleSSHClientMessage + (*TailscaleSSHStart)(nil), // 43: daemon.TailscaleSSHStart + (*TailscaleSSHInput)(nil), // 44: daemon.TailscaleSSHInput + (*TailscaleSSHResize)(nil), // 45: daemon.TailscaleSSHResize + (*TailscaleSSHServerMessage)(nil), // 46: daemon.TailscaleSSHServerMessage + (*TailscaleSSHAuthBanner)(nil), // 47: daemon.TailscaleSSHAuthBanner + (*TailscaleSSHReady)(nil), // 48: daemon.TailscaleSSHReady + (*TailscaleSSHOutput)(nil), // 49: daemon.TailscaleSSHOutput + (*TailscaleSSHExit)(nil), // 50: daemon.TailscaleSSHExit + (*TailscaleSSHError)(nil), // 51: daemon.TailscaleSSHError + (*Log_Message)(nil), // 52: daemon.Log.Message + (*emptypb.Empty)(nil), // 53: google.protobuf.Empty + } +) + +var file_daemon_started_service_proto_depIdxs = []int32{ + 2, // 0: daemon.ServiceStatus.status:type_name -> daemon.ServiceStatus.Type + 52, // 1: daemon.Log.messages:type_name -> daemon.Log.Message + 0, // 2: daemon.DefaultLogLevel.level:type_name -> daemon.LogLevel + 11, // 3: daemon.Groups.group:type_name -> daemon.Group + 12, // 4: daemon.Group.items:type_name -> daemon.GroupItem + 3, // 5: daemon.DebugCrashRequest.type:type_name -> daemon.DebugCrashRequest.Type + 1, // 6: daemon.ConnectionEvent.type:type_name -> daemon.ConnectionEventType + 24, // 7: daemon.ConnectionEvent.connection:type_name -> daemon.Connection + 22, // 8: daemon.ConnectionEvents.events:type_name -> daemon.ConnectionEvent + 25, // 9: daemon.Connection.processInfo:type_name -> daemon.ProcessInfo + 28, // 10: daemon.DeprecatedWarnings.warnings:type_name -> daemon.DeprecatedWarning + 12, // 11: daemon.OutboundList.outbounds:type_name -> daemon.GroupItem + 36, // 12: daemon.TailscaleStatusUpdate.endpoints:type_name -> daemon.TailscaleEndpointStatus + 38, // 13: daemon.TailscaleEndpointStatus.self:type_name -> daemon.TailscalePeer + 37, // 14: daemon.TailscaleEndpointStatus.userGroups:type_name -> daemon.TailscaleUserGroup + 38, // 15: daemon.TailscaleEndpointStatus.exitNode:type_name -> daemon.TailscalePeer + 38, // 16: daemon.TailscaleUserGroup.peers:type_name -> daemon.TailscalePeer + 43, // 17: daemon.TailscaleSSHClientMessage.start:type_name -> daemon.TailscaleSSHStart + 44, // 18: daemon.TailscaleSSHClientMessage.input:type_name -> daemon.TailscaleSSHInput + 45, // 19: daemon.TailscaleSSHClientMessage.resize:type_name -> daemon.TailscaleSSHResize + 47, // 20: daemon.TailscaleSSHServerMessage.authBanner:type_name -> daemon.TailscaleSSHAuthBanner + 48, // 21: daemon.TailscaleSSHServerMessage.ready:type_name -> daemon.TailscaleSSHReady + 49, // 22: daemon.TailscaleSSHServerMessage.output:type_name -> daemon.TailscaleSSHOutput + 50, // 23: daemon.TailscaleSSHServerMessage.exit:type_name -> daemon.TailscaleSSHExit + 51, // 24: daemon.TailscaleSSHServerMessage.error:type_name -> daemon.TailscaleSSHError + 0, // 25: daemon.Log.Message.level:type_name -> daemon.LogLevel + 53, // 26: daemon.StartedService.StopService:input_type -> google.protobuf.Empty + 53, // 27: daemon.StartedService.ReloadService:input_type -> google.protobuf.Empty + 53, // 28: daemon.StartedService.SubscribeServiceStatus:input_type -> google.protobuf.Empty + 53, // 29: daemon.StartedService.SubscribeLog:input_type -> google.protobuf.Empty + 53, // 30: daemon.StartedService.GetDefaultLogLevel:input_type -> google.protobuf.Empty + 53, // 31: daemon.StartedService.ClearLogs:input_type -> google.protobuf.Empty + 6, // 32: daemon.StartedService.SubscribeStatus:input_type -> daemon.SubscribeStatusRequest + 53, // 33: daemon.StartedService.SubscribeGroups:input_type -> google.protobuf.Empty + 53, // 34: daemon.StartedService.GetClashModeStatus:input_type -> google.protobuf.Empty + 53, // 35: daemon.StartedService.SubscribeClashMode:input_type -> google.protobuf.Empty + 16, // 36: daemon.StartedService.SetClashMode:input_type -> daemon.ClashMode + 13, // 37: daemon.StartedService.URLTest:input_type -> daemon.URLTestRequest + 14, // 38: daemon.StartedService.SelectOutbound:input_type -> daemon.SelectOutboundRequest + 15, // 39: daemon.StartedService.SetGroupExpand:input_type -> daemon.SetGroupExpandRequest + 53, // 40: daemon.StartedService.GetSystemProxyStatus:input_type -> google.protobuf.Empty + 19, // 41: daemon.StartedService.SetSystemProxyEnabled:input_type -> daemon.SetSystemProxyEnabledRequest + 20, // 42: daemon.StartedService.TriggerDebugCrash:input_type -> daemon.DebugCrashRequest + 53, // 43: daemon.StartedService.TriggerOOMReport:input_type -> google.protobuf.Empty + 21, // 44: daemon.StartedService.SubscribeConnections:input_type -> daemon.SubscribeConnectionsRequest + 26, // 45: daemon.StartedService.CloseConnection:input_type -> daemon.CloseConnectionRequest + 53, // 46: daemon.StartedService.CloseAllConnections:input_type -> google.protobuf.Empty + 53, // 47: daemon.StartedService.GetDeprecatedWarnings:input_type -> google.protobuf.Empty + 53, // 48: daemon.StartedService.GetStartedAt:input_type -> google.protobuf.Empty + 53, // 49: daemon.StartedService.SubscribeOutbounds:input_type -> google.protobuf.Empty + 31, // 50: daemon.StartedService.StartNetworkQualityTest:input_type -> daemon.NetworkQualityTestRequest + 33, // 51: daemon.StartedService.StartSTUNTest:input_type -> daemon.STUNTestRequest + 53, // 52: daemon.StartedService.SubscribeTailscaleStatus:input_type -> google.protobuf.Empty + 39, // 53: daemon.StartedService.StartTailscalePing:input_type -> daemon.TailscalePingRequest + 41, // 54: daemon.StartedService.SetTailscaleExitNode:input_type -> daemon.SetTailscaleExitNodeRequest + 42, // 55: daemon.StartedService.StartTailscaleSSHSession:input_type -> daemon.TailscaleSSHClientMessage + 53, // 56: daemon.StartedService.StopService:output_type -> google.protobuf.Empty + 53, // 57: daemon.StartedService.ReloadService:output_type -> google.protobuf.Empty + 4, // 58: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus + 7, // 59: daemon.StartedService.SubscribeLog:output_type -> daemon.Log + 8, // 60: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel + 53, // 61: daemon.StartedService.ClearLogs:output_type -> google.protobuf.Empty + 9, // 62: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status + 10, // 63: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups + 17, // 64: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus + 16, // 65: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode + 53, // 66: daemon.StartedService.SetClashMode:output_type -> google.protobuf.Empty + 53, // 67: daemon.StartedService.URLTest:output_type -> google.protobuf.Empty + 53, // 68: daemon.StartedService.SelectOutbound:output_type -> google.protobuf.Empty + 53, // 69: daemon.StartedService.SetGroupExpand:output_type -> google.protobuf.Empty + 18, // 70: daemon.StartedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus + 53, // 71: daemon.StartedService.SetSystemProxyEnabled:output_type -> google.protobuf.Empty + 53, // 72: daemon.StartedService.TriggerDebugCrash:output_type -> google.protobuf.Empty + 53, // 73: daemon.StartedService.TriggerOOMReport:output_type -> google.protobuf.Empty + 23, // 74: daemon.StartedService.SubscribeConnections:output_type -> daemon.ConnectionEvents + 53, // 75: daemon.StartedService.CloseConnection:output_type -> google.protobuf.Empty + 53, // 76: daemon.StartedService.CloseAllConnections:output_type -> google.protobuf.Empty + 27, // 77: daemon.StartedService.GetDeprecatedWarnings:output_type -> daemon.DeprecatedWarnings + 29, // 78: daemon.StartedService.GetStartedAt:output_type -> daemon.StartedAt + 30, // 79: daemon.StartedService.SubscribeOutbounds:output_type -> daemon.OutboundList + 32, // 80: daemon.StartedService.StartNetworkQualityTest:output_type -> daemon.NetworkQualityTestProgress + 34, // 81: daemon.StartedService.StartSTUNTest:output_type -> daemon.STUNTestProgress + 35, // 82: daemon.StartedService.SubscribeTailscaleStatus:output_type -> daemon.TailscaleStatusUpdate + 40, // 83: daemon.StartedService.StartTailscalePing:output_type -> daemon.TailscalePingResponse + 53, // 84: daemon.StartedService.SetTailscaleExitNode:output_type -> google.protobuf.Empty + 46, // 85: daemon.StartedService.StartTailscaleSSHSession:output_type -> daemon.TailscaleSSHServerMessage + 56, // [56:86] is the sub-list for method output_type + 26, // [26:56] is the sub-list for method input_type + 26, // [26:26] is the sub-list for extension type_name + 26, // [26:26] is the sub-list for extension extendee + 0, // [0:26] is the sub-list for field type_name +} + +func init() { file_daemon_started_service_proto_init() } +func file_daemon_started_service_proto_init() { + if File_daemon_started_service_proto != nil { + return + } + file_daemon_started_service_proto_msgTypes[38].OneofWrappers = []any{ + (*TailscaleSSHClientMessage_Start)(nil), + (*TailscaleSSHClientMessage_Input)(nil), + (*TailscaleSSHClientMessage_Resize)(nil), + } + file_daemon_started_service_proto_msgTypes[42].OneofWrappers = []any{ + (*TailscaleSSHServerMessage_AuthBanner)(nil), + (*TailscaleSSHServerMessage_Ready)(nil), + (*TailscaleSSHServerMessage_Output)(nil), + (*TailscaleSSHServerMessage_Exit)(nil), + (*TailscaleSSHServerMessage_Error)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_started_service_proto_rawDesc), len(file_daemon_started_service_proto_rawDesc)), + NumEnums: 4, + NumMessages: 49, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_daemon_started_service_proto_goTypes, + DependencyIndexes: file_daemon_started_service_proto_depIdxs, + EnumInfos: file_daemon_started_service_proto_enumTypes, + MessageInfos: file_daemon_started_service_proto_msgTypes, + }.Build() + File_daemon_started_service_proto = out.File + file_daemon_started_service_proto_goTypes = nil + file_daemon_started_service_proto_depIdxs = nil +} diff --git a/daemon/started_service.proto b/daemon/started_service.proto new file mode 100644 index 0000000000..e74b8f7008 --- /dev/null +++ b/daemon/started_service.proto @@ -0,0 +1,407 @@ +syntax = "proto3"; + +package daemon; +option go_package = "github.com/sagernet/sing-box/daemon"; + +import "google/protobuf/empty.proto"; + +service StartedService { + rpc StopService(google.protobuf.Empty) returns (google.protobuf.Empty); + rpc ReloadService(google.protobuf.Empty) returns (google.protobuf.Empty); + + rpc SubscribeServiceStatus(google.protobuf.Empty) returns(stream ServiceStatus) {} + rpc SubscribeLog(google.protobuf.Empty) returns(stream Log) {} + rpc GetDefaultLogLevel(google.protobuf.Empty) returns(DefaultLogLevel) {} + rpc ClearLogs(google.protobuf.Empty) returns(google.protobuf.Empty) {} + rpc SubscribeStatus(SubscribeStatusRequest) returns(stream Status) {} + rpc SubscribeGroups(google.protobuf.Empty) returns(stream Groups) {} + + rpc GetClashModeStatus(google.protobuf.Empty) returns(ClashModeStatus) {} + rpc SubscribeClashMode(google.protobuf.Empty) returns(stream ClashMode) {} + rpc SetClashMode(ClashMode) returns(google.protobuf.Empty) {} + + rpc URLTest(URLTestRequest) returns(google.protobuf.Empty) {} + rpc SelectOutbound(SelectOutboundRequest) returns (google.protobuf.Empty) {} + rpc SetGroupExpand(SetGroupExpandRequest) returns (google.protobuf.Empty) {} + + rpc GetSystemProxyStatus(google.protobuf.Empty) returns(SystemProxyStatus) {} + rpc SetSystemProxyEnabled(SetSystemProxyEnabledRequest) returns(google.protobuf.Empty) {} + rpc TriggerDebugCrash(DebugCrashRequest) returns(google.protobuf.Empty) {} + rpc TriggerOOMReport(google.protobuf.Empty) returns(google.protobuf.Empty) {} + + rpc SubscribeConnections(SubscribeConnectionsRequest) returns(stream ConnectionEvents) {} + rpc CloseConnection(CloseConnectionRequest) returns(google.protobuf.Empty) {} + rpc CloseAllConnections(google.protobuf.Empty) returns(google.protobuf.Empty) {} + rpc GetDeprecatedWarnings(google.protobuf.Empty) returns(DeprecatedWarnings) {} + rpc GetStartedAt(google.protobuf.Empty) returns(StartedAt) {} + + rpc SubscribeOutbounds(google.protobuf.Empty) returns (stream OutboundList) {} + rpc StartNetworkQualityTest(NetworkQualityTestRequest) returns (stream NetworkQualityTestProgress) {} + rpc StartSTUNTest(STUNTestRequest) returns (stream STUNTestProgress) {} + rpc SubscribeTailscaleStatus(google.protobuf.Empty) returns (stream TailscaleStatusUpdate) {} + rpc StartTailscalePing(TailscalePingRequest) returns (stream TailscalePingResponse) {} + rpc SetTailscaleExitNode(SetTailscaleExitNodeRequest) returns (google.protobuf.Empty) {} + rpc StartTailscaleSSHSession(stream TailscaleSSHClientMessage) returns (stream TailscaleSSHServerMessage) {} +} + +message ServiceStatus { + enum Type { + IDLE = 0; + STARTING = 1; + STARTED = 2; + STOPPING = 3; + FATAL = 4; + } + Type status = 1; + string errorMessage = 2; +} + +message ReloadServiceRequest { + string newProfileContent = 1; +} + +message SubscribeStatusRequest { + int64 interval = 1; +} + +enum LogLevel { + PANIC = 0; + FATAL = 1; + ERROR = 2; + WARN = 3; + INFO = 4; + DEBUG = 5; + TRACE = 6; +} + +message Log { + repeated Message messages = 1; + bool reset = 2; + message Message { + LogLevel level = 1; + string message = 2; + } +} + +message DefaultLogLevel { + LogLevel level = 1; +} + +message Status { + uint64 memory = 1; + int32 goroutines = 2; + int32 connectionsIn = 3; + int32 connectionsOut = 4; + bool trafficAvailable = 5; + int64 uplink = 6; + int64 downlink = 7; + int64 uplinkTotal = 8; + int64 downlinkTotal = 9; +} + +message Groups { + repeated Group group = 1; +} + +message Group { + string tag = 1; + string type = 2; + bool selectable = 3; + string selected = 4; + bool isExpand = 5; + repeated GroupItem items = 6; +} + +message GroupItem { + string tag = 1; + string type = 2; + int64 urlTestTime = 3; + int32 urlTestDelay = 4; +} + +message URLTestRequest { + string outboundTag = 1; +} + +message SelectOutboundRequest { + string groupTag = 1; + string outboundTag = 2; +} + +message SetGroupExpandRequest { + string groupTag = 1; + bool isExpand = 2; +} + +message ClashMode { + string mode = 3; +} + +message ClashModeStatus { + repeated string modeList = 1; + string currentMode = 2; +} + +message SystemProxyStatus { + bool available = 1; + bool enabled = 2; +} + +message SetSystemProxyEnabledRequest { + bool enabled = 1; +} + +message DebugCrashRequest { + enum Type { + GO = 0; + NATIVE = 1; + } + + Type type = 1; +} + +message SubscribeConnectionsRequest { + int64 interval = 1; +} + +enum ConnectionEventType { + CONNECTION_EVENT_NEW = 0; + CONNECTION_EVENT_UPDATE = 1; + CONNECTION_EVENT_CLOSED = 2; +} + +message ConnectionEvent { + ConnectionEventType type = 1; + string id = 2; + Connection connection = 3; + int64 uplinkDelta = 4; + int64 downlinkDelta = 5; + int64 closedAt = 6; +} + +message ConnectionEvents { + repeated ConnectionEvent events = 1; + bool reset = 2; +} + +message Connection { + string id = 1; + string inbound = 2; + string inboundType = 3; + int32 ipVersion = 4; + string network = 5; + string source = 6; + string destination = 7; + string domain = 8; + string protocol = 9; + string user = 10; + string fromOutbound = 11; + int64 createdAt = 12; + int64 closedAt = 13; + int64 uplink = 14; + int64 downlink = 15; + int64 uplinkTotal = 16; + int64 downlinkTotal = 17; + string rule = 18; + string outbound = 19; + string outboundType = 20; + repeated string chainList = 21; + ProcessInfo processInfo = 22; +} + +message ProcessInfo { + uint32 processId = 1; + int32 userId = 2; + string userName = 3; + string processPath = 4; + repeated string packageNames = 5; +} + +message CloseConnectionRequest { + string id = 1; +} + +message DeprecatedWarnings { + repeated DeprecatedWarning warnings = 1; +} + +message DeprecatedWarning { + string message = 1; + bool impending = 2; + string migrationLink = 3; + string description = 4; + string deprecatedVersion = 5; + string scheduledVersion = 6; +} + +message StartedAt { + int64 startedAt = 1; +} + +message OutboundList { + repeated GroupItem outbounds = 1; +} + +message NetworkQualityTestRequest { + string configURL = 1; + string outboundTag = 2; + bool serial = 3; + int32 maxRuntimeSeconds = 4; + bool http3 = 5; +} + +message NetworkQualityTestProgress { + int32 phase = 1; + int64 downloadCapacity = 2; + int64 uploadCapacity = 3; + int32 downloadRPM = 4; + int32 uploadRPM = 5; + int32 idleLatencyMs = 6; + int64 elapsedMs = 7; + bool isFinal = 8; + string error = 9; + int32 downloadCapacityAccuracy = 10; + int32 uploadCapacityAccuracy = 11; + int32 downloadRPMAccuracy = 12; + int32 uploadRPMAccuracy = 13; +} + +message STUNTestRequest { + string server = 1; + string outboundTag = 2; +} + +message STUNTestProgress { + int32 phase = 1; + string externalAddr = 2; + int32 latencyMs = 3; + int32 natMapping = 4; + int32 natFiltering = 5; + bool isFinal = 6; + string error = 7; + bool natTypeSupported = 8; +} + +message TailscaleStatusUpdate { + repeated TailscaleEndpointStatus endpoints = 1; +} + +message TailscaleEndpointStatus { + string endpointTag = 1; + string backendState = 2; + string authURL = 3; + string networkName = 4; + string magicDNSSuffix = 5; + TailscalePeer self = 6; + repeated TailscaleUserGroup userGroups = 7; + TailscalePeer exitNode = 8; +} + +message TailscaleUserGroup { + int64 userID = 1; + string loginName = 2; + string displayName = 3; + string profilePicURL = 4; + repeated TailscalePeer peers = 5; +} + +message TailscalePeer { + string hostName = 1; + string dnsName = 2; + string os = 3; + repeated string tailscaleIPs = 4; + bool online = 5; + bool exitNode = 6; + bool exitNodeOption = 7; + bool active = 8; + int64 rxBytes = 9; + int64 txBytes = 10; + int64 keyExpiry = 11; + string stableID = 12; + bool expired = 13; + repeated string sshHostKeys = 14; + bool shareeNode = 15; + int64 lastSeen = 16; +} + +message TailscalePingRequest { + string endpointTag = 1; + string peerIP = 2; +} + +message TailscalePingResponse { + double latencyMs = 1; + bool isDirect = 2; + string endpoint = 3; + int32 derpRegionID = 4; + string derpRegionCode = 5; + string error = 6; +} + +message SetTailscaleExitNodeRequest { + string endpointTag = 1; + string stableID = 2; +} + +message TailscaleSSHClientMessage { + oneof message { + TailscaleSSHStart start = 1; + TailscaleSSHInput input = 2; + TailscaleSSHResize resize = 3; + } +} + +message TailscaleSSHStart { + string endpointTag = 1; + string peerAddress = 2; + string username = 3; + string terminalType = 4; + int32 columns = 5; + int32 rows = 6; + int32 widthPixels = 7; + int32 heightPixels = 8; + repeated string hostKeys = 9; + bool forward_agent = 10; +} + +message TailscaleSSHInput { + bytes data = 1; +} + +message TailscaleSSHResize { + int32 columns = 1; + int32 rows = 2; + int32 widthPixels = 3; + int32 heightPixels = 4; +} + +message TailscaleSSHServerMessage { + oneof message { + TailscaleSSHAuthBanner authBanner = 1; + TailscaleSSHReady ready = 2; + TailscaleSSHOutput output = 3; + TailscaleSSHExit exit = 4; + TailscaleSSHError error = 5; + } +} + +message TailscaleSSHAuthBanner { + string message = 1; +} + +message TailscaleSSHReady { +} + +message TailscaleSSHOutput { + bytes data = 1; +} + +message TailscaleSSHExit { + int32 exitCode = 1; + string signal = 2; + string errorMessage = 3; +} + +message TailscaleSSHError { + string message = 1; +} diff --git a/daemon/started_service_grpc.pb.go b/daemon/started_service_grpc.pb.go new file mode 100644 index 0000000000..02a7c32284 --- /dev/null +++ b/daemon/started_service_grpc.pb.go @@ -0,0 +1,1276 @@ +package daemon + +import ( + context "context" + + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + StartedService_StopService_FullMethodName = "/daemon.StartedService/StopService" + StartedService_ReloadService_FullMethodName = "/daemon.StartedService/ReloadService" + StartedService_SubscribeServiceStatus_FullMethodName = "/daemon.StartedService/SubscribeServiceStatus" + StartedService_SubscribeLog_FullMethodName = "/daemon.StartedService/SubscribeLog" + StartedService_GetDefaultLogLevel_FullMethodName = "/daemon.StartedService/GetDefaultLogLevel" + StartedService_ClearLogs_FullMethodName = "/daemon.StartedService/ClearLogs" + StartedService_SubscribeStatus_FullMethodName = "/daemon.StartedService/SubscribeStatus" + StartedService_SubscribeGroups_FullMethodName = "/daemon.StartedService/SubscribeGroups" + StartedService_GetClashModeStatus_FullMethodName = "/daemon.StartedService/GetClashModeStatus" + StartedService_SubscribeClashMode_FullMethodName = "/daemon.StartedService/SubscribeClashMode" + StartedService_SetClashMode_FullMethodName = "/daemon.StartedService/SetClashMode" + StartedService_URLTest_FullMethodName = "/daemon.StartedService/URLTest" + StartedService_SelectOutbound_FullMethodName = "/daemon.StartedService/SelectOutbound" + StartedService_SetGroupExpand_FullMethodName = "/daemon.StartedService/SetGroupExpand" + StartedService_GetSystemProxyStatus_FullMethodName = "/daemon.StartedService/GetSystemProxyStatus" + StartedService_SetSystemProxyEnabled_FullMethodName = "/daemon.StartedService/SetSystemProxyEnabled" + StartedService_TriggerDebugCrash_FullMethodName = "/daemon.StartedService/TriggerDebugCrash" + StartedService_TriggerOOMReport_FullMethodName = "/daemon.StartedService/TriggerOOMReport" + StartedService_SubscribeConnections_FullMethodName = "/daemon.StartedService/SubscribeConnections" + StartedService_CloseConnection_FullMethodName = "/daemon.StartedService/CloseConnection" + StartedService_CloseAllConnections_FullMethodName = "/daemon.StartedService/CloseAllConnections" + StartedService_GetDeprecatedWarnings_FullMethodName = "/daemon.StartedService/GetDeprecatedWarnings" + StartedService_GetStartedAt_FullMethodName = "/daemon.StartedService/GetStartedAt" + StartedService_SubscribeOutbounds_FullMethodName = "/daemon.StartedService/SubscribeOutbounds" + StartedService_StartNetworkQualityTest_FullMethodName = "/daemon.StartedService/StartNetworkQualityTest" + StartedService_StartSTUNTest_FullMethodName = "/daemon.StartedService/StartSTUNTest" + StartedService_SubscribeTailscaleStatus_FullMethodName = "/daemon.StartedService/SubscribeTailscaleStatus" + StartedService_StartTailscalePing_FullMethodName = "/daemon.StartedService/StartTailscalePing" + StartedService_SetTailscaleExitNode_FullMethodName = "/daemon.StartedService/SetTailscaleExitNode" + StartedService_StartTailscaleSSHSession_FullMethodName = "/daemon.StartedService/StartTailscaleSSHSession" +) + +// StartedServiceClient is the client API for StartedService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type StartedServiceClient interface { + StopService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) + ReloadService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) + SubscribeServiceStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ServiceStatus], error) + SubscribeLog(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Log], error) + GetDefaultLogLevel(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DefaultLogLevel, error) + ClearLogs(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) + SubscribeStatus(ctx context.Context, in *SubscribeStatusRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Status], error) + SubscribeGroups(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Groups], error) + GetClashModeStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ClashModeStatus, error) + SubscribeClashMode(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ClashMode], error) + SetClashMode(ctx context.Context, in *ClashMode, opts ...grpc.CallOption) (*emptypb.Empty, error) + URLTest(ctx context.Context, in *URLTestRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + SelectOutbound(ctx context.Context, in *SelectOutboundRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + SetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + GetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error) + SetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + TriggerDebugCrash(ctx context.Context, in *DebugCrashRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + TriggerOOMReport(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) + SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error) + CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) + GetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error) + GetStartedAt(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*StartedAt, error) + SubscribeOutbounds(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[OutboundList], error) + StartNetworkQualityTest(ctx context.Context, in *NetworkQualityTestRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[NetworkQualityTestProgress], error) + StartSTUNTest(ctx context.Context, in *STUNTestRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[STUNTestProgress], error) + SubscribeTailscaleStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TailscaleStatusUpdate], error) + StartTailscalePing(ctx context.Context, in *TailscalePingRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TailscalePingResponse], error) + SetTailscaleExitNode(ctx context.Context, in *SetTailscaleExitNodeRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + StartTailscaleSSHSession(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[TailscaleSSHClientMessage, TailscaleSSHServerMessage], error) +} + +type startedServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewStartedServiceClient(cc grpc.ClientConnInterface) StartedServiceClient { + return &startedServiceClient{cc} +} + +func (c *startedServiceClient) StopService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_StopService_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) ReloadService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_ReloadService_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SubscribeServiceStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ServiceStatus], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[0], StartedService_SubscribeServiceStatus_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[emptypb.Empty, ServiceStatus]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeServiceStatusClient = grpc.ServerStreamingClient[ServiceStatus] + +func (c *startedServiceClient) SubscribeLog(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Log], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[1], StartedService_SubscribeLog_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[emptypb.Empty, Log]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeLogClient = grpc.ServerStreamingClient[Log] + +func (c *startedServiceClient) GetDefaultLogLevel(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DefaultLogLevel, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DefaultLogLevel) + err := c.cc.Invoke(ctx, StartedService_GetDefaultLogLevel_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) ClearLogs(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_ClearLogs_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SubscribeStatus(ctx context.Context, in *SubscribeStatusRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Status], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[2], StartedService_SubscribeStatus_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[SubscribeStatusRequest, Status]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeStatusClient = grpc.ServerStreamingClient[Status] + +func (c *startedServiceClient) SubscribeGroups(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Groups], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[3], StartedService_SubscribeGroups_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[emptypb.Empty, Groups]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeGroupsClient = grpc.ServerStreamingClient[Groups] + +func (c *startedServiceClient) GetClashModeStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ClashModeStatus, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ClashModeStatus) + err := c.cc.Invoke(ctx, StartedService_GetClashModeStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SubscribeClashMode(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ClashMode], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[4], StartedService_SubscribeClashMode_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[emptypb.Empty, ClashMode]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeClashModeClient = grpc.ServerStreamingClient[ClashMode] + +func (c *startedServiceClient) SetClashMode(ctx context.Context, in *ClashMode, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_SetClashMode_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) URLTest(ctx context.Context, in *URLTestRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_URLTest_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SelectOutbound(ctx context.Context, in *SelectOutboundRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_SelectOutbound_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_SetGroupExpand_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) GetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SystemProxyStatus) + err := c.cc.Invoke(ctx, StartedService_GetSystemProxyStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_SetSystemProxyEnabled_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) TriggerDebugCrash(ctx context.Context, in *DebugCrashRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_TriggerDebugCrash_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) TriggerOOMReport(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_TriggerOOMReport_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[5], StartedService_SubscribeConnections_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[SubscribeConnectionsRequest, ConnectionEvents]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeConnectionsClient = grpc.ServerStreamingClient[ConnectionEvents] + +func (c *startedServiceClient) CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_CloseConnection_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_CloseAllConnections_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) GetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeprecatedWarnings) + err := c.cc.Invoke(ctx, StartedService_GetDeprecatedWarnings_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) GetStartedAt(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*StartedAt, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(StartedAt) + err := c.cc.Invoke(ctx, StartedService_GetStartedAt_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SubscribeOutbounds(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[OutboundList], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[6], StartedService_SubscribeOutbounds_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[emptypb.Empty, OutboundList]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeOutboundsClient = grpc.ServerStreamingClient[OutboundList] + +func (c *startedServiceClient) StartNetworkQualityTest(ctx context.Context, in *NetworkQualityTestRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[NetworkQualityTestProgress], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[7], StartedService_StartNetworkQualityTest_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[NetworkQualityTestRequest, NetworkQualityTestProgress]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_StartNetworkQualityTestClient = grpc.ServerStreamingClient[NetworkQualityTestProgress] + +func (c *startedServiceClient) StartSTUNTest(ctx context.Context, in *STUNTestRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[STUNTestProgress], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[8], StartedService_StartSTUNTest_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[STUNTestRequest, STUNTestProgress]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_StartSTUNTestClient = grpc.ServerStreamingClient[STUNTestProgress] + +func (c *startedServiceClient) SubscribeTailscaleStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TailscaleStatusUpdate], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[9], StartedService_SubscribeTailscaleStatus_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[emptypb.Empty, TailscaleStatusUpdate]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeTailscaleStatusClient = grpc.ServerStreamingClient[TailscaleStatusUpdate] + +func (c *startedServiceClient) StartTailscalePing(ctx context.Context, in *TailscalePingRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TailscalePingResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[10], StartedService_StartTailscalePing_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[TailscalePingRequest, TailscalePingResponse]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_StartTailscalePingClient = grpc.ServerStreamingClient[TailscalePingResponse] + +func (c *startedServiceClient) SetTailscaleExitNode(ctx context.Context, in *SetTailscaleExitNodeRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_SetTailscaleExitNode_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) StartTailscaleSSHSession(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[TailscaleSSHClientMessage, TailscaleSSHServerMessage], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[11], StartedService_StartTailscaleSSHSession_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[TailscaleSSHClientMessage, TailscaleSSHServerMessage]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_StartTailscaleSSHSessionClient = grpc.BidiStreamingClient[TailscaleSSHClientMessage, TailscaleSSHServerMessage] + +// StartedServiceServer is the server API for StartedService service. +// All implementations must embed UnimplementedStartedServiceServer +// for forward compatibility. +type StartedServiceServer interface { + StopService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) + ReloadService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) + SubscribeServiceStatus(*emptypb.Empty, grpc.ServerStreamingServer[ServiceStatus]) error + SubscribeLog(*emptypb.Empty, grpc.ServerStreamingServer[Log]) error + GetDefaultLogLevel(context.Context, *emptypb.Empty) (*DefaultLogLevel, error) + ClearLogs(context.Context, *emptypb.Empty) (*emptypb.Empty, error) + SubscribeStatus(*SubscribeStatusRequest, grpc.ServerStreamingServer[Status]) error + SubscribeGroups(*emptypb.Empty, grpc.ServerStreamingServer[Groups]) error + GetClashModeStatus(context.Context, *emptypb.Empty) (*ClashModeStatus, error) + SubscribeClashMode(*emptypb.Empty, grpc.ServerStreamingServer[ClashMode]) error + SetClashMode(context.Context, *ClashMode) (*emptypb.Empty, error) + URLTest(context.Context, *URLTestRequest) (*emptypb.Empty, error) + SelectOutbound(context.Context, *SelectOutboundRequest) (*emptypb.Empty, error) + SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error) + GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error) + SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error) + TriggerDebugCrash(context.Context, *DebugCrashRequest) (*emptypb.Empty, error) + TriggerOOMReport(context.Context, *emptypb.Empty) (*emptypb.Empty, error) + SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error + CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error) + CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error) + GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error) + GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error) + SubscribeOutbounds(*emptypb.Empty, grpc.ServerStreamingServer[OutboundList]) error + StartNetworkQualityTest(*NetworkQualityTestRequest, grpc.ServerStreamingServer[NetworkQualityTestProgress]) error + StartSTUNTest(*STUNTestRequest, grpc.ServerStreamingServer[STUNTestProgress]) error + SubscribeTailscaleStatus(*emptypb.Empty, grpc.ServerStreamingServer[TailscaleStatusUpdate]) error + StartTailscalePing(*TailscalePingRequest, grpc.ServerStreamingServer[TailscalePingResponse]) error + SetTailscaleExitNode(context.Context, *SetTailscaleExitNodeRequest) (*emptypb.Empty, error) + StartTailscaleSSHSession(grpc.BidiStreamingServer[TailscaleSSHClientMessage, TailscaleSSHServerMessage]) error + mustEmbedUnimplementedStartedServiceServer() +} + +// UnimplementedStartedServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedStartedServiceServer struct{} + +func (UnimplementedStartedServiceServer) StopService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method StopService not implemented") +} + +func (UnimplementedStartedServiceServer) ReloadService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method ReloadService not implemented") +} + +func (UnimplementedStartedServiceServer) SubscribeServiceStatus(*emptypb.Empty, grpc.ServerStreamingServer[ServiceStatus]) error { + return status.Error(codes.Unimplemented, "method SubscribeServiceStatus not implemented") +} + +func (UnimplementedStartedServiceServer) SubscribeLog(*emptypb.Empty, grpc.ServerStreamingServer[Log]) error { + return status.Error(codes.Unimplemented, "method SubscribeLog not implemented") +} + +func (UnimplementedStartedServiceServer) GetDefaultLogLevel(context.Context, *emptypb.Empty) (*DefaultLogLevel, error) { + return nil, status.Error(codes.Unimplemented, "method GetDefaultLogLevel not implemented") +} + +func (UnimplementedStartedServiceServer) ClearLogs(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method ClearLogs not implemented") +} + +func (UnimplementedStartedServiceServer) SubscribeStatus(*SubscribeStatusRequest, grpc.ServerStreamingServer[Status]) error { + return status.Error(codes.Unimplemented, "method SubscribeStatus not implemented") +} + +func (UnimplementedStartedServiceServer) SubscribeGroups(*emptypb.Empty, grpc.ServerStreamingServer[Groups]) error { + return status.Error(codes.Unimplemented, "method SubscribeGroups not implemented") +} + +func (UnimplementedStartedServiceServer) GetClashModeStatus(context.Context, *emptypb.Empty) (*ClashModeStatus, error) { + return nil, status.Error(codes.Unimplemented, "method GetClashModeStatus not implemented") +} + +func (UnimplementedStartedServiceServer) SubscribeClashMode(*emptypb.Empty, grpc.ServerStreamingServer[ClashMode]) error { + return status.Error(codes.Unimplemented, "method SubscribeClashMode not implemented") +} + +func (UnimplementedStartedServiceServer) SetClashMode(context.Context, *ClashMode) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method SetClashMode not implemented") +} + +func (UnimplementedStartedServiceServer) URLTest(context.Context, *URLTestRequest) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method URLTest not implemented") +} + +func (UnimplementedStartedServiceServer) SelectOutbound(context.Context, *SelectOutboundRequest) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method SelectOutbound not implemented") +} + +func (UnimplementedStartedServiceServer) SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method SetGroupExpand not implemented") +} + +func (UnimplementedStartedServiceServer) GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error) { + return nil, status.Error(codes.Unimplemented, "method GetSystemProxyStatus not implemented") +} + +func (UnimplementedStartedServiceServer) SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method SetSystemProxyEnabled not implemented") +} + +func (UnimplementedStartedServiceServer) TriggerDebugCrash(context.Context, *DebugCrashRequest) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method TriggerDebugCrash not implemented") +} + +func (UnimplementedStartedServiceServer) TriggerOOMReport(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method TriggerOOMReport not implemented") +} + +func (UnimplementedStartedServiceServer) SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error { + return status.Error(codes.Unimplemented, "method SubscribeConnections not implemented") +} + +func (UnimplementedStartedServiceServer) CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method CloseConnection not implemented") +} + +func (UnimplementedStartedServiceServer) CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method CloseAllConnections not implemented") +} + +func (UnimplementedStartedServiceServer) GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error) { + return nil, status.Error(codes.Unimplemented, "method GetDeprecatedWarnings not implemented") +} + +func (UnimplementedStartedServiceServer) GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error) { + return nil, status.Error(codes.Unimplemented, "method GetStartedAt not implemented") +} + +func (UnimplementedStartedServiceServer) SubscribeOutbounds(*emptypb.Empty, grpc.ServerStreamingServer[OutboundList]) error { + return status.Error(codes.Unimplemented, "method SubscribeOutbounds not implemented") +} + +func (UnimplementedStartedServiceServer) StartNetworkQualityTest(*NetworkQualityTestRequest, grpc.ServerStreamingServer[NetworkQualityTestProgress]) error { + return status.Error(codes.Unimplemented, "method StartNetworkQualityTest not implemented") +} + +func (UnimplementedStartedServiceServer) StartSTUNTest(*STUNTestRequest, grpc.ServerStreamingServer[STUNTestProgress]) error { + return status.Error(codes.Unimplemented, "method StartSTUNTest not implemented") +} + +func (UnimplementedStartedServiceServer) SubscribeTailscaleStatus(*emptypb.Empty, grpc.ServerStreamingServer[TailscaleStatusUpdate]) error { + return status.Error(codes.Unimplemented, "method SubscribeTailscaleStatus not implemented") +} + +func (UnimplementedStartedServiceServer) StartTailscalePing(*TailscalePingRequest, grpc.ServerStreamingServer[TailscalePingResponse]) error { + return status.Error(codes.Unimplemented, "method StartTailscalePing not implemented") +} + +func (UnimplementedStartedServiceServer) SetTailscaleExitNode(context.Context, *SetTailscaleExitNodeRequest) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method SetTailscaleExitNode not implemented") +} + +func (UnimplementedStartedServiceServer) StartTailscaleSSHSession(grpc.BidiStreamingServer[TailscaleSSHClientMessage, TailscaleSSHServerMessage]) error { + return status.Error(codes.Unimplemented, "method StartTailscaleSSHSession not implemented") +} +func (UnimplementedStartedServiceServer) mustEmbedUnimplementedStartedServiceServer() {} +func (UnimplementedStartedServiceServer) testEmbeddedByValue() {} + +// UnsafeStartedServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to StartedServiceServer will +// result in compilation errors. +type UnsafeStartedServiceServer interface { + mustEmbedUnimplementedStartedServiceServer() +} + +func RegisterStartedServiceServer(s grpc.ServiceRegistrar, srv StartedServiceServer) { + // If the following call panics, it indicates UnimplementedStartedServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&StartedService_ServiceDesc, srv) +} + +func _StartedService_StopService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).StopService(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_StopService_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).StopService(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_ReloadService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).ReloadService(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_ReloadService_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).ReloadService(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SubscribeServiceStatus_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(emptypb.Empty) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).SubscribeServiceStatus(m, &grpc.GenericServerStream[emptypb.Empty, ServiceStatus]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeServiceStatusServer = grpc.ServerStreamingServer[ServiceStatus] + +func _StartedService_SubscribeLog_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(emptypb.Empty) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).SubscribeLog(m, &grpc.GenericServerStream[emptypb.Empty, Log]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeLogServer = grpc.ServerStreamingServer[Log] + +func _StartedService_GetDefaultLogLevel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).GetDefaultLogLevel(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_GetDefaultLogLevel_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).GetDefaultLogLevel(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_ClearLogs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).ClearLogs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_ClearLogs_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).ClearLogs(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SubscribeStatus_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(SubscribeStatusRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).SubscribeStatus(m, &grpc.GenericServerStream[SubscribeStatusRequest, Status]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeStatusServer = grpc.ServerStreamingServer[Status] + +func _StartedService_SubscribeGroups_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(emptypb.Empty) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).SubscribeGroups(m, &grpc.GenericServerStream[emptypb.Empty, Groups]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeGroupsServer = grpc.ServerStreamingServer[Groups] + +func _StartedService_GetClashModeStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).GetClashModeStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_GetClashModeStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).GetClashModeStatus(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SubscribeClashMode_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(emptypb.Empty) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).SubscribeClashMode(m, &grpc.GenericServerStream[emptypb.Empty, ClashMode]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeClashModeServer = grpc.ServerStreamingServer[ClashMode] + +func _StartedService_SetClashMode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ClashMode) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).SetClashMode(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_SetClashMode_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).SetClashMode(ctx, req.(*ClashMode)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_URLTest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(URLTestRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).URLTest(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_URLTest_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).URLTest(ctx, req.(*URLTestRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SelectOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SelectOutboundRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).SelectOutbound(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_SelectOutbound_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).SelectOutbound(ctx, req.(*SelectOutboundRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SetGroupExpand_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetGroupExpandRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).SetGroupExpand(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_SetGroupExpand_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).SetGroupExpand(ctx, req.(*SetGroupExpandRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_GetSystemProxyStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).GetSystemProxyStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_GetSystemProxyStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).GetSystemProxyStatus(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SetSystemProxyEnabled_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetSystemProxyEnabledRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).SetSystemProxyEnabled(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_SetSystemProxyEnabled_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).SetSystemProxyEnabled(ctx, req.(*SetSystemProxyEnabledRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_TriggerDebugCrash_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DebugCrashRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).TriggerDebugCrash(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_TriggerDebugCrash_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).TriggerDebugCrash(ctx, req.(*DebugCrashRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_TriggerOOMReport_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).TriggerOOMReport(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_TriggerOOMReport_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).TriggerOOMReport(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SubscribeConnections_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(SubscribeConnectionsRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).SubscribeConnections(m, &grpc.GenericServerStream[SubscribeConnectionsRequest, ConnectionEvents]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeConnectionsServer = grpc.ServerStreamingServer[ConnectionEvents] + +func _StartedService_CloseConnection_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CloseConnectionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).CloseConnection(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_CloseConnection_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).CloseConnection(ctx, req.(*CloseConnectionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_CloseAllConnections_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).CloseAllConnections(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_CloseAllConnections_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).CloseAllConnections(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_GetDeprecatedWarnings_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).GetDeprecatedWarnings(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_GetDeprecatedWarnings_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).GetDeprecatedWarnings(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_GetStartedAt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).GetStartedAt(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_GetStartedAt_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).GetStartedAt(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SubscribeOutbounds_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(emptypb.Empty) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).SubscribeOutbounds(m, &grpc.GenericServerStream[emptypb.Empty, OutboundList]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeOutboundsServer = grpc.ServerStreamingServer[OutboundList] + +func _StartedService_StartNetworkQualityTest_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(NetworkQualityTestRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).StartNetworkQualityTest(m, &grpc.GenericServerStream[NetworkQualityTestRequest, NetworkQualityTestProgress]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_StartNetworkQualityTestServer = grpc.ServerStreamingServer[NetworkQualityTestProgress] + +func _StartedService_StartSTUNTest_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(STUNTestRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).StartSTUNTest(m, &grpc.GenericServerStream[STUNTestRequest, STUNTestProgress]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_StartSTUNTestServer = grpc.ServerStreamingServer[STUNTestProgress] + +func _StartedService_SubscribeTailscaleStatus_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(emptypb.Empty) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).SubscribeTailscaleStatus(m, &grpc.GenericServerStream[emptypb.Empty, TailscaleStatusUpdate]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeTailscaleStatusServer = grpc.ServerStreamingServer[TailscaleStatusUpdate] + +func _StartedService_StartTailscalePing_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(TailscalePingRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).StartTailscalePing(m, &grpc.GenericServerStream[TailscalePingRequest, TailscalePingResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_StartTailscalePingServer = grpc.ServerStreamingServer[TailscalePingResponse] + +func _StartedService_SetTailscaleExitNode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetTailscaleExitNodeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).SetTailscaleExitNode(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_SetTailscaleExitNode_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).SetTailscaleExitNode(ctx, req.(*SetTailscaleExitNodeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_StartTailscaleSSHSession_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(StartedServiceServer).StartTailscaleSSHSession(&grpc.GenericServerStream[TailscaleSSHClientMessage, TailscaleSSHServerMessage]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_StartTailscaleSSHSessionServer = grpc.BidiStreamingServer[TailscaleSSHClientMessage, TailscaleSSHServerMessage] + +// StartedService_ServiceDesc is the grpc.ServiceDesc for StartedService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var StartedService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "daemon.StartedService", + HandlerType: (*StartedServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "StopService", + Handler: _StartedService_StopService_Handler, + }, + { + MethodName: "ReloadService", + Handler: _StartedService_ReloadService_Handler, + }, + { + MethodName: "GetDefaultLogLevel", + Handler: _StartedService_GetDefaultLogLevel_Handler, + }, + { + MethodName: "ClearLogs", + Handler: _StartedService_ClearLogs_Handler, + }, + { + MethodName: "GetClashModeStatus", + Handler: _StartedService_GetClashModeStatus_Handler, + }, + { + MethodName: "SetClashMode", + Handler: _StartedService_SetClashMode_Handler, + }, + { + MethodName: "URLTest", + Handler: _StartedService_URLTest_Handler, + }, + { + MethodName: "SelectOutbound", + Handler: _StartedService_SelectOutbound_Handler, + }, + { + MethodName: "SetGroupExpand", + Handler: _StartedService_SetGroupExpand_Handler, + }, + { + MethodName: "GetSystemProxyStatus", + Handler: _StartedService_GetSystemProxyStatus_Handler, + }, + { + MethodName: "SetSystemProxyEnabled", + Handler: _StartedService_SetSystemProxyEnabled_Handler, + }, + { + MethodName: "TriggerDebugCrash", + Handler: _StartedService_TriggerDebugCrash_Handler, + }, + { + MethodName: "TriggerOOMReport", + Handler: _StartedService_TriggerOOMReport_Handler, + }, + { + MethodName: "CloseConnection", + Handler: _StartedService_CloseConnection_Handler, + }, + { + MethodName: "CloseAllConnections", + Handler: _StartedService_CloseAllConnections_Handler, + }, + { + MethodName: "GetDeprecatedWarnings", + Handler: _StartedService_GetDeprecatedWarnings_Handler, + }, + { + MethodName: "GetStartedAt", + Handler: _StartedService_GetStartedAt_Handler, + }, + { + MethodName: "SetTailscaleExitNode", + Handler: _StartedService_SetTailscaleExitNode_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "SubscribeServiceStatus", + Handler: _StartedService_SubscribeServiceStatus_Handler, + ServerStreams: true, + }, + { + StreamName: "SubscribeLog", + Handler: _StartedService_SubscribeLog_Handler, + ServerStreams: true, + }, + { + StreamName: "SubscribeStatus", + Handler: _StartedService_SubscribeStatus_Handler, + ServerStreams: true, + }, + { + StreamName: "SubscribeGroups", + Handler: _StartedService_SubscribeGroups_Handler, + ServerStreams: true, + }, + { + StreamName: "SubscribeClashMode", + Handler: _StartedService_SubscribeClashMode_Handler, + ServerStreams: true, + }, + { + StreamName: "SubscribeConnections", + Handler: _StartedService_SubscribeConnections_Handler, + ServerStreams: true, + }, + { + StreamName: "SubscribeOutbounds", + Handler: _StartedService_SubscribeOutbounds_Handler, + ServerStreams: true, + }, + { + StreamName: "StartNetworkQualityTest", + Handler: _StartedService_StartNetworkQualityTest_Handler, + ServerStreams: true, + }, + { + StreamName: "StartSTUNTest", + Handler: _StartedService_StartSTUNTest_Handler, + ServerStreams: true, + }, + { + StreamName: "SubscribeTailscaleStatus", + Handler: _StartedService_SubscribeTailscaleStatus_Handler, + ServerStreams: true, + }, + { + StreamName: "StartTailscalePing", + Handler: _StartedService_StartTailscalePing_Handler, + ServerStreams: true, + }, + { + StreamName: "StartTailscaleSSHSession", + Handler: _StartedService_StartTailscaleSSHSession_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "daemon/started_service.proto", +} diff --git a/daemon/started_service_tailscale_ssh.go b/daemon/started_service_tailscale_ssh.go new file mode 100644 index 0000000000..f575e46426 --- /dev/null +++ b/daemon/started_service_tailscale_ssh.go @@ -0,0 +1,340 @@ +package daemon + +import ( + "bytes" + "context" + "io" + "net" + "os" + "strings" + "sync" + "time" + + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/agent" + "google.golang.org/grpc" +) + +type windowChangeRequest struct { + Columns uint32 + Rows uint32 + WidthPixels uint32 + HeightPixels uint32 +} + +func (s *StartedService) StartTailscaleSSHSession( + server grpc.BidiStreamingServer[TailscaleSSHClientMessage, TailscaleSSHServerMessage], +) error { + ctx := server.Context() + err := s.waitForStarted(ctx) + if err != nil { + return err + } + s.serviceAccess.RLock() + boxService := s.instance + s.serviceAccess.RUnlock() + + firstMessage, err := server.Recv() + if err != nil { + return err + } + start := firstMessage.GetStart() + + hostKeys := make([]ssh.PublicKey, 0, len(start.HostKeys)) + for _, line := range start.HostKeys { + key, _, _, _, parseErr := ssh.ParseAuthorizedKey([]byte(line)) + if parseErr != nil { + return E.Cause(parseErr, "parse host key") + } + hostKeys = append(hostKeys, key) + } + + endpoint, err := resolveTailscaleEndpoint(boxService, start.EndpointTag) + if err != nil { + return err + } + + peerAddr := M.ParseSocksaddrHostPort(start.PeerAddress, 22) + + sessionCtx, cancel := context.WithCancel(ctx) + defer cancel() + + var sendAccess sync.Mutex + sendMessage := func(msg *TailscaleSSHServerMessage) { + sendAccess.Lock() + defer sendAccess.Unlock() + sendErr := server.Send(msg) + if sendErr != nil { + cancel() + } + } + + finishWithError := func(message string) error { + sendMessage(&TailscaleSSHServerMessage{ + Message: &TailscaleSSHServerMessage_Error{Error: &TailscaleSSHError{Message: message}}, + }) + return nil + } + + peerConn, err := endpoint.DialContext(ctx, N.NetworkTCP, peerAddr) + if err != nil { + return finishWithError(E.Cause(err, "dial peer").Error()) + } + + var lastBanner string + config := &ssh.ClientConfig{ + User: start.Username, + Auth: nil, + BannerCallback: func(message string) error { + lastBanner = message + sendMessage(&TailscaleSSHServerMessage{ + Message: &TailscaleSSHServerMessage_AuthBanner{ + AuthBanner: &TailscaleSSHAuthBanner{Message: message}, + }, + }) + return nil + }, + HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { + serverKey := key.Marshal() + for _, hostKey := range hostKeys { + if bytes.Equal(serverKey, hostKey.Marshal()) { + return nil + } + } + return E.New("untrusted host key: ", key.Type()) + }, + } + sshConn, chans, reqs, err := ssh.NewClientConn(peerConn, peerAddr.String(), config) + if err != nil { + common.Close(peerConn) + banner := strings.TrimSpace(lastBanner) + if banner != "" { + return finishWithError(banner) + } + return finishWithError(E.Cause(err, "ssh handshake").Error()) + } + sshClient := ssh.NewClient(sshConn, chans, reqs) + + if start.ForwardAgent { + agentChannels := sshClient.HandleChannelOpen("auth-agent@openssh.com") + if agentChannels != nil { + go func() { + for newChannel := range agentChannels { + channel, reqs, acceptErr := newChannel.Accept() + if acceptErr != nil { + continue + } + go ssh.DiscardRequests(reqs) + go s.forwardSSHAgentChannel(channel) + } + }() + } + } + + sshSession, err := sshClient.NewSession() + if err != nil { + common.Close(sshClient) + return finishWithError(E.Cause(err, "open ssh session").Error()) + } + + cols := int(start.Columns) + rows := int(start.Rows) + err = sshSession.RequestPty(start.TerminalType, rows, cols, ssh.TerminalModes{ + ssh.ECHO: 1, + ssh.ECHOE: 1, + ssh.ECHOK: 1, + ssh.ECHOKE: 1, + ssh.ECHOCTL: 1, + ssh.ICANON: 1, + ssh.ISIG: 1, + ssh.IEXTEN: 1, + ssh.ICRNL: 1, + ssh.IXON: 1, + ssh.IXANY: 1, + ssh.IMAXBEL: 1, + ssh.OPOST: 1, + ssh.ONLCR: 1, + ssh.TTY_OP_ISPEED: 14400, + ssh.TTY_OP_OSPEED: 14400, + }) + if err != nil { + common.Close(sshSession, sshClient) + return finishWithError(E.Cause(err, "request pty").Error()) + } + if start.WidthPixels > 0 || start.HeightPixels > 0 { + _, _ = sshSession.SendRequest("window-change", false, ssh.Marshal(&windowChangeRequest{ + Columns: uint32(start.Columns), + Rows: uint32(start.Rows), + WidthPixels: uint32(start.WidthPixels), + HeightPixels: uint32(start.HeightPixels), + })) + } + + if start.ForwardAgent { + err = agent.RequestAgentForwarding(sshSession) + if err != nil { + common.Close(sshSession, sshClient) + return finishWithError(E.Cause(err, "request agent forwarding").Error()) + } + } + + stdin, err := sshSession.StdinPipe() + if err != nil { + common.Close(sshSession, sshClient) + return finishWithError(E.Cause(err, "stdin pipe").Error()) + } + stdout, err := sshSession.StdoutPipe() + if err != nil { + common.Close(sshSession, sshClient) + return finishWithError(E.Cause(err, "stdout pipe").Error()) + } + stderr, err := sshSession.StderrPipe() + if err != nil { + common.Close(sshSession, sshClient) + return finishWithError(E.Cause(err, "stderr pipe").Error()) + } + err = sshSession.Shell() + if err != nil { + common.Close(sshSession, sshClient) + return finishWithError(E.Cause(err, "start shell").Error()) + } + + var workersWg sync.WaitGroup + + sendMessage(&TailscaleSSHServerMessage{ + Message: &TailscaleSSHServerMessage_Ready{Ready: &TailscaleSSHReady{}}, + }) + + workersWg.Add(1) + go func() { + defer workersWg.Done() + for { + msg, recvErr := server.Recv() + if recvErr == io.EOF { + stdin.Close() + return + } + if recvErr != nil { + cancel() + return + } + switch m := msg.GetMessage().(type) { + case *TailscaleSSHClientMessage_Input: + if len(m.Input.Data) == 0 { + continue + } + _, writeErr := stdin.Write(m.Input.Data) + if writeErr != nil { + cancel() + return + } + case *TailscaleSSHClientMessage_Resize: + _, _ = sshSession.SendRequest("window-change", false, ssh.Marshal(&windowChangeRequest{ + Columns: uint32(m.Resize.Columns), + Rows: uint32(m.Resize.Rows), + WidthPixels: uint32(m.Resize.WidthPixels), + HeightPixels: uint32(m.Resize.HeightPixels), + })) + } + } + }() + + pumpReader := func(reader io.Reader) { + defer workersWg.Done() + buffer := buf.Get(buf.BufferSize) + defer buf.Put(buffer) + for { + n, readErr := reader.Read(buffer) + if n > 0 { + sendMessage(&TailscaleSSHServerMessage{ + Message: &TailscaleSSHServerMessage_Output{Output: &TailscaleSSHOutput{Data: bytes.Clone(buffer[:n])}}, + }) + } + if readErr != nil { + return + } + } + } + workersWg.Add(1) + go pumpReader(stdout) + workersWg.Add(1) + go pumpReader(stderr) + + workersWg.Add(1) + go func() { + defer workersWg.Done() + ticker := time.NewTicker(30 * time.Second) + defer ticker.Stop() + for { + select { + case <-sessionCtx.Done(): + return + case <-ticker.C: + _, _, keepAliveErr := sshConn.SendRequest("keepalive@openssh.com", true, nil) + if keepAliveErr != nil { + cancel() + return + } + } + } + }() + + workersWg.Add(1) + go func() { + defer workersWg.Done() + waitErr := sshSession.Wait() + exitMessage := &TailscaleSSHExit{} + switch waitErrTyped := waitErr.(type) { + case nil: + case *ssh.ExitError: + exitMessage.ExitCode = int32(waitErrTyped.ExitStatus()) + exitMessage.Signal = waitErrTyped.Signal() + default: + exitMessage.ErrorMessage = waitErrTyped.Error() + } + sendMessage(&TailscaleSSHServerMessage{ + Message: &TailscaleSSHServerMessage_Exit{Exit: exitMessage}, + }) + cancel() + }() + + go func() { + <-sessionCtx.Done() + common.Close(peerConn, sshSession, sshClient) + }() + + workersWg.Wait() + return nil +} + +func (s *StartedService) forwardSSHAgentChannel(channel ssh.Channel) { + defer channel.Close() + fd, err := s.handler.ConnectSSHAgent() + if err != nil { + return + } + file := os.NewFile(uintptr(fd), "ssh-agent") + conn, err := net.FileConn(file) + file.Close() + if err != nil { + return + } + defer conn.Close() + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + io.Copy(conn, channel) + }() + go func() { + defer wg.Done() + io.Copy(channel, conn) + }() + wg.Wait() +} diff --git a/debug_go119.go b/debug.go similarity index 61% rename from debug_go119.go rename to debug.go index cf522afb72..f620172be4 100644 --- a/debug_go119.go +++ b/debug.go @@ -1,15 +1,13 @@ -//go:build go1.19 - package box import ( "runtime/debug" - "github.com/sagernet/sing-box/common/conntrack" "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" ) -func applyDebugOptions(options option.DebugOptions) { +func applyDebugOptions(options option.DebugOptions) error { applyDebugListenOption(options) if options.GCPercent != nil { debug.SetGCPercent(*options.GCPercent) @@ -26,11 +24,11 @@ func applyDebugOptions(options option.DebugOptions) { if options.TraceBack != "" { debug.SetTraceback(options.TraceBack) } - if options.MemoryLimit != 0 { - debug.SetMemoryLimit(int64(float64(options.MemoryLimit) / 1.5)) - conntrack.MemoryLimit = uint64(options.MemoryLimit) + if options.MemoryLimit.Value() != 0 { + debug.SetMemoryLimit(int64(float64(options.MemoryLimit.Value()) / 1.5)) } if options.OOMKiller != nil { - conntrack.KillerEnabled = *options.OOMKiller + return E.New("legacy oom_killer in debug options is removed, use oom-killer service instead") } + return nil } diff --git a/debug_go118.go b/debug_go118.go deleted file mode 100644 index bb132efb98..0000000000 --- a/debug_go118.go +++ /dev/null @@ -1,36 +0,0 @@ -//go:build !go1.19 - -package box - -import ( - "runtime/debug" - - "github.com/sagernet/sing-box/common/conntrack" - "github.com/sagernet/sing-box/option" -) - -func applyDebugOptions(options option.DebugOptions) { - applyDebugListenOption(options) - if options.GCPercent != nil { - debug.SetGCPercent(*options.GCPercent) - } - if options.MaxStack != nil { - debug.SetMaxStack(*options.MaxStack) - } - if options.MaxThreads != nil { - debug.SetMaxThreads(*options.MaxThreads) - } - if options.PanicOnFault != nil { - debug.SetPanicOnFault(*options.PanicOnFault) - } - if options.TraceBack != "" { - debug.SetTraceback(options.TraceBack) - } - if options.MemoryLimit != 0 { - // debug.SetMemoryLimit(int64(options.MemoryLimit)) - conntrack.MemoryLimit = uint64(options.MemoryLimit) - } - if options.OOMKiller != nil { - conntrack.KillerEnabled = *options.OOMKiller - } -} diff --git a/debug_http.go b/debug_http.go index df356c2ed4..e51a073143 100644 --- a/debug_http.go +++ b/debug_http.go @@ -7,9 +7,9 @@ import ( "runtime/debug" "strings" - "github.com/sagernet/sing-box/common/humanize" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/byteformats" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" @@ -38,15 +38,15 @@ func applyDebugListenOption(options option.DebugOptions) { runtime.ReadMemStats(&memStats) var memObject badjson.JSONObject - memObject.Put("heap", humanize.MemoryBytes(memStats.HeapInuse)) - memObject.Put("stack", humanize.MemoryBytes(memStats.StackInuse)) - memObject.Put("idle", humanize.MemoryBytes(memStats.HeapIdle-memStats.HeapReleased)) + memObject.Put("heap", byteformats.FormatMemoryBytes(memStats.HeapInuse)) + memObject.Put("stack", byteformats.FormatMemoryBytes(memStats.StackInuse)) + memObject.Put("idle", byteformats.FormatMemoryBytes(memStats.HeapIdle-memStats.HeapReleased)) memObject.Put("goroutines", runtime.NumGoroutine()) memObject.Put("rss", rusageMaxRSS()) encoder := json.NewEncoder(writer) encoder.SetIndent("", " ") - encoder.Encode(memObject) + encoder.Encode(&memObject) }) r.Route("/pprof", func(r chi.Router) { r.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { diff --git a/dns/blocked_checker.go b/dns/blocked_checker.go new file mode 100644 index 0000000000..75fcfbf84d --- /dev/null +++ b/dns/blocked_checker.go @@ -0,0 +1,37 @@ +package dns + +import "net/netip" + +var blockedPrefixes = []netip.Prefix{ + netip.MustParsePrefix("10.0.0.0/8"), + netip.MustParsePrefix("2001:4188:2:600::/64"), +} + +func IsBlockedIP(ip netip.Addr) bool { + if !ip.IsValid() { + return true + } + + for _, p := range blockedPrefixes { + if p.Contains(ip) { + return true + } + } + return false +} + +func FilterBlocked(addrs []netip.Addr) []netip.Addr { + if addrs == nil { + return nil + } + if len(addrs) == 0 { + return addrs + } + out := addrs[:0] // in-place filter, no extra alloc + for _, ip := range addrs { + if !IsBlockedIP(ip) { + out = append(out, ip) + } + } + return out +} diff --git a/dns/client.go b/dns/client.go new file mode 100644 index 0000000000..ece3e0bdf4 --- /dev/null +++ b/dns/client.go @@ -0,0 +1,724 @@ +package dns + +import ( + "context" + "errors" + "net" + "net/netip" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/compatible" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + "github.com/sagernet/sing/common/task" + "github.com/sagernet/sing/contrab/freelru" + "github.com/sagernet/sing/contrab/maphash" + + "github.com/miekg/dns" +) + +var ( + ErrNoRawSupport = E.New("no raw query support by current transport") + ErrNotCached = E.New("not cached") + ErrResponseRejected = E.New("response rejected") + ErrResponseRejectedCached = E.Extend(ErrResponseRejected, "cached") +) + +var _ adapter.DNSClient = (*Client)(nil) + +type Client struct { + ctx context.Context + timeout time.Duration + disableCache bool + disableExpire bool + optimisticTimeout time.Duration + cacheCapacity uint32 + clientSubnet netip.Prefix + rdrc adapter.RDRCStore + initRDRCFunc func() adapter.RDRCStore + dnsCache adapter.DNSCacheStore + initDNSCacheFunc func() adapter.DNSCacheStore + logger logger.ContextLogger + cache freelru.Cache[dnsCacheKey, *dns.Msg] + cacheLock compatible.Map[dnsCacheKey, chan struct{}] + backgroundRefresh compatible.Map[dnsCacheKey, struct{}] +} + +type ClientOptions struct { + Context context.Context + Timeout time.Duration + DisableCache bool + DisableExpire bool + OptimisticTimeout time.Duration + CacheCapacity uint32 + ClientSubnet netip.Prefix + RDRC func() adapter.RDRCStore + DNSCache func() adapter.DNSCacheStore + Logger logger.ContextLogger +} + +func NewClient(options ClientOptions) *Client { + cacheCapacity := options.CacheCapacity + if cacheCapacity < 1024 { + cacheCapacity = 1024 + } + client := &Client{ + ctx: options.Context, + timeout: options.Timeout, + disableCache: options.DisableCache, + disableExpire: options.DisableExpire, + optimisticTimeout: options.OptimisticTimeout, + cacheCapacity: cacheCapacity, + clientSubnet: options.ClientSubnet, + initRDRCFunc: options.RDRC, + initDNSCacheFunc: options.DNSCache, + logger: options.Logger, + } + if client.timeout == 0 { + client.timeout = C.DNSTimeout + } + if !client.disableCache && client.initDNSCacheFunc == nil { + client.initializeMemoryCache() + } + return client +} + +type dnsCacheKey struct { + dns.Question + transportTag string +} + +func (c *Client) Start() { + if c.initRDRCFunc != nil { + c.rdrc = c.initRDRCFunc() + } + if c.initDNSCacheFunc != nil { + c.dnsCache = c.initDNSCacheFunc() + } + if c.dnsCache == nil { + c.initializeMemoryCache() + } +} + +func (c *Client) initializeMemoryCache() { + if c.disableCache || c.cache != nil { + return + } + c.cache = common.Must1(freelru.NewSharded[dnsCacheKey, *dns.Msg](c.cacheCapacity, maphash.NewHasher[dnsCacheKey]().Hash32)) +} + +func extractNegativeTTL(response *dns.Msg) (uint32, bool) { + for _, record := range response.Ns { + if soa, isSOA := record.(*dns.SOA); isSOA { + soaTTL := soa.Header().Ttl + soaMinimum := soa.Minttl + if soaTTL < soaMinimum { + return soaTTL, true + } + return soaMinimum, true + } + } + return 0, false +} + +func computeTimeToLive(response *dns.Msg) uint32 { + var timeToLive uint32 + if len(response.Answer) == 0 { + if soaTTL, hasSOA := extractNegativeTTL(response); hasSOA { + return soaTTL + } + } + for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} { + for _, record := range recordList { + if record.Header().Rrtype == dns.TypeOPT { + continue + } + if timeToLive == 0 || record.Header().Ttl > 0 && record.Header().Ttl < timeToLive { + timeToLive = record.Header().Ttl + } + } + } + return timeToLive +} + +func normalizeTTL(response *dns.Msg, timeToLive uint32) { + for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} { + for _, record := range recordList { + if record.Header().Rrtype == dns.TypeOPT { + continue + } + record.Header().Ttl = timeToLive + } + } +} + +func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) (*dns.Msg, error) { + if len(message.Question) == 0 { + if c.logger != nil { + c.logger.WarnContext(ctx, "bad question size: ", len(message.Question)) + } + return FixedResponseStatus(message, dns.RcodeFormatError), nil + } + question := message.Question[0] + if question.Qtype == dns.TypeA && options.Strategy == C.DomainStrategyIPv6Only || question.Qtype == dns.TypeAAAA && options.Strategy == C.DomainStrategyIPv4Only { + if c.logger != nil { + c.logger.DebugContext(ctx, "strategy rejected") + } + return FixedResponseStatus(message, dns.RcodeSuccess), nil + } + message = c.prepareExchangeMessage(message, options) + + isSimpleRequest := len(message.Question) == 1 && + len(message.Ns) == 0 && + (len(message.Extra) == 0 || len(message.Extra) == 1 && + message.Extra[0].Header().Rrtype == dns.TypeOPT && + message.Extra[0].Header().Class > 0 && + message.Extra[0].Header().Ttl == 0 && + len(message.Extra[0].(*dns.OPT).Option) == 0) && + !options.ClientSubnet.IsValid() + disableCache := !isSimpleRequest || c.disableCache || options.DisableCache + if !disableCache { + cacheKey := dnsCacheKey{Question: question, transportTag: transport.Tag()} + cond, loaded := c.cacheLock.LoadOrStore(cacheKey, make(chan struct{})) + if loaded { + select { + case <-cond: + case <-time.After(c.timeout): + return nil, E.New("cache wait timeout") + case <-ctx.Done(): + return nil, ctx.Err() + } + } else { + defer func() { + c.cacheLock.Delete(cacheKey) + close(cond) + }() + } + response, ttl, isStale := c.loadResponse(question, transport) + if response != nil { + if isStale && !options.DisableOptimisticCache { + c.backgroundRefreshDNS(transport, question, message.Copy(), options, responseChecker) + logOptimisticResponse(c.logger, ctx, response) + response.Id = message.Id + return response, nil + } else if !isStale { + logCachedResponse(c.logger, ctx, response, ttl) + response.Id = message.Id + return response, nil + } + } + } + + messageId := message.Id + contextTransport, clientSubnetLoaded := transportTagFromContext(ctx) + if clientSubnetLoaded && transport.Tag() == contextTransport { + return nil, E.New("DNS query loopback in transport[", contextTransport, "]") + } + ctx = contextWithTransportTag(ctx, transport.Tag()) + if !disableCache && responseChecker != nil && c.rdrc != nil { + rejected := c.rdrc.LoadRDRC(transport.Tag(), question.Name, question.Qtype) + if rejected { + return nil, ErrResponseRejectedCached + } + } + response, err := c.exchangeToTransport(ctx, transport, message) + if err != nil { + return nil, err + } + disableCache = disableCache || (response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError) + if responseChecker != nil { + var rejected bool + if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError { + rejected = true + } else { + rejected = !responseChecker(response) + } + if rejected { + if !disableCache && c.rdrc != nil { + c.rdrc.SaveRDRCAsync(transport.Tag(), question.Name, question.Qtype, c.logger) + } + logRejectedResponse(c.logger, ctx, response) + return response, ErrResponseRejected + } + } + timeToLive := applyResponseOptions(question, response, options) + if !disableCache { + c.storeCache(transport, question, response, timeToLive) + } + response.Id = messageId + requestEDNSOpt := message.IsEdns0() + responseEDNSOpt := response.IsEdns0() + if responseEDNSOpt != nil && (requestEDNSOpt == nil || requestEDNSOpt.Version() < responseEDNSOpt.Version()) { + response.Extra = common.Filter(response.Extra, func(it dns.RR) bool { + return it.Header().Rrtype != dns.TypeOPT + }) + if requestEDNSOpt != nil { + response.SetEdns0(responseEDNSOpt.UDPSize(), responseEDNSOpt.Do()) + } + } + logExchangedResponse(c.logger, ctx, response, timeToLive) + return response, nil +} + +func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) ([]netip.Addr, error) { + domain = FqdnToDomain(domain) + dnsName := dns.Fqdn(domain) + var strategy C.DomainStrategy + if options.LookupStrategy != C.DomainStrategyAsIS { + strategy = options.LookupStrategy + } else { + strategy = options.Strategy + } + lookupOptions := options + if options.LookupStrategy != C.DomainStrategyAsIS { + lookupOptions.Strategy = strategy + } + if strategy == C.DomainStrategyIPv4Only { + return c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, lookupOptions, responseChecker) + } else if strategy == C.DomainStrategyIPv6Only { + return c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, lookupOptions, responseChecker) + } + var response4 []netip.Addr + var response6 []netip.Addr + var group task.Group + group.Append("exchange4", func(ctx context.Context) error { + response, err := c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, lookupOptions, responseChecker) + if err != nil { + return err + } + response4 = response + return nil + }) + group.Append("exchange6", func(ctx context.Context) error { + response, err := c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, lookupOptions, responseChecker) + if err != nil { + return err + } + response6 = response + return nil + }) + err := group.Run(ctx) + if len(response4) == 0 && len(response6) == 0 { + return nil, err + } + return sortAddresses(response4, response6, strategy), nil +} + +func (c *Client) ClearCache() { + if c.cache != nil { + c.cache.Purge() + } + if c.dnsCache != nil { + err := c.dnsCache.ClearDNSCache() + if err != nil && c.logger != nil { + c.logger.Warn("clear DNS cache: ", err) + } + } +} + +func sortAddresses(response4 []netip.Addr, response6 []netip.Addr, strategy C.DomainStrategy) []netip.Addr { + if strategy == C.DomainStrategyPreferIPv6 { + return append(response6, response4...) + } else { + return append(response4, response6...) + } +} + +func (c *Client) storeCache(transport adapter.DNSTransport, question dns.Question, message *dns.Msg, timeToLive uint32) { + if timeToLive == 0 { + return + } + if c.dnsCache != nil { + packed, err := message.Pack() + if err == nil { + expireAt := time.Now().Add(time.Second * time.Duration(timeToLive)) + c.dnsCache.SaveDNSCacheAsync(transport.Tag(), question.Name, question.Qtype, packed, expireAt, c.logger) + } + return + } + if c.cache == nil { + return + } + key := dnsCacheKey{Question: question, transportTag: transport.Tag()} + if c.disableExpire { + c.cache.Add(key, message.Copy()) + } else { + c.cache.AddWithLifetime(key, message.Copy(), time.Second*time.Duration(timeToLive)) + } +} + +func (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTransport, name string, qType uint16, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) ([]netip.Addr, error) { + question := dns.Question{ + Name: name, + Qtype: qType, + Qclass: dns.ClassINET, + } + message := dns.Msg{ + MsgHdr: dns.MsgHdr{ + RecursionDesired: true, + }, + Question: []dns.Question{question}, + } + disableCache := c.disableCache || options.DisableCache + if !disableCache { + cachedAddresses, err := c.questionCache(ctx, transport, &message, options, responseChecker) + if err != ErrNotCached { + return cachedAddresses, err + } + } + response, err := c.Exchange(ctx, transport, &message, options, responseChecker) + if err != nil { + return nil, err + } + if response.Rcode != dns.RcodeSuccess { + return nil, RcodeError(response.Rcode) + } + return MessageToAddresses(response), nil +} + +func (c *Client) questionCache(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) ([]netip.Addr, error) { + question := message.Question[0] + response, _, isStale := c.loadResponse(question, transport) + if response == nil { + return nil, ErrNotCached + } + if isStale { + if options.DisableOptimisticCache { + return nil, ErrNotCached + } + c.backgroundRefreshDNS(transport, question, c.prepareExchangeMessage(message.Copy(), options), options, responseChecker) + logOptimisticResponse(c.logger, ctx, response) + } + if response.Rcode != dns.RcodeSuccess { + return nil, RcodeError(response.Rcode) + } + return MessageToAddresses(response), nil +} + +func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransport) (*dns.Msg, int, bool) { + if c.dnsCache != nil { + return c.loadPersistentResponse(question, transport) + } + if c.cache == nil { + return nil, 0, false + } + key := dnsCacheKey{Question: question, transportTag: transport.Tag()} + if c.disableExpire { + response, loaded := c.cache.Get(key) + if !loaded { + return nil, 0, false + } + return response.Copy(), 0, false + } + response, expireAt, loaded := c.cache.GetWithLifetimeNoExpire(key) + if !loaded { + return nil, 0, false + } + timeNow := time.Now() + if timeNow.After(expireAt) { + if c.optimisticTimeout > 0 && timeNow.Before(expireAt.Add(c.optimisticTimeout)) { + response = response.Copy() + normalizeTTL(response, 1) + return response, 0, true + } + c.cache.Remove(key) + return nil, 0, false + } + nowTTL := int(expireAt.Sub(timeNow).Seconds()) + if nowTTL < 0 { + nowTTL = 0 + } + response = response.Copy() + normalizeTTL(response, uint32(nowTTL)) + return response, nowTTL, false +} + +func (c *Client) loadPersistentResponse(question dns.Question, transport adapter.DNSTransport) (*dns.Msg, int, bool) { + rawMessage, expireAt, loaded := c.dnsCache.LoadDNSCache(transport.Tag(), question.Name, question.Qtype) + if !loaded { + return nil, 0, false + } + response := new(dns.Msg) + err := response.Unpack(rawMessage) + if err != nil { + return nil, 0, false + } + if c.disableExpire { + return response, 0, false + } + timeNow := time.Now() + if timeNow.After(expireAt) { + if c.optimisticTimeout > 0 && timeNow.Before(expireAt.Add(c.optimisticTimeout)) { + normalizeTTL(response, 1) + return response, 0, true + } + return nil, 0, false + } + nowTTL := int(expireAt.Sub(timeNow).Seconds()) + if nowTTL < 0 { + nowTTL = 0 + } + normalizeTTL(response, uint32(nowTTL)) + return response, nowTTL, false +} + +func applyResponseOptions(question dns.Question, response *dns.Msg, options adapter.DNSQueryOptions) uint32 { + if question.Qtype == dns.TypeHTTPS && (options.Strategy == C.DomainStrategyIPv4Only || options.Strategy == C.DomainStrategyIPv6Only) { + for _, rr := range response.Answer { + https, isHTTPS := rr.(*dns.HTTPS) + if !isHTTPS { + continue + } + content := https.SVCB + content.Value = common.Filter(content.Value, func(it dns.SVCBKeyValue) bool { + if options.Strategy == C.DomainStrategyIPv4Only { + return it.Key() != dns.SVCB_IPV6HINT + } + return it.Key() != dns.SVCB_IPV4HINT + }) + https.SVCB = content + } + } + timeToLive := computeTimeToLive(response) + if options.RewriteTTL != nil { + timeToLive = *options.RewriteTTL + } + normalizeTTL(response, timeToLive) + return timeToLive +} + +func (c *Client) backgroundRefreshDNS(transport adapter.DNSTransport, question dns.Question, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) { + key := dnsCacheKey{Question: question, transportTag: transport.Tag()} + _, loaded := c.backgroundRefresh.LoadOrStore(key, struct{}{}) + if loaded { + return + } + go func() { + defer c.backgroundRefresh.Delete(key) + ctx := contextWithTransportTag(c.ctx, transport.Tag()) + response, err := c.exchangeToTransport(ctx, transport, message) + if err != nil { + if c.logger != nil { + c.logger.Debug("optimistic refresh failed for ", FqdnToDomain(question.Name), ": ", err) + } + return + } + if responseChecker != nil { + var rejected bool + if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError { + rejected = true + } else { + rejected = !responseChecker(response) + } + if rejected { + if c.rdrc != nil { + c.rdrc.SaveRDRCAsync(transport.Tag(), question.Name, question.Qtype, c.logger) + } + return + } + } else if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError { + return + } + timeToLive := applyResponseOptions(question, response, options) + c.storeCache(transport, question, response, timeToLive) + }() +} + +func (c *Client) prepareExchangeMessage(message *dns.Msg, options adapter.DNSQueryOptions) *dns.Msg { + clientSubnet := options.ClientSubnet + if !clientSubnet.IsValid() { + clientSubnet = c.clientSubnet + } + if clientSubnet.IsValid() { + message = SetClientSubnet(message, clientSubnet) + } + return message +} + +func stripDNSPadding(response *dns.Msg) { + for _, record := range response.Extra { + opt, isOpt := record.(*dns.OPT) + if !isOpt { + continue + } + opt.Option = common.Filter(opt.Option, func(it dns.EDNS0) bool { + return it.Option() != dns.EDNS0PADDING + }) + } +} + +func (c *Client) exchangeToTransport(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg) (*dns.Msg, error) { + ctx, cancel := context.WithTimeout(ctx, c.timeout) + defer cancel() + response, err := transport.Exchange(ctx, message) + if err == nil { + stripDNSPadding(response) + return response, nil + } + var rcodeError RcodeError + if errors.As(err, &rcodeError) { + return FixedResponseStatus(message, int(rcodeError)), nil + } + return nil, err +} + +func MessageToAddresses(response *dns.Msg) []netip.Addr { + return adapter.DNSResponseAddresses(response) +} + +func wrapError(err error) error { + switch dnsErr := err.(type) { + case *net.DNSError: + if dnsErr.IsNotFound { + return RcodeNameError + } + case *net.AddrError: + return RcodeNameError + } + return err +} + +type transportKey struct{} + +func contextWithTransportTag(ctx context.Context, transportTag string) context.Context { + return context.WithValue(ctx, transportKey{}, transportTag) +} + +func transportTagFromContext(ctx context.Context) (string, bool) { + value, loaded := ctx.Value(transportKey{}).(string) + return value, loaded +} + +func FixedResponseStatus(message *dns.Msg, rcode int) *dns.Msg { + return &dns.Msg{ + MsgHdr: dns.MsgHdr{ + Id: message.Id, + Response: true, + Authoritative: true, + RecursionDesired: true, + RecursionAvailable: true, + Rcode: rcode, + }, + Question: message.Question, + } +} + +func FixedResponse(id uint16, question dns.Question, addresses []netip.Addr, timeToLive uint32) *dns.Msg { + response := dns.Msg{ + MsgHdr: dns.MsgHdr{ + Id: id, + Response: true, + Authoritative: true, + RecursionDesired: true, + RecursionAvailable: true, + Rcode: dns.RcodeSuccess, + }, + Question: []dns.Question{question}, + } + for _, address := range addresses { + if address.Is4() && question.Qtype == dns.TypeA { + response.Answer = append(response.Answer, &dns.A{ + Hdr: dns.RR_Header{ + Name: question.Name, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + Ttl: timeToLive, + }, + A: address.AsSlice(), + }) + } else if address.Is6() && question.Qtype == dns.TypeAAAA { + response.Answer = append(response.Answer, &dns.AAAA{ + Hdr: dns.RR_Header{ + Name: question.Name, + Rrtype: dns.TypeAAAA, + Class: dns.ClassINET, + Ttl: timeToLive, + }, + AAAA: address.AsSlice(), + }) + } + } + return &response +} + +func FixedResponseCNAME(id uint16, question dns.Question, record string, timeToLive uint32) *dns.Msg { + response := dns.Msg{ + MsgHdr: dns.MsgHdr{ + Id: id, + Response: true, + Authoritative: true, + RecursionDesired: true, + RecursionAvailable: true, + Rcode: dns.RcodeSuccess, + }, + Question: []dns.Question{question}, + Answer: []dns.RR{ + &dns.CNAME{ + Hdr: dns.RR_Header{ + Name: question.Name, + Rrtype: dns.TypeCNAME, + Class: dns.ClassINET, + Ttl: timeToLive, + }, + Target: record, + }, + }, + } + return &response +} + +func FixedResponseTXT(id uint16, question dns.Question, records []string, timeToLive uint32) *dns.Msg { + response := dns.Msg{ + MsgHdr: dns.MsgHdr{ + Id: id, + Response: true, + Authoritative: true, + RecursionDesired: true, + RecursionAvailable: true, + Rcode: dns.RcodeSuccess, + }, + Question: []dns.Question{question}, + Answer: []dns.RR{ + &dns.TXT{ + Hdr: dns.RR_Header{ + Name: question.Name, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + Ttl: timeToLive, + }, + Txt: records, + }, + }, + } + return &response +} + +func FixedResponseMX(id uint16, question dns.Question, records []*net.MX, timeToLive uint32) *dns.Msg { + response := dns.Msg{ + MsgHdr: dns.MsgHdr{ + Id: id, + Response: true, + Authoritative: true, + RecursionDesired: true, + RecursionAvailable: true, + Rcode: dns.RcodeSuccess, + }, + Question: []dns.Question{question}, + } + for _, record := range records { + response.Answer = append(response.Answer, &dns.MX{ + Hdr: dns.RR_Header{ + Name: question.Name, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + Ttl: timeToLive, + }, + Preference: record.Pref, + Mx: record.Host, + }) + } + return &response +} diff --git a/dns/client_log.go b/dns/client_log.go new file mode 100644 index 0000000000..abc726d3c4 --- /dev/null +++ b/dns/client_log.go @@ -0,0 +1,95 @@ +package dns + +import ( + "context" + "strings" + + "github.com/sagernet/sing/common/logger" + + "github.com/miekg/dns" +) + +func logCachedResponse(logger logger.ContextLogger, ctx context.Context, response *dns.Msg, ttl int) { + if logger == nil || len(response.Question) == 0 { + return + } + domain := FqdnToDomain(response.Question[0].Name) + logger.DebugContext(ctx, "cached ", domain, " ", dns.RcodeToString[response.Rcode], " ", ttl) + for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} { + for _, record := range recordList { + logger.InfoContext(ctx, "cached ", dns.Type(record.Header().Rrtype).String(), " ", FormatQuestion(record.String())) + } + } +} + +func logOptimisticResponse(logger logger.ContextLogger, ctx context.Context, response *dns.Msg) { + if logger == nil || len(response.Question) == 0 { + return + } + domain := FqdnToDomain(response.Question[0].Name) + logger.DebugContext(ctx, "optimistic ", domain, " ", dns.RcodeToString[response.Rcode]) + for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} { + for _, record := range recordList { + logger.InfoContext(ctx, "optimistic ", dns.Type(record.Header().Rrtype).String(), " ", FormatQuestion(record.String())) + } + } +} + +func logExchangedResponse(logger logger.ContextLogger, ctx context.Context, response *dns.Msg, ttl uint32) { + if logger == nil || len(response.Question) == 0 { + return + } + domain := FqdnToDomain(response.Question[0].Name) + logger.DebugContext(ctx, "exchanged ", domain, " ", dns.RcodeToString[response.Rcode], " ", ttl) + for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} { + for _, record := range recordList { + logger.InfoContext(ctx, "exchanged ", dns.Type(record.Header().Rrtype).String(), " ", FormatQuestion(record.String())) + } + } +} + +func logRefreshedResponse(logger logger.ContextLogger, ctx context.Context, response *dns.Msg, ttl uint32) { + if logger == nil || len(response.Question) == 0 { + return + } + domain := FqdnToDomain(response.Question[0].Name) + logger.DebugContext(ctx, "refreshed ", domain, " ", dns.RcodeToString[response.Rcode], " ", ttl) + for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} { + for _, record := range recordList { + logger.InfoContext(ctx, "refreshed ", dns.Type(record.Header().Rrtype).String(), " ", FormatQuestion(record.String())) + } + } +} + +func logRejectedResponse(logger logger.ContextLogger, ctx context.Context, response *dns.Msg) { + if logger == nil || len(response.Question) == 0 { + return + } + for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} { + for _, record := range recordList { + logger.InfoContext(ctx, "rejected ", dns.Type(record.Header().Rrtype).String(), " ", FormatQuestion(record.String())) + } + } +} + +func FqdnToDomain(fqdn string) string { + if dns.IsFqdn(fqdn) { + return fqdn[:len(fqdn)-1] + } + return fqdn +} + +func FormatQuestion(string string) string { + for strings.HasPrefix(string, ";") { + string = string[1:] + } + string = strings.ReplaceAll(string, "\t", " ") + string = strings.ReplaceAll(string, "\n", " ") + string = strings.ReplaceAll(string, ";; ", " ") + string = strings.ReplaceAll(string, "; ", " ") + + for strings.Contains(string, " ") { + string = strings.ReplaceAll(string, " ", " ") + } + return strings.TrimSpace(string) +} diff --git a/dns/client_truncate.go b/dns/client_truncate.go new file mode 100644 index 0000000000..19165f993a --- /dev/null +++ b/dns/client_truncate.go @@ -0,0 +1,30 @@ +package dns + +import ( + "github.com/sagernet/sing/common/buf" + + "github.com/miekg/dns" +) + +func TruncateDNSMessage(request *dns.Msg, response *dns.Msg, headroom int) (*buf.Buffer, error) { + maxLen := 512 + if edns0Option := request.IsEdns0(); edns0Option != nil { + if udpSize := int(edns0Option.UDPSize()); udpSize > 512 { + maxLen = udpSize + } + } + responseLen := response.Len() + if responseLen > maxLen { + response = response.Copy() + response.Truncate(maxLen) + } + buffer := buf.NewSize(headroom*2 + 1 + responseLen) + buffer.Resize(headroom, 0) + rawMessage, err := response.PackBuffer(buffer.FreeBytes()) + if err != nil { + buffer.Release() + return nil, err + } + buffer.Truncate(len(rawMessage)) + return buffer, nil +} diff --git a/dns/extension_edns0_subnet.go b/dns/extension_edns0_subnet.go new file mode 100644 index 0000000000..e804fb6cd1 --- /dev/null +++ b/dns/extension_edns0_subnet.go @@ -0,0 +1,57 @@ +package dns + +import ( + "net/netip" + + "github.com/miekg/dns" +) + +func SetClientSubnet(message *dns.Msg, clientSubnet netip.Prefix) *dns.Msg { + return setClientSubnet(message, clientSubnet, true) +} + +func setClientSubnet(message *dns.Msg, clientSubnet netip.Prefix, clone bool) *dns.Msg { + var ( + optRecord *dns.OPT + subnetOption *dns.EDNS0_SUBNET + ) +findExists: + for _, record := range message.Extra { + var isOPTRecord bool + if optRecord, isOPTRecord = record.(*dns.OPT); isOPTRecord { + for _, option := range optRecord.Option { + var isEDNS0Subnet bool + subnetOption, isEDNS0Subnet = option.(*dns.EDNS0_SUBNET) + if isEDNS0Subnet { + break findExists + } + } + } + } + if optRecord == nil { + exMessage := *message + message = &exMessage + optRecord = &dns.OPT{ + Hdr: dns.RR_Header{ + Name: ".", + Rrtype: dns.TypeOPT, + }, + } + message.Extra = append(message.Extra, optRecord) + } else if clone { + return setClientSubnet(message.Copy(), clientSubnet, false) + } + if subnetOption == nil { + subnetOption = new(dns.EDNS0_SUBNET) + subnetOption.Code = dns.EDNS0SUBNET + optRecord.Option = append(optRecord.Option, subnetOption) + } + if clientSubnet.Addr().Is4() { + subnetOption.Family = 1 + } else { + subnetOption.Family = 2 + } + subnetOption.SourceNetmask = uint8(clientSubnet.Bits()) + subnetOption.Address = clientSubnet.Addr().AsSlice() + return message +} diff --git a/dns/rcode.go b/dns/rcode.go new file mode 100644 index 0000000000..417d41fa1e --- /dev/null +++ b/dns/rcode.go @@ -0,0 +1,19 @@ +package dns + +import ( + mDNS "github.com/miekg/dns" +) + +const ( + RcodeSuccess RcodeError = mDNS.RcodeSuccess + RcodeServerFailure RcodeError = mDNS.RcodeServerFailure + RcodeFormatError RcodeError = mDNS.RcodeFormatError + RcodeNameError RcodeError = mDNS.RcodeNameError + RcodeRefused RcodeError = mDNS.RcodeRefused +) + +type RcodeError int + +func (e RcodeError) Error() string { + return mDNS.RcodeToString[int(e)] +} diff --git a/dns/repro_test.go b/dns/repro_test.go new file mode 100644 index 0000000000..113f7c49b9 --- /dev/null +++ b/dns/repro_test.go @@ -0,0 +1,111 @@ +package dns + +import ( + "context" + "net/netip" + "testing" + + "github.com/sagernet/sing-box/adapter" + 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/json/badoption" + + mDNS "github.com/miekg/dns" + "github.com/stretchr/testify/require" +) + +func TestReproLookupWithRulesUsesRequestStrategy(t *testing.T) { + t.Parallel() + + defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP} + var qTypes []uint16 + router := newTestRouter(t, nil, &fakeDNSTransportManager{ + defaultTransport: defaultTransport, + transports: map[string]adapter.DNSTransport{ + "default": defaultTransport, + }, + }, &fakeDNSClient{ + exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) { + qTypes = append(qTypes, message.Question[0].Qtype) + if message.Question[0].Qtype == mDNS.TypeA { + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2.2.2.2")}, 60), nil + } + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2001:db8::1")}, 60), nil + }, + }) + + addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{ + Strategy: C.DomainStrategyIPv4Only, + }) + require.NoError(t, err) + require.Equal(t, []uint16{mDNS.TypeA}, qTypes) + require.Equal(t, []netip.Addr{netip.MustParseAddr("2.2.2.2")}, addresses) +} + +func TestReproLogicalMatchResponseIPCIDR(t *testing.T) { + t.Parallel() + + transportManager := &fakeDNSTransportManager{ + defaultTransport: &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + transports: map[string]adapter.DNSTransport{ + "upstream": &fakeDNSTransport{tag: "upstream", transportType: C.DNSTypeUDP}, + "selected": &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP}, + "default": &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + }, + } + client := &fakeDNSClient{ + exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) { + switch transport.Tag() { + case "upstream": + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("1.1.1.1")}, 60), nil + case "selected": + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("8.8.8.8")}, 60), nil + default: + return nil, E.New("unexpected transport") + } + }, + } + rules := []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeEvaluate, + RouteOptions: option.DNSRouteActionOptions{Server: "upstream"}, + }, + }, + }, + { + Type: C.RuleTypeLogical, + LogicalOptions: option.LogicalDNSRule{ + RawLogicalDNSRule: option.RawLogicalDNSRule{ + Mode: C.LogicalTypeOr, + Rules: []option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + MatchResponse: true, + IPCIDR: badoption.Listable[string]{"1.1.1.0/24"}, + }, + }, + }}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, + }, + }, + }, + } + router := newTestRouter(t, rules, transportManager, client) + + response, err := router.Exchange(context.Background(), &mDNS.Msg{ + Question: []mDNS.Question{fixedQuestion("example.com", mDNS.TypeA)}, + }, adapter.DNSQueryOptions{}) + require.NoError(t, err) + require.Equal(t, []netip.Addr{netip.MustParseAddr("8.8.8.8")}, MessageToAddresses(response)) +} diff --git a/dns/router.go b/dns/router.go new file mode 100644 index 0000000000..549f42e205 --- /dev/null +++ b/dns/router.go @@ -0,0 +1,1175 @@ +package dns + +import ( + "context" + "errors" + "net/netip" + "strings" + "sync" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/taskmonitor" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/experimental/deprecated" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + R "github.com/sagernet/sing-box/route/rule" + tun "github.com/sagernet/sing-tun" + "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" + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/task" + "github.com/sagernet/sing/contrab/freelru" + "github.com/sagernet/sing/contrab/maphash" + "github.com/sagernet/sing/service" + + mDNS "github.com/miekg/dns" +) + +var ( + _ adapter.DNSRouter = (*Router)(nil) + _ adapter.DNSRuleSetUpdateValidator = (*Router)(nil) +) + +type Router struct { + ctx context.Context + logger logger.ContextLogger + transport adapter.DNSTransportManager + outbound adapter.OutboundManager + client adapter.DNSClient + rawRules []option.DNSRule + rules []adapter.DNSRule + defaultDomainStrategy C.DomainStrategy + dnsReverseMapping freelru.Cache[netip.Addr, string] + platformInterface adapter.PlatformInterface + legacyDNSMode bool + rulesAccess sync.RWMutex + started bool + closing bool +} + +func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOptions) (*Router, error) { + router := &Router{ + ctx: ctx, + logger: logFactory.NewLogger("dns"), + transport: service.FromContext[adapter.DNSTransportManager](ctx), + outbound: service.FromContext[adapter.OutboundManager](ctx), + rawRules: make([]option.DNSRule, 0, len(options.Rules)), + rules: make([]adapter.DNSRule, 0, len(options.Rules)), + defaultDomainStrategy: C.DomainStrategy(options.Strategy), + } + if options.DNSClientOptions.IndependentCache { + deprecated.Report(ctx, deprecated.OptionIndependentDNSCache) + } + var optimisticTimeout time.Duration + optimisticOptions := common.PtrValueOrDefault(options.DNSClientOptions.Optimistic) + if optimisticOptions.Enabled { + if options.DNSClientOptions.DisableCache { + return nil, E.New("`optimistic` is conflict with `disable_cache`") + } + if options.DNSClientOptions.DisableExpire { + return nil, E.New("`optimistic` is conflict with `disable_expire`") + } + optimisticTimeout = time.Duration(optimisticOptions.Timeout) + if optimisticTimeout == 0 { + optimisticTimeout = 3 * 24 * time.Hour + } + } + router.client = NewClient(ClientOptions{ + Context: ctx, + DisableCache: options.DNSClientOptions.DisableCache, + DisableExpire: options.DNSClientOptions.DisableExpire, + OptimisticTimeout: optimisticTimeout, + CacheCapacity: options.DNSClientOptions.CacheCapacity, + ClientSubnet: options.DNSClientOptions.ClientSubnet.Build(netip.Prefix{}), + RDRC: func() adapter.RDRCStore { + cacheFile := service.FromContext[adapter.CacheFile](ctx) + if cacheFile == nil { + return nil + } + if !cacheFile.StoreRDRC() { + return nil + } + return cacheFile + }, + DNSCache: func() adapter.DNSCacheStore { + cacheFile := service.FromContext[adapter.CacheFile](ctx) + if cacheFile == nil { + return nil + } + if !cacheFile.StoreDNS() { + return nil + } + cacheFile.SetDisableExpire(options.DNSClientOptions.DisableExpire) + cacheFile.SetOptimisticTimeout(optimisticTimeout) + return cacheFile + }, + Logger: router.logger, + }) + if options.ReverseMapping { + router.dnsReverseMapping = common.Must1(freelru.NewSharded[netip.Addr, string](1024, maphash.NewHasher[netip.Addr]().Hash32)) + } + return router, nil +} + +func (r *Router) Initialize(rules []option.DNSRule) error { + r.rawRules = append(r.rawRules[:0], rules...) + newRules, _, _, err := r.buildRules(false) + if err != nil { + return err + } + closeRules(newRules) + return nil +} + +func (r *Router) Start(stage adapter.StartStage) error { + monitor := taskmonitor.New(r.logger, C.StartTimeout) + switch stage { + case adapter.StartStateStart: + monitor.Start("initialize DNS client") + r.client.Start() + monitor.Finish() + + monitor.Start("initialize DNS rules") + newRules, legacyDNSMode, modeFlags, err := r.buildRules(true) + monitor.Finish() + if err != nil { + return err + } + r.rulesAccess.Lock() + if r.closing { + r.rulesAccess.Unlock() + closeRules(newRules) + return nil + } + r.rules = newRules + r.legacyDNSMode = legacyDNSMode + r.started = true + r.rulesAccess.Unlock() + if legacyDNSMode && common.Any(newRules, func(rule adapter.DNSRule) bool { return rule.WithAddressLimit() }) { + deprecated.Report(r.ctx, deprecated.OptionLegacyDNSAddressFilter) + } + if legacyDNSMode && modeFlags.neededFromStrategy { + deprecated.Report(r.ctx, deprecated.OptionLegacyDNSRuleStrategy) + } + } + return nil +} + +func (r *Router) Close() error { + r.rulesAccess.Lock() + if r.closing { + r.rulesAccess.Unlock() + return nil + } + r.closing = true + runtimeRules := r.rules + r.rules = nil + r.rulesAccess.Unlock() + closeRules(runtimeRules) + return nil +} + +func (r *Router) buildRules(startRules bool) ([]adapter.DNSRule, bool, dnsRuleModeFlags, error) { + for i, ruleOptions := range r.rawRules { + err := R.ValidateNoNestedDNSRuleActions(ruleOptions) + if err != nil { + return nil, false, dnsRuleModeFlags{}, E.Cause(err, "parse dns rule[", i, "]") + } + } + router := service.FromContext[adapter.Router](r.ctx) + legacyDNSMode, modeFlags, err := resolveLegacyDNSMode(router, r.rawRules, nil) + if err != nil { + return nil, false, dnsRuleModeFlags{}, err + } + if !legacyDNSMode { + err = validateLegacyDNSModeDisabledRules(router, r.rawRules, nil) + if err != nil { + return nil, false, dnsRuleModeFlags{}, err + } + } + err = validateEvaluateFakeIPRules(r.rawRules, r.transport) + if err != nil { + return nil, false, dnsRuleModeFlags{}, err + } + newRules := make([]adapter.DNSRule, 0, len(r.rawRules)) + for i, ruleOptions := range r.rawRules { + var dnsRule adapter.DNSRule + dnsRule, err = R.NewDNSRule(r.ctx, r.logger, ruleOptions, true, legacyDNSMode) + if err != nil { + closeRules(newRules) + return nil, false, dnsRuleModeFlags{}, E.Cause(err, "parse dns rule[", i, "]") + } + newRules = append(newRules, dnsRule) + } + if startRules { + for i, rule := range newRules { + err = rule.Start() + if err != nil { + closeRules(newRules) + return nil, false, dnsRuleModeFlags{}, E.Cause(err, "initialize DNS rule[", i, "]") + } + } + } + return newRules, legacyDNSMode, modeFlags, nil +} + +func closeRules(rules []adapter.DNSRule) { + for _, rule := range rules { + _ = rule.Close() + } +} + +func (r *Router) ValidateRuleSetMetadataUpdate(tag string, metadata adapter.RuleSetMetadata) error { + if len(r.rawRules) == 0 { + return nil + } + router := service.FromContext[adapter.Router](r.ctx) + if router == nil { + return E.New("router service not found") + } + overrides := map[string]adapter.RuleSetMetadata{ + tag: metadata, + } + r.rulesAccess.RLock() + started := r.started + legacyDNSMode := r.legacyDNSMode + closing := r.closing + r.rulesAccess.RUnlock() + if closing { + return nil + } + if !started { + candidateLegacyDNSMode, _, err := resolveLegacyDNSMode(router, r.rawRules, overrides) + if err != nil { + return err + } + if !candidateLegacyDNSMode { + return validateLegacyDNSModeDisabledRules(router, r.rawRules, overrides) + } + return nil + } + candidateLegacyDNSMode, flags, err := resolveLegacyDNSMode(router, r.rawRules, overrides) + if err != nil { + return err + } + if legacyDNSMode { + if !candidateLegacyDNSMode && flags.disabled { + err := validateLegacyDNSModeDisabledRules(router, r.rawRules, overrides) + if err != nil { + return err + } + return E.New(deprecated.OptionLegacyDNSAddressFilter.MessageWithLink()) + } + return nil + } + if candidateLegacyDNSMode { + return E.New(deprecated.OptionLegacyDNSAddressFilter.MessageWithLink()) + } + return validateLegacyDNSModeDisabledRules(router, r.rawRules, overrides) +} + +func (r *Router) matchDNS(ctx context.Context, rules []adapter.DNSRule, allowFakeIP bool, ruleIndex int, isAddressQuery bool, options *adapter.DNSQueryOptions) (adapter.DNSTransport, adapter.DNSRule, int) { + metadata := adapter.ContextFrom(ctx) + if metadata == nil { + panic("no context") + } + var currentRuleIndex int + if ruleIndex != -1 { + currentRuleIndex = ruleIndex + 1 + } + for ; currentRuleIndex < len(rules); currentRuleIndex++ { + currentRule := rules[currentRuleIndex] + if currentRule.WithAddressLimit() && !isAddressQuery { + continue + } + metadata.ResetRuleCache() + metadata.DestinationAddressMatchFromResponse = false + if currentRule.LegacyPreMatch(metadata) { + if ruleDescription := currentRule.String(); ruleDescription != "" { + r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action()) + } else { + r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] => ", currentRule.Action()) + } + switch action := currentRule.Action().(type) { + case *R.RuleActionDNSRoute: + transport, loaded := r.transport.Transport(action.Server) + if !loaded { + r.logger.ErrorContext(ctx, "transport not found: ", action.Server) + continue + } + isFakeIP := transport.Type() == C.DNSTypeFakeIP + if isFakeIP && !allowFakeIP { + continue + } + if action.Strategy != C.DomainStrategyAsIS { + options.Strategy = action.Strategy + } + if isFakeIP || action.DisableCache { + options.DisableCache = true + } + if action.RewriteTTL != nil { + options.RewriteTTL = action.RewriteTTL + } + if action.ClientSubnet.IsValid() { + options.ClientSubnet = action.ClientSubnet + } + return transport, currentRule, currentRuleIndex + case *R.RuleActionDNSRouteOptions: + if action.Strategy != C.DomainStrategyAsIS { + options.Strategy = action.Strategy + } + if action.DisableCache { + options.DisableCache = true + } + if action.RewriteTTL != nil { + options.RewriteTTL = action.RewriteTTL + } + if action.ClientSubnet.IsValid() { + options.ClientSubnet = action.ClientSubnet + } + case *R.RuleActionReject: + return nil, currentRule, currentRuleIndex + case *R.RuleActionPredefined: + return nil, currentRule, currentRuleIndex + } + } + } + transport := r.transport.Default() + return transport, nil, -1 +} + +func (r *Router) applyDNSRouteOptions(options *adapter.DNSQueryOptions, routeOptions R.RuleActionDNSRouteOptions) { + // Strategy is intentionally skipped here. A non-default DNS rule action strategy + // forces legacy mode via resolveLegacyDNSMode, so this path is only reachable + // when strategy remains at its default value. + if routeOptions.DisableCache { + options.DisableCache = true + } + if routeOptions.DisableOptimisticCache { + options.DisableOptimisticCache = true + } + if routeOptions.RewriteTTL != nil { + options.RewriteTTL = routeOptions.RewriteTTL + } + if routeOptions.ClientSubnet.IsValid() { + options.ClientSubnet = routeOptions.ClientSubnet + } +} + +type dnsRouteStatus uint8 + +const ( + dnsRouteStatusMissing dnsRouteStatus = iota + dnsRouteStatusSkipped + dnsRouteStatusResolved +) + +func (r *Router) resolveDNSRoute(server string, routeOptions R.RuleActionDNSRouteOptions, allowFakeIP bool, options *adapter.DNSQueryOptions) (adapter.DNSTransport, dnsRouteStatus) { + transport, loaded := r.transport.Transport(server) + if !loaded { + return nil, dnsRouteStatusMissing + } + isFakeIP := transport.Type() == C.DNSTypeFakeIP + if isFakeIP && !allowFakeIP { + return transport, dnsRouteStatusSkipped + } + r.applyDNSRouteOptions(options, routeOptions) + if isFakeIP { + options.DisableCache = true + } + return transport, dnsRouteStatusResolved +} + +func (r *Router) logRuleMatch(ctx context.Context, ruleIndex int, currentRule adapter.DNSRule) { + if ruleDescription := currentRule.String(); ruleDescription != "" { + r.logger.DebugContext(ctx, "match[", ruleIndex, "] ", currentRule, " => ", currentRule.Action()) + } else { + r.logger.DebugContext(ctx, "match[", ruleIndex, "] => ", currentRule.Action()) + } +} + +type exchangeWithRulesResult struct { + response *mDNS.Msg + transport adapter.DNSTransport + rejectAction *R.RuleActionReject + err error +} + +const dnsRespondMissingResponseMessage = "respond action requires an evaluated response from a preceding evaluate action" + +func (r *Router) exchangeWithRules(ctx context.Context, rules []adapter.DNSRule, message *mDNS.Msg, options adapter.DNSQueryOptions, allowFakeIP bool) exchangeWithRulesResult { + metadata := adapter.ContextFrom(ctx) + if metadata == nil { + panic("no context") + } + effectiveOptions := options + var evaluatedResponse *mDNS.Msg + var evaluatedTransport adapter.DNSTransport + for currentRuleIndex, currentRule := range rules { + metadata.ResetRuleCache() + metadata.DNSResponse = evaluatedResponse + metadata.DestinationAddressMatchFromResponse = false + if !currentRule.Match(metadata) { + continue + } + r.logRuleMatch(ctx, currentRuleIndex, currentRule) + switch action := currentRule.Action().(type) { + case *R.RuleActionDNSRouteOptions: + r.applyDNSRouteOptions(&effectiveOptions, *action) + case *R.RuleActionEvaluate: + queryOptions := effectiveOptions + transport, loaded := r.transport.Transport(action.Server) + if !loaded { + r.logger.ErrorContext(ctx, "transport not found: ", action.Server) + evaluatedResponse = nil + evaluatedTransport = nil + continue + } + r.applyDNSRouteOptions(&queryOptions, action.RuleActionDNSRouteOptions) + exchangeOptions := queryOptions + if exchangeOptions.Strategy == C.DomainStrategyAsIS { + exchangeOptions.Strategy = r.defaultDomainStrategy + } + response, err := r.client.Exchange(adapter.OverrideContext(ctx), transport, message, exchangeOptions, nil) + if err != nil { + r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String()))) + evaluatedResponse = nil + evaluatedTransport = nil + continue + } + evaluatedResponse = response + evaluatedTransport = transport + case *R.RuleActionRespond: + if evaluatedResponse == nil { + return exchangeWithRulesResult{ + err: E.New(dnsRespondMissingResponseMessage), + } + } + return exchangeWithRulesResult{ + response: evaluatedResponse, + transport: evaluatedTransport, + } + case *R.RuleActionDNSRoute: + queryOptions := effectiveOptions + transport, status := r.resolveDNSRoute(action.Server, action.RuleActionDNSRouteOptions, allowFakeIP, &queryOptions) + switch status { + case dnsRouteStatusMissing: + r.logger.ErrorContext(ctx, "transport not found: ", action.Server) + continue + case dnsRouteStatusSkipped: + continue + } + exchangeOptions := queryOptions + if exchangeOptions.Strategy == C.DomainStrategyAsIS { + exchangeOptions.Strategy = r.defaultDomainStrategy + } + response, err := r.client.Exchange(adapter.OverrideContext(ctx), transport, message, exchangeOptions, nil) + return exchangeWithRulesResult{ + response: response, + transport: transport, + err: err, + } + case *R.RuleActionReject: + switch action.Method { + case C.RuleActionRejectMethodDefault: + return exchangeWithRulesResult{ + response: &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Id: message.Id, + Rcode: mDNS.RcodeRefused, + Response: true, + }, + Question: []mDNS.Question{message.Question[0]}, + }, + rejectAction: action, + } + case C.RuleActionRejectMethodDrop: + return exchangeWithRulesResult{ + rejectAction: action, + err: tun.ErrDrop, + } + } + case *R.RuleActionPredefined: + return exchangeWithRulesResult{ + response: action.Response(message), + } + } + } + transport := r.transport.Default() + exchangeOptions := effectiveOptions + if exchangeOptions.Strategy == C.DomainStrategyAsIS { + exchangeOptions.Strategy = r.defaultDomainStrategy + } + response, err := r.client.Exchange(adapter.OverrideContext(ctx), transport, message, exchangeOptions, nil) + return exchangeWithRulesResult{ + response: response, + transport: transport, + err: err, + } +} + +func (r *Router) resolveLookupStrategy(options adapter.DNSQueryOptions) C.DomainStrategy { + if options.LookupStrategy != C.DomainStrategyAsIS { + return options.LookupStrategy + } + if options.Strategy != C.DomainStrategyAsIS { + return options.Strategy + } + return r.defaultDomainStrategy +} + +func withLookupQueryMetadata(ctx context.Context, qType uint16) context.Context { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.QueryType = qType + metadata.IPVersion = 0 + switch qType { + case mDNS.TypeA: + metadata.IPVersion = 4 + case mDNS.TypeAAAA: + metadata.IPVersion = 6 + } + return ctx +} + +func filterAddressesByQueryType(addresses []netip.Addr, qType uint16) []netip.Addr { + switch qType { + case mDNS.TypeA: + return common.Filter(addresses, func(address netip.Addr) bool { + return address.Is4() + }) + case mDNS.TypeAAAA: + return common.Filter(addresses, func(address netip.Addr) bool { + return address.Is6() + }) + default: + return addresses + } +} + +func (r *Router) lookupWithRules(ctx context.Context, rules []adapter.DNSRule, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, error) { + strategy := r.resolveLookupStrategy(options) + lookupOptions := options + if strategy != C.DomainStrategyAsIS { + lookupOptions.Strategy = strategy + } + if strategy == C.DomainStrategyIPv4Only { + return r.lookupWithRulesType(ctx, rules, domain, mDNS.TypeA, lookupOptions) + } + if strategy == C.DomainStrategyIPv6Only { + return r.lookupWithRulesType(ctx, rules, domain, mDNS.TypeAAAA, lookupOptions) + } + var ( + response4 []netip.Addr + response6 []netip.Addr + ) + var group task.Group + group.Append("exchange4", func(ctx context.Context) error { + result, err := r.lookupWithRulesType(ctx, rules, domain, mDNS.TypeA, lookupOptions) + response4 = result + return err + }) + group.Append("exchange6", func(ctx context.Context) error { + result, err := r.lookupWithRulesType(ctx, rules, domain, mDNS.TypeAAAA, lookupOptions) + response6 = result + return err + }) + err := group.Run(ctx) + if len(response4) == 0 && len(response6) == 0 { + return nil, err + } + return sortAddresses(response4, response6, strategy), nil +} + +func (r *Router) lookupWithRulesType(ctx context.Context, rules []adapter.DNSRule, domain string, qType uint16, options adapter.DNSQueryOptions) ([]netip.Addr, error) { + request := &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + RecursionDesired: true, + }, + Question: []mDNS.Question{{ + Name: mDNS.Fqdn(domain), + Qtype: qType, + Qclass: mDNS.ClassINET, + }}, + } + exchangeResult := r.exchangeWithRules(withLookupQueryMetadata(ctx, qType), rules, request, options, false) + if exchangeResult.rejectAction != nil { + return nil, exchangeResult.rejectAction.Error(ctx) + } + if exchangeResult.err != nil { + return nil, exchangeResult.err + } + if exchangeResult.response.Rcode != mDNS.RcodeSuccess { + return nil, RcodeError(exchangeResult.response.Rcode) + } + return filterAddressesByQueryType(MessageToAddresses(exchangeResult.response), qType), nil +} + +func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapter.DNSQueryOptions) (*mDNS.Msg, error) { + if len(message.Question) != 1 { + r.logger.WarnContext(ctx, "bad question size: ", len(message.Question)) + responseMessage := mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Id: message.Id, + Response: true, + Rcode: mDNS.RcodeFormatError, + }, + Question: message.Question, + } + return &responseMessage, nil + } + r.rulesAccess.RLock() + if r.closing { + r.rulesAccess.RUnlock() + return nil, E.New("dns router closed") + } + rules := r.rules + legacyDNSMode := r.legacyDNSMode + r.rulesAccess.RUnlock() + r.logger.DebugContext(ctx, "exchange ", FormatQuestion(message.Question[0].String())) + var ( + response *mDNS.Msg + transport adapter.DNSTransport + err error + ) + var metadata *adapter.InboundContext + ctx, metadata = adapter.ExtendContext(ctx) + metadata.Destination = M.Socksaddr{} + metadata.QueryType = message.Question[0].Qtype + metadata.DNSResponse = nil + metadata.DestinationAddressMatchFromResponse = false + switch metadata.QueryType { + case mDNS.TypeA: + metadata.IPVersion = 4 + case mDNS.TypeAAAA: + metadata.IPVersion = 6 + } + metadata.Domain = FqdnToDomain(message.Question[0].Name) + if options.Transport != nil { + transport = options.Transport + if options.Strategy == C.DomainStrategyAsIS { + options.Strategy = r.defaultDomainStrategy + } + response, err = r.client.Exchange(ctx, transport, message, options, nil) + } else if !legacyDNSMode { + exchangeResult := r.exchangeWithRules(ctx, rules, message, options, true) + response, transport, err = exchangeResult.response, exchangeResult.transport, exchangeResult.err + } else { + var ( + rule adapter.DNSRule + ruleIndex int + ) + ruleIndex = -1 + for { + dnsCtx := adapter.OverrideContext(ctx) + dnsOptions := options + transport, rule, ruleIndex = r.matchDNS(ctx, rules, true, ruleIndex, isAddressQuery(message), &dnsOptions) + if rule != nil { + switch action := rule.Action().(type) { + case *R.RuleActionReject: + switch action.Method { + case C.RuleActionRejectMethodDefault: + return &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Id: message.Id, + Rcode: mDNS.RcodeRefused, + Response: true, + }, + Question: []mDNS.Question{message.Question[0]}, + }, nil + case C.RuleActionRejectMethodDrop: + return nil, tun.ErrDrop + } + case *R.RuleActionPredefined: + err = nil + response = action.Response(message) + goto done + } + } + responseCheck := addressLimitResponseCheck(rule, metadata) + if dnsOptions.Strategy == C.DomainStrategyAsIS { + dnsOptions.Strategy = r.defaultDomainStrategy + } + response, err = r.client.Exchange(dnsCtx, transport, message, dnsOptions, responseCheck) + var rejected bool + var bypass bool + if err != nil { + if errors.Is(err, ErrResponseRejectedCached) { + rejected = true + r.logger.DebugContext(ctx, E.Cause(err, "response ", transport.Tag(), " rejected for ", FormatQuestion(message.Question[0].String())), " (cached)") + } else if errors.Is(err, ErrResponseRejected) { + rejected = true + r.logger.DebugContext(ctx, E.Cause(err, "response ", transport.Tag(), " rejected for ", FormatQuestion(message.Question[0].String()))) + } else if len(message.Question) > 0 { + r.logger.ErrorContext(ctx, E.Cause(err, "exchange ", transport.Tag(), " failed for ", FormatQuestion(message.Question[0].String()))) + } else { + r.logger.ErrorContext(ctx, E.Cause(err, "exchange ", transport.Tag(), " failed for ")) + } + if rule != nil && rule.BypassIfFailed() && ruleIndex != -1 { + select { + case <-ctx.Done(): + default: + bypass = true + } + } + } + if responseCheck != nil && rejected { + continue + } + if bypass { + continue + } + + break + } + } +done: + if err != nil { + return nil, err + } + if r.dnsReverseMapping != nil && len(message.Question) > 0 && response != nil && len(response.Answer) > 0 { + if transport == nil || transport.Type() != C.DNSTypeFakeIP { + for _, answer := range response.Answer { + switch record := answer.(type) { + case *mDNS.A: + r.dnsReverseMapping.AddWithLifetime(M.AddrFromIP(record.A), FqdnToDomain(record.Hdr.Name), time.Duration(record.Hdr.Ttl)*time.Second) + case *mDNS.AAAA: + r.dnsReverseMapping.AddWithLifetime(M.AddrFromIP(record.AAAA), FqdnToDomain(record.Hdr.Name), time.Duration(record.Hdr.Ttl)*time.Second) + } + } + } + } + return response, nil +} + +func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, error) { + r.rulesAccess.RLock() + if r.closing { + r.rulesAccess.RUnlock() + return nil, E.New("dns router closed") + } + rules := r.rules + legacyDNSMode := r.legacyDNSMode + r.rulesAccess.RUnlock() + var ( + responseAddrs []netip.Addr + err error + ) + printResult := func() { + if err == nil && len(responseAddrs) == 0 { + err = E.New("empty result") + } + if err != nil { + if errors.Is(err, ErrResponseRejectedCached) { + r.logger.DebugContext(ctx, "response rejected for ", domain, " (cached)") + } else if errors.Is(err, ErrResponseRejected) { + r.logger.DebugContext(ctx, "response rejected for ", domain) + } else if R.IsRejected(err) { + r.logger.DebugContext(ctx, "lookup rejected for ", domain) + } else { + r.logger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain)) + } + } + if err != nil { + err = E.Cause(err, "lookup ", domain) + } + } + r.logger.DebugContext(ctx, "lookup domain ", domain) + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Destination = M.Socksaddr{} + metadata.Domain = FqdnToDomain(domain) + metadata.DNSResponse = nil + metadata.DestinationAddressMatchFromResponse = false + if options.Transport != nil { + transport := options.Transport + if options.Strategy == C.DomainStrategyAsIS { + options.Strategy = r.defaultDomainStrategy + } + responseAddrs, err = r.client.Lookup(ctx, transport, domain, options, nil) + } else if !legacyDNSMode { + responseAddrs, err = r.lookupWithRules(ctx, rules, domain, options) + } else { + var ( + transport adapter.DNSTransport + rule adapter.DNSRule + ruleIndex int + ) + ruleIndex = -1 + for { + dnsCtx := adapter.OverrideContext(ctx) + dnsOptions := options + transport, rule, ruleIndex = r.matchDNS(ctx, rules, false, ruleIndex, true, &dnsOptions) + if rule != nil { + switch action := rule.Action().(type) { + case *R.RuleActionReject: + return nil, &R.RejectedError{Cause: action.Error(ctx)} + case *R.RuleActionPredefined: + responseAddrs = nil + if action.Rcode != mDNS.RcodeSuccess { + err = RcodeError(action.Rcode) + } else { + err = nil + for _, answer := range action.Answer { + switch record := answer.(type) { + case *mDNS.A: + responseAddrs = append(responseAddrs, M.AddrFromIP(record.A)) + case *mDNS.AAAA: + responseAddrs = append(responseAddrs, M.AddrFromIP(record.AAAA)) + } + } + responseAddrs = FilterBlocked(responseAddrs) + + } + goto response + } + } + responseCheck := addressLimitResponseCheck(rule, metadata) + if dnsOptions.Strategy == C.DomainStrategyAsIS { + dnsOptions.Strategy = r.defaultDomainStrategy + } + responseAddrs, err = r.client.Lookup(dnsCtx, transport, domain, dnsOptions, responseCheck) + if rule != nil && len(responseAddrs) == 0 && rule.BypassIfFailed() && ruleIndex != -1 { + continue + } + if responseCheck == nil || err == nil { + break + } + printResult() + } + } +response: + printResult() + if len(responseAddrs) > 0 { + r.logger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(responseAddrs), " ")) + } + return responseAddrs, err +} + +func isAddressQuery(message *mDNS.Msg) bool { + for _, question := range message.Question { + if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA || question.Qtype == mDNS.TypeHTTPS { + return true + } + } + return false +} + +func addressLimitResponseCheck(rule adapter.DNSRule, metadata *adapter.InboundContext) func(response *mDNS.Msg) bool { + if rule == nil || !rule.WithAddressLimit() { + return nil + } + responseMetadata := *metadata + return func(response *mDNS.Msg) bool { + checkMetadata := responseMetadata + return rule.MatchAddressLimit(&checkMetadata, response) + } +} + +func (r *Router) ClearCache() { + r.client.ClearCache() + if r.platformInterface != nil { + r.platformInterface.ClearDNSCache() + } +} + +func (r *Router) LookupReverseMapping(ip netip.Addr) (string, bool) { + if r.dnsReverseMapping == nil { + return "", false + } + domain, loaded := r.dnsReverseMapping.Get(ip) + return domain, loaded +} + +func (r *Router) ResetNetwork() { + r.ClearCache() + for _, transport := range r.transport.Transports() { + transport.Reset() + } +} + +func defaultRuleNeedsLegacyDNSModeFromAddressFilter(rule option.DefaultDNSRule) bool { + if rule.RuleSetIPCIDRAcceptEmpty { //nolint:staticcheck + return true + } + return !rule.MatchResponse && (rule.IPAcceptAny || len(rule.IPCIDR) > 0 || rule.IPIsPrivate) +} + +func hasResponseMatchFields(rule option.DefaultDNSRule) bool { + return rule.ResponseRcode != nil || + len(rule.ResponseAnswer) > 0 || + len(rule.ResponseNs) > 0 || + len(rule.ResponseExtra) > 0 +} + +func defaultRuleDisablesLegacyDNSMode(rule option.DefaultDNSRule) bool { + return rule.MatchResponse || + hasResponseMatchFields(rule) || + rule.Action == C.RuleActionTypeEvaluate || + rule.Action == C.RuleActionTypeRespond || + rule.IPVersion > 0 || + len(rule.QueryType) > 0 +} + +type dnsRuleModeFlags struct { + disabled bool + needed bool + neededFromStrategy bool +} + +func (f *dnsRuleModeFlags) merge(other dnsRuleModeFlags) { + f.disabled = f.disabled || other.disabled + f.needed = f.needed || other.needed + f.neededFromStrategy = f.neededFromStrategy || other.neededFromStrategy +} + +func resolveLegacyDNSMode(router adapter.Router, rules []option.DNSRule, metadataOverrides map[string]adapter.RuleSetMetadata) (bool, dnsRuleModeFlags, error) { + flags, err := dnsRuleModeRequirements(router, rules, metadataOverrides) + if err != nil { + return false, flags, err + } + if flags.disabled && flags.neededFromStrategy { + return false, flags, E.New(deprecated.OptionLegacyDNSRuleStrategy.MessageWithLink()) + } + if flags.disabled { + return false, flags, nil + } + return flags.needed, flags, nil +} + +func dnsRuleModeRequirements(router adapter.Router, rules []option.DNSRule, metadataOverrides map[string]adapter.RuleSetMetadata) (dnsRuleModeFlags, error) { + var flags dnsRuleModeFlags + for i, rule := range rules { + ruleFlags, err := dnsRuleModeRequirementsInRule(router, rule, metadataOverrides) + if err != nil { + return dnsRuleModeFlags{}, E.Cause(err, "dns rule[", i, "]") + } + flags.merge(ruleFlags) + } + return flags, nil +} + +func dnsRuleModeRequirementsInRule(router adapter.Router, rule option.DNSRule, metadataOverrides map[string]adapter.RuleSetMetadata) (dnsRuleModeFlags, error) { + switch rule.Type { + case "", C.RuleTypeDefault: + return dnsRuleModeRequirementsInDefaultRule(router, rule.DefaultOptions, metadataOverrides) + case C.RuleTypeLogical: + flags := dnsRuleModeFlags{ + disabled: dnsRuleActionType(rule) == C.RuleActionTypeEvaluate || + dnsRuleActionType(rule) == C.RuleActionTypeRespond || + dnsRuleActionDisablesLegacyDNSMode(rule.LogicalOptions.DNSRuleAction), + neededFromStrategy: dnsRuleActionHasStrategy(rule.LogicalOptions.DNSRuleAction), + } + flags.needed = flags.neededFromStrategy + for i, subRule := range rule.LogicalOptions.Rules { + subFlags, err := dnsRuleModeRequirementsInRule(router, subRule, metadataOverrides) + if err != nil { + return dnsRuleModeFlags{}, E.Cause(err, "sub rule[", i, "]") + } + flags.merge(subFlags) + } + return flags, nil + default: + return dnsRuleModeFlags{}, nil + } +} + +func dnsRuleModeRequirementsInDefaultRule(router adapter.Router, rule option.DefaultDNSRule, metadataOverrides map[string]adapter.RuleSetMetadata) (dnsRuleModeFlags, error) { + flags := dnsRuleModeFlags{ + disabled: defaultRuleDisablesLegacyDNSMode(rule) || dnsRuleActionDisablesLegacyDNSMode(rule.DNSRuleAction), + neededFromStrategy: dnsRuleActionHasStrategy(rule.DNSRuleAction), + } + flags.needed = defaultRuleNeedsLegacyDNSModeFromAddressFilter(rule) || flags.neededFromStrategy + if len(rule.RuleSet) == 0 { + return flags, nil + } + if router == nil { + return dnsRuleModeFlags{}, E.New("router service not found") + } + for _, tag := range rule.RuleSet { + metadata, err := lookupDNSRuleSetMetadata(router, tag, metadataOverrides) + if err != nil { + return dnsRuleModeFlags{}, err + } + // ip_version is not a headless-rule item, so ContainsIPVersionRule is intentionally absent. + flags.disabled = flags.disabled || metadata.ContainsDNSQueryTypeRule + if !rule.RuleSetIPCIDRMatchSource && metadata.ContainsIPCIDRRule { + flags.needed = true + } + } + return flags, nil +} + +func lookupDNSRuleSetMetadata(router adapter.Router, tag string, metadataOverrides map[string]adapter.RuleSetMetadata) (adapter.RuleSetMetadata, error) { + if metadataOverrides != nil { + if metadata, loaded := metadataOverrides[tag]; loaded { + return metadata, nil + } + } + ruleSet, loaded := router.RuleSet(tag) + if !loaded { + return adapter.RuleSetMetadata{}, E.New("rule-set not found: ", tag) + } + return ruleSet.Metadata(), nil +} + +func referencedDNSRuleSetTags(rules []option.DNSRule) []string { + tagMap := make(map[string]bool) + var walkRule func(rule option.DNSRule) + walkRule = func(rule option.DNSRule) { + switch rule.Type { + case "", C.RuleTypeDefault: + for _, tag := range rule.DefaultOptions.RuleSet { + tagMap[tag] = true + } + case C.RuleTypeLogical: + for _, subRule := range rule.LogicalOptions.Rules { + walkRule(subRule) + } + } + } + for _, rule := range rules { + walkRule(rule) + } + tags := make([]string, 0, len(tagMap)) + for tag := range tagMap { + if tag != "" { + tags = append(tags, tag) + } + } + return tags +} + +func validateLegacyDNSModeDisabledRules(router adapter.Router, rules []option.DNSRule, metadataOverrides map[string]adapter.RuleSetMetadata) error { + var seenEvaluate bool + for i, rule := range rules { + requiresPriorEvaluate, err := validateLegacyDNSModeDisabledRuleTree(router, rule, metadataOverrides) + if err != nil { + return E.Cause(err, "validate dns rule[", i, "]") + } + if requiresPriorEvaluate && !seenEvaluate { + return E.New("dns rule[", i, "]: response-based matching requires a preceding evaluate action") + } + if dnsRuleActionType(rule) == C.RuleActionTypeEvaluate { + seenEvaluate = true + } + } + return nil +} + +func validateEvaluateFakeIPRules(rules []option.DNSRule, transportManager adapter.DNSTransportManager) error { + if transportManager == nil { + return nil + } + for i, rule := range rules { + if dnsRuleActionType(rule) != C.RuleActionTypeEvaluate { + continue + } + server := dnsRuleActionServer(rule) + if server == "" { + continue + } + transport, loaded := transportManager.Transport(server) + if !loaded || transport.Type() != C.DNSTypeFakeIP { + continue + } + return E.New("dns rule[", i, "]: evaluate action cannot use fakeip server: ", server) + } + return nil +} + +func validateLegacyDNSModeDisabledRuleTree(router adapter.Router, rule option.DNSRule, metadataOverrides map[string]adapter.RuleSetMetadata) (bool, error) { + switch rule.Type { + case "", C.RuleTypeDefault: + return validateLegacyDNSModeDisabledDefaultRule(router, rule.DefaultOptions, metadataOverrides) + case C.RuleTypeLogical: + requiresPriorEvaluate := dnsRuleActionType(rule) == C.RuleActionTypeRespond + for i, subRule := range rule.LogicalOptions.Rules { + subRequiresPriorEvaluate, err := validateLegacyDNSModeDisabledRuleTree(router, subRule, metadataOverrides) + if err != nil { + return false, E.Cause(err, "sub rule[", i, "]") + } + requiresPriorEvaluate = requiresPriorEvaluate || subRequiresPriorEvaluate + } + return requiresPriorEvaluate, nil + default: + return false, nil + } +} + +func validateLegacyDNSModeDisabledDefaultRule(router adapter.Router, rule option.DefaultDNSRule, metadataOverrides map[string]adapter.RuleSetMetadata) (bool, error) { + hasResponseRecords := hasResponseMatchFields(rule) + if (hasResponseRecords || len(rule.IPCIDR) > 0 || rule.IPIsPrivate || rule.IPAcceptAny) && !rule.MatchResponse { + return false, E.New("Response Match Fields (ip_cidr, ip_is_private, ip_accept_any, response_rcode, response_answer, response_ns, response_extra) require match_response to be enabled") + } + // rule_set entries are only rejected when every referenced set is pure-IP; + // mixed sets still fall through because their non-IP branches remain matchable + // before a DNS response is available. + if !rule.MatchResponse && len(rule.RuleSet) > 0 { + for _, tag := range rule.RuleSet { + metadata, err := lookupDNSRuleSetMetadata(router, tag, metadataOverrides) + if err != nil { + return false, err + } + if metadata.ContainsIPCIDRRule && !metadata.ContainsNonIPCIDRRule { + return false, E.New(deprecated.OptionLegacyDNSAddressFilter.MessageWithLink()) + } + } + } + if rule.RuleSetIPCIDRAcceptEmpty { //nolint:staticcheck + return false, E.New(deprecated.OptionRuleSetIPCIDRAcceptEmpty.MessageWithLink()) + } + return rule.MatchResponse || rule.Action == C.RuleActionTypeRespond, nil +} + +func dnsRuleActionDisablesLegacyDNSMode(action option.DNSRuleAction) bool { + switch action.Action { + case "", C.RuleActionTypeRoute, C.RuleActionTypeEvaluate: + return action.RouteOptions.DisableOptimisticCache + case C.RuleActionTypeRouteOptions: + return action.RouteOptionsOptions.DisableOptimisticCache + default: + return false + } +} + +func dnsRuleActionHasStrategy(action option.DNSRuleAction) bool { + switch action.Action { + case "", C.RuleActionTypeRoute, C.RuleActionTypeEvaluate: + return C.DomainStrategy(action.RouteOptions.Strategy) != C.DomainStrategyAsIS + case C.RuleActionTypeRouteOptions: + return C.DomainStrategy(action.RouteOptionsOptions.Strategy) != C.DomainStrategyAsIS + default: + return false + } +} + +func dnsRuleActionType(rule option.DNSRule) string { + switch rule.Type { + case "", C.RuleTypeDefault: + if rule.DefaultOptions.Action == "" { + return C.RuleActionTypeRoute + } + return rule.DefaultOptions.Action + case C.RuleTypeLogical: + if rule.LogicalOptions.Action == "" { + return C.RuleActionTypeRoute + } + return rule.LogicalOptions.Action + default: + return "" + } +} + +func dnsRuleActionServer(rule option.DNSRule) string { + switch rule.Type { + case "", C.RuleTypeDefault: + return rule.DefaultOptions.RouteOptions.Server + case C.RuleTypeLogical: + return rule.LogicalOptions.RouteOptions.Server + default: + return "" + } +} diff --git a/dns/router_test.go b/dns/router_test.go new file mode 100644 index 0000000000..b5a7ebcfb5 --- /dev/null +++ b/dns/router_test.go @@ -0,0 +1,2670 @@ +package dns + +import ( + "context" + "net" + "net/netip" + "sync" + "testing" + "time" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/experimental/deprecated" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + rulepkg "github.com/sagernet/sing-box/route/rule" + "github.com/sagernet/sing-tun" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json/badoption" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service" + + mDNS "github.com/miekg/dns" + "github.com/stretchr/testify/require" + "go4.org/netipx" +) + +type fakeDNSTransport struct { + tag string + transportType string +} + +func (t *fakeDNSTransport) Start(adapter.StartStage) error { return nil } +func (t *fakeDNSTransport) Close() error { return nil } +func (t *fakeDNSTransport) Type() string { return t.transportType } +func (t *fakeDNSTransport) Tag() string { return t.tag } +func (t *fakeDNSTransport) Dependencies() []string { return nil } +func (t *fakeDNSTransport) Reset() {} +func (t *fakeDNSTransport) Exchange(context.Context, *mDNS.Msg) (*mDNS.Msg, error) { + return nil, E.New("unused transport exchange") +} + +type fakeDNSTransportManager struct { + defaultTransport adapter.DNSTransport + transports map[string]adapter.DNSTransport +} + +func (m *fakeDNSTransportManager) Start(adapter.StartStage) error { return nil } +func (m *fakeDNSTransportManager) Close() error { return nil } +func (m *fakeDNSTransportManager) Transports() []adapter.DNSTransport { + transports := make([]adapter.DNSTransport, 0, len(m.transports)) + for _, transport := range m.transports { + transports = append(transports, transport) + } + return transports +} + +func (m *fakeDNSTransportManager) Transport(tag string) (adapter.DNSTransport, bool) { + transport, loaded := m.transports[tag] + return transport, loaded +} +func (m *fakeDNSTransportManager) Default() adapter.DNSTransport { return m.defaultTransport } +func (m *fakeDNSTransportManager) FakeIP() adapter.FakeIPTransport { + return nil +} +func (m *fakeDNSTransportManager) Remove(string) error { return nil } +func (m *fakeDNSTransportManager) Create(context.Context, log.ContextLogger, string, string, any) error { + return E.New("unsupported") +} + +type fakeDNSClient struct { + beforeExchange func(ctx context.Context, transport adapter.DNSTransport, message *mDNS.Msg) + exchange func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) + lookupWithCtx func(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, *mDNS.Msg, error) + lookup func(transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, *mDNS.Msg, error) +} + +type fakeDeprecatedManager struct { + features []deprecated.Note +} + +type fakeRouter struct { + access sync.RWMutex + ruleSets map[string]adapter.RuleSet +} + +func (r *fakeRouter) Start(adapter.StartStage) error { return nil } +func (r *fakeRouter) Close() error { return nil } +func (r *fakeRouter) PreMatch(metadata adapter.InboundContext, _ tun.DirectRouteContext, _ time.Duration, _ bool) (tun.DirectRouteDestination, error) { + return nil, nil +} + +func (r *fakeRouter) RouteConnection(context.Context, net.Conn, adapter.InboundContext) error { + return nil +} + +func (r *fakeRouter) RoutePacketConnection(context.Context, N.PacketConn, adapter.InboundContext) error { + return nil +} + +func (r *fakeRouter) RouteConnectionEx(context.Context, net.Conn, adapter.InboundContext, N.CloseHandlerFunc) { +} + +func (r *fakeRouter) RoutePacketConnectionEx(context.Context, N.PacketConn, adapter.InboundContext, N.CloseHandlerFunc) { +} + +func (r *fakeRouter) RuleSet(tag string) (adapter.RuleSet, bool) { + r.access.RLock() + defer r.access.RUnlock() + ruleSet, loaded := r.ruleSets[tag] + return ruleSet, loaded +} + +func (r *fakeRouter) Rules() []adapter.Rule { return nil } +func (r *fakeRouter) NeedFindProcess() bool { return false } +func (r *fakeRouter) NeedFindNeighbor() bool { return false } +func (r *fakeRouter) NeighborResolver() adapter.NeighborResolver { return nil } +func (r *fakeRouter) AppendTracker(adapter.ConnectionTracker) {} +func (r *fakeRouter) ResetNetwork() {} + +type fakeRuleSet struct { + access sync.Mutex + metadata adapter.RuleSetMetadata + metadataRead func(adapter.RuleSetMetadata) adapter.RuleSetMetadata + match func(*adapter.InboundContext) bool + callbacks list.List[adapter.RuleSetUpdateCallback] + refs int + afterIncrementReference func() + beforeDecrementReference func() +} + +func (s *fakeRuleSet) Name() string { return "fake-rule-set" } +func (s *fakeRuleSet) StartContext(context.Context, *adapter.HTTPStartContext) error { return nil } +func (s *fakeRuleSet) PostStart() error { return nil } +func (s *fakeRuleSet) Metadata() adapter.RuleSetMetadata { + s.access.Lock() + metadata := s.metadata + metadataRead := s.metadataRead + s.access.Unlock() + if metadataRead != nil { + return metadataRead(metadata) + } + return metadata +} +func (s *fakeRuleSet) ExtractIPSet() []*netipx.IPSet { return nil } +func (s *fakeRuleSet) IncRef() { + s.access.Lock() + s.refs++ + afterIncrementReference := s.afterIncrementReference + s.access.Unlock() + if afterIncrementReference != nil { + afterIncrementReference() + } +} + +func (s *fakeRuleSet) DecRef() { + s.access.Lock() + beforeDecrementReference := s.beforeDecrementReference + s.access.Unlock() + if beforeDecrementReference != nil { + beforeDecrementReference() + } + s.access.Lock() + defer s.access.Unlock() + s.refs-- + if s.refs < 0 { + panic("rule-set: negative refs") + } +} +func (s *fakeRuleSet) Cleanup() {} +func (s *fakeRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] { + s.access.Lock() + defer s.access.Unlock() + return s.callbacks.PushBack(callback) +} + +func (s *fakeRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) { + s.access.Lock() + defer s.access.Unlock() + s.callbacks.Remove(element) +} +func (s *fakeRuleSet) Close() error { return nil } +func (s *fakeRuleSet) Match(metadata *adapter.InboundContext) bool { + s.access.Lock() + match := s.match + s.access.Unlock() + if match != nil { + return match(metadata) + } + return true +} +func (s *fakeRuleSet) String() string { return "fake-rule-set" } +func (s *fakeRuleSet) updateMetadata(metadata adapter.RuleSetMetadata) { + s.access.Lock() + s.metadata = metadata + callbacks := s.callbacks.Array() + s.access.Unlock() + for _, callback := range callbacks { + callback(s) + } +} + +func (s *fakeRuleSet) refCount() int { + s.access.Lock() + defer s.access.Unlock() + return s.refs +} + +func (m *fakeDeprecatedManager) ReportDeprecated(feature deprecated.Note) { + m.features = append(m.features, feature) +} + +func (c *fakeDNSClient) Start() {} + +func (c *fakeDNSClient) Exchange(ctx context.Context, transport adapter.DNSTransport, message *mDNS.Msg, _ adapter.DNSQueryOptions, _ func(*mDNS.Msg) bool) (*mDNS.Msg, error) { + if c.beforeExchange != nil { + c.beforeExchange(ctx, transport, message) + } + if c.exchange == nil { + if len(message.Question) != 1 { + return nil, E.New("unused client exchange") + } + var ( + addresses []netip.Addr + response *mDNS.Msg + err error + ) + if c.lookupWithCtx != nil { + addresses, response, err = c.lookupWithCtx(ctx, transport, FqdnToDomain(message.Question[0].Name), adapter.DNSQueryOptions{}) + } else if c.lookup != nil { + addresses, response, err = c.lookup(transport, FqdnToDomain(message.Question[0].Name), adapter.DNSQueryOptions{}) + } else { + return nil, E.New("unused client exchange") + } + if err != nil { + return nil, err + } + if response != nil { + return response, nil + } + return FixedResponse(0, message.Question[0], addresses, 60), nil + } + return c.exchange(transport, message) +} + +func (c *fakeDNSClient) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(*mDNS.Msg) bool) ([]netip.Addr, error) { + if c.lookup == nil && c.lookupWithCtx == nil { + return nil, E.New("unused client lookup") + } + var ( + addresses []netip.Addr + response *mDNS.Msg + err error + ) + if c.lookupWithCtx != nil { + addresses, response, err = c.lookupWithCtx(ctx, transport, domain, options) + } else { + addresses, response, err = c.lookup(transport, domain, options) + } + if err != nil { + return nil, err + } + if response == nil { + response = FixedResponse(0, fixedQuestion(domain, mDNS.TypeA), addresses, 60) + } + if responseChecker != nil && !responseChecker(response) { + return nil, ErrResponseRejected + } + if addresses != nil { + return addresses, nil + } + return MessageToAddresses(response), nil +} + +func (c *fakeDNSClient) ClearCache() {} + +func newTestRouter(t *testing.T, rules []option.DNSRule, transportManager *fakeDNSTransportManager, client *fakeDNSClient) *Router { + router := newTestRouterWithContext(t, context.Background(), rules, transportManager, client) + t.Cleanup(func() { + router.Close() + }) + return router +} + +func newTestRouterWithContext(t *testing.T, ctx context.Context, rules []option.DNSRule, transportManager *fakeDNSTransportManager, client *fakeDNSClient) *Router { + return newTestRouterWithContextAndLogger(t, ctx, rules, transportManager, client, log.NewNOPFactory().NewLogger("dns")) +} + +func newTestRouterWithContextAndLogger(t *testing.T, ctx context.Context, rules []option.DNSRule, transportManager *fakeDNSTransportManager, client *fakeDNSClient, dnsLogger log.ContextLogger) *Router { + t.Helper() + router := &Router{ + ctx: ctx, + logger: dnsLogger, + transport: transportManager, + client: client, + rawRules: make([]option.DNSRule, 0, len(rules)), + rules: make([]adapter.DNSRule, 0, len(rules)), + defaultDomainStrategy: C.DomainStrategyAsIS, + } + if rules != nil { + err := router.Initialize(rules) + require.NoError(t, err) + err = router.Start(adapter.StartStateStart) + require.NoError(t, err) + } + return router +} + +func fixedQuestion(name string, qType uint16) mDNS.Question { + return mDNS.Question{ + Name: mDNS.Fqdn(name), + Qtype: qType, + Qclass: mDNS.ClassINET, + } +} + +func mustRecord(t *testing.T, record string) option.DNSRecordOptions { + t.Helper() + var value option.DNSRecordOptions + require.NoError(t, value.UnmarshalJSON([]byte(`"`+record+`"`))) + return value +} + +func TestInitializeRejectsDirectLegacyRuleWhenRuleSetForcesNew(t *testing.T) { + t.Parallel() + + ctx := context.Background() + ruleSet, err := rulepkg.NewRuleSet(ctx, log.NewNOPFactory().NewLogger("router"), option.RuleSet{ + Type: C.RuleSetTypeInline, + Tag: "query-set", + InlineOptions: option.PlainRuleSet{ + Rules: []option.HeadlessRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultHeadlessRule{ + QueryType: badoption.Listable[option.DNSQueryType]{option.DNSQueryType(mDNS.TypeA)}, + }, + }}, + }, + }) + require.NoError(t, err) + ctx = service.ContextWith[adapter.Router](ctx, &fakeRouter{ + ruleSets: map[string]adapter.RuleSet{ + "query-set": ruleSet, + }, + }) + + router := &Router{ + ctx: ctx, + logger: log.NewNOPFactory().NewLogger("dns"), + transport: &fakeDNSTransportManager{}, + client: &fakeDNSClient{}, + rawRules: make([]option.DNSRule, 0, 2), + defaultDomainStrategy: C.DomainStrategyAsIS, + } + err = router.Initialize([]option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + RuleSet: badoption.Listable[string]{"query-set"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "default"}, + }, + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + IPIsPrivate: true, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "private"}, + }, + }, + }, + }) + require.ErrorContains(t, err, "Response Match Fields") + require.ErrorContains(t, err, "require match_response") +} + +func TestLookupLegacyDNSModeDefersRuleSetDestinationIPMatch(t *testing.T) { + t.Parallel() + + ctx := context.Background() + ruleSet, err := rulepkg.NewRuleSet(ctx, log.NewNOPFactory().NewLogger("router"), option.RuleSet{ + Type: C.RuleSetTypeInline, + Tag: "legacy-ipcidr-set", + InlineOptions: option.PlainRuleSet{ + Rules: []option.HeadlessRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultHeadlessRule{ + IPCIDR: badoption.Listable[string]{"10.0.0.0/8"}, + }, + }}, + }, + }) + require.NoError(t, err) + ctx = service.ContextWith[adapter.Router](ctx, &fakeRouter{ + ruleSets: map[string]adapter.RuleSet{ + "legacy-ipcidr-set": ruleSet, + }, + }) + + defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP} + privateTransport := &fakeDNSTransport{tag: "private", transportType: C.DNSTypeUDP} + router := newTestRouterWithContext(t, ctx, []option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + RuleSet: badoption.Listable[string]{"legacy-ipcidr-set"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "private"}, + }, + }, + }}, &fakeDNSTransportManager{ + defaultTransport: defaultTransport, + transports: map[string]adapter.DNSTransport{ + "default": defaultTransport, + "private": privateTransport, + }, + }, &fakeDNSClient{ + lookup: func(transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, *mDNS.Msg, error) { + require.Equal(t, "example.com", domain) + require.Equal(t, "private", transport.Tag()) + response := FixedResponse(0, fixedQuestion(domain, mDNS.TypeA), []netip.Addr{netip.MustParseAddr("10.0.0.1")}, 60) + return MessageToAddresses(response), response, nil + }, + }) + + require.True(t, router.legacyDNSMode) + + addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{ + LookupStrategy: C.DomainStrategyIPv4Only, + }) + require.NoError(t, err) + require.Equal(t, []netip.Addr{netip.MustParseAddr("10.0.0.1")}, addresses) +} + +func TestRuleSetUpdateReleasesOldRuleSetRefs(t *testing.T) { + t.Parallel() + + fakeSet := &fakeRuleSet{} + ctx := service.ContextWith[adapter.Router](context.Background(), &fakeRouter{ + ruleSets: map[string]adapter.RuleSet{ + "dynamic-set": fakeSet, + }, + }) + defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP} + router := newTestRouterWithContext(t, ctx, []option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + RuleSet: badoption.Listable[string]{"dynamic-set"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "default"}, + }, + }, + }}, &fakeDNSTransportManager{ + defaultTransport: defaultTransport, + transports: map[string]adapter.DNSTransport{ + "default": defaultTransport, + }, + }, &fakeDNSClient{}) + + require.Equal(t, 1, fakeSet.refCount()) + + fakeSet.updateMetadata(adapter.RuleSetMetadata{}) + require.Equal(t, 1, fakeSet.refCount()) + + fakeSet.updateMetadata(adapter.RuleSetMetadata{}) + require.Equal(t, 1, fakeSet.refCount()) + + require.NoError(t, router.Close()) + require.Zero(t, fakeSet.refCount()) +} + +func TestValidateRuleSetMetadataUpdateRejectsRuleSetThatWouldDisableLegacyDNSMode(t *testing.T) { + t.Parallel() + + fakeSet := &fakeRuleSet{ + metadata: adapter.RuleSetMetadata{ + ContainsIPCIDRRule: true, + }, + } + routerService := &fakeRouter{ + ruleSets: map[string]adapter.RuleSet{ + "dynamic-set": fakeSet, + }, + } + ctx := service.ContextWith[adapter.Router](context.Background(), routerService) + router := newTestRouterWithContext(t, ctx, []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + RuleSet: badoption.Listable[string]{"dynamic-set"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, + }, + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + IPIsPrivate: true, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, + }, + }, + }, + }, &fakeDNSTransportManager{ + defaultTransport: &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + transports: map[string]adapter.DNSTransport{ + "default": &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + "selected": &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP}, + }, + }, &fakeDNSClient{ + lookup: func(adapter.DNSTransport, string, adapter.DNSQueryOptions) ([]netip.Addr, *mDNS.Msg, error) { + return []netip.Addr{netip.MustParseAddr("10.0.0.1")}, nil, nil + }, + }) + require.True(t, router.legacyDNSMode) + + err := router.ValidateRuleSetMetadataUpdate("dynamic-set", adapter.RuleSetMetadata{ + ContainsDNSQueryTypeRule: true, + }) + require.ErrorContains(t, err, "require match_response") +} + +func TestValidateRuleSetMetadataUpdateRejectsRuleSetOnlyLegacyModeSwitchToNew(t *testing.T) { + t.Parallel() + + fakeSet := &fakeRuleSet{ + metadata: adapter.RuleSetMetadata{ + ContainsIPCIDRRule: true, + }, + } + routerService := &fakeRouter{ + ruleSets: map[string]adapter.RuleSet{ + "dynamic-set": fakeSet, + }, + } + ctx := service.ContextWith[adapter.Router](context.Background(), routerService) + router := newTestRouterWithContext(t, ctx, []option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + RuleSet: badoption.Listable[string]{"dynamic-set"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, + }, + }, + }}, &fakeDNSTransportManager{ + defaultTransport: &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + transports: map[string]adapter.DNSTransport{ + "default": &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + "selected": &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP}, + }, + }, &fakeDNSClient{ + lookup: func(adapter.DNSTransport, string, adapter.DNSQueryOptions) ([]netip.Addr, *mDNS.Msg, error) { + return []netip.Addr{netip.MustParseAddr("10.0.0.1")}, nil, nil + }, + }) + require.True(t, router.legacyDNSMode) + + err := router.ValidateRuleSetMetadataUpdate("dynamic-set", adapter.RuleSetMetadata{ + ContainsIPCIDRRule: true, + ContainsDNSQueryTypeRule: true, + }) + require.ErrorContains(t, err, "Address Filter Fields") +} + +func TestValidateRuleSetMetadataUpdateBeforeStartUsesStartupValidation(t *testing.T) { + t.Parallel() + + fakeSet := &fakeRuleSet{} + routerService := &fakeRouter{ + ruleSets: map[string]adapter.RuleSet{ + "dynamic-set": fakeSet, + }, + } + ctx := service.ContextWith[adapter.Router](context.Background(), routerService) + router := &Router{ + ctx: ctx, + logger: log.NewNOPFactory().NewLogger("dns"), + transport: &fakeDNSTransportManager{}, + client: &fakeDNSClient{}, + rawRules: make([]option.DNSRule, 0, 2), + rules: make([]adapter.DNSRule, 0, 2), + defaultDomainStrategy: C.DomainStrategyAsIS, + } + err := router.Initialize([]option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + RuleSet: badoption.Listable[string]{"dynamic-set"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, + }, + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + IPIsPrivate: true, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, + }, + }, + }, + }) + require.NoError(t, err) + require.False(t, router.started) + + err = router.ValidateRuleSetMetadataUpdate("dynamic-set", adapter.RuleSetMetadata{ + ContainsDNSQueryTypeRule: true, + }) + require.ErrorContains(t, err, "require match_response") +} + +func TestValidateRuleSetMetadataUpdateRejectsRuleSetThatWouldRequireLegacyDNSMode(t *testing.T) { + t.Parallel() + + fakeSet := &fakeRuleSet{} + routerService := &fakeRouter{ + ruleSets: map[string]adapter.RuleSet{ + "dynamic-set": fakeSet, + }, + } + ctx := service.ContextWith[adapter.Router](context.Background(), routerService) + router := newTestRouterWithContext(t, ctx, []option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + RuleSet: badoption.Listable[string]{"dynamic-set"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, + }, + }, + }}, &fakeDNSTransportManager{ + defaultTransport: &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + transports: map[string]adapter.DNSTransport{ + "default": &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + "selected": &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP}, + }, + }, &fakeDNSClient{ + lookup: func(adapter.DNSTransport, string, adapter.DNSQueryOptions) ([]netip.Addr, *mDNS.Msg, error) { + return []netip.Addr{netip.MustParseAddr("1.1.1.1")}, nil, nil + }, + }) + require.False(t, router.legacyDNSMode) + + err := router.ValidateRuleSetMetadataUpdate("dynamic-set", adapter.RuleSetMetadata{ + ContainsIPCIDRRule: true, + }) + require.ErrorContains(t, err, "Address Filter Fields") +} + +func TestValidateRuleSetMetadataUpdateAllowsRuleSetThatKeepsNonLegacyDNSMode(t *testing.T) { + t.Parallel() + + fakeSet := &fakeRuleSet{} + routerService := &fakeRouter{ + ruleSets: map[string]adapter.RuleSet{ + "dynamic-set": fakeSet, + }, + } + ctx := service.ContextWith[adapter.Router](context.Background(), routerService) + router := newTestRouterWithContext(t, ctx, []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeEvaluate, + RouteOptions: option.DNSRouteActionOptions{Server: "upstream"}, + }, + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + RuleSet: badoption.Listable[string]{"dynamic-set"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, + }, + }, + }, + }, &fakeDNSTransportManager{ + defaultTransport: &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + transports: map[string]adapter.DNSTransport{ + "default": &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + "selected": &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP}, + "upstream": &fakeDNSTransport{tag: "upstream", transportType: C.DNSTypeUDP}, + }, + }, &fakeDNSClient{}) + require.False(t, router.legacyDNSMode) + + err := router.ValidateRuleSetMetadataUpdate("dynamic-set", adapter.RuleSetMetadata{ + ContainsIPCIDRRule: true, + ContainsNonIPCIDRRule: true, + }) + require.NoError(t, err) +} + +func TestValidateRuleSetMetadataUpdateAllowsRelaxingLegacyRequirement(t *testing.T) { + t.Parallel() + + fakeSet := &fakeRuleSet{ + metadata: adapter.RuleSetMetadata{ + ContainsIPCIDRRule: true, + }, + } + routerService := &fakeRouter{ + ruleSets: map[string]adapter.RuleSet{ + "dynamic-set": fakeSet, + }, + } + ctx := service.ContextWith[adapter.Router](context.Background(), routerService) + router := newTestRouterWithContext(t, ctx, []option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + RuleSet: badoption.Listable[string]{"dynamic-set"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, + }, + }, + }}, &fakeDNSTransportManager{ + defaultTransport: &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + transports: map[string]adapter.DNSTransport{ + "default": &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + "selected": &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP}, + }, + }, &fakeDNSClient{ + lookup: func(adapter.DNSTransport, string, adapter.DNSQueryOptions) ([]netip.Addr, *mDNS.Msg, error) { + return []netip.Addr{netip.MustParseAddr("10.0.0.1")}, nil, nil + }, + }) + require.True(t, router.legacyDNSMode) + + err := router.ValidateRuleSetMetadataUpdate("dynamic-set", adapter.RuleSetMetadata{}) + require.NoError(t, err) +} + +func TestInitializeRejectsPureIPRuleSetWhenLegacyDNSModeDisabled(t *testing.T) { + t.Parallel() + + fakeSet := &fakeRuleSet{ + metadata: adapter.RuleSetMetadata{ + ContainsIPCIDRRule: true, + }, + } + routerService := &fakeRouter{ + ruleSets: map[string]adapter.RuleSet{ + "pure-ip": fakeSet, + }, + } + ctx := service.ContextWith[adapter.Router](context.Background(), routerService) + router := &Router{ + ctx: ctx, + logger: log.NewNOPFactory().NewLogger("dns"), + transport: &fakeDNSTransportManager{}, + client: &fakeDNSClient{}, + rawRules: make([]option.DNSRule, 0, 2), + rules: make([]adapter.DNSRule, 0, 2), + defaultDomainStrategy: C.DomainStrategyAsIS, + } + err := router.Initialize([]option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + QueryType: badoption.Listable[option.DNSQueryType]{option.DNSQueryType(mDNS.TypeA)}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, + }, + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + RuleSet: badoption.Listable[string]{"pure-ip"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, + }, + }, + }, + }) + require.ErrorContains(t, err, "Address Filter Fields") +} + +func TestInitializeAllowsMixedRuleSetWhenLegacyDNSModeDisabled(t *testing.T) { + t.Parallel() + + fakeSet := &fakeRuleSet{ + metadata: adapter.RuleSetMetadata{ + ContainsIPCIDRRule: true, + ContainsNonIPCIDRRule: true, + }, + } + routerService := &fakeRouter{ + ruleSets: map[string]adapter.RuleSet{ + "mixed": fakeSet, + }, + } + ctx := service.ContextWith[adapter.Router](context.Background(), routerService) + router := newTestRouterWithContext(t, ctx, []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + QueryType: badoption.Listable[option.DNSQueryType]{option.DNSQueryType(mDNS.TypeA)}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, + }, + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + RuleSet: badoption.Listable[string]{"mixed"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, + }, + }, + }, + }, &fakeDNSTransportManager{ + defaultTransport: &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + transports: map[string]adapter.DNSTransport{ + "default": &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + "selected": &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP}, + }, + }, &fakeDNSClient{}) + require.False(t, router.legacyDNSMode) +} + +func TestValidateRuleSetMetadataUpdateRejectsRuleSetFlippingToPureIP(t *testing.T) { + t.Parallel() + + fakeSet := &fakeRuleSet{ + metadata: adapter.RuleSetMetadata{ + ContainsIPCIDRRule: true, + ContainsNonIPCIDRRule: true, + }, + } + routerService := &fakeRouter{ + ruleSets: map[string]adapter.RuleSet{ + "mixed": fakeSet, + }, + } + ctx := service.ContextWith[adapter.Router](context.Background(), routerService) + router := newTestRouterWithContext(t, ctx, []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + QueryType: badoption.Listable[option.DNSQueryType]{option.DNSQueryType(mDNS.TypeA)}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, + }, + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + RuleSet: badoption.Listable[string]{"mixed"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, + }, + }, + }, + }, &fakeDNSTransportManager{ + defaultTransport: &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + transports: map[string]adapter.DNSTransport{ + "default": &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + "selected": &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP}, + }, + }, &fakeDNSClient{}) + require.False(t, router.legacyDNSMode) + + err := router.ValidateRuleSetMetadataUpdate("mixed", adapter.RuleSetMetadata{ + ContainsIPCIDRRule: true, + }) + require.ErrorContains(t, err, "Address Filter Fields") +} + +func TestCloseWaitsForInFlightLookupUntilContextCancellation(t *testing.T) { + t.Parallel() + + defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP} + selectedTransport := &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP} + lookupStarted := make(chan struct{}) + var lookupStartedOnce sync.Once + router := newTestRouter(t, []option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, + }, + }, + }}, &fakeDNSTransportManager{ + defaultTransport: defaultTransport, + transports: map[string]adapter.DNSTransport{ + "default": defaultTransport, + "selected": selectedTransport, + }, + }, &fakeDNSClient{ + lookupWithCtx: func(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, *mDNS.Msg, error) { + require.Equal(t, "selected", transport.Tag()) + require.Equal(t, "example.com", domain) + lookupStartedOnce.Do(func() { + close(lookupStarted) + }) + <-ctx.Done() + return nil, nil, ctx.Err() + }, + }) + + lookupCtx, cancelLookup := context.WithCancel(context.Background()) + defer cancelLookup() + var ( + lookupErr error + closeErr error + ) + lookupDone := make(chan struct{}) + go func() { + _, lookupErr = router.Lookup(lookupCtx, "example.com", adapter.DNSQueryOptions{}) + close(lookupDone) + }() + + select { + case <-lookupStarted: + case <-time.After(time.Second): + t.Fatal("lookup did not reach DNS client") + } + + closeDone := make(chan struct{}) + go func() { + closeErr = router.Close() + close(closeDone) + }() + + select { + case <-closeDone: + t.Fatal("close finished before lookup context cancellation") + default: + } + + cancelLookup() + + select { + case <-lookupDone: + case <-time.After(time.Second): + t.Fatal("lookup did not finish after cancellation") + } + select { + case <-closeDone: + case <-time.After(time.Second): + t.Fatal("close did not finish after lookup cancellation") + } + + require.ErrorIs(t, lookupErr, context.Canceled) + require.NoError(t, closeErr) +} + +func TestLookupLegacyDNSModeDefersDirectDestinationIPMatch(t *testing.T) { + t.Parallel() + + defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP} + privateTransport := &fakeDNSTransport{tag: "private", transportType: C.DNSTypeUDP} + client := &fakeDNSClient{ + lookup: func(transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, *mDNS.Msg, error) { + require.Equal(t, "example.com", domain) + require.Equal(t, C.DomainStrategyIPv4Only, options.LookupStrategy) + switch transport.Tag() { + case "private": + response := FixedResponse(0, fixedQuestion(domain, mDNS.TypeA), []netip.Addr{netip.MustParseAddr("10.0.0.1")}, 60) + return MessageToAddresses(response), response, nil + case "default": + t.Fatal("default transport should not be used when legacy rule matches after response") + } + return nil, nil, E.New("unexpected transport") + }, + } + router := newTestRouter(t, []option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + IPIsPrivate: true, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "private"}, + }, + }, + }}, &fakeDNSTransportManager{ + defaultTransport: defaultTransport, + transports: map[string]adapter.DNSTransport{ + "default": defaultTransport, + "private": privateTransport, + }, + }, client) + + require.True(t, router.legacyDNSMode) + + addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{ + LookupStrategy: C.DomainStrategyIPv4Only, + }) + require.NoError(t, err) + require.Equal(t, []netip.Addr{netip.MustParseAddr("10.0.0.1")}, addresses) +} + +func TestLookupLegacyDNSModeFallsBackAfterRejectedAddressLimitResponse(t *testing.T) { + t.Parallel() + + defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP} + privateTransport := &fakeDNSTransport{tag: "private", transportType: C.DNSTypeUDP} + var lookupAccess sync.Mutex + var lookupTags []string + recordLookup := func(tag string) { + lookupAccess.Lock() + lookupTags = append(lookupTags, tag) + lookupAccess.Unlock() + } + currentLookupTags := func() []string { + lookupAccess.Lock() + defer lookupAccess.Unlock() + return append([]string(nil), lookupTags...) + } + client := &fakeDNSClient{ + lookup: func(transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, *mDNS.Msg, error) { + require.Equal(t, "example.com", domain) + require.Equal(t, C.DomainStrategyIPv4Only, options.LookupStrategy) + recordLookup(transport.Tag()) + switch transport.Tag() { + case "private": + response := FixedResponse(0, fixedQuestion(domain, mDNS.TypeA), []netip.Addr{netip.MustParseAddr("8.8.8.8")}, 60) + return MessageToAddresses(response), response, nil + case "default": + response := FixedResponse(0, fixedQuestion(domain, mDNS.TypeA), []netip.Addr{netip.MustParseAddr("9.9.9.9")}, 60) + return MessageToAddresses(response), response, nil + } + return nil, nil, E.New("unexpected transport") + }, + } + router := newTestRouter(t, []option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + IPIsPrivate: true, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "private"}, + }, + }, + }}, &fakeDNSTransportManager{ + defaultTransport: defaultTransport, + transports: map[string]adapter.DNSTransport{ + "default": defaultTransport, + "private": privateTransport, + }, + }, client) + + addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{ + LookupStrategy: C.DomainStrategyIPv4Only, + }) + require.NoError(t, err) + require.Equal(t, []netip.Addr{netip.MustParseAddr("9.9.9.9")}, addresses) + require.Equal(t, []string{"private", "default"}, currentLookupTags()) +} + +func TestLookupLegacyDNSModeRuleSetAcceptEmptyDoesNotTreatMismatchAsEmpty(t *testing.T) { + t.Parallel() + + ctx := context.Background() + ruleSet, err := rulepkg.NewRuleSet(ctx, log.NewNOPFactory().NewLogger("router"), option.RuleSet{ + Type: C.RuleSetTypeInline, + Tag: "legacy-ipcidr-set", + InlineOptions: option.PlainRuleSet{ + Rules: []option.HeadlessRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultHeadlessRule{ + IPCIDR: badoption.Listable[string]{"10.0.0.0/8"}, + }, + }}, + }, + }) + require.NoError(t, err) + ctx = service.ContextWith[adapter.Router](ctx, &fakeRouter{ + ruleSets: map[string]adapter.RuleSet{ + "legacy-ipcidr-set": ruleSet, + }, + }) + + defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP} + privateTransport := &fakeDNSTransport{tag: "private", transportType: C.DNSTypeUDP} + var lookupAccess sync.Mutex + var lookupTags []string + recordLookup := func(tag string) { + lookupAccess.Lock() + lookupTags = append(lookupTags, tag) + lookupAccess.Unlock() + } + currentLookupTags := func() []string { + lookupAccess.Lock() + defer lookupAccess.Unlock() + return append([]string(nil), lookupTags...) + } + router := newTestRouterWithContext(t, ctx, []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + RuleSet: badoption.Listable[string]{"legacy-ipcidr-set"}, + RuleSetIPCIDRAcceptEmpty: true, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "private"}, + }, + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "default"}, + }, + }, + }, + }, &fakeDNSTransportManager{ + defaultTransport: defaultTransport, + transports: map[string]adapter.DNSTransport{ + "default": defaultTransport, + "private": privateTransport, + }, + }, &fakeDNSClient{ + lookup: func(transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, *mDNS.Msg, error) { + require.Equal(t, "example.com", domain) + require.Equal(t, C.DomainStrategyIPv4Only, options.LookupStrategy) + recordLookup(transport.Tag()) + switch transport.Tag() { + case "private": + response := FixedResponse(0, fixedQuestion(domain, mDNS.TypeA), []netip.Addr{netip.MustParseAddr("8.8.8.8")}, 60) + return MessageToAddresses(response), response, nil + case "default": + response := FixedResponse(0, fixedQuestion(domain, mDNS.TypeA), []netip.Addr{netip.MustParseAddr("9.9.9.9")}, 60) + return MessageToAddresses(response), response, nil + } + return nil, nil, E.New("unexpected transport") + }, + }) + + require.True(t, router.legacyDNSMode) + + addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{ + LookupStrategy: C.DomainStrategyIPv4Only, + }) + require.NoError(t, err) + require.Equal(t, []netip.Addr{netip.MustParseAddr("9.9.9.9")}, addresses) + require.Equal(t, []string{"private", "default"}, currentLookupTags()) +} + +func TestExchangeLegacyDNSModeDisabledEvaluateMatchResponseRoute(t *testing.T) { + t.Parallel() + + transportManager := &fakeDNSTransportManager{ + defaultTransport: &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + transports: map[string]adapter.DNSTransport{ + "upstream": &fakeDNSTransport{tag: "upstream", transportType: C.DNSTypeUDP}, + "selected": &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP}, + "default": &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + }, + } + client := &fakeDNSClient{ + exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) { + switch transport.Tag() { + case "upstream": + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("1.1.1.1")}, 60), nil + case "selected": + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("8.8.8.8")}, 60), nil + default: + return nil, E.New("unexpected transport") + } + }, + } + rules := []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeEvaluate, + RouteOptions: option.DNSRouteActionOptions{Server: "upstream"}, + }, + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + MatchResponse: true, + ResponseAnswer: badoption.Listable[option.DNSRecordOptions]{mustRecord(t, "example.com. IN A 1.1.1.1")}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, + }, + }, + }, + } + router := newTestRouter(t, rules, transportManager, client) + + response, err := router.Exchange(context.Background(), &mDNS.Msg{ + Question: []mDNS.Question{fixedQuestion("example.com", mDNS.TypeA)}, + }, adapter.DNSQueryOptions{}) + require.NoError(t, err) + require.Equal(t, []netip.Addr{netip.MustParseAddr("8.8.8.8")}, MessageToAddresses(response)) +} + +func TestExchangeLegacyDNSModeDisabledEvaluateMatchResponseRcodeRoute(t *testing.T) { + t.Parallel() + + transportManager := &fakeDNSTransportManager{ + defaultTransport: &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + transports: map[string]adapter.DNSTransport{ + "upstream": &fakeDNSTransport{tag: "upstream", transportType: C.DNSTypeUDP}, + "selected": &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP}, + "default": &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + }, + } + client := &fakeDNSClient{ + exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) { + switch transport.Tag() { + case "upstream": + return &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Response: true, + Rcode: mDNS.RcodeNameError, + }, + Question: []mDNS.Question{message.Question[0]}, + }, nil + case "selected": + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("8.8.8.8")}, 60), nil + default: + return nil, E.New("unexpected transport") + } + }, + } + rcode := option.DNSRCode(mDNS.RcodeNameError) + rules := []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeEvaluate, + RouteOptions: option.DNSRouteActionOptions{Server: "upstream"}, + }, + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + MatchResponse: true, + ResponseRcode: &rcode, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, + }, + }, + }, + } + router := newTestRouter(t, rules, transportManager, client) + + response, err := router.Exchange(context.Background(), &mDNS.Msg{ + Question: []mDNS.Question{fixedQuestion("example.com", mDNS.TypeA)}, + }, adapter.DNSQueryOptions{}) + require.NoError(t, err) + require.Equal(t, []netip.Addr{netip.MustParseAddr("8.8.8.8")}, MessageToAddresses(response)) +} + +func TestExchangeLegacyDNSModeDisabledEvaluateMatchResponseNsRoute(t *testing.T) { + t.Parallel() + + transportManager := &fakeDNSTransportManager{ + defaultTransport: &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + transports: map[string]adapter.DNSTransport{ + "upstream": &fakeDNSTransport{tag: "upstream", transportType: C.DNSTypeUDP}, + "selected": &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP}, + "default": &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + }, + } + nsRecord := mustRecord(t, "example.com. IN NS ns1.example.com.") + client := &fakeDNSClient{ + exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) { + switch transport.Tag() { + case "upstream": + return &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Response: true, + Rcode: mDNS.RcodeSuccess, + }, + Question: []mDNS.Question{message.Question[0]}, + Ns: []mDNS.RR{nsRecord.Build()}, + }, nil + case "selected": + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("8.8.8.8")}, 60), nil + default: + return nil, E.New("unexpected transport") + } + }, + } + rules := []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeEvaluate, + RouteOptions: option.DNSRouteActionOptions{Server: "upstream"}, + }, + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + MatchResponse: true, + ResponseNs: badoption.Listable[option.DNSRecordOptions]{nsRecord}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, + }, + }, + }, + } + router := newTestRouter(t, rules, transportManager, client) + + response, err := router.Exchange(context.Background(), &mDNS.Msg{ + Question: []mDNS.Question{fixedQuestion("example.com", mDNS.TypeA)}, + }, adapter.DNSQueryOptions{}) + require.NoError(t, err) + require.Equal(t, []netip.Addr{netip.MustParseAddr("8.8.8.8")}, MessageToAddresses(response)) +} + +func TestExchangeLegacyDNSModeDisabledEvaluateMatchResponseExtraRoute(t *testing.T) { + t.Parallel() + + transportManager := &fakeDNSTransportManager{ + defaultTransport: &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + transports: map[string]adapter.DNSTransport{ + "upstream": &fakeDNSTransport{tag: "upstream", transportType: C.DNSTypeUDP}, + "selected": &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP}, + "default": &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + }, + } + extraRecord := mustRecord(t, "ns1.example.com. IN A 192.0.2.53") + client := &fakeDNSClient{ + exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) { + switch transport.Tag() { + case "upstream": + return &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Response: true, + Rcode: mDNS.RcodeSuccess, + }, + Question: []mDNS.Question{message.Question[0]}, + Extra: []mDNS.RR{extraRecord.Build()}, + }, nil + case "selected": + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("8.8.8.8")}, 60), nil + default: + return nil, E.New("unexpected transport") + } + }, + } + rules := []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeEvaluate, + RouteOptions: option.DNSRouteActionOptions{Server: "upstream"}, + }, + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + MatchResponse: true, + ResponseExtra: badoption.Listable[option.DNSRecordOptions]{extraRecord}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, + }, + }, + }, + } + router := newTestRouter(t, rules, transportManager, client) + + response, err := router.Exchange(context.Background(), &mDNS.Msg{ + Question: []mDNS.Question{fixedQuestion("example.com", mDNS.TypeA)}, + }, adapter.DNSQueryOptions{}) + require.NoError(t, err) + require.Equal(t, []netip.Addr{netip.MustParseAddr("8.8.8.8")}, MessageToAddresses(response)) +} + +func TestExchangeLegacyDNSModeDisabledEvaluateDoesNotLeakAddressesToNextQuery(t *testing.T) { + t.Parallel() + + transportManager := &fakeDNSTransportManager{ + defaultTransport: &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + transports: map[string]adapter.DNSTransport{ + "upstream": &fakeDNSTransport{tag: "upstream", transportType: C.DNSTypeUDP}, + "selected": &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP}, + "default": &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + }, + } + var inspectedSelected bool + client := &fakeDNSClient{ + beforeExchange: func(ctx context.Context, transport adapter.DNSTransport, message *mDNS.Msg) { + if transport.Tag() != "selected" { + return + } + inspectedSelected = true + metadata := adapter.ContextFrom(ctx) + require.NotNil(t, metadata) + require.Empty(t, metadata.DestinationAddresses) + require.NotNil(t, metadata.DNSResponse) + }, + exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) { + switch transport.Tag() { + case "upstream": + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("1.1.1.1")}, 60), nil + case "selected": + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("8.8.8.8")}, 60), nil + default: + return nil, E.New("unexpected transport") + } + }, + } + rules := []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeEvaluate, + RouteOptions: option.DNSRouteActionOptions{Server: "upstream"}, + }, + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + MatchResponse: true, + ResponseAnswer: badoption.Listable[option.DNSRecordOptions]{mustRecord(t, "example.com. IN A 1.1.1.1")}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, + }, + }, + }, + } + router := newTestRouter(t, rules, transportManager, client) + + response, err := router.Exchange(context.Background(), &mDNS.Msg{ + Question: []mDNS.Question{fixedQuestion("example.com", mDNS.TypeA)}, + }, adapter.DNSQueryOptions{}) + require.NoError(t, err) + require.True(t, inspectedSelected) + require.Equal(t, []netip.Addr{netip.MustParseAddr("8.8.8.8")}, MessageToAddresses(response)) +} + +func TestExchangeLegacyDNSModeDisabledEvaluateRouteResolutionFailureClearsResponse(t *testing.T) { + t.Parallel() + + transportManager := &fakeDNSTransportManager{ + defaultTransport: &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + transports: map[string]adapter.DNSTransport{ + "upstream": &fakeDNSTransport{tag: "upstream", transportType: C.DNSTypeUDP}, + "selected": &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP}, + "default": &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + }, + } + client := &fakeDNSClient{ + exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) { + switch transport.Tag() { + case "upstream": + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("1.1.1.1")}, 60), nil + case "selected": + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("8.8.8.8")}, 60), nil + case "default": + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("4.4.4.4")}, 60), nil + default: + return nil, E.New("unexpected transport") + } + }, + } + rules := []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeEvaluate, + RouteOptions: option.DNSRouteActionOptions{Server: "upstream"}, + }, + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeEvaluate, + RouteOptions: option.DNSRouteActionOptions{Server: "missing"}, + }, + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + MatchResponse: true, + ResponseAnswer: badoption.Listable[option.DNSRecordOptions]{mustRecord(t, "example.com. IN A 1.1.1.1")}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, + }, + }, + }, + } + router := newTestRouter(t, rules, transportManager, client) + + response, err := router.Exchange(context.Background(), &mDNS.Msg{ + Question: []mDNS.Question{fixedQuestion("example.com", mDNS.TypeA)}, + }, adapter.DNSQueryOptions{}) + require.NoError(t, err) + require.Equal(t, []netip.Addr{netip.MustParseAddr("4.4.4.4")}, MessageToAddresses(response)) +} + +func TestExchangeLegacyDNSModeDisabledSecondEvaluateOverwritesFirstResponse(t *testing.T) { + t.Parallel() + + transportManager := &fakeDNSTransportManager{ + defaultTransport: &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + transports: map[string]adapter.DNSTransport{ + "default": &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + "first-upstream": &fakeDNSTransport{tag: "first-upstream", transportType: C.DNSTypeUDP}, + "second-upstream": &fakeDNSTransport{tag: "second-upstream", transportType: C.DNSTypeUDP}, + "first-match": &fakeDNSTransport{tag: "first-match", transportType: C.DNSTypeUDP}, + "second-match": &fakeDNSTransport{tag: "second-match", transportType: C.DNSTypeUDP}, + }, + } + client := &fakeDNSClient{ + exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) { + switch transport.Tag() { + case "first-upstream": + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("1.1.1.1")}, 60), nil + case "second-upstream": + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2.2.2.2")}, 60), nil + case "first-match": + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("7.7.7.7")}, 60), nil + case "second-match": + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("8.8.8.8")}, 60), nil + case "default": + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("4.4.4.4")}, 60), nil + default: + return nil, E.New("unexpected transport") + } + }, + } + rules := []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeEvaluate, + RouteOptions: option.DNSRouteActionOptions{Server: "first-upstream"}, + }, + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeEvaluate, + RouteOptions: option.DNSRouteActionOptions{Server: "second-upstream"}, + }, + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + MatchResponse: true, + ResponseAnswer: badoption.Listable[option.DNSRecordOptions]{mustRecord(t, "example.com. IN A 1.1.1.1")}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "first-match"}, + }, + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + MatchResponse: true, + ResponseAnswer: badoption.Listable[option.DNSRecordOptions]{mustRecord(t, "example.com. IN A 2.2.2.2")}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "second-match"}, + }, + }, + }, + } + router := newTestRouter(t, rules, transportManager, client) + + response, err := router.Exchange(context.Background(), &mDNS.Msg{ + Question: []mDNS.Question{fixedQuestion("example.com", mDNS.TypeA)}, + }, adapter.DNSQueryOptions{}) + require.NoError(t, err) + require.Equal(t, []netip.Addr{netip.MustParseAddr("8.8.8.8")}, MessageToAddresses(response)) +} + +func TestExchangeLegacyDNSModeDisabledEvaluateExchangeFailureUsesMatchResponseBooleanSemantics(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + invert bool + expectedAddr netip.Addr + }{ + { + name: "plain match_response rule stays false", + expectedAddr: netip.MustParseAddr("4.4.4.4"), + }, + { + name: "invert match_response rule becomes true", + invert: true, + expectedAddr: netip.MustParseAddr("8.8.8.8"), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + transportManager := &fakeDNSTransportManager{ + defaultTransport: &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + transports: map[string]adapter.DNSTransport{ + "upstream": &fakeDNSTransport{tag: "upstream", transportType: C.DNSTypeUDP}, + "selected": &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP}, + "default": &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + }, + } + client := &fakeDNSClient{ + exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) { + switch transport.Tag() { + case "upstream": + return nil, E.New("upstream exchange failed") + case "selected": + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("8.8.8.8")}, 60), nil + case "default": + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("4.4.4.4")}, 60), nil + default: + return nil, E.New("unexpected transport") + } + }, + } + rules := []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeEvaluate, + RouteOptions: option.DNSRouteActionOptions{Server: "upstream"}, + }, + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + MatchResponse: true, + Invert: testCase.invert, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, + }, + }, + }, + } + router := newTestRouter(t, rules, transportManager, client) + + response, err := router.Exchange(context.Background(), &mDNS.Msg{ + Question: []mDNS.Question{fixedQuestion("example.com", mDNS.TypeA)}, + }, adapter.DNSQueryOptions{}) + require.NoError(t, err) + require.Equal(t, []netip.Addr{testCase.expectedAddr}, MessageToAddresses(response)) + }) + } +} + +func TestExchangeLegacyDNSModeDisabledRespondReturnsEvaluatedResponse(t *testing.T) { + t.Parallel() + + var exchanges []string + defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP} + router := newTestRouter(t, []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeEvaluate, + RouteOptions: option.DNSRouteActionOptions{Server: "upstream"}, + }, + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRespond, + }, + }, + }, + }, &fakeDNSTransportManager{ + defaultTransport: defaultTransport, + transports: map[string]adapter.DNSTransport{ + "default": defaultTransport, + "upstream": &fakeDNSTransport{tag: "upstream", transportType: C.DNSTypeUDP}, + }, + }, &fakeDNSClient{ + exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) { + exchanges = append(exchanges, transport.Tag()) + require.Equal(t, "upstream", transport.Tag()) + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("1.1.1.1")}, 60), nil + }, + }) + require.False(t, router.legacyDNSMode) + + response, err := router.Exchange(context.Background(), &mDNS.Msg{ + Question: []mDNS.Question{fixedQuestion("example.com", mDNS.TypeA)}, + }, adapter.DNSQueryOptions{}) + require.NoError(t, err) + require.Equal(t, []string{"upstream"}, exchanges) + require.Equal(t, []netip.Addr{netip.MustParseAddr("1.1.1.1")}, MessageToAddresses(response)) +} + +func TestLookupLegacyDNSModeDisabledRespondReturnsEvaluatedResponse(t *testing.T) { + t.Parallel() + + defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP} + router := newTestRouter(t, []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeEvaluate, + RouteOptions: option.DNSRouteActionOptions{Server: "upstream"}, + }, + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRespond, + }, + }, + }, + }, &fakeDNSTransportManager{ + defaultTransport: defaultTransport, + transports: map[string]adapter.DNSTransport{ + "default": defaultTransport, + "upstream": &fakeDNSTransport{tag: "upstream", transportType: C.DNSTypeUDP}, + }, + }, &fakeDNSClient{ + exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) { + require.Equal(t, "upstream", transport.Tag()) + switch message.Question[0].Qtype { + case mDNS.TypeA: + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("1.1.1.1")}, 60), nil + case mDNS.TypeAAAA: + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2001:db8::1")}, 60), nil + default: + return nil, E.New("unexpected qtype") + } + }, + }) + require.False(t, router.legacyDNSMode) + + addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{}) + require.NoError(t, err) + require.Equal(t, []netip.Addr{ + netip.MustParseAddr("1.1.1.1"), + netip.MustParseAddr("2001:db8::1"), + }, addresses) +} + +func TestExchangeLegacyDNSModeDisabledRespondWithoutEvaluatedResponseReturnsError(t *testing.T) { + t.Parallel() + + defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP} + router := newTestRouter(t, []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeEvaluate, + RouteOptions: option.DNSRouteActionOptions{Server: "upstream"}, + }, + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRespond, + }, + }, + }, + }, &fakeDNSTransportManager{ + defaultTransport: defaultTransport, + transports: map[string]adapter.DNSTransport{ + "default": defaultTransport, + "upstream": &fakeDNSTransport{tag: "upstream", transportType: C.DNSTypeUDP}, + }, + }, &fakeDNSClient{ + exchange: func(transport adapter.DNSTransport, _ *mDNS.Msg) (*mDNS.Msg, error) { + require.Equal(t, "upstream", transport.Tag()) + return nil, E.New("upstream exchange failed") + }, + }) + require.False(t, router.legacyDNSMode) + + response, err := router.Exchange(context.Background(), &mDNS.Msg{ + Question: []mDNS.Question{fixedQuestion("example.com", mDNS.TypeA)}, + }, adapter.DNSQueryOptions{}) + require.Nil(t, response) + require.ErrorContains(t, err, dnsRespondMissingResponseMessage) +} + +func TestLookupLegacyDNSModeDisabledAllowsPartialSuccessForExchangeFailure(t *testing.T) { + t.Parallel() + + defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP} + router := newTestRouter(t, nil, &fakeDNSTransportManager{ + defaultTransport: defaultTransport, + transports: map[string]adapter.DNSTransport{ + "default": defaultTransport, + }, + }, &fakeDNSClient{ + exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) { + require.Equal(t, "default", transport.Tag()) + switch message.Question[0].Qtype { + case mDNS.TypeA: + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("1.1.1.1")}, 60), nil + case mDNS.TypeAAAA: + return nil, E.New("ipv6 failed") + default: + return nil, E.New("unexpected qtype") + } + }, + }) + router.legacyDNSMode = false + + addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{}) + require.NoError(t, err) + require.Equal(t, []netip.Addr{netip.MustParseAddr("1.1.1.1")}, addresses) +} + +func TestLookupLegacyDNSModeDisabledAllowsPartialSuccessForRcodeError(t *testing.T) { + t.Parallel() + + defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP} + router := newTestRouter(t, nil, &fakeDNSTransportManager{ + defaultTransport: defaultTransport, + transports: map[string]adapter.DNSTransport{ + "default": defaultTransport, + }, + }, &fakeDNSClient{ + exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) { + require.Equal(t, "default", transport.Tag()) + switch message.Question[0].Qtype { + case mDNS.TypeA: + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("1.1.1.1")}, 60), nil + case mDNS.TypeAAAA: + return &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Response: true, + Rcode: mDNS.RcodeNameError, + }, + Question: []mDNS.Question{message.Question[0]}, + }, nil + default: + return nil, E.New("unexpected qtype") + } + }, + }) + router.legacyDNSMode = false + + addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{}) + require.NoError(t, err) + require.Equal(t, []netip.Addr{netip.MustParseAddr("1.1.1.1")}, addresses) +} + +func TestLookupLegacyDNSModeDisabledSkipsFakeIPRule(t *testing.T) { + t.Parallel() + + defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP} + router := newTestRouter(t, []option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "fake"}, + }, + }, + }}, &fakeDNSTransportManager{ + defaultTransport: defaultTransport, + transports: map[string]adapter.DNSTransport{ + "default": defaultTransport, + "fake": &fakeDNSTransport{tag: "fake", transportType: C.DNSTypeFakeIP}, + }, + }, &fakeDNSClient{ + exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) { + require.Equal(t, "default", transport.Tag()) + if message.Question[0].Qtype == mDNS.TypeA { + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2.2.2.2")}, 60), nil + } + return FixedResponse(0, message.Question[0], nil, 60), nil + }, + }) + router.legacyDNSMode = false + + addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{}) + require.NoError(t, err) + require.Equal(t, []netip.Addr{netip.MustParseAddr("2.2.2.2")}, addresses) +} + +func TestExchangeLegacyDNSModeDisabledAllowsRouteFakeIPRule(t *testing.T) { + t.Parallel() + + fakeTransport := &fakeDNSTransport{tag: "fake", transportType: C.DNSTypeFakeIP} + router := newTestRouter(t, []option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "fake"}, + }, + }, + }}, &fakeDNSTransportManager{ + defaultTransport: &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + transports: map[string]adapter.DNSTransport{ + "default": &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + "fake": fakeTransport, + }, + }, &fakeDNSClient{ + exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) { + require.Same(t, fakeTransport, transport) + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("198.18.0.1")}, 60), nil + }, + }) + + response, err := router.Exchange(context.Background(), &mDNS.Msg{ + Question: []mDNS.Question{fixedQuestion("example.com", mDNS.TypeA)}, + }, adapter.DNSQueryOptions{}) + require.NoError(t, err) + require.Equal(t, []netip.Addr{netip.MustParseAddr("198.18.0.1")}, MessageToAddresses(response)) +} + +func TestInitializeRejectsDNSRuleStrategyWhenLegacyDNSModeIsDisabledByEvaluate(t *testing.T) { + t.Parallel() + + router := &Router{ + ctx: context.Background(), + logger: log.NewNOPFactory().NewLogger("dns"), + transport: &fakeDNSTransportManager{}, + client: &fakeDNSClient{}, + rawRules: make([]option.DNSRule, 0, 1), + defaultDomainStrategy: C.DomainStrategyAsIS, + } + err := router.Initialize([]option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeEvaluate, + RouteOptions: option.DNSRouteActionOptions{ + Server: "default", + Strategy: option.DomainStrategy(C.DomainStrategyIPv4Only), + }, + }, + }, + }}) + require.ErrorContains(t, err, "strategy") + require.ErrorContains(t, err, "deprecated") +} + +func TestInitializeRejectsEvaluateFakeIPServerInDefaultRule(t *testing.T) { + t.Parallel() + + router := &Router{ + ctx: context.Background(), + logger: log.NewNOPFactory().NewLogger("dns"), + transport: &fakeDNSTransportManager{transports: map[string]adapter.DNSTransport{"fake": &fakeDNSTransport{tag: "fake", transportType: C.DNSTypeFakeIP}}}, + client: &fakeDNSClient{}, + rawRules: make([]option.DNSRule, 0, 1), + defaultDomainStrategy: C.DomainStrategyAsIS, + } + err := router.Initialize([]option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeEvaluate, + RouteOptions: option.DNSRouteActionOptions{Server: "fake"}, + }, + }, + }}) + require.ErrorContains(t, err, "evaluate action cannot use fakeip server") + require.ErrorContains(t, err, "fake") +} + +func TestInitializeRejectsEvaluateFakeIPServerInLogicalRule(t *testing.T) { + t.Parallel() + + router := &Router{ + ctx: context.Background(), + logger: log.NewNOPFactory().NewLogger("dns"), + transport: &fakeDNSTransportManager{transports: map[string]adapter.DNSTransport{"fake": &fakeDNSTransport{tag: "fake", transportType: C.DNSTypeFakeIP}}}, + client: &fakeDNSClient{}, + rawRules: make([]option.DNSRule, 0, 1), + defaultDomainStrategy: C.DomainStrategyAsIS, + } + err := router.Initialize([]option.DNSRule{{ + Type: C.RuleTypeLogical, + LogicalOptions: option.LogicalDNSRule{ + RawLogicalDNSRule: option.RawLogicalDNSRule{ + Mode: C.LogicalTypeOr, + Rules: []option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + }, + }}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeEvaluate, + RouteOptions: option.DNSRouteActionOptions{Server: "fake"}, + }, + }, + }}) + require.ErrorContains(t, err, "evaluate action cannot use fakeip server") + require.ErrorContains(t, err, "fake") +} + +func TestInitializeRejectsDNSRuleStrategyWhenLegacyDNSModeIsDisabledByMatchResponse(t *testing.T) { + t.Parallel() + + router := &Router{ + ctx: context.Background(), + logger: log.NewNOPFactory().NewLogger("dns"), + transport: &fakeDNSTransportManager{}, + client: &fakeDNSClient{}, + rawRules: make([]option.DNSRule, 0, 1), + defaultDomainStrategy: C.DomainStrategyAsIS, + } + err := router.Initialize([]option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + MatchResponse: true, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRouteOptions, + RouteOptionsOptions: option.DNSRouteOptionsActionOptions{ + Strategy: option.DomainStrategy(C.DomainStrategyIPv4Only), + }, + }, + }, + }}) + require.ErrorContains(t, err, "strategy") + require.ErrorContains(t, err, "deprecated") +} + +func TestInitializeRejectsDNSMatchResponseWithoutPrecedingEvaluate(t *testing.T) { + t.Parallel() + + router := &Router{ + ctx: context.Background(), + logger: log.NewNOPFactory().NewLogger("dns"), + transport: &fakeDNSTransportManager{}, + client: &fakeDNSClient{}, + rawRules: make([]option.DNSRule, 0, 1), + defaultDomainStrategy: C.DomainStrategyAsIS, + } + err := router.Initialize([]option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + MatchResponse: true, + ResponseAnswer: badoption.Listable[option.DNSRecordOptions]{mustRecord(t, "example.com. IN A 1.1.1.1")}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "default"}, + }, + }, + }}) + require.ErrorContains(t, err, "preceding evaluate action") +} + +func TestInitializeRejectsDNSRespondWithoutPrecedingEvaluate(t *testing.T) { + t.Parallel() + + router := &Router{ + ctx: context.Background(), + logger: log.NewNOPFactory().NewLogger("dns"), + transport: &fakeDNSTransportManager{}, + client: &fakeDNSClient{}, + rawRules: make([]option.DNSRule, 0, 1), + defaultDomainStrategy: C.DomainStrategyAsIS, + } + err := router.Initialize([]option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRespond, + }, + }, + }}) + require.ErrorContains(t, err, "preceding evaluate action") +} + +func TestInitializeRejectsLogicalDNSRespondWithoutPrecedingEvaluate(t *testing.T) { + t.Parallel() + + router := &Router{ + ctx: context.Background(), + logger: log.NewNOPFactory().NewLogger("dns"), + transport: &fakeDNSTransportManager{}, + client: &fakeDNSClient{}, + rawRules: make([]option.DNSRule, 0, 1), + defaultDomainStrategy: C.DomainStrategyAsIS, + } + err := router.Initialize([]option.DNSRule{{ + Type: C.RuleTypeLogical, + LogicalOptions: option.LogicalDNSRule{ + RawLogicalDNSRule: option.RawLogicalDNSRule{ + Mode: C.LogicalTypeOr, + Rules: []option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + }, + }}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRespond, + }, + }, + }}) + require.ErrorContains(t, err, "preceding evaluate action") +} + +func TestInitializeRejectsEvaluateRuleWithResponseMatchWithoutPrecedingEvaluate(t *testing.T) { + t.Parallel() + + router := &Router{ + ctx: context.Background(), + logger: log.NewNOPFactory().NewLogger("dns"), + transport: &fakeDNSTransportManager{}, + client: &fakeDNSClient{}, + rawRules: make([]option.DNSRule, 0, 1), + defaultDomainStrategy: C.DomainStrategyAsIS, + } + err := router.Initialize([]option.DNSRule{{ + Type: C.RuleTypeLogical, + LogicalOptions: option.LogicalDNSRule{ + RawLogicalDNSRule: option.RawLogicalDNSRule{ + Mode: C.LogicalTypeOr, + Rules: []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + MatchResponse: true, + ResponseAnswer: badoption.Listable[option.DNSRecordOptions]{mustRecord(t, "example.com. IN A 1.1.1.1")}, + }, + }, + }, + }, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeEvaluate, + RouteOptions: option.DNSRouteActionOptions{Server: "default"}, + }, + }, + }}) + require.ErrorContains(t, err, "preceding evaluate action") +} + +func TestInitializeAllowsEvaluateRuleWithResponseMatchAfterPrecedingEvaluate(t *testing.T) { + t.Parallel() + + router := &Router{ + ctx: context.Background(), + logger: log.NewNOPFactory().NewLogger("dns"), + transport: &fakeDNSTransportManager{}, + client: &fakeDNSClient{}, + rawRules: make([]option.DNSRule, 0, 2), + defaultDomainStrategy: C.DomainStrategyAsIS, + } + err := router.Initialize([]option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"bootstrap.example"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeEvaluate, + RouteOptions: option.DNSRouteActionOptions{Server: "bootstrap"}, + }, + }, + }, + { + Type: C.RuleTypeLogical, + LogicalOptions: option.LogicalDNSRule{ + RawLogicalDNSRule: option.RawLogicalDNSRule{ + Mode: C.LogicalTypeOr, + Rules: []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + }, + }, + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + MatchResponse: true, + ResponseAnswer: badoption.Listable[option.DNSRecordOptions]{mustRecord(t, "example.com. IN A 1.1.1.1")}, + }, + }, + }, + }, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeEvaluate, + RouteOptions: option.DNSRouteActionOptions{Server: "default"}, + }, + }, + }, + }) + require.NoError(t, err) +} + +func TestLookupLegacyDNSModeDisabledReturnsRejectedErrorForRejectAction(t *testing.T) { + t.Parallel() + + defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP} + router := newTestRouter(t, []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeReject, + RejectOptions: option.RejectActionOptions{ + Method: C.RuleActionRejectMethodDefault, + }, + }, + }, + }, + }, &fakeDNSTransportManager{ + defaultTransport: defaultTransport, + transports: map[string]adapter.DNSTransport{ + "default": defaultTransport, + }, + }, &fakeDNSClient{}) + require.False(t, router.legacyDNSMode) + + addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{}) + require.Nil(t, addresses) + require.Error(t, err) + require.True(t, rulepkg.IsRejected(err)) +} + +func TestExchangeLegacyDNSModeDisabledReturnsRefusedResponseForRejectAction(t *testing.T) { + t.Parallel() + + defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP} + router := newTestRouter(t, []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeReject, + RejectOptions: option.RejectActionOptions{ + Method: C.RuleActionRejectMethodDefault, + }, + }, + }, + }, + }, &fakeDNSTransportManager{ + defaultTransport: defaultTransport, + transports: map[string]adapter.DNSTransport{ + "default": defaultTransport, + }, + }, &fakeDNSClient{}) + require.False(t, router.legacyDNSMode) + + response, err := router.Exchange(context.Background(), &mDNS.Msg{ + Question: []mDNS.Question{fixedQuestion("example.com", mDNS.TypeA)}, + }, adapter.DNSQueryOptions{}) + require.NoError(t, err) + require.Equal(t, mDNS.RcodeRefused, response.Rcode) + require.Equal(t, []mDNS.Question{fixedQuestion("example.com", mDNS.TypeA)}, response.Question) +} + +func TestExchangeLegacyDNSModeDisabledReturnsDropErrorForRejectDropAction(t *testing.T) { + t.Parallel() + + defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP} + router := newTestRouter(t, []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeReject, + RejectOptions: option.RejectActionOptions{ + Method: C.RuleActionRejectMethodDrop, + }, + }, + }, + }, + }, &fakeDNSTransportManager{ + defaultTransport: defaultTransport, + transports: map[string]adapter.DNSTransport{ + "default": defaultTransport, + }, + }, &fakeDNSClient{}) + require.False(t, router.legacyDNSMode) + + response, err := router.Exchange(context.Background(), &mDNS.Msg{ + Question: []mDNS.Question{fixedQuestion("example.com", mDNS.TypeA)}, + }, adapter.DNSQueryOptions{}) + require.Nil(t, response) + require.ErrorIs(t, err, tun.ErrDrop) +} + +func TestLookupLegacyDNSModeDisabledFiltersPerQueryTypeAddressesBeforeMerging(t *testing.T) { + t.Parallel() + + defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP} + router := newTestRouter(t, []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypePredefined, + PredefinedOptions: option.DNSRouteActionPredefined{ + Answer: badoption.Listable[option.DNSRecordOptions]{ + mustRecord(t, "example.com. IN A 1.1.1.1"), + mustRecord(t, "example.com. IN AAAA 2001:db8::1"), + }, + }, + }, + }, + }, + }, &fakeDNSTransportManager{ + defaultTransport: defaultTransport, + transports: map[string]adapter.DNSTransport{ + "default": defaultTransport, + }, + }, &fakeDNSClient{}) + require.False(t, router.legacyDNSMode) + + addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{}) + require.NoError(t, err) + require.Equal(t, []netip.Addr{ + netip.MustParseAddr("1.1.1.1"), + netip.MustParseAddr("2001:db8::1"), + }, addresses) +} + +func TestExchangeLegacyDNSModeDisabledLogicalMatchResponseIPCIDRFallsThrough(t *testing.T) { + t.Parallel() + + transportManager := &fakeDNSTransportManager{ + defaultTransport: &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + transports: map[string]adapter.DNSTransport{ + "upstream": &fakeDNSTransport{tag: "upstream", transportType: C.DNSTypeUDP}, + "selected": &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP}, + "default": &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + }, + } + client := &fakeDNSClient{ + exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) { + switch transport.Tag() { + case "upstream": + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("9.9.9.9")}, 60), nil + case "selected": + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("8.8.8.8")}, 60), nil + case "default": + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("4.4.4.4")}, 60), nil + default: + return nil, E.New("unexpected transport") + } + }, + } + rules := []option.DNSRule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeEvaluate, + RouteOptions: option.DNSRouteActionOptions{Server: "upstream"}, + }, + }, + }, + { + Type: C.RuleTypeLogical, + LogicalOptions: option.LogicalDNSRule{ + RawLogicalDNSRule: option.RawLogicalDNSRule{ + Mode: C.LogicalTypeOr, + Rules: []option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + MatchResponse: true, + IPCIDR: badoption.Listable[string]{"1.1.1.0/24"}, + }, + }, + }}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, + }, + }, + }, + } + router := newTestRouter(t, rules, transportManager, client) + + response, err := router.Exchange(context.Background(), &mDNS.Msg{ + Question: []mDNS.Question{fixedQuestion("example.com", mDNS.TypeA)}, + }, adapter.DNSQueryOptions{}) + require.NoError(t, err) + require.Equal(t, []netip.Addr{netip.MustParseAddr("4.4.4.4")}, MessageToAddresses(response)) +} + +func TestLegacyDNSModeReportsLegacyAddressFilterDeprecation(t *testing.T) { + t.Parallel() + + manager := &fakeDeprecatedManager{} + ctx := service.ContextWith[deprecated.Manager](context.Background(), manager) + router := &Router{ + ctx: ctx, + logger: log.NewNOPFactory().NewLogger("dns"), + client: &fakeDNSClient{}, + defaultDomainStrategy: C.DomainStrategyAsIS, + } + err := router.Initialize([]option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + IPCIDR: badoption.Listable[string]{"1.1.1.0/24"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "default"}, + }, + }, + }}) + require.NoError(t, err) + + err = router.Start(adapter.StartStateStart) + require.NoError(t, err) + require.Len(t, manager.features, 1) + require.Equal(t, deprecated.OptionLegacyDNSAddressFilter.Name, manager.features[0].Name) +} + +func TestLegacyDNSModeReportsDNSRuleStrategyDeprecation(t *testing.T) { + t.Parallel() + + manager := &fakeDeprecatedManager{} + ctx := service.ContextWith[deprecated.Manager](context.Background(), manager) + router := &Router{ + ctx: ctx, + logger: log.NewNOPFactory().NewLogger("dns"), + client: &fakeDNSClient{}, + defaultDomainStrategy: C.DomainStrategyAsIS, + } + err := router.Initialize([]option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{ + Server: "default", + Strategy: option.DomainStrategy(C.DomainStrategyIPv4Only), + }, + }, + }, + }}) + require.NoError(t, err) + + err = router.Start(adapter.StartStateStart) + require.NoError(t, err) + require.Len(t, manager.features, 1) + require.Equal(t, deprecated.OptionLegacyDNSRuleStrategy.Name, manager.features[0].Name) +} diff --git a/dns/transport/base.go b/dns/transport/base.go new file mode 100644 index 0000000000..06e41fd029 --- /dev/null +++ b/dns/transport/base.go @@ -0,0 +1,145 @@ +package transport + +import ( + "context" + "os" + "sync" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" +) + +type TransportState int + +const ( + StateNew TransportState = iota + StateStarted + StateClosing + StateClosed +) + +var ( + ErrTransportClosed = os.ErrClosed + ErrConnectionReset = E.New("connection reset") +) + +type BaseTransport struct { + dns.TransportAdapter + Logger logger.ContextLogger + + mutex sync.Mutex + state TransportState + inFlight int32 + queriesComplete chan struct{} + closeCtx context.Context + closeCancel context.CancelFunc +} + +func NewBaseTransport(adapter dns.TransportAdapter, logger logger.ContextLogger) *BaseTransport { + ctx, cancel := context.WithCancel(context.Background()) + return &BaseTransport{ + TransportAdapter: adapter, + Logger: logger, + state: StateNew, + closeCtx: ctx, + closeCancel: cancel, + } +} + +func (t *BaseTransport) State() TransportState { + t.mutex.Lock() + defer t.mutex.Unlock() + return t.state +} + +func (t *BaseTransport) SetStarted() error { + t.mutex.Lock() + defer t.mutex.Unlock() + switch t.state { + case StateNew: + t.state = StateStarted + return nil + case StateStarted: + return nil + default: + return ErrTransportClosed + } +} + +func (t *BaseTransport) BeginQuery() bool { + t.mutex.Lock() + defer t.mutex.Unlock() + if t.state != StateStarted { + return false + } + t.inFlight++ + return true +} + +func (t *BaseTransport) EndQuery() { + t.mutex.Lock() + if t.inFlight > 0 { + t.inFlight-- + } + if t.inFlight == 0 && t.queriesComplete != nil { + close(t.queriesComplete) + t.queriesComplete = nil + } + t.mutex.Unlock() +} + +func (t *BaseTransport) CloseContext() context.Context { + return t.closeCtx +} + +func (t *BaseTransport) Shutdown(ctx context.Context) error { + t.mutex.Lock() + + if t.state >= StateClosing { + t.mutex.Unlock() + return nil + } + + if t.state == StateNew { + t.state = StateClosed + t.mutex.Unlock() + t.closeCancel() + return nil + } + + t.state = StateClosing + + if t.inFlight == 0 { + t.state = StateClosed + t.mutex.Unlock() + t.closeCancel() + return nil + } + + t.queriesComplete = make(chan struct{}) + queriesComplete := t.queriesComplete + t.mutex.Unlock() + + t.closeCancel() + + select { + case <-queriesComplete: + t.mutex.Lock() + t.state = StateClosed + t.mutex.Unlock() + return nil + case <-ctx.Done(): + t.mutex.Lock() + t.state = StateClosed + t.mutex.Unlock() + return ctx.Err() + } +} + +func (t *BaseTransport) Close() error { + ctx, cancel := context.WithTimeout(context.Background(), C.TCPTimeout) + defer cancel() + return t.Shutdown(ctx) +} diff --git a/dns/transport/conn_pool.go b/dns/transport/conn_pool.go new file mode 100644 index 0000000000..0e7a20a8c3 --- /dev/null +++ b/dns/transport/conn_pool.go @@ -0,0 +1,444 @@ +package transport + +import ( + "context" + "net" + "sync" + + "github.com/sagernet/sing/common/x/list" + + "golang.org/x/sync/semaphore" +) + +type ConnPoolMode int + +const ( + ConnPoolSingle ConnPoolMode = iota + ConnPoolOrdered +) + +type ConnPoolOptions[T comparable] struct { + Mode ConnPoolMode + // MaxInflight caps concurrent in-progress dials. Only honored in ConnPoolOrdered mode. + MaxInflight int + IsAlive func(T) bool + Close func(T, error) +} + +type ConnPool[T comparable] struct { + options ConnPoolOptions[T] + + sem *semaphore.Weighted + + access sync.Mutex + closed bool + state *connPoolState[T] +} + +type connPoolState[T comparable] struct { + ctx context.Context + cancel context.CancelCauseFunc + + all map[T]struct{} + + idle list.List[T] + idleElements map[T]*list.Element[T] + + shared T + hasShared bool + sharedClaimed bool + sharedCtx context.Context + sharedCancel context.CancelCauseFunc + + connecting *connPoolConnect[T] +} + +type connPoolConnect[T comparable] struct { + done chan struct{} + err error +} + +func NewConnPool[T comparable](options ConnPoolOptions[T]) *ConnPool[T] { + p := &ConnPool[T]{ + options: options, + } + if options.Mode == ConnPoolOrdered && options.MaxInflight > 0 { + p.sem = semaphore.NewWeighted(int64(options.MaxInflight)) + } + p.state = newConnPoolState[T](options.Mode) + return p +} + +func newConnPoolState[T comparable](mode ConnPoolMode) *connPoolState[T] { + ctx, cancel := context.WithCancelCause(context.Background()) + state := &connPoolState[T]{ + ctx: ctx, + cancel: cancel, + all: make(map[T]struct{}), + } + if mode == ConnPoolOrdered { + state.idleElements = make(map[T]*list.Element[T]) + } + return state +} + +func (p *ConnPool[T]) Acquire(ctx context.Context, dial func(context.Context) (T, error)) (T, bool, error) { + switch p.options.Mode { + case ConnPoolSingle: + conn, _, created, err := p.acquireShared(ctx, dial) + return conn, created, err + case ConnPoolOrdered: + return p.acquireOrdered(ctx, dial) + default: + var zero T + return zero, false, net.ErrClosed + } +} + +func (p *ConnPool[T]) AcquireShared(ctx context.Context, dial func(context.Context) (T, error)) (T, context.Context, bool, error) { + if p.options.Mode != ConnPoolSingle { + var zero T + return zero, nil, false, net.ErrClosed + } + return p.acquireShared(ctx, dial) +} + +func (p *ConnPool[T]) Release(conn T, reuse bool) { + p.access.Lock() + if p.closed { + p.access.Unlock() + p.options.Close(conn, net.ErrClosed) + return + } + state := p.state + if _, tracked := state.all[conn]; !tracked { + p.access.Unlock() + p.options.Close(conn, net.ErrClosed) + return + } + if !reuse || !p.options.IsAlive(conn) { + p.removeConn(state, conn, net.ErrClosed) + p.access.Unlock() + p.options.Close(conn, net.ErrClosed) + return + } + if p.options.Mode == ConnPoolOrdered { + if _, idle := state.idleElements[conn]; !idle { + state.idleElements[conn] = state.idle.PushBack(conn) + } + } + p.access.Unlock() +} + +func (p *ConnPool[T]) Invalidate(conn T, cause error) { + p.access.Lock() + if p.closed { + p.access.Unlock() + p.options.Close(conn, cause) + return + } + state := p.state + if _, tracked := state.all[conn]; !tracked { + p.access.Unlock() + return + } + p.removeConn(state, conn, cause) + p.access.Unlock() + p.options.Close(conn, cause) +} + +func (p *ConnPool[T]) acquireSlot(ctx context.Context, state *connPoolState[T]) error { + if p.sem == nil { + return nil + } + acquireCtx, cancel := context.WithCancel(ctx) + stopStateCancel := context.AfterFunc(state.ctx, cancel) + err := p.sem.Acquire(acquireCtx, 1) + stopStateCancel() + cancel() + if err == nil { + return nil + } + ctxErr := ctx.Err() + if ctxErr != nil { + return ctxErr + } + return context.Cause(state.ctx) +} + +func (p *ConnPool[T]) releaseSlot() { + if p.sem != nil { + p.sem.Release(1) + } +} + +// removeConn must be called with p.access held. +func (p *ConnPool[T]) removeConn(state *connPoolState[T], conn T, cause error) { + delete(state.all, conn) + switch p.options.Mode { + case ConnPoolSingle: + if state.hasShared && state.shared == conn { + var zero T + state.shared = zero + state.hasShared = false + state.sharedClaimed = false + state.sharedCtx = nil + if state.sharedCancel != nil { + state.sharedCancel(cause) + state.sharedCancel = nil + } + } + case ConnPoolOrdered: + if element, loaded := state.idleElements[conn]; loaded { + state.idle.Remove(element) + delete(state.idleElements, conn) + } + } +} + +func (p *ConnPool[T]) Reset() { + p.access.Lock() + if p.closed { + p.access.Unlock() + return + } + oldState := p.state + p.state = newConnPoolState[T](p.options.Mode) + p.access.Unlock() + + p.closeState(oldState, net.ErrClosed) +} + +func (p *ConnPool[T]) Close() error { + p.access.Lock() + if p.closed { + p.access.Unlock() + return nil + } + p.closed = true + oldState := p.state + p.state = nil + p.access.Unlock() + + p.closeState(oldState, net.ErrClosed) + return nil +} + +func (p *ConnPool[T]) acquireOrdered(ctx context.Context, dial func(context.Context) (T, error)) (T, bool, error) { + var zero T + for { + p.access.Lock() + if p.closed { + p.access.Unlock() + return zero, false, net.ErrClosed + } + current := p.state + if element := current.idle.Front(); element != nil { + idleConn := current.idle.Remove(element) + delete(current.idleElements, idleConn) + if p.options.IsAlive(idleConn) { + p.access.Unlock() + return idleConn, false, nil + } + delete(current.all, idleConn) + p.access.Unlock() + p.options.Close(idleConn, net.ErrClosed) + continue + } + p.access.Unlock() + return p.dialAndInstall(ctx, current, dial) + } +} + +func (p *ConnPool[T]) dialAndInstall(ctx context.Context, current *connPoolState[T], dial func(context.Context) (T, error)) (T, bool, error) { + var zero T + err := p.acquireSlot(ctx, current) + if err != nil { + return zero, false, err + } + defer p.releaseSlot() + dialCtx, dialCancel := context.WithCancelCause(ctx) + stopStateCancel := context.AfterFunc(current.ctx, func() { + dialCancel(context.Cause(current.ctx)) + }) + conn, err := dial(dialCtx) + stateCancelStopped := stopStateCancel() + dialErr := context.Cause(dialCtx) + if dialErr == nil && !stateCancelStopped { + dialErr = context.Cause(current.ctx) + } + dialCancel(nil) + if err != nil { + if dialErr != nil { + return zero, false, dialErr + } + return zero, false, err + } + if dialErr != nil { + p.options.Close(conn, dialErr) + return zero, false, dialErr + } + + p.access.Lock() + if p.closed { + p.access.Unlock() + p.options.Close(conn, net.ErrClosed) + return zero, false, net.ErrClosed + } + if p.state != current { + p.access.Unlock() + p.options.Close(conn, net.ErrClosed) + return zero, false, net.ErrClosed + } + current.all[conn] = struct{}{} + p.access.Unlock() + return conn, true, nil +} + +func (p *ConnPool[T]) acquireShared(ctx context.Context, dial func(context.Context) (T, error)) (T, context.Context, bool, error) { + var zero T + for { + p.access.Lock() + if p.closed { + p.access.Unlock() + return zero, nil, false, net.ErrClosed + } + current := p.state + if current.hasShared { + conn := current.shared + if p.options.IsAlive(conn) { + created := !current.sharedClaimed + current.sharedClaimed = true + connCtx := current.sharedCtx + p.access.Unlock() + return conn, connCtx, created, nil + } + p.removeConn(current, conn, net.ErrClosed) + p.access.Unlock() + p.options.Close(conn, net.ErrClosed) + continue + } + + startDial := current.connecting == nil + if startDial { + current.connecting = &connPoolConnect[T]{done: make(chan struct{})} + } + state := current.connecting + p.access.Unlock() + + if startDial { + go p.connectSingle(current, state, ctx, dial) + } + + select { + case <-state.done: + conn, connCtx, created, retry, err := p.collectShared(current, state, startDial) + if retry { + continue + } + return conn, connCtx, created, err + case <-ctx.Done(): + return zero, nil, false, ctx.Err() + case <-current.ctx.Done(): + p.access.Lock() + closed := p.closed + p.access.Unlock() + if closed { + return zero, nil, false, net.ErrClosed + } + } + } +} + +func (p *ConnPool[T]) connectSingle(current *connPoolState[T], state *connPoolConnect[T], ctx context.Context, dial func(context.Context) (T, error)) { + dialCtx, dialCancel := context.WithCancelCause(ctx) + stopStateCancel := context.AfterFunc(current.ctx, func() { + dialCancel(context.Cause(current.ctx)) + }) + conn, err := dial(dialCtx) + stateCancelStopped := stopStateCancel() + dialErr := context.Cause(dialCtx) + if dialErr == nil && !stateCancelStopped { + dialErr = context.Cause(current.ctx) + } + dialCancel(nil) + if dialErr != nil { + if err == nil { + p.options.Close(conn, dialErr) + } + err = dialErr + } + + var closeErr error + p.access.Lock() + current.connecting = nil + if err != nil { + state.err = err + } else if p.closed { + closeErr = net.ErrClosed + state.err = closeErr + } else if p.state != current { + closeErr = net.ErrClosed + state.err = closeErr + } else { + sharedCtx, sharedCancel := context.WithCancelCause(current.ctx) + current.shared = conn + current.hasShared = true + current.sharedCtx = sharedCtx + current.sharedCancel = sharedCancel + current.all[conn] = struct{}{} + } + p.access.Unlock() + + if closeErr != nil { + p.options.Close(conn, closeErr) + } + close(state.done) +} + +func (p *ConnPool[T]) collectShared(current *connPoolState[T], state *connPoolConnect[T], startDial bool) (T, context.Context, bool, bool, error) { + var zero T + + p.access.Lock() + if state.err != nil { + err := state.err + p.access.Unlock() + if startDial { + return zero, nil, false, false, err + } + return zero, nil, false, true, nil + } + if p.closed { + p.access.Unlock() + return zero, nil, false, false, net.ErrClosed + } + if p.state != current { + p.access.Unlock() + return zero, nil, false, false, net.ErrClosed + } + if !current.hasShared { + p.access.Unlock() + return zero, nil, false, true, nil + } + + conn := current.shared + if !p.options.IsAlive(conn) { + p.removeConn(current, conn, net.ErrClosed) + p.access.Unlock() + p.options.Close(conn, net.ErrClosed) + return zero, nil, false, true, nil + } + + created := !current.sharedClaimed + current.sharedClaimed = true + connCtx := current.sharedCtx + p.access.Unlock() + return conn, connCtx, created, false, nil +} + +func (p *ConnPool[T]) closeState(state *connPoolState[T], cause error) { + state.cancel(cause) + for conn := range state.all { + p.options.Close(conn, cause) + } +} diff --git a/dns/transport/connector.go b/dns/transport/connector.go new file mode 100644 index 0000000000..0a64fea7dc --- /dev/null +++ b/dns/transport/connector.go @@ -0,0 +1,323 @@ +package transport + +import ( + "context" + "net" + "sync" + "time" + + E "github.com/sagernet/sing/common/exceptions" +) + +type ConnectorCallbacks[T any] struct { + IsClosed func(connection T) bool + Close func(connection T) + Reset func(connection T) +} + +type Connector[T any] struct { + dial func(ctx context.Context) (T, error) + callbacks ConnectorCallbacks[T] + + access sync.Mutex + connection T + hasConnection bool + connectionCancel context.CancelFunc + connecting chan struct{} + + closeCtx context.Context + closed bool +} + +func NewConnector[T any](closeCtx context.Context, dial func(context.Context) (T, error), callbacks ConnectorCallbacks[T]) *Connector[T] { + return &Connector[T]{ + dial: dial, + callbacks: callbacks, + closeCtx: closeCtx, + } +} + +func NewSingleflightConnector(closeCtx context.Context, dial func(context.Context) (*Connection, error)) *Connector[*Connection] { + return NewConnector(closeCtx, dial, ConnectorCallbacks[*Connection]{ + IsClosed: func(connection *Connection) bool { + return connection.IsClosed() + }, + Close: func(connection *Connection) { + connection.CloseWithError(ErrTransportClosed) + }, + Reset: func(connection *Connection) { + connection.CloseWithError(ErrConnectionReset) + }, + }) +} + +type contextKeyConnecting struct{} + +var errRecursiveConnectorDial = E.New("recursive connector dial") + +type connectorDialResult[T any] struct { + connection T + cancel context.CancelFunc + err error +} + +func (c *Connector[T]) Get(ctx context.Context) (T, error) { + var zero T + for { + c.access.Lock() + + if c.closed { + c.access.Unlock() + return zero, ErrTransportClosed + } + + if c.hasConnection && !c.callbacks.IsClosed(c.connection) { + connection := c.connection + c.access.Unlock() + return connection, nil + } + + c.hasConnection = false + if c.connectionCancel != nil { + c.connectionCancel() + c.connectionCancel = nil + } + if isRecursiveConnectorDial(ctx, c) { + c.access.Unlock() + return zero, errRecursiveConnectorDial + } + + if c.connecting != nil { + connecting := c.connecting + c.access.Unlock() + + select { + case <-connecting: + continue + case <-ctx.Done(): + return zero, ctx.Err() + case <-c.closeCtx.Done(): + return zero, ErrTransportClosed + } + } + + if err := ctx.Err(); err != nil { + c.access.Unlock() + return zero, err + } + + connecting := make(chan struct{}) + c.connecting = connecting + dialContext := context.WithValue(ctx, contextKeyConnecting{}, c) + dialResult := make(chan connectorDialResult[T], 1) + c.access.Unlock() + + go func() { + connection, cancel, err := c.dialWithCancellation(dialContext) + dialResult <- connectorDialResult[T]{ + connection: connection, + cancel: cancel, + err: err, + } + }() + + select { + case result := <-dialResult: + return c.completeDial(ctx, connecting, result) + case <-ctx.Done(): + go func() { + result := <-dialResult + _, _ = c.completeDial(ctx, connecting, result) + }() + return zero, ctx.Err() + case <-c.closeCtx.Done(): + go func() { + result := <-dialResult + _, _ = c.completeDial(ctx, connecting, result) + }() + return zero, ErrTransportClosed + } + } +} + +func isRecursiveConnectorDial[T any](ctx context.Context, connector *Connector[T]) bool { + dialConnector, loaded := ctx.Value(contextKeyConnecting{}).(*Connector[T]) + return loaded && dialConnector == connector +} + +func (c *Connector[T]) completeDial(ctx context.Context, connecting chan struct{}, result connectorDialResult[T]) (T, error) { + var zero T + + c.access.Lock() + defer c.access.Unlock() + defer func() { + if c.connecting == connecting { + c.connecting = nil + } + close(connecting) + }() + + if result.err != nil { + return zero, result.err + } + if c.closed || c.closeCtx.Err() != nil { + result.cancel() + c.callbacks.Close(result.connection) + return zero, ErrTransportClosed + } + if err := ctx.Err(); err != nil { + result.cancel() + c.callbacks.Close(result.connection) + return zero, err + } + + c.connection = result.connection + c.hasConnection = true + c.connectionCancel = result.cancel + return c.connection, nil +} + +func (c *Connector[T]) dialWithCancellation(ctx context.Context) (T, context.CancelFunc, error) { + var zero T + if err := ctx.Err(); err != nil { + return zero, nil, err + } + connCtx, cancel := context.WithCancel(c.closeCtx) + + var ( + stateAccess sync.Mutex + dialComplete bool + ) + stopCancel := context.AfterFunc(ctx, func() { + stateAccess.Lock() + if !dialComplete { + cancel() + } + stateAccess.Unlock() + }) + select { + case <-ctx.Done(): + stateAccess.Lock() + dialComplete = true + stateAccess.Unlock() + stopCancel() + cancel() + return zero, nil, ctx.Err() + default: + } + + connection, err := c.dial(valueContext{connCtx, ctx}) + stateAccess.Lock() + dialComplete = true + stateAccess.Unlock() + stopCancel() + if err != nil { + cancel() + return zero, nil, err + } + return connection, cancel, nil +} + +type valueContext struct { + context.Context + parent context.Context +} + +func (v valueContext) Value(key any) any { + return v.parent.Value(key) +} + +func (v valueContext) Deadline() (time.Time, bool) { + return v.parent.Deadline() +} + +func (c *Connector[T]) Close() error { + c.access.Lock() + + if c.closed { + c.access.Unlock() + return nil + } + hasConnectiopn := c.hasConnection + c.closed = true + c.hasConnection = false + c.access.Unlock() + + if c.connectionCancel != nil { + c.connectionCancel() + c.connectionCancel = nil + } + if hasConnectiopn { + c.callbacks.Close(c.connection) + } + + return nil +} + +func (c *Connector[T]) Reset() { + c.access.Lock() + defer c.access.Unlock() + + if c.connectionCancel != nil { + c.connectionCancel() + c.connectionCancel = nil + } + if c.hasConnection { + c.callbacks.Reset(c.connection) + c.hasConnection = false + } +} + +type Connection struct { + net.Conn + + closeOnce sync.Once + done chan struct{} + closeError error +} + +func WrapConnection(conn net.Conn) *Connection { + return &Connection{ + Conn: conn, + done: make(chan struct{}), + } +} + +func (c *Connection) Done() <-chan struct{} { + return c.done +} + +func (c *Connection) IsClosed() bool { + select { + case <-c.done: + return true + default: + return false + } +} + +func (c *Connection) CloseError() error { + select { + case <-c.done: + if c.closeError != nil { + return c.closeError + } + return ErrTransportClosed + default: + return nil + } +} + +func (c *Connection) Close() error { + return c.CloseWithError(ErrTransportClosed) +} + +func (c *Connection) CloseWithError(err error) error { + var returnError error + c.closeOnce.Do(func() { + c.closeError = err + returnError = c.Conn.Close() + close(c.done) + }) + return returnError +} diff --git a/dns/transport/dhcp/dhcp.go b/dns/transport/dhcp/dhcp.go new file mode 100644 index 0000000000..9585629c8e --- /dev/null +++ b/dns/transport/dhcp/dhcp.go @@ -0,0 +1,311 @@ +package dhcp + +import ( + "context" + "errors" + "io" + "net" + "runtime" + "strings" + "sync" + "syscall" + "time" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/task" + "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service" + + "github.com/insomniacslk/dhcp/dhcpv4" + mDNS "github.com/miekg/dns" + "golang.org/x/exp/slices" +) + +func RegisterTransport(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.DHCPDNSServerOptions](registry, C.DNSTypeDHCP, NewTransport) +} + +var _ adapter.DNSTransport = (*Transport)(nil) + +type Transport struct { + dns.TransportAdapter + ctx context.Context + dialer N.Dialer + logger logger.ContextLogger + networkManager adapter.NetworkManager + interfaceName string + interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback] + transportLock sync.RWMutex + updatedAt time.Time + lastError error + servers []M.Socksaddr + search []string + ndots int + attempts int +} + +func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.DHCPDNSServerOptions) (adapter.DNSTransport, error) { + transportDialer, err := dns.NewLocalDialer(ctx, options.LocalDNSServerOptions) + if err != nil { + return nil, err + } + return &Transport{ + TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeDHCP, tag, options.LocalDNSServerOptions), + ctx: ctx, + dialer: transportDialer, + logger: logger, + networkManager: service.FromContext[adapter.NetworkManager](ctx), + interfaceName: options.Interface, + ndots: 1, + attempts: 2, + }, nil +} + +func NewRawTransport(transportAdapter dns.TransportAdapter, ctx context.Context, dialer N.Dialer, logger log.ContextLogger) *Transport { + return &Transport{ + TransportAdapter: transportAdapter, + ctx: ctx, + dialer: dialer, + logger: logger, + networkManager: service.FromContext[adapter.NetworkManager](ctx), + ndots: 1, + attempts: 2, + } +} + +func (t *Transport) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + if t.interfaceName == "" { + t.interfaceCallback = t.networkManager.InterfaceMonitor().RegisterCallback(t.interfaceUpdated) + } + go func() { + _, err := t.fetch() + if err != nil { + t.logger.Error(E.Cause(err, "fetch DNS servers")) + } + }() + return nil +} + +func (t *Transport) Close() error { + if t.interfaceCallback != nil { + t.networkManager.InterfaceMonitor().UnregisterCallback(t.interfaceCallback) + } + return nil +} + +func (t *Transport) Reset() { + t.transportLock.Lock() + t.updatedAt = time.Time{} + t.lastError = nil + t.servers = nil + t.transportLock.Unlock() +} + +func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + servers, err := t.fetch() + if err != nil { + return nil, err + } + if len(servers) == 0 { + return nil, E.New("dhcp: empty DNS servers from response") + } + return t.Exchange0(ctx, message, servers) +} + +func (t *Transport) Exchange0(ctx context.Context, message *mDNS.Msg, servers []M.Socksaddr) (*mDNS.Msg, error) { + question := message.Question[0] + domain := dns.FqdnToDomain(question.Name) + if len(servers) == 1 || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) { + return t.exchangeSingleRequest(ctx, servers, message, domain) + } else { + return t.exchangeParallel(ctx, servers, message, domain) + } +} + +func (t *Transport) Fetch() []M.Socksaddr { + servers, _ := t.fetch() + return servers +} + +func (t *Transport) fetch() ([]M.Socksaddr, error) { + t.transportLock.RLock() + updatedAt := t.updatedAt + lastError := t.lastError + servers := t.servers + t.transportLock.RUnlock() + if lastError != nil { + return nil, lastError + } + if time.Since(updatedAt) < C.DHCPTTL { + return servers, nil + } + t.transportLock.Lock() + defer t.transportLock.Unlock() + if time.Since(t.updatedAt) < C.DHCPTTL { + return t.servers, nil + } + err := t.updateServers() + if err != nil { + return servers, err + } + return t.servers, nil +} + +func (t *Transport) fetchInterface() (*control.Interface, error) { + if t.interfaceName == "" { + if t.networkManager.InterfaceMonitor() == nil { + return nil, E.New("missing monitor for auto DHCP, set route.auto_detect_interface") + } + defaultInterface := t.networkManager.InterfaceMonitor().DefaultInterface() + if defaultInterface == nil { + return nil, E.New("missing default interface") + } + return defaultInterface, nil + } else { + return t.networkManager.InterfaceFinder().ByName(t.interfaceName) + } +} + +func (t *Transport) updateServers() error { + iface, err := t.fetchInterface() + if err != nil { + return E.Cause(err, "dhcp: prepare interface") + } + + t.logger.Info("dhcp: query DNS servers on ", iface.Name) + fetchCtx, cancel := context.WithTimeout(t.ctx, C.DHCPTimeout) + err = t.fetchServers0(fetchCtx, iface) + cancel() + t.updatedAt = time.Now() + if err != nil { + t.lastError = err + return err + } else if len(t.servers) == 0 { + t.lastError = E.New("dhcp: empty DNS servers response") + return t.lastError + } else { + t.lastError = nil + return nil + } +} + +func (t *Transport) interfaceUpdated(defaultInterface *control.Interface, flags int) { + err := t.updateServers() + if err != nil { + t.logger.Error("update servers: ", err) + } +} + +func (t *Transport) fetchServers0(ctx context.Context, iface *control.Interface) error { + var listener net.ListenConfig + listener.Control = control.Append(listener.Control, control.BindToInterface(t.networkManager.InterfaceFinder(), iface.Name, iface.Index)) + listener.Control = control.Append(listener.Control, control.ReuseAddr()) + listenAddr := "0.0.0.0:68" + if runtime.GOOS == "linux" || runtime.GOOS == "android" { + listenAddr = "255.255.255.255:68" + } + var ( + packetConn net.PacketConn + err error + ) + for range 5 { + packetConn, err = listener.ListenPacket(t.ctx, "udp4", listenAddr) + if err == nil || !errors.Is(err, syscall.EADDRINUSE) { + break + } + time.Sleep(time.Second) + } + if err != nil { + return err + } + defer packetConn.Close() + + discovery, err := dhcpv4.NewDiscovery(iface.HardwareAddr, dhcpv4.WithBroadcast(true), dhcpv4.WithRequestedOptions( + dhcpv4.OptionDomainName, + dhcpv4.OptionDomainNameServer, + dhcpv4.OptionDNSDomainSearchList, + )) + if err != nil { + return err + } + + _, err = packetConn.WriteTo(discovery.ToBytes(), &net.UDPAddr{IP: net.IPv4bcast, Port: 67}) + if err != nil { + return err + } + + var group task.Group + group.Append0(func(ctx context.Context) error { + return t.fetchServersResponse(iface, packetConn, discovery.TransactionID) + }) + group.Cleanup(func() { + packetConn.Close() + }) + return group.Run(ctx) +} + +func (t *Transport) fetchServersResponse(iface *control.Interface, packetConn net.PacketConn, transactionID dhcpv4.TransactionID) error { + buffer := buf.NewSize(dhcpv4.MaxMessageSize) + defer buffer.Release() + + for { + buffer.Reset() + _, _, err := buffer.ReadPacketFrom(packetConn) + if err != nil { + if errors.Is(err, io.ErrShortBuffer) { + continue + } + return err + } + + dhcpPacket, err := dhcpv4.FromBytes(buffer.Bytes()) + if err != nil { + t.logger.Trace("dhcp: parse DHCP response: ", err) + return err + } + + if dhcpPacket.MessageType() != dhcpv4.MessageTypeOffer { + t.logger.Trace("dhcp: expected OFFER response, but got ", dhcpPacket.MessageType()) + continue + } + + if dhcpPacket.TransactionID != transactionID { + t.logger.Trace("dhcp: expected transaction ID ", transactionID, ", but got ", dhcpPacket.TransactionID) + continue + } + + return t.recreateServers(iface, dhcpPacket) + } +} + +func (t *Transport) recreateServers(iface *control.Interface, dhcpPacket *dhcpv4.DHCPv4) error { + searchList := dhcpPacket.DomainSearch() + if searchList != nil && len(searchList.Labels) > 0 { + t.search = searchList.Labels + } else if dhcpPacket.DomainName() != "" { + t.search = []string{dhcpPacket.DomainName()} + } + serverAddrs := common.Map(dhcpPacket.DNS(), func(it net.IP) M.Socksaddr { + return M.SocksaddrFrom(M.AddrFromIP(it), 53) + }) + if len(serverAddrs) > 0 && !slices.Equal(t.servers, serverAddrs) { + t.logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, M.Socksaddr.String), ","), "], search: [", strings.Join(t.search, ","), "]") + } + t.servers = serverAddrs + return nil +} diff --git a/dns/transport/dhcp/dhcp_shared.go b/dns/transport/dhcp/dhcp_shared.go new file mode 100644 index 0000000000..16e319bac3 --- /dev/null +++ b/dns/transport/dhcp/dhcp_shared.go @@ -0,0 +1,205 @@ +package dhcp + +import ( + "context" + "errors" + "math/rand" + "strings" + "syscall" + + "github.com/sagernet/sing-box/dns/transport" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + mDNS "github.com/miekg/dns" +) + +func (t *Transport) exchangeSingleRequest(ctx context.Context, servers []M.Socksaddr, message *mDNS.Msg, domain string) (*mDNS.Msg, error) { + var lastErr error + for _, fqdn := range t.nameList(domain) { + response, err := t.tryOneName(ctx, servers, fqdn, message) + if err != nil { + lastErr = err + continue + } + return response, nil + } + return nil, lastErr +} + +func (t *Transport) exchangeParallel(ctx context.Context, servers []M.Socksaddr, message *mDNS.Msg, domain string) (*mDNS.Msg, error) { + returned := make(chan struct{}) + defer close(returned) + type queryResult struct { + response *mDNS.Msg + err error + } + results := make(chan queryResult) + startRacer := func(ctx context.Context, fqdn string) { + response, err := t.tryOneName(ctx, servers, fqdn, message) + select { + case results <- queryResult{response, err}: + case <-returned: + } + } + queryCtx, queryCancel := context.WithCancel(ctx) + defer queryCancel() + var nameCount int + for _, fqdn := range t.nameList(domain) { + nameCount++ + go startRacer(queryCtx, fqdn) + } + var errors []error + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + case result := <-results: + if result.err == nil { + return result.response, nil + } + errors = append(errors, result.err) + if len(errors) == nameCount { + return nil, E.Errors(errors...) + } + } + } +} + +func (t *Transport) tryOneName(ctx context.Context, servers []M.Socksaddr, fqdn string, message *mDNS.Msg) (*mDNS.Msg, error) { + sLen := len(servers) + var lastErr error + for i := 0; i < t.attempts; i++ { + for j := range sLen { + server := servers[j] + question := message.Question[0] + question.Name = fqdn + response, err := t.exchangeOne(ctx, server, question) + if err != nil { + lastErr = err + continue + } + return response, nil + } + } + return nil, E.Cause(lastErr, fqdn) +} + +func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question) (*mDNS.Msg, error) { + if server.Port == 0 { + server.Port = 53 + } + request := &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Id: uint16(rand.Uint32()), + RecursionDesired: true, + AuthenticatedData: true, + }, + Question: []mDNS.Question{question}, + Compress: true, + } + request.SetEdns0(buf.UDPBufferSize, false) + return t.exchangeUDP(ctx, server, request) +} + +func (t *Transport) exchangeUDP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg) (*mDNS.Msg, error) { + conn, err := t.dialer.DialContext(ctx, N.NetworkUDP, server) + if err != nil { + return nil, err + } + defer conn.Close() + if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() { + conn.SetDeadline(deadline) + } + buffer := buf.Get(buf.UDPBufferSize) + defer buf.Put(buffer) + rawMessage, err := request.PackBuffer(buffer) + if err != nil { + return nil, E.Cause(err, "pack request") + } + _, err = conn.Write(rawMessage) + if err != nil { + if errors.Is(err, syscall.EMSGSIZE) { + return t.exchangeTCP(ctx, server, request) + } + return nil, E.Cause(err, "write request") + } + n, err := conn.Read(buffer) + if err != nil { + if errors.Is(err, syscall.EMSGSIZE) { + return t.exchangeTCP(ctx, server, request) + } + return nil, E.Cause(err, "read response") + } + var response mDNS.Msg + err = response.Unpack(buffer[:n]) + if err != nil { + return nil, E.Cause(err, "unpack response") + } + if response.Truncated { + return t.exchangeTCP(ctx, server, request) + } + return &response, nil +} + +func (t *Transport) exchangeTCP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg) (*mDNS.Msg, error) { + conn, err := t.dialer.DialContext(ctx, N.NetworkTCP, server) + if err != nil { + return nil, err + } + defer conn.Close() + if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() { + conn.SetDeadline(deadline) + } + err = transport.WriteMessage(conn, 0, request) + if err != nil { + return nil, err + } + return transport.ReadMessage(conn) +} + +func (t *Transport) nameList(name string) []string { + l := len(name) + rooted := l > 0 && name[l-1] == '.' + if l > 254 || l == 254 && !rooted { + return nil + } + + if rooted { + if avoidDNS(name) { + return nil + } + return []string{name} + } + + hasNdots := strings.Count(name, ".") >= t.ndots + name += "." + // l++ + + names := make([]string, 0, 1+len(t.search)) + if hasNdots && !avoidDNS(name) { + names = append(names, name) + } + for _, suffix := range t.search { + fqdn := name + suffix + if !avoidDNS(fqdn) && len(fqdn) <= 254 { + names = append(names, fqdn) + } + } + if !hasNdots && !avoidDNS(name) { + names = append(names, name) + } + return names +} + +func avoidDNS(name string) bool { + if name == "" { + return true + } + if name[len(name)-1] == '.' { + name = name[:len(name)-1] + } + return strings.HasSuffix(name, ".onion") +} diff --git a/dns/transport/fakeip/fakeip.go b/dns/transport/fakeip/fakeip.go new file mode 100644 index 0000000000..9aa41e586f --- /dev/null +++ b/dns/transport/fakeip/fakeip.go @@ -0,0 +1,79 @@ +package fakeip + +import ( + "context" + "net/netip" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + + mDNS "github.com/miekg/dns" +) + +func RegisterTransport(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.FakeIPDNSServerOptions](registry, C.DNSTypeFakeIP, NewTransport) +} + +var _ adapter.FakeIPTransport = (*Transport)(nil) + +type Transport struct { + dns.TransportAdapter + logger logger.ContextLogger + store adapter.FakeIPStore + inet4Enabled bool + inet6Enabled bool +} + +func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.FakeIPDNSServerOptions) (adapter.DNSTransport, error) { + inet4Range := options.Inet4Range.Build(netip.Prefix{}) + inet6Range := options.Inet6Range.Build(netip.Prefix{}) + if !inet4Range.IsValid() && !inet6Range.IsValid() { + return nil, E.New("at least one of inet4_range or inet6_range must be set") + } + store := NewStore(ctx, logger, inet4Range, inet6Range) + return &Transport{ + TransportAdapter: dns.NewTransportAdapter(C.DNSTypeFakeIP, tag, nil), + logger: logger, + store: store, + inet4Enabled: inet4Range.IsValid(), + inet6Enabled: inet6Range.IsValid(), + }, nil +} + +func (t *Transport) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + return t.store.Start() +} + +func (t *Transport) Close() error { + return t.store.Close() +} + +func (t *Transport) Reset() { +} + +func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + question := message.Question[0] + if question.Qtype != mDNS.TypeA && question.Qtype != mDNS.TypeAAAA { + return nil, E.New("only IP queries are supported by fakeip") + } + if question.Qtype == mDNS.TypeA && !t.inet4Enabled || question.Qtype == mDNS.TypeAAAA && !t.inet6Enabled { + return dns.FixedResponseStatus(message, mDNS.RcodeSuccess), nil + } + address, err := t.store.Create(dns.FqdnToDomain(question.Name), question.Qtype == mDNS.TypeAAAA) + if err != nil { + return nil, err + } + return dns.FixedResponse(message.Id, question, []netip.Addr{address}, C.DefaultDNSTTL), nil +} + +func (t *Transport) Store() adapter.FakeIPStore { + return t.store +} diff --git a/transport/fakeip/memory.go b/dns/transport/fakeip/memory.go similarity index 95% rename from transport/fakeip/memory.go rename to dns/transport/fakeip/memory.go index 1640ab349d..0cf8ecc7d3 100644 --- a/transport/fakeip/memory.go +++ b/dns/transport/fakeip/memory.go @@ -82,8 +82,12 @@ func (s *MemoryStorage) FakeIPLoadDomain(domain string, isIPv6 bool) (netip.Addr } func (s *MemoryStorage) FakeIPReset() error { + s.addressAccess.Lock() + s.domainAccess.Lock() s.addressCache = make(map[netip.Addr]string) s.domainCache4 = make(map[string]netip.Addr) s.domainCache6 = make(map[string]netip.Addr) + s.domainAccess.Unlock() + s.addressAccess.Unlock() return nil } diff --git a/transport/fakeip/store.go b/dns/transport/fakeip/store.go similarity index 63% rename from transport/fakeip/store.go rename to dns/transport/fakeip/store.go index 83677b0d05..b7c51dfa57 100644 --- a/transport/fakeip/store.go +++ b/dns/transport/fakeip/store.go @@ -3,6 +3,7 @@ package fakeip import ( "context" "net/netip" + "sync" "github.com/sagernet/sing-box/adapter" E "github.com/sagernet/sing/common/exceptions" @@ -13,22 +14,49 @@ import ( var _ adapter.FakeIPStore = (*Store)(nil) type Store struct { - ctx context.Context - logger logger.Logger - inet4Range netip.Prefix - inet6Range netip.Prefix - storage adapter.FakeIPStorage - inet4Current netip.Addr - inet6Current netip.Addr + ctx context.Context + logger logger.Logger + inet4Range netip.Prefix + inet6Range netip.Prefix + inet4Last netip.Addr + inet6Last netip.Addr + storage adapter.FakeIPStorage + + addressAccess sync.Mutex + inet4Current netip.Addr + inet6Current netip.Addr } func NewStore(ctx context.Context, logger logger.Logger, inet4Range netip.Prefix, inet6Range netip.Prefix) *Store { - return &Store{ + store := &Store{ ctx: ctx, logger: logger, inet4Range: inet4Range, inet6Range: inet6Range, } + if inet4Range.IsValid() { + store.inet4Last = broadcastAddress(inet4Range) + } + if inet6Range.IsValid() { + store.inet6Last = broadcastAddress(inet6Range) + } + return store +} + +func broadcastAddress(prefix netip.Prefix) netip.Addr { + addr := prefix.Addr() + raw := addr.As16() + bits := prefix.Bits() + if addr.Is4() { + bits += 96 + } + for i := bits; i < 128; i++ { + raw[i/8] |= 1 << (7 - i%8) + } + if addr.Is4() { + return netip.AddrFrom4([4]byte(raw[12:])) + } + return netip.AddrFrom16(raw) } func (s *Store) Start() error { @@ -46,10 +74,10 @@ func (s *Store) Start() error { s.inet6Current = metadata.Inet6Current } else { if s.inet4Range.IsValid() { - s.inet4Current = s.inet4Range.Addr().Next().Next() + s.inet4Current = s.inet4Range.Addr().Next() } if s.inet6Range.IsValid() { - s.inet6Current = s.inet6Range.Addr().Next().Next() + s.inet6Current = s.inet6Range.Addr().Next() } _ = storage.FakeIPReset() } @@ -65,25 +93,37 @@ func (s *Store) Close() error { if s.storage == nil { return nil } - return s.storage.FakeIPSaveMetadata(&adapter.FakeIPMetadata{ + s.addressAccess.Lock() + metadata := &adapter.FakeIPMetadata{ Inet4Range: s.inet4Range, Inet6Range: s.inet6Range, Inet4Current: s.inet4Current, Inet6Current: s.inet6Current, - }) + } + s.addressAccess.Unlock() + return s.storage.FakeIPSaveMetadata(metadata) } func (s *Store) Create(domain string, isIPv6 bool) (netip.Addr, error) { if address, loaded := s.storage.FakeIPLoadDomain(domain, isIPv6); loaded { return address, nil } + + s.addressAccess.Lock() + defer s.addressAccess.Unlock() + + // Double-check after acquiring lock + if address, loaded := s.storage.FakeIPLoadDomain(domain, isIPv6); loaded { + return address, nil + } + var address netip.Addr if !isIPv6 { if !s.inet4Current.IsValid() { return netip.Addr{}, E.New("missing IPv4 fakeip address range") } nextAddress := s.inet4Current.Next() - if !s.inet4Range.Contains(nextAddress) { + if nextAddress == s.inet4Last || !s.inet4Range.Contains(nextAddress) { nextAddress = s.inet4Range.Addr().Next().Next() } s.inet4Current = nextAddress @@ -93,13 +133,16 @@ func (s *Store) Create(domain string, isIPv6 bool) (netip.Addr, error) { return netip.Addr{}, E.New("missing IPv6 fakeip address range") } nextAddress := s.inet6Current.Next() - if !s.inet6Range.Contains(nextAddress) { + if nextAddress == s.inet6Last || !s.inet6Range.Contains(nextAddress) { nextAddress = s.inet6Range.Addr().Next().Next() } s.inet6Current = nextAddress address = nextAddress } - s.storage.FakeIPStoreAsync(address, domain, s.logger) + err := s.storage.FakeIPStore(address, domain) + if err != nil { + s.logger.Warn("save FakeIP cache: ", err) + } s.storage.FakeIPSaveMetadataAsync(&adapter.FakeIPMetadata{ Inet4Range: s.inet4Range, Inet6Range: s.inet6Range, diff --git a/dns/transport/hosts/hosts.go b/dns/transport/hosts/hosts.go new file mode 100644 index 0000000000..d593ef8cc9 --- /dev/null +++ b/dns/transport/hosts/hosts.go @@ -0,0 +1,145 @@ +package hosts + +import ( + "context" + "net/netip" + "os" + "regexp" + "strings" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/service/filemanager" + + mDNS "github.com/miekg/dns" +) + +func RegisterTransport(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.HostsDNSServerOptions](registry, C.DNSTypeHosts, NewTransport) +} + +var ( + _ adapter.DNSTransport = (*Transport)(nil) + _ adapter.DNSTransportWithPreferredDomain = (*Transport)(nil) +) + +type Transport struct { + dns.TransportAdapter + files []*File + predefined map[string][]netip.Addr +} + +func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.HostsDNSServerOptions) (adapter.DNSTransport, error) { + var ( + files []*File + predefined = make(map[string][]netip.Addr) + ) + if len(options.Path) == 0 { + defaultFile, err := NewDefault() + if err != nil { + return nil, err + } + files = append(files, defaultFile) + } else { + for _, path := range options.Path { + files = append(files, NewFile(filemanager.BasePath(ctx, os.ExpandEnv(path)))) + } + } + if options.Predefined != nil { + for _, entry := range options.Predefined.Entries() { + predefined[mDNS.CanonicalName(entry.Key)] = entry.Value + } + } + return &Transport{ + TransportAdapter: dns.NewTransportAdapter(C.DNSTypeHosts, tag, nil), + files: files, + predefined: predefined, + }, nil +} + +func (t *Transport) Start(stage adapter.StartStage) error { + return nil +} + +func (t *Transport) Close() error { + return nil +} + +func (t *Transport) Reset() { +} + +func (t *Transport) PreferredDomain(domain string) bool { + if _, loaded := t.predefined[domain]; loaded { + return true + } + for _, file := range t.files { + if len(file.Lookup(domain)) > 0 { + return true + } + } + return false +} + +func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + question := message.Question[0] + domain := mDNS.CanonicalName(question.Name) + if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA { + if addresses, ok := t.predefined[domain]; ok { + return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil + } + for _, file := range t.files { + addresses := file.Lookup(domain) + if len(addresses) > 0 { + return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil + } + } + addresses := getIpOfSslip(domain) + if len(addresses) > 0 { + return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil + } + } + return &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Id: message.Id, + Rcode: mDNS.RcodeNameError, + Response: true, + }, + Question: []mDNS.Question{question}, + }, nil +} + +const ( + ipv4Pattern = `((25[0-5]|2[0-4][0-9]|[0-1]?[0-9]?[0-9])[\.-](25[0-5]|2[0-4][0-9]|[0-1]?[0-9]?[0-9])[\.-](25[0-5]|2[0-4][0-9]|[0-1]?[0-9]?[0-9])[\.-](25[0-5]|2[0-4][0-9]|[0-1]?[0-9]?[0-9])).sslip.io$` + ipv6Pattern = `((([0-9a-fA-F]{1,4}-){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}-){1,7}-|([0-9a-fA-F]{1,4}-){1,6}-[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}-){1,5}(-[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}-){1,4}(-[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}-){1,3}(-[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}-){1,2}(-[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}-((-[0-9a-fA-F]{1,4}){1,6})|-((-[0-9a-fA-F]{1,4}){1,7}|-)|fe80-(-[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|--(ffff(-0{1,4}){0,1}-){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}-){1,4}-((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))).sslip.io$` +) + +var ( + ipv4Regex, _ = regexp.Compile(ipv4Pattern) + ipv6Regex, _ = regexp.Compile(ipv6Pattern) +) + +func getIpOfSslip(sni string) []netip.Addr { + if !strings.HasSuffix(sni, ".sslip.io") { + return nil + } + submatches := ipv4Regex.FindStringSubmatch(sni) + ipstr := "" + if len(submatches) > 1 { + ipstr = strings.ReplaceAll(submatches[1], "-", ".") + + } else { + submatches := ipv6Regex.FindStringSubmatch(sni) + if len(submatches) > 1 { + ipstr = strings.ReplaceAll(submatches[1], "-", ":") + } + } + ip, err := netip.ParseAddr(ipstr) + if err != nil { + return nil + } + return []netip.Addr{ip} + +} diff --git a/dns/transport/hosts/hosts_file.go b/dns/transport/hosts/hosts_file.go new file mode 100644 index 0000000000..af507f012a --- /dev/null +++ b/dns/transport/hosts/hosts_file.go @@ -0,0 +1,112 @@ +package hosts + +import ( + "bufio" + "errors" + "io" + "net/netip" + "os" + "strings" + "sync" + "time" + + E "github.com/sagernet/sing/common/exceptions" + + "github.com/miekg/dns" +) + +const cacheMaxAge = 5 * time.Second + +type File struct { + path string + access sync.Mutex + byName map[string][]netip.Addr + expire time.Time + modTime time.Time + size int64 +} + +func NewFile(path string) *File { + return &File{ + path: path, + } +} + +func NewDefault() (*File, error) { + defaultPathResolved, err := defaultPath() + if err != nil { + return nil, E.Cause(err, "resolve default hosts path") + } + return NewFile(defaultPathResolved), nil +} + +func (f *File) Lookup(name string) []netip.Addr { + f.access.Lock() + defer f.access.Unlock() + f.update() + return f.byName[dns.CanonicalName(name)] +} + +func (f *File) update() { + now := time.Now() + if now.Before(f.expire) && len(f.byName) > 0 { + return + } + stat, err := os.Stat(f.path) + if err != nil { + return + } + if f.modTime.Equal(stat.ModTime()) && f.size == stat.Size() { + f.expire = now.Add(cacheMaxAge) + return + } + byName := make(map[string][]netip.Addr) + file, err := os.Open(f.path) + if err != nil { + return + } + defer file.Close() + reader := bufio.NewReader(file) + var ( + prefix []byte + line []byte + isPrefix bool + ) + for { + line, isPrefix, err = reader.ReadLine() + if err != nil { + if errors.Is(err, io.EOF) { + break + } + return + } + if isPrefix { + prefix = append(prefix, line...) + continue + } else if len(prefix) > 0 { + line = append(prefix, line...) + prefix = nil + } + commentIndex := strings.IndexRune(string(line), '#') + if commentIndex != -1 { + line = line[:commentIndex] + } + fields := strings.Fields(string(line)) + if len(fields) < 2 { + continue + } + var addr netip.Addr + addr, err = netip.ParseAddr(fields[0]) + if err != nil { + continue + } + for index := 1; index < len(fields); index++ { + canonicalName := dns.CanonicalName(fields[index]) + byName[canonicalName] = append(byName[canonicalName], addr) + } + } + f.expire = now.Add(cacheMaxAge) + f.modTime = stat.ModTime() + f.size = stat.Size() + f.byName = byName +} diff --git a/dns/transport/hosts/hosts_test.go b/dns/transport/hosts/hosts_test.go new file mode 100644 index 0000000000..61d20e0c63 --- /dev/null +++ b/dns/transport/hosts/hosts_test.go @@ -0,0 +1,29 @@ +package hosts + +import ( + "net/netip" + "os" + "runtime" + "testing" + + E "github.com/sagernet/sing/common/exceptions" + + "github.com/stretchr/testify/require" +) + +func TestHosts(t *testing.T) { + t.Parallel() + require.Equal(t, []netip.Addr{netip.AddrFrom4([4]byte{127, 0, 0, 1}), netip.IPv6Loopback()}, NewFile("testdata/hosts").Lookup("localhost")) + if runtime.GOOS != "windows" { + defaultPathResolved, err := defaultPath() + if err != nil { + t.Fatal(E.Cause(err, "resolve default hosts path")) + } + content, readErr := os.ReadFile(defaultPathResolved) + require.NoError(t, readErr) + hFile := NewFile(defaultPathResolved) + if len(hFile.Lookup("localhost")) == 0 { + t.Fatal("failed to resolve localhost: ", defaultPathResolved, ": \n", string(content)) + } + } +} diff --git a/dns/transport/hosts/hosts_unix.go b/dns/transport/hosts/hosts_unix.go new file mode 100644 index 0000000000..9c44853194 --- /dev/null +++ b/dns/transport/hosts/hosts_unix.go @@ -0,0 +1,7 @@ +//go:build !windows + +package hosts + +func defaultPath() (string, error) { + return "/etc/hosts", nil +} diff --git a/dns/transport/hosts/hosts_windows.go b/dns/transport/hosts/hosts_windows.go new file mode 100644 index 0000000000..a17e6d2b87 --- /dev/null +++ b/dns/transport/hosts/hosts_windows.go @@ -0,0 +1,16 @@ +package hosts + +import ( + "path/filepath" + "sync" + + "golang.org/x/sys/windows" +) + +var defaultPath = sync.OnceValues(func() (string, error) { + systemDirectory, err := windows.GetSystemDirectory() + if err != nil { + return "", err + } + return filepath.Join(systemDirectory, "Drivers", "etc", "hosts"), nil +}) diff --git a/dns/transport/hosts/testdata/hosts b/dns/transport/hosts/testdata/hosts new file mode 100644 index 0000000000..9ddcc8c137 --- /dev/null +++ b/dns/transport/hosts/testdata/hosts @@ -0,0 +1,2 @@ +127.0.0.1 localhost +::1 localhost diff --git a/dns/transport/https.go b/dns/transport/https.go new file mode 100644 index 0000000000..db89799cdf --- /dev/null +++ b/dns/transport/https.go @@ -0,0 +1,224 @@ +package transport + +import ( + "bytes" + "context" + "errors" + "io" + "net" + "net/http" + "net/url" + "strconv" + "sync" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + sHTTP "github.com/sagernet/sing/protocol/http" + + mDNS "github.com/miekg/dns" + "golang.org/x/net/http2" +) + +const MimeType = "application/dns-message" + +var _ adapter.DNSTransport = (*HTTPSTransport)(nil) + +func RegisterHTTPS(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.RemoteHTTPSDNSServerOptions](registry, C.DNSTypeHTTPS, NewHTTPS) +} + +type HTTPSTransport struct { + dns.TransportAdapter + logger logger.ContextLogger + dialer N.Dialer + destination *url.URL + headers http.Header + transportAccess sync.Mutex + transport *HTTPSTransportWrapper + transportResetAt time.Time +} + +func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteHTTPSDNSServerOptions) (adapter.DNSTransport, error) { + transportDialer, err := dns.NewRemoteDialer(ctx, options.RemoteDNSServerOptions) + if err != nil { + return nil, err + } + tlsOptions := common.PtrValueOrDefault(options.TLS) + tlsOptions.Enabled = true + tlsConfig, err := tls.NewClient(ctx, logger, options.Server, tlsOptions) + if err != nil { + return nil, err + } + if len(tlsConfig.NextProtos()) == 0 { + tlsConfig.SetNextProtos([]string{http2.NextProtoTLS, "http/1.1"}) + } + headers := options.Headers.Build() + host := headers.Get("Host") + if host != "" { + headers.Del("Host") + } else { + if tlsConfig.ServerName() != "" { + host = tlsConfig.ServerName() + } else { + host = options.Server + } + } + destinationURL := url.URL{ + Scheme: "https", + Host: host, + } + if destinationURL.Host == "" { + destinationURL.Host = options.Server + } + if options.ServerPort != 0 && options.ServerPort != 443 { + destinationURL.Host = net.JoinHostPort(destinationURL.Host, strconv.Itoa(int(options.ServerPort))) + } + path := options.Path + if path == "" { + path = "/dns-query" + } + err = sHTTP.URLSetPath(&destinationURL, path) + if err != nil { + return nil, err + } + serverAddr := options.DNSServerAddressOptions.Build() + if serverAddr.Port == 0 { + serverAddr.Port = 443 + } + if !serverAddr.IsValid() { + return nil, E.New("invalid server address: ", serverAddr) + } + return NewHTTPSRaw( + dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTPS, tag, options.RemoteDNSServerOptions), + logger, + transportDialer, + &destinationURL, + headers, + serverAddr, + tlsConfig, + ), nil +} + +func NewHTTPSRaw( + adapter dns.TransportAdapter, + logger log.ContextLogger, + dialer N.Dialer, + destination *url.URL, + headers http.Header, + serverAddr M.Socksaddr, + tlsConfig tls.Config, +) *HTTPSTransport { + if tlsConfig != nil { + dialer = tls.NewDialer(dialer, tlsConfig) + } + return &HTTPSTransport{ + TransportAdapter: adapter, + logger: logger, + dialer: dialer, + destination: destination, + headers: headers, + transport: NewHTTPSTransportWrapper(dialer, serverAddr, destination), + } +} + +func (t *HTTPSTransport) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + return dialer.InitializeDetour(t.dialer) +} + +func (t *HTTPSTransport) Close() error { + t.Reset() + return nil +} + +func (t *HTTPSTransport) Reset() { + t.transportAccess.Lock() + defer t.transportAccess.Unlock() + t.transport.CloseIdleConnections() + t.transport = t.transport.Clone() +} + +func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + startAt := time.Now() + response, err := t.exchange(ctx, message) + if err != nil { + if errors.Is(err, context.DeadlineExceeded) { + t.transportAccess.Lock() + defer t.transportAccess.Unlock() + if t.transportResetAt.After(startAt) { + return nil, err + } + t.transport.CloseIdleConnections() + t.transport = t.transport.Clone() + t.transportResetAt = time.Now() + } + return nil, err + } + return response, nil +} + +func (t *HTTPSTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + exMessage := *message + exMessage.Id = 0 + exMessage.Compress = true + requestBuffer := buf.NewSize(1 + message.Len()) + rawMessage, err := exMessage.PackBuffer(requestBuffer.FreeBytes()) + if err != nil { + requestBuffer.Release() + return nil, err + } + request, err := http.NewRequestWithContext(ctx, http.MethodPost, t.destination.String(), bytes.NewReader(rawMessage)) + if err != nil { + requestBuffer.Release() + return nil, err + } + request.Header = t.headers.Clone() + request.Header.Set("Content-Type", MimeType) + request.Header.Set("Accept", MimeType) + t.transportAccess.Lock() + currentTransport := t.transport + t.transportAccess.Unlock() + response, err := currentTransport.RoundTrip(request) + requestBuffer.Release() + if err != nil { + return nil, err + } + defer response.Body.Close() + if response.StatusCode != http.StatusOK { + return nil, E.New("unexpected status: ", response.Status) + } + var responseMessage mDNS.Msg + if response.ContentLength > 0 { + responseBuffer := buf.NewSize(int(response.ContentLength)) + defer responseBuffer.Release() + _, err = responseBuffer.ReadFullFrom(response.Body, int(response.ContentLength)) + if err != nil { + return nil, err + } + err = responseMessage.Unpack(responseBuffer.Bytes()) + } else { + rawMessage, err = io.ReadAll(response.Body) + if err != nil { + return nil, err + } + err = responseMessage.Unpack(rawMessage) + } + if err != nil { + return nil, err + } + return &responseMessage, nil +} diff --git a/dns/transport/https_transport.go b/dns/transport/https_transport.go new file mode 100644 index 0000000000..c823718aa1 --- /dev/null +++ b/dns/transport/https_transport.go @@ -0,0 +1,90 @@ +package transport + +import ( + "context" + "errors" + "net" + "net/http" + "net/url" + "sync/atomic" + + "github.com/sagernet/sing-box/common/tls" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + "golang.org/x/net/http2" +) + +var errFallback = E.New("fallback to HTTP/1.1") + +type HTTPSTransportWrapper struct { + http2Transport *http2.Transport + httpTransport *http.Transport + fallback *atomic.Bool +} + +func NewHTTPSTransportWrapper(dialer N.Dialer, serverAddr M.Socksaddr, destination *url.URL) *HTTPSTransportWrapper { + var fallback atomic.Bool + if destination.Scheme == "http" { + // plain HTTP DoH used by Tailscale + fallback.Store(true) + } + return &HTTPSTransportWrapper{ + http2Transport: &http2.Transport{ + DialTLSContext: func(ctx context.Context, _, _ string, _ *tls.STDConfig) (net.Conn, error) { + resultConn, err := dialer.DialContext(ctx, N.NetworkTCP, serverAddr) + if err != nil { + return nil, err + } + if tlsConn, isTLSConn := resultConn.(tls.Conn); isTLSConn { + state := tlsConn.ConnectionState() + if state.NegotiatedProtocol != http2.NextProtoTLS { + tlsConn.Close() + fallback.Store(true) + return nil, errFallback + } + } + return resultConn, nil + }, + }, + httpTransport: &http.Transport{ + DialContext: func(ctx context.Context, _, addr string) (net.Conn, error) { + return dialer.DialContext(ctx, N.NetworkTCP, serverAddr) + }, + DialTLSContext: func(ctx context.Context, _, _ string) (net.Conn, error) { + return dialer.DialContext(ctx, N.NetworkTCP, serverAddr) + }, + }, + fallback: &fallback, + } +} + +func (h *HTTPSTransportWrapper) RoundTrip(request *http.Request) (*http.Response, error) { + if h.fallback.Load() { + return h.httpTransport.RoundTrip(request) + } + response, err := h.http2Transport.RoundTrip(request) + if err != nil { + if errors.Is(err, errFallback) { + return h.httpTransport.RoundTrip(request) + } + return nil, err + } + return response, nil +} + +func (h *HTTPSTransportWrapper) CloseIdleConnections() { + h.http2Transport.CloseIdleConnections() + h.httpTransport.CloseIdleConnections() +} + +func (h *HTTPSTransportWrapper) Clone() *HTTPSTransportWrapper { + return &HTTPSTransportWrapper{ + httpTransport: h.httpTransport, + http2Transport: &http2.Transport{ + DialTLSContext: h.http2Transport.DialTLSContext, + }, + fallback: h.fallback, + } +} diff --git a/dns/transport/local/local.go b/dns/transport/local/local.go new file mode 100644 index 0000000000..34897e128e --- /dev/null +++ b/dns/transport/local/local.go @@ -0,0 +1,182 @@ +package local + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/dns/transport/hosts" + "github.com/sagernet/sing-box/dns/transport/mdns" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" + + mDNS "github.com/miekg/dns" +) + +func RegisterTransport(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.LocalDNSServerOptions](registry, C.DNSTypeLocal, NewTransport) +} + +var ( + _ adapter.DNSTransport = (*Transport)(nil) + _ adapter.DNSTransportWithPreferredDomain = (*Transport)(nil) +) + +type Transport struct { + dns.TransportAdapter + ctx context.Context + logger logger.ContextLogger + hosts *hosts.File + dialer N.Dialer + preferGo bool + fallback bool + resolved ResolvedResolver + mdnsTransport adapter.DNSTransport + dhcpTransport dhcpTransport + neighborResolver adapter.NeighborResolver + neighborSuffixes []string +} + +type dhcpTransport interface { + adapter.DNSTransport + Fetch() []M.Socksaddr + Exchange0(ctx context.Context, message *mDNS.Msg, servers []M.Socksaddr) (*mDNS.Msg, error) +} + +func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) { + transportDialer, err := dns.NewLocalDialer(ctx, options) + if err != nil { + return nil, err + } + suffixes, err := buildNeighborMatchers(options.NeighborDomain) + if err != nil { + return nil, err + } + return &Transport{ + TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options), + ctx: ctx, + logger: logger, + dialer: transportDialer, + preferGo: options.PreferGo, + neighborSuffixes: suffixes, + }, nil +} + +func (t *Transport) Start(stage adapter.StartStage) error { + switch stage { + case adapter.StartStateInitialize: + defaultHosts, err := hosts.NewDefault() + if err != nil { + t.logger.Warn(err) + } else { + t.hosts = defaultHosts + } + if !t.preferGo && isSystemdResolvedManaged() { + resolvedResolver, err := NewResolvedResolver(t.ctx, t.logger) + if err == nil { + err = resolvedResolver.Start() + if err == nil { + t.resolved = resolvedResolver + } else { + t.logger.Warn(E.Cause(err, "initialize resolved resolver")) + } + } + } + case adapter.StartStateStart: + if C.IsDarwin { + inboundManager := service.FromContext[adapter.InboundManager](t.ctx) + for _, inbound := range inboundManager.Inbounds() { + if inbound.Type() == C.TypeTun { + t.fallback = true + break + } + } + if t.fallback { + t.dhcpTransport = newDHCPTransport(t.TransportAdapter, log.ContextWithOverrideLevel(t.ctx, log.LevelDebug), t.dialer, t.logger) + } + } else { + t.mdnsTransport = mdns.NewRawTransport(t.TransportAdapter, t.ctx, t.logger) + } + router := service.FromContext[adapter.Router](t.ctx) + if router != nil { + t.neighborResolver = router.NeighborResolver() + } + fallthrough + default: + if t.dhcpTransport != nil { + err := t.dhcpTransport.Start(stage) + if err != nil { + return err + } + } + if t.mdnsTransport != nil { + err := t.mdnsTransport.Start(stage) + if err != nil { + return err + } + } + } + return nil +} + +func (t *Transport) Close() error { + return common.Close(t.resolved, t.dhcpTransport, t.mdnsTransport) +} + +func (t *Transport) Reset() { + if t.dhcpTransport != nil { + t.dhcpTransport.Reset() + } + if t.mdnsTransport != nil { + t.mdnsTransport.Reset() + } +} + +func (t *Transport) PreferredDomain(domain string) bool { + if t.hosts != nil { + if len(t.hosts.Lookup(dns.FqdnToDomain(domain))) > 0 { + return true + } + } + return t.hasNeighborHost(domain) || mdns.IsLocalDomain(domain) +} + +func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + question := message.Question[0] + if t.hosts != nil && (question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA) { + addresses := t.hosts.Lookup(dns.FqdnToDomain(question.Name)) + if len(addresses) > 0 { + return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil + } + } + response := t.lookupNeighbor(message) + if response != nil { + return response, nil + } + if mdns.IsLocalDomain(question.Name) { + if C.IsDarwin { + return t.systemExchange(ctx, message) + } + return t.mdnsTransport.Exchange(ctx, message) + } + if t.resolved != nil { + return t.resolved.Exchange(ctx, message) + } + if t.dhcpTransport != nil { + servers := t.dhcpTransport.Fetch() + if len(servers) > 0 { + return t.dhcpTransport.Exchange0(ctx, message, servers) + } + } + if t.fallback { + return t.systemExchange(ctx, message) + } + return t.exchange(ctx, message, question.Name) +} diff --git a/dns/transport/local/local_darwin_cgo.go b/dns/transport/local/local_darwin_cgo.go new file mode 100644 index 0000000000..de2ec56a4b --- /dev/null +++ b/dns/transport/local/local_darwin_cgo.go @@ -0,0 +1,118 @@ +//go:build darwin + +package local + +/* +#include +#include +#include + +static int cgo_dns_search(const char *name, int class, int type, + unsigned char *answer, int anslen, int *out_h_errno) { + dns_handle_t handle = (dns_handle_t)dns_open(NULL); + if (handle == NULL) { + *out_h_errno = NO_RECOVERY; + return -1; + } + struct sockaddr_storage from; + uint32_t fromlen = sizeof(from); + h_errno = 0; + int n = dns_search(handle, name, class, type, (char *)answer, anslen, + (struct sockaddr *)&from, &fromlen); + *out_h_errno = h_errno; + dns_free(handle); + return n; +} +*/ +import "C" + +import ( + "context" + "errors" + "unsafe" + + "github.com/sagernet/sing-box/dns" + E "github.com/sagernet/sing/common/exceptions" + + mDNS "github.com/miekg/dns" +) + +const ( + darwinResolverHostNotFound = 1 + darwinResolverTryAgain = 2 + darwinResolverNoRecovery = 3 + darwinResolverNoData = 4 +) + +func darwinLookupSystemDNS(name string, class, qtype int) (*mDNS.Msg, error) { + cName := C.CString(name) + defer C.free(unsafe.Pointer(cName)) + + answer := make([]byte, 4096) + var hErrno C.int + n := C.cgo_dns_search(cName, C.int(class), C.int(qtype), + (*C.uchar)(unsafe.Pointer(&answer[0])), C.int(len(answer)), + &hErrno) + if n <= 0 { + return nil, darwinResolverHErrno(name, int(hErrno)) + } + var response mDNS.Msg + err := response.Unpack(answer[:int(n)]) + if err != nil { + return nil, E.Cause(err, "unpack dns_search response") + } + return &response, nil +} + +func darwinResolverHErrno(name string, hErrno int) error { + switch hErrno { + case darwinResolverHostNotFound: + return dns.RcodeNameError + case darwinResolverNoData: + return dns.RcodeSuccess + case darwinResolverTryAgain, darwinResolverNoRecovery: + return dns.RcodeServerFailure + default: + return E.New("dns_search: unknown h_errno ", hErrno, " for ", name) + } +} + +func (t *Transport) systemExchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + question := message.Question[0] + type resolvResult struct { + response *mDNS.Msg + err error + } + resultCh := make(chan resolvResult, 1) + go func() { + response, err := darwinLookupSystemDNS(question.Name, int(question.Qclass), int(question.Qtype)) + resultCh <- resolvResult{response, err} + }() + var result resolvResult + select { + case <-ctx.Done(): + return nil, ctx.Err() + case result = <-resultCh: + } + if result.err != nil { + var rcodeError dns.RcodeError + if errors.As(result.err, &rcodeError) { + return dns.FixedResponseStatus(message, int(rcodeError)), nil + } + return nil, result.err + } + result.response.Id = message.Id + // Workaround for a bug in Apple libresolv: res_query_mDNSResponder + // (libresolv/res_query.c), used when the resolver has + // DNS_FLAG_FORWARD_TO_MDNSRESPONDER set (typical inside a Network + // Extension), writes: + // + // ans->qr = 1; + // ans->qr = htons(ans->qr); + // + // HEADER.qr is a 1-bit bitfield (), so + // htons(1) == 0x0100 gets truncated back to 0, clearing the QR bit. + // Force it on so downstream clients see a valid response. + result.response.Response = true + return result.response, nil +} diff --git a/dns/transport/local/local_darwin_stun.go b/dns/transport/local/local_darwin_stun.go new file mode 100644 index 0000000000..b99833c262 --- /dev/null +++ b/dns/transport/local/local_darwin_stun.go @@ -0,0 +1,15 @@ +//go:build darwin && !cgo + +package local + +import ( + "context" + + E "github.com/sagernet/sing/common/exceptions" + + mDNS "github.com/miekg/dns" +) + +func (t *Transport) systemExchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + return nil, E.New(`local DNS server requires CGO on darwin, rebuild with CGO_ENABLED=1`) +} diff --git a/dns/transport/local/local_dhcp.go b/dns/transport/local/local_dhcp.go new file mode 100644 index 0000000000..bf77ed252d --- /dev/null +++ b/dns/transport/local/local_dhcp.go @@ -0,0 +1,16 @@ +//go:build with_dhcp + +package local + +import ( + "context" + + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/dns/transport/dhcp" + "github.com/sagernet/sing-box/log" + N "github.com/sagernet/sing/common/network" +) + +func newDHCPTransport(transportAdapter dns.TransportAdapter, ctx context.Context, dialer N.Dialer, logger log.ContextLogger) dhcpTransport { + return dhcp.NewRawTransport(transportAdapter, ctx, dialer, logger) +} diff --git a/dns/transport/local/local_neighbor.go b/dns/transport/local/local_neighbor.go new file mode 100644 index 0000000000..3dd394a8b6 --- /dev/null +++ b/dns/transport/local/local_neighbor.go @@ -0,0 +1,68 @@ +package local + +import ( + "strings" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + E "github.com/sagernet/sing/common/exceptions" + + mDNS "github.com/miekg/dns" +) + +func buildNeighborMatchers(domains []string) ([]string, error) { + if len(domains) == 0 { + return nil, nil + } + var suffixes []string + for _, domain := range domains { + if !strings.HasPrefix(domain, ".") { + return nil, E.New("neighbor_domain entry must start with '.': ", domain) + } + suffixes = append(suffixes, mDNS.CanonicalName(domain)) + } + return suffixes, nil +} + +func (t *Transport) lookupNeighbor(message *mDNS.Msg) *mDNS.Msg { + if t.neighborResolver == nil { + return nil + } + question := message.Question[0] + if question.Qtype != mDNS.TypeA && question.Qtype != mDNS.TypeAAAA { + return nil + } + host := extractNeighborHost(mDNS.CanonicalName(question.Name), t.neighborSuffixes) + if host == "" { + return nil + } + addresses := t.neighborResolver.LookupAddresses(host) + if len(addresses) == 0 { + return nil + } + return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL) +} + +func (t *Transport) hasNeighborHost(domain string) bool { + if t.neighborResolver == nil { + return false + } + host := extractNeighborHost(domain, t.neighborSuffixes) + if host == "" { + return false + } + return len(t.neighborResolver.LookupAddresses(host)) > 0 +} + +func extractNeighborHost(canonical string, suffixes []string) string { + for _, suffix := range suffixes { + if !strings.HasSuffix(canonical, suffix) || len(canonical) <= len(suffix) { + continue + } + host := canonical[:len(canonical)-len(suffix)] + if !strings.ContainsRune(host, '.') { + return host + } + } + return "" +} diff --git a/dns/transport/local/local_nodhcp.go b/dns/transport/local/local_nodhcp.go new file mode 100644 index 0000000000..7893d416f6 --- /dev/null +++ b/dns/transport/local/local_nodhcp.go @@ -0,0 +1,15 @@ +//go:build !with_dhcp + +package local + +import ( + "context" + + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/log" + N "github.com/sagernet/sing/common/network" +) + +func newDHCPTransport(transportAdapter dns.TransportAdapter, ctx context.Context, dialer N.Dialer, logger log.ContextLogger) dhcpTransport { + return nil +} diff --git a/dns/transport/local/local_other.go b/dns/transport/local/local_other.go new file mode 100644 index 0000000000..9bb3d7777a --- /dev/null +++ b/dns/transport/local/local_other.go @@ -0,0 +1,14 @@ +//go:build !darwin + +package local + +import ( + "context" + "os" + + mDNS "github.com/miekg/dns" +) + +func (t *Transport) systemExchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + return nil, os.ErrInvalid +} diff --git a/dns/transport/local/local_resolved.go b/dns/transport/local/local_resolved.go new file mode 100644 index 0000000000..e0128d6de5 --- /dev/null +++ b/dns/transport/local/local_resolved.go @@ -0,0 +1,13 @@ +package local + +import ( + "context" + + mDNS "github.com/miekg/dns" +) + +type ResolvedResolver interface { + Start() error + Close() error + Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) +} diff --git a/dns/transport/local/local_resolved_linux.go b/dns/transport/local/local_resolved_linux.go new file mode 100644 index 0000000000..fc3ca2b7a9 --- /dev/null +++ b/dns/transport/local/local_resolved_linux.go @@ -0,0 +1,501 @@ +package local + +import ( + "bufio" + "context" + "errors" + "net/netip" + "os" + "strings" + "sync" + "sync/atomic" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + dnsTransport "github.com/sagernet/sing-box/dns/transport" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/service/resolved" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service" + + "github.com/godbus/dbus/v5" + mDNS "github.com/miekg/dns" +) + +func isSystemdResolvedManaged() bool { + resolvContent, err := os.Open("/etc/resolv.conf") + if err != nil { + return false + } + defer resolvContent.Close() + scanner := bufio.NewScanner(resolvContent) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" || line[0] != '#' { + return false + } + if strings.Contains(line, "systemd-resolved") { + return true + } + } + return false +} + +type DBusResolvedResolver struct { + ctx context.Context + logger logger.ContextLogger + interfaceMonitor tun.DefaultInterfaceMonitor + interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback] + systemBus *dbus.Conn + savedServerSet atomic.Pointer[resolvedServerSet] + closeOnce sync.Once +} + +type resolvedServerSet struct { + servers []resolvedServer +} + +type resolvedServer struct { + primaryTransport adapter.DNSTransport + fallbackTransport adapter.DNSTransport +} + +type resolvedServerSpecification struct { + address netip.Addr + port uint16 + serverName string +} + +func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (ResolvedResolver, error) { + interfaceMonitor := service.FromContext[adapter.NetworkManager](ctx).InterfaceMonitor() + if interfaceMonitor == nil { + return nil, os.ErrInvalid + } + systemBus, err := dbus.SystemBus() + if err != nil { + return nil, err + } + return &DBusResolvedResolver{ + ctx: ctx, + logger: logger, + interfaceMonitor: interfaceMonitor, + systemBus: systemBus, + }, nil +} + +func (t *DBusResolvedResolver) Start() error { + t.updateStatus() + t.interfaceCallback = t.interfaceMonitor.RegisterCallback(t.updateDefaultInterface) + err := t.systemBus.BusObject().AddMatchSignal( + "org.freedesktop.DBus", + "NameOwnerChanged", + dbus.WithMatchSender("org.freedesktop.DBus"), + dbus.WithMatchArg(0, "org.freedesktop.resolve1"), + ).Err + if err != nil { + return E.Cause(err, "configure resolved restart listener") + } + err = t.systemBus.BusObject().AddMatchSignal( + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + dbus.WithMatchSender("org.freedesktop.resolve1"), + dbus.WithMatchArg(0, "org.freedesktop.resolve1.Manager"), + ).Err + if err != nil { + return E.Cause(err, "configure resolved properties listener") + } + go t.loopUpdateStatus() + return nil +} + +func (t *DBusResolvedResolver) Close() error { + var closeErr error + t.closeOnce.Do(func() { + serverSet := t.savedServerSet.Swap(nil) + if serverSet != nil { + closeErr = serverSet.Close() + } + if t.interfaceCallback != nil { + t.interfaceMonitor.UnregisterCallback(t.interfaceCallback) + } + if t.systemBus != nil { + _ = t.systemBus.Close() + } + }) + return closeErr +} + +func (t *DBusResolvedResolver) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + serverSet := t.savedServerSet.Load() + if serverSet == nil { + var err error + serverSet, err = t.checkResolved(context.Background()) + if err != nil { + return nil, err + } + previousServerSet := t.savedServerSet.Swap(serverSet) + if previousServerSet != nil { + _ = previousServerSet.Close() + } + } + response, err := t.exchangeServerSet(ctx, message, serverSet) + if err == nil { + return response, nil + } + t.updateStatus() + refreshedServerSet := t.savedServerSet.Load() + if refreshedServerSet == nil || refreshedServerSet == serverSet { + return nil, err + } + return t.exchangeServerSet(ctx, message, refreshedServerSet) +} + +func (t *DBusResolvedResolver) loopUpdateStatus() { + signalChan := make(chan *dbus.Signal, 1) + t.systemBus.Signal(signalChan) + for signal := range signalChan { + switch signal.Name { + case "org.freedesktop.DBus.NameOwnerChanged": + if len(signal.Body) != 3 { + continue + } + newOwner, loaded := signal.Body[2].(string) + if !loaded || newOwner == "" { + continue + } + t.updateStatus() + case "org.freedesktop.DBus.Properties.PropertiesChanged": + if !shouldUpdateResolvedServerSet(signal) { + continue + } + t.updateStatus() + } + } +} + +func (t *DBusResolvedResolver) updateStatus() { + serverSet, err := t.checkResolved(context.Background()) + oldServerSet := t.savedServerSet.Swap(serverSet) + if oldServerSet != nil { + _ = oldServerSet.Close() + } + if err != nil { + var dbusErr dbus.Error + if !errors.As(err, &dbusErr) || dbusErr.Name != "org.freedesktop.DBus.Error.NameHasNoOwner" { + t.logger.Debug(E.Cause(err, "systemd-resolved service unavailable")) + } + if oldServerSet != nil { + t.logger.Debug("systemd-resolved service is gone") + } + return + } else if oldServerSet == nil { + t.logger.Debug("using systemd-resolved service as resolver") + } +} + +func (t *DBusResolvedResolver) exchangeServerSet(ctx context.Context, message *mDNS.Msg, serverSet *resolvedServerSet) (*mDNS.Msg, error) { + if serverSet == nil || len(serverSet.servers) == 0 { + return nil, E.New("link has no DNS servers configured") + } + var lastError error + for _, server := range serverSet.servers { + response, err := server.primaryTransport.Exchange(ctx, message) + if err != nil && server.fallbackTransport != nil { + response, err = server.fallbackTransport.Exchange(ctx, message) + } + if err != nil { + lastError = err + continue + } + return response, nil + } + return nil, lastError +} + +func (t *DBusResolvedResolver) checkResolved(ctx context.Context) (*resolvedServerSet, error) { + dbusObject := t.systemBus.Object("org.freedesktop.resolve1", "/org/freedesktop/resolve1") + err := dbusObject.Call("org.freedesktop.DBus.Peer.Ping", 0).Err + if err != nil { + return nil, err + } + defaultInterface := t.interfaceMonitor.DefaultInterface() + if defaultInterface == nil { + return nil, E.New("missing default interface") + } + call := dbusObject.(*dbus.Object).CallWithContext( + ctx, + "org.freedesktop.resolve1.Manager.GetLink", + 0, + int32(defaultInterface.Index), + ) + if call.Err != nil { + return nil, call.Err + } + var linkPath dbus.ObjectPath + err = call.Store(&linkPath) + if err != nil { + return nil, err + } + linkObject := t.systemBus.Object("org.freedesktop.resolve1", linkPath) + if linkObject == nil { + return nil, E.New("missing link object for default interface") + } + dnsOverTLSMode, err := loadResolvedLinkDNSOverTLS(linkObject) + if err != nil { + return nil, err + } + linkDNSEx, err := loadResolvedLinkDNSEx(linkObject) + if err != nil { + return nil, err + } + linkDNS, err := loadResolvedLinkDNS(linkObject) + if err != nil { + return nil, err + } + if len(linkDNSEx) == 0 && len(linkDNS) == 0 { + for _, inbound := range service.FromContext[adapter.InboundManager](t.ctx).Inbounds() { + if inbound.Type() == C.TypeTun { + return nil, E.New("No appropriate name servers or networks for name found") + } + } + return nil, E.New("link has no DNS servers configured") + } + serverDialer, err := dialer.NewDefault(t.ctx, option.DialerOptions{ + BindInterface: defaultInterface.Name, + UDPFragmentDefault: true, + }) + if err != nil { + return nil, err + } + var serverSpecifications []resolvedServerSpecification + if len(linkDNSEx) > 0 { + for _, entry := range linkDNSEx { + serverSpecification, loaded := buildResolvedServerSpecification(defaultInterface.Name, entry.Address, entry.Port, entry.Name) + if !loaded { + continue + } + serverSpecifications = append(serverSpecifications, serverSpecification) + } + } else { + for _, entry := range linkDNS { + serverSpecification, loaded := buildResolvedServerSpecification(defaultInterface.Name, entry.Address, 0, "") + if !loaded { + continue + } + serverSpecifications = append(serverSpecifications, serverSpecification) + } + } + if len(serverSpecifications) == 0 { + return nil, E.New("no valid DNS servers on link") + } + serverSet := &resolvedServerSet{ + servers: make([]resolvedServer, 0, len(serverSpecifications)), + } + for _, serverSpecification := range serverSpecifications { + server, createErr := t.createResolvedServer(serverDialer, dnsOverTLSMode, serverSpecification) + if createErr != nil { + _ = serverSet.Close() + return nil, createErr + } + serverSet.servers = append(serverSet.servers, server) + } + return serverSet, nil +} + +func (t *DBusResolvedResolver) createResolvedServer(serverDialer N.Dialer, dnsOverTLSMode string, serverSpecification resolvedServerSpecification) (resolvedServer, error) { + if dnsOverTLSMode == "yes" { + primaryTransport, err := t.createResolvedTransport(serverDialer, serverSpecification, true) + if err != nil { + return resolvedServer{}, err + } + return resolvedServer{ + primaryTransport: primaryTransport, + }, nil + } + if dnsOverTLSMode == "opportunistic" { + primaryTransport, err := t.createResolvedTransport(serverDialer, serverSpecification, true) + if err != nil { + return resolvedServer{}, err + } + fallbackTransport, err := t.createResolvedTransport(serverDialer, serverSpecification, false) + if err != nil { + _ = primaryTransport.Close() + return resolvedServer{}, err + } + return resolvedServer{ + primaryTransport: primaryTransport, + fallbackTransport: fallbackTransport, + }, nil + } + primaryTransport, err := t.createResolvedTransport(serverDialer, serverSpecification, false) + if err != nil { + return resolvedServer{}, err + } + return resolvedServer{ + primaryTransport: primaryTransport, + }, nil +} + +func (t *DBusResolvedResolver) createResolvedTransport(serverDialer N.Dialer, serverSpecification resolvedServerSpecification, useTLS bool) (adapter.DNSTransport, error) { + serverAddress := M.SocksaddrFrom(serverSpecification.address, resolvedServerPort(serverSpecification.port, useTLS)) + if useTLS { + tlsAddress := serverSpecification.address + if tlsAddress.Zone() != "" { + tlsAddress = tlsAddress.WithZone("") + } + serverName := serverSpecification.serverName + if serverName == "" { + serverName = tlsAddress.String() + } + tlsConfig, err := tls.NewClient(t.ctx, t.logger, tlsAddress.String(), option.OutboundTLSOptions{ + Enabled: true, + ServerName: serverName, + }) + if err != nil { + return nil, err + } + serverTransport := dnsTransport.NewTLSRaw(t.logger, dns.NewTransportAdapter(C.DNSTypeTLS, "", nil), serverDialer, serverAddress, tlsConfig) + err = serverTransport.Start(adapter.StartStateStart) + if err != nil { + _ = serverTransport.Close() + return nil, err + } + return serverTransport, nil + } + serverTransport := dnsTransport.NewUDPRaw(t.logger, dns.NewTransportAdapter(C.DNSTypeUDP, "", nil), serverDialer, serverAddress) + err := serverTransport.Start(adapter.StartStateStart) + if err != nil { + _ = serverTransport.Close() + return nil, err + } + return serverTransport, nil +} + +func (s *resolvedServerSet) Close() error { + var errors []error + for _, server := range s.servers { + errors = append(errors, server.primaryTransport.Close()) + if server.fallbackTransport != nil { + errors = append(errors, server.fallbackTransport.Close()) + } + } + return E.Errors(errors...) +} + +func buildResolvedServerSpecification(interfaceName string, rawAddress []byte, port uint16, serverName string) (resolvedServerSpecification, bool) { + address, loaded := netip.AddrFromSlice(rawAddress) + if !loaded { + return resolvedServerSpecification{}, false + } + if address.Is6() && address.IsLinkLocalUnicast() && address.Zone() == "" { + address = address.WithZone(interfaceName) + } + return resolvedServerSpecification{ + address: address, + port: port, + serverName: serverName, + }, true +} + +func resolvedServerPort(port uint16, useTLS bool) uint16 { + if port > 0 { + return port + } + if useTLS { + return 853 + } + return 53 +} + +func loadResolvedLinkDNS(linkObject dbus.BusObject) ([]resolved.LinkDNS, error) { + dnsProperty, err := linkObject.GetProperty("org.freedesktop.resolve1.Link.DNS") + if err != nil { + if isResolvedUnknownPropertyError(err) { + return nil, nil + } + return nil, err + } + var linkDNS []resolved.LinkDNS + err = dnsProperty.Store(&linkDNS) + if err != nil { + return nil, err + } + return linkDNS, nil +} + +func loadResolvedLinkDNSEx(linkObject dbus.BusObject) ([]resolved.LinkDNSEx, error) { + dnsProperty, err := linkObject.GetProperty("org.freedesktop.resolve1.Link.DNSEx") + if err != nil { + if isResolvedUnknownPropertyError(err) { + return nil, nil + } + return nil, err + } + var linkDNSEx []resolved.LinkDNSEx + err = dnsProperty.Store(&linkDNSEx) + if err != nil { + return nil, err + } + return linkDNSEx, nil +} + +func loadResolvedLinkDNSOverTLS(linkObject dbus.BusObject) (string, error) { + dnsOverTLSProperty, err := linkObject.GetProperty("org.freedesktop.resolve1.Link.DNSOverTLS") + if err != nil { + if isResolvedUnknownPropertyError(err) { + return "", nil + } + return "", err + } + var dnsOverTLSMode string + err = dnsOverTLSProperty.Store(&dnsOverTLSMode) + if err != nil { + return "", err + } + return dnsOverTLSMode, nil +} + +func isResolvedUnknownPropertyError(err error) bool { + var dbusError dbus.Error + return errors.As(err, &dbusError) && dbusError.Name == "org.freedesktop.DBus.Error.UnknownProperty" +} + +func shouldUpdateResolvedServerSet(signal *dbus.Signal) bool { + if len(signal.Body) != 3 { + return true + } + changedProperties, loaded := signal.Body[1].(map[string]dbus.Variant) + if !loaded { + return true + } + for propertyName := range changedProperties { + switch propertyName { + case "DNS", "DNSEx", "DNSOverTLS": + return true + } + } + invalidatedProperties, loaded := signal.Body[2].([]string) + if !loaded { + return true + } + for _, propertyName := range invalidatedProperties { + switch propertyName { + case "DNS", "DNSEx", "DNSOverTLS": + return true + } + } + return false +} + +func (t *DBusResolvedResolver) updateDefaultInterface(defaultInterface *control.Interface, flags int) { + t.updateStatus() +} diff --git a/dns/transport/local/local_resolved_stub.go b/dns/transport/local/local_resolved_stub.go new file mode 100644 index 0000000000..e3bf843226 --- /dev/null +++ b/dns/transport/local/local_resolved_stub.go @@ -0,0 +1,19 @@ +//go:build !linux + +//nolint:unused +package local + +import ( + "context" + "os" + + "github.com/sagernet/sing/common/logger" +) + +func isSystemdResolvedManaged() bool { + return false +} + +func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (ResolvedResolver, error) { + return nil, os.ErrInvalid +} diff --git a/dns/transport/local/local_shared.go b/dns/transport/local/local_shared.go new file mode 100644 index 0000000000..070409119c --- /dev/null +++ b/dns/transport/local/local_shared.go @@ -0,0 +1,183 @@ +package local + +import ( + "context" + "errors" + "math/rand" + "syscall" + "time" + + "github.com/sagernet/sing-box/dns/transport" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + mDNS "github.com/miekg/dns" +) + +func (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, domain string) (*mDNS.Msg, error) { + systemConfig := getSystemDNSConfig(t.ctx) + if systemConfig.singleRequest || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) { + return t.exchangeSingleRequest(ctx, systemConfig, message, domain) + } else { + return t.exchangeParallel(ctx, systemConfig, message, domain) + } +} + +func (t *Transport) exchangeSingleRequest(ctx context.Context, systemConfig *dnsConfig, message *mDNS.Msg, domain string) (*mDNS.Msg, error) { + var lastErr error + for _, fqdn := range systemConfig.nameList(domain) { + response, err := t.tryOneName(ctx, systemConfig, fqdn, message) + if err != nil { + lastErr = err + continue + } + return response, nil + } + return nil, lastErr +} + +func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfig, message *mDNS.Msg, domain string) (*mDNS.Msg, error) { + returned := make(chan struct{}) + defer close(returned) + type queryResult struct { + response *mDNS.Msg + err error + } + results := make(chan queryResult) + startRacer := func(ctx context.Context, fqdn string) { + response, err := t.tryOneName(ctx, systemConfig, fqdn, message) + select { + case results <- queryResult{response, err}: + case <-returned: + } + } + queryCtx, queryCancel := context.WithCancel(ctx) + defer queryCancel() + var nameCount int + for _, fqdn := range systemConfig.nameList(domain) { + nameCount++ + go startRacer(queryCtx, fqdn) + } + var errors []error + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + case result := <-results: + if result.err == nil { + return result.response, nil + } + errors = append(errors, result.err) + if len(errors) == nameCount { + return nil, E.Errors(errors...) + } + } + } +} + +func (t *Transport) tryOneName(ctx context.Context, config *dnsConfig, fqdn string, message *mDNS.Msg) (*mDNS.Msg, error) { + serverOffset := config.serverOffset() + sLen := uint32(len(config.servers)) + var lastErr error + for i := 0; i < config.attempts; i++ { + for j := range sLen { + server := config.servers[(serverOffset+j)%sLen] + question := message.Question[0] + question.Name = fqdn + response, err := t.exchangeOne(ctx, M.ParseSocksaddr(server), question, config.timeout, config.useTCP, config.trustAD) + if err != nil { + lastErr = err + continue + } + return response, nil + } + } + return nil, E.Cause(lastErr, fqdn) +} + +func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question, timeout time.Duration, useTCP, ad bool) (*mDNS.Msg, error) { + if server.Port == 0 { + server.Port = 53 + } + request := &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Id: uint16(rand.Uint32()), + RecursionDesired: true, + AuthenticatedData: ad, + }, + Question: []mDNS.Question{question}, + Compress: true, + } + request.SetEdns0(buf.UDPBufferSize, false) + if !useTCP { + return t.exchangeUDP(ctx, server, request, timeout) + } else { + return t.exchangeTCP(ctx, server, request, timeout) + } +} + +func (t *Transport) exchangeUDP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg, timeout time.Duration) (*mDNS.Msg, error) { + conn, err := t.dialer.DialContext(ctx, N.NetworkUDP, server) + if err != nil { + return nil, err + } + defer conn.Close() + if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() { + newDeadline := time.Now().Add(timeout) + if deadline.After(newDeadline) { + deadline = newDeadline + } + conn.SetDeadline(deadline) + } + buffer := buf.Get(buf.UDPBufferSize) + defer buf.Put(buffer) + rawMessage, err := request.PackBuffer(buffer) + if err != nil { + return nil, E.Cause(err, "pack request") + } + _, err = conn.Write(rawMessage) + if err != nil { + if errors.Is(err, syscall.EMSGSIZE) { + return t.exchangeTCP(ctx, server, request, timeout) + } + return nil, E.Cause(err, "write request") + } + n, err := conn.Read(buffer) + if err != nil { + if errors.Is(err, syscall.EMSGSIZE) { + return t.exchangeTCP(ctx, server, request, timeout) + } + return nil, E.Cause(err, "read response") + } + var response mDNS.Msg + err = response.Unpack(buffer[:n]) + if err != nil { + return nil, E.Cause(err, "unpack response") + } + if response.Truncated { + return t.exchangeTCP(ctx, server, request, timeout) + } + return &response, nil +} + +func (t *Transport) exchangeTCP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg, timeout time.Duration) (*mDNS.Msg, error) { + conn, err := t.dialer.DialContext(ctx, N.NetworkTCP, server) + if err != nil { + return nil, err + } + defer conn.Close() + if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() { + newDeadline := time.Now().Add(timeout) + if deadline.After(newDeadline) { + deadline = newDeadline + } + conn.SetDeadline(deadline) + } + err = transport.WriteMessage(conn, 0, request) + if err != nil { + return nil, err + } + return transport.ReadMessage(conn) +} diff --git a/dns/transport/local/resolv.go b/dns/transport/local/resolv.go new file mode 100644 index 0000000000..4aa10a64d9 --- /dev/null +++ b/dns/transport/local/resolv.go @@ -0,0 +1,145 @@ +//nolint:unused +package local + +import ( + "context" + "os" + "runtime" + "strings" + "sync" + "sync/atomic" + "time" +) + +type resolverConfig struct { + initOnce sync.Once + ch chan struct{} + lastChecked time.Time + dnsConfig atomic.Pointer[dnsConfig] +} + +var resolvConf resolverConfig + +func getSystemDNSConfig(ctx context.Context) *dnsConfig { + resolvConf.tryUpdate(ctx, "/etc/resolv.conf") + return resolvConf.dnsConfig.Load() +} + +func (conf *resolverConfig) init(ctx context.Context) { + conf.dnsConfig.Store(dnsReadConfig(ctx, "/etc/resolv.conf")) + conf.lastChecked = time.Now() + conf.ch = make(chan struct{}, 1) +} + +func (conf *resolverConfig) tryUpdate(ctx context.Context, name string) { + conf.initOnce.Do(func() { + conf.init(ctx) + }) + + if conf.dnsConfig.Load().noReload { + return + } + if !conf.tryAcquireSema() { + return + } + defer conf.releaseSema() + + now := time.Now() + if conf.lastChecked.After(now.Add(-5 * time.Second)) { + return + } + conf.lastChecked = now + if runtime.GOOS != "windows" { + var mtime time.Time + if fi, err := os.Stat(name); err == nil { + mtime = fi.ModTime() + } + if mtime.Equal(conf.dnsConfig.Load().mtime) { + return + } + } + dnsConf := dnsReadConfig(ctx, name) + conf.dnsConfig.Store(dnsConf) +} + +func (conf *resolverConfig) tryAcquireSema() bool { + select { + case conf.ch <- struct{}{}: + return true + default: + return false + } +} + +func (conf *resolverConfig) releaseSema() { + <-conf.ch +} + +type dnsConfig struct { + servers []string + search []string + ndots int + timeout time.Duration + attempts int + rotate bool + unknownOpt bool + lookup []string + err error + mtime time.Time + soffset uint32 + singleRequest bool + useTCP bool + trustAD bool + noReload bool +} + +func (c *dnsConfig) serverOffset() uint32 { + if c.rotate { + return atomic.AddUint32(&c.soffset, 1) - 1 // return 0 to start + } + return 0 +} + +func (c *dnsConfig) nameList(name string) []string { + l := len(name) + rooted := l > 0 && name[l-1] == '.' + if l > 254 || l == 254 && !rooted { + return nil + } + + if rooted { + if avoidDNS(name) { + return nil + } + return []string{name} + } + + hasNdots := strings.Count(name, ".") >= c.ndots + name += "." + // l++ + + names := make([]string, 0, 1+len(c.search)) + if hasNdots && !avoidDNS(name) { + names = append(names, name) + } + for _, suffix := range c.search { + fqdn := name + suffix + if !avoidDNS(fqdn) && len(fqdn) <= 254 { + names = append(names, fqdn) + } + } + if !hasNdots && !avoidDNS(name) { + names = append(names, name) + } + return names +} + +func avoidDNS(name string) bool { + if name == "" { + return true + } + if name[len(name)-1] == '.' { + name = name[:len(name)-1] + } + return strings.HasSuffix(name, ".onion") +} diff --git a/dns/transport/local/resolv_default.go b/dns/transport/local/resolv_default.go new file mode 100644 index 0000000000..9c5e8fa298 --- /dev/null +++ b/dns/transport/local/resolv_default.go @@ -0,0 +1,24 @@ +//nolint:unused +package local + +import ( + "os" + "strings" + _ "unsafe" + + "github.com/miekg/dns" +) + +//go:linkname defaultNS net.defaultNS +var defaultNS []string + +func dnsDefaultSearch() []string { + hn, err := os.Hostname() + if err != nil { + return nil + } + if i := strings.IndexRune(hn, '.'); i >= 0 && i < len(hn)-1 { + return []string{dns.Fqdn(hn[i+1:])} + } + return nil +} diff --git a/dns/transport/local/resolv_test.go b/dns/transport/local/resolv_test.go new file mode 100644 index 0000000000..546e84082f --- /dev/null +++ b/dns/transport/local/resolv_test.go @@ -0,0 +1,13 @@ +package local + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDNSReadConfig(t *testing.T) { + t.Parallel() + require.NoError(t, dnsReadConfig(context.Background(), "/etc/resolv.conf").err) +} diff --git a/dns/transport/local/resolv_unix.go b/dns/transport/local/resolv_unix.go new file mode 100644 index 0000000000..51512f65e6 --- /dev/null +++ b/dns/transport/local/resolv_unix.go @@ -0,0 +1,156 @@ +//go:build !windows + +package local + +import ( + "bufio" + "context" + "net" + "net/netip" + "os" + "strings" + "time" + + "github.com/miekg/dns" +) + +func dnsReadConfig(_ context.Context, name string) *dnsConfig { + conf := &dnsConfig{ + ndots: 1, + timeout: 5 * time.Second, + attempts: 2, + } + file, err := os.Open(name) + if err != nil { + conf.servers = defaultNS + conf.search = dnsDefaultSearch() + conf.err = err + return conf + } + defer file.Close() + fi, err := file.Stat() + if err == nil { + conf.mtime = fi.ModTime() + } else { + conf.servers = defaultNS + conf.search = dnsDefaultSearch() + conf.err = err + return conf + } + reader := bufio.NewReader(file) + var ( + prefix []byte + line []byte + isPrefix bool + ) + for { + line, isPrefix, err = reader.ReadLine() + if err != nil { + break + } + if isPrefix { + prefix = append(prefix, line...) + continue + } else if len(prefix) > 0 { + line = append(prefix, line...) + prefix = nil + } + if len(line) > 0 && (line[0] == ';' || line[0] == '#') { + continue + } + f := strings.Fields(string(line)) + if len(f) < 1 { + continue + } + switch f[0] { + case "nameserver": + if len(f) > 1 && len(conf.servers) < 3 { + if _, err := netip.ParseAddr(f[1]); err == nil { + conf.servers = append(conf.servers, net.JoinHostPort(f[1], "53")) + } + } + case "domain": + if len(f) > 1 { + conf.search = []string{dns.Fqdn(f[1])} + } + + case "search": + conf.search = make([]string, 0, len(f)-1) + for i := 1; i < len(f); i++ { + name := dns.Fqdn(f[i]) + if name == "." { + continue + } + conf.search = append(conf.search, name) + } + + case "options": + for _, s := range f[1:] { + switch { + case strings.HasPrefix(s, "ndots:"): + n, _, _ := dtoi(s[6:]) + if n < 0 { + n = 0 + } else if n > 15 { + n = 15 + } + conf.ndots = n + case strings.HasPrefix(s, "timeout:"): + n, _, _ := dtoi(s[8:]) + if n < 1 { + n = 1 + } + conf.timeout = time.Duration(n) * time.Second + case strings.HasPrefix(s, "attempts:"): + n, _, _ := dtoi(s[9:]) + if n < 1 { + n = 1 + } + conf.attempts = n + case s == "rotate": + conf.rotate = true + case s == "single-request" || s == "single-request-reopen": + conf.singleRequest = true + case s == "use-vc" || s == "usevc" || s == "tcp": + conf.useTCP = true + case s == "trust-ad": + conf.trustAD = true + case s == "edns0": + case s == "no-reload": + conf.noReload = true + default: + conf.unknownOpt = true + } + } + + case "lookup": + conf.lookup = f[1:] + + default: + conf.unknownOpt = true + } + } + if len(conf.servers) == 0 { + conf.servers = defaultNS + } + if len(conf.search) == 0 { + conf.search = dnsDefaultSearch() + } + return conf +} + +const big = 0xFFFFFF + +func dtoi(s string) (n int, i int, ok bool) { + n = 0 + for i = 0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { + n = n*10 + int(s[i]-'0') + if n >= big { + return big, i, false + } + } + if i == 0 { + return 0, 0, false + } + return n, i, true +} diff --git a/dns/transport/local/resolv_windows.go b/dns/transport/local/resolv_windows.go new file mode 100644 index 0000000000..04b8d4ef25 --- /dev/null +++ b/dns/transport/local/resolv_windows.go @@ -0,0 +1,118 @@ +package local + +import ( + "context" + "net" + "net/netip" + "os" + "strconv" + "syscall" + "time" + "unsafe" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/service" + + "golang.org/x/sys/windows" +) + +func dnsReadConfig(ctx context.Context, _ string) *dnsConfig { + conf := &dnsConfig{ + ndots: 1, + timeout: 5 * time.Second, + attempts: 2, + } + defer func() { + if len(conf.servers) == 0 { + conf.servers = defaultNS + } + }() + addresses, err := adapterAddresses() + if err != nil { + return nil + } + var dnsAddresses []struct { + ifName string + netip.Addr + } + for _, address := range addresses { + if address.OperStatus != windows.IfOperStatusUp { + continue + } + if address.IfType == windows.IF_TYPE_TUNNEL { + continue + } + if address.FirstGatewayAddress == nil { + continue + } + for dnsServerAddress := address.FirstDnsServerAddress; dnsServerAddress != nil; dnsServerAddress = dnsServerAddress.Next { + rawSockaddr, err := dnsServerAddress.Address.Sockaddr.Sockaddr() + if err != nil { + continue + } + var dnsServerAddr netip.Addr + switch sockaddr := rawSockaddr.(type) { + case *syscall.SockaddrInet4: + dnsServerAddr = netip.AddrFrom4(sockaddr.Addr) + case *syscall.SockaddrInet6: + if sockaddr.Addr[0] == 0xfe && sockaddr.Addr[1] == 0xc0 { + // fec0/10 IPv6 addresses are site local anycast DNS + // addresses Microsoft sets by default if no other + // IPv6 DNS address is set. Site local anycast is + // deprecated since 2004, see + // https://datatracker.ietf.org/doc/html/rfc3879 + continue + } + dnsServerAddr = netip.AddrFrom16(sockaddr.Addr) + if sockaddr.ZoneId != 0 { + dnsServerAddr = dnsServerAddr.WithZone(strconv.FormatInt(int64(sockaddr.ZoneId), 10)) + } + default: + // Unexpected type. + continue + } + dnsAddresses = append(dnsAddresses, struct { + ifName string + netip.Addr + }{ifName: windows.UTF16PtrToString(address.FriendlyName), Addr: dnsServerAddr}) + } + } + var myInterface string + if networkManager := service.FromContext[adapter.NetworkManager](ctx); networkManager != nil { + myInterface = networkManager.InterfaceMonitor().MyInterface() + } + for _, address := range dnsAddresses { + if address.ifName == myInterface { + continue + } + conf.servers = append(conf.servers, net.JoinHostPort(address.String(), "53")) + } + return conf +} + +func adapterAddresses() ([]*windows.IpAdapterAddresses, error) { + var b []byte + l := uint32(15000) // recommended initial size + for { + b = make([]byte, l) + const flags = windows.GAA_FLAG_INCLUDE_PREFIX | windows.GAA_FLAG_INCLUDE_GATEWAYS + err := windows.GetAdaptersAddresses(syscall.AF_UNSPEC, flags, 0, (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])), &l) + if err == nil { + if l == 0 { + return nil, nil + } + break + } + if err.(syscall.Errno) != syscall.ERROR_BUFFER_OVERFLOW { + return nil, os.NewSyscallError("getadaptersaddresses", err) + } + if l <= uint32(len(b)) { + return nil, os.NewSyscallError("getadaptersaddresses", err) + } + } + var aas []*windows.IpAdapterAddresses + for aa := (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])); aa != nil; aa = aa.Next { + aas = append(aas, aa) + } + return aas, nil +} diff --git a/dns/transport/mdns/mdns.go b/dns/transport/mdns/mdns.go new file mode 100644 index 0000000000..2db3390d53 --- /dev/null +++ b/dns/transport/mdns/mdns.go @@ -0,0 +1,456 @@ +package mdns + +import ( + "context" + "net" + "net/netip" + "slices" + "strings" + "time" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json/badoption" + "github.com/sagernet/sing/common/logger" + "github.com/sagernet/sing/common/task" + "github.com/sagernet/sing/service" + + mDNS "github.com/miekg/dns" + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" +) + +const ( + mdnsPort = 5353 + mdnsClassTopBit = 1 << 15 + mdnsTimeout = time.Second +) + +var ( + mdnsGroupIPv4 = net.IPv4(224, 0, 0, 251) + mdnsGroupIPv6 = net.ParseIP("ff02::fb") + mdnsLocalZones = []string{ + "local.", + "254.169.in-addr.arpa.", + "8.e.f.ip6.arpa.", + "9.e.f.ip6.arpa.", + "a.e.f.ip6.arpa.", + "b.e.f.ip6.arpa.", + } +) + +func IsLocalDomain(name string) bool { + canonical := mDNS.CanonicalName(name) + return common.Any(mdnsLocalZones, func(zone string) bool { + return canonical == zone || strings.HasSuffix(canonical, "."+zone) + }) +} + +func RegisterTransport(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.MDNSDNSServerOptions](registry, C.DNSTypeMDNS, NewTransport) +} + +var ( + _ adapter.DNSTransport = (*Transport)(nil) + _ adapter.DNSTransportWithPreferredDomain = (*Transport)(nil) +) + +type Transport struct { + dns.TransportAdapter + ctx context.Context + logger logger.ContextLogger + networkManager adapter.NetworkManager + interfaceNames badoption.Listable[string] +} + +func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.MDNSDNSServerOptions) (adapter.DNSTransport, error) { + return &Transport{ + TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeMDNS, tag, options.LocalDNSServerOptions), + ctx: ctx, + logger: logger, + networkManager: service.FromContext[adapter.NetworkManager](ctx), + interfaceNames: options.Interface, + }, nil +} + +func NewRawTransport(transportAdapter dns.TransportAdapter, ctx context.Context, logger log.ContextLogger) *Transport { + return &Transport{ + TransportAdapter: transportAdapter, + ctx: ctx, + logger: logger, + networkManager: service.FromContext[adapter.NetworkManager](ctx), + } +} + +func (t *Transport) Start(stage adapter.StartStage) error { + return nil +} + +func (t *Transport) Close() error { + return nil +} + +func (t *Transport) Reset() { +} + +func (t *Transport) PreferredDomain(domain string) bool { + return IsLocalDomain(domain) +} + +func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + targets, err := t.queryTargets() + if err != nil { + return nil, E.Cause(err, "mdns: prepare interfaces") + } + request := makeQueryMessage(message) + rawMessage, err := request.Pack() + if err != nil { + return nil, E.Cause(err, "mdns: pack request") + } + deadline, loaded := ctx.Deadline() + if !loaded || deadline.IsZero() { + deadline = time.Now().Add(mdnsTimeout) + } + exchangeCtx, cancel := context.WithDeadline(ctx, deadline) + defer cancel() + results := make(chan exchangeResult, len(targets)) + var group task.Group + for _, target := range targets { + group.Append0(func(ctx context.Context) error { + response, err := t.exchangeTarget(ctx, target, rawMessage, message.Question[0], deadline) + if err != nil || response != nil { + results <- exchangeResult{ + response: response, + err: err, + } + } + return nil + }) + } + groupErr := group.Run(exchangeCtx) + close(results) + response := newResponse(message) + seenRecords := make(map[string]bool) + var lastErr error + for result := range results { + if result.err != nil { + lastErr = result.err + t.logger.TraceContext(ctx, result.err) + continue + } + mergeResponse(response, result.response, seenRecords) + } + if len(response.Answer) > 0 || len(response.Ns) > 0 || len(response.Extra) > 0 { + return response, nil + } + if lastErr != nil { + return nil, lastErr + } + if groupErr != nil && ctx.Err() != nil { + return nil, groupErr + } + return nil, E.New("mdns: query timeout") +} + +type exchangeResult struct { + response *mDNS.Msg + err error +} + +type queryTarget struct { + iface control.Interface + family string +} + +func (t *Transport) exchangeTarget(ctx context.Context, target queryTarget, rawMessage []byte, question mDNS.Question, deadline time.Time) (*mDNS.Msg, error) { + packetConn, destination, err := t.listenPacket(ctx, target) + if err != nil { + return nil, err + } + defer packetConn.Close() + + _, err = packetConn.WriteTo(rawMessage, destination) + if err != nil { + return nil, E.Cause(err, "mdns: write request on ", target.iface.Name, " ", target.family) + } + err = packetConn.SetReadDeadline(deadline) + if err != nil { + return nil, E.Cause(err, "mdns: set deadline on ", target.iface.Name, " ", target.family) + } + response := newResponseFromQuestion(question) + seenRecords := make(map[string]bool) + buffer := buf.Get(buf.UDPBufferSize) + defer buf.Put(buffer) + for { + n, source, readErr := packetConn.ReadFrom(buffer) + if readErr != nil { + if E.IsTimeout(readErr) { + if len(response.Answer) > 0 || len(response.Ns) > 0 || len(response.Extra) > 0 { + return response, nil + } + return nil, nil + } + return nil, E.Cause(readErr, "mdns: read response on ", target.iface.Name, " ", target.family) + } + if !validSource(source, target) { + continue + } + var candidate mDNS.Msg + err = candidate.Unpack(buffer[:n]) + if err != nil { + t.logger.TraceContext(ctx, "mdns: unpack response: ", err) + continue + } + if !validResponse(&candidate, question) { + continue + } + normalizeResponse(&candidate, question) + mergeResponse(response, &candidate, seenRecords) + } +} + +func (t *Transport) listenPacket(ctx context.Context, target queryTarget) (net.PacketConn, net.Addr, error) { + var listenConfig net.ListenConfig + listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(t.networkManager.InterfaceFinder(), target.iface.Name, target.iface.Index)) + netInterface := target.iface.NetInterface() + switch target.family { + case "udp4": + packetConn, err := listenConfig.ListenPacket(ctx, "udp4", "0.0.0.0:0") + if err != nil { + return nil, nil, E.Cause(err, "mdns: listen on ", target.iface.Name, " udp4") + } + ipv4Conn := ipv4.NewPacketConn(packetConn) + err = ipv4Conn.SetMulticastInterface(&netInterface) + if err != nil { + packetConn.Close() + return nil, nil, E.Cause(err, "mdns: set multicast interface on ", target.iface.Name, " udp4") + } + _ = ipv4Conn.SetMulticastTTL(255) + return packetConn, &net.UDPAddr{IP: mdnsGroupIPv4, Port: mdnsPort}, nil + case "udp6": + packetConn, err := listenConfig.ListenPacket(ctx, "udp6", "[::]:0") + if err != nil { + return nil, nil, E.Cause(err, "mdns: listen on ", target.iface.Name, " udp6") + } + ipv6Conn := ipv6.NewPacketConn(packetConn) + err = ipv6Conn.SetMulticastInterface(&netInterface) + if err != nil { + packetConn.Close() + return nil, nil, E.Cause(err, "mdns: set multicast interface on ", target.iface.Name, " udp6") + } + _ = ipv6Conn.SetMulticastHopLimit(255) + return packetConn, &net.UDPAddr{IP: mdnsGroupIPv6, Port: mdnsPort, Zone: target.iface.Name}, nil + default: + return nil, nil, E.New("mdns: unknown network: ", target.family) + } +} + +func (t *Transport) queryTargets() ([]queryTarget, error) { + interfaces, err := t.fetchInterfaces() + if err != nil { + return nil, err + } + var targets []queryTarget + for _, iface := range interfaces { + supports4, supports6 := interfaceFamilies(iface) + if supports4 { + targets = append(targets, queryTarget{ + iface: iface, + family: "udp4", + }) + } + if supports6 { + targets = append(targets, queryTarget{ + iface: iface, + family: "udp6", + }) + } + } + if len(targets) == 0 { + return nil, E.New("missing usable mDNS interfaces") + } + return targets, nil +} + +func (t *Transport) fetchInterfaces() ([]control.Interface, error) { + finder := t.networkManager.InterfaceFinder() + var interfaces []control.Interface + if len(t.interfaceNames) > 0 { + for _, interfaceName := range t.interfaceNames { + iface, err := finder.ByName(interfaceName) + if err != nil { + t.logger.Warn("mdns: interface ", interfaceName, " not found") + continue + } + if !isUsableInterface(*iface) { + t.logger.Warn("mdns: interface ", interfaceName, " is not usable") + continue + } + interfaces = append(interfaces, *iface) + } + } else { + interfaces = common.Filter(finder.Interfaces(), isUsableInterface) + } + if len(interfaces) == 0 { + return nil, E.New("mdns: missing usable interface") + } + return interfaces, nil +} + +func isUsableInterface(iface control.Interface) bool { + return iface.Flags&net.FlagUp != 0 && + iface.Flags&net.FlagMulticast != 0 && + iface.Flags&net.FlagLoopback == 0 +} + +func interfaceFamilies(iface control.Interface) (supports4, supports6 bool) { + for _, prefix := range iface.Addresses { + addr := prefix.Addr() + if addr.IsLoopback() { + continue + } + if addr.Is4() { + supports4 = true + } else if addr.Is6() && !addr.Is4In6() { + supports6 = true + } + if supports4 && supports6 { + return + } + } + return +} + +func makeQueryMessage(message *mDNS.Msg) *mDNS.Msg { + request := &mDNS.Msg{ + Question: slices.Clone(message.Question), + } + for i := range request.Question { + stripQuestionClass(&request.Question[i]) + } + return request +} + +func newResponse(message *mDNS.Msg) *mDNS.Msg { + response := newResponseFromQuestion(message.Question[0]) + response.Id = message.Id + return response +} + +func newResponseFromQuestion(question mDNS.Question) *mDNS.Msg { + stripQuestionClass(&question) + return &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Response: true, + Authoritative: true, + Rcode: mDNS.RcodeSuccess, + }, + Question: []mDNS.Question{question}, + } +} + +func validSource(source net.Addr, target queryTarget) bool { + sourceUDP, isUDP := source.(*net.UDPAddr) + if !isUDP || sourceUDP.Port != mdnsPort { + return false + } + sourceAddr, loaded := netip.AddrFromSlice(sourceUDP.IP) + if !loaded { + return false + } + sourceAddr = sourceAddr.Unmap() + if (target.family == "udp4" && !sourceAddr.Is4()) || (target.family == "udp6" && !sourceAddr.Is6()) { + return false + } + for _, prefix := range target.iface.Addresses { + if prefix.Contains(sourceAddr) { + return true + } + } + return false +} + +func validResponse(response *mDNS.Msg, question mDNS.Question) bool { + if !response.Response || + response.Opcode != mDNS.OpcodeQuery || + response.Rcode != mDNS.RcodeSuccess { + return false + } + for _, responseQuestion := range response.Question { + if questionMatches(responseQuestion, question) { + return true + } + } + return responseHasMatchingRecord(response, question) +} + +func responseHasMatchingRecord(response *mDNS.Msg, question mDNS.Question) bool { + for _, recordList := range [][]mDNS.RR{response.Answer, response.Ns, response.Extra} { + for _, record := range recordList { + if recordMatchesQuestion(record, question) { + return true + } + } + } + return false +} + +func questionMatches(left mDNS.Question, right mDNS.Question) bool { + stripQuestionClass(&left) + stripQuestionClass(&right) + return left.Qtype == right.Qtype && + left.Qclass == right.Qclass && + strings.EqualFold(left.Name, right.Name) +} + +func recordMatchesQuestion(record mDNS.RR, question mDNS.Question) bool { + header := record.Header() + return strings.EqualFold(header.Name, question.Name) && + (question.Qtype == mDNS.TypeANY || + header.Rrtype == question.Qtype || + header.Rrtype == mDNS.TypeCNAME) +} + +func normalizeResponse(response *mDNS.Msg, question mDNS.Question) { + response.Id = 0 + response.Question = []mDNS.Question{question} + for i := range response.Question { + stripQuestionClass(&response.Question[i]) + } + for _, recordList := range [][]mDNS.RR{response.Answer, response.Ns, response.Extra} { + for _, record := range recordList { + stripRecordClass(record) + } + } +} + +func mergeResponse(destination *mDNS.Msg, source *mDNS.Msg, seenRecords map[string]bool) { + appendRecords := func(destinationRecords *[]mDNS.RR, sourceRecords []mDNS.RR) { + for _, record := range sourceRecords { + key := record.String() + if seenRecords[key] { + continue + } + seenRecords[key] = true + *destinationRecords = append(*destinationRecords, record) + } + } + appendRecords(&destination.Answer, source.Answer) + appendRecords(&destination.Ns, source.Ns) + appendRecords(&destination.Extra, source.Extra) +} + +func stripQuestionClass(question *mDNS.Question) { + question.Qclass &^= mdnsClassTopBit +} + +func stripRecordClass(record mDNS.RR) { + record.Header().Class &^= mdnsClassTopBit +} diff --git a/dns/transport/multi/multi.go b/dns/transport/multi/multi.go new file mode 100644 index 0000000000..7e36a62286 --- /dev/null +++ b/dns/transport/multi/multi.go @@ -0,0 +1,251 @@ +package multi + +import ( + "context" + "net" + "net/netip" + "sync" + "time" + + mDNS "github.com/miekg/dns" + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/service" +) + +func RegisterTransport(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.MultiDNSServerOptions](registry, C.DNSTypeMulti, NewTransport) +} + +var _ adapter.DNSTransport = (*Transport)(nil) + +type Transport struct { + dns.TransportAdapter + serverTags []string + parallel bool + transports []adapter.DNSTransport + manager adapter.DNSTransportManager + ignoredRanges []netip.Prefix + done chan struct{} + logger log.ContextLogger +} + +func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.MultiDNSServerOptions) (adapter.DNSTransport, error) { + ignoreRanges := make([]netip.Prefix, 0, len(options.IgnoreRanges)) + for _, r := range options.IgnoreRanges { + ignoreRanges = append(ignoreRanges, netip.Prefix(r)) + } + + return &Transport{ + TransportAdapter: dns.NewTransportAdapter(C.DNSTypeMulti, tag, options.Servers), + manager: service.FromContext[adapter.DNSTransportManager](ctx), + serverTags: options.Servers, + parallel: options.Parallel, + ignoredRanges: ignoreRanges, + done: make(chan struct{}), + logger: logger, + }, nil +} + +func (m *Transport) Start(stage adapter.StartStage) error { + if stage == adapter.StartStateStart { + for _, tag := range m.serverTags { + transport, found := m.manager.Transport(tag) + if !found { + return E.New("dns transport not found: ", tag) + } + m.transports = append(m.transports, transport) + } + } + return nil +} + +func (t *Transport) Close() error { + t.logger.Debug("closing multi dns transport ", t.Tag()) + close(t.done) + return nil +} + +func (t *Transport) Reset() { +} + +func (m *Transport) Exchange(ctx context.Context, msg *mDNS.Msg) (*mDNS.Msg, error) { + defer func() { + select { + case <-m.done: + m.logger.Debug("m done closing multi dns transport successfully ", m.Tag()) + case <-ctx.Done(): + m.logger.Debug("ctx done, closing multi dns transport successfully ", m.Tag(), " ctx error: ", ctx.Err()) + default: + } + }() + if len(m.transports) == 0 { + return nil, E.New("no dns transports configured") + } + + if !m.parallel { + return m.exchangeSerial(ctx, msg) + } + + return m.exchangeParallel(ctx, msg) +} +func (m *Transport) exchangeSerial(parent context.Context, msg *mDNS.Msg) (*mDNS.Msg, error) { + ctx, cancel := context.WithTimeout(parent, 10*time.Second) + defer cancel() + var lastErr error + var lastResp *mDNS.Msg + for _, tr := range m.transports { + select { + case <-m.done: + return nil, E.New("transport closed") + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + resp, err := tr.Exchange(ctx, msg) + if err != nil { + lastErr = err + continue + } + var change bool + resp, change = m.filterBlocked(resp) + if len(resp.Answer) > 0 { + return resp, nil + } + if !change { + lastResp = resp + } + } + + if lastResp != nil { + return lastResp, nil + } + if lastErr != nil { + return nil, lastErr + } + return nil, E.New("no dns response") +} +func (m *Transport) exchangeParallel(parent context.Context, msg *mDNS.Msg) (*mDNS.Msg, error) { + ctx, cancel := context.WithTimeout(parent, 5*time.Second) + defer cancel() + + type result struct { + resp *mDNS.Msg + err error + } + + results := make(chan result, len(m.transports)) + + var wg sync.WaitGroup + wg.Add(len(m.transports)) + + for _, tr := range m.transports { + go func(tr adapter.DNSTransport) { + defer wg.Done() + + resp, err := tr.Exchange(ctx, msg) + select { + case results <- result{resp: resp, err: err}: + case <-ctx.Done(): + case <-m.done: + } + }(tr) + } + + // Close results exactly once + go func() { + wg.Wait() + close(results) + + }() + + var lastErr error + var lastResp *mDNS.Msg + + for { + select { + case r, ok := <-results: + if !ok { + m.logger.Trace("all transports completed, no response received") + if lastResp != nil { + return lastResp, nil + } + if lastErr != nil { + return nil, lastErr + } + return nil, E.New("no dns response") + } + if r.err != nil { + lastErr = r.err + continue + } + resp, change := m.filterBlocked(r.resp) + if len(resp.Answer) > 0 { + cancel() // stop others + return resp, nil + } + if !change { + lastResp = resp + } + case <-m.done: + return nil, E.New("transport closed") + case <-ctx.Done(): + if lastResp != nil { + return lastResp, nil + } + if lastErr != nil { + return nil, lastErr + } + return nil, ctx.Err() + } + } +} + +func (m *Transport) filterBlocked(msg *mDNS.Msg) (*mDNS.Msg, bool) { + if msg == nil { + return nil, false + } + if len(msg.Answer) == 0 { + return msg, false + } + answers := make([]mDNS.RR, 0) + for _, r := range msg.Answer { + switch answer := r.(type) { + case *mDNS.A: + if m.isBlocked(answer.A) { + continue + } + case *mDNS.AAAA: + if m.isBlocked(answer.AAAA) { + continue + } + default: + } + answers = append(answers, r) + } + hasChanged := len(answers) != len(msg.Answer) + msg.Answer = answers + return msg, hasChanged +} + +func (m *Transport) isBlocked(ip net.IP) bool { + netipAddr, ok := netip.AddrFromSlice(ip) + if !ok { + return true + } + if !netipAddr.IsValid() { + return true + } + + for _, p := range m.ignoredRanges { + if p.Contains(netipAddr) { + return true + } + } + return false +} diff --git a/dns/transport/quic/http3.go b/dns/transport/quic/http3.go new file mode 100644 index 0000000000..c3a5ca81cb --- /dev/null +++ b/dns/transport/quic/http3.go @@ -0,0 +1,205 @@ +package quic + +import ( + "bytes" + "context" + "io" + "net" + "net/http" + "net/url" + "strconv" + "sync" + + "github.com/sagernet/quic-go" + "github.com/sagernet/quic-go/http3" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/dns/transport" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + sHTTP "github.com/sagernet/sing/protocol/http" + + mDNS "github.com/miekg/dns" +) + +var _ adapter.DNSTransport = (*HTTP3Transport)(nil) + +func RegisterHTTP3Transport(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.RemoteHTTPSDNSServerOptions](registry, C.DNSTypeHTTP3, NewHTTP3) +} + +type HTTP3Transport struct { + dns.TransportAdapter + logger logger.ContextLogger + dialer N.Dialer + destination *url.URL + headers http.Header + serverAddr M.Socksaddr + tlsConfig *tls.STDConfig + transportAccess sync.Mutex + transport *http3.Transport +} + +func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteHTTPSDNSServerOptions) (adapter.DNSTransport, error) { + transportDialer, err := dns.NewRemoteDialer(ctx, options.RemoteDNSServerOptions) + if err != nil { + return nil, err + } + tlsOptions := common.PtrValueOrDefault(options.TLS) + tlsOptions.Enabled = true + tlsConfig, err := tls.NewClient(ctx, logger, options.Server, tlsOptions) + if err != nil { + return nil, err + } + stdConfig, err := tlsConfig.STDConfig() + if err != nil { + return nil, err + } + headers := options.Headers.Build() + host := headers.Get("Host") + if host != "" { + headers.Del("Host") + } else { + if tlsConfig.ServerName() != "" { + host = tlsConfig.ServerName() + } else { + host = options.Server + } + } + destinationURL := url.URL{ + Scheme: "https", + Host: host, + } + if destinationURL.Host == "" { + destinationURL.Host = options.Server + } + if options.ServerPort != 0 && options.ServerPort != 443 { + destinationURL.Host = net.JoinHostPort(destinationURL.Host, strconv.Itoa(int(options.ServerPort))) + } + path := options.Path + if path == "" { + path = "/dns-query" + } + err = sHTTP.URLSetPath(&destinationURL, path) + if err != nil { + return nil, err + } + serverAddr := options.DNSServerAddressOptions.Build() + if serverAddr.Port == 0 { + serverAddr.Port = 443 + } + if !serverAddr.IsValid() { + return nil, E.New("invalid server address: ", serverAddr) + } + t := &HTTP3Transport{ + TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTP3, tag, options.RemoteDNSServerOptions), + logger: logger, + dialer: transportDialer, + destination: &destinationURL, + headers: headers, + serverAddr: serverAddr, + tlsConfig: stdConfig, + } + t.transport = t.newTransport() + return t, nil +} + +func (t *HTTP3Transport) newTransport() *http3.Transport { + return &http3.Transport{ + Dial: func(ctx context.Context, addr string, tlsCfg *tls.STDConfig, cfg *quic.Config) (*quic.Conn, error) { + conn, dialErr := t.dialer.DialContext(ctx, N.NetworkUDP, t.serverAddr) + if dialErr != nil { + return nil, dialErr + } + quicConn, dialErr := quic.DialEarly(ctx, bufio.NewUnbindPacketConn(conn), conn.RemoteAddr(), tlsCfg, cfg) + if dialErr != nil { + conn.Close() + return nil, dialErr + } + return quicConn, nil + }, + TLSClientConfig: t.tlsConfig, + } +} + +func (t *HTTP3Transport) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + return dialer.InitializeDetour(t.dialer) +} + +func (t *HTTP3Transport) Close() error { + t.transportAccess.Lock() + defer t.transportAccess.Unlock() + return t.transport.Close() +} + +func (t *HTTP3Transport) Reset() { + t.transportAccess.Lock() + defer t.transportAccess.Unlock() + t.transport.Close() + t.transport = t.newTransport() +} + +func (t *HTTP3Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + exMessage := *message + exMessage.Id = 0 + exMessage.Compress = true + requestBuffer := buf.NewSize(1 + message.Len()) + rawMessage, err := exMessage.PackBuffer(requestBuffer.FreeBytes()) + if err != nil { + requestBuffer.Release() + return nil, err + } + request, err := http.NewRequestWithContext(ctx, http.MethodPost, t.destination.String(), bytes.NewReader(rawMessage)) + if err != nil { + requestBuffer.Release() + return nil, err + } + request.Header = t.headers.Clone() + request.Header.Set("Content-Type", transport.MimeType) + request.Header.Set("Accept", transport.MimeType) + t.transportAccess.Lock() + currentTransport := t.transport + t.transportAccess.Unlock() + response, err := currentTransport.RoundTrip(request) + requestBuffer.Release() + if err != nil { + return nil, err + } + defer response.Body.Close() + if response.StatusCode != http.StatusOK { + return nil, E.New("unexpected status: ", response.Status) + } + var responseMessage mDNS.Msg + if response.ContentLength > 0 { + responseBuffer := buf.NewSize(int(response.ContentLength)) + defer responseBuffer.Release() + _, err = responseBuffer.ReadFullFrom(response.Body, int(response.ContentLength)) + if err != nil { + return nil, err + } + err = responseMessage.Unpack(responseBuffer.Bytes()) + } else { + rawMessage, err = io.ReadAll(response.Body) + if err != nil { + return nil, err + } + err = responseMessage.Unpack(rawMessage) + } + if err != nil { + return nil, err + } + return &responseMessage, nil +} diff --git a/dns/transport/quic/quic.go b/dns/transport/quic/quic.go new file mode 100644 index 0000000000..3bb93e4178 --- /dev/null +++ b/dns/transport/quic/quic.go @@ -0,0 +1,191 @@ +package quic + +import ( + "context" + "errors" + "os" + + "github.com/sagernet/quic-go" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/dns/transport" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + sQUIC "github.com/sagernet/sing-quic" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + mDNS "github.com/miekg/dns" +) + +var _ adapter.DNSTransport = (*Transport)(nil) + +func RegisterTransport(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.RemoteTLSDNSServerOptions](registry, C.DNSTypeQUIC, NewQUIC) +} + +type Transport struct { + dns.TransportAdapter + + dialer N.Dialer + serverAddr M.Socksaddr + tlsConfig tls.Config + + connection *transport.ConnPool[*quic.Conn] +} + +func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteTLSDNSServerOptions) (adapter.DNSTransport, error) { + transportDialer, err := dns.NewRemoteDialer(ctx, options.RemoteDNSServerOptions) + if err != nil { + return nil, err + } + tlsOptions := common.PtrValueOrDefault(options.TLS) + tlsOptions.Enabled = true + tlsConfig, err := tls.NewClient(ctx, logger, options.Server, tlsOptions) + if err != nil { + return nil, err + } + if len(tlsConfig.NextProtos()) == 0 { + tlsConfig.SetNextProtos([]string{"doq"}) + } + serverAddr := options.DNSServerAddressOptions.Build() + if serverAddr.Port == 0 { + serverAddr.Port = 853 + } + if !serverAddr.IsValid() { + return nil, E.New("invalid server address: ", serverAddr) + } + + return &Transport{ + TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeQUIC, tag, options.RemoteDNSServerOptions), + dialer: transportDialer, + serverAddr: serverAddr, + tlsConfig: tlsConfig, + connection: transport.NewConnPool(transport.ConnPoolOptions[*quic.Conn]{ + Mode: transport.ConnPoolSingle, + IsAlive: func(conn *quic.Conn) bool { + return conn != nil && !common.Done(conn.Context()) + }, + Close: func(conn *quic.Conn, _ error) { + conn.CloseWithError(0, "") + }, + }), + }, nil +} + +func (t *Transport) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + return dialer.InitializeDetour(t.dialer) +} + +func (t *Transport) Close() error { + return t.connection.Close() +} + +func (t *Transport) Reset() { + t.connection.Reset() +} + +func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + var ( + conn *quic.Conn + err error + response *mDNS.Msg + ) + for range 2 { + conn, _, err = t.connection.Acquire(ctx, func(ctx context.Context) (*quic.Conn, error) { + rawConn, err := t.dialer.DialContext(ctx, N.NetworkUDP, t.serverAddr) + if err != nil { + return nil, E.Cause(err, "dial UDP connection") + } + earlyConnection, err := sQUIC.DialEarly( + ctx, + bufio.NewUnbindPacketConn(rawConn), + t.serverAddr.UDPAddr(), + t.tlsConfig, + nil, + ) + if err != nil { + rawConn.Close() + return nil, E.Cause(err, "establish QUIC connection") + } + return earlyConnection, nil + }) + if err != nil { + return nil, err + } + response, err = t.exchange(ctx, message, conn) + if err == nil { + t.connection.Release(conn, true) + return response, nil + } else if !isQUICRetryError(err) { + t.connection.Release(conn, true) + return nil, err + } else { + t.connection.Release(conn, true) + t.Reset() + continue + } + } + return nil, err +} + +func (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, conn *quic.Conn) (*mDNS.Msg, error) { + stream, err := conn.OpenStreamSync(ctx) + if err != nil { + return nil, E.Cause(err, "open stream") + } + defer stream.CancelRead(0) + err = transport.WriteMessage(stream, 0, message) + if err != nil { + stream.Close() + return nil, E.Cause(err, "write request") + } + stream.Close() + response, err := transport.ReadMessage(stream) + if err != nil { + return nil, E.Cause(err, "read response") + } + return response, nil +} + +// https://github.com/AdguardTeam/dnsproxy/blob/fd1868577652c639cce3da00e12ca548f421baf1/upstream/upstream_quic.go#L394 +func isQUICRetryError(err error) (ok bool) { + if errors.Is(err, os.ErrClosed) { + return true + } + + var qAppErr *quic.ApplicationError + if errors.As(err, &qAppErr) && qAppErr.ErrorCode == 0 { + return true + } + + var qIdleErr *quic.IdleTimeoutError + if errors.As(err, &qIdleErr) { + return true + } + + var resetErr *quic.StatelessResetError + if errors.As(err, &resetErr) { + return true + } + + var qTransportError *quic.TransportError + if errors.As(err, &qTransportError) && qTransportError.ErrorCode == quic.NoError { + return true + } + + if errors.Is(err, quic.Err0RTTRejected) { + return true + } + + return false +} diff --git a/dns/transport/sdns.go b/dns/transport/sdns.go new file mode 100644 index 0000000000..1c4a1a2390 --- /dev/null +++ b/dns/transport/sdns.go @@ -0,0 +1,73 @@ +package transport + +import ( + "context" + "net" + "sync" + "time" + + "github.com/ameshkov/dnscrypt/v2" + mDNS "github.com/miekg/dns" + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +var _ adapter.DNSTransport = (*SDNSTransport)(nil) + +func RegisterSDNS(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.SDNSDNSServerOptions](registry, C.DNSTypeSDNS, NewSDNSTransport) +} + +type SDNSTransport struct { + dns.TransportAdapter + client *dnscrypt.Client + name string + stamp string + + mtx sync.Mutex +} + +func NewSDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.SDNSDNSServerOptions) (adapter.DNSTransport, error) { + transportDialer, err := dns.NewRemoteDialer(ctx, options.RemoteDNSServerOptions) + if err != nil { + return nil, err + } + return &SDNSTransport{ + client: &dnscrypt.Client{ + Net: "udp", + Timeout: 10 * time.Second, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return transportDialer.DialContext(ctx, N.NetworkName(network), M.ParseSocksaddr(addr)) + }, + }, + stamp: options.Stamp, + }, err +} + +func (t *SDNSTransport) Name() string { + return t.name +} + +func (t *SDNSTransport) Start(adapter.StartStage) error { + return nil +} + +func (t *SDNSTransport) Close() error { + return nil +} + +func (t *SDNSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + resolverInfo, err := t.client.Dial(t.stamp) + if err != nil { + return nil, err + } + return t.client.Exchange(message, resolverInfo) +} + +func (t *SDNSTransport) Reset() { +} diff --git a/dns/transport/tcp.go b/dns/transport/tcp.go new file mode 100644 index 0000000000..f8249437a1 --- /dev/null +++ b/dns/transport/tcp.go @@ -0,0 +1,137 @@ +package transport + +import ( + "context" + "encoding/binary" + "io" + "net" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio/deadline" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + mDNS "github.com/miekg/dns" +) + +var _ adapter.DNSTransport = (*TCPTransport)(nil) + +func RegisterTCP(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.RemoteDNSServerOptions](registry, C.DNSTypeTCP, NewTCP) +} + +type TCPTransport struct { + dns.TransportAdapter + dialer N.Dialer + serverAddr M.Socksaddr +} + +func NewTCP(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteDNSServerOptions) (adapter.DNSTransport, error) { + transportDialer, err := dns.NewRemoteDialer(ctx, options) + if err != nil { + return nil, err + } + serverAddr := options.DNSServerAddressOptions.Build() + if serverAddr.Port == 0 { + serverAddr.Port = 53 + } + if !serverAddr.IsValid() { + return nil, E.New("invalid server address: ", serverAddr) + } + return &TCPTransport{ + TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTCP, tag, options), + dialer: transportDialer, + serverAddr: serverAddr, + }, nil +} + +func (t *TCPTransport) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + return dialer.InitializeDetour(t.dialer) +} + +func (t *TCPTransport) Close() error { + return nil +} + +func (t *TCPTransport) Reset() { +} + +func (t *TCPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + conn, err := t.dialer.DialContext(ctx, N.NetworkTCP, t.serverAddr) + if err != nil { + return nil, E.Cause(err, "dial TCP connection") + } + defer conn.Close() + defer setConnDeadline(ctx, conn, deadline.NeedAdditionalReadDeadline(conn))() + err = WriteMessage(conn, 0, message) + if err != nil { + return nil, E.Cause(err, "write request") + } + response, err := ReadMessage(conn) + if err != nil { + return nil, E.Cause(err, "read response") + } + return response, nil +} + +func setConnDeadline(ctx context.Context, conn net.Conn, needClose bool) func() { + if needClose { + stop := context.AfterFunc(ctx, func() { + conn.Close() + }) + return func() { stop() } + } + if d, ok := ctx.Deadline(); ok { + conn.SetDeadline(d) + return func() { conn.SetDeadline(time.Time{}) } + } + return func() {} +} + +func ReadMessage(reader io.Reader) (*mDNS.Msg, error) { + var responseLen uint16 + err := binary.Read(reader, binary.BigEndian, &responseLen) + if err != nil { + return nil, err + } + if responseLen < 10 { + return nil, mDNS.ErrShortRead + } + buffer := buf.NewSize(int(responseLen)) + defer buffer.Release() + _, err = buffer.ReadFullFrom(reader, int(responseLen)) + if err != nil { + return nil, err + } + var message mDNS.Msg + err = message.Unpack(buffer.Bytes()) + return &message, err +} + +func WriteMessage(writer io.Writer, messageId uint16, message *mDNS.Msg) error { + requestLen := message.Len() + buffer := buf.NewSize(3 + requestLen) + defer buffer.Release() + common.Must(binary.Write(buffer, binary.BigEndian, uint16(requestLen))) + exMessage := *message + exMessage.Id = messageId + exMessage.Compress = true + rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes()) + if err != nil { + return err + } + buffer.Truncate(2 + len(rawMessage)) + return common.Error(writer.Write(buffer.Bytes())) +} diff --git a/dns/transport/tls.go b/dns/transport/tls.go new file mode 100644 index 0000000000..fdb485637b --- /dev/null +++ b/dns/transport/tls.go @@ -0,0 +1,146 @@ +package transport + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/bufio/deadline" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + mDNS "github.com/miekg/dns" +) + +var _ adapter.DNSTransport = (*TLSTransport)(nil) + +const tlsDNSMaxInflight = 8 + +func RegisterTLS(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.RemoteTLSDNSServerOptions](registry, C.DNSTypeTLS, NewTLS) +} + +type TLSTransport struct { + dns.TransportAdapter + logger logger.ContextLogger + + dialer tls.Dialer + serverAddr M.Socksaddr + tlsConfig tls.Config + connections *ConnPool[*tlsDNSConn] +} + +type tlsDNSConn struct { + tls.Conn + queryId uint16 + needDeadlineClose bool +} + +func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteTLSDNSServerOptions) (adapter.DNSTransport, error) { + transportDialer, err := dns.NewRemoteDialer(ctx, options.RemoteDNSServerOptions) + if err != nil { + return nil, err + } + tlsOptions := common.PtrValueOrDefault(options.TLS) + tlsOptions.Enabled = true + tlsConfig, err := tls.NewClient(ctx, logger, options.Server, tlsOptions) + if err != nil { + return nil, err + } + serverAddr := options.DNSServerAddressOptions.Build() + if serverAddr.Port == 0 { + serverAddr.Port = 853 + } + if !serverAddr.IsValid() { + return nil, E.New("invalid server address: ", serverAddr) + } + return NewTLSRaw(logger, dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTLS, tag, options.RemoteDNSServerOptions), transportDialer, serverAddr, tlsConfig), nil +} + +func NewTLSRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer N.Dialer, serverAddr M.Socksaddr, tlsConfig tls.Config) *TLSTransport { + return &TLSTransport{ + TransportAdapter: adapter, + logger: logger, + dialer: tls.NewDialer(dialer, tlsConfig), + serverAddr: serverAddr, + tlsConfig: tlsConfig, + connections: NewConnPool(ConnPoolOptions[*tlsDNSConn]{ + Mode: ConnPoolOrdered, + MaxInflight: tlsDNSMaxInflight, + IsAlive: func(conn *tlsDNSConn) bool { + return conn != nil + }, + Close: func(conn *tlsDNSConn, _ error) { + conn.Close() + }, + }), + } +} + +func (t *TLSTransport) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + return dialer.InitializeDetour(t.dialer) +} + +func (t *TLSTransport) Close() error { + return t.connections.Close() +} + +func (t *TLSTransport) Reset() { + t.connections.Reset() +} + +func (t *TLSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + var lastErr error + for range 2 { + conn, created, err := t.connections.Acquire(ctx, func(ctx context.Context) (*tlsDNSConn, error) { + tlsConn, err := t.dialer.DialTLSContext(ctx, t.serverAddr) + if err != nil { + return nil, E.Cause(err, "dial TLS connection") + } + return &tlsDNSConn{ + Conn: tlsConn, + needDeadlineClose: deadline.NeedAdditionalReadDeadline(tlsConn.NetConn()), + }, nil + }) + if err != nil { + return nil, err + } + response, err := t.exchange(ctx, message, conn) + if err == nil { + t.connections.Release(conn, true) + return response, nil + } + lastErr = err + t.logger.DebugContext(ctx, "discarded pooled connection: ", err) + t.connections.Release(conn, false) + if created { + return nil, err + } + } + return nil, lastErr +} + +func (t *TLSTransport) exchange(ctx context.Context, message *mDNS.Msg, conn *tlsDNSConn) (*mDNS.Msg, error) { + defer setConnDeadline(ctx, conn, conn.needDeadlineClose)() + conn.queryId++ + err := WriteMessage(conn, conn.queryId, message) + if err != nil { + return nil, E.Cause(err, "write request") + } + response, err := ReadMessage(conn) + if err != nil { + return nil, E.Cause(err, "read response") + } + return response, nil +} diff --git a/dns/transport/udp.go b/dns/transport/udp.go new file mode 100644 index 0000000000..7203b5ad4d --- /dev/null +++ b/dns/transport/udp.go @@ -0,0 +1,262 @@ +package transport + +import ( + "context" + "net" + "sync" + "sync/atomic" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio/deadline" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + mDNS "github.com/miekg/dns" +) + +var _ adapter.DNSTransport = (*UDPTransport)(nil) + +func RegisterUDP(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.RemoteDNSServerOptions](registry, C.DNSTypeUDP, NewUDP) +} + +type UDPTransport struct { + dns.TransportAdapter + logger logger.ContextLogger + + dialer N.Dialer + serverAddr M.Socksaddr + udpSize atomic.Int32 + + connection *ConnPool[net.Conn] + + callbackAccess sync.RWMutex + queryId uint16 + callbacks map[uint16]*udpCallback +} + +type udpCallback struct { + access sync.Mutex + response *mDNS.Msg + done chan struct{} +} + +func NewUDP(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteDNSServerOptions) (adapter.DNSTransport, error) { + transportDialer, err := dns.NewRemoteDialer(ctx, options) + if err != nil { + return nil, err + } + serverAddr := options.DNSServerAddressOptions.Build() + if serverAddr.Port == 0 { + serverAddr.Port = 53 + } + if !serverAddr.IsValid() { + return nil, E.New("invalid server address: ", serverAddr) + } + return NewUDPRaw(logger, dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeUDP, tag, options), transportDialer, serverAddr), nil +} + +func NewUDPRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialerInstance N.Dialer, serverAddr M.Socksaddr) *UDPTransport { + t := &UDPTransport{ + TransportAdapter: adapter, + logger: logger, + dialer: dialerInstance, + serverAddr: serverAddr, + callbacks: make(map[uint16]*udpCallback), + connection: NewConnPool(ConnPoolOptions[net.Conn]{ + Mode: ConnPoolSingle, + IsAlive: func(conn net.Conn) bool { + return conn != nil + }, + Close: func(conn net.Conn, cause error) { + conn.Close() + }, + }), + } + t.udpSize.Store(2048) + return t +} + +func (t *UDPTransport) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + return dialer.InitializeDetour(t.dialer) +} + +func (t *UDPTransport) Close() error { + return t.connection.Close() +} + +func (t *UDPTransport) Reset() { + t.connection.Reset() +} + +func (t *UDPTransport) nextAvailableQueryId() (uint16, error) { + start := t.queryId + for { + t.queryId++ + if _, exists := t.callbacks[t.queryId]; !exists { + return t.queryId, nil + } + if t.queryId == start { + return 0, E.New("no available query ID") + } + } +} + +func (t *UDPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + response, err := t.exchange(ctx, message) + if err != nil { + return nil, err + } + if response.Truncated { + t.logger.InfoContext(ctx, "response truncated, retrying with TCP") + return t.exchangeTCP(ctx, message) + } + return response, nil +} + +func (t *UDPTransport) exchangeTCP(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + conn, err := t.dialer.DialContext(ctx, N.NetworkTCP, t.serverAddr) + if err != nil { + return nil, E.Cause(err, "dial TCP connection") + } + defer conn.Close() + defer setConnDeadline(ctx, conn, deadline.NeedAdditionalReadDeadline(conn))() + err = WriteMessage(conn, message.Id, message) + if err != nil { + return nil, E.Cause(err, "write request") + } + response, err := ReadMessage(conn) + if err != nil { + return nil, E.Cause(err, "read response") + } + return response, nil +} + +func (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + if edns0Opt := message.IsEdns0(); edns0Opt != nil { + udpSize := int32(edns0Opt.UDPSize()) + for { + current := t.udpSize.Load() + if udpSize <= current { + break + } + if t.udpSize.CompareAndSwap(current, udpSize) { + t.Reset() + break + } + } + } + + conn, connCtx, created, err := t.connection.AcquireShared(ctx, func(ctx context.Context) (net.Conn, error) { + rawConn, err := t.dialer.DialContext(ctx, N.NetworkUDP, t.serverAddr) + if err != nil { + return nil, E.Cause(err, "dial UDP connection") + } + return rawConn, nil + }) + if err != nil { + return nil, err + } + if created { + go t.recvLoop(conn) + } + + callback := &udpCallback{ + done: make(chan struct{}), + } + + t.callbackAccess.Lock() + queryId, err := t.nextAvailableQueryId() + if err != nil { + t.callbackAccess.Unlock() + t.connection.Release(conn, true) + return nil, err + } + t.callbacks[queryId] = callback + t.callbackAccess.Unlock() + + defer func() { + t.callbackAccess.Lock() + delete(t.callbacks, queryId) + t.callbackAccess.Unlock() + }() + + buffer := buf.NewSize(1 + message.Len()) + defer buffer.Release() + + exMessage := *message + exMessage.Compress = true + originalId := message.Id + exMessage.Id = queryId + + rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes()) + if err != nil { + return nil, err + } + + _, err = conn.Write(rawMessage) + if err != nil { + t.connection.Invalidate(conn, err) + return nil, E.Cause(err, "write request") + } + + select { + case <-callback.done: + t.connection.Release(conn, true) + callback.response.Id = originalId + return callback.response, nil + case <-connCtx.Done(): + return nil, context.Cause(connCtx) + case <-ctx.Done(): + t.connection.Release(conn, true) + return nil, ctx.Err() + } +} + +func (t *UDPTransport) recvLoop(conn net.Conn) { + for { + buffer := buf.NewSize(int(t.udpSize.Load())) + _, err := buffer.ReadOnceFrom(conn) + if err != nil { + buffer.Release() + t.connection.Invalidate(conn, err) + return + } + + var message mDNS.Msg + err = message.Unpack(buffer.Bytes()) + buffer.Release() + if err != nil { + t.logger.Debug("discarded malformed UDP response: ", err) + continue + } + + t.callbackAccess.RLock() + callback, loaded := t.callbacks[message.Id] + t.callbackAccess.RUnlock() + + if !loaded { + continue + } + + callback.access.Lock() + select { + case <-callback.done: + default: + callback.response = &message + close(callback.done) + } + callback.access.Unlock() + } +} diff --git a/dns/transport_adapter.go b/dns/transport_adapter.go new file mode 100644 index 0000000000..1e6620f25d --- /dev/null +++ b/dns/transport_adapter.go @@ -0,0 +1,55 @@ +package dns + +import ( + "github.com/sagernet/sing-box/option" +) + +type TransportAdapter struct { + transportType string + transportTag string + dependencies []string +} + +func NewTransportAdapter(transportType string, transportTag string, dependencies []string) TransportAdapter { + return TransportAdapter{ + transportType: transportType, + transportTag: transportTag, + dependencies: dependencies, + } +} + +func NewTransportAdapterWithLocalOptions(transportType string, transportTag string, localOptions option.LocalDNSServerOptions) TransportAdapter { + var dependencies []string + if localOptions.DomainResolver != nil && localOptions.DomainResolver.Server != "" { + dependencies = append(dependencies, localOptions.DomainResolver.Server) + } + return TransportAdapter{ + transportType: transportType, + transportTag: transportTag, + dependencies: dependencies, + } +} + +func NewTransportAdapterWithRemoteOptions(transportType string, transportTag string, remoteOptions option.RemoteDNSServerOptions) TransportAdapter { + var dependencies []string + if remoteOptions.DomainResolver != nil && remoteOptions.DomainResolver.Server != "" { + dependencies = append(dependencies, remoteOptions.DomainResolver.Server) + } + return TransportAdapter{ + transportType: transportType, + transportTag: transportTag, + dependencies: dependencies, + } +} + +func (a *TransportAdapter) Type() string { + return a.transportType +} + +func (a *TransportAdapter) Tag() string { + return a.transportTag +} + +func (a *TransportAdapter) Dependencies() []string { + return a.dependencies +} diff --git a/dns/transport_dialer.go b/dns/transport_dialer.go new file mode 100644 index 0000000000..971002ac40 --- /dev/null +++ b/dns/transport_dialer.go @@ -0,0 +1,26 @@ +package dns + +import ( + "context" + + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/option" + N "github.com/sagernet/sing/common/network" +) + +func NewLocalDialer(ctx context.Context, options option.LocalDNSServerOptions) (N.Dialer, error) { + return dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: options.DialerOptions, + DirectResolver: true, + }) +} + +func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions) (N.Dialer, error) { + return dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: options.DialerOptions, + RemoteIsDomain: options.ServerIsDomain(), + DirectResolver: true, + }) +} diff --git a/dns/transport_manager.go b/dns/transport_manager.go new file mode 100644 index 0000000000..e289ccea0c --- /dev/null +++ b/dns/transport_manager.go @@ -0,0 +1,300 @@ +package dns + +import ( + "context" + "io" + "os" + "strings" + "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.DNSTransportManager = (*TransportManager)(nil) + +type TransportManager struct { + logger log.ContextLogger + registry adapter.DNSTransportRegistry + outbound adapter.OutboundManager + defaultTag string + access sync.RWMutex + started bool + stage adapter.StartStage + transports []adapter.DNSTransport + transportByTag map[string]adapter.DNSTransport + dependByTag map[string][]string + defaultTransport adapter.DNSTransport + defaultTransportFallback func() (adapter.DNSTransport, error) + fakeIPTransport adapter.FakeIPTransport +} + +func NewTransportManager(logger logger.ContextLogger, registry adapter.DNSTransportRegistry, outbound adapter.OutboundManager, defaultTag string) *TransportManager { + return &TransportManager{ + logger: logger, + registry: registry, + outbound: outbound, + defaultTag: defaultTag, + transportByTag: make(map[string]adapter.DNSTransport), + dependByTag: make(map[string][]string), + } +} + +func (m *TransportManager) Initialize(defaultTransportFallback func() (adapter.DNSTransport, error)) { + m.defaultTransportFallback = defaultTransportFallback +} + +func (m *TransportManager) Start(stage adapter.StartStage) error { + m.access.Lock() + if m.started && m.stage >= stage { + panic("already started") + } + m.started = true + m.stage = stage + if stage == adapter.StartStateStart { + if m.defaultTag != "" && m.defaultTransport == nil { + m.access.Unlock() + return E.New("default DNS server not found: ", m.defaultTag) + } + if m.defaultTransport == nil { + defaultTransport, err := m.defaultTransportFallback() + if err != nil { + m.access.Unlock() + return E.Cause(err, "default DNS server fallback") + } + m.transports = append(m.transports, defaultTransport) + m.transportByTag[defaultTransport.Tag()] = defaultTransport + m.defaultTransport = defaultTransport + } + transports := m.transports + m.access.Unlock() + return m.startTransports(transports) + } else { + transports := m.transports + m.access.Unlock() + for _, outbound := range transports { + err := adapter.LegacyStart(outbound, stage) + if err != nil { + return E.Cause(err, stage, " dns/", outbound.Type(), "[", outbound.Tag(), "]") + } + } + } + return nil +} + +func (m *TransportManager) startTransports(transports []adapter.DNSTransport) error { + monitor := taskmonitor.New(m.logger, C.StartTimeout) + started := make(map[string]bool) + for { + canContinue := false + startOne: + for _, transportToStart := range transports { + transportTag := transportToStart.Tag() + if started[transportTag] { + continue + } + dependencies := transportToStart.Dependencies() + for _, dependency := range dependencies { + if !started[dependency] { + continue startOne + } + } + started[transportTag] = true + canContinue = true + if starter, isStarter := transportToStart.(adapter.Lifecycle); isStarter { + monitor.Start("start dns/", transportToStart.Type(), "[", transportTag, "]") + err := starter.Start(adapter.StartStateStart) + monitor.Finish() + if err != nil { + return E.Cause(err, "start dns/", transportToStart.Type(), "[", transportTag, "]") + } + } + } + if len(started) == len(transports) { + break + } + if canContinue { + continue + } + currentTransport := common.Find(transports, func(it adapter.DNSTransport) bool { + return !started[it.Tag()] + }) + var lintTransport func(oTree []string, oCurrent adapter.DNSTransport) error + lintTransport = func(oTree []string, oCurrent adapter.DNSTransport) error { + problemTransportTag := common.Find(oCurrent.Dependencies(), func(it string) bool { + return !started[it] + }) + if common.Contains(oTree, problemTransportTag) { + return E.New("circular server dependency: ", strings.Join(oTree, " -> "), " -> ", problemTransportTag) + } + m.access.Lock() + problemTransport := m.transportByTag[problemTransportTag] + m.access.Unlock() + if problemTransport == nil { + return E.New("dependency[", problemTransportTag, "] not found for server[", oCurrent.Tag(), "]") + } + return lintTransport(append(oTree, problemTransportTag), problemTransport) + } + return lintTransport([]string{currentTransport.Tag()}, currentTransport) + } + return nil +} + +func (m *TransportManager) Close() error { + monitor := taskmonitor.New(m.logger, C.StopTimeout) + m.access.Lock() + if !m.started { + m.access.Unlock() + return nil + } + m.started = false + transports := m.transports + m.transports = nil + m.access.Unlock() + var err error + for _, transport := range transports { + if closer, isCloser := transport.(io.Closer); isCloser { + monitor.Start("close server/", transport.Type(), "[", transport.Tag(), "]") + err = E.Append(err, closer.Close(), func(err error) error { + return E.Cause(err, "close server/", transport.Type(), "[", transport.Tag(), "]") + }) + monitor.Finish() + } + } + return nil +} + +func (m *TransportManager) Transports() []adapter.DNSTransport { + m.access.RLock() + defer m.access.RUnlock() + return m.transports +} + +func (m *TransportManager) Transport(tag string) (adapter.DNSTransport, bool) { + m.access.RLock() + outbound, found := m.transportByTag[tag] + m.access.RUnlock() + return outbound, found +} + +func (m *TransportManager) Default() adapter.DNSTransport { + m.access.RLock() + defer m.access.RUnlock() + return m.defaultTransport +} + +func (m *TransportManager) FakeIP() adapter.FakeIPTransport { + m.access.RLock() + defer m.access.RUnlock() + return m.fakeIPTransport +} + +func (m *TransportManager) Remove(tag string) error { + m.access.Lock() + defer m.access.Unlock() + transport, found := m.transportByTag[tag] + if !found { + return os.ErrInvalid + } + delete(m.transportByTag, tag) + index := common.Index(m.transports, func(it adapter.DNSTransport) bool { + return it == transport + }) + if index == -1 { + panic("invalid inbound index") + } + m.transports = append(m.transports[:index], m.transports[index+1:]...) + started := m.started + if m.defaultTransport == transport { + if len(m.transports) > 0 { + nextTransport := m.transports[0] + if nextTransport.Type() != C.DNSTypeFakeIP { + return E.New("default server cannot be fakeip") + } + m.defaultTransport = nextTransport + m.logger.Info("updated default server to ", m.defaultTransport.Tag()) + } else { + m.defaultTransport = nil + } + } + dependBy := m.dependByTag[tag] + if len(dependBy) > 0 { + return E.New("server[", tag, "] is depended by ", strings.Join(dependBy, ", ")) + } + dependencies := transport.Dependencies() + for _, dependency := range dependencies { + if len(m.dependByTag[dependency]) == 1 { + delete(m.dependByTag, dependency) + } else { + m.dependByTag[dependency] = common.Filter(m.dependByTag[dependency], func(it string) bool { + return it != tag + }) + } + } + if started { + transport.Close() + } + return nil +} + +func (m *TransportManager) Create(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) error { + if tag == "" { + return os.ErrInvalid + } + transport, err := m.registry.CreateDNSTransport(ctx, logger, tag, transportType, options) + if err != nil { + return err + } + m.access.Lock() + defer m.access.Unlock() + if m.started { + for _, stage := range adapter.ListStartStages { + err = adapter.LegacyStart(transport, stage) + if err != nil { + return E.Cause(err, stage, " dns/", transport.Type(), "[", transport.Tag(), "]") + } + } + } + if existsTransport, loaded := m.transportByTag[tag]; loaded { + if m.started { + err = common.Close(existsTransport) + if err != nil { + return E.Cause(err, "close dns/", existsTransport.Type(), "[", existsTransport.Tag(), "]") + } + } + existsIndex := common.Index(m.transports, func(it adapter.DNSTransport) bool { + return it == existsTransport + }) + if existsIndex == -1 { + panic("invalid inbound index") + } + m.transports = append(m.transports[:existsIndex], m.transports[existsIndex+1:]...) + } + m.transports = append(m.transports, transport) + m.transportByTag[tag] = transport + dependencies := transport.Dependencies() + for _, dependency := range dependencies { + m.dependByTag[dependency] = append(m.dependByTag[dependency], tag) + } + if tag == m.defaultTag || (m.defaultTag == "" && m.defaultTransport == nil) { + if transport.Type() == C.DNSTypeFakeIP { + return E.New("default server cannot be fakeip") + } + m.defaultTransport = transport + if m.started { + m.logger.Info("updated default server to ", transport.Tag()) + } + } + if transport.Type() == C.DNSTypeFakeIP { + if m.fakeIPTransport != nil { + return E.New("multiple fakeip server are not supported") + } + m.fakeIPTransport = transport.(adapter.FakeIPTransport) + } + return nil +} diff --git a/dns/transport_registry.go b/dns/transport_registry.go new file mode 100644 index 0000000000..d838158b22 --- /dev/null +++ b/dns/transport_registry.go @@ -0,0 +1,72 @@ +package dns + +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 TransportConstructorFunc[T any] func(ctx context.Context, logger log.ContextLogger, tag string, options T) (adapter.DNSTransport, error) + +func RegisterTransport[Options any](registry *TransportRegistry, transportType string, constructor TransportConstructorFunc[Options]) { + registry.register(transportType, func() any { + return new(Options) + }, func(ctx context.Context, logger log.ContextLogger, tag string, rawOptions any) (adapter.DNSTransport, error) { + var options *Options + if rawOptions != nil { + options = rawOptions.(*Options) + } + return constructor(ctx, logger, tag, common.PtrValueOrDefault(options)) + }) +} + +var _ adapter.DNSTransportRegistry = (*TransportRegistry)(nil) + +type ( + optionsConstructorFunc func() any + constructorFunc func(ctx context.Context, logger log.ContextLogger, tag string, options any) (adapter.DNSTransport, error) +) + +type TransportRegistry struct { + access sync.Mutex + optionsType map[string]optionsConstructorFunc + constructors map[string]constructorFunc +} + +func NewTransportRegistry() *TransportRegistry { + return &TransportRegistry{ + optionsType: make(map[string]optionsConstructorFunc), + constructors: make(map[string]constructorFunc), + } +} + +func (r *TransportRegistry) CreateOptions(transportType string) (any, bool) { + r.access.Lock() + defer r.access.Unlock() + optionsConstructor, loaded := r.optionsType[transportType] + if !loaded { + return nil, false + } + return optionsConstructor(), true +} + +func (r *TransportRegistry) CreateDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) (adapter.DNSTransport, error) { + r.access.Lock() + defer r.access.Unlock() + constructor, loaded := r.constructors[transportType] + if !loaded { + return nil, E.New("transport type not found: " + transportType) + } + return constructor(ctx, logger, tag, options) +} + +func (r *TransportRegistry) register(transportType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) { + r.access.Lock() + defer r.access.Unlock() + r.optionsType[transportType] = optionsConstructor + r.constructors[transportType] = constructor +} diff --git a/docs/changelog.md b/docs/changelog.md index 2ba895beb9..f370c934e1 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,11 +2,2145 @@ icon: material/alert-decagram --- -!!! failure "Help needed" +#### 1.14.0-alpha.27 - Due to problems with our Apple developer account, sing-box apps on Apple platforms are temporarily unavailable for download or update. +* Add Firebase Tunnel endpoint **1** +* Fixes and improvements + +**1**: + +Adds `firebasetunnel_client` and `firebasetunnel_server` endpoints, which +relay TCP connections through Firebase Realtime Database for use when +direct connectivity is blocked but Firebase's REST API is reachable. +Supports multi-user accounting (wired into the existing SSM traffic stats +used for Shadowsocks usage), optional per-user PSK payload encryption, and +an optional Firebase Auth token alternative to the legacy database secret. +See [Firebase Tunnel docs](https://github.com/hiddify/hiddify-sing-box/tree/testing-merge-base/protocol/firebasetunnel/docs) +for configuration, security considerations, and operational guidance. +Adapted from [Hiddify2/Firebase-Tunnel](https://github.com/Hiddify2/Firebase-Tunnel); +see the provenance doc for what was reused vs. rewritten. + +#### 1.14.0-alpha.26 + +* Add gecko obfs for Hysteria2 **1** +* Fixes and improvements + +**1**: + +Adds `gecko` as a new QUIC traffic obfuscation type for +[Hysteria2 inbound](/configuration/inbound/hysteria2/#obfstype) and +[outbound](/configuration/outbound/hysteria2/#obfstype), alongside the +existing `salamander`. Gecko supports configurable +[`min_packet_size`](/configuration/inbound/hysteria2/#obfsmin_packet_size) +(default 512) and +[`max_packet_size`](/configuration/inbound/hysteria2/#obfsmax_packet_size) +(default 1200) fields. + +#### 1.14.0-alpha.25 + +* Revert Tailscale endpoint dial fields deprecation and remove `control_http_client` **1** +* Fixes and improvements + +**1**: + +The `control_http_client` field on +[Tailscale](/configuration/endpoint/tailscale/) endpoints introduced in +`1.14.0-alpha.13` is removed, and the deprecation of +[Dial Fields](/configuration/endpoint/tailscale/#dial-fields) is reverted. + +#### 1.13.12 + +* Update naiveproxy to v148.0.7778.96-1 +* Fixes and improvements + +#### 1.14.0-alpha.22 + +* Add Hysteria Realm service and Hysteria2 NAT traversal support **1** +* Fixes and improvements + +**1**: + +The new [Hysteria Realm service](/configuration/service/hysteria-realm/) +is a rendezvous service for Hysteria2 NAT traversal. A Hysteria2 server +behind NAT registers its STUN-discovered public addresses on a stable +realm endpoint via the new +[`realm`](/configuration/inbound/hysteria2/#realm) inbound field; +clients query the realm via the new +[`realm`](/configuration/outbound/hysteria2/#realm) outbound field to +learn the server's current addresses and perform UDP hole-punching to +establish a direct QUIC connection. Once hole-punching succeeds, all +proxy traffic flows directly between client and server. + +#### 1.14.0-alpha.21 + +* Allow customizing TUN DNS mode and hijack interface DNS by default **1** +* Add mDNS DNS server **2** +* Add `preferred_by` DNS rule item **3** +* Add neighbor-based hostname resolution for the local DNS server **4** +* Update NaiveProxy to 148.0.7778.96-1 +* Add more TLS spoof methods and route rule action support **5** +* Fixes and improvements + +**1**: + +Adds [`dns_mode`](/configuration/inbound/tun/#dns_mode) and +[`dns_address`](/configuration/inbound/tun/#dns_address) on the TUN inbound. +The default `hijack` mode now sets the platform's native interface DNS +(`systemd-resolved` on Linux, per-interface DNS on Windows and Apple) and +installs platform-level DNS hijacking (an `iproute2` rule on Linux, +nftables DNAT when `auto_redirect` is enabled, WFP filters on Windows when +`strict_route` is enabled). Earlier versions did not touch the interface +DNS or the platform firewall. + +**2**: + +The new [mDNS DNS server](/configuration/dns/server/mdns/) sends queries via +multicast on the local network. The default +[local DNS server](/configuration/dns/server/local/) also routes queries for +`*.local.` and IPv4/IPv6 link-local reverse zones via mDNS on non-Apple +platforms (and via the system resolver on Apple), so an explicit `mdns` +server is only needed to reference it from +[`preferred_by`](/configuration/dns/rule/#preferred_by) or to use it +standalone. + +**3**: + +The new [`preferred_by`](/configuration/dns/rule/#preferred_by) DNS rule +item matches domains that the listed DNS servers consider their preferred +names. Supported server types are `hosts`, `local`, `mdns`, `tailscale`, and +`resolved`. The [Tailscale](/configuration/dns/server/tailscale/), +[Hosts](/configuration/dns/server/hosts/) and +[Resolved](/configuration/dns/server/resolved/) example pages have been +updated to use this rule item in place of the previous `evaluate` + +`ip_accept_any` + `respond` pattern. + +**4**: + +Adds [`neighbor_domain`](/configuration/dns/server/local/#neighbor_domain) +on the local DNS server. Listed suffixes (each starting with `.`) cause +A/AAAA queries for single-label hosts under those suffixes to be answered +from the [neighbor resolver](/configuration/shared/neighbor/) instead of +the upstream (for example `[".", ".lan"]`). + +**5**: + +Adds `wrong-ack`, `wrong-md5`, and `wrong-timestamp` +[spoof methods](/configuration/shared/tls/#spoof_method), and adds +[`tls_spoof`](/configuration/route/rule_action/#tls_spoof) / +[`tls_spoof_method`](/configuration/route/rule_action/#tls_spoof_method) +to route rule actions for per-rule TLS spoofing without outbound TLS settings. + +#### 1.14.0-alpha.20 + +** Fixes and improvements + +#### 1.14.0-alpha.19 + +* Preserve comments between formatting +* Add cipher, MAC, and key exchange algorithm options for SSH outbound **1** +* Add DNS query timeout options **2** +** Fixes and improvements + +**1**: + +See [SSH](/configuration/outbound/ssh/#cipher). + +**2**: + +Adds [`dns.timeout`](/configuration/dns/#timeout), with per-query +overrides via [DNS rule action](/configuration/dns/rule_action/#timeout) +and [`resolve` route rule action](/configuration/route/rule_action/#timeout), +and a `timeout` field on +[`domain_resolver`](/configuration/shared/dial/#domain_resolver). + +#### 1.14.0-alpha.18 + +* Add Windows TLS engine **1** +* Fixes and improvements + +**1**: + +The new `windows` value for outbound TLS +[`engine`](/configuration/shared/tls/#engine) routes the TLS handshake +through Schannel via SSPI. Only available on Windows build 17763 or +later (Windows 10 version 1809, Windows Server 2019, or newer); TLS 1.3 +is only negotiated on Windows 11 or Windows Server 2022 and newer. + +#### 1.13.11 + +* Fix process searcher failure introduced in 1.13.9 +* Fixes and improvements + +#### 1.14.0-alpha.16 + +* Add ACME profile support for IP address certificates **1** +* Fixes and improvements + +**1**: + +See [ACME Certificate Provider](/configuration/shared/certificate-provider/acme/#profile). + +#### 1.13.10 + +* Fix process searcher failure introduced in 1.13.9 + +#### 1.14.0-alpha.15 + +* Add search domain support for Tailscale DNS **1** +* Fixes and improvements + +**1**: + +See [Tailscale DNS Server](/configuration/dns/server/tailscale/#accept_search_domain). + +#### 1.13.9 + +* Fixes and improvements + +#### 1.14.0-alpha.13 + +* Unify HTTP client **1** +* Add Apple HTTP and TLS engines **2** +* Unify HTTP/2 and QUIC parameters **3** +* Add TLS spoof **4** +* Fixes and improvements + +**1**: + +The new top-level [`http_clients`](/configuration/shared/http-client/) +option defines reusable HTTP clients (engine, version, dialer, TLS, +HTTP/2 and QUIC parameters). Components that make outbound HTTP requests +— remote rule-sets, ACME and Cloudflare Origin CA certificate providers, +DERP `verify_client_url`, and the Tailscale `control_http_client` — now +accept an inline HTTP client object or the tag of an `http_clients` +entry, replacing the dial and TLS fields previously inlined in each +component. When the field is omitted, ACME, Cloudflare Origin CA, DERP +and Tailscale dial direct (their existing default). + +Remote rule-sets are the only HTTP-using component whose default for an +omitted `http_client` has historically resolved to the default outbound, +not to direct, and a typical configuration contains many of them. To +avoid repeating the same `http_client` block in every rule-set, +[`route.default_http_client`](/configuration/route/#default_http_client) +selects a default rule-set client by tag and is the only field that +consults it. If `default_http_client` is empty and `http_clients` is +non-empty, the first entry is used automatically. The legacy fallback +(use the default outbound when `http_clients` is empty altogether) is +preserved with a deprecation warning and will be removed in sing-box +1.16.0, together with the legacy `download_detour` remote rule-set +option and the legacy dialer fields on Tailscale endpoints. + +**2**: + +A new `apple` engine is available on Apple platforms in two independent +places: + +* [HTTP client `engine`](/configuration/shared/http-client/#engine) — + routes HTTP requests through `NSURLSession`. +* Outbound TLS [`engine`](/configuration/shared/tls/#engine) — routes + the TLS handshake through `Network.framework` for direct TCP TLS + client connections. + +The default remains `go`. Both engines come with additional CGO and +framework memory overhead and platform restrictions documented on each +field. + +**3**: + +[HTTP/2](/configuration/shared/http2/) and +[QUIC](/configuration/shared/quic/) parameters +(`idle_timeout`, `keep_alive_period`, `stream_receive_window`, +`connection_receive_window`, `max_concurrent_streams`, +`initial_packet_size`, `disable_path_mtu_discovery`) are now shared +across QUIC-based outbounds +([Hysteria](/configuration/outbound/hysteria/), +[Hysteria2](/configuration/outbound/hysteria2/), +[TUIC](/configuration/outbound/tuic/)) and HTTP clients running HTTP/2 +or HTTP/3. + +This deprecates the Hysteria v1 tuning fields `recv_window_conn`, +`recv_window`, `recv_window_client`, `max_conn_client` and +`disable_mtu_discovery`; they will be removed in sing-box 1.16.0. + +**4**: + +Added outbound TLS [`spoof`](/configuration/shared/tls/#spoof) and +[`spoof_method`](/configuration/shared/tls/#spoof_method) fields. When +enabled, a forged ClientHello carrying a whitelisted SNI is sent before +the real handshake to fool SNI-filtering middleboxes. Requires +`CAP_NET_RAW` + `CAP_NET_ADMIN` or root on Linux and macOS, and +Administrator privileges on Windows (ARM64 is not supported). IP-literal +server names are rejected. + +#### 1.14.0-alpha.12 + +* Fix fake-ip DNS server should return SUCCESS when address type is not configured +* Fixes and improvements + +#### 1.13.8 + +* Update naiveproxy to v147.0.7727.49-1 +* Fix fake-ip DNS server should return SUCCESS when address type is not configured +* Fixes and improvements + +#### 1.14.0-alpha.11 + +* Add optimistic DNS cache **1** +* Update NaiveProxy to 147.0.7727.49 +* Fixes and improvements + +**1**: + +Optimistic DNS cache returns an expired cached response immediately while +refreshing it in the background, reducing tail latency for repeated +queries. Enabled via [`optimistic`](/configuration/dns/#optimistic) +in DNS options, and can be persisted across restarts with the new +[`store_dns`](/configuration/experimental/cache-file/#store_dns) cache +file option. A per-query +[`disable_optimistic_cache`](/configuration/dns/rule_action/#disable_optimistic_cache) +field is also available on DNS rule actions and the `resolve` route rule +action. + +This deprecates the `independent_cache` DNS option (the DNS cache now +always keys by transport) and the `store_rdrc` cache file option +(replaced by `store_dns`); both will be removed in sing-box 1.16.0. +See [Migration](/migration/#migrate-independent-dns-cache). + +#### 1.14.0-alpha.10 + +* Add `evaluate` DNS rule action and Response Match Fields **1** +* `ip_version` and `query_type` now also take effect on internal DNS lookups **2** +* Add `package_name_regex` route, DNS and headless rule item **3** +* Add cloudflared inbound **4** +* Fixes and improvements + +**1**: + +Response Match Fields +([`response_rcode`](/configuration/dns/rule/#response_rcode), +[`response_answer`](/configuration/dns/rule/#response_answer), +[`response_ns`](/configuration/dns/rule/#response_ns), +and [`response_extra`](/configuration/dns/rule/#response_extra)) +match the evaluated DNS response. They are gated by the new +[`match_response`](/configuration/dns/rule/#match_response) field and +populated by a preceding +[`evaluate`](/configuration/dns/rule_action/#evaluate) DNS rule action; +the evaluated response can also be returned directly by a +[`respond`](/configuration/dns/rule_action/#respond) action. + +This deprecates the Legacy Address Filter Fields (`ip_cidr`, +`ip_is_private` without `match_response`) in DNS rules, the Legacy +`strategy` DNS rule action option, and the Legacy +`rule_set_ip_cidr_accept_empty` DNS rule item; all three will be removed +in sing-box 1.16.0. +See [Migration](/migration/#migrate-address-filter-fields-to-response-matching). + +**2**: + +`ip_version` and `query_type` in DNS rules, together with `query_type` in +referenced rule-sets, now take effect on every DNS rule evaluation, +including matches from internal domain resolutions that do not target a +specific DNS server (for example a `resolve` route rule action without +`server` set). In earlier versions they were silently ignored in that +path. Combining these fields with any of the legacy DNS fields deprecated +in **1** in the same DNS configuration is no longer supported and is +rejected at startup. +See [Migration](/migration/#ip_version-and-query_type-behavior-changes-in-dns-rules). + +**3**: + +See [Route Rule](/configuration/route/rule/#package_name_regex), +[DNS Rule](/configuration/dns/rule/#package_name_regex) and +[Headless Rule](/configuration/rule-set/headless-rule/#package_name_regex). + +**4**: + +See [Cloudflared](/configuration/inbound/cloudflared/). + +#### 1.13.7 + +* Fixes and improvement + +#### 1.13.6 + +* Fixes and improvements + +#### 1.14.0-alpha.8 + +* Add BBR profile and hop interval randomization for Hysteria2 **1** +* Fixes and improvements + +**1**: + +See [Hysteria2 Inbound](/configuration/inbound/hysteria2/#bbr_profile) and [Hysteria2 Outbound](/configuration/outbound/hysteria2/#bbr_profile). + +#### 1.13.5 + +* Fixes and improvements + +#### 1.14.0-alpha.7 + +* Fixes and improvements + +#### 1.13.4 + +* Fixes and improvements + +#### 1.14.0-alpha.4 + +* Refactor ACME support to certificate provider system **1** +* Add Cloudflare Origin CA certificate provider **2** +* Add Tailscale certificate provider **3** +* Fixes and improvements + +**1**: + +See [Certificate Provider](/configuration/shared/certificate-provider/) and [Migration](/migration/#migrate-inline-acme-to-certificate-provider). + +**2**: + +See [Cloudflare Origin CA](/configuration/shared/certificate-provider/cloudflare-origin-ca). + +**3**: + +See [Tailscale](/configuration/shared/certificate-provider/tailscale). + +#### 1.13.3 + +* Add OpenWrt and Alpine APK packages to release **1** +* Backport to macOS 10.13 High Sierra **2** +* OCM service: Add WebSocket support for Responses API **3** +* Fixes and improvements + +**1**: + +Alpine APK files use `linux` in the filename to distinguish from OpenWrt APKs which use the `openwrt` prefix: + +- OpenWrt: `sing-box_{version}_openwrt_{architecture}.apk` +- Alpine: `sing-box_{version}_linux_{architecture}.apk` + +**2**: + +Legacy macOS binaries (with `-legacy-macos-10.13` suffix) now support +macOS 10.13 High Sierra, built using Go 1.25 with patches +from [SagerNet/go](https://github.com/SagerNet/go). + +**3**: + +See [OCM](/configuration/service/ocm). + +#### 1.12.24 + +* Fixes and improvements + +#### 1.14.0-alpha.2 + +* Add OpenWrt and Alpine APK packages to release **1** +* Backport to macOS 10.13 High Sierra **2** +* OCM service: Add WebSocket support for Responses API **3** +* Fixes and improvements + +**1**: + +Alpine APK files use `linux` in the filename to distinguish from OpenWrt APKs which use the `openwrt` prefix: + +- OpenWrt: `sing-box_{version}_openwrt_{architecture}.apk` +- Alpine: `sing-box_{version}_linux_{architecture}.apk` + +**2**: + +Legacy macOS binaries (with `-legacy-macos-10.13` suffix) now support +macOS 10.13 High Sierra, built using Go 1.25 with patches +from [SagerNet/go](https://github.com/SagerNet/go). + +**3**: + +See [OCM](/configuration/service/ocm). + +#### 1.14.0-alpha.1 + +* Add `source_mac_address` and `source_hostname` rule items **1** +* Add `include_mac_address` and `exclude_mac_address` TUN options **2** +* Update NaiveProxy to 145.0.7632.159 **3** +* Fixes and improvements + +**1**: + +New rule items for matching LAN devices by MAC address and hostname via neighbor resolution. +Supported on Linux, macOS, or in graphical clients on Android and macOS. + +See [Route Rule](/configuration/route/rule/#source_mac_address), [DNS Rule](/configuration/dns/rule/#source_mac_address) and [Neighbor Resolution](/configuration/shared/neighbor/). + +**2**: + +Limit or exclude devices from TUN routing by MAC address. +Only supported on Linux with `auto_route` and `auto_redirect` enabled. + +See [TUN](/configuration/inbound/tun/#include_mac_address). + +**3**: + +This is not an official update from NaiveProxy. Instead, it's a Chromium codebase update maintained by Project S. + +#### 1.13.2 + +* Fixes and improvements + +#### 1.13.1 + +* Fixes and improvements + +#### 1.12.14 + +* Backport fixes + +#### 1.13.0 + +Important changes since 1.12: + +* Add NaiveProxy outbound **1** +* Add pre-match support for `auto_redirect` **2** +* Improve `auto_redirect` **3** +* Add Chrome Root Store certificate option **4** +* Add new options for ACME DNS-01 challenge providers **5** +* Add Wi-Fi state support for Linux and Windows **6** +* Add curve preferences, pinned public key SHA256, mTLS and ECH `query_server_name` for TLS options **7** +* Add kTLS support **8** +* Add ICMP echo (ping) proxy support **9** +* Add `interface_address`, `network_interface_address` and `default_interface_address` rule items **10** +* Add `preferred_by` route rule item **11** +* Improve `local` DNS server **12** +* Add `disable_tcp_keep_alive`, `tcp_keep_alive` and `tcp_keep_alive_interval` options for listen and dial fields **13** +* Add `bind_address_no_port` option for dial fields **14** +* Add system interface, relay server and advertise tags options for Tailscale endpoint **15** +* Add Claude Code Multiplexer service **16** +* Add OpenAI Codex Multiplexer service **17** +* Apple/Android: Refactor GUI +* Apple/Android: Add support for sharing configurations via [QRS](https://github.com/qifi-dev/qrs) +* Android: Add support for resisting VPN detection via Xposed +* Drop support for go1.23 **18** +* Drop support for Android 5.0 **19** +* Update uTLS to v1.8.2 **20** +* Update quic-go to v0.59.0 +* Update gVisor to v20250811 +* Update Tailscale to v1.92.4 + +**1**: + +NaiveProxy outbound now supports QUIC, ECH, UDP over TCP, and configurable QUIC congestion control. + +Only available on Apple platforms, Android, Windows and some Linux architectures. +Each Windows release includes `libcronet.dll` — +ensure this file is in the same directory as `sing-box.exe` or in a directory listed in `PATH`. + +See [NaiveProxy outbound](/configuration/outbound/naive/). + +**2**: + +`auto_redirect` now allows you to bypass sing-box for connections based on routing rules. + +A new rule action `bypass` is introduced to support this feature. When matched during pre-match, the connection will bypass sing-box and connect directly. + +This feature requires Linux with `auto_redirect` enabled. + +See [Pre-match](/configuration/shared/pre-match/) and [Rule Action](/configuration/route/rule_action/#bypass). + +**3**: + +`auto_redirect` now rejects MPTCP connections by default to fix compatibility issues. +You can change it to bypass sing-box via the new `exclude_mptcp` option. + +Adds a fallback iproute2 rule checked after system default rules (32766: main, 32767: default), +ensuring traffic is routed to the sing-box table when no route is found in system tables. +The rule index can be customized via `auto_redirect_iproute2_fallback_rule_index` (default: 32768). + +See [TUN](/configuration/inbound/tun/#exclude_mptcp). + +**4**: + +Adds `chrome` as a new certificate store option alongside `mozilla`. +Both stores filter out China-based CA certificates. + +See [Certificate](/configuration/certificate/#store). + +**5**: + +See [DNS-01 Challenge](/configuration/shared/dns01_challenge/). + +**6**: + +sing-box can now monitor Wi-Fi state on Linux and Windows to enable routing rules based on `wifi_ssid` and `wifi_bssid`. + +See [Wi-Fi State](/configuration/shared/wifi-state/). + +**7**: + +See [TLS](/configuration/shared/tls/). + +**8**: + +Adds `kernel_tx` and `kernel_rx` options for TLS inbound. +Enables kernel-level TLS offloading via `splice(2)` on Linux 5.1+ with TLS 1.3. + +See [TLS](/configuration/shared/tls/). + +**9**: + +sing-box can now proxy ICMP echo (ping) requests. +A new `icmp` network type is available for route rules. +Supported from TUN, WireGuard and Tailscale inbounds to Direct, WireGuard and Tailscale outbounds. +The `reject` action can also reply to ICMP echo requests. + +**10**: + +New rule items for matching based on interface IP addresses, available in route rules, DNS rules and rule-sets. + +**11**: + +Matches outbounds' preferred routes. +For Tailscale: MagicDNS domains and peers' allowed IPs. For WireGuard: peers' allowed IPs. + +**12**: + +The `local` DNS server now uses platform-native resolution: +`getaddrinfo`/libresolv on Apple platforms, systemd-resolved DBus on Linux. +A new `prefer_go` option is available to opt out. + +See [Local DNS](/configuration/dns/server/local/). + +**13**: + +The default TCP keep-alive initial period has been updated from 10 minutes to 5 minutes. + +See [Dial Fields](/configuration/shared/dial/#tcp_keep_alive). + +**14**: + +Adds the Linux socket option `IP_BIND_ADDRESS_NO_PORT` support when explicitly binding to a source address. + +This allows reusing the same source port for multiple connections, improving scalability for high-concurrency proxy scenarios. + +See [Dial Fields](/configuration/shared/dial/#bind_address_no_port). + +**15**: + +Tailscale endpoint can now create a system TUN interface to handle traffic directly. +New `relay_server_port` and `relay_server_static_endpoints` options for incoming relay connections. +New `advertise_tags` option for ACL tag advertisement. + +See [Tailscale endpoint](/configuration/endpoint/tailscale/). + +**16**: + +CCM (Claude Code Multiplexer) service allows you to access your local Claude Code subscription remotely through custom tokens, eliminating the need for OAuth authentication on remote clients. + +See [CCM](/configuration/service/ccm). + +**17**: + +See [OCM](/configuration/service/ocm). + +**18**: + +Due to maintenance difficulties, sing-box 1.13.0 requires at least Go 1.24 to compile. + +**19**: + +Due to maintenance difficulties, sing-box 1.13.0 will be the last version to support Android 5.0, +and only through a separate legacy build (with `-legacy-android-5` suffix). + +For standalone binaries, the minimum Android version has been raised to Android 6.0, +since Termux requires Android 7.0 or later. + +**20**: + +This update fixes missing padding extension for Chrome 120+ fingerprints. + +Also, documentation has been updated with a warning about uTLS fingerprinting vulnerabilities. +uTLS is not recommended for censorship circumvention due to fundamental architectural limitations; +use NaiveProxy instead for TLS fingerprint resistance. + +#### 1.12.23 + +* Fixes and improvements + +#### 1.13.0-rc.5 + +* Add `mipsle`, `mips64le`, `riscv64` and `loong64` support for NaiveProxy outbound + +#### 1.12.22 + +* Fixes and improvements + +#### 1.13.0-rc.3 + +* Fixes and improvements + +#### 1.12.21 + +* Fixes and improvements + +#### 1.13.0-rc.2 + +* Fixes and improvements + +#### 1.12.20 + +* Fixes and improvements + +#### 1.13.0-rc.1 + +* Fixes and improvements + +#### 1.12.19 + +* Fixes and improvements + +#### 1.13.0-beta.8 + +* Add fallback routing rule for `auto_redirect` **1** +* Fixes and improvements + +**1**: + +Adds a fallback iproute2 rule checked after system default rules (32766: main, 32767: default), +ensuring traffic is routed to the sing-box table when no route is found in system tables. + +The rule index can be customized via `auto_redirect_iproute2_fallback_rule_index` (default: 32768). + +#### 1.12.18 + +* Add fallback routing rule for `auto_redirect` **1** +* Fixes and improvements + +**1**: + +Adds a fallback iproute2 rule checked after system default rules (32766: main, 32767: default), +ensuring traffic is routed to the sing-box table when no route is found in system tables. + +The rule index can be customized via `auto_redirect_iproute2_fallback_rule_index` (default: 32768). + +#### 1.13.0-beta.6 + +* Update uTLS to v1.8.2 **1** +* Fixes and improvements + +**1**: + +This update fixes missing padding extension for Chrome 120+ fingerprints. + +Also, documentation has been updated with a warning about uTLS fingerprinting vulnerabilities. +uTLS is not recommended for censorship circumvention due to fundamental architectural limitations; +use NaiveProxy instead for TLS fingerprint resistance. + +#### 1.12.17 + +* Update uTLS to v1.8.2 **1** +* Fixes and improvements + +**1**: + +This update fixes missing padding extension for Chrome 120+ fingerprints. + +Also, documentation has been updated with a warning about uTLS fingerprinting vulnerabilities. +uTLS is not recommended for censorship circumvention due to fundamental architectural limitations; +use NaiveProxy instead for TLS fingerprint resistance. + +#### 1.13.0-beta.5 + +* Fixes and improvements + +#### 1.12.16 + +* Fixes and improvements + +#### 1.13.0-beta.4 + +* Apple/Android: Add support for sharing configurations via [QRS](https://github.com/qifi-dev/qrs) +* Android: Add support for resisting VPN detection via Xposed +* Update quic-go to v0.59.0 +* Fixes and improvements + +#### 1.13.0-beta.2 + +* Add `bind_address_no_port` option for dial fields **1** +* Fixes and improvements + +**1**: + +Adds the Linux socket option `IP_BIND_ADDRESS_NO_PORT` support when explicitly binding to a source address. + +This allows reusing the same source port for multiple connections, improving scalability for high-concurrency proxy scenarios. + +See [Dial Fields](/configuration/shared/dial/#bind_address_no_port). + +#### 1.13.0-beta.1 + +* Add system interface support for Tailscale endpoint **1** +* Fixes and improvements + +**1**: + +Tailscale endpoint can now create a system TUN interface to handle traffic directly. + +See [Tailscale endpoint](/configuration/endpoint/tailscale/#system_interface). + +#### 1.12.15 + +* Fixes and improvements + +#### 1.13.0-alpha.36 + +* Downgrade quic-go to v0.57.1 +* Fixes and improvements + +#### 1.13.0-alpha.35 + +* Add pre-match support for `auto_redirect` **1** +* Fixes and improvements + +**1**: + +`auto_redirect` now allows you to bypass sing-box for connections based on routing rules. + +A new rule action `bypass` is introduced to support this feature. When matched during pre-match, the connection will bypass sing-box and connect directly. + +This feature requires Linux with `auto_redirect` enabled. + +See [Pre-match](/configuration/shared/pre-match/) and [Rule Action](/configuration/route/rule_action/#bypass). + +#### 1.13.0-alpha.34 + +* Add Chrome Root Store certificate option **1** +* Add new options for ACME DNS-01 challenge providers **2** +* Add Wi-Fi state support for Linux and Windows **3** +* Update naiveproxy to 143.0.7499.109 +* Update quic-go to v0.58.0 +* Update tailscale to v1.92.4 +* Drop support for go1.23 **4** +* Drop support for Android 5.0 **5** + +**1**: + +Adds `chrome` as a new certificate store option alongside `mozilla`. +Both stores filter out China-based CA certificates. + +See [Certificate](/configuration/certificate/#store). + +**2**: + +See [DNS-01 Challenge](/configuration/shared/dns01_challenge/). + +**3**: + +sing-box can now monitor Wi-Fi state on Linux and Windows to enable routing rules based on `wifi_ssid` and `wifi_bssid`. + +See [Wi-Fi State](/configuration/shared/wifi-state/). + +**4**: + +Due to maintenance difficulties, sing-box 1.13.0 requires at least Go 1.24 to compile. + +**5**: + +Due to maintenance difficulties, sing-box 1.13.0 will be the last version to support Android 5.0, +and only through a separate legacy build (with `-legacy-android-5` suffix). + +For standalone binaries, the minimum Android version has been raised to Android 6.0, +since Termux requires Android 7.0 or later. + +#### 1.12.14 + +* Fixes and improvements + +#### 1.13.0-alpha.33 + +* Fixes and improvements + +#### 1.13.0-alpha.32 + +* Remove `certificate_public_key_sha256` option for NaiveProxy outbound **1** +* Fixes and improvements + +**1**: + +Self-signed certificates change traffic behavior significantly, which defeats the purpose of NaiveProxy's design to resist traffic analysis. +For this reason, and due to maintenance costs, there is no reason to continue supporting `certificate_public_key_sha256`, which was designed to simplify the use of self-signed certificates. + +#### 1.13.0-alpha.31 + +* Add QUIC support for NaiveProxy outbound **1** +* Add QUIC congestion control option for NaiveProxy **2** +* Fixes and improvements + +**1**: + +NaiveProxy outbound now supports QUIC. + +See [NaiveProxy outbound](/configuration/outbound/naive/#quic). + +**2**: + +NaiveProxy inbound and outbound now supports configurable QUIC congestion control algorithms, including BBR and BBRv2. + +See [NaiveProxy inbound](/configuration/inbound/naive/#quic_congestion_control) and [NaiveProxy outbound](/configuration/outbound/naive/#quic_congestion_control). + +#### 1.13.0-alpha.30 + +* Add ECH support for NaiveProxy outbound **1** +* Add `tls.ech.query_server_name` option **2** +* Fix NaiveProxy outbound on Windows **3** +* Add OpenAI Codex Multiplexer service **4** +* Fixes and improvements + +**1**: + +See [NaiveProxy outbound](/configuration/outbound/naive/#tls). + +**2**: + +See [TLS](/configuration/shared/tls/#query_server_name). + +**3**: + +Each Windows release now includes `libcronet.dll`. +Ensure this file is in the same directory as `sing-box.exe` or in a directory listed in `PATH`. + +**4**: + +See [OCM](/configuration/service/ocm). + +#### 1.13.0-alpha.29 + +* Add UDP over TCP support for naiveproxy outbound **1** +* Fixes and improvements + +**1**: + +See [NaiveProxy outbound](/configuration/outbound/naive/#udp_over_tcp). + +#### 1.13.0-alpha.28 + +* Add naiveproxy outbound **1** +* Add `disable_tcp_keep_alive`, `tcp_keep_alive` and `tcp_keep_alive_interval` options for dial fields **2** +* Update default TCP keep-alive initial period from 10 minutes to 5 minutes +* Update quic-go to v0.57.1 +* Fixes and improvements + +**1**: + +Only available on Apple platforms, Android, Windows and some Linux architectures. + +See [NaiveProxy outbound](/configuration/outbound/naive/). + +**2**: + +See [Dial Fields](/configuration/shared/dial/#tcp_keep_alive). + +* __Unfortunately, for non-technical reasons, we are currently unable to notarize the standalone version of the macOS client: +because system extensions require signatures to function, we have had to temporarily halt its release.__ + +__We plan to fix the App Store release issue and launch a new standalone desktop client, but until then, +only clients on TestFlight will be available (unless you have an Apple Developer Program and compile from source code).__ + + +#### 1.12.13 + +* Fix naive inbound +* Fixes and improvements + +__Unfortunately, for non-technical reasons, we are currently unable to notarize the standalone version of the macOS client: +because system extensions require signatures to function, we have had to temporarily halt its release.__ + +__We plan to fix the App Store release issue and launch a new standalone desktop client, but until then, +only clients on TestFlight will be available (unless you have an Apple Developer Program and compile from source code).__ + +#### 1.12.12 + +* Fixes and improvements + +#### 1.13.0-alpha.26 + +* Update quic-go to v0.55.0 +* Fix memory leak in hysteria2 +* Fixes and improvements + +#### 1.12.11 + +* Fixes and improvements + +#### 1.13.0-alpha.24 + +* Add Claude Code Multiplexer service **1** +* Fixes and improvements + +**1**: + +CCM (Claude Code Multiplexer) service allows you to access your local Claude Code subscription remotely through custom tokens, eliminating the need for OAuth authentication on remote clients. + +See [CCM](/configuration/service/ccm). + +#### 1.13.0-alpha.23 + +* Fix compatibility with MPTCP **1** +* Fixes and improvements + +**1**: + +`auto_redirect` now rejects MPTCP connections by default to fix compatibility issues, +but you can change it to bypass the sing-box via the new `exclude_mptcp` option. + +See [TUN](/configuration/inbound/tun/#exclude_mptcp). + +#### 1.13.0-alpha.22 + +* Update uTLS to v1.8.1 **1** +* Fixes and improvements + +**1**: + +This update fixes an critical issue that could cause simulated Chrome fingerprints to be detected, +see https://github.com/refraction-networking/utls/pull/375. + +#### 1.12.10 + +* Update uTLS to v1.8.1 **1** +* Fixes and improvements + +**1**: + +This update fixes an critical issue that could cause simulated Chrome fingerprints to be detected, +see https://github.com/refraction-networking/utls/pull/375. + +#### 1.13.0-alpha.21 + +* Fix missing mTLS support in client options **1** +* Fixes and improvements + +See [TLS](/configuration/shared/tls/). + +#### 1.12.9 + +* Fixes and improvements + +#### 1.13.0-alpha.16 + +* Add curve preferences, pinned public key SHA256 and mTLS for TLS options **1** +* Fixes and improvements + +See [TLS](/configuration/shared/tls/). + +#### 1.13.0-alpha.15 + +* Update quic-go to v0.54.0 +* Update gVisor to v20250811 +* Update Tailscale to v1.86.5 +* Fixes and improvements + +#### 1.12.8 + +* Fixes and improvements + +#### 1.13.0-alpha.11 + +* Fixes and improvements + +#### 1.12.5 + +* Fixes and improvements + +#### 1.13.0-alpha.10 + +* Improve kTLS support **1** +* Fixes and improvements + +**1**: + +kTLS is now compatible with custom TLS implementations other than uTLS. + +#### 1.12.4 + +* Fixes and improvements + +#### 1.12.3 + +* Fixes and improvements + +#### 1.12.2 + +* Fixes and improvements + +#### 1.12.1 + +* Fixes and improvements + +#### 1.12.0 + +* Refactor DNS servers **1** +* Add domain resolver options**2** +* Add TLS fragment/record fragment support to route options and outbound TLS options **3** +* Add certificate options **4** +* Add Tailscale endpoint and DNS server **5** +* Drop support for go1.22 **6** +* Add AnyTLS protocol **7** +* Migrate to stdlib ECH implementation **8** +* Add NTP sniffer **9** +* Add wildcard SNI support for ShadowTLS inbound **10** +* Improve `auto_redirect` **11** +* Add control options for listeners **12** +* Add DERP service **13** +* Add Resolved service and DNS server **14** +* Add SSM API service **15** +* Add loopback address support for tun **16** +* Improve tun performance on Apple platforms **17** +* Update quic-go to v0.52.0 +* Update gVisor to 20250319.0 +* Update the status of graphical clients in stores **18** + +**1**: + +DNS servers are refactored for better performance and scalability. + +See [DNS server](/configuration/dns/server/). + +For migration, see [Migrate to new DNS server formats](/migration/#migrate-to-new-dns-server-formats). + +Compatibility for old formats will be removed in sing-box 1.14.0. + +**2**: + +Legacy `outbound` DNS rules are deprecated +and can be replaced by the new `domain_resolver` option. + +See [Dial Fields](/configuration/shared/dial/#domain_resolver) and +[Route](/configuration/route/#default_domain_resolver). + +For migration, +see [Migrate outbound DNS rule items to domain resolver](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver). + +**3**: + +See [Route Action](/configuration/route/rule_action/#tls_fragment) and [TLS](/configuration/shared/tls/). + +**4**: + +New certificate options allow you to manage the default list of trusted X509 CA certificates. + +For the system certificate list, fixed Go not reading Android trusted certificates correctly. + +You can also use the Mozilla Included List instead, or add trusted certificates yourself. + +See [Certificate](/configuration/certificate/). + +**5**: + +See [Tailscale](/configuration/endpoint/tailscale/). + +**6**: + +Due to maintenance difficulties, sing-box 1.12.0 requires at least Go 1.23 to compile. + +For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and patches +from [MetaCubeX/go](https://github.com/MetaCubeX/go). + +**7**: + +The new AnyTLS protocol claims to mitigate TLS proxy traffic characteristics and comes with a new multiplexing scheme. + +See [AnyTLS Inbound](/configuration/inbound/anytls/) and [AnyTLS Outbound](/configuration/outbound/anytls/). + +**8**: + +See [TLS](/configuration/shared/tls). + +The build tag `with_ech` is no longer needed and has been removed. + +**9**: + +See [Protocol Sniff](/configuration/route/sniff/). + +**10**: + +See [ShadowTLS](/configuration/inbound/shadowtls/#wildcard_sni). + +**11**: + +Now `auto_redirect` fixes compatibility issues between tun and Docker bridge networks, +see [Tun](/configuration/inbound/tun/#auto_redirect). + +**12**: + +You can now set `bind_interface`, `routing_mark` and `reuse_addr` in Listen Fields. + +See [Listen Fields](/configuration/shared/listen/). + +**13**: + +DERP service is a Tailscale DERP server, similar to [derper](https://pkg.go.dev/tailscale.com/cmd/derper). + +See [DERP Service](/configuration/service/derp/). + +**14**: + +Resolved service is a fake systemd-resolved DBUS service to receive DNS settings from other programs +(e.g. NetworkManager) and provide DNS resolution. + +See [Resolved Service](/configuration/service/resolved/) and [Resolved DNS Server](/configuration/dns/server/resolved/). + +**15**: + +SSM API service is a RESTful API server for managing Shadowsocks servers. + +See [SSM API Service](/configuration/service/ssm-api/). + +**16**: + +TUN now implements SideStore's StosVPN. + +See [Tun](/configuration/inbound/tun/#loopback_address). + +**17**: + +We have significantly improved the performance of tun inbound on Apple platforms, especially in the gVisor stack. + +The following data was tested +using [tun_bench](https://github.com/SagerNet/sing-box/blob/dev-next/cmd/internal/tun_bench/main.go) on M4 MacBook pro. + +| Version | Stack | MTU | Upload | Download | +|-------------|--------|-------|--------|----------| +| 1.11.15 | gvisor | 1500 | 852M | 2.57G | +| 1.12.0-rc.4 | gvisor | 1500 | 2.90G | 4.68G | +| 1.11.15 | gvisor | 4064 | 2.31G | 6.34G | +| 1.12.0-rc.4 | gvisor | 4064 | 7.54G | 12.2G | +| 1.11.15 | gvisor | 65535 | 27.6G | 18.1G | +| 1.12.0-rc.4 | gvisor | 65535 | 39.8G | 34.7G | +| 1.11.15 | system | 1500 | 664M | 706M | +| 1.12.0-rc.4 | system | 1500 | 2.44G | 2.51G | +| 1.11.15 | system | 4064 | 1.88G | 1.94G | +| 1.12.0-rc.4 | system | 4064 | 6.45G | 6.27G | +| 1.11.15 | system | 65535 | 26.2G | 17.4G | +| 1.12.0-rc.4 | system | 65535 | 17.6G | 21.0G | + +**18**: + +We continue to experience issues updating our sing-box apps on the App Store and Play Store. +Until we rewrite and resubmit the apps, they are considered irrecoverable. +Therefore, after this release, we will not be repeating this notice unless there is new information. + +### 1.11.15 + +* Fixes and improvements + +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we +violated the rules (TestFlight users are not affected)._ + +#### 1.12.0-beta.32 + +* Improve tun performance on Apple platforms **1** +* Fixes and improvements + +**1**: + +We have significantly improved the performance of tun inbound on Apple platforms, especially in the gVisor stack. + +### 1.11.14 + +* Fixes and improvements + +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we +violated the rules (TestFlight users are not affected)._ + +#### 1.12.0-beta.24 + +* Allow `tls_fragment` and `tls_record_fragment` to be enabled together **1** +* Also add fragment options for TLS client configuration **2** +* Fixes and improvements + +**1**: + +For debugging only, it is recommended to disable if record fragmentation works. + +See [Route Action](/configuration/route/rule_action/#tls_fragment). + +**2**: + +See [TLS](/configuration/shared/tls/). + +#### 1.12.0-beta.23 + +* Add loopback address support for tun **1** +* Add cache support for ssm-api **2** +* Fixes and improvements + +**1**: + +TUN now implements SideStore's StosVPN. + +See [Tun](/configuration/inbound/tun/#loopback_address). + +**2**: + +See [SSM API Service](/configuration/service/ssm-api/#cache_path). + +#### 1.12.0-beta.21 + +* Fix missing `home` option for DERP service **1** +* Fixes and improvements + +**1**: + +You can now choose what the DERP home page shows, just like with derper's `-home` flag. + +See [DERP](/configuration/service/derp/#home). + +### 1.11.13 + +* Fixes and improvements + +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we +violated the rules (TestFlight users are not affected)._ + +#### 1.12.0-beta.17 + +* Update quic-go to v0.52.0 +* Fixes and improvements + +#### 1.12.0-beta.15 + +* Add DERP service **1** +* Add Resolved service and DNS server **2** +* Add SSM API service **3** +* Fixes and improvements + +**1**: + +DERP service is a Tailscale DERP server, similar to [derper](https://pkg.go.dev/tailscale.com/cmd/derper). + +See [DERP Service](/configuration/service/derp/). + +**2**: + +Resolved service is a fake systemd-resolved DBUS service to receive DNS settings from other programs +(e.g. NetworkManager) and provide DNS resolution. + +See [Resolved Service](/configuration/service/resolved/) and [Resolved DNS Server](/configuration/dns/server/resolved/). + +**3**: + +SSM API service is a RESTful API server for managing Shadowsocks servers. + +See [SSM API Service](/configuration/service/ssm-api/). + +### 1.11.11 + +* Fixes and improvements + +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we +violated the rules (TestFlight users are not affected)._ + +#### 1.12.0-beta.13 + +* Add TLS record fragment route options **1** +* Add missing `accept_routes` option for Tailscale **2** +* Fixes and improvements + +**1**: + +See [Route Action](/configuration/route/rule_action/#tls_record_fragment). + +**2**: + +See [Tailscale](/configuration/endpoint/tailscale/#accept_routes). + +#### 1.12.0-beta.10 + +* Add control options for listeners **1** +* Fixes and improvements + +**1**: + +You can now set `bind_interface`, `routing_mark` and `reuse_addr` in Listen Fields. + +See [Listen Fields](/configuration/shared/listen/). + +### 1.11.10 + +* Undeprecate the `block` outbound **1** +* Fixes and improvements + +**1**: + +Since we don’t have a replacement for using the `block` outbound in selectors yet, +we decided to temporarily undeprecate the `block` outbound until a replacement is available in the future. + +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we +violated the rules (TestFlight users are not affected)._ + +#### 1.12.0-beta.9 + +* Update quic-go to v0.51.0 +* Fixes and improvements + +### 1.11.9 + +* Fixes and improvements + +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we +violated the rules (TestFlight users are not affected)._ + +#### 1.12.0-beta.5 + +* Fixes and improvements + +### 1.11.8 + +* Improve `auto_redirect` **1** +* Fixes and improvements + +**1**: + +Now `auto_redirect` fixes compatibility issues between TUN and Docker bridge networks, +see [Tun](/configuration/inbound/tun/#auto_redirect). + +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we +violated the rules (TestFlight users are not affected)._ + +#### 1.12.0-beta.3 + +* Fixes and improvements + +### 1.11.7 + +* Fixes and improvements + +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we +violated the rules (TestFlight users are not affected)._ + +#### 1.12.0-beta.1 + +* Fixes and improvements + +**1**: + +Now `auto_redirect` fixes compatibility issues between tun and Docker bridge networks, +see [Tun](/configuration/inbound/tun/#auto_redirect). + +### 1.11.6 + +* Fixes and improvements + +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we +violated the rules (TestFlight users are not affected)._ + +#### 1.12.0-alpha.19 + +* Update gVisor to 20250319.0 +* Fixes and improvements + +#### 1.12.0-alpha.18 + +* Add wildcard SNI support for ShadowTLS inbound **1** +* Fixes and improvements + +**1**: + +See [ShadowTLS](/configuration/inbound/shadowtls/#wildcard_sni). + +#### 1.12.0-alpha.17 + +* Add NTP sniffer **1** +* Fixes and improvements + +**1**: + +See [Protocol Sniff](/configuration/route/sniff/). + +#### 1.12.0-alpha.16 + +* Update `domain_resolver` behavior **1** +* Fixes and improvements + +**1**: + +`route.default_domain_resolver` or `outbound.domain_resolver` is now optional when only one DNS server is configured. + +See [Dial Fields](/configuration/shared/dial/#domain_resolver). + +### 1.11.5 + +* Fixes and improvements + +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we +violated the rules (TestFlight users are not affected)._ + +#### 1.12.0-alpha.13 + +* Move `predefined` DNS server to DNS rule action **1** +* Fixes and improvements + +**1**: + +See [DNS Rule Action](/configuration/dns/rule_action/#predefined). + +### 1.11.4 + +* Fixes and improvements + +#### 1.12.0-alpha.11 + +* Fixes and improvements + +#### 1.12.0-alpha.10 + +* Add AnyTLS protocol **1** +* Improve `resolve` route action **2** +* Migrate to stdlib ECH implementation **3** +* Fixes and improvements + +**1**: + +The new AnyTLS protocol claims to mitigate TLS proxy traffic characteristics and comes with a new multiplexing scheme. + +See [AnyTLS Inbound](/configuration/inbound/anytls/) and [AnyTLS Outbound](/configuration/outbound/anytls/). + +**2**: + +`resolve` route action now accepts `disable_cache` and other options like in DNS route actions, +see [Route Action](/configuration/route/rule_action). + +**3**: + +See [TLS](/configuration/shared/tls). + +The build tag `with_ech` is no longer needed and has been removed. + +#### 1.12.0-alpha.7 + +* Add Tailscale DNS server **1** +* Fixes and improvements + +**1**: + +See [Tailscale](/configuration/dns/server/tailscale/). + +#### 1.12.0-alpha.6 + +* Add Tailscale endpoint **1** +* Drop support for go1.22 **2** +* Fixes and improvements + +**1**: + +See [Tailscale](/configuration/endpoint/tailscale/). + +**2**: + +Due to maintenance difficulties, sing-box 1.12.0 requires at least Go 1.23 to compile. + +For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and patches +from [MetaCubeX/go](https://github.com/MetaCubeX/go). + +### 1.11.3 + +* Fixes and improvements + +_This version overwrites 1.11.2, as incorrect binaries were released due to a bug in the continuous integration +process._ + +#### 1.12.0-alpha.5 + +* Fixes and improvements + +### 1.11.1 + +* Fixes and improvements + +#### 1.12.0-alpha.2 + +* Update quic-go to v0.49.0 +* Fixes and improvements + +#### 1.12.0-alpha.1 + +* Refactor DNS servers **1** +* Add domain resolver options**2** +* Add TLS fragment route options **3** +* Add certificate options **4** + +**1**: + +DNS servers are refactored for better performance and scalability. + +See [DNS server](/configuration/dns/server/). + +For migration, see [Migrate to new DNS server formats](/migration/#migrate-to-new-dns-server-formats). + +Compatibility for old formats will be removed in sing-box 1.14.0. + +**2**: + +Legacy `outbound` DNS rules are deprecated +and can be replaced by the new `domain_resolver` option. + +See [Dial Fields](/configuration/shared/dial/#domain_resolver) and +[Route](/configuration/route/#default_domain_resolver). + +For migration, +see [Migrate outbound DNS rule items to domain resolver](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver). + +**3**: + +The new TLS fragment route options allow you to fragment TLS handshakes to bypass firewalls. + +This feature is intended to circumvent simple firewalls based on **plaintext packet matching**, and should not be used +to circumvent real censorship. + +Since it is not designed for performance, it should not be applied to all connections, but only to server names that are +known to be blocked. + +See [Route Action](/configuration/route/rule_action/#tls_fragment). + +**4**: + +New certificate options allow you to manage the default list of trusted X509 CA certificates. + +For the system certificate list, fixed Go not reading Android trusted certificates correctly. + +You can also use the Mozilla Included List instead, or add trusted certificates yourself. + +See [Certificate](/configuration/certificate/). + +### 1.11.0 + +Important changes since 1.10: + +* Introducing rule actions **1** +* Improve tun compatibility **3** +* Merge route options to route actions **4** +* Add `network_type`, `network_is_expensive` and `network_is_constrainted` rule items **5** +* Add multi network dialing **6** +* Add `cache_capacity` DNS option **7** +* Add `override_address` and `override_port` route options **8** +* Upgrade WireGuard outbound to endpoint **9** +* Add UDP GSO support for WireGuard +* Make GSO adaptive **10** +* Add UDP timeout route option **11** +* Add more masquerade options for hysteria2 **12** +* Add `rule-set merge` command +* Add port hopping support for Hysteria2 **13** +* Hysteria2 `ignore_client_bandwidth` behavior update **14** + +**1**: + +New rule actions replace legacy inbound fields and special outbound fields, +and can be used for pre-matching **2**. + +See [Rule](/configuration/route/rule/), +[Rule Action](/configuration/route/rule_action/), +[DNS Rule](/configuration/dns/rule/) and +[DNS Rule Action](/configuration/dns/rule_action/). + +For migration, see +[Migrate legacy special outbounds to rule actions](/migration/#migrate-legacy-special-outbounds-to-rule-actions), +[Migrate legacy inbound fields to rule actions](/migration/#migrate-legacy-inbound-fields-to-rule-actions) +and [Migrate legacy DNS route options to rule actions](/migration/#migrate-legacy-dns-route-options-to-rule-actions). + +**2**: + +Similar to Surge's pre-matching. + +Specifically, new rule actions allow you to reject connections with +TCP RST (for TCP connections) and ICMP port unreachable (for UDP packets) +before connection established to improve tun's compatibility. + +See [Rule Action](/configuration/route/rule_action/). + +**3**: + +When `gvisor` tun stack is enabled, even if the request passes routing, +if the outbound connection establishment fails, +the connection still does not need to be established and a TCP RST is replied. + +**4**: + +Route options in DNS route actions will no longer be considered deprecated, +see [DNS Route Action](/configuration/dns/rule_action/). + +Also, now `udp_disable_domain_unmapping` and `udp_connect` can also be configured in route action, +see [Route Action](/configuration/route/rule_action/). + +**5**: + +When using in graphical clients, new routing rule items allow you to match on +network type (WIFI, cellular, etc.), whether the network is expensive, and whether Low Data Mode is enabled. + +See [Route Rule](/configuration/route/rule/), [DNS Route Rule](/configuration/dns/rule/) +and [Headless Rule](/configuration/rule-set/headless-rule/). + +**6**: + +Similar to Surge's strategy. + +New options allow you to connect using multiple network interfaces, +prefer or only use one type of interface, +and configure a timeout to fallback to other interfaces. + +See [Dial Fields](/configuration/shared/dial/#network_strategy), +[Rule Action](/configuration/route/rule_action/#network_strategy) +and [Route](/configuration/route/#default_network_strategy). + +**7**: + +See [DNS](/configuration/dns/#cache_capacity). + +**8**: + +See [Rule Action](/configuration/route/#override_address) and +[Migrate destination override fields to route options](/migration/#migrate-destination-override-fields-to-route-options). + +**9**: + +The new WireGuard endpoint combines inbound and outbound capabilities, +and the old outbound will be removed in sing-box 1.13.0. + +See [Endpoint](/configuration/endpoint/), [WireGuard Endpoint](/configuration/endpoint/wireguard/) +and [Migrate WireGuard outbound fields to route options](/migration/#migrate-wireguard-outbound-to-endpoint). + +**10**: + +For WireGuard outbound and endpoint, GSO will be automatically enabled when available, +see [WireGuard Outbound](/configuration/outbound/wireguard/#gso). + +For TUN, GSO has been removed, +see [Deprecated](/deprecated/#gso-option-in-tun). + +**11**: + +See [Rule Action](/configuration/route/rule_action/#udp_timeout). + +**12**: + +See [Hysteria2](/configuration/inbound/hysteria2/#masquerade). + +**13**: + +See [Hysteria2](/configuration/outbound/hysteria2/). + +**14**: + +When `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC. + +### 1.10.7 + +* Fixes and improvements + +#### 1.11.0-beta.20 + +* Hysteria2 `ignore_client_bandwidth` behavior update **1** +* Fixes and improvements + +**1**: + +When `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC. + +See [Hysteria2](/configuration/inbound/hysteria2/#ignore_client_bandwidth). + +#### 1.11.0-beta.17 + +* Add port hopping support for Hysteria2 **1** +* Fixes and improvements + +**1**: + +See [Hysteria2](/configuration/outbound/hysteria2/). + +#### 1.11.0-beta.14 + +* Allow adding route (exclude) address sets to routes **1** +* Fixes and improvements + +**1**: + +When `auto_redirect` is not enabled, directly add `route[_exclude]_address_set` +to tun routes (equivalent to `route[_exclude]_address`). + +Note that it **doesn't work on the Android graphical client** due to +the Android VpnService not being able to handle a large number of routes (DeadSystemException), +but otherwise it works fine on all command line clients and Apple platforms. + +See [route_address_set](/configuration/inbound/tun/#route_address_set) and +[route_exclude_address_set](/configuration/inbound/tun/#route_exclude_address_set). + +#### 1.11.0-beta.12 + +* Add `rule-set merge` command +* Fixes and improvements + +#### 1.11.0-beta.3 + +* Add more masquerade options for hysteria2 **1** +* Fixes and improvements + +**1**: + +See [Hysteria2](/configuration/inbound/hysteria2/#masquerade). + +#### 1.11.0-alpha.25 + +* Update quic-go to v0.48.2 +* Fixes and improvements + +#### 1.11.0-alpha.22 + +* Add UDP timeout route option **1** +* Fixes and improvements + +**1**: + +See [Rule Action](/configuration/route/rule_action/#udp_timeout). + +#### 1.11.0-alpha.20 + +* Add UDP GSO support for WireGuard +* Make GSO adaptive **1** + +**1**: + +For WireGuard outbound and endpoint, GSO will be automatically enabled when available, +see [WireGuard Outbound](/configuration/outbound/wireguard/#gso). + +For TUN, GSO has been removed, +see [Deprecated](/deprecated/#gso-option-in-tun). + +#### 1.11.0-alpha.19 + +* Upgrade WireGuard outbound to endpoint **1** +* Fixes and improvements + +**1**: + +The new WireGuard endpoint combines inbound and outbound capabilities, +and the old outbound will be removed in sing-box 1.13.0. + +See [Endpoint](/configuration/endpoint/), [WireGuard Endpoint](/configuration/endpoint/wireguard/) +and [Migrate WireGuard outbound fields to route options](/migration/#migrate-wireguard-outbound-to-endpoint). + +### 1.10.2 + +* Add deprecated warnings +* Fix proxying websocket connections in HTTP/mixed inbounds +* Fixes and improvements + +#### 1.11.0-alpha.18 + +* Fixes and improvements + +#### 1.11.0-alpha.16 + +* Add `cache_capacity` DNS option **1** +* Add `override_address` and `override_port` route options **2** +* Fixes and improvements + +**1**: + +See [DNS](/configuration/dns/#cache_capacity). + +**2**: + +See [Rule Action](/configuration/route/#override_address) and +[Migrate destination override fields to route options](/migration/#migrate-destination-override-fields-to-route-options). + +#### 1.11.0-alpha.15 + +* Improve multi network dialing **1** +* Fixes and improvements - If your company or organization is willing to help us return to the App Store, please [contact us](mailto:contact@sagernet.org). +**1**: + +New options allow you to configure the network strategy flexibly. + +See [Dial Fields](/configuration/shared/dial/#network_strategy), +[Rule Action](/configuration/route/rule_action/#network_strategy) +and [Route](/configuration/route/#default_network_strategy). + +#### 1.11.0-alpha.14 + +* Add multi network dialing **1** +* Fixes and improvements + +**1**: + +Similar to Surge's strategy. + +New options allow you to connect using multiple network interfaces, +prefer or only use one type of interface, +and configure a timeout to fallback to other interfaces. + +See [Dial Fields](/configuration/shared/dial/#network_strategy), +[Rule Action](/configuration/route/rule_action/#network_strategy) +and [Route](/configuration/route/#default_network_strategy). + +#### 1.11.0-alpha.13 + +* Fixes and improvements + +#### 1.11.0-alpha.12 + +* Merge route options to route actions **1** +* Add `network_type`, `network_is_expensive` and `network_is_constrainted` rule items **2** +* Fixes and improvements + +**1**: + +Route options in DNS route actions will no longer be considered deprecated, +see [DNS Route Action](/configuration/dns/rule_action/). + +Also, now `udp_disable_domain_unmapping` and `udp_connect` can also be configured in route action, +see [Route Action](/configuration/route/rule_action/). + +**2**: + +When using in graphical clients, new routing rule items allow you to match on +network type (WIFI, cellular, etc.), whether the network is expensive, and whether Low Data Mode is enabled. + +See [Route Rule](/configuration/route/rule/), [DNS Route Rule](/configuration/dns/rule/) +and [Headless Rule](/configuration/rule-set/headless-rule/). + +#### 1.11.0-alpha.9 + +* Improve tun compatibility **1** +* Fixes and improvements + +**1**: + +When `gvisor` tun stack is enabled, even if the request passes routing, +if the outbound connection establishment fails, +the connection still does not need to be established and a TCP RST is replied. + +#### 1.11.0-alpha.7 + +* Introducing rule actions **1** + +**1**: + +New rule actions replace legacy inbound fields and special outbound fields, +and can be used for pre-matching **2**. + +See [Rule](/configuration/route/rule/), +[Rule Action](/configuration/route/rule_action/), +[DNS Rule](/configuration/dns/rule/) and +[DNS Rule Action](/configuration/dns/rule_action/). + +For migration, see +[Migrate legacy special outbounds to rule actions](/migration/#migrate-legacy-special-outbounds-to-rule-actions), +[Migrate legacy inbound fields to rule actions](/migration/#migrate-legacy-inbound-fields-to-rule-actions) +and [Migrate legacy DNS route options to rule actions](/migration/#migrate-legacy-dns-route-options-to-rule-actions). + +**2**: + +Similar to Surge's pre-matching. + +Specifically, new rule actions allow you to reject connections with +TCP RST (for TCP connections) and ICMP port unreachable (for UDP packets) +before connection established to improve tun's compatibility. + +See [Rule Action](/configuration/route/rule_action/). + +#### 1.11.0-alpha.6 + +* Update quic-go to v0.48.1 +* Set gateway for tun correctly +* Fixes and improvements + +#### 1.11.0-alpha.2 + +* Add warnings for usage of deprecated features +* Fixes and improvements + +#### 1.11.0-alpha.1 + +* Update quic-go to v0.48.0 +* Fixes and improvements + +### 1.10.1 + +* Fixes and improvements + +### 1.10.0 + +Important changes since 1.9: + +* Introducing auto-redirect **1** +* Add AdGuard DNS Filter support **2** +* TUN address fields are merged **3** +* Add custom options for `auto-route` and `auto-redirect` **4** +* Drop support for go1.18 and go1.19 **5** +* Add tailing comma support in JSON configuration +* Improve sniffers **6** +* Add new `inline` rule-set type **7** +* Add access control options for Clash API **8** +* Add `rule_set_ip_cidr_accept_empty` DNS address filter rule item **9** +* Add auto reload support for local rule-set +* Update fsnotify usages **10** +* Add IP address support for `rule-set match` command +* Add `rule-set decompile` command +* Add `process_path_regex` rule item +* Update uTLS to v1.6.7 **11** +* Optimize memory usages of rule-sets **12** + +**1**: + +The new auto-redirect feature allows TUN to automatically +configure connection redirection to improve proxy performance. + +When auto-redirect is enabled, new route address set options will allow you to +automatically configure destination IP CIDR rules from a specified rule set to the firewall. + +Specified or unspecified destinations will bypass the sing-box routes to get better performance +(for example, keep hardware offloading of direct traffics on the router). + +See [TUN](/configuration/inbound/tun). + +**2**: + +The new feature allows you to use AdGuard DNS Filter lists in a sing-box without AdGuard Home. + +See [AdGuard DNS Filter](/configuration/rule-set/adguard/). + +**3**: + +See [Migration](/migration/#tun-address-fields-are-merged). + +**4**: + +See [iproute2_table_index](/configuration/inbound/tun/#iproute2_table_index), +[iproute2_rule_index](/configuration/inbound/tun/#iproute2_rule_index), +[auto_redirect_input_mark](/configuration/inbound/tun/#auto_redirect_input_mark) and +[auto_redirect_output_mark](/configuration/inbound/tun/#auto_redirect_output_mark). + +**5**: + +Due to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile. + +**6**: + +BitTorrent, DTLS, RDP, SSH sniffers are added. + +Now the QUIC sniffer can correctly extract the server name from Chromium requests and +can identify common QUIC clients, including +Chromium, Safari, Firefox, quic-go (including uquic disguised as Chrome). + +**7**: + +The new [rule-set](/configuration/rule-set/) type inline (which also becomes the default type) +allows you to write headless rules directly without creating a rule-set file. + +**8**: + +With new access control options, not only can you allow Clash dashboards +to access the Clash API on your local network, +you can also manually limit the websites that can access the API instead of allowing everyone. + +See [Clash API](/configuration/experimental/clash-api/). + +**9**: + +See [DNS Rule](/configuration/dns/rule/#rule_set_ip_cidr_accept_empty). + +**10**: + +sing-box now uses fsnotify correctly and will not cancel watching +if the target file is deleted or recreated via rename (e.g. `mv`). + +This affects all path options that support reload, including +`tls.certificate_path`, `tls.key_path`, `tls.ech.key_path` and `rule_set.path`. + +**11**: + +Some legacy chrome fingerprints have been removed and will fallback to chrome, +see [utls](/configuration/shared/tls#utls). + +**12**: + +See [Source Format](/configuration/rule-set/source-format/#version). + +### 1.9.7 + +* Fixes and improvements + +#### 1.10.0-beta.11 + +* Update uTLS to v1.6.7 **1** + +**1**: + +Some legacy chrome fingerprints have been removed and will fallback to chrome, +see [utls](/configuration/shared/tls#utls). + +#### 1.10.0-beta.10 + +* Add `process_path_regex` rule item +* Fixes and improvements + +_The macOS standalone versions of sing-box (>=1.9.5/<1.10.0-beta.11) now silently fail and require manual granting of +the **Full Disk Access** permission to system extension to start, probably due to Apple's changed security policy. We +will prompt users about this in feature versions._ + +### 1.9.6 + +* Fixes and improvements + +### 1.9.5 + +* Update quic-go to v0.47.0 +* Fix direct dialer not resolving domain +* Fix no error return when empty DNS cache retrieved +* Fix build with go1.23 +* Fix stream sniffer +* Fix bad redirect in clash-api +* Fix wireguard events chan leak +* Fix cached conn eats up read deadlines +* Fix disconnected interface selected as default in windows +* Update Bundle Identifiers for Apple platform clients **1** + +**1**: + +See [Migration](/migration/#bundle-identifier-updates-in-apple-platform-clients). + +We are still working on getting all sing-box apps back on the App Store, which should be completed within a week +(SFI on the App Store and others on TestFlight are already available). + +#### 1.10.0-beta.8 + +* Fixes and improvements + +_With the help of a netizen, we are in the process of getting sing-box apps back on the App Store, which should be +completed within a month (TestFlight is already available)._ + +#### 1.10.0-beta.7 + +* Update quic-go to v0.47.0 +* Fixes and improvements + +#### 1.10.0-beta.6 + +* Add RDP sniffer +* Fixes and improvements + +#### 1.10.0-beta.5 + +* Add PNA support for [Clash API](/configuration/experimental/clash-api/) +* Fixes and improvements + +#### 1.10.0-beta.3 + +* Add SSH sniffer +* Fixes and improvements + +#### 1.10.0-beta.2 + +* Build with go1.23 +* Fixes and improvements ### 1.9.4 @@ -23,18 +2157,224 @@ icon: material/alert-decagram * Fix UDP connnection leak when sniffing * Fixes and improvements +_Due to problems with our Apple developer account, +sing-box apps on Apple platforms are temporarily unavailable for download or update. +If your company or organization is willing to help us return to the App Store, +please [contact us](mailto:contact@sagernet.org)._ + +#### 1.10.0-alpha.29 + +* Update quic-go to v0.46.0 +* Fixes and improvements + +#### 1.10.0-alpha.25 + +* Add AdGuard DNS Filter support **1** + +**1**: + +The new feature allows you to use AdGuard DNS Filter lists in a sing-box without AdGuard Home. + +See [AdGuard DNS Filter](/configuration/rule-set/adguard/). + +#### 1.10.0-alpha.23 + +* Add Chromium support for QUIC sniffer +* Add client type detect support for QUIC sniffer **1** +* Fixes and improvements + +**1**: + +Now the QUIC sniffer can correctly extract the server name from Chromium requests and +can identify common QUIC clients, including +Chromium, Safari, Firefox, quic-go (including uquic disguised as Chrome). + +See [Protocol Sniff](/configuration/route/sniff/) and [Route Rule](/configuration/route/rule/#client). + +#### 1.10.0-alpha.22 + +* Optimize memory usages of rule-sets **1** +* Fixes and improvements + +**1**: + +See [Source Format](/configuration/rule-set/source-format/#version). + +#### 1.10.0-alpha.20 + +* Add DTLS sniffer +* Fixes and improvements + +#### 1.10.0-alpha.19 + +* Add `rule-set decompile` command +* Add IP address support for `rule-set match` command +* Fixes and improvements + +#### 1.10.0-alpha.18 + +* Add new `inline` rule-set type **1** +* Add auto reload support for local rule-set +* Update fsnotify usages **2** +* Fixes and improvements + +**1**: + +The new [rule-set](/configuration/rule-set/) type inline (which also becomes the default type) +allows you to write headless rules directly without creating a rule-set file. + +**2**: + +sing-box now uses fsnotify correctly and will not cancel watching +if the target file is deleted or recreated via rename (e.g. `mv`). + +This affects all path options that support reload, including +`tls.certificate_path`, `tls.key_path`, `tls.ech.key_path` and `rule_set.path`. + +#### 1.10.0-alpha.17 + +* Some chaotic changes **1** +* `rule_set_ipcidr_match_source` rule items are renamed **2** +* Add `rule_set_ip_cidr_accept_empty` DNS address filter rule item **3** +* Update quic-go to v0.45.1 +* Fixes and improvements + +**1**: + +Something may be broken, please actively report problems with this version. + +**2**: + +`rule_set_ipcidr_match_source` route and DNS rule items are renamed to +`rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0. + +**3**: + +See [DNS Rule](/configuration/dns/rule/#rule_set_ip_cidr_accept_empty). + +#### 1.10.0-alpha.16 + +* Add custom options for `auto-route` and `auto-redirect` **1** +* Fixes and improvements + +**1**: + +See [iproute2_table_index](/configuration/inbound/tun/#iproute2_table_index), +[iproute2_rule_index](/configuration/inbound/tun/#iproute2_rule_index), +[auto_redirect_input_mark](/configuration/inbound/tun/#auto_redirect_input_mark) and +[auto_redirect_output_mark](/configuration/inbound/tun/#auto_redirect_output_mark). + +#### 1.10.0-alpha.13 + +* TUN address fields are merged **1** +* Add route address set support for auto-redirect **2** + +**1**: + +See [Migration](/migration/#tun-address-fields-are-merged). + +**2**: + +The new feature will allow you to configure the destination IP CIDR rules +in the specified rule-sets to the firewall automatically. + +Specified or unspecified destinations will bypass the sing-box routes to get better performance +(for example, keep hardware offloading of direct traffics on the router). + +See [route_address_set](/configuration/inbound/tun/#route_address_set) +and [route_exclude_address_set](/configuration/inbound/tun/#route_exclude_address_set). + +#### 1.10.0-alpha.12 + +* Fix auto-redirect not configuring nftables forward chain correctly +* Fixes and improvements + ### 1.9.3 * Fixes and improvements +#### 1.10.0-alpha.10 + +* Fixes and improvements + ### 1.9.2 * Fixes and improvements +#### 1.10.0-alpha.8 + +* Drop support for go1.18 and go1.19 **1** +* Update quic-go to v0.45.0 +* Update Hysteria2 BBR congestion control +* Fixes and improvements + +**1**: + +Due to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile. + ### 1.9.1 * Fixes and improvements +#### 1.10.0-alpha.7 + +* Fixes and improvements + +#### 1.10.0-alpha.5 + +* Improve auto-redirect **1** + +**1**: + +nftables support and DNS hijacking has been added. + +Tun inbounds with `auto_route` and `auto_redirect` now works as expected on routers **without intervention**. + +#### 1.10.0-alpha.4 + +* Fix auto-redirect **1** +* Improve auto-route on linux **2** + +**1**: + +Tun inbounds with `auto_route` and `auto_redirect` now works as expected on routers. + +**2**: + +Tun inbounds with `auto_route` and `strict_route` now works as expected on routers and servers, +but the usages of [exclude_interface](/configuration/inbound/tun/#exclude_interface) need to be updated. + +#### 1.10.0-alpha.2 + +* Move auto-redirect to Tun **1** +* Fixes and improvements + +**1**: + +Linux support are added. + +See [Tun](/configuration/inbound/tun/#auto_redirect). + +#### 1.10.0-alpha.1 + +* Add tailing comma support in JSON configuration +* Add simple auto-redirect for Android **1** +* Add BitTorrent sniffer **2** + +**1**: + +It allows you to use redirect inbound in the sing-box Android client +and automatically configures IPv4 TCP redirection via su. + +This may alleviate the symptoms of some OCD patients who think that +redirect can effectively save power compared to the system HTTP Proxy. + +See [Redirect](/configuration/inbound/redirect/). + +**2**: + +See [Protocol Sniff](/configuration/route/sniff/). + ### 1.9.0 * Fixes and improvements @@ -69,7 +2409,7 @@ See [Migration](/migration/#process_path-format-update-on-windows). The new DNS feature allows you to more precisely bypass Chinese websites via **DNS leaks**. Do not use plain local DNS if using this method. -See [Address Filter Fields](/configuration/dns/rule#address-filter-fields). +See [Legacy Address Filter Fields](/configuration/dns/rule#legacy-address-filter-fields). [Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) updated. @@ -83,7 +2423,7 @@ the [Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users **5**: The new feature allows you to cache the check results of -[Address filter DNS rule items](/configuration/dns/rule/#address-filter-fields) until expiration. +[Legacy Address Filter Fields](/configuration/dns/rule/#legacy-address-filter-fields) until expiration. **6**: @@ -264,7 +2604,7 @@ See [TUN](/configuration/inbound/tun) inbound. **1**: The new feature allows you to cache the check results of -[Address filter DNS rule items](/configuration/dns/rule/#address-filter-fields) until expiration. +[Legacy Address Filter Fields](/configuration/dns/rule/#legacy-address-filter-fields) until expiration. #### 1.9.0-alpha.7 @@ -311,7 +2651,7 @@ See [Migration](/migration/#process_path-format-update-on-windows). The new DNS feature allows you to more precisely bypass Chinese websites via **DNS leaks**. Do not use plain local DNS if using this method. -See [Address Filter Fields](/configuration/dns/rule#address-filter-fields). +See [Legacy Address Filter Fields](/configuration/dns/rule#legacy-address-filter-fields). [Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) updated. @@ -338,7 +2678,7 @@ See [Address Filter Fields](/configuration/dns/rule#address-filter-fields). Important changes since 1.7: * Migrate cache file from Clash API to independent options **1** -* Introducing [Rule Set](/configuration/rule-set/) **2** +* Introducing [rule-set](/configuration/rule-set/) **2** * Add `sing-box geoip`, `sing-box geosite` and `sing-box rule-set` commands **3** * Allow nested logical rules **4** * Independent `source_ip_is_private` and `ip_is_private` rules **5** @@ -358,7 +2698,7 @@ See [Cache File](/configuration/experimental/cache-file/) and **2**: -Rule set is independent collections of rules that can be compiled into binaries to improve performance. +rule-set is independent collections of rules that can be compiled into binaries to improve performance. Compared to legacy GeoIP and Geosite resources, it can include more types of rules, load faster, use less memory, and update automatically. @@ -366,16 +2706,16 @@ use less memory, and update automatically. See [Route#rule_set](/configuration/route/#rule_set), [Route Rule](/configuration/route/rule/), [DNS Rule](/configuration/dns/rule/), -[Rule Set](/configuration/rule-set/), +[rule-set](/configuration/rule-set/), [Source Format](/configuration/rule-set/source-format/) and [Headless Rule](/configuration/rule-set/headless-rule/). -For GEO resources migration, see [Migrate GeoIP to rule sets](/migration/#migrate-geoip-to-rule-sets) and -[Migrate Geosite to rule sets](/migration/#migrate-geosite-to-rule-sets). +For GEO resources migration, see [Migrate GeoIP to rule-sets](/migration/#migrate-geoip-to-rule-sets) and +[Migrate Geosite to rule-sets](/migration/#migrate-geosite-to-rule-sets). **3**: -New commands manage GeoIP, Geosite and rule set resources, and help you migrate GEO resources to rule sets. +New commands manage GeoIP, Geosite and rule-set resources, and help you migrate GEO resources to rule-sets. **4**: @@ -572,7 +2912,7 @@ This change is intended to break incorrect usage and essentially requires no act **1**: -Now the rules in the `rule_set` rule item can be logically considered to be merged into the rule using rule sets, +Now the rules in the `rule_set` rule item can be logically considered to be merged into the rule using rule-sets, rather than completely following the AND logic. #### 1.8.0-alpha.5 @@ -588,7 +2928,7 @@ Since GeoIP was deprecated, we made this rule independent, see [Migration](/migr #### 1.8.0-alpha.1 * Migrate cache file from Clash API to independent options **1** -* Introducing [Rule Set](/configuration/rule-set/) **2** +* Introducing [rule-set](/configuration/rule-set/) **2** * Add `sing-box geoip`, `sing-box geosite` and `sing-box rule-set` commands **3** * Allow nested logical rules **4** @@ -599,7 +2939,7 @@ See [Cache File](/configuration/experimental/cache-file/) and **2**: -Rule set is independent collections of rules that can be compiled into binaries to improve performance. +rule-set is independent collections of rules that can be compiled into binaries to improve performance. Compared to legacy GeoIP and Geosite resources, it can include more types of rules, load faster, use less memory, and update automatically. @@ -607,16 +2947,16 @@ use less memory, and update automatically. See [Route#rule_set](/configuration/route/#rule_set), [Route Rule](/configuration/route/rule/), [DNS Rule](/configuration/dns/rule/), -[Rule Set](/configuration/rule-set/), +[rule-set](/configuration/rule-set/), [Source Format](/configuration/rule-set/source-format/) and [Headless Rule](/configuration/rule-set/headless-rule/). -For GEO resources migration, see [Migrate GeoIP to rule sets](/migration/#migrate-geoip-to-rule-sets) and -[Migrate Geosite to rule sets](/migration/#migrate-geosite-to-rule-sets). +For GEO resources migration, see [Migrate GeoIP to rule-sets](/migration/#migrate-geoip-to-rule-sets) and +[Migrate Geosite to rule-sets](/migration/#migrate-geosite-to-rule-sets). **3**: -New commands manage GeoIP, Geosite and rule set resources, and help you migrate GEO resources to rule sets. +New commands manage GeoIP, Geosite and rule-set resources, and help you migrate GEO resources to rule-sets. **4**: diff --git a/docs/clients/android/features.md b/docs/clients/android/features.md index 8fe84add26..b76a6418e4 100644 --- a/docs/clients/android/features.md +++ b/docs/clients/android/features.md @@ -40,7 +40,9 @@ SFA provides an unprivileged TUN implementation through Android VpnService. |-----------------------|------------------|-----------------------------------| | `process_name` | :material-close: | No permission | | `process_path` | :material-close: | No permission | +| `process_path_regex` | :material-close: | No permission | | `package_name` | :material-check: | / | +| `package_name_regex` | :material-check: | / | | `user` | :material-close: | Use `package_name` instead | | `user_id` | :material-close: | Use `package_name` instead | | `wifi_ssid` | :material-check: | Fine location permission required | diff --git a/docs/clients/apple/features.md b/docs/clients/apple/features.md index 7d419103b5..e1f3d7ccd1 100644 --- a/docs/clients/apple/features.md +++ b/docs/clients/apple/features.md @@ -42,7 +42,9 @@ SFI/SFM/SFT provides an unprivileged TUN implementation through NetworkExtension |-----------------------|------------------|-----------------------| | `process_name` | :material-close: | No permission | | `process_path` | :material-close: | No permission | +| `process_path_regex` | :material-close: | No permission | | `package_name` | :material-close: | / | +| `package_name_regex` | :material-close: | / | | `user` | :material-close: | No permission | | `user_id` | :material-close: | No permission | | `wifi_ssid` | :material-alert: | Only supported on iOS | diff --git a/docs/clients/apple/index.md b/docs/clients/apple/index.md index 98cc342602..ef9807e779 100644 --- a/docs/clients/apple/index.md +++ b/docs/clients/apple/index.md @@ -7,12 +7,9 @@ icon: material/apple SFI/SFM/SFT allows users to manage and run local or remote sing-box configuration files, and provides platform-specific function implementation, such as TUN transparent proxy implementation. -!!! failure "Unavailable" - - Due to problems with our Apple developer account, sing-box apps on Apple platforms are temporarily unavailable for download or update. - - If your company or organization is willing to help us return to the App Store, please [contact us](mailto:contact@sagernet.org). +!!! failure "" + Due to non-technical reasons, we are temporarily unable to update the sing-box app on the App Store and release the standalone version of the macOS client (TestFlight users are not affected) ## :material-graph: Requirements @@ -21,23 +18,23 @@ platform-specific function implementation, such as TUN transparent proxy impleme ## :material-download: Download -* [App Store](https://apps.apple.com/us/app/sing-box/id6451272673) -* ~~TestFlight (Beta)~~ +* ~~[App Store](https://apps.apple.com/app/sing-box-vt/id6673731168)~~ +* TestFlight (Beta) TestFlight quota is only available to [sponsors](https://github.com/sponsors/nekohasekai) (one-time sponsorships are accepted). -Once you donate, you can get an invitation by sending us your Apple ID [via email](mailto:contact@sagernet.org), -or join our Telegram group for sponsors from [@yet_another_sponsor_bot](https://t.me/yet_another_sponsor_bot). +Once you donate, you can get an invitation by join our Telegram group for sponsors from [@yet_another_sponsor_bot](https://t.me/yet_another_sponsor_bot) +or sending us your Apple ID [via email](mailto:contact@sagernet.org). -## :material-file-download: Download (macOS standalone version) +## ~~:material-file-download: Download (macOS standalone version)~~ -* [Homebrew Cask](https://formulae.brew.sh/cask/sfm) +* ~~[Homebrew Cask](https://formulae.brew.sh/cask/sfm)~~ ```bash -brew install sfm +# brew install sfm ``` -* [GitHub Releases](https://github.com/SagerNet/sing-box/releases) +* ~~[GitHub Releases](https://github.com/SagerNet/sing-box/releases)~~ ## :material-source-repository: Source code diff --git a/docs/clients/index.md b/docs/clients/index.md index 9dacff6e60..45d2c9a948 100644 --- a/docs/clients/index.md +++ b/docs/clients/index.md @@ -3,9 +3,9 @@ Maintained by Project S to provide a unified experience and platform-specific functionality. | Platform | Client | -| ------------------------------------- | ---------------------------------------- | +|---------------------------------------|------------------------------------------| | :material-android: Android | [sing-box for Android](./android/) | -| :material-apple: iOS/macOS/Apple tvOS | :material-alert: [Unavailable](./apple/) | +| :material-apple: iOS/macOS/Apple tvOS | [sing-box for Apple platforms](./apple/) | | :material-laptop: Desktop | Working in progress | Some third-party projects that claim to use sing-box or use sing-box as a selling point are not listed here. The core diff --git a/docs/clients/index.zh.md b/docs/clients/index.zh.md index 6717fa6106..736b42ea20 100644 --- a/docs/clients/index.zh.md +++ b/docs/clients/index.zh.md @@ -2,11 +2,11 @@ 由 Project S 维护,提供统一的体验与平台特定的功能。 -| 平台 | 客户端 | -| ------------------------------------- | ----------------------------------- | -| :material-android: Android | [sing-box for Android](./android/) | -| :material-apple: iOS/macOS/Apple tvOS | :material-alert: [不可用](./apple/) | -| :material-laptop: Desktop | 施工中 | +| 平台 | 客户端 | +|---------------------------------------|------------------------------------------| +| :material-android: Android | [sing-box for Android](./android/) | +| :material-apple: iOS/macOS/Apple tvOS | [sing-box for Apple platforms](./apple/) | +| :material-laptop: Desktop | 施工中 | 此处没有列出一些声称使用或以 sing-box 为卖点的第三方项目。此类项目维护者的动机是获得更多用户,即使它们提供友好的商业 VPN 客户端功能, 但代码质量很差且包含广告。 diff --git a/docs/clients/privacy.md b/docs/clients/privacy.md index b1ecd2aa34..61df710259 100644 --- a/docs/clients/privacy.md +++ b/docs/clients/privacy.md @@ -9,6 +9,10 @@ and the data generated by the software is always on your device. ## Android +The broad package (App) visibility (QUERY_ALL_PACKAGES) permission +is used to provide per-application proxy features for VPN, +sing-box will not collect your app list. + If your configuration contains `wifi_ssid` or `wifi_bssid` routing rules, sing-box uses the location permission in the background to get information about the connected Wi-Fi network to make them work. diff --git a/docs/configuration/certificate/index.md b/docs/configuration/certificate/index.md new file mode 100644 index 0000000000..88d733802d --- /dev/null +++ b/docs/configuration/certificate/index.md @@ -0,0 +1,59 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [Chrome Root Store](#store) + +# Certificate + +### Structure + +```json +{ + "store": "", + "certificate": [], + "certificate_path": [], + "certificate_directory_path": [] +} +``` + +!!! note "" + + You can ignore the JSON Array [] tag when the content is only one item + +### Fields + +#### store + +The default X509 trusted CA certificate list. + +| Type | Description | +|--------------------|----------------------------------------------------------------------------------------------------------------| +| `system` (default) | System trusted CA certificates | +| `mozilla` | [Mozilla Included List](https://wiki.mozilla.org/CA/Included_Certificates) with China CA certificates removed | +| `chrome` | [Chrome Root Store](https://g.co/chrome/root-policy) with China CA certificates removed | +| `none` | Empty list | + +#### certificate + +The certificate line array to trust, in PEM format. + +#### certificate_path + +!!! note "" + + Will be automatically reloaded if file modified. + +The paths to certificates to trust, in PEM format. + +#### certificate_directory_path + +!!! note "" + + Will be automatically reloaded if file modified. + +The directory path to search for certificates to trust,in PEM format. diff --git a/docs/configuration/certificate/index.zh.md b/docs/configuration/certificate/index.zh.md new file mode 100644 index 0000000000..77f3fd88b4 --- /dev/null +++ b/docs/configuration/certificate/index.zh.md @@ -0,0 +1,59 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [Chrome Root Store](#store) + +# 证书 + +### 结构 + +```json +{ + "store": "", + "certificate": [], + "certificate_path": [], + "certificate_directory_path": [] +} +``` + +!!! note "" + + 当内容只有一项时,可以忽略 JSON 数组 [] 标签 + +### 字段 + +#### store + +默认的 X509 受信任 CA 证书列表。 + +| 类型 | 描述 | +|-------------------|--------------------------------------------------------------------------------------------| +| `system`(默认) | 系统受信任的 CA 证书 | +| `mozilla` | [Mozilla 包含列表](https://wiki.mozilla.org/CA/Included_Certificates)(已移除中国 CA 证书) | +| `chrome` | [Chrome Root Store](https://g.co/chrome/root-policy)(已移除中国 CA 证书) | +| `none` | 空列表 | + +#### certificate + +要信任的证书行数组,PEM 格式。 + +#### certificate_path + +!!! note "" + + 文件修改时将自动重新加载。 + +要信任的证书路径,PEM 格式。 + +#### certificate_directory_path + +!!! note "" + + 文件修改时将自动重新加载。 + +搜索要信任的证书的目录路径,PEM 格式。 \ No newline at end of file diff --git a/docs/configuration/dns/fakeip.md b/docs/configuration/dns/fakeip.md index 51db1f42d6..a0524dc8b0 100644 --- a/docs/configuration/dns/fakeip.md +++ b/docs/configuration/dns/fakeip.md @@ -1,4 +1,10 @@ -# FakeIP +--- +icon: material/note-remove +--- + +!!! failure "Removed in sing-box 1.14.0" + + Legacy fake-ip configuration is deprecated in sing-box 1.12.0 and removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-server-formats). ### Structure @@ -20,6 +26,6 @@ Enable FakeIP service. IPv4 address range for FakeIP. -#### inet6_address +#### inet6_range IPv6 address range for FakeIP. diff --git a/docs/configuration/dns/fakeip.zh.md b/docs/configuration/dns/fakeip.zh.md index 3d9a814a66..1e5eca60b6 100644 --- a/docs/configuration/dns/fakeip.zh.md +++ b/docs/configuration/dns/fakeip.zh.md @@ -1,4 +1,10 @@ -# FakeIP +--- +icon: material/note-remove +--- + +!!! failure "已在 sing-box 1.14.0 移除" + + 旧的 fake-ip 配置已在 sing-box 1.12.0 废弃且已在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。 ### 结构 diff --git a/docs/configuration/dns/index.md b/docs/configuration/dns/index.md index c0eafccc3b..19a183ac25 100644 --- a/docs/configuration/dns/index.md +++ b/docs/configuration/dns/index.md @@ -1,10 +1,20 @@ --- -icon: material/new-box +icon: material/alert-decagram --- -!!! quote "Changes in sing-box 1.9.0" +!!! quote "Changes in sing-box 1.14.0" - :material-plus: [client_subnet](#client_subnet) + :material-delete-clock: [independent_cache](#independent_cache) + :material-plus: [optimistic](#optimistic) + :material-plus: [timeout](#timeout) + +!!! quote "Changes in sing-box 1.12.0" + + :material-decagram: [servers](#servers) + +!!! quote "Changes in sing-box 1.11.0" + + :material-plus: [cache_capacity](#cache_capacity) # DNS @@ -20,6 +30,9 @@ icon: material/new-box "disable_cache": false, "disable_expire": false, "independent_cache": false, + "cache_capacity": 0, + "optimistic": false, // or {} + "timeout": "", "reverse_mapping": false, "client_subnet": "", "fakeip": {} @@ -34,7 +47,7 @@ icon: material/new-box |----------|---------------------------------| | `server` | List of [DNS Server](./server/) | | `rules` | List of [DNS Rule](./rule/) | -| `fakeip` | [FakeIP](./fakeip/) | +| `fakeip` | :material-note-remove: [FakeIP](./fakeip/) | #### final @@ -48,20 +61,72 @@ Default domain strategy for resolving the domain names. One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`. -Take no effect if `server.strategy` is set. - #### disable_cache Disable dns cache. +Conflict with `optimistic`. + #### disable_expire Disable dns cache expire. +Conflict with `optimistic`. + #### independent_cache +!!! failure "Deprecated in sing-box 1.14.0" + + `independent_cache` is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-independent-dns-cache). + Make each DNS server's cache independent for special purposes. If enabled, will slightly degrade performance. +#### cache_capacity + +!!! question "Since sing-box 1.11.0" + +LRU cache capacity. + +Value less than 1024 will be ignored. + +#### optimistic + +!!! question "Since sing-box 1.14.0" + +Enable optimistic DNS caching. When a cached DNS entry has expired but is still within the timeout window, +the stale response is returned immediately while a background refresh is triggered. + +Conflict with `disable_cache` and `disable_expire`. + +Accepts a boolean or an object. When set to `true`, the default timeout of `3d` is used. + +```json +{ + "enabled": true, + "timeout": "3d" +} +``` + +##### enabled + +Enable optimistic DNS caching. + +##### timeout + +The maximum time an expired cache entry can be served optimistically. + +`3d` is used by default. + +#### timeout + +!!! question "Since sing-box 1.14.0" + +Default timeout for each DNS query. + +`10s` is used by default. + +Can be overridden by `rules.[].timeout` (DNS rule action) or `domain_resolver.timeout`. + #### reverse_mapping Stores a reverse mapping of IP addresses after responding to a DNS query in order to provide domain names when routing. @@ -77,4 +142,4 @@ Append a `edns0-subnet` OPT extra record with the specified IP prefix to every q If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically. -Can be overrides by `servers.[].client_subnet` or `rules.[].client_subnet`. +Can be overridden by `servers.[].client_subnet` or `rules.[].client_subnet`. diff --git a/docs/configuration/dns/index.zh.md b/docs/configuration/dns/index.zh.md index ba390cef1c..e5c2213953 100644 --- a/docs/configuration/dns/index.zh.md +++ b/docs/configuration/dns/index.zh.md @@ -1,10 +1,20 @@ --- -icon: material/new-box +icon: material/alert-decagram --- -!!! quote "sing-box 1.9.0 中的更改" +!!! quote "sing-box 1.14.0 中的更改" - :material-plus: [client_subnet](#client_subnet) + :material-delete-clock: [independent_cache](#independent_cache) + :material-plus: [optimistic](#optimistic) + :material-plus: [timeout](#timeout) + +!!! quote "sing-box 1.12.0 中的更改" + + :material-decagram: [servers](#servers) + +!!! quote "sing-box 1.11.0 中的更改" + + :material-plus: [cache_capacity](#cache_capacity) # DNS @@ -20,6 +30,9 @@ icon: material/new-box "disable_cache": false, "disable_expire": false, "independent_cache": false, + "cache_capacity": 0, + "optimistic": false, // or {} + "timeout": "", "reverse_mapping": false, "client_subnet": "", "fakeip": {} @@ -47,20 +60,72 @@ icon: material/new-box 可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。 -如果设置了 `server.strategy`,则不生效。 - #### disable_cache 禁用 DNS 缓存。 +与 `optimistic` 冲突。 + #### disable_expire 禁用 DNS 缓存过期。 +与 `optimistic` 冲突。 + #### independent_cache +!!! failure "已在 sing-box 1.14.0 废弃" + + `independent_cache` 已在 sing-box 1.14.0 废弃,且将在 sing-box 1.16.0 中被移除,参阅[迁移指南](/zh/migration/#迁移-independent-dns-cache)。 + 使每个 DNS 服务器的缓存独立,以满足特殊目的。如果启用,将轻微降低性能。 +#### cache_capacity + +!!! question "自 sing-box 1.11.0 起" + +LRU 缓存容量。 + +小于 1024 的值将被忽略。 + +#### optimistic + +!!! question "自 sing-box 1.14.0 起" + +启用乐观 DNS 缓存。当缓存的 DNS 条目已过期但仍在超时窗口内时, +立即返回过期的响应,同时在后台触发刷新。 + +与 `disable_cache` 和 `disable_expire` 冲突。 + +接受布尔值或对象。当设置为 `true` 时,使用默认超时 `3d`。 + +```json +{ + "enabled": true, + "timeout": "3d" +} +``` + +##### enabled + +启用乐观 DNS 缓存。 + +##### timeout + +过期缓存条目可被乐观提供的最长时间。 + +默认使用 `3d`。 + +#### timeout + +!!! question "自 sing-box 1.14.0 起" + +每次 DNS 查询的默认超时时间。 + +默认使用 `10s`。 + +可被 `rules.[].timeout`(DNS 规则动作)或 `domain_resolver.timeout` 覆盖。 + #### reverse_mapping 在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。 @@ -77,6 +142,6 @@ icon: material/new-box 可以被 `servers.[].client_subnet` 或 `rules.[].client_subnet` 覆盖。 -#### fakeip +#### fakeip :material-note-remove: [FakeIP](./fakeip/) 设置。 diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 4c4abacb65..1fc4eafaf3 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -1,13 +1,57 @@ --- -icon: material/new-box +icon: material/alert-decagram --- +!!! quote "Changes in sing-box 1.14.0" + + :material-plus: [source_mac_address](#source_mac_address) + :material-plus: [source_hostname](#source_hostname) + :material-plus: [preferred_by](#preferred_by) + :material-plus: [match_response](#match_response) + :material-delete-clock: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty) + :material-plus: [response_rcode](#response_rcode) + :material-plus: [response_answer](#response_answer) + :material-plus: [response_ns](#response_ns) + :material-plus: [response_extra](#response_extra) + :material-plus: [package_name_regex](#package_name_regex) + :material-alert: [ip_version](#ip_version) + :material-alert: [query_type](#query_type) + +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [interface_address](#interface_address) + :material-plus: [network_interface_address](#network_interface_address) + :material-plus: [default_interface_address](#default_interface_address) + +!!! quote "Changes in sing-box 1.12.0" + + :material-plus: [ip_accept_any](#ip_accept_any) + :material-delete-clock: [outbound](#outbound) + +!!! quote "Changes in sing-box 1.11.0" + + :material-plus: [action](#action) + :material-alert: [server](#server) + :material-alert: [disable_cache](#disable_cache) + :material-alert: [rewrite_ttl](#rewrite_ttl) + :material-alert: [client_subnet](#client_subnet) + :material-plus: [network_type](#network_type) + :material-plus: [network_is_expensive](#network_is_expensive) + :material-plus: [network_is_constrained](#network_is_constrained) + +!!! quote "Changes in sing-box 1.10.0" + + :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) + :material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source) + :material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty) + :material-plus: [process_path_regex](#process_path_regex) + !!! quote "Changes in sing-box 1.9.0" :material-plus: [geoip](#geoip) :material-plus: [ip_cidr](#ip_cidr) :material-plus: [ip_is_private](#ip_is_private) - :material-plus: [client_subnet](#client_subnet) + :material-plus: [client_subnet](#client_subnet) :material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) !!! quote "Changes in sing-box 1.8.0" @@ -55,25 +99,11 @@ icon: material/new-box "domain_regex": [ "^stun\\..+" ], - "geosite": [ - "cn" - ], - "source_geoip": [ - "private" - ], - "geoip": [ - "cn" - ], "source_ip_cidr": [ "10.0.0.0/24", "192.168.0.1" ], "source_ip_is_private": false, - "ip_cidr": [ - "10.0.0.0/24", - "192.168.0.1" - ], - "ip_is_private": false, "source_port": [ 12345 ], @@ -97,9 +127,15 @@ icon: material/new-box "process_path": [ "/usr/bin/curl" ], + "process_path_regex": [ + "^/usr/bin/.+" + ], "package_name": [ "com.termux" ], + "package_name_regex": [ + "^com\\.termux.*" + ], "user": [ "sekai" ], @@ -107,6 +143,34 @@ icon: material/new-box 1000 ], "clash_mode": "direct", + "network_type": [ + "wifi" + ], + "network_is_expensive": false, + "network_is_constrained": false, + "interface_address": { + "en0": [ + "2000::/3" + ] + }, + "network_interface_address": { + "wifi": [ + "2000::/3" + ] + }, + "default_interface_address": [ + "2000::/3" + ], + "source_mac_address": [ + "00:11:22:33:44:55" + ], + "source_hostname": [ + "my-device" + ], + "preferred_by": [ + "local", + "ts-dns" + ], "wifi_ssid": [ "My WIFI" ], @@ -117,24 +181,45 @@ icon: material/new-box "geoip-cn", "geosite-cn" ], - "rule_set_ipcidr_match_source": false, + "rule_set_ip_cidr_match_source": false, + "match_response": false, + "ip_cidr": [ + "10.0.0.0/24", + "192.168.0.1" + ], + "ip_is_private": false, + "ip_accept_any": false, + "response_rcode": "", + "response_answer": [], + "response_ns": [], + "response_extra": [], "invert": false, "outbound": [ "direct" ], + "action": "route", "server": "local", - "disable_cache": false, - "rewrite_ttl": 100, - "client_subnet": "127.0.0.1/24" + + // Deprecated + + "rule_set_ip_cidr_accept_empty": false, + "rule_set_ipcidr_match_source": false, + "geosite": [ + "cn" + ], + "source_geoip": [ + "private" + ], + "geoip": [ + "cn" + ] }, { "type": "logical", "mode": "and", "rules": [], - "server": "local", - "disable_cache": false, - "rewrite_ttl": 100, - "client_subnet": "127.0.0.1/24" + "action": "route", + "server": "local" } ] } @@ -157,7 +242,7 @@ icon: material/new-box (`source_port` || `source_port_range`) && `other fields` - Additionally, included rule sets can be considered merged rather than as a single rule sub-item. + Additionally, each branch inside an included rule-set can be considered merged into the outer rule, while different branches keep OR semantics. #### inbound @@ -165,12 +250,46 @@ Tags of [Inbound](/configuration/inbound/). #### ip_version +!!! quote "Changes in sing-box 1.14.0" + + This field now also applies when a DNS rule is matched from an internal + domain resolution that does not target a specific DNS server, such as a + [`resolve`](../../route/rule_action/#resolve) route rule action without a + `server` set. In earlier versions, only DNS queries received from a + client evaluated this field. See + [Migration](/migration/#ip_version-and-query_type-behavior-changes-in-dns-rules) + for the full list. + + Setting this field makes the DNS rule incompatible in the same DNS + configuration with Legacy Address Filter Fields in DNS rules, the Legacy + `strategy` DNS rule action option, and the Legacy + `rule_set_ip_cidr_accept_empty` DNS rule item. To combine with + address-based filtering, use the [`evaluate`](../rule_action/#evaluate) + action and [`match_response`](#match_response). + 4 (A DNS query) or 6 (AAAA DNS query). Not limited if empty. #### query_type +!!! quote "Changes in sing-box 1.14.0" + + This field now also applies when a DNS rule is matched from an internal + domain resolution that does not target a specific DNS server, such as a + [`resolve`](../../route/rule_action/#resolve) route rule action without a + `server` set. In earlier versions, only DNS queries received from a + client evaluated this field. See + [Migration](/migration/#ip_version-and-query_type-behavior-changes-in-dns-rules) + for the full list. + + Setting this field makes the DNS rule incompatible in the same DNS + configuration with Legacy Address Filter Fields in DNS rules, the Legacy + `strategy` DNS rule action option, and the Legacy + `rule_set_ip_cidr_accept_empty` DNS rule item. To combine with + address-based filtering, use the [`evaluate`](../rule_action/#evaluate) + action and [`match_response`](#match_response). + DNS query type. Values can be integers or type name strings. #### network @@ -205,7 +324,7 @@ Match domain using regular expression. !!! failure "Deprecated in sing-box 1.8.0" - Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-sets). + Geosite is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets). Match geosite. @@ -213,7 +332,7 @@ Match geosite. !!! failure "Deprecated in sing-box 1.8.0" - GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets). + GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets). Match source geoip. @@ -259,10 +378,26 @@ Match process name. Match process path. +#### process_path_regex + +!!! question "Since sing-box 1.10.0" + +!!! quote "" + + Only supported on Linux, Windows, and macOS. + +Match process path using regular expression. + #### package_name Match android package name. +#### package_name_regex + +!!! question "Since sing-box 1.14.0" + +Match android package name using regular expression. + #### user !!! quote "" @@ -283,19 +418,116 @@ Match user id. Match Clash mode. -#### wifi_ssid +#### network_type + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Match network type. + +Available values: `wifi`, `cellular`, `ethernet` and `other`. + +#### network_is_expensive + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Match if network is considered Metered (on Android) or considered expensive, +such as Cellular or a Personal Hotspot (on Apple platforms). + +#### network_is_constrained + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Apple platforms. + +Match if network is in Low Data Mode. + +#### interface_address + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported on Linux, Windows, and macOS. + +Match interface address. + +#### network_interface_address + +!!! question "Since sing-box 1.13.0" !!! quote "" Only supported in graphical clients on Android and Apple platforms. +Matches network interface (same values as `network_type`) address. + +#### default_interface_address + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported on Linux, Windows, and macOS. + +Match default interface address. + +#### source_mac_address + +!!! question "Since sing-box 1.14.0" + +!!! quote "" + + Only supported on Linux, macOS, or in graphical clients on Android and macOS. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup. + +Match source device MAC address. + +#### source_hostname + +!!! question "Since sing-box 1.14.0" + +!!! quote "" + + Only supported on Linux, macOS, or in graphical clients on Android and macOS. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup. + +Match source device hostname from DHCP leases. + +#### preferred_by + +!!! question "Since sing-box 1.14.0" + +Match specified DNS servers' preferred domains. + +| Type | Match | +|-------------|------------------------------------------------------------------------------| +| `hosts` | Match predefined entries and entries in hosts files | +| `local` | Match hosts entries, neighbor-resolved hosts, and mDNS local domains | +| `mdns` | Match mDNS local domains (`*.local.` and IPv4/IPv6 link-local reverse zones) | +| `tailscale` | Match MagicDNS hosts and DNS route suffixes | +| `resolved` | Match split DNS and search domains from systemd-resolved links | + +#### wifi_ssid + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms, or on Linux. + Match WiFi SSID. #### wifi_bssid !!! quote "" - Only supported in graphical clients on Android and Apple platforms. + Only supported in graphical clients on Android and Apple platforms, or on Linux. Match WiFi BSSID. @@ -303,13 +535,42 @@ Match WiFi BSSID. !!! question "Since sing-box 1.8.0" -Match [Rule Set](/configuration/route/#rule_set). +Match [rule-set](/configuration/route/#rule_set). #### rule_set_ipcidr_match_source !!! question "Since sing-box 1.9.0" -Make `ipcidr` in rule sets match the source IP. +!!! failure "Deprecated in sing-box 1.10.0" + + `rule_set_ipcidr_match_source` is renamed to `rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0. + +Make `ip_cidr` rule items in rule-sets match the source IP. + +#### rule_set_ip_cidr_match_source + +!!! question "Since sing-box 1.10.0" + +Make `ip_cidr` rule items in rule-sets match the source IP. + +#### match_response + +!!! question "Since sing-box 1.14.0" + +Enable response-based matching. When enabled, this rule matches against the evaluated response +(set by a preceding [`evaluate`](/configuration/dns/rule_action/#evaluate) action) +instead of only matching the original query. + +The evaluated response can also be returned directly by a later [`respond`](/configuration/dns/rule_action/#respond) action. + +Required for Response Match Fields (`response_rcode`, `response_answer`, `response_ns`, `response_extra`). +Also required for `ip_cidr`, `ip_is_private`, and `ip_accept_any` when used with `evaluate` or Response Match Fields. + +#### ip_accept_any + +!!! question "Since sing-box 1.12.0" + +Match when the DNS query response contains at least one address. #### invert @@ -317,41 +578,56 @@ Invert match result. #### outbound +!!! failure "Deprecated in sing-box 1.12.0" + + `outbound` rule items are deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver). + Match outbound. `any` can be used as a value to match any outbound. -#### server +#### action ==Required== -Tag of the target dns server. +See [DNS Rule Actions](../rule_action/) for details. + +#### server + +!!! failure "Deprecated in sing-box 1.11.0" + + Moved to [DNS Rule Action](../rule_action#route). #### disable_cache -Disable cache and save cache in this query. +!!! failure "Deprecated in sing-box 1.11.0" + + Moved to [DNS Rule Action](../rule_action#route). #### rewrite_ttl -Rewrite TTL in DNS responses. +!!! failure "Deprecated in sing-box 1.11.0" + + Moved to [DNS Rule Action](../rule_action#route). #### client_subnet -!!! question "Since sing-box 1.9.0" +!!! failure "Deprecated in sing-box 1.11.0" -Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default. + Moved to [DNS Rule Action](../rule_action#route). -If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically. +### Legacy Address Filter Fields -Will overrides `dns.client_subnet` and `servers.[].client_subnet`. +!!! failure "Deprecated in sing-box 1.14.0" -### Address Filter Fields + Legacy Address Filter Fields are deprecated and will be removed in sing-box 1.16.0, + check [Migration](/migration/#migrate-address-filter-fields-to-response-matching). -Only takes effect for IP address requests. When the query results do not match the address filtering rule items, the current rule will be skipped. +Only takes effect for address requests (A/AAAA/HTTPS). When the query results do not match the address filtering rule items, the current rule will be skipped. !!! info "" - `ip_cidr` items in included rule sets also takes effect as an address filtering field. + `ip_cidr` items in included rule-sets also takes effect as an address filtering field. !!! note "" @@ -359,7 +635,9 @@ Only takes effect for IP address requests. When the query results do not match t #### geoip -!!! question "Since sing-box 1.9.0" +!!! failure "Removed in sing-box 1.12.0" + + GeoIP is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets). Match GeoIP with query response. @@ -369,12 +647,62 @@ Match GeoIP with query response. Match IP CIDR with query response. +As a Legacy Address Filter Field, deprecated. Use with `match_response` instead, +check [Migration](/migration/#migrate-address-filter-fields-to-response-matching). + #### ip_is_private !!! question "Since sing-box 1.9.0" Match private IP with query response. +As a Legacy Address Filter Field, deprecated. Use with `match_response` instead, +check [Migration](/migration/#migrate-address-filter-fields-to-response-matching). + +#### rule_set_ip_cidr_accept_empty + +!!! question "Since sing-box 1.10.0" + +!!! failure "Deprecated in sing-box 1.14.0" + + `rule_set_ip_cidr_accept_empty` is deprecated and will be removed in sing-box 1.16.0, + check [Migration](/migration/#migrate-address-filter-fields-to-response-matching). + +Make `ip_cidr` rules in rule-sets accept empty query response. + +### Response Match Fields + +!!! question "Since sing-box 1.14.0" + +Match fields for the evaluated response. Require `match_response` to be set to `true` +and a preceding rule with [`evaluate`](/configuration/dns/rule_action/#evaluate) action to populate the response. + +That evaluated response may also be returned directly by a later [`respond`](/configuration/dns/rule_action/#respond) action. + +#### response_rcode + +Match DNS response code. + +Accepted values are the same as in the [predefined action rcode](/configuration/dns/rule_action/#rcode). + +#### response_answer + +Match DNS answer records. + +Record format is the same as in [predefined action answer](/configuration/dns/rule_action/#answer). + +#### response_ns + +Match DNS name server records. + +Record format is the same as in [predefined action ns](/configuration/dns/rule_action/#ns). + +#### response_extra + +Match DNS extra records. + +Record format is the same as in [predefined action extra](/configuration/dns/rule_action/#extra). + ### Logical Fields #### type @@ -387,4 +715,4 @@ Match private IP with query response. #### rules -Included rules. \ No newline at end of file +Included rules. diff --git a/docs/configuration/dns/rule.zh.md b/docs/configuration/dns/rule.zh.md index 796d29e8c4..c56e7f5bfc 100644 --- a/docs/configuration/dns/rule.zh.md +++ b/docs/configuration/dns/rule.zh.md @@ -1,13 +1,57 @@ --- -icon: material/new-box +icon: material/alert-decagram --- +!!! quote "sing-box 1.14.0 中的更改" + + :material-plus: [source_mac_address](#source_mac_address) + :material-plus: [source_hostname](#source_hostname) + :material-plus: [preferred_by](#preferred_by) + :material-plus: [match_response](#match_response) + :material-delete-clock: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty) + :material-plus: [response_rcode](#response_rcode) + :material-plus: [response_answer](#response_answer) + :material-plus: [response_ns](#response_ns) + :material-plus: [response_extra](#response_extra) + :material-plus: [package_name_regex](#package_name_regex) + :material-alert: [ip_version](#ip_version) + :material-alert: [query_type](#query_type) + +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [interface_address](#interface_address) + :material-plus: [network_interface_address](#network_interface_address) + :material-plus: [default_interface_address](#default_interface_address) + +!!! quote "sing-box 1.12.0 中的更改" + + :material-plus: [ip_accept_any](#ip_accept_any) + :material-delete-clock: [outbound](#outbound) + +!!! quote "sing-box 1.11.0 中的更改" + + :material-plus: [action](#action) + :material-alert: [server](#server) + :material-alert: [disable_cache](#disable_cache) + :material-alert: [rewrite_ttl](#rewrite_ttl) + :material-alert: [client_subnet](#client_subnet) + :material-plus: [network_type](#network_type) + :material-plus: [network_is_expensive](#network_is_expensive) + :material-plus: [network_is_constrained](#network_is_constrained) + +!!! quote "sing-box 1.10.0 中的更改" + + :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) + :material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source) + :material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty) + :material-plus: [process_path_regex](#process_path_regex) + !!! quote "sing-box 1.9.0 中的更改" :material-plus: [geoip](#geoip) :material-plus: [ip_cidr](#ip_cidr) :material-plus: [ip_is_private](#ip_is_private) - :material-plus: [client_subnet](#client_subnet) + :material-plus: [client_subnet](#client_subnet) :material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) !!! quote "sing-box 1.8.0 中的更改" @@ -55,25 +99,11 @@ icon: material/new-box "domain_regex": [ "^stun\\..+" ], - "geosite": [ - "cn" - ], - "source_geoip": [ - "private" - ], - "geoip": [ - "cn" - ], "source_ip_cidr": [ "10.0.0.0/24", "192.168.0.1" ], "source_ip_is_private": false, - "ip_cidr": [ - "10.0.0.0/24", - "192.168.0.1" - ], - "ip_is_private": false, "source_port": [ 12345 ], @@ -97,9 +127,15 @@ icon: material/new-box "process_path": [ "/usr/bin/curl" ], + "process_path_regex": [ + "^/usr/bin/.+" + ], "package_name": [ "com.termux" ], + "package_name_regex": [ + "^com\\.termux.*" + ], "user": [ "sekai" ], @@ -107,6 +143,34 @@ icon: material/new-box 1000 ], "clash_mode": "direct", + "network_type": [ + "wifi" + ], + "network_is_expensive": false, + "network_is_constrained": false, + "interface_address": { + "en0": [ + "2000::/3" + ] + }, + "network_interface_address": { + "wifi": [ + "2000::/3" + ] + }, + "default_interface_address": [ + "2000::/3" + ], + "source_mac_address": [ + "00:11:22:33:44:55" + ], + "source_hostname": [ + "my-device" + ], + "preferred_by": [ + "local", + "ts-dns" + ], "wifi_ssid": [ "My WIFI" ], @@ -117,22 +181,45 @@ icon: material/new-box "geoip-cn", "geosite-cn" ], - "rule_set_ipcidr_match_source": false, + "rule_set_ip_cidr_match_source": false, + "match_response": false, + "ip_cidr": [ + "10.0.0.0/24", + "192.168.0.1" + ], + "ip_is_private": false, + "ip_accept_any": false, + "response_rcode": "", + "response_answer": [], + "response_ns": [], + "response_extra": [], "invert": false, "outbound": [ "direct" ], + "action": "route", "server": "local", - "disable_cache": false, - "client_subnet": "127.0.0.1/24" + + // 已弃用 + + "rule_set_ip_cidr_accept_empty": false, + "rule_set_ipcidr_match_source": false, + "geosite": [ + "cn" + ], + "source_geoip": [ + "private" + ], + "geoip": [ + "cn" + ] }, { "type": "logical", "mode": "and", "rules": [], - "server": "local", - "disable_cache": false, - "client_subnet": "127.0.0.1/24" + "action": "route", + "server": "local" } ] } @@ -155,7 +242,7 @@ icon: material/new-box (`source_port` || `source_port_range`) && `other fields` - 另外,引用的规则集可视为被合并,而不是作为一个单独的规则子项。 + 另外,引用规则集中的每个分支都可视为与外层规则合并,不同分支之间仍保持 OR 语义。 #### inbound @@ -163,12 +250,38 @@ icon: material/new-box #### ip_version +!!! quote "sing-box 1.14.0 中的更改" + + 此字段现在也会在 DNS 规则被未指定具体 DNS 服务器的内部域名解析匹配时生效, + 例如未设置 `server` 的 [`resolve`](../../route/rule_action/#resolve) 路由规则动作。 + 此前只有来自客户端的 DNS 查询才会评估此字段。完整列表参阅 + [迁移指南](/zh/migration/#dns-规则中的-ip_version-和-query_type-行为更改)。 + + 在 DNS 规则中设置此字段后,该 DNS 规则在同一 DNS 配置中不能与 + 旧版地址筛选字段 (DNS 规则)、旧版 DNS 规则动作 `strategy` 选项, + 或旧版 `rule_set_ip_cidr_accept_empty` DNS 规则项共存。如需与 + 基于地址的筛选组合,请使用 [`evaluate`](../rule_action/#evaluate) 动作和 + [`match_response`](#match_response)。 + 4 (A DNS 查询) 或 6 (AAAA DNS 查询)。 默认不限制。 #### query_type +!!! quote "sing-box 1.14.0 中的更改" + + 此字段现在也会在 DNS 规则被未指定具体 DNS 服务器的内部域名解析匹配时生效, + 例如未设置 `server` 的 [`resolve`](../../route/rule_action/#resolve) 路由规则动作。 + 此前只有来自客户端的 DNS 查询才会评估此字段。完整列表参阅 + [迁移指南](/zh/migration/#dns-规则中的-ip_version-和-query_type-行为更改)。 + + 在 DNS 规则中设置此字段后,该 DNS 规则在同一 DNS 配置中不能与 + 旧版地址筛选字段 (DNS 规则)、旧版 DNS 规则动作 `strategy` 选项, + 或旧版 `rule_set_ip_cidr_accept_empty` DNS 规则项共存。如需与 + 基于地址的筛选组合,请使用 [`evaluate`](../rule_action/#evaluate) 动作和 + [`match_response`](#match_response)。 + DNS 查询类型。值可以为整数或者类型名称字符串。 #### network @@ -201,17 +314,17 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 #### geosite -!!! failure "已在 sing-box 1.8.0 废弃" +!!! failure "已在 sing-box 1.12.0 中被移除" - Geosite 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geosite)。 + GeoSite 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移-geosite-到规则集)。 匹配 Geosite。 #### source_geoip -!!! failure "已在 sing-box 1.8.0 废弃" +!!! failure "已在 sing-box 1.12.0 中被移除" - GeoIP 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geoip)。 + GeoIP 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移-geoip-到规则集)。 匹配源 GeoIP。 @@ -257,10 +370,26 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 匹配进程路径。 +#### process_path_regex + +!!! question "自 sing-box 1.10.0 起" + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +使用正则表达式匹配进程路径。 + #### package_name 匹配 Android 应用包名。 +#### package_name_regex + +!!! question "自 sing-box 1.14.0 起" + +使用正则表达式匹配 Android 应用包名。 + #### user !!! quote "" @@ -281,19 +410,116 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 匹配 Clash 模式。 -#### wifi_ssid +#### network_type + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配网络类型。 + +Available values: `wifi`, `cellular`, `ethernet` and `other`. + +#### network_is_expensive + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配如果网络被视为计费 (在 Android) 或被视为昂贵, +像蜂窝网络或个人热点 (在 Apple 平台)。 + +#### network_is_constrained + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Apple 平台图形客户端中支持。 + +匹配如果网络在低数据模式下。 + +#### interface_address + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +匹配接口地址。 + +#### network_interface_address + +!!! question "自 sing-box 1.13.0 起" !!! quote "" 仅在 Android 与 Apple 平台图形客户端中支持。 +匹配网络接口(可用值同 `network_type`)地址。 + +#### default_interface_address + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +匹配默认接口地址。 + +#### source_mac_address + +!!! question "自 sing-box 1.14.0 起" + +!!! quote "" + + 仅支持 Linux、macOS,或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。 + +匹配源设备 MAC 地址。 + +#### source_hostname + +!!! question "自 sing-box 1.14.0 起" + +!!! quote "" + + 仅支持 Linux、macOS,或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。 + +匹配源设备从 DHCP 租约获取的主机名。 + +#### preferred_by + +!!! question "自 sing-box 1.14.0 起" + +匹配指定 DNS 服务器的首选域名。 + +| 类型 | 匹配 | +|-------------|-------------------------------------------------------------| +| `hosts` | 匹配预定义条目和 hosts 文件中的条目 | +| `local` | 匹配 hosts 中的条目、邻居解析得到的主机名以及 mDNS 本地域名 | +| `mdns` | 匹配 mDNS 本地域名(`*.local.` 以及 IPv4/IPv6 链路本地反向区域) | +| `tailscale` | 匹配 MagicDNS 主机和 DNS 路由后缀 | +| `resolved` | 匹配 systemd-resolved 链路中的分流域名和搜索域 | + +#### wifi_ssid + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端和 Linux 中支持。 + 匹配 WiFi SSID。 #### wifi_bssid !!! quote "" - 仅在 Android 与 Apple 平台图形客户端中支持。 + 仅在 Android 与 Apple 平台图形客户端和 Linux 中支持。 匹配 WiFi BSSID。 @@ -307,7 +533,34 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 !!! question "自 sing-box 1.9.0 起" -使规则集中的 `ipcidr` 规则匹配源 IP。 +!!! failure "已在 sing-box 1.10.0 废弃" + + `rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 中被移除。 + +使规则集中的 `ip_cidr` 规则匹配源 IP。 + +#### rule_set_ip_cidr_match_source + +!!! question "自 sing-box 1.10.0 起" + +使规则集中的 `ip_cidr` 规则匹配源 IP。 + +#### match_response + +!!! question "自 sing-box 1.14.0 起" + +启用响应匹配。启用后,此规则将匹配已评估的响应(由前序 [`evaluate`](/zh/configuration/dns/rule_action/#evaluate) 动作设置),而不仅是匹配原始查询。 + +该已评估的响应也可以被后续的 [`respond`](/zh/configuration/dns/rule_action/#respond) 动作直接返回。 + +响应匹配字段(`response_rcode`、`response_answer`、`response_ns`、`response_extra`)需要此选项。 +当与 `evaluate` 或响应匹配字段一起使用时,`ip_cidr`、`ip_is_private` 和 `ip_accept_any` 也需要此选项。 + +#### ip_accept_any + +!!! question "自 sing-box 1.12.0 起" + +当 DNS 查询响应包含至少一个地址时匹配。 #### invert @@ -315,37 +568,52 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 #### outbound +!!! failure "已在 sing-box 1.12.0 废弃" + + `outbound` 规则项已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移-outbound-dns-规则项到域解析选项)。 + 匹配出站。 `any` 可作为值用于匹配任意出站。 -#### server +#### action ==必填== -目标 DNS 服务器的标签。 +参阅 [规则动作](../rule_action/)。 + +#### server + +!!! failure "已在 sing-box 1.11.0 废弃" + + 已移动到 [DNS 规则动作](../rule_action#route). #### disable_cache -在此查询中禁用缓存。 +!!! failure "已在 sing-box 1.11.0 废弃" + + 已移动到 [DNS 规则动作](../rule_action#route). #### rewrite_ttl -重写 DNS 回应中的 TTL。 +!!! failure "已在 sing-box 1.11.0 废弃" + + 已移动到 [DNS 规则动作](../rule_action#route). #### client_subnet -!!! question "自 sing-box 1.9.0 起" +!!! failure "已在 sing-box 1.11.0 废弃" -默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。 + 已移动到 [DNS 规则动作](../rule_action#route). -如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。 +### 旧版地址筛选字段 -将覆盖 `dns.client_subnet` 与 `servers.[].client_subnet`。 +!!! failure "已在 sing-box 1.14.0 废弃" -### 地址筛选字段 + 旧版地址筛选字段已废弃,且将在 sing-box 1.16.0 中被移除, + 参阅[迁移指南](/zh/migration/#迁移地址筛选字段到响应匹配)。 -仅对IP地址请求生效。 当查询结果与地址筛选规则项不匹配时,将跳过当前规则。 +仅对地址请求 (A/AAAA/HTTPS) 生效。 当查询结果与地址筛选规则项不匹配时,将跳过当前规则。 !!! info "" @@ -357,7 +625,10 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 #### geoip -!!! question "自 sing-box 1.9.0 起" +!!! failure "已在 sing-box 1.12.0 中被移除" + + GeoIP 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移-geoip-到规则集)。 + 与查询响应匹配 GeoIP。 @@ -365,7 +636,10 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 !!! question "自 sing-box 1.9.0 起" -与查询相应匹配 IP CIDR。 +与查询响应匹配 IP CIDR。 + +作为旧版地址筛选字段已废弃。请改为配合 `match_response` 使用, +参阅[迁移指南](/zh/migration/#迁移地址筛选字段到响应匹配)。 #### ip_is_private @@ -373,6 +647,53 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 与查询响应匹配非公开 IP。 +作为旧版地址筛选字段已废弃。请改为配合 `match_response` 使用, +参阅[迁移指南](/zh/migration/#迁移地址筛选字段到响应匹配)。 + +#### rule_set_ip_cidr_accept_empty + +!!! question "自 sing-box 1.10.0 起" + +!!! failure "已在 sing-box 1.14.0 废弃" + + `rule_set_ip_cidr_accept_empty` 已废弃且将在 sing-box 1.16.0 中被移除, + 参阅[迁移指南](/zh/migration/#迁移地址筛选字段到响应匹配)。 + +使规则集中的 `ip_cidr` 规则接受空查询响应。 + +### 响应匹配字段 + +!!! question "自 sing-box 1.14.0 起" + +已评估的响应的匹配字段。需要将 `match_response` 设为 `true`, +且需要前序规则使用 [`evaluate`](/zh/configuration/dns/rule_action/#evaluate) 动作来填充响应。 + +该已评估的响应也可以被后续的 [`respond`](/zh/configuration/dns/rule_action/#respond) 动作直接返回。 + +#### response_rcode + +匹配 DNS 响应码。 + +接受的值与 [predefined 动作 rcode](/zh/configuration/dns/rule_action/#rcode) 中相同。 + +#### response_answer + +匹配 DNS 应答记录。 + +记录格式与 [predefined 动作 answer](/zh/configuration/dns/rule_action/#answer) 中相同。 + +#### response_ns + +匹配 DNS 名称服务器记录。 + +记录格式与 [predefined 动作 ns](/zh/configuration/dns/rule_action/#ns) 中相同。 + +#### response_extra + +匹配 DNS 额外记录。 + +记录格式与 [predefined 动作 extra](/zh/configuration/dns/rule_action/#extra) 中相同。 + ### 逻辑字段 #### type @@ -381,8 +702,12 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 #### mode +==必填== + `and` 或 `or` #### rules +==必填== + 包括的规则。 diff --git a/docs/configuration/dns/rule_action.md b/docs/configuration/dns/rule_action.md new file mode 100644 index 0000000000..3555d6ede3 --- /dev/null +++ b/docs/configuration/dns/rule_action.md @@ -0,0 +1,251 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.14.0" + + :material-delete-clock: [strategy](#strategy) + :material-plus: [evaluate](#evaluate) + :material-plus: [respond](#respond) + :material-plus: [disable_optimistic_cache](#disable_optimistic_cache) + :material-plus: [timeout](#timeout) + +!!! quote "Changes in sing-box 1.12.0" + + :material-plus: [strategy](#strategy) + :material-plus: [predefined](#predefined) + +!!! question "Since sing-box 1.11.0" + +### route + +```json +{ + "action": "route", // default + "server": "", + "strategy": "", + "disable_cache": false, + "disable_optimistic_cache": false, + "rewrite_ttl": null, + "timeout": "", + "client_subnet": null +} +``` + +`route` inherits the classic rule behavior of routing DNS requests to the specified server. + +#### server + +==Required== + +Tag of target server. + +#### strategy + +!!! question "Since sing-box 1.12.0" + +!!! failure "Deprecated in sing-box 1.14.0" + + `strategy` is deprecated in sing-box 1.14.0 and will be removed in sing-box 1.16.0. + +Set domain strategy for this query. + +One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`. + +#### disable_cache + +Disable cache and save cache in this query. + +#### disable_optimistic_cache + +!!! question "Since sing-box 1.14.0" + +Disable optimistic DNS caching in this query. + +#### rewrite_ttl + +Rewrite TTL in DNS responses. + +#### timeout + +!!! question "Since sing-box 1.14.0" + +Override the DNS query timeout for matched queries. + +Will override `dns.timeout`. + +#### client_subnet + +Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default. + +If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically. + +Will override `dns.client_subnet`. + +### evaluate + +!!! question "Since sing-box 1.14.0" + +```json +{ + "action": "evaluate", + "server": "", + "disable_cache": false, + "disable_optimistic_cache": false, + "rewrite_ttl": null, + "timeout": "", + "client_subnet": null +} +``` + +`evaluate` sends a DNS query to the specified server and saves the evaluated response for subsequent rules +to match against using [`match_response`](/configuration/dns/rule/#match_response) and response fields. +Unlike `route`, it does **not** terminate rule evaluation. + +Only allowed on top-level DNS rules (not inside logical sub-rules). +Rules that use [`match_response`](/configuration/dns/rule/#match_response) or Response Match Fields +require a preceding top-level rule with `evaluate` action. A rule's own `evaluate` action +does not satisfy this requirement, because matching happens before the action runs. + +#### server + +==Required== + +Tag of target server. + +#### disable_cache + +Disable cache and save cache in this query. + +#### disable_optimistic_cache + +!!! question "Since sing-box 1.14.0" + +Disable optimistic DNS caching in this query. + +#### rewrite_ttl + +Rewrite TTL in DNS responses. + +#### timeout + +!!! question "Since sing-box 1.14.0" + +Override the DNS query timeout for matched queries. + +Will override `dns.timeout`. + +#### client_subnet + +Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default. + +If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically. + +Will override `dns.client_subnet`. + +### respond + +!!! question "Since sing-box 1.14.0" + +```json +{ + "action": "respond" +} +``` + +`respond` terminates rule evaluation and returns the evaluated response from a preceding [`evaluate`](/configuration/dns/rule_action/#evaluate) action. + +This action does not send a new DNS query and has no extra options. + +Only allowed after a preceding top-level `evaluate` rule. If the action is reached without an evaluated response at runtime, the request fails with an error instead of falling through to later rules. + +### route-options + +```json +{ + "action": "route-options", + "disable_cache": false, + "disable_optimistic_cache": false, + "rewrite_ttl": null, + "timeout": "", + "client_subnet": null +} +``` + +`route-options` set options for routing. + +### reject + +```json +{ + "action": "reject", + "method": "", + "no_drop": false +} +``` + +`reject` reject DNS requests. + +#### method + +- `default`: Reply with REFUSED. +- `drop`: Drop the request. + +`default` will be used by default. + +#### no_drop + +If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s. + +Not available when `method` is set to drop. + +### predefined + +!!! question "Since sing-box 1.12.0" + +```json +{ + "action": "predefined", + "rcode": "", + "answer": [], + "ns": [], + "extra": [] +} +``` + +`predefined` responds with predefined DNS records. + +#### rcode + +The response code. + +| Value | Value in the legacy rcode server | Description | +|------------|----------------------------------|-----------------| +| `NOERROR` | `success` | Ok | +| `FORMERR` | `format_error` | Bad request | +| `SERVFAIL` | `server_failure` | Server failure | +| `NXDOMAIN` | `name_error` | Not found | +| `NOTIMP` | `not_implemented` | Not implemented | +| `REFUSED` | `refused` | Refused | + +`NOERROR` will be used by default. + +#### answer + +List of text DNS record to respond as answers. + +Examples: + +| Record Type | Example | +|-------------|-------------------------------| +| `A` | `localhost. IN A 127.0.0.1` | +| `AAAA` | `localhost. IN AAAA ::1` | +| `TXT` | `localhost. IN TXT \"Hello\"` | + +#### ns + +List of text DNS record to respond as name servers. + +#### extra + +List of text DNS record to respond as extra records. diff --git a/docs/configuration/dns/rule_action.zh.md b/docs/configuration/dns/rule_action.zh.md new file mode 100644 index 0000000000..756051dd9b --- /dev/null +++ b/docs/configuration/dns/rule_action.zh.md @@ -0,0 +1,249 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.14.0 中的更改" + + :material-delete-clock: [strategy](#strategy) + :material-plus: [evaluate](#evaluate) + :material-plus: [respond](#respond) + :material-plus: [disable_optimistic_cache](#disable_optimistic_cache) + :material-plus: [timeout](#timeout) + +!!! quote "sing-box 1.12.0 中的更改" + + :material-plus: [strategy](#strategy) + :material-plus: [predefined](#predefined) + +!!! question "自 sing-box 1.11.0 起" + +### route + +```json +{ + "action": "route", // 默认 + "server": "", + "strategy": "", + "disable_cache": false, + "disable_optimistic_cache": false, + "rewrite_ttl": null, + "timeout": "", + "client_subnet": null +} +``` + +`route` 继承了将 DNS 请求 路由到指定服务器的经典规则动作。 + +#### server + +==必填== + +目标 DNS 服务器的标签。 + +#### strategy + +!!! question "自 sing-box 1.12.0 起" + +!!! failure "已在 sing-box 1.14.0 废弃" + + `strategy` 已在 sing-box 1.14.0 废弃,且将在 sing-box 1.16.0 中被移除。 + +为此查询设置域名策略。 + +可选项:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。 + +#### disable_cache + +在此查询中禁用缓存。 + +#### disable_optimistic_cache + +!!! question "自 sing-box 1.14.0 起" + +在此查询中禁用乐观 DNS 缓存。 + +#### rewrite_ttl + +重写 DNS 回应中的 TTL。 + +#### timeout + +!!! question "自 sing-box 1.14.0 起" + +覆盖匹配查询的 DNS 查询超时时间。 + +将覆盖 `dns.timeout`。 + +#### client_subnet + +默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。 + +如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。 + +将覆盖 `dns.client_subnet`. + +### evaluate + +!!! question "自 sing-box 1.14.0 起" + +```json +{ + "action": "evaluate", + "server": "", + "disable_cache": false, + "disable_optimistic_cache": false, + "rewrite_ttl": null, + "timeout": "", + "client_subnet": null +} +``` + +`evaluate` 向指定服务器发送 DNS 查询并保存已评估的响应,供后续规则通过 [`match_response`](/zh/configuration/dns/rule/#match_response) 和响应字段进行匹配。与 `route` 不同,它**不会**终止规则评估。 + +仅允许在顶层 DNS 规则中使用(不可在逻辑子规则内部使用)。 +使用 [`match_response`](/zh/configuration/dns/rule/#match_response) 或响应匹配字段的规则, +需要位于更早的顶层 `evaluate` 规则之后。规则自身的 `evaluate` 动作不能满足这个条件, +因为匹配发生在动作执行之前。 + +#### server + +==必填== + +目标 DNS 服务器的标签。 + +#### disable_cache + +在此查询中禁用缓存。 + +#### disable_optimistic_cache + +!!! question "自 sing-box 1.14.0 起" + +在此查询中禁用乐观 DNS 缓存。 + +#### rewrite_ttl + +重写 DNS 回应中的 TTL。 + +#### timeout + +!!! question "自 sing-box 1.14.0 起" + +覆盖匹配查询的 DNS 查询超时时间。 + +将覆盖 `dns.timeout`。 + +#### client_subnet + +默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。 + +如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。 + +将覆盖 `dns.client_subnet`. + +### respond + +!!! question "自 sing-box 1.14.0 起" + +```json +{ + "action": "respond" +} +``` + +`respond` 会终止规则评估,并直接返回前序 [`evaluate`](/zh/configuration/dns/rule_action/#evaluate) 动作保存的已评估的响应。 + +此动作不会发起新的 DNS 查询,也没有额外选项。 + +只能用于前面已有顶层 `evaluate` 规则的场景。如果运行时命中该动作时没有已评估的响应,则请求会直接返回错误,而不是继续匹配后续规则。 + +### route-options + +```json +{ + "action": "route-options", + "disable_cache": false, + "disable_optimistic_cache": false, + "rewrite_ttl": null, + "timeout": "", + "client_subnet": null +} +``` + +`route-options` 为路由设置选项。 + +### reject + +```json +{ + "action": "reject", + "method": "", + "no_drop": false +} +``` + +`reject` 拒绝 DNS 请求。 + +#### method + +- `default`: 返回 REFUSED。 +- `drop`: 丢弃请求。 + +默认使用 `default`。 + +#### no_drop + +如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`。 + +当 `method` 设为 `drop` 时不可用。 + +### predefined + +!!! question "自 sing-box 1.12.0 起" + +```json +{ + "action": "predefined", + "rcode": "", + "answer": [], + "ns": [], + "extra": [] +} +``` + +`predefined` 以预定义的 DNS 记录响应。 + +#### rcode + +响应码。 + +| 值 | 旧 rcode DNS 服务器中的值 | 描述 | +|------------|--------------------|-----------------| +| `NOERROR` | `success` | Ok | +| `FORMERR` | `format_error` | Bad request | +| `SERVFAIL` | `server_failure` | Server failure | +| `NXDOMAIN` | `name_error` | Not found | +| `NOTIMP` | `not_implemented` | Not implemented | +| `REFUSED` | `refused` | Refused | + +默认使用 `NOERROR`。 + +#### answer + +用于作为回答响应的文本 DNS 记录列表。 + +例子: + +| 记录类型 | 例子 | +|--------|-------------------------------| +| `A` | `localhost. IN A 127.0.0.1` | +| `AAAA` | `localhost. IN AAAA ::1` | +| `TXT` | `localhost. IN TXT \"Hello\"` | + +#### ns + +用于作为名称服务器响应的文本 DNS 记录列表。 + +#### extra + +用于作为额外记录响应的文本 DNS 记录列表。 diff --git a/docs/configuration/dns/server/dhcp.md b/docs/configuration/dns/server/dhcp.md new file mode 100644 index 0000000000..b26da2a547 --- /dev/null +++ b/docs/configuration/dns/server/dhcp.md @@ -0,0 +1,38 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# DHCP + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "dhcp", + "tag": "", + + "interface": "", + + // Dial Fields + } + ] + } +} +``` + +### Fields + +#### interface + +Interface name to listen on. + +Tge default interface will be used by default. + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/dns/server/dhcp.zh.md b/docs/configuration/dns/server/dhcp.zh.md new file mode 100644 index 0000000000..2a67a7a305 --- /dev/null +++ b/docs/configuration/dns/server/dhcp.zh.md @@ -0,0 +1,38 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# DHCP + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "dhcp", + "tag": "", + + "interface": "", + + // 拨号字段 + } + ] + } +} +``` + +### 字段 + +#### interface + +要监听的网络接口名称。 + +默认使用默认接口。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。 \ No newline at end of file diff --git a/docs/configuration/dns/server/fakeip.md b/docs/configuration/dns/server/fakeip.md new file mode 100644 index 0000000000..6d2bba4395 --- /dev/null +++ b/docs/configuration/dns/server/fakeip.md @@ -0,0 +1,35 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# Fake IP + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "fakeip", + "tag": "", + + "inet4_range": "198.18.0.0/15", + "inet6_range": "fc00::/18" + } + ] + } +} +``` + +### Fields + +#### inet4_range + +IPv4 address range for FakeIP. + +#### inet6_address + +IPv6 address range for FakeIP. diff --git a/docs/configuration/dns/server/fakeip.zh.md b/docs/configuration/dns/server/fakeip.zh.md new file mode 100644 index 0000000000..06dbdff03f --- /dev/null +++ b/docs/configuration/dns/server/fakeip.zh.md @@ -0,0 +1,35 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# Fake IP + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "fakeip", + "tag": "", + + "inet4_range": "198.18.0.0/15", + "inet6_range": "fc00::/18" + } + ] + } +} +``` + +### 字段 + +#### inet4_range + +FakeIP 的 IPv4 地址范围。 + +#### inet6_range + +FakeIP 的 IPv6 地址范围。 \ No newline at end of file diff --git a/docs/configuration/dns/server/hosts.md b/docs/configuration/dns/server/hosts.md new file mode 100644 index 0000000000..2b2cdee63b --- /dev/null +++ b/docs/configuration/dns/server/hosts.md @@ -0,0 +1,123 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# Hosts + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "hosts", + "tag": "", + + "path": [], + "predefined": {} + } + ] + } +} +``` + +!!! note "" + + You can ignore the JSON Array [] tag when the content is only one item + +### Fields + +#### path + +List of paths to hosts files. + +`/etc/hosts` is used by default. + +`C:\Windows\System32\Drivers\etc\hosts` is used by default on Windows. + +Example: + +```json +{ + // "path": "/etc/hosts" + + "path": [ + "/etc/hosts", + "$HOME/.hosts" + ] +} +``` + +#### predefined + +Predefined hosts. + +Example: + +```json +{ + "predefined": { + "www.google.com": "127.0.0.1", + "localhost": [ + "127.0.0.1", + "::1" + ] + } +} +``` + +### Examples + +=== "Use hosts if available" + + === ":material-card-multiple: sing-box 1.14.0" + + ```json + { + "dns": { + "servers": [ + { + ... + }, + { + "type": "hosts", + "tag": "hosts" + } + ], + "rules": [ + { + "preferred_by": "hosts", + "action": "route", + "server": "hosts" + } + ] + } + } + ``` + + === ":material-card-remove: sing-box < 1.14.0" + + ```json + { + "dns": { + "servers": [ + { + ... + }, + { + "type": "hosts", + "tag": "hosts" + } + ], + "rules": [ + { + "ip_accept_any": true, + "server": "hosts" + } + ] + } + } + ``` \ No newline at end of file diff --git a/docs/configuration/dns/server/hosts.zh.md b/docs/configuration/dns/server/hosts.zh.md new file mode 100644 index 0000000000..bd4ae7638d --- /dev/null +++ b/docs/configuration/dns/server/hosts.zh.md @@ -0,0 +1,123 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# Hosts + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "hosts", + "tag": "", + + "path": [], + "predefined": {} + } + ] + } +} +``` + +!!! note "" + + 当内容只有一项时,可以忽略 JSON 数组 [] 标签 + +### 字段 + +#### path + +hosts 文件路径列表。 + +默认使用 `/etc/hosts`。 + +在 Windows 上默认使用 `C:\Windows\System32\Drivers\etc\hosts`。 + +示例: + +```json +{ + // "path": "/etc/hosts" + + "path": [ + "/etc/hosts", + "$HOME/.hosts" + ] +} +``` + +#### predefined + +预定义的 hosts。 + +示例: + +```json +{ + "predefined": { + "www.google.com": "127.0.0.1", + "localhost": [ + "127.0.0.1", + "::1" + ] + } +} +``` + +### 示例 + +=== "如果可用则使用 hosts" + + === ":material-card-multiple: sing-box 1.14.0" + + ```json + { + "dns": { + "servers": [ + { + ... + }, + { + "type": "hosts", + "tag": "hosts" + } + ], + "rules": [ + { + "preferred_by": "hosts", + "action": "route", + "server": "hosts" + } + ] + } + } + ``` + + === ":material-card-remove: sing-box < 1.14.0" + + ```json + { + "dns": { + "servers": [ + { + ... + }, + { + "type": "hosts", + "tag": "hosts" + } + ], + "rules": [ + { + "ip_accept_any": true, + "server": "hosts" + } + ] + } + } + ``` \ No newline at end of file diff --git a/docs/configuration/dns/server/http3.md b/docs/configuration/dns/server/http3.md new file mode 100644 index 0000000000..dd81ba2dae --- /dev/null +++ b/docs/configuration/dns/server/http3.md @@ -0,0 +1,71 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# DNS over HTTP3 (DoH3) + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "h3", + "tag": "", + + "server": "", + "server_port": 443, + + "path": "", + "headers": {}, + + "tls": {}, + + // Dial Fields + } + ] + } +} +``` + +!!! info "Difference from legacy H3 server" + + * The old server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default. + * The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead. + +### Fields + +#### server + +==Required== + +The address of the DNS server. + +If domain name is used, `domain_resolver` must also be set to resolve IP address. + +#### server_port + +The port of the DNS server. + +`443` will be used by default. + +#### path + +The path of the DNS server. + +`/dns-query` will be used by default. + +#### headers + +Additional headers to be sent to the DNS server. + +#### tls + +TLS configuration, see [TLS](/configuration/shared/tls/#outbound). + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/dns/server/http3.zh.md b/docs/configuration/dns/server/http3.zh.md new file mode 100644 index 0000000000..1032fedb89 --- /dev/null +++ b/docs/configuration/dns/server/http3.zh.md @@ -0,0 +1,71 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# DNS over HTTP3 (DoH3) + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "h3", + "tag": "", + + "server": "", + "server_port": 443, + + "path": "", + "headers": {}, + + "tls": {}, + + // 拨号字段 + } + ] + } +} +``` + +!!! info "与旧版 H3 服务器的区别" + + * 旧服务器默认使用默认出站,除非指定了绕行;新服务器像出站一样使用拨号器,相当于默认使用空的直连出站。 + * 旧服务器使用 `address_resolver` 和 `address_strategy` 来解析服务器中的域名;新服务器改用 [拨号字段](/zh/configuration/shared/dial/) 中的 `domain_resolver` 和 `domain_strategy`。 + +### 字段 + +#### server + +==必填== + +DNS 服务器的地址。 + +如果使用域名,还必须设置 `domain_resolver` 来解析 IP 地址。 + +#### server_port + +DNS 服务器的端口。 + +默认使用 `443`。 + +#### path + +DNS 服务器的路径。 + +默认使用 `/dns-query`。 + +#### headers + +发送到 DNS 服务器的额外标头。 + +#### tls + +TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#出站)。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。 \ No newline at end of file diff --git a/docs/configuration/dns/server/https.md b/docs/configuration/dns/server/https.md new file mode 100644 index 0000000000..46e69a558e --- /dev/null +++ b/docs/configuration/dns/server/https.md @@ -0,0 +1,71 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# DNS over HTTPS (DoH) + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "https", + "tag": "", + + "server": "", + "server_port": 443, + + "path": "", + "headers": {}, + + "tls": {}, + + // Dial Fields + } + ] + } +} +``` + +!!! info "Difference from legacy HTTPS server" + + * The old server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default. + * The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead. + +### Fields + +#### server + +==Required== + +The address of the DNS server. + +If domain name is used, `domain_resolver` must also be set to resolve IP address. + +#### server_port + +The port of the DNS server. + +`443` will be used by default. + +#### path + +The path of the DNS server. + +`/dns-query` will be used by default. + +#### headers + +Additional headers to be sent to the DNS server. + +#### tls + +TLS configuration, see [TLS](/configuration/shared/tls/#outbound). + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/dns/server/https.zh.md b/docs/configuration/dns/server/https.zh.md new file mode 100644 index 0000000000..7aa73c3fb1 --- /dev/null +++ b/docs/configuration/dns/server/https.zh.md @@ -0,0 +1,71 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# DNS over HTTPS (DoH) + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "https", + "tag": "", + + "server": "", + "server_port": 443, + + "path": "", + "headers": {}, + + "tls": {}, + + // 拨号字段 + } + ] + } +} +``` + +!!! info "与旧版 HTTPS 服务器的区别" + + * 旧服务器默认使用默认出站,除非指定了绕行;新服务器像出站一样使用拨号器,相当于默认使用空的直连出站。 + * 旧服务器使用 `address_resolver` 和 `address_strategy` 来解析服务器中的域名;新服务器改用 [拨号字段](/zh/configuration/shared/dial/) 中的 `domain_resolver` 和 `domain_strategy`。 + +### 字段 + +#### server + +==必填== + +DNS 服务器的地址。 + +如果使用域名,还必须设置 `domain_resolver` 来解析 IP 地址。 + +#### server_port + +DNS 服务器的端口。 + +默认使用 `443`。 + +#### path + +DNS 服务器的路径。 + +默认使用 `/dns-query`。 + +#### headers + +发送到 DNS 服务器的额外标头。 + +#### tls + +TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#出站)。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。 \ No newline at end of file diff --git a/docs/configuration/dns/server/index.md b/docs/configuration/dns/server/index.md new file mode 100644 index 0000000000..bcb0586175 --- /dev/null +++ b/docs/configuration/dns/server/index.md @@ -0,0 +1,53 @@ +--- +icon: material/alert-decagram +--- + +!!! quote "Changes in sing-box 1.14.0" + + :material-plus: [mdns](./mdns/) + +!!! quote "Changes in sing-box 1.12.0" + + :material-plus: [type](#type) + +# DNS Server + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "", + "tag": "" + } + ] + } +} +``` + +#### type + +The type of the DNS server. + +| Type | Format | +|-----------------|---------------------------| +| empty (default) | :material-note-remove: [Legacy](./legacy/) | +| `local` | [Local](./local/) | +| `hosts` | [Hosts](./hosts/) | +| `tcp` | [TCP](./tcp/) | +| `udp` | [UDP](./udp/) | +| `tls` | [TLS](./tls/) | +| `quic` | [QUIC](./quic/) | +| `https` | [HTTPS](./https/) | +| `h3` | [HTTP/3](./http3/) | +| `dhcp` | [DHCP](./dhcp/) | +| `mdns` | [mDNS](./mdns/) | +| `fakeip` | [Fake IP](./fakeip/) | +| `tailscale` | [Tailscale](./tailscale/) | +| `resolved` | [Resolved](./resolved/) | + +#### tag + +The tag of the DNS server. diff --git a/docs/configuration/dns/server/index.zh.md b/docs/configuration/dns/server/index.zh.md new file mode 100644 index 0000000000..54dd97e7f9 --- /dev/null +++ b/docs/configuration/dns/server/index.zh.md @@ -0,0 +1,53 @@ +--- +icon: material/alert-decagram +--- + +!!! quote "sing-box 1.14.0 中的更改" + + :material-plus: [mdns](./mdns/) + +!!! quote "sing-box 1.12.0 中的更改" + + :material-plus: [type](#type) + +# DNS Server + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "", + "tag": "" + } + ] + } +} +``` + +#### type + +DNS 服务器的类型。 + +| 类型 | 格式 | +|-----------------|---------------------------| +| empty (default) | :material-note-remove: [Legacy](./legacy/) | +| `local` | [Local](./local/) | +| `hosts` | [Hosts](./hosts/) | +| `tcp` | [TCP](./tcp/) | +| `udp` | [UDP](./udp/) | +| `tls` | [TLS](./tls/) | +| `quic` | [QUIC](./quic/) | +| `https` | [HTTPS](./https/) | +| `h3` | [HTTP/3](./http3/) | +| `dhcp` | [DHCP](./dhcp/) | +| `mdns` | [mDNS](./mdns/) | +| `fakeip` | [Fake IP](./fakeip/) | +| `tailscale` | [Tailscale](./tailscale/) | +| `resolved` | [Resolved](./resolved/) | + +#### tag + +DNS 服务器的标签。 diff --git a/docs/configuration/dns/server.md b/docs/configuration/dns/server/legacy.md similarity index 90% rename from docs/configuration/dns/server.md rename to docs/configuration/dns/server/legacy.md index 3c5245812d..e27b19cbfd 100644 --- a/docs/configuration/dns/server.md +++ b/docs/configuration/dns/server/legacy.md @@ -1,7 +1,11 @@ --- -icon: material/new-box +icon: material/note-remove --- +!!! failure "Removed in sing-box 1.14.0" + + Legacy DNS servers are deprecated in sing-box 1.12.0 and removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-server-formats). + !!! quote "Changes in sing-box 1.9.0" :material-plus: [client_subnet](#client_subnet) @@ -104,6 +108,6 @@ Append a `edns0-subnet` OPT extra record with the specified IP prefix to every q If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically. -Can be overrides by `rules.[].client_subnet`. +Can be overridden by `rules.[].client_subnet`. -Will overrides `dns.client_subnet`. +Will override `dns.client_subnet`. diff --git a/docs/configuration/dns/server.zh.md b/docs/configuration/dns/server/legacy.zh.md similarity index 89% rename from docs/configuration/dns/server.zh.md rename to docs/configuration/dns/server/legacy.zh.md index baa117510a..2ad36839f8 100644 --- a/docs/configuration/dns/server.zh.md +++ b/docs/configuration/dns/server/legacy.zh.md @@ -1,7 +1,11 @@ --- -icon: material/new-box +icon: material/note-remove --- +!!! failure "已在 sing-box 1.14.0 移除" + + 旧的 DNS 服务器配置已在 sing-box 1.12.0 废弃且已在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。 + !!! quote "sing-box 1.9.0 中的更改" :material-plus: [client_subnet](#client_subnet) @@ -49,7 +53,7 @@ DNS 服务器的地址。 | `HTTP3` | `h3://8.8.8.8/dns-query` | | `RCode` | `rcode://refused` | | `DHCP` | `dhcp://auto` 或 `dhcp://en0` | -| [FakeIP](/configuration/dns/fakeip/) | `fakeip` | +| [FakeIP](/zh/configuration/dns/fakeip/) | `fakeip` | !!! warning "" diff --git a/docs/configuration/dns/server/local.md b/docs/configuration/dns/server/local.md new file mode 100644 index 0000000000..39c22b065c --- /dev/null +++ b/docs/configuration/dns/server/local.md @@ -0,0 +1,79 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.14.0" + + :material-plus: [neighbor_domain](#neighbor_domain) + +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [prefer_go](#prefer_go) + +!!! question "Since sing-box 1.12.0" + +# Local + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "local", + "tag": "", + "prefer_go": false, + "neighbor_domain": [] + + // Dial Fields + } + ] + } +} +``` + +!!! info "Difference from legacy local server" + + * The old legacy local server only handles IP requests; the new one handles all types of requests and supports concurrent for IP requests. + * The old local server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default. + +### Fields + +#### prefer_go + +!!! question "Since sing-box 1.13.0" + +When enabled, `local` DNS server will resolve DNS by dialing itself whenever possible. + +Specifically, it disables following behaviors which was added as features in sing-box 1.13.0: + +1. On Apple platforms: Attempt to resolve A/AAAA requests using `getaddrinfo` in NetworkExtension. +2. On Linux: Resolve through `systemd-resolvd`'s DBus interface when available. + +As a sole exception, it cannot disable the following behavior: + +1. In the Android graphical client, +`local` will always resolve DNS through the platform interface, +as there is no other way to obtain upstream DNS servers; +On devices running Android versions lower than 10, this interface can only resolve A/AAAA requests. + +2. On macOS, `local` will try DHCP first in Network Extension, since DHCP respects DIal Fields, +it will not be disabled by `prefer_go`. + +#### neighbor_domain + +!!! question "Since sing-box 1.14.0" + +A list of domain suffixes for which A/AAAA queries are answered from the +[neighbor resolver](/configuration/shared/neighbor/) instead of the upstream. + +Each entry must start with `.`. Only queries whose host part (the portion +before the suffix) contains no dots are matched; `.` matches any +single-label name such as `nas`. + +Example: `[".", ".lan"]`. + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/dns/server/local.zh.md b/docs/configuration/dns/server/local.zh.md new file mode 100644 index 0000000000..c19efc1e15 --- /dev/null +++ b/docs/configuration/dns/server/local.zh.md @@ -0,0 +1,77 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.14.0 中的更改" + + :material-plus: [neighbor_domain](#neighbor_domain) + +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [prefer_go](#prefer_go) + +!!! question "自 sing-box 1.12.0 起" + +# Local + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "local", + "tag": "", + "prefer_go": false, + "neighbor_domain": [], + + // 拨号字段 + } + ] + } +} +``` + +!!! info "与旧版本地服务器的区别" + + * 旧的传统本地服务器只处理 IP 请求;新的服务器处理所有类型的请求,并支持 IP 请求的并发处理。 + * 旧的本地服务器默认使用默认出站,除非指定了绕行;新服务器像出站一样使用拨号器,相当于默认使用空的直连出站。 + +### 字段 + +#### prefer_go + +!!! question "自 sing-box 1.13.0 起" + +启用后,`local` DNS 服务器将尽可能通过拨号自身来解析 DNS。 + +具体来说,它禁用了在 sing-box 1.13.0 中作为功能添加的以下行为: + +1. 在 Apple 平台上:尝试在 NetworkExtension 中使用 `getaddrinfo` 解析 A/AAAA 请求。 +2. 在 Linux 上:当可用时通过 `systemd-resolvd` 的 DBus 接口进行解析。 + +作为唯一的例外,它无法禁用以下行为: + +1. 在 Android 图形客户端中, +`local` 将始终通过平台接口解析 DNS, +因为没有其他方法来获取上游 DNS 服务器; +在运行 Android 10 以下版本的设备上,此接口只能解析 A/AAAA 请求。 + +2. 在 macOS 上,`local` 会在 Network Extension 中首先尝试 DHCP,由于 DHCP 遵循拨号字段, +它不会被 `prefer_go` 禁用。 + +#### neighbor_domain + +!!! question "自 sing-box 1.14.0 起" + +用于从[邻居解析器](/zh/configuration/shared/neighbor/)而非上游回答 A/AAAA 查询的域后缀列表。 + +每一项必须以 `.` 开头。仅匹配后缀之前的主机名部分不包含点的查询; +`.` 匹配任意单标签名称,例如 `nas`。 + +示例:`[".", ".lan"]`。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。 \ No newline at end of file diff --git a/docs/configuration/dns/server/mdns.md b/docs/configuration/dns/server/mdns.md new file mode 100644 index 0000000000..d42e071522 --- /dev/null +++ b/docs/configuration/dns/server/mdns.md @@ -0,0 +1,42 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.14.0" + +# mDNS + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "mdns", + "tag": "", + + "interface": [], + + // Dial Fields + } + ] + } +} +``` + +!!! info "" + + You usually do not need an explicit `mdns` server in addition to a [Local](./local/) server: the local server already routes queries for `*.local.` and IPv4/IPv6 link-local reverse zones via mDNS on non-Apple platforms and via the system resolver on Apple platforms. Add an explicit `mdns` server only when you want to reference it from [`preferred_by`](../rule/#preferred_by) or use it standalone. + +### Fields + +#### interface + +List of network interface names to send mDNS queries on. + +When empty, all interfaces that are up, multicast-capable, and non-loopback are used. + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/dns/server/mdns.zh.md b/docs/configuration/dns/server/mdns.zh.md new file mode 100644 index 0000000000..3af3763e5f --- /dev/null +++ b/docs/configuration/dns/server/mdns.zh.md @@ -0,0 +1,42 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.14.0 起" + +# mDNS + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "mdns", + "tag": "", + + "interface": [], + + // 拨号字段 + } + ] + } +} +``` + +!!! info "" + + 通常不需要在 [Local](./local/) 服务器之外再添加显式的 `mdns` 服务器:本地服务器已经会在非 Apple 平台通过 mDNS、在 Apple 平台通过系统解析器来回答 `*.local.` 与 IPv4/IPv6 链路本地反向区域的查询。仅当需要从 [`preferred_by`](../rule/#preferred_by) 引用,或独立使用时,才需要显式添加 `mdns` 服务器。 + +### 字段 + +#### interface + +用于发送 mDNS 查询的网络接口名称列表。 + +留空时,将使用所有处于 up 状态、支持多播且非环回的接口。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。 diff --git a/docs/configuration/dns/server/quic.md b/docs/configuration/dns/server/quic.md new file mode 100644 index 0000000000..096264fe68 --- /dev/null +++ b/docs/configuration/dns/server/quic.md @@ -0,0 +1,58 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# DNS over QUIC (DoQ) + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "quic", + "tag": "", + + "server": "", + "server_port": 853, + + "tls": {}, + + // Dial Fields + } + ] + } +} +``` + +!!! info "Difference from legacy QUIC server" + + * The old server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default. + * The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead. + +### Fields + +#### server + +==Required== + +The address of the DNS server. + +If domain name is used, `domain_resolver` must also be set to resolve IP address. + +#### server_port + +The port of the DNS server. + +`853` will be used by default. + +#### tls + +TLS configuration, see [TLS](/configuration/shared/tls/#outbound). + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/dns/server/quic.zh.md b/docs/configuration/dns/server/quic.zh.md new file mode 100644 index 0000000000..c18c18ed7e --- /dev/null +++ b/docs/configuration/dns/server/quic.zh.md @@ -0,0 +1,58 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# DNS over QUIC (DoQ) + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "quic", + "tag": "", + + "server": "", + "server_port": 853, + + "tls": {}, + + // 拨号字段 + } + ] + } +} +``` + +!!! info "与旧版 QUIC 服务器的区别" + + * 旧服务器默认使用默认出站,除非指定了绕行;新服务器像出站一样使用拨号器,相当于默认使用空的直连出站。 + * 旧服务器使用 `address_resolver` 和 `address_strategy` 来解析服务器中的域名;新服务器改用 [拨号字段](/zh/configuration/shared/dial/) 中的 `domain_resolver` 和 `domain_strategy`。 + +### 字段 + +#### server + +==必填== + +DNS 服务器的地址。 + +如果使用域名,还必须设置 `domain_resolver` 来解析 IP 地址。 + +#### server_port + +DNS 服务器的端口。 + +默认使用 `853`。 + +#### tls + +TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#出站)。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。 \ No newline at end of file diff --git a/docs/configuration/dns/server/resolved.md b/docs/configuration/dns/server/resolved.md new file mode 100644 index 0000000000..44a4123c43 --- /dev/null +++ b/docs/configuration/dns/server/resolved.md @@ -0,0 +1,113 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# Resolved + +```json +{ + "dns": { + "servers": [ + { + "type": "resolved", + "tag": "", + + "service": "resolved", + "accept_default_resolvers": false + } + ] + } +} +``` + + +### Fields + +#### service + +==Required== + +The tag of the [Resolved Service](/configuration/service/resolved). + +#### accept_default_resolvers + +Indicates whether the default DNS resolvers should be accepted for fallback queries in addition to matching domains. + +Specifically, default DNS resolvers are DNS servers that have `SetLinkDefaultRoute` or `SetLinkDomains ~.` set. + +If not enabled, `NXDOMAIN` will be returned for requests that do not match search or match domains. + +### Examples + +=== "Split DNS only" + + === ":material-card-multiple: sing-box 1.14.0" + + ```json + { + "dns": { + "servers": [ + { + "type": "local", + "tag": "local" + }, + { + "type": "resolved", + "tag": "resolved", + "service": "resolved" + } + ], + "rules": [ + { + "preferred_by": "resolved", + "action": "route", + "server": "resolved" + } + ] + } + } + ``` + + === ":material-card-remove: sing-box < 1.14.0" + + ```json + { + "dns": { + "servers": [ + { + "type": "local", + "tag": "local" + }, + { + "type": "resolved", + "tag": "resolved", + "service": "resolved" + } + ], + "rules": [ + { + "ip_accept_any": true, + "server": "resolved" + } + ] + } + } + ``` + +=== "Use as global DNS" + + ```json + { + "dns": { + "servers": [ + { + "type": "resolved", + "service": "resolved", + "accept_default_resolvers": true + } + ] + } + } + ``` \ No newline at end of file diff --git a/docs/configuration/dns/server/resolved.zh.md b/docs/configuration/dns/server/resolved.zh.md new file mode 100644 index 0000000000..6102aa8099 --- /dev/null +++ b/docs/configuration/dns/server/resolved.zh.md @@ -0,0 +1,112 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# Resolved + +```json +{ + "dns": { + "servers": [ + { + "type": "resolved", + "tag": "", + + "service": "resolved", + "accept_default_resolvers": false + } + ] + } +} +``` + +### 字段 + +#### service + +==必填== + +[Resolved 服务](/zh/configuration/service/resolved) 的标签。 + +#### accept_default_resolvers + +指示是否除了匹配域名外,还应接受默认 DNS 解析器以进行回退查询。 + +具体来说,默认 DNS 解析器是设置了 `SetLinkDefaultRoute` 或 `SetLinkDomains ~.` 的 DNS 服务器。 + +如果未启用,对于不匹配搜索域或匹配域的请求,将返回 `NXDOMAIN`。 + +### 示例 + +=== "仅分割 DNS" + + === ":material-card-multiple: sing-box 1.14.0" + + ```json + { + "dns": { + "servers": [ + { + "type": "local", + "tag": "local" + }, + { + "type": "resolved", + "tag": "resolved", + "service": "resolved" + } + ], + "rules": [ + { + "preferred_by": "resolved", + "action": "route", + "server": "resolved" + } + ] + } + } + ``` + + === ":material-card-remove: sing-box < 1.14.0" + + ```json + { + "dns": { + "servers": [ + { + "type": "local", + "tag": "local" + }, + { + "type": "resolved", + "tag": "resolved", + "service": "resolved" + } + ], + "rules": [ + { + "ip_accept_any": true, + "server": "resolved" + } + ] + } + } + ``` + +=== "用作全局 DNS" + + ```json + { + "dns": { + "servers": [ + { + "type": "resolved", + "service": "resolved", + "accept_default_resolvers": true + } + ] + } + } + ``` \ No newline at end of file diff --git a/docs/configuration/dns/server/tailscale.md b/docs/configuration/dns/server/tailscale.md new file mode 100644 index 0000000000..d28a11c635 --- /dev/null +++ b/docs/configuration/dns/server/tailscale.md @@ -0,0 +1,125 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.14.0" + + :material-plus: [accept_search_domain](#accept_search_domain) + +!!! question "Since sing-box 1.12.0" + +# Tailscale + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "tailscale", + "tag": "", + + "endpoint": "ts-ep", + "accept_default_resolvers": false, + "accept_search_domain": false + } + ] + } +} +``` + +### Fields + +#### endpoint + +==Required== + +The tag of the [Tailscale Endpoint](/configuration/endpoint/tailscale). + +#### accept_default_resolvers + +Indicates whether default DNS resolvers should be accepted for fallback queries in addition to MagicDNS。 + +if not enabled, `NXDOMAIN` will be returned for non-Tailscale domain queries. + +#### accept_search_domain + +!!! question "Since sing-box 1.14.0" + +When enabled, single-label queries (e.g. `my-device`) are retried against each Tailscale search domain until one resolves. + +Default resolvers are not consulted for single-label queries regardless of `accept_default_resolvers`. + +### Examples + +=== "MagicDNS only" + + === ":material-card-multiple: sing-box 1.14.0" + + ```json + { + "dns": { + "servers": [ + { + "type": "local", + "tag": "local" + }, + { + "type": "tailscale", + "tag": "ts", + "endpoint": "ts-ep" + } + ], + "rules": [ + { + "preferred_by": "ts", + "action": "route", + "server": "ts" + } + ] + } + } + ``` + + === ":material-card-remove: sing-box < 1.14.0" + + ```json + { + "dns": { + "servers": [ + { + "type": "local", + "tag": "local" + }, + { + "type": "tailscale", + "tag": "ts", + "endpoint": "ts-ep" + } + ], + "rules": [ + { + "ip_accept_any": true, + "server": "ts" + } + ] + } + } + ``` + +=== "Use as global DNS" + + ```json + { + "dns": { + "servers": [ + { + "type": "tailscale", + "endpoint": "ts-ep", + "accept_default_resolvers": true + } + ] + } + } + ``` diff --git a/docs/configuration/dns/server/tailscale.zh.md b/docs/configuration/dns/server/tailscale.zh.md new file mode 100644 index 0000000000..40fae69cc7 --- /dev/null +++ b/docs/configuration/dns/server/tailscale.zh.md @@ -0,0 +1,125 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.14.0 中的更改" + + :material-plus: [accept_search_domain](#accept_search_domain) + +!!! question "自 sing-box 1.12.0 起" + +# Tailscale + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "tailscale", + "tag": "", + + "endpoint": "ts-ep", + "accept_default_resolvers": false, + "accept_search_domain": false + } + ] + } +} +``` + +### 字段 + +#### endpoint + +==必填== + +[Tailscale 端点](/zh/configuration/endpoint/tailscale) 的标签。 + +#### accept_default_resolvers + +指示是否除了 MagicDNS 外,还应接受默认 DNS 解析器以进行回退查询。 + +如果未启用,对于非 Tailscale 域名查询将返回 `NXDOMAIN`。 + +#### accept_search_domain + +!!! question "自 sing-box 1.14.0 起" + +启用后,单标签查询(例如 `my-device`)将依次附加 Tailscale 搜索域进行重试,直到其中一个解析成功。 + +对于单标签查询,无论 `accept_default_resolvers` 是否启用,都不会使用默认 DNS 解析器。 + +### 示例 + +=== "仅 MagicDNS" + + === ":material-card-multiple: sing-box 1.14.0" + + ```json + { + "dns": { + "servers": [ + { + "type": "local", + "tag": "local" + }, + { + "type": "tailscale", + "tag": "ts", + "endpoint": "ts-ep" + } + ], + "rules": [ + { + "preferred_by": "ts", + "action": "route", + "server": "ts" + } + ] + } + } + ``` + + === ":material-card-remove: sing-box < 1.14.0" + + ```json + { + "dns": { + "servers": [ + { + "type": "local", + "tag": "local" + }, + { + "type": "tailscale", + "tag": "ts", + "endpoint": "ts-ep" + } + ], + "rules": [ + { + "ip_accept_any": true, + "server": "ts" + } + ] + } + } + ``` + +=== "用作全局 DNS" + + ```json + { + "dns": { + "servers": [ + { + "type": "tailscale", + "endpoint": "ts-ep", + "accept_default_resolvers": true + } + ] + } + } + ``` \ No newline at end of file diff --git a/docs/configuration/dns/server/tcp.md b/docs/configuration/dns/server/tcp.md new file mode 100644 index 0000000000..c0a4ceaf47 --- /dev/null +++ b/docs/configuration/dns/server/tcp.md @@ -0,0 +1,52 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# TCP + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "tcp", + "tag": "", + + "server": "", + "server_port": 53, + + // Dial Fields + } + ] + } +} +``` + +!!! info "Difference from legacy TCP server" + + * The old server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default. + * The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead. + +### Fields + +#### server + +==Required== + +The address of the DNS server. + +If domain name is used, `domain_resolver` must also be set to resolve IP address. + +#### server_port + +The port of the DNS server. + +`53` will be used by default. + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/dns/server/tcp.zh.md b/docs/configuration/dns/server/tcp.zh.md new file mode 100644 index 0000000000..6f439bdf3a --- /dev/null +++ b/docs/configuration/dns/server/tcp.zh.md @@ -0,0 +1,52 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# TCP + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "tcp", + "tag": "", + + "server": "", + "server_port": 53, + + // 拨号字段 + } + ] + } +} +``` + +!!! info "与旧版 TCP 服务器的区别" + + * 旧服务器默认使用默认出站,除非指定了绕行;新服务器像出站一样使用拨号器,相当于默认使用空的直连出站。 + * 旧服务器使用 `address_resolver` 和 `address_strategy` 来解析服务器中的域名;新服务器改用 [拨号字段](/zh/configuration/shared/dial/) 中的 `domain_resolver` 和 `domain_strategy`。 + +### 字段 + +#### server + +==必填== + +DNS 服务器的地址。 + +如果使用域名,还必须设置 `domain_resolver` 来解析 IP 地址。 + +#### server_port + +DNS 服务器的端口。 + +默认使用 `53`。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。 \ No newline at end of file diff --git a/docs/configuration/dns/server/tls.md b/docs/configuration/dns/server/tls.md new file mode 100644 index 0000000000..0e3be2260c --- /dev/null +++ b/docs/configuration/dns/server/tls.md @@ -0,0 +1,58 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# DNS over TLS (DoT) + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "tls", + "tag": "", + + "server": "", + "server_port": 853, + + "tls": {}, + + // Dial Fields + } + ] + } +} +``` + +!!! info "Difference from legacy TLS server" + + * The old server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default. + * The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead. + +### Fields + +#### server + +==Required== + +The address of the DNS server. + +If domain name is used, `domain_resolver` must also be set to resolve IP address. + +#### server_port + +The port of the DNS server. + +`853` will be used by default. + +#### tls + +TLS configuration, see [TLS](/configuration/shared/tls/#outbound). + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/dns/server/tls.zh.md b/docs/configuration/dns/server/tls.zh.md new file mode 100644 index 0000000000..afd1111a5f --- /dev/null +++ b/docs/configuration/dns/server/tls.zh.md @@ -0,0 +1,58 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# DNS over TLS (DoT) + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "tls", + "tag": "", + + "server": "", + "server_port": 853, + + "tls": {}, + + // 拨号字段 + } + ] + } +} +``` + +!!! info "与旧版 TLS 服务器的区别" + + * 旧服务器默认使用默认出站,除非指定了绕行;新服务器像出站一样使用拨号器,相当于默认使用空的直连出站。 + * 旧服务器使用 `address_resolver` 和 `address_strategy` 来解析服务器中的域名;新服务器改用 [拨号字段](/zh/configuration/shared/dial/) 中的 `domain_resolver` 和 `domain_strategy`。 + +### 字段 + +#### server + +==必填== + +DNS 服务器的地址。 + +如果使用域名,还必须设置 `domain_resolver` 来解析 IP 地址。 + +#### server_port + +DNS 服务器的端口。 + +默认使用 `853`。 + +#### tls + +TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#出站)。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。 \ No newline at end of file diff --git a/docs/configuration/dns/server/udp.md b/docs/configuration/dns/server/udp.md new file mode 100644 index 0000000000..c551d3c9c5 --- /dev/null +++ b/docs/configuration/dns/server/udp.md @@ -0,0 +1,52 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# UDP + +### Structure + +```json +{ + "dns": { + "servers": [ + { + "type": "udp", + "tag": "", + + "server": "", + "server_port": 53, + + // Dial Fields + } + ] + } +} +``` + +!!! info "Difference from legacy UDP server" + + * The old server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default. + * The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead. + +### Fields + +#### server + +==Required== + +The address of the DNS server. + +If domain name is used, `domain_resolver` must also be set to resolve IP address. + +#### server_port + +The port of the DNS server. + +`53` will be used by default. + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/dns/server/udp.zh.md b/docs/configuration/dns/server/udp.zh.md new file mode 100644 index 0000000000..63feedd86d --- /dev/null +++ b/docs/configuration/dns/server/udp.zh.md @@ -0,0 +1,52 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# UDP + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "udp", + "tag": "", + + "server": "", + "server_port": 53, + + // 拨号字段 + } + ] + } +} +``` + +!!! info "与旧版 UDP 服务器的区别" + + * 旧服务器默认使用默认出站,除非指定了绕行;新服务器像出站一样使用拨号器,相当于默认使用空的直连出站。 + * 旧服务器使用 `address_resolver` 和 `address_strategy` 来解析服务器中的域名;新服务器改用 [拨号字段](/zh/configuration/shared/dial/) 中的 `domain_resolver` 和 `domain_strategy`。 + +### 字段 + +#### server + +==必填== + +DNS 服务器的地址。 + +如果使用域名,还必须设置 `domain_resolver` 来解析 IP 地址。 + +#### server_port + +DNS 服务器的端口。 + +默认使用 `53`。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。 \ No newline at end of file diff --git a/docs/configuration/endpoint/index.md b/docs/configuration/endpoint/index.md new file mode 100644 index 0000000000..b409a7839f --- /dev/null +++ b/docs/configuration/endpoint/index.md @@ -0,0 +1,29 @@ +!!! question "Since sing-box 1.11.0" + +# Endpoint + +An endpoint is a protocol with inbound and outbound behavior. + +### Structure + +```json +{ + "endpoints": [ + { + "type": "", + "tag": "" + } + ] +} +``` + +### Fields + +| Type | Format | +|-------------|---------------------------| +| `wireguard` | [WireGuard](./wireguard/) | +| `tailscale` | [Tailscale](./tailscale/) | + +#### tag + +The tag of the endpoint. diff --git a/docs/configuration/endpoint/index.zh.md b/docs/configuration/endpoint/index.zh.md new file mode 100644 index 0000000000..f7e71b7574 --- /dev/null +++ b/docs/configuration/endpoint/index.zh.md @@ -0,0 +1,29 @@ +!!! question "自 sing-box 1.11.0 起" + +# 端点 + +端点是具有入站和出站行为的协议。 + +### 结构 + +```json +{ + "endpoints": [ + { + "type": "", + "tag": "" + } + ] +} +``` + +### 字段 + +| 类型 | 格式 | +|-------------|---------------------------| +| `wireguard` | [WireGuard](./wireguard/) | +| `tailscale` | [Tailscale](./tailscale/) | + +#### tag + +端点的标签。 diff --git a/docs/configuration/endpoint/tailscale.md b/docs/configuration/endpoint/tailscale.md new file mode 100644 index 0000000000..9ecebab9ac --- /dev/null +++ b/docs/configuration/endpoint/tailscale.md @@ -0,0 +1,171 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.14.0" + + :material-plus: [control_http_client](#control_http_client) + :material-delete-clock: [Dial Fields](#dial-fields) + +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [relay_server_port](#relay_server_port) + :material-plus: [relay_server_static_endpoints](#relay_server_static_endpoints) + :material-plus: [system_interface](#system_interface) + :material-plus: [system_interface_name](#system_interface_name) + :material-plus: [system_interface_mtu](#system_interface_mtu) + :material-plus: [advertise_tags](#advertise_tags) + +!!! question "Since sing-box 1.12.0" + +### Structure + +```json +{ + "type": "tailscale", + "tag": "ts-ep", + "state_directory": "", + "auth_key": "", + "control_url": "", + "control_http_client": {}, // or "" + "ephemeral": false, + "hostname": "", + "accept_routes": false, + "exit_node": "", + "exit_node_allow_lan_access": false, + "advertise_routes": [], + "advertise_exit_node": false, + "advertise_tags": [], + "relay_server_port": 0, + "relay_server_static_endpoints": [], + "system_interface": false, + "system_interface_name": "", + "system_interface_mtu": 0, + "udp_timeout": "5m", + + ... // Dial Fields +} +``` + +### Fields + +#### state_directory + +The directory where the Tailscale state is stored. + +`tailscale` is used by default. + +Example: `$HOME/.tailscale` + +#### auth_key + +!!! note + + Auth key is not required. By default, sing-box will log the login URL (or popup a notification on graphical clients). + +The auth key to create the node. If the node is already created (from state previously stored), then this field is not +used. + +#### control_url + +The coordination server URL. + +`https://controlplane.tailscale.com` is used by default. + +#### ephemeral + +Indicates whether the instance should register as an Ephemeral node (https://tailscale.com/s/ephemeral-nodes). + +#### hostname + +The hostname of the node. + +System hostname is used by default. + +Example: `localhost` + +#### accept_routes + +Indicates whether the node should accept routes advertised by other nodes. + +#### exit_node + +The exit node name or IP address to use. + +#### exit_node_allow_lan_access + +!!! note + + When the exit node does not have a corresponding advertised route, private traffics cannot be routed to the exit node even if `exit_node_allow_lan_access is` set. + +Indicates whether locally accessible subnets should be routed directly or via the exit node. + +#### advertise_routes + +CIDR prefixes to advertise into the Tailscale network as reachable through the current node. + +Example: `["192.168.1.1/24"]` + +#### advertise_exit_node + +Indicates whether the node should advertise itself as an exit node. + +#### advertise_tags + +!!! question "Since sing-box 1.13.0" + +Tags to advertise for this node, for ACL enforcement purposes. + +Example: `["tag:server"]` + +#### relay_server_port + +!!! question "Since sing-box 1.13.0" + +The port to listen on for incoming relay connections from other Tailscale nodes. + +#### relay_server_static_endpoints + +!!! question "Since sing-box 1.13.0" + +Static endpoints to advertise for the relay server. + +#### system_interface + +!!! question "Since sing-box 1.13.0" + +Create a system TUN interface for Tailscale. + +#### system_interface_name + +!!! question "Since sing-box 1.13.0" + +Custom TUN interface name. By default, `tailscale` (or `utun` on macOS) will be used. + +#### system_interface_mtu + +!!! question "Since sing-box 1.13.0" + +Override the TUN MTU. By default, Tailscale's own MTU is used. + +#### udp_timeout + +UDP NAT expiration time. + +`5m` will be used by default. + +#### control_http_client + +!!! question "Since sing-box 1.14.0" + +HTTP Client for connecting to the Tailscale control plane. + +See [HTTP Client Fields](/configuration/shared/http-client/) for details. + +### Dial Fields + +!!! failure "Deprecated in sing-box 1.14.0" + + Dial Fields in Tailscale endpoints are deprecated in sing-box 1.14.0 and will be removed in sing-box 1.16.0, use `control_http_client` instead. + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/endpoint/tailscale.zh.md b/docs/configuration/endpoint/tailscale.zh.md new file mode 100644 index 0000000000..58b4708ae2 --- /dev/null +++ b/docs/configuration/endpoint/tailscale.zh.md @@ -0,0 +1,170 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.14.0 中的更改" + + :material-plus: [control_http_client](#control_http_client) + :material-delete-clock: [拨号字段](#拨号字段) + +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [relay_server_port](#relay_server_port) + :material-plus: [relay_server_static_endpoints](#relay_server_static_endpoints) + :material-plus: [system_interface](#system_interface) + :material-plus: [system_interface_name](#system_interface_name) + :material-plus: [system_interface_mtu](#system_interface_mtu) + :material-plus: [advertise_tags](#advertise_tags) + +!!! question "自 sing-box 1.12.0 起" + +### 结构 + +```json +{ + "type": "tailscale", + "tag": "ts-ep", + "state_directory": "", + "auth_key": "", + "control_url": "", + "control_http_client": {}, // 或 "" + "ephemeral": false, + "hostname": "", + "accept_routes": false, + "exit_node": "", + "exit_node_allow_lan_access": false, + "advertise_routes": [], + "advertise_exit_node": false, + "advertise_tags": [], + "relay_server_port": 0, + "relay_server_static_endpoints": [], + "system_interface": false, + "system_interface_name": "", + "system_interface_mtu": 0, + "udp_timeout": "5m", + + ... // 拨号字段 +} +``` + +### 字段 + +#### state_directory + +存储 Tailscale 状态的目录。 + +默认使用 `tailscale`。 + +示例:`$HOME/.tailscale` + +#### auth_key + +!!! note + + 认证密钥不是必需的。默认情况下,sing-box 将记录登录 URL(或在图形客户端上弹出通知)。 + +用于创建节点的认证密钥。如果节点已经创建(从之前存储的状态),则不使用此字段。 + +#### control_url + +协调服务器 URL。 + +默认使用 `https://controlplane.tailscale.com`。 + +#### ephemeral + +指示实例是否应注册为临时节点 (https://tailscale.com/s/ephemeral-nodes)。 + +#### hostname + +节点的主机名。 + +默认使用系统主机名。 + +示例:`localhost` + +#### accept_routes + +指示节点是否应接受其他节点通告的路由。 + +#### exit_node + +要使用的出口节点名称或 IP 地址。 + +#### exit_node_allow_lan_access + +!!! note + + 当出口节点没有相应的通告路由时,即使设置了 `exit_node_allow_lan_access`,私有流量也无法路由到出口节点。 + +指示本地可访问的子网应该直接路由还是通过出口节点路由。 + +#### advertise_routes + +通告到 Tailscale 网络的 CIDR 前缀,作为可通过当前节点访问的路由。 + +示例:`["192.168.1.1/24"]` + +#### advertise_exit_node + +指示节点是否应将自己通告为出口节点。 + +#### advertise_tags + +!!! question "自 sing-box 1.13.0 起" + +为此节点通告的标签,用于 ACL 执行。 + +示例:`["tag:server"]` + +#### relay_server_port + +!!! question "自 sing-box 1.13.0 起" + +监听来自其他 Tailscale 节点的中继连接的端口。 + +#### relay_server_static_endpoints + +!!! question "自 sing-box 1.13.0 起" + +为中继服务器通告的静态端点。 + +#### system_interface + +!!! question "自 sing-box 1.13.0 起" + +为 Tailscale 创建系统 TUN 接口。 + +#### system_interface_name + +!!! question "自 sing-box 1.13.0 起" + +自定义 TUN 接口名。默认使用 `tailscale`(macOS 上为 `utun`)。 + +#### system_interface_mtu + +!!! question "自 sing-box 1.13.0 起" + +覆盖 TUN 的 MTU。默认使用 Tailscale 自己的 MTU。 + +#### udp_timeout + +UDP NAT 过期时间。 + +默认使用 `5m`。 + +#### control_http_client + +!!! question "自 sing-box 1.14.0 起" + +用于连接 Tailscale 控制平面的 HTTP 客户端。 + +参阅 [HTTP 客户端字段](/zh/configuration/shared/http-client/) 了解详情。 + +### 拨号字段 + +!!! failure "已在 sing-box 1.14.0 废弃" + + Tailscale 端点中的拨号字段已在 sing-box 1.14.0 废弃且将在 sing-box 1.16.0 中被移除,请使用 `control_http_client` 代替。 + +参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。 diff --git a/docs/configuration/endpoint/wireguard.md b/docs/configuration/endpoint/wireguard.md new file mode 100644 index 0000000000..dc3b82289a --- /dev/null +++ b/docs/configuration/endpoint/wireguard.md @@ -0,0 +1,129 @@ +!!! question "Since sing-box 1.11.0" + +### Structure + +```json +{ + "type": "wireguard", + "tag": "wg-ep", + + "system": false, + "name": "", + "mtu": 1408, + "address": [], + "private_key": "", + "listen_port": 10000, + "peers": [ + { + "address": "127.0.0.1", + "port": 10001, + "public_key": "", + "pre_shared_key": "", + "allowed_ips": [], + "persistent_keepalive_interval": 0, + "reserved": [0, 0, 0] + } + ], + "udp_timeout": "", + "workers": 0, + + ... // Dial Fields +} +``` + +!!! note "" + + You can ignore the JSON Array [] tag when the content is only one item + +### Fields + +#### system + +Use system interface. + +Requires privilege and cannot conflict with exists system interfaces. + +#### name + +Custom interface name for system interface. + +#### mtu + +WireGuard MTU. + +`1408` will be used by default. + +#### address + +==Required== + +List of IP (v4 or v6) address prefixes to be assigned to the interface. + +#### private_key + +==Required== + +WireGuard requires base64-encoded public and private keys. These can be generated using the wg(8) utility: + +```shell +wg genkey +echo "private key" || wg pubkey +``` + +or `sing-box generate wg-keypair`. + +#### peers + +==Required== + +List of WireGuard peers. + +#### peers.address + +WireGuard peer address. + +#### peers.port + +WireGuard peer port. + +#### peers.public_key + +==Required== + +WireGuard peer public key. + +#### peers.pre_shared_key + +WireGuard peer pre-shared key. + +#### peers.allowed_ips + +==Required== + +WireGuard allowed IPs. + +#### peers.persistent_keepalive_interval + +WireGuard persistent keepalive interval, in seconds. + +Disabled by default. + +#### peers.reserved + +WireGuard reserved field bytes. + +#### udp_timeout + +UDP NAT expiration time. + +`5m` will be used by default. + +#### workers + +WireGuard worker count. + +CPU count is used by default. + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/endpoint/wireguard.zh.md b/docs/configuration/endpoint/wireguard.zh.md new file mode 100644 index 0000000000..1935135f87 --- /dev/null +++ b/docs/configuration/endpoint/wireguard.zh.md @@ -0,0 +1,131 @@ +!!! question "自 sing-box 1.11.0 起" + +### 结构 + +```json +{ + "type": "wireguard", + "tag": "wg-ep", + + "system": false, + "name": "", + "mtu": 1408, + "address": [], + "private_key": "", + "listen_port": 10000, + "peers": [ + { + "address": "127.0.0.1", + "port": 10001, + "public_key": "", + "pre_shared_key": "", + "allowed_ips": [], + "persistent_keepalive_interval": 0, + "reserved": [0, 0, 0] + } + ], + "udp_timeout": "", + "workers": 0, + + ... // 拨号字段 +} +``` + +!!! note "" + + 当内容只有一项时,可以忽略 JSON 数组 [] 标签 + +### 字段 + +#### system + +使用系统设备。 + +需要特权且不能与已有系统接口冲突。 + +#### name + +为系统接口自定义设备名称。 + +#### mtu + +WireGuard MTU。 + +默认使用 1408。 + +#### address + +==必填== + +接口的 IPv4/IPv6 地址或地址段的列表。 + +要分配给接口的 IP(v4 或 v6)地址段列表。 + +#### private_key + +==必填== + +WireGuard 需要 base64 编码的公钥和私钥。 这些可以使用 wg(8) 实用程序生成: + +```shell +wg genkey +echo "private key" || wg pubkey +``` + +或 `sing-box generate wg-keypair`. + +#### peers + +==必填== + +WireGuard 对等方的列表。 + +#### peers.address + +对等方的 IP 地址。 + +#### peers.port + +对等方的 WireGuard 端口。 + +#### peers.public_key + +==必填== + +对等方的 WireGuard 公钥。 + +#### peers.pre_shared_key + +对等方的预共享密钥。 + +#### peers.allowed_ips + +==必填== + +对等方的允许 IP 地址。 + +#### peers.persistent_keepalive_interval + +对等方的持久性保持活动间隔,以秒为单位。 + +默认禁用。 + +#### peers.reserved + +对等方的保留字段字节。 + +#### udp_timeout + +UDP NAT 过期时间。 + +默认使用 `5m`。 + +#### workers + +WireGuard worker 数量。 + +默认使用 CPU 数量。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/)。 diff --git a/docs/configuration/experimental/cache-file.md b/docs/configuration/experimental/cache-file.md index b30538e591..b93aa19065 100644 --- a/docs/configuration/experimental/cache-file.md +++ b/docs/configuration/experimental/cache-file.md @@ -1,13 +1,14 @@ ---- -icon: material/new-box ---- - !!! question "Since sing-box 1.8.0" +!!! quote "Changes in sing-box 1.14.0" + + :material-delete-clock: [store_rdrc](#store_rdrc) + :material-plus: [store_dns](#store_dns) + !!! quote "Changes in sing-box 1.9.0" :material-plus: [store_rdrc](#store_rdrc) - :material-plus: [rdrc_timeout](#rdrc_timeout) + :material-plus: [rdrc_timeout](#rdrc_timeout) ### Structure @@ -18,7 +19,8 @@ icon: material/new-box "cache_id": "", "store_fakeip": false, "store_rdrc": false, - "rdrc_timeout": "" + "rdrc_timeout": "", + "store_dns": false } ``` @@ -46,9 +48,13 @@ Store fakeip in the cache file #### store_rdrc +!!! failure "Deprecated in sing-box 1.14.0" + + `store_rdrc` is deprecated and will be removed in sing-box 1.16.0, check [Migration](/migration/#migrate-store-rdrc). + Store rejected DNS response cache in the cache file -The check results of [Address filter DNS rule items](/configuration/dns/rule/#address-filter-fields) +The check results of [Legacy Address Filter Fields](/configuration/dns/rule/#legacy-address-filter-fields) will be cached until expiration. #### rdrc_timeout @@ -56,3 +62,9 @@ will be cached until expiration. Timeout of rejected DNS response cache. `7d` is used by default. + +#### store_dns + +!!! question "Since sing-box 1.14.0" + +Store DNS cache in the cache file. diff --git a/docs/configuration/experimental/cache-file.zh.md b/docs/configuration/experimental/cache-file.zh.md index 6d86dc8423..5382f3a185 100644 --- a/docs/configuration/experimental/cache-file.zh.md +++ b/docs/configuration/experimental/cache-file.zh.md @@ -1,13 +1,14 @@ ---- -icon: material/new-box ---- - !!! question "自 sing-box 1.8.0 起" +!!! quote "sing-box 1.14.0 中的更改" + + :material-delete-clock: [store_rdrc](#store_rdrc) + :material-plus: [store_dns](#store_dns) + !!! quote "sing-box 1.9.0 中的更改" :material-plus: [store_rdrc](#store_rdrc) - :material-plus: [rdrc_timeout](#rdrc_timeout) + :material-plus: [rdrc_timeout](#rdrc_timeout) ### 结构 @@ -18,7 +19,8 @@ icon: material/new-box "cache_id": "", "store_fakeip": false, "store_rdrc": false, - "rdrc_timeout": "" + "rdrc_timeout": "", + "store_dns": false } ``` @@ -44,12 +46,22 @@ icon: material/new-box #### store_rdrc +!!! failure "已在 sing-box 1.14.0 废弃" + + `store_rdrc` 已在 sing-box 1.14.0 废弃,且将在 sing-box 1.16.0 中被移除,参阅[迁移指南](/zh/migration/#迁移-store_rdrc)。 + 将拒绝的 DNS 响应缓存存储在缓存文件中。 -[地址筛选 DNS 规则项](/zh/configuration/dns/rule/#_3) 的检查结果将被缓存至过期。 +[旧版地址筛选字段](/zh/configuration/dns/rule/#旧版地址筛选字段) 的检查结果将被缓存至过期。 #### rdrc_timeout 拒绝的 DNS 响应缓存超时。 默认使用 `7d`。 + +#### store_dns + +!!! question "自 sing-box 1.14.0 起" + +将 DNS 缓存存储在缓存文件中。 diff --git a/docs/configuration/experimental/clash-api.md b/docs/configuration/experimental/clash-api.md index e1ca981521..a8908940e1 100644 --- a/docs/configuration/experimental/clash-api.md +++ b/docs/configuration/experimental/clash-api.md @@ -1,3 +1,8 @@ +!!! quote "Changes in sing-box 1.10.0" + + :material-plus: [access_control_allow_origin](#access_control_allow_origin) + :material-plus: [access_control_allow_private_network](#access_control_allow_private_network) + !!! quote "Changes in sing-box 1.8.0" :material-delete-alert: [store_mode](#store_mode) @@ -8,24 +13,59 @@ ### Structure -```json -{ - "external_controller": "127.0.0.1:9090", - "external_ui": "", - "external_ui_download_url": "", - "external_ui_download_detour": "", - "secret": "", - "default_mode": "", - - // Deprecated - - "store_mode": false, - "store_selected": false, - "store_fakeip": false, - "cache_file": "", - "cache_id": "" -} -``` +=== "Structure" + + ```json + { + "external_controller": "127.0.0.1:9090", + "external_ui": "", + "external_ui_download_url": "", + "external_ui_download_detour": "", + "secret": "", + "default_mode": "", + "access_control_allow_origin": [], + "access_control_allow_private_network": false, + + // Deprecated + + "store_mode": false, + "store_selected": false, + "store_fakeip": false, + "cache_file": "", + "cache_id": "" + } + ``` + +=== "Example (online)" + + !!! question "Since sing-box 1.10.0" + + ```json + { + "external_controller": "127.0.0.1:9090", + "access_control_allow_origin": [ + "http://127.0.0.1", + "http://yacd.haishan.me" + ], + "access_control_allow_private_network": true + } + ``` + +=== "Example (download)" + + !!! question "Since sing-box 1.10.0" + + ```json + { + "external_controller": "0.0.0.0:9090", + "external_ui": "dashboard" + // "external_ui_download_detour": "direct" + } + ``` + +!!! note "" + + You can ignore the JSON Array [] tag when the content is only one item ### Fields @@ -63,6 +103,22 @@ Default mode in clash, `Rule` will be used if empty. This setting has no direct effect, but can be used in routing and DNS rules via the `clash_mode` rule item. +#### access_control_allow_origin + +!!! question "Since sing-box 1.10.0" + +CORS allowed origins, `*` will be used if empty. + +To access the Clash API on a private network from a public website, you must explicitly specify it in `access_control_allow_origin` instead of using `*`. + +#### access_control_allow_private_network + +!!! question "Since sing-box 1.10.0" + +Allow access from private network. + +To access the Clash API on a private network from a public website, `access_control_allow_private_network` must be enabled. + #### store_mode !!! failure "Deprecated in sing-box 1.8.0" diff --git a/docs/configuration/experimental/clash-api.zh.md b/docs/configuration/experimental/clash-api.zh.md index 092769ac93..f86b2cac9c 100644 --- a/docs/configuration/experimental/clash-api.zh.md +++ b/docs/configuration/experimental/clash-api.zh.md @@ -1,3 +1,8 @@ +!!! quote "sing-box 1.10.0 中的更改" + + :material-plus: [access_control_allow_origin](#access_control_allow_origin) + :material-plus: [access_control_allow_private_network](#access_control_allow_private_network) + !!! quote "sing-box 1.8.0 中的更改" :material-delete-alert: [store_mode](#store_mode) @@ -8,24 +13,59 @@ ### 结构 -```json -{ - "external_controller": "127.0.0.1:9090", - "external_ui": "", - "external_ui_download_url": "", - "external_ui_download_detour": "", - "secret": "", - "default_mode": "", - - // Deprecated - - "store_mode": false, - "store_selected": false, - "store_fakeip": false, - "cache_file": "", - "cache_id": "" -} -``` +=== "结构" + + ```json + { + "external_controller": "127.0.0.1:9090", + "external_ui": "", + "external_ui_download_url": "", + "external_ui_download_detour": "", + "secret": "", + "default_mode": "", + "access_control_allow_origin": [], + "access_control_allow_private_network": false, + + // Deprecated + + "store_mode": false, + "store_selected": false, + "store_fakeip": false, + "cache_file": "", + "cache_id": "" + } + ``` + +=== "示例 (在线)" + + !!! question "自 sing-box 1.10.0 起" + + ```json + { + "external_controller": "127.0.0.1:9090", + "access_control_allow_origin": [ + "http://127.0.0.1", + "http://yacd.haishan.me" + ], + "access_control_allow_private_network": true + } + ``` + +=== "示例 (下载)" + + !!! question "自 sing-box 1.10.0 起" + + ```json + { + "external_controller": "0.0.0.0:9090", + "external_ui": "dashboard" + // "external_ui_download_detour": "direct" + } + ``` + +!!! note "" + + 当内容只有一项时,可以忽略 JSON 数组 [] 标签 ### Fields @@ -61,6 +101,22 @@ Clash 中的默认模式,默认使用 `Rule`。 此设置没有直接影响,但可以通过 `clash_mode` 规则项在路由和 DNS 规则中使用。 +#### access_control_allow_origin + +!!! question "自 sing-box 1.10.0 起" + +允许的 CORS 来源,默认使用 `*`。 + +要从公共网站访问私有网络上的 Clash API,必须在 `access_control_allow_origin` 中明确指定它而不是使用 `*`。 + +#### access_control_allow_private_network + +!!! question "自 sing-box 1.10.0 起" + +允许从私有网络访问。 + +要从公共网站访问私有网络上的 Clash API,必须启用 `access_control_allow_private_network`。 + #### store_mode !!! failure "已在 sing-box 1.8.0 废弃" diff --git a/docs/configuration/experimental/v2ray-api.zh.md b/docs/configuration/experimental/v2ray-api.zh.md index 81fc842787..87d5c95d91 100644 --- a/docs/configuration/experimental/v2ray-api.zh.md +++ b/docs/configuration/experimental/v2ray-api.zh.md @@ -1,6 +1,6 @@ !!! quote "" - 默认安装不包含 V2Ray API,参阅 [安装](/zh/installation/build-from-source/#_5)。 + 默认安装不包含 V2Ray API,参阅 [安装](/zh/installation/build-from-source/#构建标记)。 ### 结构 diff --git a/docs/configuration/inbound/anytls.md b/docs/configuration/inbound/anytls.md new file mode 100644 index 0000000000..f3780119f6 --- /dev/null +++ b/docs/configuration/inbound/anytls.md @@ -0,0 +1,61 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +### Structure + +```json +{ + "type": "anytls", + "tag": "anytls-in", + + ... // Listen Fields + + "users": [ + { + "name": "sekai", + "password": "8JCsPssfgS8tiRwiMlhARg==" + } + ], + "padding_scheme": [], + "tls": {} +} +``` + +### Listen Fields + +See [Listen Fields](/configuration/shared/listen/) for details. + +### Fields + +#### users + +==Required== + +AnyTLS users. + +#### padding_scheme + +AnyTLS padding scheme line array. + +Default padding scheme: + +```json +[ + "stop=8", + "0=30-30", + "1=100-400", + "2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000", + "3=9-9,500-1000", + "4=500-1000", + "5=500-1000", + "6=500-1000", + "7=500-1000" +] +``` + +#### tls + +TLS configuration, see [TLS](/configuration/shared/tls/#inbound). diff --git a/docs/configuration/inbound/anytls.zh.md b/docs/configuration/inbound/anytls.zh.md new file mode 100644 index 0000000000..8c3d1daf6a --- /dev/null +++ b/docs/configuration/inbound/anytls.zh.md @@ -0,0 +1,61 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +### 结构 + +```json +{ + "type": "anytls", + "tag": "anytls-in", + + ... // 监听字段 + + "users": [ + { + "name": "sekai", + "password": "8JCsPssfgS8tiRwiMlhARg==" + } + ], + "padding_scheme": [], + "tls": {} +} +``` + +### 监听字段 + +参阅 [监听字段](/zh/configuration/shared/listen/)。 + +### 字段 + +#### users + +==必填== + +AnyTLS 用户。 + +#### padding_scheme + +AnyTLS 填充方案行数组。 + +默认填充方案: + +```json +[ + "stop=8", + "0=30-30", + "1=100-400", + "2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000", + "3=9-9,500-1000", + "4=500-1000", + "5=500-1000", + "6=500-1000", + "7=500-1000" +] +``` + +#### tls + +TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#入站)。 diff --git a/docs/configuration/inbound/cloudflared.md b/docs/configuration/inbound/cloudflared.md new file mode 100644 index 0000000000..e91d73e09b --- /dev/null +++ b/docs/configuration/inbound/cloudflared.md @@ -0,0 +1,89 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.14.0" + +`cloudflared` inbound runs an embedded Cloudflare Tunnel client and routes all +incoming tunnel traffic (TCP, UDP, ICMP) through sing-box's routing engine. + +### Structure + +```json +{ + "type": "cloudflared", + "tag": "", + + "token": "", + "ha_connections": 0, + "protocol": "", + "post_quantum": false, + "edge_ip_version": 0, + "datagram_version": "", + "grace_period": "", + "region": "", + "control_dialer": { + ... // Dial Fields + }, + "tunnel_dialer": { + ... // Dial Fields + } +} +``` + +### Fields + +#### token + +==Required== + +Base64-encoded tunnel token from the Cloudflare Zero Trust dashboard +(`Networks → Tunnels → Install connector`). + +#### ha_connections + +Number of high-availability connections to the Cloudflare edge. + +Capped by the number of discovered edge addresses. + +#### protocol + +Transport protocol for edge connections. + +One of `quic` `http2`. + +#### post_quantum + +Enable post-quantum key exchange on the control connection. + +#### edge_ip_version + +IP version used when connecting to the Cloudflare edge. + +One of `0` (automatic) `4` `6`. + +#### datagram_version + +Datagram protocol version used for UDP proxying over QUIC. + +One of `v2` `v3`. Only meaningful when `protocol` is `quic`. + +#### grace_period + +Graceful shutdown window for in-flight edge connections. + +#### region + +Cloudflare edge region selector. + +Conflict with endpoints embedded in `token`. + +#### control_dialer + +[Dial Fields](/configuration/shared/dial/) used when the tunnel client dials the +Cloudflare control plane. + +#### tunnel_dialer + +[Dial Fields](/configuration/shared/dial/) used when the tunnel client dials the +Cloudflare edge data plane. diff --git a/docs/configuration/inbound/cloudflared.zh.md b/docs/configuration/inbound/cloudflared.zh.md new file mode 100644 index 0000000000..65aa7dcf81 --- /dev/null +++ b/docs/configuration/inbound/cloudflared.zh.md @@ -0,0 +1,89 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.14.0 起" + +`cloudflared` 入站运行一个内嵌的 Cloudflare Tunnel 客户端,并将所有传入的隧道流量 +(TCP、UDP、ICMP)通过 sing-box 的路由引擎转发。 + +### 结构 + +```json +{ + "type": "cloudflared", + "tag": "", + + "token": "", + "ha_connections": 0, + "protocol": "", + "post_quantum": false, + "edge_ip_version": 0, + "datagram_version": "", + "grace_period": "", + "region": "", + "control_dialer": { + ... // 拨号字段 + }, + "tunnel_dialer": { + ... // 拨号字段 + } +} +``` + +### 字段 + +#### token + +==必填== + +来自 Cloudflare Zero Trust 仪表板的 Base64 编码隧道令牌 +(`Networks → Tunnels → Install connector`)。 + +#### ha_connections + +到 Cloudflare edge 的高可用连接数。 + +上限为已发现的 edge 地址数量。 + +#### protocol + +edge 连接使用的传输协议。 + +`quic` `http2` 之一。 + +#### post_quantum + +在控制连接上启用后量子密钥交换。 + +#### edge_ip_version + +连接 Cloudflare edge 时使用的 IP 版本。 + +`0`(自动)`4` `6` 之一。 + +#### datagram_version + +通过 QUIC 进行 UDP 代理时使用的数据报协议版本。 + +`v2` `v3` 之一。仅在 `protocol` 为 `quic` 时有效。 + +#### grace_period + +正在处理的 edge 连接的优雅关闭窗口。 + +#### region + +Cloudflare edge 区域选择器。 + +与 `token` 中嵌入的 endpoint 冲突。 + +#### control_dialer + +隧道客户端拨向 Cloudflare 控制面时使用的 +[拨号字段](/zh/configuration/shared/dial/)。 + +#### tunnel_dialer + +隧道客户端拨向 Cloudflare edge 数据面时使用的 +[拨号字段](/zh/configuration/shared/dial/)。 diff --git a/docs/configuration/inbound/http.zh.md b/docs/configuration/inbound/http.zh.md index 2f3d44f556..e1dd876b74 100644 --- a/docs/configuration/inbound/http.zh.md +++ b/docs/configuration/inbound/http.zh.md @@ -26,7 +26,7 @@ #### tls -TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 +TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#入站)。 #### users diff --git a/docs/configuration/inbound/hysteria.md b/docs/configuration/inbound/hysteria.md index 4725aafcea..bd30973f77 100644 --- a/docs/configuration/inbound/hysteria.md +++ b/docs/configuration/inbound/hysteria.md @@ -21,11 +21,16 @@ } ], + "tls": {}, + + ... // QUIC Fields + + // Deprecated + "recv_window_conn": 0, "recv_window_client": 0, "max_conn_client": 0, - "disable_mtu_discovery": false, - "tls": {} + "disable_mtu_discovery": false } ``` @@ -76,32 +81,38 @@ Authentication password, in base64. Authentication password. +#### tls + +==Required== + +TLS configuration, see [TLS](/configuration/shared/tls/#inbound). + +### QUIC Fields + +See [QUIC Fields](/configuration/shared/quic/) for details. + +### Deprecated Fields + #### recv_window_conn -The QUIC stream-level flow control window for receiving data. +!!! failure "Deprecated in sing-box 1.14.0" -`15728640 (15 MB/s)` will be used if empty. + Use QUIC fields `stream_receive_window` instead. #### recv_window_client -The QUIC connection-level flow control window for receiving data. +!!! failure "Deprecated in sing-box 1.14.0" -`67108864 (64 MB/s)` will be used if empty. + Use QUIC fields `connection_receive_window` instead. #### max_conn_client -The maximum number of QUIC concurrent bidirectional streams that a peer is allowed to open. +!!! failure "Deprecated in sing-box 1.14.0" -`1024` will be used if empty. + Use QUIC fields `max_concurrent_streams` instead. #### disable_mtu_discovery -Disables Path MTU Discovery (RFC 8899). Packets will then be at most 1252 (IPv4) / 1232 (IPv6) bytes in size. - -Force enabled on for systems other than Linux and Windows (according to upstream). - -#### tls - -==Required== +!!! failure "Deprecated in sing-box 1.14.0" -TLS configuration, see [TLS](/configuration/shared/tls/#inbound). \ No newline at end of file + Use QUIC fields `disable_path_mtu_discovery` instead. \ No newline at end of file diff --git a/docs/configuration/inbound/hysteria.zh.md b/docs/configuration/inbound/hysteria.zh.md index b75660523e..af1be8f6de 100644 --- a/docs/configuration/inbound/hysteria.zh.md +++ b/docs/configuration/inbound/hysteria.zh.md @@ -21,11 +21,16 @@ } ], + "tls": {}, + + ... // QUIC 字段 + + // 废弃的 + "recv_window_conn": 0, "recv_window_client": 0, "max_conn_client": 0, - "disable_mtu_discovery": false, - "tls": {} + "disable_mtu_discovery": false } ``` @@ -76,32 +81,38 @@ base64 编码的认证密码。 认证密码。 +#### tls + +==必填== + +TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#入站)。 + +### QUIC 字段 + +参阅 [QUIC 字段](/zh/configuration/shared/quic/) 了解详情。 + +### 废弃字段 + #### recv_window_conn -用于接收数据的 QUIC 流级流控制窗口。 +!!! failure "已在 sing-box 1.14.0 废弃" -默认 `15728640 (15 MB/s)`。 + 请使用 QUIC 字段 `stream_receive_window` 代替。 #### recv_window_client -用于接收数据的 QUIC 连接级流控制窗口。 +!!! failure "已在 sing-box 1.14.0 废弃" -默认 `67108864 (64 MB/s)`。 + 请使用 QUIC 字段 `connection_receive_window` 代替。 #### max_conn_client -允许对等点打开的 QUIC 并发双向流的最大数量。 +!!! failure "已在 sing-box 1.14.0 废弃" -默认 `1024`。 + 请使用 QUIC 字段 `max_concurrent_streams` 代替。 #### disable_mtu_discovery -禁用路径 MTU 发现 (RFC 8899)。 数据包的大小最多为 1252 (IPv4) / 1232 (IPv6) 字节。 - -强制为 Linux 和 Windows 以外的系统启用(根据上游)。 - -#### tls - -==必填== +!!! failure "已在 sing-box 1.14.0 废弃" -TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 \ No newline at end of file + 请使用 QUIC 字段 `disable_path_mtu_discovery` 代替。 \ No newline at end of file diff --git a/docs/configuration/inbound/hysteria2.md b/docs/configuration/inbound/hysteria2.md index 7c611e6491..28caae87a0 100644 --- a/docs/configuration/inbound/hysteria2.md +++ b/docs/configuration/inbound/hysteria2.md @@ -1,11 +1,26 @@ +--- +icon: material/alert-decagram +--- + +!!! quote "Changes in sing-box 1.14.0" + + :material-plus: [bbr_profile](#bbr_profile) + :material-plus: [realm](#realm) + :material-alert: [obfs](#obfstype) + +!!! quote "Changes in sing-box 1.11.0" + + :material-alert: [masquerade](#masquerade) + :material-alert: [ignore_client_bandwidth](#ignore_client_bandwidth) + ### Structure ```json { "type": "hysteria2", "tag": "hy2-in", - ... - // Listen Fields + + ... // Listen Fields "up_mbps": 100, "down_mbps": 100, @@ -21,8 +36,20 @@ ], "ignore_client_bandwidth": false, "tls": {}, - "masquerade": "", - "brutal_debug": false + + ... // QUIC Fields + + "masquerade": "", // or {} + "bbr_profile": "", + "brutal_debug": false, + "realm": { + "server_url": "https://realm.example.com", + "token": "", + "realm_id": "", + "stun_servers": [], + "stun_domain_resolver": "", // or {} + "http_client": {} + } } ``` @@ -49,7 +76,7 @@ Conflict with `ignore_client_bandwidth`. #### obfs.type -QUIC traffic obfuscator type, only available with `salamander`. +QUIC traffic obfuscator type, one of `salamander` `gecko`. Disabled if empty. @@ -57,6 +84,22 @@ Disabled if empty. QUIC traffic obfuscator password. +#### obfs.min_packet_size + +!!! question "Since sing-box 1.14.0" + +Minimum on-wire packet size in bytes. Gecko only. + +`512` is used by default. + +#### obfs.max_packet_size + +!!! question "Since sing-box 1.14.0" + +Maximum on-wire packet size in bytes. Gecko only. + +`1200` is used by default. + #### users Hysteria2 users @@ -67,9 +110,13 @@ Authentication password #### ignore_client_bandwidth -Commands the client to use the BBR flow control algorithm instead of Hysteria CC. +*When `up_mbps` and `down_mbps` are not set*: -Conflict with `up_mbps` and `down_mbps`. +Commands clients to use the BBR CC instead of Hysteria CC. + +*When `up_mbps` and `down_mbps` are set*: + +Deny clients to use the BBR CC. #### tls @@ -77,17 +124,121 @@ Conflict with `up_mbps` and `down_mbps`. TLS configuration, see [TLS](/configuration/shared/tls/#inbound). +### QUIC Fields + +See [QUIC Fields](/configuration/shared/quic/) for details. + #### masquerade -HTTP3 server behavior when authentication fails. +HTTP3 server behavior (URL string configuration) when authentication fails. | Scheme | Example | Description | |--------------|-------------------------|--------------------| | `file` | `file:///var/www` | As a file server | | `http/https` | `http://127.0.0.1:8080` | As a reverse proxy | -A 404 page will be returned if empty. +Conflict with `masquerade.type`. + +A 404 page will be returned if masquerade is not configured. + +#### masquerade.type + +HTTP3 server behavior (Object configuration) when authentication fails. + +| Type | Description | Fields | +|----------|-----------------------------|-------------------------------------| +| `file` | As a file server | `directory` | +| `proxy` | As a reverse proxy | `url`, `rewrite_host` | +| `string` | Reply with a fixed response | `status_code`, `headers`, `content` | + +Conflict with `masquerade`. + +A 404 page will be returned if masquerade is not configured. + +#### masquerade.directory + +File server root directory. + +#### masquerade.url + +Reverse proxy target URL. + +#### masquerade.rewrite_host + +Rewrite the `Host` header to the target URL. + +#### masquerade.status_code + +Fixed response status code. + +#### masquerade.headers + +Fixed response headers. + +#### masquerade.content + +Fixed response content. + +#### bbr_profile + +!!! question "Since sing-box 1.14.0" + +BBR congestion control algorithm profile, one of `conservative` `standard` `aggressive`. + +`standard` is used by default. #### brutal_debug Enable debug information logging for Hysteria Brutal CC. + +#### realm + +!!! question "Since sing-box 1.14.0" + +Register this inbound to a Hysteria Realm rendezvous service to enable NAT traversal. + +The inbound discovers its public addresses via STUN, registers them on the realm, and uses UDP hole-punching to accept incoming clients without a publicly reachable listen address. + +See [Hysteria Realm](/configuration/service/hysteria-realm/) for the rendezvous service. + +#### realm.server_url + +==Required== + +Realm rendezvous service URL. + +#### realm.token + +Bearer token for the realm. Must match one of `users[].token` configured on the realm. + +#### realm.realm_id + +==Required== + +Slot identifier on the realm. + +1–64 characters, must match `^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$`. + +Outbounds must use the same `realm_id` to find this server. + +#### realm.stun_servers + +==Required== + +List of STUN servers (`host` or `host:port`) used to discover public addresses. + +#### realm.stun_domain_resolver + +Set domain resolver to use for resolving STUN server domain names. + +This option uses the same format as the [route DNS rule action](/configuration/dns/rule_action/#route) without the `action` field. + +Setting this option directly to a string is equivalent to setting `server` of this options. + +If empty, the default domain resolver is used. + +#### realm.http_client + +HTTP client used to talk to the realm. + +See [HTTP Client](/configuration/shared/http-client/) for details. diff --git a/docs/configuration/inbound/hysteria2.zh.md b/docs/configuration/inbound/hysteria2.zh.md index c936aae8f8..d59101b386 100644 --- a/docs/configuration/inbound/hysteria2.zh.md +++ b/docs/configuration/inbound/hysteria2.zh.md @@ -1,11 +1,26 @@ +--- +icon: material/alert-decagram +--- + +!!! quote "sing-box 1.14.0 中的更改" + + :material-plus: [bbr_profile](#bbr_profile) + :material-plus: [realm](#realm) + :material-alert: [obfs](#obfstype) + +!!! quote "sing-box 1.11.0 中的更改" + + :material-alert: [masquerade](#masquerade) + :material-alert: [ignore_client_bandwidth](#ignore_client_bandwidth) + ### 结构 ```json { "type": "hysteria2", "tag": "hy2-in", - ... - // 监听字段 + + ... // 监听字段 "up_mbps": 100, "down_mbps": 100, @@ -21,15 +36,27 @@ ], "ignore_client_bandwidth": false, "tls": {}, - "masquerade": "", - "brutal_debug": false + + ... // QUIC 字段 + + "masquerade": "", // 或 {} + "bbr_profile": "", + "brutal_debug": false, + "realm": { + "server_url": "https://realm.example.com", + "token": "", + "realm_id": "", + "stun_servers": [], + "stun_domain_resolver": "", // 或 {} + "http_client": {} + } } ``` !!! warning "与官方 Hysteria2 的区别" 官方程序支持一种名为 **userpass** 的验证方式, - 本质上上是将用户名与密码的组合 `:` 作为实际上的密码,而 sing-box 不提供此别名。 + 本质上是将用户名与密码的组合 `:` 作为实际上的密码,而 sing-box 不提供此别名。 要将 sing-box 与官方程序一起使用, 您需要填写该组合作为实际密码。 ### 监听字段 @@ -46,13 +73,29 @@ #### obfs.type -QUIC 流量混淆器类型,仅可设为 `salamander`。 +QUIC 流量混淆器类型,可选 `salamander` `gecko`。 如果为空则禁用。 #### obfs.password -QUIC 流量混淆器密码. +QUIC 流量混淆器密码。 + +#### obfs.min_packet_size + +!!! question "自 sing-box 1.14.0 起" + +最小线上数据包大小(字节)。仅限 Gecko。 + +默认使用 `512`。 + +#### obfs.max_packet_size + +!!! question "自 sing-box 1.14.0 起" + +最大线上数据包大小(字节)。仅限 Gecko。 + +默认使用 `1200`。 #### users @@ -64,27 +107,135 @@ Hysteria 用户 #### ignore_client_bandwidth +*当 `up_mbps` 和 `down_mbps` 未设定时*: + 命令客户端使用 BBR 拥塞控制算法而不是 Hysteria CC。 -与 `up_mbps` 和 `down_mbps` 冲突。 +*当 `up_mbps` 和 `down_mbps` 已设定时*: + +禁止客户端使用 BBR 拥塞控制算法。 #### tls ==必填== -TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 +TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#入站)。 + +### QUIC 字段 + +参阅 [QUIC 字段](/zh/configuration/shared/quic/) 了解详情。 #### masquerade -HTTP3 服务器认证失败时的行为。 +HTTP3 服务器认证失败时的行为 (URL 字符串配置)。 | Scheme | 示例 | 描述 | |--------------|-------------------------|---------| | `file` | `file:///var/www` | 作为文件服务器 | | `http/https` | `http://127.0.0.1:8080` | 作为反向代理 | -如果为空,则返回 404 页。 +如果 masquerade 未配置,则返回 404 页。 + +与 `masquerade.type` 冲突。 + +#### masquerade.type + +HTTP3 服务器认证失败时的行为 (对象配置)。 + +| Type | 描述 | 字段 | +|----------|---------|-------------------------------------| +| `file` | 作为文件服务器 | `directory` | +| `proxy` | 作为反向代理 | `url`, `rewrite_host` | +| `string` | 返回固定响应 | `status_code`, `headers`, `content` | + +如果 masquerade 未配置,则返回 404 页。 + +与 `masquerade` 冲突。 + +#### masquerade.directory + +文件服务器根目录。 + +#### masquerade.url + +反向代理目标 URL。 + +#### masquerade.rewrite_host + +重写请求头中的 Host 字段到目标 URL。 + +#### masquerade.status_code + +固定响应状态码。 + +#### masquerade.headers + +固定响应头。 + +#### masquerade.content + +固定响应内容。 + +#### bbr_profile + +!!! question "自 sing-box 1.14.0 起" + +BBR 拥塞控制算法配置,可选 `conservative` `standard` `aggressive`。 + +默认使用 `standard`。 #### brutal_debug 启用 Hysteria Brutal CC 的调试信息日志记录。 + +#### realm + +!!! question "自 sing-box 1.14.0 起" + +将此入站注册到 Hysteria Realm 会合服务,以启用 NAT 穿透。 + +入站通过 STUN 发现自己的公网地址并注册到 realm,借助 UDP 打洞接受客户端连接,无需可公网直达的监听地址。 + +会合服务参阅 [Hysteria Realm](/zh/configuration/service/hysteria-realm/)。 + +#### realm.server_url + +==必填== + +Realm 会合服务 URL。 + +#### realm.token + +Realm 的 Bearer 令牌,需与 realm 上配置的 `users[].token` 之一匹配。 + +#### realm.realm_id + +==必填== + +Realm 上的槽位标识符。 + +1–64 字符,需匹配 `^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$`。 + +出站需使用相同的 `realm_id` 才能找到本服务器。 + +#### realm.stun_servers + +==必填== + +用于发现公网地址的 STUN 服务器列表(`host` 或 `host:port`)。 + +#### realm.stun_domain_resolver + +用于解析 STUN 服务器域名的域名解析器。 + +此选项的格式与 [路由 DNS 规则动作](/zh/configuration/dns/rule_action/#route) 相同,但不包含 `action` 字段。 + +若直接将此选项设置为字符串,则等同于设置该选项的 `server` 字段。 + +如果为空,则使用默认域名解析器。 + +#### realm.http_client + +与 realm 通信使用的 HTTP 客户端。 + +参阅 [HTTP 客户端](/zh/configuration/shared/http-client/) 了解详情。 diff --git a/docs/configuration/inbound/index.md b/docs/configuration/inbound/index.md index 9ff4a7cf3e..316302c83d 100644 --- a/docs/configuration/inbound/index.md +++ b/docs/configuration/inbound/index.md @@ -22,6 +22,7 @@ | `socks` | [SOCKS](./socks/) | TCP | | `http` | [HTTP](./http/) | TCP | | `shadowsocks` | [Shadowsocks](./shadowsocks/) | TCP | +| `snell` | [Snell](./snell/) | TCP | | `vmess` | [VMess](./vmess/) | TCP | | `trojan` | [Trojan](./trojan/) | TCP | | `naive` | [Naive](./naive/) | :material-close: | @@ -30,9 +31,11 @@ | `tuic` | [TUIC](./tuic/) | :material-close: | | `hysteria2` | [Hysteria2](./hysteria2/) | :material-close: | | `vless` | [VLESS](./vless/) | TCP | +| `anytls` | [AnyTLS](./anytls/) | TCP | | `tun` | [Tun](./tun/) | :material-close: | | `redirect` | [Redirect](./redirect/) | :material-close: | | `tproxy` | [TProxy](./tproxy/) | :material-close: | +| `cloudflared` | [Cloudflared](./cloudflared/) | :material-close: | #### tag diff --git a/docs/configuration/inbound/index.zh.md b/docs/configuration/inbound/index.zh.md index 2c036340a7..9ed80d0f65 100644 --- a/docs/configuration/inbound/index.zh.md +++ b/docs/configuration/inbound/index.zh.md @@ -22,6 +22,7 @@ | `socks` | [SOCKS](./socks/) | TCP | | `http` | [HTTP](./http/) | TCP | | `shadowsocks` | [Shadowsocks](./shadowsocks/) | TCP | +| `snell` | [Snell](./snell/) | TCP | | `vmess` | [VMess](./vmess/) | TCP | | `trojan` | [Trojan](./trojan/) | TCP | | `naive` | [Naive](./naive/) | :material-close: | @@ -30,9 +31,11 @@ | `tuic` | [TUIC](./tuic/) | :material-close: | | `hysteria2` | [Hysteria2](./hysteria2/) | :material-close: | | `vless` | [VLESS](./vless/) | TCP | +| `anytls` | [AnyTLS](./anytls/) | TCP | | `tun` | [Tun](./tun/) | :material-close: | | `redirect` | [Redirect](./redirect/) | :material-close: | | `tproxy` | [TProxy](./tproxy/) | :material-close: | +| `cloudflared` | [Cloudflared](./cloudflared/) | :material-close: | #### tag diff --git a/docs/configuration/inbound/naive.md b/docs/configuration/inbound/naive.md index 0b4ff4b776..a360fa95b0 100644 --- a/docs/configuration/inbound/naive.md +++ b/docs/configuration/inbound/naive.md @@ -1,20 +1,25 @@ +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [quic_congestion_control](#quic_congestion_control) + ### Structure ```json { - "type": "naive", - "tag": "naive-in", - "network": "udp", - - ... // Listen Fields - - "users": [ - { - "username": "sekai", - "password": "password" - } - ], - "tls": {} +"type": "naive", +"tag": "naive-in", +"network": "udp", +... +// Listen Fields + +"users": [ +{ +"username": "sekai", +"password": "password" +} +], +"quic_congestion_control": "", +"tls": {} } ``` @@ -36,6 +41,23 @@ Both if empty. Naive users. +#### quic_congestion_control + +!!! question "Since sing-box 1.13.0" + +QUIC congestion control algorithm. + +| Algorithm | Description | +|----------------|---------------------------------| +| `bbr` | BBR | +| `bbr_standard` | BBR (Standard version) | +| `bbr2` | BBRv2 | +| `bbr2_variant` | BBRv2 (An experimental variant) | +| `cubic` | CUBIC | +| `reno` | New Reno | + +`bbr` is used by default (the default of QUICHE, used by Chromium which NaiveProxy is based on). + #### tls TLS configuration, see [TLS](/configuration/shared/tls/#inbound). \ No newline at end of file diff --git a/docs/configuration/inbound/naive.zh.md b/docs/configuration/inbound/naive.zh.md index 5707e65361..0984d3108d 100644 --- a/docs/configuration/inbound/naive.zh.md +++ b/docs/configuration/inbound/naive.zh.md @@ -1,20 +1,25 @@ +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [quic_congestion_control](#quic_congestion_control) + ### 结构 ```json { - "type": "naive", - "tag": "naive-in", - "network": "udp", - - ... // 监听字段 - - "users": [ - { - "username": "sekai", - "password": "password" - } - ], - "tls": {} +"type": "naive", +"tag": "naive-in", +"network": "udp", + +... // 监听字段 + +"users": [ +{ +"username": "sekai", +"password": "password" +} +], +"quic_congestion_control": "", +"tls": {} } ``` @@ -36,6 +41,23 @@ Naive 用户。 +#### quic_congestion_control + +!!! question "Since sing-box 1.13.0" + +QUIC 拥塞控制算法。 + +| 算法 | 描述 | +|----------------|--------------------| +| `bbr` | BBR | +| `bbr_standard` | BBR (标准版) | +| `bbr2` | BBRv2 | +| `bbr2_variant` | BBRv2 (一种试验变体) | +| `cubic` | CUBIC | +| `reno` | New Reno | + +默认使用 `bbr`(NaiveProxy 基于的 Chromium 使用的 QUICHE 的默认值)。 + #### tls -TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 \ No newline at end of file +TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#入站)。 \ No newline at end of file diff --git a/docs/configuration/inbound/shadowsocks.md b/docs/configuration/inbound/shadowsocks.md index 4072782bc4..71f1448bcf 100644 --- a/docs/configuration/inbound/shadowsocks.md +++ b/docs/configuration/inbound/shadowsocks.md @@ -9,7 +9,10 @@ "method": "2022-blake3-aes-128-gcm", "password": "8JCsPssfgS8tiRwiMlhARg==", - "multiplex": {} + "managed": false, + "multiplex": {}, + "obfs_mode": "", + "obfs_host": "" } ``` @@ -86,6 +89,22 @@ Both if empty. | 2022 methods | `sing-box generate rand --base64 ` | | other methods | any string | +#### managed + +Defaults to `false`. Enable this when the inbound is managed by the [SSM API](/configuration/service/ssm-api) for dynamic user. + #### multiplex See [Multiplex](/configuration/shared/multiplex#inbound) for details. + +#### obfs_mode + +Simple-obfs obfuscation mode. + +One of `http` `tls`, or empty to disable. + +#### obfs_host + +The obfuscation hostname used for HTTP/TLS obfuscation. + +Defaults to `bing.com` if not set. diff --git a/docs/configuration/inbound/shadowsocks.zh.md b/docs/configuration/inbound/shadowsocks.zh.md index cdfd80806c..5cfb4bd6ca 100644 --- a/docs/configuration/inbound/shadowsocks.zh.md +++ b/docs/configuration/inbound/shadowsocks.zh.md @@ -9,7 +9,10 @@ "method": "2022-blake3-aes-128-gcm", "password": "8JCsPssfgS8tiRwiMlhARg==", - "multiplex": {} + "managed": false, + "multiplex": {}, + "obfs_mode": "", + "obfs_host": "" } ``` @@ -48,9 +51,9 @@ } ``` -### Listen Fields +### 监听字段 -See [Listen Fields](/configuration/shared/listen/) for details. +参阅 [监听字段](/zh/configuration/shared/listen/)。 ### 字段 @@ -86,6 +89,22 @@ See [Listen Fields](/configuration/shared/listen/) for details. | 2022 methods | `sing-box generate rand --base64 <密钥长度>` | | other methods | 任意字符串 | +#### managed + +默认为 `false`。当该入站需要由 [SSM API](/zh/configuration/service/ssm-api) 管理用户时必须启用此字段。 + #### multiplex -参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。 +参阅 [多路复用](/zh/configuration/shared/multiplex#入站)。 + +#### obfs_mode + +Simple-obfs 混淆模式。 + +可选值为 `http` `tls`,留空则禁用混淆。 + +#### obfs_host + +HTTP/TLS 混淆使用的主机名。 + +未设置时默认为 `bing.com`。 diff --git a/docs/configuration/inbound/shadowtls.md b/docs/configuration/inbound/shadowtls.md index db010b7908..9dbf1dd5cb 100644 --- a/docs/configuration/inbound/shadowtls.md +++ b/docs/configuration/inbound/shadowtls.md @@ -1,3 +1,11 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.12.0" + + :material-plus: [wildcard_sni](#wildcard_sni) + ### Structure ```json @@ -29,7 +37,8 @@ ... // Dial Fields } }, - "strict_mode": false + "strict_mode": false, + "wildcard_sni": "" } ``` @@ -55,7 +64,6 @@ ShadowTLS password. Only available in the ShadowTLS protocol 2. - #### users ShadowTLS users. @@ -66,11 +74,13 @@ Only available in the ShadowTLS protocol 3. ==Required== -Handshake server address and [Dial options](/configuration/shared/dial/). +When `wildcard_sni` is configured to `all`, the server address is optional. + +Handshake server address and [Dial Fields](/configuration/shared/dial/). #### handshake_for_server_name -Handshake server address and [Dial options](/configuration/shared/dial/) for specific server name. +Handshake server address and [Dial Fields](/configuration/shared/dial/) for specific server name. Only available in the ShadowTLS protocol 2/3. @@ -79,3 +89,19 @@ Only available in the ShadowTLS protocol 2/3. ShadowTLS strict mode. Only available in the ShadowTLS protocol 3. + +#### wildcard_sni + +!!! question "Since sing-box 1.12.0" + +ShadowTLS wildcard SNI mode. + +Available values are: + +* `off`: (default) Disabled. +* `authed`: Authenticated connections will have their destination overwritten to `(servername):443` +* `all`: All connections will have their destination overwritten to `(servername):443` + +Additionally, connections matching `handshake_for_server_name` are not affected. + +Only available in the ShadowTLS protocol 3. diff --git a/docs/configuration/inbound/shadowtls.zh.md b/docs/configuration/inbound/shadowtls.zh.md index 1497ac42f8..be86005131 100644 --- a/docs/configuration/inbound/shadowtls.zh.md +++ b/docs/configuration/inbound/shadowtls.zh.md @@ -1,3 +1,11 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.12.0 中的更改" + + :material-plus: [wildcard_sni](#wildcard_sni) + ### 结构 ```json @@ -29,7 +37,8 @@ ... // 拨号字段 } }, - "strict_mode": false + "strict_mode": false, + "wildcard_sni": "" } ``` @@ -80,3 +89,19 @@ ShadowTLS 用户。 ShadowTLS 严格模式。 仅在 ShadowTLS 协议版本 3 中可用。 + +#### wildcard_sni + +!!! question "自 sing-box 1.12.0 起" + +ShadowTLS 通配符 SNI 模式。 + +可用值: + +* `off`:(默认)禁用。 +* `authed`:已认证的连接的目标将被重写为 `(servername):443`。 +* `all`:所有连接的目标将被重写为 `(servername):443`。 + +此外,匹配 `handshake_for_server_name` 的连接不受影响。 + +仅在 ShadowTLS 协议 3 中可用。 diff --git a/docs/configuration/inbound/snell.md b/docs/configuration/inbound/snell.md new file mode 100644 index 0000000000..0bb747ae91 --- /dev/null +++ b/docs/configuration/inbound/snell.md @@ -0,0 +1,87 @@ +### Structure + +```json +{ + "type": "snell", + "tag": "snell-in", + + ... // Listen Fields + + "psk": "my-pre-shared-key", + "version": 4, + "obfs_mode": "", + "obfs_host": "" +} +``` + +### Multi-User Structure + +```json +{ + "type": "snell", + "tag": "snell-in", + + ... // Listen Fields + + "users": [ + { + "name": "alice", + "psk": "alice-pre-shared-key" + }, + { + "name": "bob", + "psk": "bob-pre-shared-key" + } + ], + "version": 4, + "obfs_mode": "", + "obfs_host": "" +} +``` + +### Listen Fields + +See [Listen Fields](/configuration/shared/listen/) for details. + +### Fields + +#### psk + +==Required if `users` is not set== + +The pre-shared key for single-user authentication. Mutually exclusive with `users`. + +#### users + +==Required if `psk` is not set== + +User list for multi-user mode. Each entry has a `name` and a `psk`. Mutually exclusive with `psk`. + +The matched user name is available to routing rules via `auth_user`. + +#### version + +Snell protocol version. Must be `4` or `5`. + +Defaults to `4`. + +!!! note "QUIC Proxy Mode (v5)" + When `version` is `5`, the server automatically accepts QUIC traffic on the + same port. The destination address and the first QUIC Initial packet are + encrypted by Snell; subsequent packets are forwarded as-is (QUIC provides + its own encryption). No additional configuration is required. + +#### obfs_mode + +Simple-obfs obfuscation mode. + +One of `http` `tls`, or empty to disable. + +!!! warning + TLS obfuscation is not supported for v4/v5. Use [ShadowTLS](/configuration/inbound/shadowtls/) instead. + +#### obfs_host + +The obfuscation hostname used for HTTP/TLS obfuscation. + +Defaults to `bing.com` if not set. diff --git a/docs/configuration/inbound/snell.zh.md b/docs/configuration/inbound/snell.zh.md new file mode 100644 index 0000000000..c9e054b5c7 --- /dev/null +++ b/docs/configuration/inbound/snell.zh.md @@ -0,0 +1,86 @@ +### 结构 + +```json +{ + "type": "snell", + "tag": "snell-in", + + ... // 监听字段 + + "psk": "my-pre-shared-key", + "version": 4, + "obfs_mode": "", + "obfs_host": "" +} +``` + +### 多用户结构 + +```json +{ + "type": "snell", + "tag": "snell-in", + + ... // 监听字段 + + "users": [ + { + "name": "alice", + "psk": "alice-pre-shared-key" + }, + { + "name": "bob", + "psk": "bob-pre-shared-key" + } + ], + "version": 4, + "obfs_mode": "", + "obfs_host": "" +} +``` + +### 监听字段 + +参阅 [监听字段](/zh/configuration/shared/listen/)。 + +### 字段 + +#### psk + +==未设置 `users` 时必填== + +单用户模式的预共享密钥,与 `users` 互斥。 + +#### users + +==未设置 `psk` 时必填== + +多用户模式的用户列表,每项包含 `name` 和 `psk`,与 `psk` 互斥。 + +匹配到的用户名可在路由规则中通过 `auth_user` 使用。 + +#### version + +Snell 协议版本,必须为 `4` 或 `5`。 + +默认为 `4`。 + +!!! note "QUIC 代理模式(v5)" + 当 `version` 为 `5` 时,服务端自动在同一端口接收 QUIC 流量。 + 目标地址和首个 QUIC Initial 包经 Snell 加密传输,后续包直接转发(QUIC 本身已加密)。 + 无需额外配置。 + +#### obfs_mode + +simple-obfs 混淆模式。 + +可选 `http` `tls`,留空则禁用混淆。 + +!!! warning + v4/v5 不支持 TLS 混淆,请改用 [ShadowTLS](/zh/configuration/inbound/shadowtls/)。 + +#### obfs_host + +用于 HTTP/TLS 混淆的主机名。 + +未设置时默认为 `bing.com`。 diff --git a/docs/configuration/inbound/trojan.md b/docs/configuration/inbound/trojan.md index bd6c73b311..e277236b06 100644 --- a/docs/configuration/inbound/trojan.md +++ b/docs/configuration/inbound/trojan.md @@ -47,7 +47,7 @@ TLS configuration, see [TLS](/configuration/shared/tls/#inbound). #### fallback -!!! quote "" +!!! failure "" There is no evidence that GFW detects and blocks Trojan servers based on HTTP responses, and opening the standard http/s port on the server is a much bigger signature. diff --git a/docs/configuration/inbound/trojan.zh.md b/docs/configuration/inbound/trojan.zh.md index d8b30cae68..d81b4c1d31 100644 --- a/docs/configuration/inbound/trojan.zh.md +++ b/docs/configuration/inbound/trojan.zh.md @@ -43,13 +43,11 @@ Trojan 用户。 #### tls -==如果启用 HTTP3 则必填== - -TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 +TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#入站)。 #### fallback -!!! quote "" +!!! failure "" 没有证据表明 GFW 基于 HTTP 响应检测并阻止 Trojan 服务器,并且在服务器上打开标准 http/s 端口是一个更大的特征。 @@ -63,7 +61,7 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 #### multiplex -参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。 +参阅 [多路复用](/zh/configuration/shared/multiplex#入站)。 #### transport diff --git a/docs/configuration/inbound/tuic.md b/docs/configuration/inbound/tuic.md index 8a2d8c7e06..e7d1129b24 100644 --- a/docs/configuration/inbound/tuic.md +++ b/docs/configuration/inbound/tuic.md @@ -18,7 +18,9 @@ "auth_timeout": "3s", "zero_rtt_handshake": false, "heartbeat": "10s", - "tls": {} + "tls": {}, + + ... // QUIC Fields } ``` @@ -75,4 +77,8 @@ Interval for sending heartbeat packets for keeping the connection alive ==Required== -TLS configuration, see [TLS](/configuration/shared/tls/#inbound). \ No newline at end of file +TLS configuration, see [TLS](/configuration/shared/tls/#inbound). + +### QUIC Fields + +See [QUIC Fields](/configuration/shared/quic/) for details. \ No newline at end of file diff --git a/docs/configuration/inbound/tuic.zh.md b/docs/configuration/inbound/tuic.zh.md index 99252056e1..6a231d794e 100644 --- a/docs/configuration/inbound/tuic.zh.md +++ b/docs/configuration/inbound/tuic.zh.md @@ -18,7 +18,9 @@ "auth_timeout": "3s", "zero_rtt_handshake": false, "heartbeat": "10s", - "tls": {} + "tls": {}, + + ... // QUIC 字段 } ``` @@ -75,4 +77,8 @@ QUIC 拥塞控制算法 ==必填== -TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 \ No newline at end of file +TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#入站)。 + +### QUIC 字段 + +参阅 [QUIC 字段](/zh/configuration/shared/quic/) 了解详情。 \ No newline at end of file diff --git a/docs/configuration/inbound/tun.md b/docs/configuration/inbound/tun.md index 1d5d8d0f65..9af118b68a 100644 --- a/docs/configuration/inbound/tun.md +++ b/docs/configuration/inbound/tun.md @@ -2,10 +2,57 @@ icon: material/new-box --- +!!! quote "Changes in sing-box 1.14.0" + + :material-plus: [include_mac_address](#include_mac_address) + :material-plus: [exclude_mac_address](#exclude_mac_address) + :material-plus: [dns_mode](#dns_mode) + :material-plus: [dns_address](#dns_address) + +!!! quote "Changes in sing-box 1.13.3" + + :material-alert: [strict_route](#strict_route) + +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [auto_redirect_reset_mark](#auto_redirect_reset_mark) + :material-plus: [auto_redirect_nfqueue](#auto_redirect_nfqueue) + :material-plus: [exclude_mptcp](#exclude_mptcp) + :material-plus: [auto_redirect_iproute2_fallback_rule_index](#auto_redirect_iproute2_fallback_rule_index) + +!!! quote "Changes in sing-box 1.12.0" + + :material-plus: [loopback_address](#loopback_address) + +!!! quote "Changes in sing-box 1.11.0" + + :material-delete-alert: [gso](#gso) + :material-alert-decagram: [route_address_set](#stack) + :material-alert-decagram: [route_exclude_address_set](#stack) + +!!! quote "Changes in sing-box 1.10.0" + + :material-plus: [address](#address) + :material-delete-clock: [inet4_address](#inet4_address) + :material-delete-clock: [inet6_address](#inet6_address) + :material-plus: [route_address](#route_address) + :material-delete-clock: [inet4_route_address](#inet4_route_address) + :material-delete-clock: [inet6_route_address](#inet6_route_address) + :material-plus: [route_exclude_address](#route_address) + :material-delete-clock: [inet4_route_exclude_address](#inet4_route_exclude_address) + :material-delete-clock: [inet6_route_exclude_address](#inet6_route_exclude_address) + :material-plus: [iproute2_table_index](#iproute2_table_index) + :material-plus: [iproute2_rule_index](#iproute2_table_index) + :material-plus: [auto_redirect](#auto_redirect) + :material-plus: [auto_redirect_input_mark](#auto_redirect_input_mark) + :material-plus: [auto_redirect_output_mark](#auto_redirect_output_mark) + :material-plus: [route_address_set](#route_address_set) + :material-plus: [route_exclude_address_set](#route_address_set) + !!! quote "Changes in sing-box 1.9.0" :material-plus: [platform.http_proxy.bypass_domain](#platformhttp_proxybypass_domain) - :material-plus: [platform.http_proxy.match_domain](#platformhttp_proxymatch_domain) + :material-plus: [platform.http_proxy.match_domain](#platformhttp_proxymatch_domain) !!! quote "Changes in sing-box 1.8.0" @@ -23,26 +70,46 @@ icon: material/new-box "type": "tun", "tag": "tun-in", "interface_name": "tun0", - "inet4_address": "172.19.0.1/30", - "inet6_address": "fdfe:dcba:9876::1/126", + "address": [ + "172.18.0.1/30", + "fdfe:dcba:9876::1/126" + ], "mtu": 9000, - "gso": false, + "dns_mode": "hijack", + "dns_address": [ + "172.18.0.2", + "fdfe:dcba:9876::2" + ], "auto_route": true, + "iproute2_table_index": 2022, + "iproute2_rule_index": 9000, + "auto_redirect": true, + "auto_redirect_input_mark": "0x2023", + "auto_redirect_output_mark": "0x2024", + "auto_redirect_reset_mark": "0x2025", + "auto_redirect_nfqueue": 100, + "auto_redirect_iproute2_fallback_rule_index": 32768, + "exclude_mptcp": false, + "loopback_address": [ + "10.7.0.1" + ], "strict_route": true, - "inet4_route_address": [ + "route_address": [ "0.0.0.0/1", - "128.0.0.0/1" - ], - "inet6_route_address": [ + "128.0.0.0/1", "::/1", "8000::/1" ], - "inet4_route_exclude_address": [ - "192.168.0.0/16" - ], - "inet6_route_exclude_address": [ + "route_exclude_address": [ + "192.168.0.0/16", "fc00::/7" ], + "route_address_set": [ + "geoip-cloudflare" + ], + "route_exclude_address_set": [ + "geoip-cn" + ], "endpoint_independent_nat": false, "udp_timeout": "5m", "stack": "system", @@ -56,13 +123,13 @@ icon: material/new-box 0 ], "include_uid_range": [ - "1000-99999" + "1000:99999" ], "exclude_uid": [ 1000 ], "exclude_uid_range": [ - "1000-99999" + "1000:99999" ], "include_android_user": [ 0, @@ -74,6 +141,12 @@ icon: material/new-box "exclude_package": [ "com.android.captiveportallogin" ], + "include_mac_address": [ + "00:11:22:33:44:55" + ], + "exclude_mac_address": [ + "66:77:88:99:aa:bb" + ], "platform": { "http_proxy": { "enabled": false, @@ -83,8 +156,30 @@ icon: material/new-box "match_domain": [] } }, - - ... // Listen Fields + // Deprecated + "gso": false, + "inet4_address": [ + "172.19.0.1/30" + ], + "inet6_address": [ + "fdfe:dcba:9876::1/126" + ], + "inet4_route_address": [ + "0.0.0.0/1", + "128.0.0.0/1" + ], + "inet6_route_address": [ + "::/1", + "8000::/1" + ], + "inet4_route_exclude_address": [ + "192.168.0.0/16" + ], + "inet6_route_exclude_address": [ + "fc00::/7" + ], + ... + // Listen Fields } ``` @@ -102,27 +197,89 @@ icon: material/new-box Virtual device name, automatically selected if empty. +#### address + +!!! question "Since sing-box 1.10.0" + +IPv4 and IPv6 prefix for the tun interface. + #### inet4_address -==Required== +!!! failure "Deprecated in sing-box 1.10.0" + + `inet4_address` is merged to `address` and will be removed in sing-box 1.12.0. IPv4 prefix for the tun interface. #### inet6_address +!!! failure "Deprecated in sing-box 1.10.0" + + `inet6_address` is merged to `address` and will be removed in sing-box 1.12.0. + IPv6 prefix for the tun interface. #### mtu The maximum transmission unit. +#### dns_mode + +!!! question "Since sing-box 1.14.0" + +How DNS is handled on the TUN interface. + +| Mode | Description | +|------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `disabled` | Do not configure native DNS and do not hijack DNS traffic. | +| `native` | Set the platform's native interface DNS where possible: per-interface DNS on Windows and Apple platforms, and `systemd-resolved` interface DNS on Linux. | +| `hijack` | Same as `native`, with additional port 53 hijacking described below. Used by default. | + +`hijack` adds the following on top of `native`: + +*On Linux*: only DNS sent to non-local destinations can be intercepted. +Traffic destined to addresses on the host's own interfaces (such as +`127.0.0.53` or the host's LAN-side IP) is delivered through the kernel +`local` routing table before any user rule applies, and `OUTPUT` NAT cannot +redirect packets going through `lo`. + +- Without `auto_redirect`, an `iproute2` rule makes port 53 skip the `main` + table's specific-route lookup, forcing DNS that would otherwise be + delivered through a directly-attached subnet through the TUN. Destination + addresses are not rewritten. +- With `auto_redirect`, an nftables rule DNATs port 53 traffic directly to + [`dns_address`](#dns_address). + +*On Windows with [`strict_route`](#strict_route)*: a WFP filter blocks port +53 traffic going through interfaces other than the TUN. + +#### dns_address + +!!! question "Since sing-box 1.14.0" + +List of DNS server addresses used by [`dns_mode`](#dns_mode). + +When unset, sing-box derives one address per family by taking the next IP after +the first IPv4/IPv6 entry in [`address`](#address). Connections toward those +derived addresses are additionally hijacked into the sing-box DNS module, +equivalent to a [`hijack-dns`](/configuration/route/rule_action/#hijack-dns) +route action; this preserves the behaviour from before this option was added. + +When set, this auto-hijack is not applied; configure an explicit +[`hijack-dns`](/configuration/route/rule_action/#hijack-dns) route rule if the +behaviour is still required. + #### gso +!!! failure "Deprecated in sing-box 1.11.0" + + GSO has no advantages for transparent proxy scenarios, is deprecated and no longer works, and will be removed in sing-box 1.12.0. + !!! question "Since sing-box 1.8.0" !!! quote "" - Only supported on Linux. + Only supported on Linux with `auto_route` enabled. Enable generic segmentation offload. @@ -138,6 +295,119 @@ Set the default route to the Tun. By default, VPN takes precedence over tun. To make tun go through VPN, enable `route.override_android_vpn`. +!!! note "Also enable `auto_redirect`" + + `auto_redirect` is always recommended on Linux, it provides better routing, higher performance (better than tproxy), and avoids conflicts between TUN and Docker bridge networks. + +#### iproute2_table_index + +!!! question "Since sing-box 1.10.0" + +Linux iproute2 table index generated by `auto_route`. + +`2022` is used by default. + +#### iproute2_rule_index + +!!! question "Since sing-box 1.10.0" + +Linux iproute2 rule start index generated by `auto_route`. + +`9000` is used by default. + +#### auto_redirect + +!!! question "Since sing-box 1.10.0" + +!!! quote "" + + Only supported on Linux with `auto_route` enabled. + +Improve TUN routing and performance using nftables. + +`auto_redirect` is always recommended on Linux, it provides better routing, +higher performance (better than tproxy), +and avoids conflicts between TUN and Docker bridge networks. + +Note that `auto_redirect` also works on Android, +but due to the lack of `nftables` and `ip6tables`, +only simple IPv4 TCP forwarding is performed. +To share your VPN connection over hotspot or repeater on Android, +use [VPNHotspot](https://github.com/Mygod/VPNHotspot). + +`auto_redirect` also automatically inserts compatibility rules +into the OpenWrt fw4 table, i.e. +it will work on routers without any extra configuration. + +Conflict with `route.default_mark` and `[dialOptions].routing_mark`. + +#### auto_redirect_input_mark + +!!! question "Since sing-box 1.10.0" + +Connection input mark used by `auto_redirect`. + +`0x2023` is used by default. + +#### auto_redirect_output_mark + +!!! question "Since sing-box 1.10.0" + +Connection output mark used by `auto_redirect`. + +`0x2024` is used by default. + +#### auto_redirect_reset_mark + +!!! question "Since sing-box 1.13.0" + +Connection reset mark used by `auto_redirect` pre-matching. + +`0x2025` is used by default. + +#### auto_redirect_nfqueue + +!!! question "Since sing-box 1.13.0" + +NFQueue number used by `auto_redirect` pre-matching. + +`100` is used by default. + +#### auto_redirect_iproute2_fallback_rule_index + +!!! question "Since sing-box 1.12.18" + +Linux iproute2 fallback rule index generated by `auto_redirect`. + +This rule is checked after system default rules (32766: main, 32767: default), +routing traffic to the sing-box table only when no route is found in system tables. + +`32768` is used by default. + +#### exclude_mptcp + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled. + +MPTCP cannot be transparently proxied due to protocol limitations. + +Such traffic is usually created by Apple systems. + +When enabled, MPTCP connections will bypass sing-box and connect directly, otherwise, will be rejected to avoid errors by default. + +#### loopback_address + +!!! question "Since sing-box 1.12.0" + +Loopback addresses make TCP connections to the specified address connect to the source address. + +Setting option value to `10.7.0.1` achieves the same behavior as SideStore/StosVPN. + +When `auto_redirect` is enabled, the same behavior can be achieved for LAN devices (not just local) as a gateway. + #### strict_route Enforce strict routing rules when `auto_route` is enabled: @@ -145,33 +415,117 @@ Enforce strict routing rules when `auto_route` is enabled: *In Linux*: * Let unsupported network unreachable -* Route all connections to tun - -It prevents address leaks and makes DNS hijacking work on Android. +* For legacy reasons, when neither `strict_route` nor `auto_redirect` are enabled, all ICMP traffic will not go through TUN. +* When `auto_redirect` is enabled, `strict_route` also affects `SO_BINDTODEVICE` traffic: + * Enabled: `SO_BINDTODEVICE` traffic is redirected through sing-box. + * Disabled: `SO_BINDTODEVICE` traffic bypasses sing-box. *In Windows*: -* Add firewall rules to prevent DNS leak caused by +* Let unsupported network unreachable +* prevent DNS leak caused by Windows' [ordinary multihomed DNS resolution behavior](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29) -It may prevent some applications (such as VirtualBox) from working properly in certain situations. +It may prevent some Windows applications (such as VirtualBox) from working properly in certain situations. + +#### route_address + +!!! question "Since sing-box 1.10.0" + +Use custom routes instead of default when `auto_route` is enabled. #### inet4_route_address +!!! failure "Deprecated in sing-box 1.10.0" + +`inet4_route_address` is deprecated and will be removed in sing-box 1.12.0, please use [route_address](#route_address) +instead. + Use custom routes instead of default when `auto_route` is enabled. #### inet6_route_address +!!! failure "Deprecated in sing-box 1.10.0" + +`inet6_route_address` is deprecated and will be removed in sing-box 1.12.0, please use [route_address](#route_address) +instead. + Use custom routes instead of default when `auto_route` is enabled. +#### route_exclude_address + +!!! question "Since sing-box 1.10.0" + +Exclude custom routes when `auto_route` is enabled. + #### inet4_route_exclude_address +!!! failure "Deprecated in sing-box 1.10.0" + +`inet4_route_exclude_address` is deprecated and will be removed in sing-box 1.12.0, please +use [route_exclude_address](#route_exclude_address) instead. + Exclude custom routes when `auto_route` is enabled. #### inet6_route_exclude_address +!!! failure "Deprecated in sing-box 1.10.0" + +`inet6_route_exclude_address` is deprecated and will be removed in sing-box 1.12.0, please +use [route_exclude_address](#route_exclude_address) instead. + Exclude custom routes when `auto_route` is enabled. +#### route_address_set + +=== "With `auto_redirect` enabled" + + !!! question "Since sing-box 1.10.0" + + !!! quote "" + + Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled. + + Add the destination IP CIDR rules in the specified rule-sets to the firewall. + Unmatched traffic will bypass the sing-box routes. + + Conflict with `route.default_mark` and `[dialOptions].routing_mark`. + +=== "Without `auto_redirect` enabled" + + !!! question "Since sing-box 1.11.0" + + Add the destination IP CIDR rules in the specified rule-sets to routes, equivalent to adding to `route_address`. + Unmatched traffic will bypass the sing-box routes. + + Note that it **doesn't work on the Android graphical client** due to + the Android VpnService not being able to handle a large number of routes (DeadSystemException), + but otherwise it works fine on all command line clients and Apple platforms. + +#### route_exclude_address_set + +=== "With `auto_redirect` enabled" + + !!! question "Since sing-box 1.10.0" + + !!! quote "" + + Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled. + + Add the destination IP CIDR rules in the specified rule-sets to the firewall. + Matched traffic will bypass the sing-box routes. + +=== "Without `auto_redirect` enabled" + + !!! question "Since sing-box 1.11.0" + + Add the destination IP CIDR rules in the specified rule-sets to routes, equivalent to adding to `route_exclude_address`. + Matched traffic will bypass the sing-box routes. + + Note that it **doesn't work on the Android graphical client** due to + the Android VpnService not being able to handle a large number of routes (DeadSystemException), + but otherwise it works fine on all command line clients and Apple platforms. + #### endpoint_independent_nat !!! info "" @@ -184,7 +538,9 @@ Performance may degrade slightly, so it is not recommended to enable on when it #### udp_timeout -UDP NAT expiration time in seconds, default is 300 (5 minutes). +UDP NAT expiration time. + +`5m` will be used by default. #### stack @@ -214,6 +570,10 @@ Conflict with `exclude_interface`. #### exclude_interface +!!! warning "" + + When `strict_route` enabled, return traffic to excluded interfaces will not be automatically excluded, so add them as well (example: `br-lan` and `pppoe-wan`). + Exclude interfaces in route. Conflict with `include_interface`. @@ -259,6 +619,30 @@ Limit android packages in route. Exclude android packages in route. +#### include_mac_address + +!!! question "Since sing-box 1.14.0" + +!!! quote "" + + Only supported on Linux with `auto_route` and `auto_redirect` enabled. + +Limit MAC addresses in route. Not limited by default. + +Conflict with `exclude_mac_address`. + +#### exclude_mac_address + +!!! question "Since sing-box 1.14.0" + +!!! quote "" + + Only supported on Linux with `auto_route` and `auto_redirect` enabled. + +Exclude MAC addresses in route. + +Conflict with `include_mac_address`. + #### platform Platform-specific settings, provided by client applications. diff --git a/docs/configuration/inbound/tun.zh.md b/docs/configuration/inbound/tun.zh.md index 73d31d6497..7471771eec 100644 --- a/docs/configuration/inbound/tun.zh.md +++ b/docs/configuration/inbound/tun.zh.md @@ -2,10 +2,57 @@ icon: material/new-box --- +!!! quote "sing-box 1.14.0 中的更改" + + :material-plus: [include_mac_address](#include_mac_address) + :material-plus: [exclude_mac_address](#exclude_mac_address) + :material-plus: [dns_mode](#dns_mode) + :material-plus: [dns_address](#dns_address) + +!!! quote "sing-box 1.13.3 中的更改" + + :material-alert: [strict_route](#strict_route) + +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [auto_redirect_reset_mark](#auto_redirect_reset_mark) + :material-plus: [auto_redirect_nfqueue](#auto_redirect_nfqueue) + :material-plus: [exclude_mptcp](#exclude_mptcp) + :material-plus: [auto_redirect_iproute2_fallback_rule_index](#auto_redirect_iproute2_fallback_rule_index) + +!!! quote "sing-box 1.12.0 中的更改" + + :material-plus: [loopback_address](#loopback_address) + +!!! quote "sing-box 1.11.0 中的更改" + + :material-delete-alert: [gso](#gso) + :material-alert-decagram: [route_address_set](#stack) + :material-alert-decagram: [route_exclude_address_set](#stack) + +!!! quote "sing-box 1.10.0 中的更改" + + :material-plus: [address](#address) + :material-delete-clock: [inet4_address](#inet4_address) + :material-delete-clock: [inet6_address](#inet6_address) + :material-plus: [route_address](#route_address) + :material-delete-clock: [inet4_route_address](#inet4_route_address) + :material-delete-clock: [inet6_route_address](#inet6_route_address) + :material-plus: [route_exclude_address](#route_address) + :material-delete-clock: [inet4_route_exclude_address](#inet4_route_exclude_address) + :material-delete-clock: [inet6_route_exclude_address](#inet6_route_exclude_address) + :material-plus: [iproute2_table_index](#iproute2_table_index) + :material-plus: [iproute2_rule_index](#iproute2_table_index) + :material-plus: [auto_redirect](#auto_redirect) + :material-plus: [auto_redirect_input_mark](#auto_redirect_input_mark) + :material-plus: [auto_redirect_output_mark](#auto_redirect_output_mark) + :material-plus: [route_address_set](#route_address_set) + :material-plus: [route_exclude_address_set](#route_address_set) + !!! quote "sing-box 1.9.0 中的更改" :material-plus: [platform.http_proxy.bypass_domain](#platformhttp_proxybypass_domain) - :material-plus: [platform.http_proxy.match_domain](#platformhttp_proxymatch_domain) + :material-plus: [platform.http_proxy.match_domain](#platformhttp_proxymatch_domain) !!! quote "sing-box 1.8.0 中的更改" @@ -23,26 +70,47 @@ icon: material/new-box "type": "tun", "tag": "tun-in", "interface_name": "tun0", - "inet4_address": "172.19.0.1/30", - "inet6_address": "fdfe:dcba:9876::1/126", + "address": [ + "172.18.0.1/30", + "fdfe:dcba:9876::1/126" + ], "mtu": 9000, - "gso": false, + "dns_mode": "hijack", + "dns_address": [ + "172.18.0.2", + "fdfe:dcba:9876::2" + ], "auto_route": true, + "iproute2_table_index": 2022, + "iproute2_rule_index": 9000, + "auto_redirect": true, + "auto_redirect_input_mark": "0x2023", + "auto_redirect_output_mark": "0x2024", + "auto_redirect_reset_mark": "0x2025", + "auto_redirect_nfqueue": 100, + "auto_redirect_iproute2_fallback_rule_index": 32768, + "exclude_mptcp": false, + "loopback_address": [ + "10.7.0.1" + ], "strict_route": true, - "inet4_route_address": [ + "route_address": [ "0.0.0.0/1", - "128.0.0.0/1" - ], - "inet6_route_address": [ + "128.0.0.0/1", "::/1", "8000::/1" ], - "inet4_route_exclude_address": [ - "192.168.0.0/16" - ], - "inet6_route_exclude_address": [ + + "route_exclude_address": [ + "192.168.0.0/16", "fc00::/7" ], + "route_address_set": [ + "geoip-cloudflare" + ], + "route_exclude_address_set": [ + "geoip-cn" + ], "endpoint_independent_nat": false, "udp_timeout": "5m", "stack": "system", @@ -56,13 +124,13 @@ icon: material/new-box 0 ], "include_uid_range": [ - "1000-99999" + "1000:99999" ], "exclude_uid": [ 1000 ], "exclude_uid_range": [ - "1000-99999" + "1000:99999" ], "include_android_user": [ 0, @@ -74,6 +142,12 @@ icon: material/new-box "exclude_package": [ "com.android.captiveportallogin" ], + "include_mac_address": [ + "00:11:22:33:44:55" + ], + "exclude_mac_address": [ + "66:77:88:99:aa:bb" + ], "platform": { "http_proxy": { "enabled": false, @@ -83,6 +157,29 @@ icon: material/new-box "match_domain": [] } }, + + // 已弃用 + "gso": false, + "inet4_address": [ + "172.19.0.1/30" + ], + "inet6_address": [ + "fdfe:dcba:9876::1/126" + ], + "inet4_route_address": [ + "0.0.0.0/1", + "128.0.0.0/1" + ], + "inet6_route_address": [ + "::/1", + "8000::/1" + ], + "inet4_route_exclude_address": [ + "192.168.0.0/16" + ], + "inet6_route_exclude_address": [ + "fc00::/7" + ], ... // 监听字段 } @@ -102,22 +199,82 @@ icon: material/new-box 虚拟设备名称,默认自动选择。 +#### address + +!!! question "自 sing-box 1.10.0 起" + +==必填== + +tun 接口的 IPv4 和 IPv6 前缀。 + #### inet4_address +!!! failure "已在 sing-box 1.10.0 废弃" + + `inet4_address` 已合并到 `address` 且将在 sing-box 1.12.0 中被移除。 + ==必填== tun 接口的 IPv4 前缀。 #### inet6_address +!!! failure "已在 sing-box 1.10.0 废弃" + + `inet6_address` 已合并到 `address` 且将在 sing-box 1.12.0 中被移除。 + tun 接口的 IPv6 前缀。 #### mtu 最大传输单元。 +#### dns_mode + +!!! question "自 sing-box 1.14.0 起" + +TUN 接口上 DNS 的处理方式。 + +| 模式 | 描述 | +|------------|-------------------------------------------------------------------------------------------------------| +| `disabled` | 不设置原生 DNS,也不劫持 DNS 流量。 | +| `native` | 尽可能设置平台的原生接口 DNS:Windows 与 Apple 上的接口 DNS,Linux 上的 `systemd-resolved` 接口 DNS。 | +| `hijack` | 与 `native` 相同,并额外执行下文所述的 53 端口劫持。默认使用。 | + +`hijack` 在 `native` 之上额外执行: + +*Linux*:只能劫持发往非本机地址的 DNS。发往本机接口地址(如 `127.0.0.53` +或本机 LAN 接口 IP)的流量由内核 `local` 路由表在所有用户规则之前直接交付, +`OUTPUT` 链 NAT 也无法对走 `lo` 的包生效。 + +- 未启用 `auto_redirect` 时:通过 `iproute2` 规则让 53 端口跳过 `main` 表的 + 具体路由查找,把本来会经直连子网直接送达的 DNS 改走 TUN —— 不重写目的地址。 +- 启用 `auto_redirect` 时:通过 nftables 规则将 53 端口流量直接 DNAT 至 + [`dns_address`](#dns_address)。 + +*Windows 启用 [`strict_route`](#strict_route) 时*:通过 WFP 过滤器阻止经由非 +TUN 接口的 53 端口流量。 + +#### dns_address + +!!! question "自 sing-box 1.14.0 起" + +[`dns_mode`](#dns_mode) 使用的 DNS 服务器地址列表。 + +未设置时,sing-box 会按地址族在 [`address`](#address) 的第一个 IPv4/IPv6 +条目后面取下一个 IP 作为 DNS 服务器地址,并将流向这些推导地址的连接额外劫持到 +sing-box DNS 模块,等价于一条 +[`hijack-dns`](/zh/configuration/route/rule_action/#hijack-dns) 路由动作;这与此选项加入之前的行为一致。 + +设置后,将不再自动劫持;如仍需此行为,请显式配置 +[`hijack-dns`](/zh/configuration/route/rule_action/#hijack-dns) 路由规则。 + #### gso +!!! failure "已在 sing-box 1.11.0 废弃" + + GSO 对于透明代理场景没有优势,已废弃和不再生效,且将在 sing-box 1.12.0 中被移除。 + !!! question "自 sing-box 1.8.0 起" !!! quote "" @@ -138,41 +295,223 @@ tun 接口的 IPv6 前缀。 VPN 默认优先于 tun。要使 tun 经过 VPN,启用 `route.override_android_vpn`。 +!!! note "也启用 `auto_redirect`" + + 在 Linux 上始终推荐使用 `auto_redirect`,它提供更好的路由, 更高的性能(优于 tproxy), 并避免 TUN 与 Docker 桥接网络冲突。 + +#### iproute2_table_index + +!!! question "自 sing-box 1.10.0 起" + +`auto_route` 生成的 iproute2 路由表索引。 + +默认使用 `2022`。 + +#### iproute2_rule_index + +!!! question "自 sing-box 1.10.0 起" + +`auto_route` 生成的 iproute2 规则起始索引。 + +默认使用 `9000`。 + +#### auto_redirect + +!!! question "自 sing-box 1.10.0 起" + +!!! quote "" + + 仅支持 Linux,且需要 `auto_route` 已启用。 + +通过使用 nftables 改善 TUN 路由和性能。 + +在 Linux 上始终推荐使用 `auto_redirect`,它提供更好的路由、更高的性能(优于 tproxy),并避免了 TUN 和 Docker 桥接网络之间的冲突。 + +请注意,`auto_redirect` 也适用于 Android,但由于缺少 `nftables` 和 `ip6tables`,仅执行简单的 IPv4 TCP 转发。 +若要在 Android 上通过热点或中继器共享 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。 + +`auto_redirect` 还会自动将兼容性规则插入 OpenWrt 的 fw4 表中,即无需额外配置即可在路由器上工作。 + +与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。 + +#### auto_redirect_input_mark + +!!! question "自 sing-box 1.10.0 起" + +`auto_redirect` 使用的连接输入标记。 + +默认使用 `0x2023`。 + +#### auto_redirect_output_mark + +!!! question "自 sing-box 1.10.0 起" + +`auto_redirect` 使用的连接输出标记。 + +默认使用 `0x2024`。 + +#### auto_redirect_reset_mark + +!!! question "自 sing-box 1.13.0 起" + +`auto_redirect` 预匹配使用的连接重置标记。 + +默认使用 `0x2025`。 + +#### auto_redirect_nfqueue + +!!! question "自 sing-box 1.13.0 起" + +`auto_redirect` 预匹配使用的 NFQueue 编号。 + +默认使用 `100`。 + +#### auto_redirect_iproute2_fallback_rule_index + +!!! question "自 sing-box 1.12.18 起" + +`auto_redirect` 生成的 iproute2 回退规则索引。 + +此规则在系统默认规则(32766: main,32767: default)之后检查, +仅当系统路由表中未找到路由时才将流量路由到 sing-box 路由表。 + +默认使用 `32768`。 + +#### exclude_mptcp + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。 + +由于协议限制,MPTCP 无法被透明代理。 + +此类流量通常由 Apple 系统创建。 + +启用时,MPTCP 连接将绕过 sing-box 直接连接,否则,将被拒绝以避免错误。 + +#### loopback_address + +!!! question "自 sing-box 1.12.0 起" + +环回地址是用于使指向指定地址的 TCP 连接连接到来源地址的。 + +将选项值设置为 `10.7.0.1` 可实现与 SideStore/StosVPN 相同的行为。 + +当启用 `auto_redirect` 时,可以作为网关为局域网设备(而不仅仅是本地)实现相同的行为。 + #### strict_route -启用 `auto_route` 时执行严格的路由规则。 +当启用 `auto_route` 时,强制执行严格的路由规则: + +*在 Linux 中*: + +* 使不支持的网络不可达。 +* 出于历史遗留原因,当未启用 `strict_route` 或 `auto_redirect` 时,所有 ICMP 流量将不会通过 TUN。 +* 当启用 `auto_redirect` 时,`strict_route` 也影响 `SO_BINDTODEVICE` 流量: + * 启用:`SO_BINDTODEVICE` 流量被重定向通过 sing-box。 + * 禁用:`SO_BINDTODEVICE` 流量绕过 sing-box。 -*在 Linux 中*: +*在 Windows 中*: -* 让不支持的网络无法到达 -* 将所有连接路由到 tun +* 使不支持的网络不可达。 +* 阻止 Windows 的 [普通多宿主 DNS 解析行为](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29) 造成的 DNS 泄露 -它可以防止地址泄漏,并使 DNS 劫持在 Android 上工作。 +它可能会使某些 Windows 应用程序(如 VirtualBox)在某些情况下无法正常工作。 -*在 Windows 中*: +#### route_address -* 添加防火墙规则以阻止 Windows - 的 [普通多宿主 DNS 解析行为](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29) - 造成的 DNS 泄露 +!!! question "自 sing-box 1.10.0 起" -它可能会使某些应用程序(如 VirtualBox)在某些情况下无法正常工作。 +设置到 Tun 的自定义路由。 #### inet4_route_address +!!! failure "已在 sing-box 1.10.0 废弃" + + `inet4_route_address` 已合并到 `route_address` 且将在 sing-box 1.12.0 中被移除。 + 启用 `auto_route` 时使用自定义路由而不是默认路由。 #### inet6_route_address +!!! failure "已在 sing-box 1.10.0 废弃" + + `inet6_route_address` 已合并到 `route_address` 且将在 sing-box 1.12.0 中被移除。 + 启用 `auto_route` 时使用自定义路由而不是默认路由。 +#### route_exclude_address + +!!! question "自 sing-box 1.10.0 起" + +设置到 Tun 的排除自定义路由。 + #### inet4_route_exclude_address +!!! failure "已在 sing-box 1.10.0 废弃" + + `inet4_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.12.0 中被移除。 + 启用 `auto_route` 时排除自定义路由。 #### inet6_route_exclude_address +!!! failure "已在 sing-box 1.10.0 废弃" + + `inet6_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.12.0 中被移除。 + 启用 `auto_route` 时排除自定义路由。 +#### route_address_set + +=== "`auto_redirect` 已启用" + + !!! question "自 sing-box 1.10.0 起" + + !!! quote "" + + 仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。 + + 将指定规则集中的目标 IP CIDR 规则添加到防火墙。 + 不匹配的流量将绕过 sing-box 路由。 + +=== "`auto_redirect` 未启用" + + !!! question "自 sing-box 1.11.0 起" + + 将指定规则集中的目标 IP CIDR 规则添加到路由,相当于添加到 `route_address`。 + 不匹配的流量将绕过 sing-box 路由。 + + 请注意,由于 Android VpnService 无法处理大量路由(DeadSystemException), + 因此它**在 Android 图形客户端上不起作用**,但除此之外,它在所有命令行客户端和 Apple 平台上都可以正常工作。 + +#### route_exclude_address_set + +=== "`auto_redirect` 已启用" + + !!! question "自 sing-box 1.10.0 起" + + !!! quote "" + + 仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。 + + 将指定规则集中的目标 IP CIDR 规则添加到防火墙。 + 匹配的流量将绕过 sing-box 路由。 + + 与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。 + +=== "`auto_redirect` 未启用" + + !!! question "自 sing-box 1.11.0 起" + + 将指定规则集中的目标 IP CIDR 规则添加到路由,相当于添加到 `route_exclude_address`。 + 匹配的流量将绕过 sing-box 路由。 + + 请注意,由于 Android VpnService 无法处理大量路由(DeadSystemException), + 因此它**在 Android 图形客户端上不起作用**,但除此之外,它在所有命令行客户端和 Apple 平台上都可以正常工作。 + #### endpoint_independent_nat 启用独立于端点的 NAT。 @@ -181,7 +520,9 @@ tun 接口的 IPv6 前缀。 #### udp_timeout -UDP NAT 过期时间,以秒为单位,默认为 300(5 分钟)。 +UDP NAT 过期时间。 + +默认使用 `5m`。 #### stack @@ -191,11 +532,11 @@ UDP NAT 过期时间,以秒为单位,默认为 300(5 分钟)。 TCP/IP 栈。 -| 栈 | 描述 | -|--------|------------------------------------------------------------------| -| system | 基于系统网络栈执行 L3 到 L4 转换 | -| gVisor | 基于 [gVisor](https://github.com/google/gvisor) 虚拟网络栈执行 L3 到 L4 转换 | -| mixed | 混合 `system` TCP 栈与 `gvisor` UDP 栈 | +| 栈 | 描述 | +|----------|-------------------------------------------------------------------------------------------------------| +| `system` | 基于系统网络栈执行 L3 到 L4 转换 | +| `gvisor` | 基于 [gVisor](https://github.com/google/gvisor) 虚拟网络栈执行 L3 到 L4 转换 | +| `mixed` | 混合 `system` TCP 栈与 `gvisor` UDP 栈 | 默认使用 `mixed` 栈如果 gVisor 构建标记已启用,否则默认使用 `system` 栈。 @@ -211,6 +552,10 @@ TCP/IP 栈。 #### exclude_interface +!!! warning "" + + 当 `strict_route` 启用,到被排除接口的回程流量将不会被自动排除,因此也要添加它们(例:`br-lan` 与 `pppoe-wan`)。 + 排除路由的接口。 与 `include_interface` 冲突。 @@ -256,6 +601,30 @@ TCP/IP 栈。 排除路由的 Android 应用包名。 +#### include_mac_address + +!!! question "自 sing-box 1.14.0 起" + +!!! quote "" + + 仅支持 Linux,且需要 `auto_route` 和 `auto_redirect` 已启用。 + +限制被路由的 MAC 地址。默认不限制。 + +与 `exclude_mac_address` 冲突。 + +#### exclude_mac_address + +!!! question "自 sing-box 1.14.0 起" + +!!! quote "" + + 仅支持 Linux,且需要 `auto_route` 和 `auto_redirect` 已启用。 + +排除路由的 MAC 地址。 + +与 `include_mac_address` 冲突。 + #### platform 平台特定的设置,由客户端应用提供。 @@ -284,7 +653,7 @@ TCP/IP 栈。 !!! note "" - 在 Apple 平台,`bypass_domain` 项匹配主机名 **后缀**. + 在 Apple 平台,`bypass_domain` 项匹配主机名 **后缀**. 绕过代理的主机名列表。 diff --git a/docs/configuration/inbound/vless.zh.md b/docs/configuration/inbound/vless.zh.md index 30b151dad3..2ce4785b8d 100644 --- a/docs/configuration/inbound/vless.zh.md +++ b/docs/configuration/inbound/vless.zh.md @@ -48,11 +48,11 @@ VLESS 子协议。 #### tls -TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 +TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#入站)。 #### multiplex -参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。 +参阅 [多路复用](/zh/configuration/shared/multiplex#入站)。 #### transport diff --git a/docs/configuration/inbound/vmess.zh.md b/docs/configuration/inbound/vmess.zh.md index 9aef44df51..f741ed1bed 100644 --- a/docs/configuration/inbound/vmess.zh.md +++ b/docs/configuration/inbound/vmess.zh.md @@ -43,11 +43,11 @@ VMess 用户。 #### tls -TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 +TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#入站)。 #### multiplex -参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。 +参阅 [多路复用](/zh/configuration/shared/multiplex#入站)。 #### transport diff --git a/docs/configuration/index.md b/docs/configuration/index.md index 0c22fc25ab..311161c1cc 100644 --- a/docs/configuration/index.md +++ b/docs/configuration/index.md @@ -1,7 +1,6 @@ # Introduction sing-box uses JSON for configuration files. - ### Structure ```json @@ -9,9 +8,14 @@ sing-box uses JSON for configuration files. "log": {}, "dns": {}, "ntp": {}, + "certificate": {}, + "certificate_providers": [], + "http_clients": [], + "endpoints": [], "inbounds": [], "outbounds": [], "route": {}, + "services": [], "experimental": {} } ``` @@ -23,9 +27,14 @@ sing-box uses JSON for configuration files. | `log` | [Log](./log/) | | `dns` | [DNS](./dns/) | | `ntp` | [NTP](./ntp/) | +| `certificate` | [Certificate](./certificate/) | +| `certificate_providers` | [Certificate Provider](./shared/certificate-provider/) | +| `http_clients` | [HTTP Client](./shared/http-client/) | +| `endpoints` | [Endpoint](./endpoint/) | | `inbounds` | [Inbound](./inbound/) | | `outbounds` | [Outbound](./outbound/) | | `route` | [Route](./route/) | +| `services` | [Service](./service/) | | `experimental` | [Experimental](./experimental/) | ### Check @@ -44,4 +53,4 @@ sing-box format -w -c config.json -D config_directory ```bash sing-box merge output.json -c config.json -D config_directory -``` \ No newline at end of file +``` diff --git a/docs/configuration/index.zh.md b/docs/configuration/index.zh.md index 0d24a7ca7a..fbb44c79e1 100644 --- a/docs/configuration/index.zh.md +++ b/docs/configuration/index.zh.md @@ -1,16 +1,21 @@ # 引言 sing-box 使用 JSON 作为配置文件格式。 - ### 结构 ```json { "log": {}, "dns": {}, + "ntp": {}, + "certificate": {}, + "certificate_providers": [], + "http_clients": [], + "endpoints": [], "inbounds": [], "outbounds": [], "route": {}, + "services": [], "experimental": {} } ``` @@ -21,9 +26,15 @@ sing-box 使用 JSON 作为配置文件格式。 |----------------|------------------------| | `log` | [日志](./log/) | | `dns` | [DNS](./dns/) | +| `ntp` | [NTP](./ntp/) | +| `certificate` | [证书](./certificate/) | +| `certificate_providers` | [证书提供者](./shared/certificate-provider/) | +| `http_clients` | [HTTP 客户端](./shared/http-client/) | +| `endpoints` | [端点](./endpoint/) | | `inbounds` | [入站](./inbound/) | | `outbounds` | [出站](./outbound/) | | `route` | [路由](./route/) | +| `services` | [服务](./service/) | | `experimental` | [实验性](./experimental/) | ### 检查 @@ -42,4 +53,4 @@ sing-box format -w -c config.json -D config_directory ```bash sing-box merge output.json -c config.json -D config_directory -``` \ No newline at end of file +``` diff --git a/docs/configuration/outbound/anytls.md b/docs/configuration/outbound/anytls.md new file mode 100644 index 0000000000..4b3e037161 --- /dev/null +++ b/docs/configuration/outbound/anytls.md @@ -0,0 +1,66 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +### Structure + +```json +{ + "type": "anytls", + "tag": "anytls-out", + + "server": "127.0.0.1", + "server_port": 1080, + "password": "8JCsPssfgS8tiRwiMlhARg==", + "idle_session_check_interval": "30s", + "idle_session_timeout": "30s", + "min_idle_session": 5, + "tls": {}, + + ... // Dial Fields +} +``` + +### Fields + +#### server + +==Required== + +The server address. + +#### server_port + +==Required== + +The server port. + +#### password + +==Required== + +The AnyTLS password. + +#### idle_session_check_interval + +Interval checking for idle sessions. Default: 30s. + +#### idle_session_timeout + +In the check, close sessions that have been idle for longer than this. Default: 30s. + +#### min_idle_session + +In the check, at least the first `n` idle sessions are kept open. Default value: `n`=0 + +#### tls + +==Required== + +TLS configuration, see [TLS](/configuration/shared/tls/#outbound). + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/outbound/anytls.zh.md b/docs/configuration/outbound/anytls.zh.md new file mode 100644 index 0000000000..c1f8999e76 --- /dev/null +++ b/docs/configuration/outbound/anytls.zh.md @@ -0,0 +1,66 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +### 结构 + +```json +{ + "type": "anytls", + "tag": "anytls-out", + + "server": "127.0.0.1", + "server_port": 1080, + "password": "8JCsPssfgS8tiRwiMlhARg==", + "idle_session_check_interval": "30s", + "idle_session_timeout": "30s", + "min_idle_session": 5, + "tls": {}, + + ... // 拨号字段 +} +``` + +### 字段 + +#### server + +==必填== + +服务器地址。 + +#### server_port + +==必填== + +服务器端口。 + +#### password + +==必填== + +AnyTLS 密码。 + +#### idle_session_check_interval + +检查空闲会话的时间间隔。默认值:30秒。 + +#### idle_session_timeout + +在检查中,关闭闲置时间超过此值的会话。默认值:30秒。 + +#### min_idle_session + +在检查中,至少前 `n` 个空闲会话保持打开状态。默认值:`n`=0 + +#### tls + +==必填== + +TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/)。 diff --git a/docs/configuration/outbound/block.md b/docs/configuration/outbound/block.md index e27a4b3ef3..6e2322632c 100644 --- a/docs/configuration/outbound/block.md +++ b/docs/configuration/outbound/block.md @@ -1,4 +1,6 @@ -`block` outbound closes all incoming requests. +--- +icon: material/delete-clock +--- ### Structure diff --git a/docs/configuration/outbound/block.zh.md b/docs/configuration/outbound/block.zh.md index bc0762e3ab..93227476ec 100644 --- a/docs/configuration/outbound/block.zh.md +++ b/docs/configuration/outbound/block.zh.md @@ -1,3 +1,7 @@ +--- +icon: material/delete-clock +--- + `block` 出站关闭所有传入请求。 ### 结构 @@ -11,4 +15,4 @@ ### 字段 -无字段。 \ No newline at end of file +无字段。 diff --git a/docs/configuration/outbound/direct.md b/docs/configuration/outbound/direct.md index c2f5671a6b..3e28db8fc6 100644 --- a/docs/configuration/outbound/direct.md +++ b/docs/configuration/outbound/direct.md @@ -1,3 +1,12 @@ +--- +icon: material/alert-decagram +--- + +!!! quote "Changes in sing-box 1.11.0" + + :material-delete-clock: [override_address](#override_address) + :material-delete-clock: [override_port](#override_port) + `direct` outbound send requests directly. ### Structure @@ -9,7 +18,6 @@ "override_address": "1.0.0.1", "override_port": 53, - "proxy_protocol": 0, ... // Dial Fields } @@ -19,15 +27,19 @@ #### override_address +!!! failure "Deprecated in sing-box 1.11.0" + + Destination override fields are deprecated in sing-box 1.11.0 and will be removed in sing-box 1.13.0, see [Migration](/migration/#migrate-destination-override-fields-to-route-options). + Override the connection destination address. #### override_port -Override the connection destination port. +!!! failure "Deprecated in sing-box 1.11.0" -#### proxy_protocol + Destination override fields are deprecated in sing-box 1.11.0 and will be removed in sing-box 1.13.0, see [Migration](/migration/#migrate-destination-override-fields-to-route-options). -Write [Proxy Protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) in the connection header. +Override the connection destination port. Protocol value can be `1` or `2`. diff --git a/docs/configuration/outbound/direct.zh.md b/docs/configuration/outbound/direct.zh.md index aee131758a..824a35295f 100644 --- a/docs/configuration/outbound/direct.zh.md +++ b/docs/configuration/outbound/direct.zh.md @@ -1,3 +1,12 @@ +--- +icon: material/alert-decagram +--- + +!!! quote "sing-box 1.11.0 中的更改" + + :material-delete-clock: [override_address](#override_address) + :material-delete-clock: [override_port](#override_port) + `direct` 出站直接发送请求。 ### 结构 @@ -9,7 +18,6 @@ "override_address": "1.0.0.1", "override_port": 53, - "proxy_protocol": 0, ... // 拨号字段 } @@ -19,17 +27,19 @@ #### override_address +!!! failure "已在 sing-box 1.11.0 废弃" + + 目标覆盖字段在 sing-box 1.11.0 中已废弃,并将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移-direct-出站中的目标地址覆盖字段到路由字段)。 + 覆盖连接目标地址。 #### override_port -覆盖连接目标端口。 - -#### proxy_protocol +!!! failure "已在 sing-box 1.11.0 废弃" -写出 [代理协议](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) 到连接头。 + 目标覆盖字段在 sing-box 1.11.0 中已废弃,并将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移-direct-出站中的目标地址覆盖字段到路由字段)。 -可用协议版本值:`1` 或 `2`。 +覆盖连接目标端口。 ### 拨号字段 diff --git a/docs/configuration/outbound/dns.md b/docs/configuration/outbound/dns.md index 1f8c547701..d73360413e 100644 --- a/docs/configuration/outbound/dns.md +++ b/docs/configuration/outbound/dns.md @@ -1,3 +1,11 @@ +--- +icon: material/delete-clock +--- + +!!! failure "Deprecated in sing-box 1.11.0" + + Legacy special outbounds are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-special-outbounds-to-rule-actions). + `dns` outbound is a internal DNS server. ### Structure diff --git a/docs/configuration/outbound/dns.zh.md b/docs/configuration/outbound/dns.zh.md index 67538f6e02..592075b3a9 100644 --- a/docs/configuration/outbound/dns.zh.md +++ b/docs/configuration/outbound/dns.zh.md @@ -1,3 +1,11 @@ +--- +icon: material/delete-clock +--- + +!!! failure "已在 sing-box 1.11.0 废弃" + + 旧的特殊出站已被弃用,且将在 sing-box 1.13.0 中被移除, 参阅 [迁移指南](/zh/migration/#迁移旧的特殊出站到规则动作). + `dns` 出站是一个内部 DNS 服务器。 ### 结构 diff --git a/docs/configuration/outbound/http.zh.md b/docs/configuration/outbound/http.zh.md index 53dd1b6ad0..55387a6357 100644 --- a/docs/configuration/outbound/http.zh.md +++ b/docs/configuration/outbound/http.zh.md @@ -51,7 +51,7 @@ HTTP 请求的额外标头。 #### tls -TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。 +TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。 ### 拨号字段 diff --git a/docs/configuration/outbound/hysteria.md b/docs/configuration/outbound/hysteria.md index 90190e051f..4908799bdb 100644 --- a/docs/configuration/outbound/hysteria.md +++ b/docs/configuration/outbound/hysteria.md @@ -1,3 +1,12 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.12.0" + + :material-plus: [server_ports](#server_ports) + :material-plus: [hop_interval](#hop_interval) + ### Structure ```json @@ -7,6 +16,10 @@ "server": "127.0.0.1", "server_port": 1080, + "server_ports": [ + "2080:3000" + ], + "hop_interval": "", "up": "100 Mbps", "up_mbps": 100, "down": "100 Mbps", @@ -14,13 +27,18 @@ "obfs": "fuck me till the daylight", "auth": "", "auth_str": "password", - "recv_window_conn": 0, - "recv_window": 0, - "disable_mtu_discovery": false, - "network": "tcp", + "network": "", "tls": {}, - + + ... // QUIC Fields + ... // Dial Fields + + // Deprecated + + "recv_window_conn": 0, + "recv_window": 0, + "disable_mtu_discovery": false } ``` @@ -38,6 +56,22 @@ The server address. The server port. +#### server_ports + +!!! question "Since sing-box 1.12.0" + +Server port range list. + +Conflicts with `server_port`. + +#### hop_interval + +!!! question "Since sing-box 1.12.0" + +Port hopping interval. + +`30s` is used by default. + #### up, down ==Required== @@ -75,24 +109,6 @@ Authentication password, in base64. Authentication password. -#### recv_window_conn - -The QUIC stream-level flow control window for receiving data. - -`15728640 (15 MB/s)` will be used if empty. - -#### recv_window - -The QUIC connection-level flow control window for receiving data. - -`67108864 (64 MB/s)` will be used if empty. - -#### disable_mtu_discovery - -Disables Path MTU Discovery (RFC 8899). Packets will then be at most 1252 (IPv4) / 1232 (IPv6) bytes in size. - -Force enabled on for systems other than Linux and Windows (according to upstream). - #### network Enabled network @@ -107,6 +123,30 @@ Both is enabled by default. TLS configuration, see [TLS](/configuration/shared/tls/#outbound). +### QUIC Fields + +See [QUIC Fields](/configuration/shared/quic/) for details. + ### Dial Fields See [Dial Fields](/configuration/shared/dial/) for details. + +### Deprecated Fields + +#### recv_window_conn + +!!! failure "Deprecated in sing-box 1.14.0" + + Use QUIC fields `stream_receive_window` instead. + +#### recv_window + +!!! failure "Deprecated in sing-box 1.14.0" + + Use QUIC fields `connection_receive_window` instead. + +#### disable_mtu_discovery + +!!! failure "Deprecated in sing-box 1.14.0" + + Use QUIC fields `disable_path_mtu_discovery` instead. diff --git a/docs/configuration/outbound/hysteria.zh.md b/docs/configuration/outbound/hysteria.zh.md index c6ee2313f0..004d2d165a 100644 --- a/docs/configuration/outbound/hysteria.zh.md +++ b/docs/configuration/outbound/hysteria.zh.md @@ -1,3 +1,12 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.12.0 中的更改" + + :material-plus: [server_ports](#server_ports) + :material-plus: [hop_interval](#hop_interval) + ### 结构 ```json @@ -7,6 +16,10 @@ "server": "127.0.0.1", "server_port": 1080, + "server_ports": [ + "2080:3000" + ], + "hop_interval": "", "up": "100 Mbps", "up_mbps": 100, "down": "100 Mbps", @@ -14,13 +27,18 @@ "obfs": "fuck me till the daylight", "auth": "", "auth_str": "password", - "recv_window_conn": 0, - "recv_window": 0, - "disable_mtu_discovery": false, - "network": "tcp", + "network": "", "tls": {}, - + + ... // QUIC 字段 + ... // 拨号字段 + + // 废弃的 + + "recv_window_conn": 0, + "recv_window": 0, + "disable_mtu_discovery": false } ``` @@ -38,6 +56,22 @@ 服务器端口。 +#### server_ports + +!!! question "自 sing-box 1.12.0 起" + +服务器端口范围列表。 + +与 `server_port` 冲突。 + +#### hop_interval + +!!! question "自 sing-box 1.12.0 起" + +端口跳跃间隔。 + +默认使用 `30s`。 + #### up, down ==必填== @@ -75,39 +109,44 @@ base64 编码的认证密码。 认证密码。 -#### recv_window_conn +#### network -用于接收数据的 QUIC 流级流控制窗口。 +启用的网络协议。 -默认 `15728640 (15 MB/s)`。 +`tcp` 或 `udp`。 -#### recv_window +默认所有。 -用于接收数据的 QUIC 连接级流控制窗口。 +#### tls -默认 `67108864 (64 MB/s)`。 +==必填== -#### disable_mtu_discovery +TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。 -禁用路径 MTU 发现 (RFC 8899)。 数据包的大小最多为 1252 (IPv4) / 1232 (IPv6) 字节。 +### QUIC 字段 -强制为 Linux 和 Windows 以外的系统启用(根据上游)。 +参阅 [QUIC 字段](/zh/configuration/shared/quic/) 了解详情。 -#### network +### 拨号字段 -启用的网络协议。 +参阅 [拨号字段](/zh/configuration/shared/dial/)。 -`tcp` 或 `udp`。 +### 废弃字段 -默认所有。 +#### recv_window_conn -#### tls +!!! failure "已在 sing-box 1.14.0 废弃" -==必填== + 请使用 QUIC 字段 `stream_receive_window` 代替。 -TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。 +#### recv_window +!!! failure "已在 sing-box 1.14.0 废弃" -### 拨号字段 + 请使用 QUIC 字段 `connection_receive_window` 代替。 -参阅 [拨号字段](/zh/configuration/shared/dial/)。 +#### disable_mtu_discovery + +!!! failure "已在 sing-box 1.14.0 废弃" + + 请使用 QUIC 字段 `disable_path_mtu_discovery` 代替。 diff --git a/docs/configuration/outbound/hysteria2.md b/docs/configuration/outbound/hysteria2.md index ae0b96edda..1df36cfc2b 100644 --- a/docs/configuration/outbound/hysteria2.md +++ b/docs/configuration/outbound/hysteria2.md @@ -1,12 +1,29 @@ +!!! quote "Changes in sing-box 1.14.0" + + :material-plus: [hop_interval_max](#hop_interval_max) + :material-plus: [bbr_profile](#bbr_profile) + :material-plus: [realm](#realm) + :material-alert: [obfs](#obfstype) + +!!! quote "Changes in sing-box 1.11.0" + + :material-plus: [server_ports](#server_ports) + :material-plus: [hop_interval](#hop_interval) + ### Structure ```json { "type": "hysteria2", "tag": "hy2-out", - + "server": "127.0.0.1", "server_port": 1080, + "server_ports": [ + "2080:3000" + ], + "hop_interval": "", + "hop_interval_max": "", "up_mbps": 100, "down_mbps": 100, "obfs": { @@ -16,12 +33,27 @@ "password": "goofy_ahh_password", "network": "tcp", "tls": {}, + + ... // QUIC Fields + + "bbr_profile": "", "brutal_debug": false, - + "realm": { + "server_url": "https://realm.example.com", + "token": "", + "realm_id": "", + "stun_servers": [], + "http_client": {} + }, + ... // Dial Fields } ``` +!!! note "" + + You can ignore the JSON Array [] tag when the content is only one item + !!! warning "Difference from official Hysteria2" The official Hysteria2 supports an authentication method called **userpass**, @@ -38,12 +70,42 @@ The server address. +Conflicts with `realm`. + #### server_port ==Required== The server port. +Ignored if `server_ports` is set. + +Conflicts with `realm`. + +#### server_ports + +!!! question "Since sing-box 1.11.0" + +Server port range list. + +Conflicts with `server_port` and `realm`. + +#### hop_interval + +!!! question "Since sing-box 1.11.0" + +Port hopping interval. + +`30s` is used by default. + +#### hop_interval_max + +!!! question "Since sing-box 1.14.0" + +Maximum port hopping interval, used for randomization. + +If set, the actual hop interval will be randomly chosen between `hop_interval` and `hop_interval_max`. + #### up_mbps, down_mbps Max bandwidth, in Mbps. @@ -52,7 +114,7 @@ If empty, the BBR congestion control algorithm will be used instead of Hysteria #### obfs.type -QUIC traffic obfuscator type, only available with `salamander`. +QUIC traffic obfuscator type, one of `salamander` `gecko`. Disabled if empty. @@ -60,6 +122,22 @@ Disabled if empty. QUIC traffic obfuscator password. +#### obfs.min_packet_size + +!!! question "Since sing-box 1.14.0" + +Minimum on-wire packet size in bytes. Gecko only. + +`512` is used by default. + +#### obfs.max_packet_size + +!!! question "Since sing-box 1.14.0" + +Maximum on-wire packet size in bytes. Gecko only. + +`1200` is used by default. + #### password Authentication password. @@ -78,10 +156,66 @@ Both is enabled by default. TLS configuration, see [TLS](/configuration/shared/tls/#outbound). +### QUIC Fields + +See [QUIC Fields](/configuration/shared/quic/) for details. + +#### bbr_profile + +!!! question "Since sing-box 1.14.0" + +BBR congestion control algorithm profile, one of `conservative` `standard` `aggressive`. + +`standard` is used by default. + #### brutal_debug Enable debug information logging for Hysteria Brutal CC. +#### realm + +!!! question "Since sing-box 1.14.0" + +Connect to a Hysteria2 server through a Hysteria Realm rendezvous service. + +The outbound queries the realm for the server's current public addresses, performs UDP hole-punching, and proceeds with the normal QUIC handshake. + +Conflicts with `server`, `server_port` and `server_ports`. + +The TLS SNI defaults to the host portion of `server_url`. Set `tls.server_name` to match the certificate the Hysteria2 server presents. + +See [Hysteria Realm](/configuration/service/hysteria-realm/) for the rendezvous service. + +#### realm.server_url + +==Required== + +Realm rendezvous service URL. + +#### realm.token + +Bearer token for the realm. Must match one of `users[].token` configured on the realm. + +#### realm.realm_id + +==Required== + +The same slot identifier the target Hysteria2 server registered. + +#### realm.stun_servers + +==Required== + +List of STUN servers (`host` or `host:port`) used to discover this client's public addresses. + +Domain names are resolved using [`domain_resolver`](/configuration/shared/dial/#domain_resolver) from Dial Fields. + +#### realm.http_client + +HTTP client used to talk to the realm. + +See [HTTP Client](/configuration/shared/http-client/) for details. + ### Dial Fields See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/outbound/hysteria2.zh.md b/docs/configuration/outbound/hysteria2.zh.md index 7176b9a6f7..07ec96ce11 100644 --- a/docs/configuration/outbound/hysteria2.zh.md +++ b/docs/configuration/outbound/hysteria2.zh.md @@ -1,3 +1,15 @@ +!!! quote "sing-box 1.14.0 中的更改" + + :material-plus: [hop_interval_max](#hop_interval_max) + :material-plus: [bbr_profile](#bbr_profile) + :material-plus: [realm](#realm) + :material-alert: [obfs](#obfstype) + +!!! quote "sing-box 1.11.0 中的更改" + + :material-plus: [server_ports](#server_ports) + :material-plus: [hop_interval](#hop_interval) + ### 结构 ```json @@ -7,6 +19,11 @@ "server": "127.0.0.1", "server_port": 1080, + "server_ports": [ + "2080:3000" + ], + "hop_interval": "", + "hop_interval_max": "", "up_mbps": 100, "down_mbps": 100, "obfs": { @@ -16,16 +33,31 @@ "password": "goofy_ahh_password", "network": "tcp", "tls": {}, + + ... // QUIC 字段 + + "bbr_profile": "", "brutal_debug": false, - + "realm": { + "server_url": "https://realm.example.com", + "token": "", + "realm_id": "", + "stun_servers": [], + "http_client": {} + }, + ... // 拨号字段 } ``` +!!! note "" + + 当内容只有一项时,可以忽略 JSON 数组 [] 标签 + !!! warning "与官方 Hysteria2 的区别" 官方程序支持一种名为 **userpass** 的验证方式, - 本质上上是将用户名与密码的组合 `:` 作为实际上的密码,而 sing-box 不提供此别名。 + 本质上是将用户名与密码的组合 `:` 作为实际上的密码,而 sing-box 不提供此别名。 要将 sing-box 与官方程序一起使用, 您需要填写该组合作为实际密码。 ### 字段 @@ -36,12 +68,42 @@ 服务器地址。 +与 `realm` 冲突。 + #### server_port ==必填== 服务器端口。 +如果设置了 `server_ports`,则忽略此项。 + +与 `realm` 冲突。 + +#### server_ports + +!!! question "自 sing-box 1.11.0 起" + +服务器端口范围列表。 + +与 `server_port` 和 `realm` 冲突。 + +#### hop_interval + +!!! question "自 sing-box 1.11.0 起" + +端口跳跃间隔。 + +默认使用 `30s`。 + +#### hop_interval_max + +!!! question "自 sing-box 1.14.0 起" + +最大端口跳跃间隔,用于随机化。 + +如果设置,实际跳跃间隔将在 `hop_interval` 和 `hop_interval_max` 之间随机选择。 + #### up_mbps, down_mbps 最大带宽。 @@ -50,13 +112,29 @@ #### obfs.type -QUIC 流量混淆器类型,仅可设为 `salamander`。 +QUIC 流量混淆器类型,可选 `salamander` `gecko`。 如果为空则禁用。 #### obfs.password -QUIC 流量混淆器密码. +QUIC 流量混淆器密码。 + +#### obfs.min_packet_size + +!!! question "自 sing-box 1.14.0 起" + +最小线上数据包大小(字节)。仅限 Gecko。 + +默认使用 `512`。 + +#### obfs.max_packet_size + +!!! question "自 sing-box 1.14.0 起" + +最大线上数据包大小(字节)。仅限 Gecko。 + +默认使用 `1200`。 #### password @@ -74,12 +152,68 @@ QUIC 流量混淆器密码. ==必填== -TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。 +TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。 + +### QUIC 字段 + +参阅 [QUIC 字段](/zh/configuration/shared/quic/) 了解详情。 + +#### bbr_profile + +!!! question "自 sing-box 1.14.0 起" + +BBR 拥塞控制算法配置,可选 `conservative` `standard` `aggressive`。 + +默认使用 `standard`。 #### brutal_debug 启用 Hysteria Brutal CC 的调试信息日志记录。 +#### realm + +!!! question "自 sing-box 1.14.0 起" + +通过 Hysteria Realm 会合服务连接 Hysteria2 服务器。 + +出站从 realm 查询服务器当前的公网地址,执行 UDP 打洞,然后进行常规的 QUIC 握手。 + +与 `server`、`server_port` 和 `server_ports` 冲突。 + +TLS SNI 默认使用 `server_url` 中的主机名。需设置 `tls.server_name` 以匹配 Hysteria2 服务器证书覆盖的名字。 + +会合服务参阅 [Hysteria Realm](/zh/configuration/service/hysteria-realm/)。 + +#### realm.server_url + +==必填== + +Realm 会合服务 URL。 + +#### realm.token + +Realm 的 Bearer 令牌,需与 realm 上配置的 `users[].token` 之一匹配。 + +#### realm.realm_id + +==必填== + +目标 Hysteria2 服务器注册时使用的相同槽位标识符。 + +#### realm.stun_servers + +==必填== + +用于发现本客户端公网地址的 STUN 服务器列表(`host` 或 `host:port`)。 + +域名通过 [拨号字段](/zh/configuration/shared/dial/) 中的 [`domain_resolver`](/zh/configuration/shared/dial/#domain_resolver) 解析。 + +#### realm.http_client + +与 realm 通信使用的 HTTP 客户端。 + +参阅 [HTTP 客户端](/zh/configuration/shared/http-client/) 了解详情。 + ### 拨号字段 参阅 [拨号字段](/zh/configuration/shared/dial/)。 diff --git a/docs/configuration/outbound/index.md b/docs/configuration/outbound/index.md index c5dc291769..6eab225702 100644 --- a/docs/configuration/outbound/index.md +++ b/docs/configuration/outbound/index.md @@ -22,6 +22,7 @@ | `socks` | [SOCKS](./socks/) | | `http` | [HTTP](./http/) | | `shadowsocks` | [Shadowsocks](./shadowsocks/) | +| `snell` | [Snell](./snell/) | | `vmess` | [VMess](./vmess/) | | `trojan` | [Trojan](./trojan/) | | `wireguard` | [Wireguard](./wireguard/) | @@ -30,11 +31,14 @@ | `shadowtls` | [ShadowTLS](./shadowtls/) | | `tuic` | [TUIC](./tuic/) | | `hysteria2` | [Hysteria2](./hysteria2/) | +| `mieru` | [Mieru](./mieru/) | +| `anytls` | [AnyTLS](./anytls/) | | `tor` | [Tor](./tor/) | | `ssh` | [SSH](./ssh/) | | `dns` | [DNS](./dns/) | | `selector` | [Selector](./selector/) | | `urltest` | [URLTest](./urltest/) | +| `naive` | [NaiveProxy](./naive/) | #### tag diff --git a/docs/configuration/outbound/index.zh.md b/docs/configuration/outbound/index.zh.md index c7ee59e91d..7364dd03ce 100644 --- a/docs/configuration/outbound/index.zh.md +++ b/docs/configuration/outbound/index.zh.md @@ -22,6 +22,7 @@ | `socks` | [SOCKS](./socks/) | | `http` | [HTTP](./http/) | | `shadowsocks` | [Shadowsocks](./shadowsocks/) | +| `snell` | [Snell](./snell/) | | `vmess` | [VMess](./vmess/) | | `trojan` | [Trojan](./trojan/) | | `wireguard` | [Wireguard](./wireguard/) | @@ -30,11 +31,14 @@ | `shadowtls` | [ShadowTLS](./shadowtls/) | | `tuic` | [TUIC](./tuic/) | | `hysteria2` | [Hysteria2](./hysteria2/) | +| `mieru` | [Mieru](./mieru/) | +| `anytls` | [AnyTLS](./anytls/) | | `tor` | [Tor](./tor/) | | `ssh` | [SSH](./ssh/) | | `dns` | [DNS](./dns/) | | `selector` | [Selector](./selector/) | | `urltest` | [URLTest](./urltest/) | +| `naive` | [NaiveProxy](./naive/) | #### tag diff --git a/docs/configuration/outbound/mieru.md b/docs/configuration/outbound/mieru.md new file mode 100644 index 0000000000..135403f04d --- /dev/null +++ b/docs/configuration/outbound/mieru.md @@ -0,0 +1,71 @@ +--- +icon: material/new-box +--- + +### Structure + +```json +{ + "type": "mieru", + "tag": "mieru-out", + + "server": "127.0.0.1", + "server_port": 1080, + "server_ports": [ + "9000-9010", + "9020-9030" + ], + "transport": "TCP", + "username": "asdf", + "password": "hjkl", + "multiplexing": "MULTIPLEXING_LOW", + + ... // Dial Fields +} +``` + +### Fields + +#### server + +==Required== + +The server address. + +#### server_port + +The server port. + +Must set at least one field between `server_port` and `server_ports`. + +#### server_ports + +Server port range list. + +Must set at least one field between `server_port` and `server_ports`. + +#### transport + +==Required== + +Transmission protocol. The only allowed value is `TCP`. + +#### username + +==Required== + +mieru user name. + +#### password + +==Required== + +mieru password. + +#### multiplexing + +Multiplexing level. Supported values are `MULTIPLEXING_OFF`, `MULTIPLEXING_LOW`, `MULTIPLEXING_MIDDLE`, `MULTIPLEXING_HIGH`. `MULTIPLEXING_OFF` disables multiplexing. + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/outbound/mieru.zh.md b/docs/configuration/outbound/mieru.zh.md new file mode 100644 index 0000000000..fe9b37b83b --- /dev/null +++ b/docs/configuration/outbound/mieru.zh.md @@ -0,0 +1,71 @@ +--- +icon: material/new-box +--- + +### 结构 + +```json +{ + "type": "mieru", + "tag": "mieru-out", + + "server": "127.0.0.1", + "server_port": 1080, + "server_ports": [ + "9000-9010", + "9020-9030" + ], + "transport": "TCP", + "username": "asdf", + "password": "hjkl", + "multiplexing": "MULTIPLEXING_LOW", + + ... // 拨号字段 +} +``` + +### 字段 + +#### server + +==必填== + +服务器地址。 + +#### server_port + +服务器端口。 + +必须填写 `server_port` 和 `server_ports` 中至少一项。 + +#### server_ports + +服务器端口范围列表。 + +必须填写 `server_port` 和 `server_ports` 中至少一项。 + +#### transport + +==必填== + +通信协议。仅可设为 `TCP`。 + +#### username + +==必填== + +mieru 用户名。 + +#### password + +==必填== + +mieru 密码。 + +#### multiplexing + +多路复用设置。可以设为 `MULTIPLEXING_OFF`,`MULTIPLEXING_LOW`,`MULTIPLEXING_MIDDLE`,`MULTIPLEXING_HIGH`。其中 `MULTIPLEXING_OFF` 会关闭多路复用功能。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/)。 diff --git a/docs/configuration/outbound/naive.md b/docs/configuration/outbound/naive.md new file mode 100644 index 0000000000..5ed9d2b8e8 --- /dev/null +++ b/docs/configuration/outbound/naive.md @@ -0,0 +1,116 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.13.0" + +### Structure + +```json +{ + "type": "naive", + "tag": "naive-out", + + "server": "127.0.0.1", + "server_port": 443, + "username": "sekai", + "password": "password", + "insecure_concurrency": 0, + "extra_headers": {}, + "udp_over_tcp": false | {}, + "quic": false, + "quic_congestion_control": "", + "tls": {}, + + ... // Dial Fields +} +``` + +!!! warning "Platform Support" + + NaiveProxy outbound is only available on Apple platforms, Android, Windows and certain Linux builds. + + **Official Release Build Variants:** + + | Build Variant | Platforms | Description | + |---------------|-----------|-------------| + | (no suffix) | Linux amd64/arm64 | purego build, `libcronet.so` included | + | `-glibc` | Linux 386/amd64/arm/arm64/mipsle/mips64le/riscv64/loong64 | CGO build, dynamically linked with glibc, requires glibc >= 2.31 (loong64: >= 2.36) | + | `-musl` | Linux 386/amd64/arm/arm64/mipsle/riscv64/loong64 | CGO build, statically linked with musl | + | (no suffix) | Windows amd64/arm64 | purego build, `libcronet.dll` included | + + For Linux, choose the glibc or musl variant based on your distribution's libc type. + + **Runtime Requirements:** + + - **Linux purego**: `libcronet.so` must be in the same directory as the sing-box binary or in system library path + - **Windows**: `libcronet.dll` must be in the same directory as `sing-box.exe` or in a directory listed in `PATH` + + For self-built binaries, see [Build from source](/installation/build-from-source/#with_naive_outbound). + +### Fields + +#### server + +==Required== + +The server address. + +#### server_port + +==Required== + +The server port. + +#### username + +Authentication username. + +#### password + +Authentication password. + +#### insecure_concurrency + +Number of concurrent tunnel connections. Multiple connections make the tunneling easier to detect through traffic analysis, which defeats the purpose of NaiveProxy's design to resist traffic analysis. + +#### extra_headers + +Extra headers to send in HTTP requests. + +#### udp_over_tcp + +UDP over TCP protocol settings. + +See [UDP Over TCP](/configuration/shared/udp-over-tcp/) for details. + +#### quic + +Use QUIC instead of HTTP/2. + +#### quic_congestion_control + +QUIC congestion control algorithm. + +| Algorithm | Description | +|-----------|-------------| +| `bbr` | BBR | +| `bbr2` | BBRv2 | +| `cubic` | CUBIC | +| `reno` | New Reno | + +`bbr` is used by default (the default of QUICHE, used by Chromium which NaiveProxy is based on). + +#### tls + +==Required== + +TLS configuration, see [TLS](/configuration/shared/tls/#outbound). + +Only `server_name`, `certificate`, `certificate_path` and `ech` are supported. + +Self-signed certificates change traffic behavior significantly, which defeats the purpose of NaiveProxy's design to resist traffic analysis, and should not be used in production. + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/outbound/naive.zh.md b/docs/configuration/outbound/naive.zh.md new file mode 100644 index 0000000000..dbfd7fbff6 --- /dev/null +++ b/docs/configuration/outbound/naive.zh.md @@ -0,0 +1,116 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.13.0 起" + +### 结构 + +```json +{ + "type": "naive", + "tag": "naive-out", + + "server": "127.0.0.1", + "server_port": 443, + "username": "sekai", + "password": "password", + "insecure_concurrency": 0, + "extra_headers": {}, + "udp_over_tcp": false | {}, + "quic": false, + "quic_congestion_control": "", + "tls": {}, + + ... // 拨号字段 +} +``` + +!!! warning "平台支持" + + NaiveProxy 出站仅在 Apple 平台、Android、Windows 和特定 Linux 构建上可用。 + + **官方发布版本区别:** + + | 构建变体 | 平台 | 说明 | + |---|---|---| + | (无后缀) | Linux amd64/arm64 | purego 构建,包含 `libcronet.so` | + | `-glibc` | Linux 386/amd64/arm/arm64/mipsle/mips64le/riscv64/loong64 | CGO 构建,动态链接 glibc,要求 glibc >= 2.31(loong64: >= 2.36) | + | `-musl` | Linux 386/amd64/arm/arm64/mipsle/riscv64/loong64 | CGO 构建,静态链接 musl | + | (无后缀) | Windows amd64/arm64 | purego 构建,包含 `libcronet.dll` | + + 对于 Linux,请根据发行版的 libc 类型选择 glibc 或 musl 变体。 + + **运行时要求:** + + - **Linux purego**:`libcronet.so` 必须位于 sing-box 二进制文件相同目录或系统库路径中 + - **Windows**:`libcronet.dll` 必须位于 `sing-box.exe` 相同目录或 `PATH` 中的任意目录 + + 自行构建请参阅 [从源代码构建](/zh/installation/build-from-source/#with_naive_outbound)。 + +### 字段 + +#### server + +==必填== + +服务器地址。 + +#### server_port + +==必填== + +服务器端口。 + +#### username + +认证用户名。 + +#### password + +认证密码。 + +#### insecure_concurrency + +并发隧道连接数。多连接使隧道更容易被流量分析检测,违背 NaiveProxy 抵抗流量分析的设计目的。 + +#### extra_headers + +HTTP 请求中发送的额外头部。 + +#### udp_over_tcp + +UDP over TCP 配置。 + +参阅 [UDP Over TCP](/zh/configuration/shared/udp-over-tcp/)。 + +#### quic + +使用 QUIC 代替 HTTP/2。 + +#### quic_congestion_control + +QUIC 拥塞控制算法。 + +| 算法 | 描述 | +|------|------| +| `bbr` | BBR | +| `bbr2` | BBRv2 | +| `cubic` | CUBIC | +| `reno` | New Reno | + +默认使用 `bbr`(NaiveProxy 基于的 Chromium 使用的 QUICHE 的默认值)。 + +#### tls + +==必填== + +TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。 + +只有 `server_name`、`certificate`、`certificate_path` 和 `ech` 是被支持的。 + +自签名证书会显著改变流量行为,违背了 NaiveProxy 旨在抵抗流量分析的设计初衷,不应该在生产环境中使用。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/)。 diff --git a/docs/configuration/outbound/selector.zh.md b/docs/configuration/outbound/selector.zh.md index ffe2d70ae1..520fb15cfc 100644 --- a/docs/configuration/outbound/selector.zh.md +++ b/docs/configuration/outbound/selector.zh.md @@ -17,7 +17,7 @@ !!! quote "" - 选择器目前只能通过 [Clash API](/zh/configuration/experimental#clash-api) 来控制。 + 选择器目前只能通过 [Clash API](/zh/configuration/experimental/clash-api/) 来控制。 ### 字段 diff --git a/docs/configuration/outbound/shadowsocks.zh.md b/docs/configuration/outbound/shadowsocks.zh.md index 818a4fa988..7b4ff56097 100644 --- a/docs/configuration/outbound/shadowsocks.zh.md +++ b/docs/configuration/outbound/shadowsocks.zh.md @@ -95,7 +95,7 @@ UDP over TCP 配置。 #### multiplex -参阅 [多路复用](/zh/configuration/shared/multiplex#outbound)。 +参阅 [多路复用](/zh/configuration/shared/multiplex#出站)。 ### 拨号字段 diff --git a/docs/configuration/outbound/shadowtls.zh.md b/docs/configuration/outbound/shadowtls.zh.md index bb8d0e87a8..72a73d7d1e 100644 --- a/docs/configuration/outbound/shadowtls.zh.md +++ b/docs/configuration/outbound/shadowtls.zh.md @@ -49,7 +49,7 @@ ShadowTLS 协议版本。 ==必填== -TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。 +TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。 ### 拨号字段 diff --git a/docs/configuration/outbound/snell.md b/docs/configuration/outbound/snell.md new file mode 100644 index 0000000000..4faae9f2b9 --- /dev/null +++ b/docs/configuration/outbound/snell.md @@ -0,0 +1,91 @@ +### Structure + +```json +{ + "type": "snell", + "tag": "snell-out", + + "server": "127.0.0.1", + "server_port": 8388, + "psk": "my-pre-shared-key", + "version": 4, + "reuse": false, + "network": "", + "obfs_mode": "", + "obfs_host": "", + + ... // Dial Fields +} +``` + +### Fields + +#### server + +==Required== + +The server address. + +#### server_port + +==Required== + +The server port. + +#### psk + +==Required== + +The pre-shared key for authentication. + +#### version + +Snell protocol version. One of `1` `2` `3` `4` `5`. + +Defaults to `4`. + +| Version | TCP | UDP | +|---------|-----|-----------------| +| 1, 2 | ✔ | ✘ | +| 3, 4 | ✔ | ✔ (UoT) | +| 5 | ✔ | ✔ (QUIC proxy) | + +!!! note "QUIC Proxy Mode (v5)" + In v5, QUIC traffic uses a dedicated proxy path: the destination address + and the first QUIC Initial packet are encrypted by Snell; subsequent + packets are forwarded as-is (QUIC provides its own encryption). + Other UDP traffic falls back to the standard UDP-over-TCP tunnel. + +#### network + +Enabled network, one of `tcp` `udp`. + +Both are enabled by default for v3/v4/v5. For v1/v2 only TCP is enabled by default (no UDP support). + +UDP requires version `3` or above. + +#### reuse + +Enable connection reuse (Snell v4+ only). Reuses an existing TCP connection for +multiple sequential requests, avoiding the overhead of a new TCP and encryption +handshake for each request. + +Requires the server to support Snell v4 connection reuse. + +Defaults to `false`. + +#### obfs_mode + +Simple-obfs obfuscation mode. + +One of `http` `tls`, or empty to disable. + +#### obfs_host + +The obfuscation hostname used for HTTP/TLS obfuscation. + +Defaults to `bing.com` if not set. + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/outbound/snell.zh.md b/docs/configuration/outbound/snell.zh.md new file mode 100644 index 0000000000..ad5bde0337 --- /dev/null +++ b/docs/configuration/outbound/snell.zh.md @@ -0,0 +1,87 @@ +### 结构 + +```json +{ + "type": "snell", + "tag": "snell-out", + + "server": "127.0.0.1", + "server_port": 8388, + "psk": "my-pre-shared-key", + "version": 4, + "reuse": false, + "network": "", + "obfs_mode": "", + "obfs_host": "", + + ... // 拨号字段 +} +``` + +### 字段 + +#### server + +==必填== + +服务器地址。 + +#### server_port + +==必填== + +服务器端口。 + +#### psk + +==必填== + +用于身份验证的预共享密钥。 + +#### version + +Snell 协议版本,可选 `1` `2` `3` `4` `5`。 + +默认为 `4`。 + +| 版本 | TCP | UDP | +|--------|-----|------------------| +| 1, 2 | ✔ | ✘ | +| 3, 4 | ✔ | ✔(UDP over TCP) | +| 5 | ✔ | ✔(QUIC 代理) | + +!!! note "QUIC 代理模式(v5)" + v5 的 QUIC 流量采用专用代理路径:目标地址和首个 QUIC Initial 包经 Snell 加密传输, + 后续包直接转发(QUIC 本身已加密)。其他 UDP 流量仍走标准 UDP over TCP 隧道。 + +#### network + +启用的网络,可选 `tcp` `udp`。 + +v3/v4/v5 默认两者均启用;v1/v2 默认仅 TCP(不支持 UDP)。 + +UDP 需要版本 `3` 及以上。 + +#### reuse + +启用连接复用(仅限 Snell v4+)。复用已有的 TCP 连接串行处理多个请求,避免每次请求都重新建立 TCP 连接和加密握手的开销。 + +需要服务端支持 Snell v4 连接复用。 + +默认为 `false`。 + +#### obfs_mode + +simple-obfs 混淆模式。 + +可选 `http` `tls`,留空则禁用混淆。 + +#### obfs_host + +用于 HTTP/TLS 混淆的主机名。 + +未设置时默认为 `bing.com`。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/)。 diff --git a/docs/configuration/outbound/ssh.md b/docs/configuration/outbound/ssh.md index 45ec72b2b5..8ef49b9cac 100644 --- a/docs/configuration/outbound/ssh.md +++ b/docs/configuration/outbound/ssh.md @@ -1,3 +1,9 @@ +!!! quote "Changes in sing-box 1.14.0" + + :material-plus: [cipher](#cipher) + :material-plus: [mac](#mac) + :material-plus: [kex_algorithm](#kex_algorithm) + ### Structure ```json @@ -17,6 +23,9 @@ ], "host_key_algorithms": [], "client_version": "SSH-2.0-OpenSSH_7.4p1", + "cipher": [], + "mac": [], + "kex_algorithm": [], ... // Dial Fields } @@ -66,6 +75,24 @@ Host key algorithms. Client version. Random version will be used if empty. +#### cipher + +!!! question "Since sing-box 1.14.0" + +Allowed ciphers. Default values are used if empty. + +#### mac + +!!! question "Since sing-box 1.14.0" + +Allowed MAC algorithms. Default values are used if empty. + +#### kex_algorithm + +!!! question "Since sing-box 1.14.0" + +Allowed key exchange algorithms. Default values are used if empty. + ### Dial Fields See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/outbound/ssh.zh.md b/docs/configuration/outbound/ssh.zh.md index e538e64aa8..cb2c0345e4 100644 --- a/docs/configuration/outbound/ssh.zh.md +++ b/docs/configuration/outbound/ssh.zh.md @@ -1,3 +1,9 @@ +!!! quote "sing-box 1.14.0 中的更改" + + :material-plus: [cipher](#cipher) + :material-plus: [mac](#mac) + :material-plus: [kex_algorithm](#kex_algorithm) + ### 结构 ```json @@ -17,6 +23,9 @@ ], "host_key_algorithms": [], "client_version": "SSH-2.0-OpenSSH_7.4p1", + "cipher": [], + "mac": [], + "kex_algorithm": [], ... // 拨号字段 } @@ -66,6 +75,24 @@ SSH 用户, 默认使用 root。 客户端版本,默认使用随机值。 +#### cipher + +!!! question "自 sing-box 1.14.0 起" + +允许的加密算法。留空使用默认值。 + +#### mac + +!!! question "自 sing-box 1.14.0 起" + +允许的 MAC 算法。留空使用默认值。 + +#### kex_algorithm + +!!! question "自 sing-box 1.14.0 起" + +允许的密钥交换算法。留空使用默认值。 + ### 拨号字段 参阅 [拨号字段](/zh/configuration/shared/dial/)。 diff --git a/docs/configuration/outbound/tor.zh.md b/docs/configuration/outbound/tor.zh.md index be5059642f..a49eb3230b 100644 --- a/docs/configuration/outbound/tor.zh.md +++ b/docs/configuration/outbound/tor.zh.md @@ -18,7 +18,7 @@ !!! info "" - 默认安装不包含嵌入式 Tor, 参阅 [安装](/zh/installation/build-from-source/#_5)。 + 默认安装不包含嵌入式 Tor, 参阅 [安装](/zh/installation/build-from-source/#构建标记)。 ### 字段 diff --git a/docs/configuration/outbound/trojan.zh.md b/docs/configuration/outbound/trojan.zh.md index 2248c739bc..8a78ca2dcc 100644 --- a/docs/configuration/outbound/trojan.zh.md +++ b/docs/configuration/outbound/trojan.zh.md @@ -47,11 +47,11 @@ Trojan 密码。 #### tls -TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。 +TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。 #### multiplex -参阅 [多路复用](/zh/configuration/shared/multiplex#outbound)。 +参阅 [多路复用](/zh/configuration/shared/multiplex#出站)。 #### transport diff --git a/docs/configuration/outbound/tuic.md b/docs/configuration/outbound/tuic.md index 4f4ef4850d..3701e73dd1 100644 --- a/docs/configuration/outbound/tuic.md +++ b/docs/configuration/outbound/tuic.md @@ -16,7 +16,9 @@ "heartbeat": "10s", "network": "tcp", "tls": {}, - + + ... // QUIC Fields + ... // Dial Fields } ``` @@ -91,6 +93,10 @@ Both is enabled by default. TLS configuration, see [TLS](/configuration/shared/tls/#outbound). +### QUIC Fields + +See [QUIC Fields](/configuration/shared/quic/) for details. + ### Dial Fields See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/outbound/tuic.zh.md b/docs/configuration/outbound/tuic.zh.md index ee8fd15d3f..43df2cfc8a 100644 --- a/docs/configuration/outbound/tuic.zh.md +++ b/docs/configuration/outbound/tuic.zh.md @@ -16,7 +16,9 @@ "heartbeat": "10s", "network": "tcp", "tls": {}, - + + ... // QUIC 字段 + ... // 拨号字段 } ``` @@ -66,7 +68,7 @@ UDP 包中继模式 #### udp_over_stream -这是 TUIC 的 [UDP over TCP 协议](/configuration/shared/udp-over-tcp/) 移植, 旨在提供 TUIC 不提供的 基于 QUIC 流的 UDP 中继模式。 由于它是一个附加协议,因此您需要使用 sing-box 或其他兼容的程序作为服务器。 +这是 TUIC 的 [UDP over TCP 协议](/zh/configuration/shared/udp-over-tcp/) 移植, 旨在提供 TUIC 不提供的 基于 QUIC 流的 UDP 中继模式。 由于它是一个附加协议,因此您需要使用 sing-box 或其他兼容的程序作为服务器。 此模式在正确的 UDP 代理场景中没有任何积极作用,仅适用于中继流式 UDP 流量(基本上是 QUIC 流)。 @@ -97,7 +99,11 @@ UDP 包中继模式 ==必填== -TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。 +TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。 + +### QUIC 字段 + +参阅 [QUIC 字段](/zh/configuration/shared/quic/) 了解详情。 ### 拨号字段 diff --git a/docs/configuration/outbound/vless.zh.md b/docs/configuration/outbound/vless.zh.md index 8978a6ac6b..f3bc9a0821 100644 --- a/docs/configuration/outbound/vless.zh.md +++ b/docs/configuration/outbound/vless.zh.md @@ -57,7 +57,7 @@ VLESS 子协议。 #### tls -TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。 +TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。 #### packet_encoding @@ -71,7 +71,7 @@ UDP 包编码,默认使用 xudp。 #### multiplex -参阅 [多路复用](/zh/configuration/shared/multiplex#outbound)。 +参阅 [多路复用](/zh/configuration/shared/multiplex#出站)。 #### transport diff --git a/docs/configuration/outbound/vmess.zh.md b/docs/configuration/outbound/vmess.zh.md index 295b8ddef0..cbc8bdeea4 100644 --- a/docs/configuration/outbound/vmess.zh.md +++ b/docs/configuration/outbound/vmess.zh.md @@ -82,7 +82,7 @@ VMess 用户 ID。 #### tls -TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。 +TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。 #### packet_encoding @@ -96,7 +96,7 @@ UDP 包编码。 #### multiplex -参阅 [多路复用](/zh/configuration/shared/multiplex#outbound)。 +参阅 [多路复用](/zh/configuration/shared/multiplex#出站)。 #### transport diff --git a/docs/configuration/outbound/wireguard.md b/docs/configuration/outbound/wireguard.md index c3f51f1fb6..648ba60725 100644 --- a/docs/configuration/outbound/wireguard.md +++ b/docs/configuration/outbound/wireguard.md @@ -1,6 +1,18 @@ +--- +icon: material/delete-clock +--- + +!!! failure "Deprecated in sing-box 1.11.0" + + WireGuard outbound is deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-wireguard-outbound-to-endpoint). + +!!! quote "Changes in sing-box 1.11.0" + + :material-delete-alert: [gso](#gso) + !!! quote "Changes in sing-box 1.8.0" - :material-plus: [gso](#gso) + :material-plus: [gso](#gso) ### Structure @@ -12,10 +24,9 @@ "server": "127.0.0.1", "server_port": 1080, "system_interface": false, - "gso": false, "interface_name": "wg0", "local_address": [ - "10.0.0.2/32" + "10.0.0.1/32" ], "private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=", "peers": [ @@ -37,6 +48,10 @@ "mtu": 1408, "network": "tcp", + // Deprecated + + "gso": false, + ... // Dial Fields } ``` @@ -69,6 +84,10 @@ Custom interface name for system interface. #### gso +!!! failure "Deprecated in sing-box 1.11.0" + + GSO will be automatically enabled when available since sing-box 1.11.0. + !!! question "Since sing-box 1.8.0" !!! quote "" diff --git a/docs/configuration/outbound/wireguard.zh.md b/docs/configuration/outbound/wireguard.zh.md index 5de2813225..2b6d4a0aa2 100644 --- a/docs/configuration/outbound/wireguard.zh.md +++ b/docs/configuration/outbound/wireguard.zh.md @@ -1,6 +1,18 @@ +--- +icon: material/delete-clock +--- + +!!! failure "已在 sing-box 1.11.0 废弃" + + WireGuard 出站已被弃用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移-wireguard-出站到端点)。 + +!!! quote "sing-box 1.11.0 中的更改" + + :material-delete-alert: [gso](#gso) + !!! quote "sing-box 1.8.0 中的更改" - :material-plus: [gso](#gso) + :material-plus: [gso](#gso) ### 结构 @@ -12,10 +24,9 @@ "server": "127.0.0.1", "server_port": 1080, "system_interface": false, - "gso": false, "interface_name": "wg0", "local_address": [ - "10.0.0.2/32" + "10.0.0.1/32" ], "private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=", "peer_public_key": "Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=", @@ -24,6 +35,10 @@ "workers": 4, "mtu": 1408, "network": "tcp", + + // 废弃的 + + "gso": false, ... // 拨号字段 } @@ -57,6 +72,10 @@ #### gso +!!! failure "已在 sing-box 1.11.0 废弃" + + 自 sing-box 1.11.0 起,GSO 将可用时自动启用。 + !!! question "自 sing-box 1.8.0 起" !!! quote "" @@ -69,7 +88,7 @@ ==必填== -接口的 IPv4/IPv6 地址或地址段的列表您。 +接口的 IPv4/IPv6 地址或地址段的列表。 要分配给接口的 IP(v4 或 v6)地址段列表。 diff --git a/docs/configuration/route/geoip.md b/docs/configuration/route/geoip.md index 8a2ed1d4a1..62162cdf8d 100644 --- a/docs/configuration/route/geoip.md +++ b/docs/configuration/route/geoip.md @@ -1,10 +1,10 @@ --- -icon: material/delete-clock +icon: material/note-remove --- -!!! failure "Deprecated in sing-box 1.8.0" +!!! failure "Removed in sing-box 1.12.0" - GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets). + GeoIP is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets). ### Structure diff --git a/docs/configuration/route/geoip.zh.md b/docs/configuration/route/geoip.zh.md index fb2481e220..17559a4657 100644 --- a/docs/configuration/route/geoip.zh.md +++ b/docs/configuration/route/geoip.zh.md @@ -1,10 +1,10 @@ --- -icon: material/delete-clock +icon: material/note-remove --- -!!! failure "已在 sing-box 1.8.0 废弃" +!!! failure "已在 sing-box 1.12.0 中被移除" - GeoIP 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geoip)。 + GeoIP 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移-geoip-到规则集)。 ### 结构 diff --git a/docs/configuration/route/geosite.md b/docs/configuration/route/geosite.md index 0463057153..830d115803 100644 --- a/docs/configuration/route/geosite.md +++ b/docs/configuration/route/geosite.md @@ -1,10 +1,10 @@ --- -icon: material/delete-clock +icon: material/note-remove --- -!!! failure "Deprecated in sing-box 1.8.0" +!!! failure "Removed in sing-box 1.12.0" - Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-sets). + Geosite is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets). ### Structure diff --git a/docs/configuration/route/geosite.zh.md b/docs/configuration/route/geosite.zh.md index eeee38ff17..1ea0752ac9 100644 --- a/docs/configuration/route/geosite.zh.md +++ b/docs/configuration/route/geosite.zh.md @@ -1,10 +1,10 @@ --- -icon: material/delete-clock +icon: material/note-remove --- -!!! failure "已在 sing-box 1.8.0 废弃" +!!! failure "已在 sing-box 1.12.0 中被移除" - Geosite 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geosite)。 + Geosite 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移-geosite-到规则集)。 ### 结构 diff --git a/docs/configuration/route/index.md b/docs/configuration/route/index.md index 7b2a7e7ef2..1255723f47 100644 --- a/docs/configuration/route/index.md +++ b/docs/configuration/route/index.md @@ -1,5 +1,28 @@ +--- +icon: material/alert-decagram +--- + # Route +!!! quote "Changes in sing-box 1.14.0" + + :material-plus: [default_http_client](#default_http_client) + :material-plus: [find_neighbor](#find_neighbor) + :material-plus: [dhcp_lease_files](#dhcp_lease_files) + +!!! quote "Changes in sing-box 1.12.0" + + :material-plus: [default_domain_resolver](#default_domain_resolver) + :material-note-remove: [geoip](#geoip) + :material-note-remove: [geosite](#geosite) + +!!! quote "Changes in sing-box 1.11.0" + + :material-plus: [default_network_strategy](#default_network_strategy) + :material-plus: [default_network_type](#default_network_type) + :material-plus: [default_fallback_network_type](#default_fallback_network_type) + :material-plus: [default_fallback_delay](#default_fallback_delay) + !!! quote "Changes in sing-box 1.8.0" :material-plus: [rule_set](#rule_set) @@ -11,25 +34,36 @@ ```json { "route": { - "geoip": {}, - "geosite": {}, "rules": [], "rule_set": [], "final": "", "auto_detect_interface": false, "override_android_vpn": false, - "default_interface": "en0", - "default_mark": 233 + "default_interface": "", + "default_mark": 0, + "find_process": false, + "find_neighbor": false, + "dhcp_lease_files": [], + "default_http_client": "", + "default_domain_resolver": "", // or {} + "default_network_strategy": "", + "default_network_type": [], + "default_fallback_network_type": [], + "default_fallback_delay": "", + + // Removed + + "geoip": {}, + "geosite": {} } } ``` -### Fields +!!! note "" -| Key | Format | -|-----------|----------------------| -| `geoip` | [GeoIP](./geoip/) | -| `geosite` | [Geosite](./geosite/) | + You can ignore the JSON Array [] tag when the content is only one item + +### Fields #### rules @@ -39,7 +73,7 @@ List of [Route Rule](./rule/) !!! question "Since sing-box 1.8.0" -List of [Rule Set](/configuration/rule-set/) +List of [rule-set](/configuration/rule-set/) #### final @@ -81,4 +115,82 @@ Takes no effect if `auto_detect_interface` is set. Set routing mark by default. -Takes no effect if `outbound.routing_mark` is set. \ No newline at end of file +Takes no effect if `outbound.routing_mark` is set. + +#### find_process + +!!! quote "" + + Only supported on Linux, Windows, and macOS. + +Enable process search for logging when no `process_name`, `process_path`, `package_name`, `user` or `user_id` rules exist. + +#### find_neighbor + +!!! question "Since sing-box 1.14.0" + +!!! quote "" + + Only supported on Linux and macOS. + +Enable neighbor resolution for logging when no `source_mac_address` or `source_hostname` rules exist. + +See [Neighbor Resolution](/configuration/shared/neighbor/) for setup. + +#### dhcp_lease_files + +!!! question "Since sing-box 1.14.0" + +!!! quote "" + + Only supported on Linux and macOS. + +Custom DHCP lease file paths for hostname and MAC address resolution. + +Automatically detected from common DHCP servers (dnsmasq, odhcpd, ISC dhcpd, Kea) if empty. + +#### default_http_client + +!!! question "Since sing-box 1.14.0" + +Tag of the default [HTTP Client](/configuration/shared/http-client/) used by remote rule-sets. + +If empty and `http_clients` is defined, the first HTTP client is used. + +#### default_domain_resolver + +!!! question "Since sing-box 1.12.0" + +See [Dial Fields](/configuration/shared/dial/#domain_resolver) for details. + +Can be overridden by `outbound.domain_resolver`. + +#### default_network_strategy + +!!! question "Since sing-box 1.11.0" + +See [Dial Fields](/configuration/shared/dial/#network_strategy) for details. + +Takes no effect if `outbound.bind_interface`, `outbound.inet4_bind_address` or `outbound.inet6_bind_address` is set. + +Can be overridden by `outbound.network_strategy`. + +Conflicts with `default_interface`. + +#### default_network_type + +!!! question "Since sing-box 1.11.0" + +See [Dial Fields](/configuration/shared/dial/#network_type) for details. + +#### default_fallback_network_type + +!!! question "Since sing-box 1.11.0" + +See [Dial Fields](/configuration/shared/dial/#fallback_network_type) for details. + +#### default_fallback_delay + +!!! question "Since sing-box 1.11.0" + +See [Dial Fields](/configuration/shared/dial/#fallback_delay) for details. diff --git a/docs/configuration/route/index.zh.md b/docs/configuration/route/index.zh.md index 68d4f66d91..58d718df02 100644 --- a/docs/configuration/route/index.zh.md +++ b/docs/configuration/route/index.zh.md @@ -1,5 +1,28 @@ +--- +icon: material/alert-decagram +--- + # 路由 +!!! quote "sing-box 1.14.0 中的更改" + + :material-plus: [default_http_client](#default_http_client) + :material-plus: [find_neighbor](#find_neighbor) + :material-plus: [dhcp_lease_files](#dhcp_lease_files) + +!!! quote "sing-box 1.12.0 中的更改" + + :material-plus: [default_domain_resolver](#default_domain_resolver) + :material-note-remove: [geoip](#geoip) + :material-note-remove: [geosite](#geosite) + +!!! quote "sing-box 1.11.0 中的更改" + + :material-plus: [default_network_strategy](#default_network_strategy) + :material-plus: [default_network_type](#default_network_type) + :material-plus: [default_fallback_network_type](#default_fallback_network_type) + :material-plus: [default_fallback_delay](#default_fallback_delay) + !!! quote "sing-box 1.8.0 中的更改" :material-plus: [rule_set](#rule_set) @@ -18,12 +41,22 @@ "final": "", "auto_detect_interface": false, "override_android_vpn": false, - "default_interface": "en0", - "default_mark": 233 + "default_interface": "", + "default_mark": 0, + "find_process": false, + "find_neighbor": false, + "dhcp_lease_files": [], + "default_http_client": "", + "default_network_strategy": "", + "default_fallback_delay": "" } } ``` +!!! note "" + + 当内容只有一项时,可以忽略 JSON 数组 [] 标签 + ### 字段 | 键 | 格式 | @@ -39,7 +72,7 @@ !!! question "自 sing-box 1.8.0 起" -一组 [规则集](/configuration/rule-set/)。 +一组 [规则集](/zh/configuration/rule-set/)。 #### final @@ -82,3 +115,81 @@ 默认为出站连接设置路由标记。 如果设置了 `outbound.routing_mark` 设置,则不生效。 + +#### find_process + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS。 + +在没有 `process_name`、`process_path`、`package_name`、`user` 或 `user_id` 规则时启用进程搜索以输出日志。 + +#### find_neighbor + +!!! question "自 sing-box 1.14.0 起" + +!!! quote "" + + 仅支持 Linux 和 macOS。 + +在没有 `source_mac_address` 或 `source_hostname` 规则时启用邻居解析以输出日志。 + +参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。 + +#### dhcp_lease_files + +!!! question "自 sing-box 1.14.0 起" + +!!! quote "" + + 仅支持 Linux 和 macOS。 + +用于主机名和 MAC 地址解析的自定义 DHCP 租约文件路径。 + +为空时自动从常见 DHCP 服务器(dnsmasq、odhcpd、ISC dhcpd、Kea)检测。 + +#### default_http_client + +!!! question "自 sing-box 1.14.0 起" + +远程规则集使用的默认 [HTTP 客户端](/zh/configuration/shared/http-client/) 的标签。 + +如果为空且 `http_clients` 已定义,将使用第一个 HTTP 客户端。 + +#### default_domain_resolver + +!!! question "自 sing-box 1.12.0 起" + +详情参阅 [拨号字段](/zh/configuration/shared/dial/#domain_resolver)。 + +可以被 `outbound.domain_resolver` 覆盖。 + +#### network_strategy + +!!! question "自 sing-box 1.11.0 起" + +详情参阅 [拨号字段](/zh/configuration/shared/dial/#network_strategy)。 + +当 `outbound.bind_interface`, `outbound.inet4_bind_address` 或 `outbound.inet6_bind_address` 已设置时不生效。 + +可以被 `outbound.network_strategy` 覆盖。 + +与 `default_interface` 冲突。 + +#### default_network_type + +!!! question "自 sing-box 1.11.0 起" + +详情参阅 [拨号字段](/zh/configuration/shared/dial/#default_network_type)。 + +#### default_fallback_network_type + +!!! question "自 sing-box 1.11.0 起" + +详情参阅 [拨号字段](/zh/configuration/shared/dial/#default_fallback_network_type)。 + +#### default_fallback_delay + +!!! question "自 sing-box 1.11.0 起" + +详情参阅 [拨号字段](/zh/configuration/shared/dial/#fallback_delay)。 diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index 62d33c6c53..97bbe37606 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -1,3 +1,36 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.14.0" + + :material-plus: [source_mac_address](#source_mac_address) + :material-plus: [source_hostname](#source_hostname) + :material-plus: [package_name_regex](#package_name_regex) + +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [interface_address](#interface_address) + :material-plus: [network_interface_address](#network_interface_address) + :material-plus: [default_interface_address](#default_interface_address) + :material-plus: [preferred_by](#preferred_by) + :material-alert: [network](#network) + +!!! quote "Changes in sing-box 1.11.0" + + :material-plus: [action](#action) + :material-alert: [outbound](#outbound) + :material-plus: [network_type](#network_type) + :material-plus: [network_is_expensive](#network_is_expensive) + :material-plus: [network_is_constrained](#network_is_constrained) + +!!! quote "Changes in sing-box 1.10.0" + + :material-plus: [client](#client) + :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) + :material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source) + :material-plus: [process_path_regex](#process_path_regex) + !!! quote "Changes in sing-box 1.8.0" :material-plus: [rule_set](#rule_set) @@ -31,6 +64,12 @@ "http", "quic" ], + "client": [ + "chromium", + "safari", + "firefox", + "quic-go" + ], "domain": [ "test.com" ], @@ -85,9 +124,15 @@ "process_path": [ "/usr/bin/curl" ], + "process_path_regex": [ + "^/usr/bin/.+" + ], "package_name": [ "com.termux" ], + "package_name_regex": [ + "^com\\.termux.*" + ], "user": [ "sekai" ], @@ -95,18 +140,49 @@ 1000 ], "clash_mode": "direct", + "network_type": [ + "wifi" + ], + "network_is_expensive": false, + "network_is_constrained": false, + "interface_address": { + "en0": [ + "2000::/3" + ] + }, + "network_interface_address": { + "wifi": [ + "2000::/3" + ] + }, + "default_interface_address": [ + "2000::/3" + ], "wifi_ssid": [ "My WIFI" ], "wifi_bssid": [ "00:00:00:00:00:00" ], + "preferred_by": [ + "tailscale", + "wireguard" + ], + "source_mac_address": [ + "00:11:22:33:44:55" + ], + "source_hostname": [ + "my-device" + ], "rule_set": [ "geoip-cn", "geosite-cn" ], + // deprecated "rule_set_ipcidr_match_source": false, + "rule_set_ip_cidr_match_source": false, "invert": false, + "action": "route", "outbound": "direct" }, { @@ -114,6 +190,7 @@ "mode": "and", "rules": [], "invert": false, + "action": "route", "outbound": "direct" } ] @@ -137,7 +214,7 @@ (`source_port` || `source_port_range`) && `other fields` - Additionally, included rule sets can be considered merged rather than as a single rule sub-item. + Additionally, each branch inside an included rule-set can be considered merged into the outer rule, while different branches keep OR semantics. #### inbound @@ -155,11 +232,25 @@ Username, see each inbound for details. #### protocol -Sniffed protocol, see [Sniff](/configuration/route/sniff/) for details. +Sniffed protocol, see [Protocol Sniff](/configuration/route/sniff/) for details. + +#### client + +!!! question "Since sing-box 1.10.0" + +Sniffed client type, see [Protocol Sniff](/configuration/route/sniff/) for details. #### network -`tcp` or `udp`. +!!! quote "Changes in sing-box 1.13.0" + + Since sing-box 1.13.0, you can match ICMP echo (ping) requests via the new `icmp` network. + + Such traffic originates from `TUN`, `WireGuard`, and `Tailscale` inbounds and can be routed to `Direct`, `WireGuard`, and `Tailscale` outbounds. + +Match network type. + +`tcp`, `udp` or `icmp`. #### domain @@ -181,7 +272,7 @@ Match domain using regular expression. !!! failure "Deprecated in sing-box 1.8.0" - Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-sets). + Geosite is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets). Match geosite. @@ -189,7 +280,7 @@ Match geosite. !!! failure "Deprecated in sing-box 1.8.0" - GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets). + GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets). Match source geoip. @@ -197,7 +288,7 @@ Match source geoip. !!! failure "Deprecated in sing-box 1.8.0" - GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets). + GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets). Match geoip. @@ -253,10 +344,26 @@ Match process name. Match process path. +#### process_path_regex + +!!! question "Since sing-box 1.10.0" + +!!! quote "" + + Only supported on Linux, Windows, and macOS. + +Match process path using regular expression. + #### package_name Match android package name. +#### package_name_regex + +!!! question "Since sing-box 1.14.0" + +Match android package name using regular expression. + #### user !!! quote "" @@ -277,43 +384,149 @@ Match user id. Match Clash mode. -#### wifi_ssid +#### network_type + +!!! question "Since sing-box 1.11.0" !!! quote "" Only supported in graphical clients on Android and Apple platforms. -Match WiFi SSID. +Match network type. -#### wifi_bssid +Available values: `wifi`, `cellular`, `ethernet` and `other`. + +#### network_is_expensive + +!!! question "Since sing-box 1.11.0" !!! quote "" Only supported in graphical clients on Android and Apple platforms. +Match if network is considered Metered (on Android) or considered expensive, +such as Cellular or a Personal Hotspot (on Apple platforms). + +#### network_is_constrained + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Apple platforms. + +Match if network is in Low Data Mode. + +#### interface_address + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported on Linux, Windows, and macOS. + +Match interface address. + +#### network_interface_address + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Matches network interface (same values as `network_type`) address. + +#### default_interface_address + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported on Linux, Windows, and macOS. + +Match default interface address. + +#### wifi_ssid + +Match WiFi SSID. + +See [Wi-Fi State](/configuration/shared/wifi-state/) for details. + +#### wifi_bssid + Match WiFi BSSID. +See [Wi-Fi State](/configuration/shared/wifi-state/) for details. + +#### preferred_by + +!!! question "Since sing-box 1.13.0" + +Match specified outbounds' preferred routes. + +| Type | Match | +|-------------|-----------------------------------------------| +| `tailscale` | Match MagicDNS domains and peers' allowed IPs | +| `wireguard` | Match peers's allowed IPs | + +#### source_mac_address + +!!! question "Since sing-box 1.14.0" + +!!! quote "" + + Only supported on Linux, macOS, or in graphical clients on Android and macOS. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup. + +Match source device MAC address. + +#### source_hostname + +!!! question "Since sing-box 1.14.0" + +!!! quote "" + + Only supported on Linux, macOS, or in graphical clients on Android and macOS. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup. + +Match source device hostname from DHCP leases. + #### rule_set !!! question "Since sing-box 1.8.0" -Match [Rule Set](/configuration/route/#rule_set). +Match [rule-set](/configuration/route/#rule_set). #### rule_set_ipcidr_match_source !!! question "Since sing-box 1.8.0" -Make `ipcidr` in rule sets match the source IP. +!!! failure "Deprecated in sing-box 1.10.0" + + `rule_set_ipcidr_match_source` is renamed to `rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0. + +Make `ip_cidr` in rule-sets match the source IP. + +#### rule_set_ip_cidr_match_source + +!!! question "Since sing-box 1.10.0" + +Make `ip_cidr` in rule-sets match the source IP. #### invert Invert match result. -#### outbound +#### action ==Required== -Tag of the target outbound. +See [Rule Actions](../rule_action/) for details. + +#### outbound + +!!! failure "Deprecated in sing-box 1.11.0" + + Moved to [Rule Action](../rule_action#route). ### Logical Fields diff --git a/docs/configuration/route/rule.zh.md b/docs/configuration/route/rule.zh.md index cba35bc581..d55b565dd9 100644 --- a/docs/configuration/route/rule.zh.md +++ b/docs/configuration/route/rule.zh.md @@ -1,3 +1,36 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.14.0 中的更改" + + :material-plus: [source_mac_address](#source_mac_address) + :material-plus: [source_hostname](#source_hostname) + :material-plus: [package_name_regex](#package_name_regex) + +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [interface_address](#interface_address) + :material-plus: [network_interface_address](#network_interface_address) + :material-plus: [default_interface_address](#default_interface_address) + :material-plus: [preferred_by](#preferred_by) + :material-alert: [network](#network) + +!!! quote "sing-box 1.11.0 中的更改" + + :material-plus: [action](#action) + :material-alert: [outbound](#outbound) + :material-plus: [network_type](#network_type) + :material-plus: [network_is_expensive](#network_is_expensive) + :material-plus: [network_is_constrained](#network_is_constrained) + +!!! quote "sing-box 1.10.0 中的更改" + + :material-plus: [client](#client) + :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) + :material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source) + :material-plus: [process_path_regex](#process_path_regex) + !!! quote "sing-box 1.8.0 中的更改" :material-plus: [rule_set](#rule_set) @@ -31,6 +64,12 @@ "http", "quic" ], + "client": [ + "chromium", + "safari", + "firefox", + "quic-go" + ], "domain": [ "test.com" ], @@ -83,9 +122,15 @@ "process_path": [ "/usr/bin/curl" ], + "process_path_regex": [ + "^/usr/bin/.+" + ], "package_name": [ "com.termux" ], + "package_name_regex": [ + "^com\\.termux.*" + ], "user": [ "sekai" ], @@ -93,18 +138,49 @@ 1000 ], "clash_mode": "direct", + "network_type": [ + "wifi" + ], + "network_is_expensive": false, + "network_is_constrained": false, + "interface_address": { + "en0": [ + "2000::/3" + ] + }, + "network_interface_address": { + "wifi": [ + "2000::/3" + ] + }, + "default_interface_address": [ + "2000::/3" + ], "wifi_ssid": [ "My WIFI" ], "wifi_bssid": [ "00:00:00:00:00:00" ], + "preferred_by": [ + "tailscale", + "wireguard" + ], + "source_mac_address": [ + "00:11:22:33:44:55" + ], + "source_hostname": [ + "my-device" + ], "rule_set": [ "geoip-cn", "geosite-cn" ], + // 已弃用 "rule_set_ipcidr_match_source": false, + "rule_set_ip_cidr_match_source": false, "invert": false, + "action": "route", "outbound": "direct" }, { @@ -112,6 +188,7 @@ "mode": "and", "rules": [], "invert": false, + "action": "route", "outbound": "direct" } ] @@ -124,7 +201,7 @@ 当内容只有一项时,可以忽略 JSON 数组 [] 标签。 -### Default Fields +### 默认字段 !!! note "" @@ -135,7 +212,7 @@ (`source_port` || `source_port_range`) && `other fields` - 另外,引用的规则集可视为被合并,而不是作为一个单独的规则子项。 + 另外,引用规则集中的每个分支都可视为与外层规则合并,不同分支之间仍保持 OR 语义。 #### inbound @@ -155,9 +232,23 @@ 探测到的协议, 参阅 [协议探测](/zh/configuration/route/sniff/)。 +#### client + +!!! question "自 sing-box 1.10.0 起" + +探测到的客户端类型, 参阅 [协议探测](/zh/configuration/route/sniff/)。 + #### network -`tcp` 或 `udp`。 +!!! quote "sing-box 1.13.0 中的更改" + + 自 sing-box 1.13.0 起,您可以通过新的 `icmp` 网络匹配 ICMP 回显(ping)请求。 + + 此类流量源自 `TUN`、`WireGuard` 和 `Tailscale` 入站,并可路由至 `Direct`、`WireGuard` 和 `Tailscale` 出站。 + +匹配网络类型。 + +`tcp`、`udp` 或 `icmp`。 #### domain @@ -179,7 +270,7 @@ !!! failure "已在 sing-box 1.8.0 废弃" - Geosite 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geosite)。 + Geosite 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#迁移-geosite-到规则集)。 匹配 Geosite。 @@ -187,7 +278,7 @@ !!! failure "已在 sing-box 1.8.0 废弃" - GeoIP 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geoip)。 + GeoIP 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#迁移-geoip-到规则集)。 匹配源 GeoIP。 @@ -195,7 +286,7 @@ !!! failure "已在 sing-box 1.8.0 废弃" - GeoIP 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geoip)。 + GeoIP 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#迁移-geoip-到规则集)。 匹配 GeoIP。 @@ -251,10 +342,26 @@ 匹配进程路径。 +#### process_path_regex + +!!! question "自 sing-box 1.10.0 起" + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +使用正则表达式匹配进程路径。 + #### package_name 匹配 Android 应用包名。 +#### package_name_regex + +!!! question "自 sing-box 1.14.0 起" + +使用正则表达式匹配 Android 应用包名。 + #### user !!! quote "" @@ -275,22 +382,112 @@ 匹配 Clash 模式。 -#### wifi_ssid +#### network_type + +!!! question "自 sing-box 1.11.0 起" !!! quote "" 仅在 Android 与 Apple 平台图形客户端中支持。 -匹配 WiFi SSID。 +匹配网络类型。 -#### wifi_bssid +可用值: `wifi`, `cellular`, `ethernet` and `other`. + +#### network_is_expensive + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配如果网络被视为计费 (在 Android) 或被视为昂贵, +像蜂窝网络或个人热点 (在 Apple 平台)。 + +#### network_is_constrained + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Apple 平台图形客户端中支持。 + +匹配如果网络在低数据模式下。 + +#### interface_address + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +匹配接口地址。 + +#### network_interface_address + +!!! question "自 sing-box 1.13.0 起" !!! quote "" 仅在 Android 与 Apple 平台图形客户端中支持。 +匹配网络接口(可用值同 `network_type`)地址。 + +#### default_interface_address + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +匹配默认接口地址。 + +#### wifi_ssid + +匹配 WiFi SSID。 + +参阅 [Wi-Fi 状态](/zh/configuration/shared/wifi-state/)。 + +#### wifi_bssid + 匹配 WiFi BSSID。 +参阅 [Wi-Fi 状态](/zh/configuration/shared/wifi-state/)。 + +#### preferred_by + +!!! question "自 sing-box 1.13.0 起" + +匹配制定出站的首选路由。 + +| 类型 | 匹配 | +|-------------|--------------------------------| +| `tailscale` | 匹配 MagicDNS 域名和对端的 allowed IPs | +| `wireguard` | 匹配对端的 allowed IPs | + +#### source_mac_address + +!!! question "自 sing-box 1.14.0 起" + +!!! quote "" + + 仅支持 Linux、macOS,或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。 + +匹配源设备 MAC 地址。 + +#### source_hostname + +!!! question "自 sing-box 1.14.0 起" + +!!! quote "" + + 仅支持 Linux、macOS,或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。 + +匹配源设备从 DHCP 租约获取的主机名。 + #### rule_set !!! question "自 sing-box 1.8.0 起" @@ -301,17 +498,33 @@ !!! question "自 sing-box 1.8.0 起" -使规则集中的 `ipcidr` 规则匹配源 IP。 +!!! failure "已在 sing-box 1.10.0 废弃" + + `rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 中被移除。 + +使规则集中的 `ip_cidr` 规则匹配源 IP。 + +#### rule_set_ip_cidr_match_source + +!!! question "自 sing-box 1.10.0 起" + +使规则集中的 `ip_cidr` 规则匹配源 IP。 #### invert 反选匹配结果。 -#### outbound +#### action ==必填== -目标出站的标签。 +参阅 [规则动作](../rule_action/)。 + +#### outbound + +!!! failure "已在 sing-box 1.11.0 废弃" + + 已移动到 [规则动作](../rule_action#route). ### 逻辑字段 @@ -329,4 +542,4 @@ ==必填== -包括的规则。 \ No newline at end of file +包括的规则。 diff --git a/docs/configuration/route/rule_action.md b/docs/configuration/route/rule_action.md new file mode 100644 index 0000000000..b362eefe58 --- /dev/null +++ b/docs/configuration/route/rule_action.md @@ -0,0 +1,364 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [bypass](#bypass) + :material-alert: [reject](#reject) + +!!! quote "Changes in sing-box 1.14.0" + + :material-plus: [resolve.disable_optimistic_cache](#disable_optimistic_cache) + :material-plus: [resolve.timeout](#timeout) + :material-plus: [tls_spoof](#tls_spoof) + :material-plus: [tls_spoof_method](#tls_spoof_method) + +!!! quote "Changes in sing-box 1.12.0" + + :material-plus: [tls_fragment](#tls_fragment) + :material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay) + :material-plus: [tls_record_fragment](#tls_record_fragment) + :material-plus: [resolve.disable_cache](#disable_cache) + :material-plus: [resolve.rewrite_ttl](#rewrite_ttl) + :material-plus: [resolve.client_subnet](#client_subnet) + +## Final actions + +### route + +```json +{ + "action": "route", // default + "outbound": "", + + ... // route-options Fields +} +``` + +!!! note "" + + You can ignore the JSON Array [] tag when the content is only one item + +`route` inherits the classic rule behavior of routing connection to the specified outbound. + +#### outbound + +==Required== + +Tag of target outbound. + +#### route-options Fields + +See `route-options` fields below. + +### bypass + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported on Linux with `auto_redirect` enabled. + +```json +{ + "action": "bypass", + "outbound": "", + + ... // route-options Fields +} +``` + +`bypass` bypasses sing-box at the kernel level for auto redirect connections in pre-match. + +For non-auto-redirect connections and already established connections, +if `outbound` is specified, the behavior is the same as `route`; +otherwise, the rule will be skipped. + +#### outbound + +Tag of target outbound. + +If not specified, the rule only matches in [pre-match](/configuration/shared/pre-match/) +from auto redirect, and will be skipped in other contexts. + +#### route-options Fields + +See `route-options` fields below. + +### reject + +!!! quote "Changes in sing-box 1.13.0" + + Since sing-box 1.13.0, you can reject (or directly reply to) ICMP echo (ping) requests using `reject` action. + +```json +{ + "action": "reject", + "method": "default", // default + "no_drop": false +} +``` + +`reject` reject connections + +The specified method is used for reject tun connections if `sniff` action has not been performed yet. + +For non-tun connections and already established connections, will just be closed. + +#### method + +For TCP and UDP connections: + +- `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets. +- `drop`: Drop packets. + +For ICMP echo requests: + +- `default`: Reply with ICMP host unreachable. +- `drop`: Drop packets. +- `reply`: Reply with ICMP echo reply. + +#### no_drop + +If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s. + +Not available when `method` is set to drop. + +### hijack-dns + +```json +{ + "action": "hijack-dns" +} +``` + +`hijack-dns` hijack DNS requests to the sing-box DNS module. + +## Non-final actions + +### route-options + +```json +{ + "action": "route-options", + "override_address": "", + "override_port": 0, + "network_strategy": "", + "fallback_delay": "", + "udp_disable_domain_unmapping": false, + "udp_connect": false, + "udp_timeout": "", + "tls_fragment": false, + "tls_fragment_fallback_delay": "", + "tls_record_fragment": "", + "tls_spoof": "", + "tls_spoof_method": "" +} +``` + +`route-options` set options for routing. + +#### override_address + +Override the connection destination address. + +#### override_port + +Override the connection destination port. + +#### network_strategy + +See [Dial Fields](/configuration/shared/dial/#network_strategy) for details. + +Only take effect if outbound is direct without `outbound.bind_interface`, +`outbound.inet4_bind_address` and `outbound.inet6_bind_address` set. + +#### network_type + +See [Dial Fields](/configuration/shared/dial/#network_type) for details. + +#### fallback_network_type + +See [Dial Fields](/configuration/shared/dial/#fallback_network_type) for details. + +#### fallback_delay + +See [Dial Fields](/configuration/shared/dial/#fallback_delay) for details. + +#### udp_disable_domain_unmapping + +If enabled, for UDP proxy requests addressed to a domain, +the original packet address will be sent in the response instead of the mapped domain. + +This option is used for compatibility with clients that +do not support receiving UDP packets with domain addresses, such as Surge. + +#### udp_connect + +If enabled, attempts to connect UDP connection to the destination instead of listen. + +#### udp_timeout + +Timeout for UDP connections. + +Setting a larger value than the UDP timeout in inbounds will have no effect. + +Default value for protocol sniffed connections: + +| Timeout | Protocol | +|---------|----------------------| +| `10s` | `dns`, `ntp`, `stun` | +| `30s` | `quic`, `dtls` | + +If no protocol is sniffed, the following ports will be recognized as protocols by default: + +| Port | Protocol | +|------|----------| +| 53 | `dns` | +| 123 | `ntp` | +| 443 | `quic` | +| 3478 | `stun` | + +#### tls_fragment + +!!! question "Since sing-box 1.12.0" + +Fragment TLS handshakes to bypass firewalls. + +This feature is intended to circumvent simple firewalls based on **plaintext packet matching**, +and should not be used to circumvent real censorship. + +Due to poor performance, try `tls_record_fragment` first, and only apply to server names known to be blocked. + +On Linux, Apple platforms, (administrator privileges required) Windows, +the wait time can be automatically detected. Otherwise, it will fall back to +waiting for a fixed time specified by `tls_fragment_fallback_delay`. + +In addition, if the actual wait time is less than 20ms, it will also fall back to waiting for a fixed time, +because the target is considered to be local or behind a transparent proxy. + +#### tls_fragment_fallback_delay + +!!! question "Since sing-box 1.12.0" + +The fallback value used when TLS segmentation cannot automatically determine the wait time. + +`500ms` is used by default. + +#### tls_record_fragment + +!!! question "Since sing-box 1.12.0" + +Fragment TLS handshake into multiple TLS records to bypass firewalls. + +#### tls_spoof + +!!! question "Since sing-box 1.14.0" + +==Linux/macOS/Windows only, requires elevated privileges== + +Inject a forged TLS ClientHello carrying this SNI before the real one, +to fool SNI-filtering middleboxes that permit specific hostnames. + +See outbound TLS [`spoof`](/configuration/shared/tls/#spoof) for details +and required privileges. + +#### tls_spoof_method + +!!! question "Since sing-box 1.14.0" + +How the forged segment is rejected by the real server. See outbound TLS +[`spoof_method`](/configuration/shared/tls/#spoof_method) for the full table +of accepted values and platform notes. + +### sniff + +```json +{ + "action": "sniff", + "sniffer": [], + "timeout": "" +} +``` + +`sniff` performs protocol sniffing on connections. + +For deprecated `inbound.sniff` options, it is considered to `sniff()` performed before routing. + +#### sniffer + +Enabled sniffers. + +All sniffers enabled by default. + +Available protocol values an be found on in [Protocol Sniff](../sniff/) + +#### timeout + +Timeout for sniffing. + +`300ms` is used by default. + +### resolve + +```json +{ + "action": "resolve", + "server": "", + "strategy": "", + "disable_cache": false, + "disable_optimistic_cache": false, + "rewrite_ttl": null, + "timeout": "", + "client_subnet": null +} +``` + +`resolve` resolve request destination from domain to IP addresses. + +#### server + +Specifies DNS server tag to use instead of selecting through DNS routing. + +#### strategy + +DNS resolution strategy, available values are: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`. + +`dns.strategy` will be used by default. + +#### disable_cache + +!!! question "Since sing-box 1.12.0" + +Disable cache and save cache in this query. + +#### disable_optimistic_cache + +!!! question "Since sing-box 1.14.0" + +Disable optimistic DNS caching in this query. + +#### rewrite_ttl + +!!! question "Since sing-box 1.12.0" + +Rewrite TTL in DNS responses. + +#### timeout + +!!! question "Since sing-box 1.14.0" + +Override the DNS query timeout for this lookup. + +Will override `dns.timeout`. + +#### client_subnet + +!!! question "Since sing-box 1.12.0" + +Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default. + +If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically. + +Will override `dns.client_subnet`. diff --git a/docs/configuration/route/rule_action.zh.md b/docs/configuration/route/rule_action.zh.md new file mode 100644 index 0000000000..3ef8d8d435 --- /dev/null +++ b/docs/configuration/route/rule_action.zh.md @@ -0,0 +1,354 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [bypass](#bypass) + :material-alert: [reject](#reject) + +!!! quote "sing-box 1.14.0 中的更改" + + :material-plus: [resolve.disable_optimistic_cache](#disable_optimistic_cache) + :material-plus: [resolve.timeout](#timeout) + :material-plus: [tls_spoof](#tls_spoof) + :material-plus: [tls_spoof_method](#tls_spoof_method) + +!!! quote "sing-box 1.12.0 中的更改" + + :material-plus: [tls_fragment](#tls_fragment) + :material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay) + :material-plus: [tls_record_fragment](#tls_record_fragment) + :material-plus: [resolve.disable_cache](#disable_cache) + :material-plus: [resolve.rewrite_ttl](#rewrite_ttl) + :material-plus: [resolve.client_subnet](#client_subnet) + +## 最终动作 + +### route + +```json +{ + "action": "route", // 默认 + "outbound": "", + + ... // route-options 字段 +} +``` + +`route` 继承了将连接路由到指定出站的经典规则动作。 + +#### outbound + +==必填== + +目标出站的标签。 + +#### route-options 字段 + +参阅下方的 `route-options` 字段。 + +### bypass + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅支持 Linux,且需要启用 `auto_redirect`。 + +```json +{ + "action": "bypass", + "outbound": "", + + ... // route-options 字段 +} +``` + +`bypass` 在预匹配中为 auto redirect 连接在内核层面绕过 sing-box。 + +对于非 auto redirect 连接和已建立的连接,如果指定了 `outbound`,行为与 `route` 相同;否则规则将被跳过。 + +#### outbound + +目标出站的标签。 + +如果未指定,规则仅在来自 auto redirect 的[预匹配](/zh/configuration/shared/pre-match/)中匹配,在其他场景中将被跳过。 + +#### route-options 字段 + +参阅下方的 `route-options` 字段。 + +### reject + +!!! quote "sing-box 1.13.0 中的更改" + + 自 sing-box 1.13.0 起,您可以通过 `reject` 动作拒绝(或直接回复)ICMP 回显(ping)请求。 + +```json +{ + "action": "reject", + "method": "default", // 默认 + "no_drop": false +} +``` + +`reject` 拒绝连接。 + +如果尚未执行 `sniff` 操作,则将使用指定方法拒绝 tun 连接。 + +对于非 tun 连接和已建立的连接,将直接关闭。 + +#### method + +对于 TCP 和 UDP 连接: + +- `default`: 对于 TCP 连接回复 RST,对于 UDP 包回复 ICMP 端口不可达。 +- `drop`: 丢弃数据包。 + +对于 ICMP 回显请求: + +- `default`: 回复 ICMP 主机不可达。 +- `drop`: 丢弃数据包。 +- `reply`: 回复以 ICMP 回显应答。 + +#### no_drop + +如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`。 + +当 `method` 设为 `drop` 时不可用。 + +### hijack-dns + +```json +{ + "action": "hijack-dns" +} +``` + +`hijack-dns` 劫持 DNS 请求至 sing-box DNS 模块。 + +## 非最终动作 + +### route-options + +```json +{ + "action": "route-options", + "override_address": "", + "override_port": 0, + "network_strategy": "", + "fallback_delay": "", + "udp_disable_domain_unmapping": false, + "udp_connect": false, + "udp_timeout": "", + "tls_fragment": false, + "tls_fragment_fallback_delay": "", + "tls_record_fragment": false, + "tls_spoof": "", + "tls_spoof_method": "" +} +``` + +!!! note "" + + 当内容只有一项时,可以忽略 JSON 数组 [] 标签 + +`route-options` 为路由设置选项。 + +#### override_address + +覆盖目标地址。 + +#### override_port + +覆盖目标端口。 + +#### network_strategy + +详情参阅 [拨号字段](/zh/configuration/shared/dial/#network_strategy)。 + +仅当出站为 `direct` 且 `outbound.bind_interface`, `outbound.inet4_bind_address` +且 `outbound.inet6_bind_address` 未设置时生效。 + +#### network_type + +详情参阅 [拨号字段](/zh/configuration/shared/dial/#network_type)。 + +#### fallback_network_type + +详情参阅 [拨号字段](/zh/configuration/shared/dial/#fallback_network_type)。 + +#### fallback_delay + +详情参阅 [拨号字段](/zh/configuration/shared/dial/#fallback_delay)。 + +#### udp_disable_domain_unmapping + +如果启用,对于地址为域的 UDP 代理请求,将在响应中发送原始包地址而不是映射的域。 + +此选项用于兼容不支持接收带有域地址的 UDP 包的客户端,如 Surge。 + +#### udp_connect + +如果启用,将尝试将 UDP 连接 connect 到目标而不是 listen。 + +#### udp_timeout + +UDP 连接超时时间。 + +设置比入站 UDP 超时更大的值将无效。 + +已探测协议连接的默认值: + +| 超时 | 协议 | +|-------|----------------------| +| `10s` | `dns`, `ntp`, `stun` | +| `30s` | `quic`, `dtls` | + +如果没有探测到协议,以下端口将默认识别为协议: + +| 端口 | 协议 | +|------|--------| +| 53 | `dns` | +| 123 | `ntp` | +| 443 | `quic` | +| 3478 | `stun` | + +#### tls_fragment + +!!! question "自 sing-box 1.12.0 起" + +通过分段 TLS 握手数据包来绕过防火墙检测。 + +此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。 + +由于性能不佳,请首先尝试 `tls_record_fragment`,且仅应用于已知被阻止的服务器名称。 + +在 Linux、Apple 平台和需要管理员权限的 Windows 系统上,可自动检测等待时间。 +若无法自动检测,将回退使用 `tls_fragment_fallback_delay` 指定的固定等待时间。 + +此外,若实际等待时间小于 20 毫秒,同样会回退至固定等待时间模式,因为此时判定目标处于本地或透明代理之后。 + +#### tls_fragment_fallback_delay + +!!! question "自 sing-box 1.12.0 起" + +当 TLS 分片功能无法自动判定等待时间时使用的回退值。 + +默认使用 `500ms`。 + +#### tls_record_fragment + +!!! question "自 sing-box 1.12.0 起" + +通过分段 TLS 握手数据包到多个 TLS 记录来绕过防火墙检测。 + +#### tls_spoof + +!!! question "自 sing-box 1.14.0 起" + +==仅 Linux/macOS/Windows,需要管理员权限== + +在真实 ClientHello 之前注入携带本字段所指定 SNI 的伪造 TLS ClientHello, +用于欺骗仅放行特定主机名的 SNI 过滤中间盒。 + +详情与所需权限参阅出站 TLS [`spoof`](/zh/configuration/shared/tls/#spoof)。 + +#### tls_spoof_method + +!!! question "自 sing-box 1.14.0 起" + +控制伪造报文被真实服务器拒绝的方式。完整取值表与平台说明参阅出站 TLS +[`spoof_method`](/zh/configuration/shared/tls/#spoof_method)。 + +### sniff + +```json +{ + "action": "sniff", + "sniffer": [], + "timeout": "" +} +``` + +`sniff` 对连接执行协议嗅探。 + +对于已弃用的 `inbound.sniff` 选项,被视为在路由之前执行的 `sniff`。 + +#### sniffer + +启用的探测器。 + +默认启用所有探测器。 + +可用的协议值可以在 [协议嗅探](../sniff/) 中找到。 + +#### timeout + +探测超时时间。 + +默认使用 300ms。 + +### resolve + +```json +{ + "action": "resolve", + "server": "", + "strategy": "", + "disable_cache": false, + "disable_optimistic_cache": false, + "rewrite_ttl": null, + "timeout": "", + "client_subnet": null +} +``` + +`resolve` 将请求的目标从域名解析为 IP 地址。 + +#### server + +指定要使用的 DNS 服务器的标签,而不是通过 DNS 路由进行选择。 + +#### strategy + +DNS 解析策略,可用值有:`prefer_ipv4`、`prefer_ipv6`、`ipv4_only`、`ipv6_only`。 + +默认使用 `dns.strategy`。 + +#### disable_cache + +!!! question "自 sing-box 1.12.0 起" + +在此查询中禁用缓存。 + +#### disable_optimistic_cache + +!!! question "自 sing-box 1.14.0 起" + +在此查询中禁用乐观 DNS 缓存。 + +#### rewrite_ttl + +!!! question "自 sing-box 1.12.0 起" + +重写 DNS 回应中的 TTL。 + +#### timeout + +!!! question "自 sing-box 1.14.0 起" + +覆盖此查询的 DNS 查询超时时间。 + +将覆盖 `dns.timeout`。 + +#### client_subnet + +!!! question "自 sing-box 1.12.0 起" + +默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。 + +如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。 + +将覆盖 `dns.client_subnet`. diff --git a/docs/configuration/route/sniff.md b/docs/configuration/route/sniff.md index 2ba2c25135..0fb386f750 100644 --- a/docs/configuration/route/sniff.md +++ b/docs/configuration/route/sniff.md @@ -1,11 +1,32 @@ +!!! quote "Changes in sing-box 1.10.0" + + :material-plus: QUIC client type detect support for QUIC + :material-plus: Chromium support for QUIC + :material-plus: BitTorrent support + :material-plus: DTLS support + :material-plus: SSH support + :material-plus: RDP support + If enabled in the inbound, the protocol and domain name (if present) of by the connection can be sniffed. #### Supported Protocols -| Network | Protocol | Domain Name | -|:-------:|:--------:|:-----------:| -| TCP | HTTP | Host | -| TCP | TLS | Server Name | -| UDP | QUIC | Server Name | -| UDP | STUN | / | -| TCP/UDP | DNS | / | \ No newline at end of file +| Network | Protocol | Domain Name | Client | +|:-------:|:------------:|:-----------:|:----------------:| +| TCP | `http` | Host | / | +| TCP | `tls` | Server Name | / | +| UDP | `quic` | Server Name | QUIC Client Type | +| UDP | `stun` | / | / | +| TCP/UDP | `dns` | / | / | +| TCP/UDP | `bittorrent` | / | / | +| UDP | `dtls` | / | / | +| TCP | `ssh` | / | SSH Client Name | +| TCP | `rdp` | / | / | +| UDP | `ntp` | / | / | + +| QUIC Client | Type | +|:------------------------:|:----------:| +| Chromium/Cronet | `chromium` | +| Safari/Apple Network API | `safari` | +| Firefox / uquic firefox | `firefox` | +| quic-go / uquic chrome | `quic-go` | diff --git a/docs/configuration/route/sniff.zh.md b/docs/configuration/route/sniff.zh.md index c3cdcc4ee3..8cb9488f9f 100644 --- a/docs/configuration/route/sniff.zh.md +++ b/docs/configuration/route/sniff.zh.md @@ -1,11 +1,32 @@ +!!! quote "sing-box 1.10.0 中的更改" + + :material-plus: QUIC 的 客户端类型探测支持 + :material-plus: QUIC 的 Chromium 支持 + :material-plus: BitTorrent 支持 + :material-plus: DTLS 支持 + :material-plus: SSH 支持 + :material-plus: RDP 支持 + 如果在入站中启用,则可以嗅探连接的协议和域名(如果存在)。 #### 支持的协议 -| 网络 | 协议 | 域名 | -|:-------:|:----:|:-----------:| -| TCP | HTTP | Host | -| TCP | TLS | Server Name | -| UDP | QUIC | Server Name | -| UDP | STUN | / | -| TCP/UDP | DNS | / | \ No newline at end of file +| 网络 | 协议 | 域名 | 客户端 | +|:-------:|:------------:|:-----------:|:----------:| +| TCP | `http` | Host | / | +| TCP | `tls` | Server Name | / | +| UDP | `quic` | Server Name | QUIC 客户端类型 | +| UDP | `stun` | / | / | +| TCP/UDP | `dns` | / | / | +| TCP/UDP | `bittorrent` | / | / | +| UDP | `dtls` | / | / | +| TCP | `ssh` | / | SSH 客户端名称 | +| TCP | `rdp` | / | / | +| UDP | `ntp` | / | / | + +| QUIC 客户端 | 类型 | +|:------------------------:|:----------:| +| Chromium/Cronet | `chromium` | +| Safari/Apple Network API | `safari` | +| Firefox / uquic firefox | `firefox` | +| quic-go / uquic chrome | `quic-go` | diff --git a/docs/configuration/rule-set/adguard.md b/docs/configuration/rule-set/adguard.md new file mode 100644 index 0000000000..c8bd32fa87 --- /dev/null +++ b/docs/configuration/rule-set/adguard.md @@ -0,0 +1,65 @@ +!!! question "Since sing-box 1.10.0" + +sing-box supports some rule-set formats from other projects which cannot be fully translated to sing-box, +currently only AdGuard DNS Filter. + +These formats are not directly supported as source formats, +instead you need to convert them to binary rule-set. + +## Convert + +Use `sing-box rule-set convert --type adguard [--output .srs] .txt` to convert to binary rule-set. + +## Performance + +AdGuard keeps all rules in memory and matches them sequentially, +while sing-box chooses high performance and smaller memory usage. +As a trade-off, you cannot know which rule item is matched. + +## Compatibility + +Almost all rules in [AdGuardSDNSFilter](https://github.com/AdguardTeam/AdGuardSDNSFilter) +and rules in rule-sets listed in [adguard-filter-list](https://github.com/ppfeufer/adguard-filter-list) +are supported. + +## Supported formats + +### AdGuard Filter + +#### Basic rule syntax + +| Syntax | Supported | +|--------|------------------| +| `@@` | :material-check: | +| `\|\|` | :material-check: | +| `\|` | :material-check: | +| `^` | :material-check: | +| `*` | :material-check: | + +#### Host syntax + +| Syntax | Example | Supported | +|-------------|--------------------------|--------------------------| +| Scheme | `https://` | :material-alert: Ignored | +| Domain Host | `example.org` | :material-check: | +| IP Host | `1.1.1.1`, `10.0.0.` | :material-close: | +| Regexp | `/regexp/` | :material-check: | +| Port | `example.org:80` | :material-close: | +| Path | `example.org/path/ad.js` | :material-close: | + +#### Modifier syntax + +| Modifier | Supported | +|-----------------------|--------------------------| +| `$important` | :material-check: | +| `$dnsrewrite=0.0.0.0` | :material-alert: Ignored | +| Any other modifiers | :material-close: | + +### Hosts + +Only items with `0.0.0.0` IP addresses will be accepted. + +### Simple + +When all rule lines are valid domains, they are treated as simple line-by-line domain rules which, +like hosts, only match the exact same domain. \ No newline at end of file diff --git a/docs/configuration/rule-set/adguard.zh.md b/docs/configuration/rule-set/adguard.zh.md new file mode 100644 index 0000000000..82773280c6 --- /dev/null +++ b/docs/configuration/rule-set/adguard.zh.md @@ -0,0 +1,64 @@ +!!! question "自 sing-box 1.10.0 起" + +sing-box 支持其他项目的一些规则集格式,这些格式无法完全转换为 sing-box, +目前只有 AdGuard DNS Filter。 + +这些格式不直接作为源格式支持, +而是需要将它们转换为二进制规则集。 + +## 转换 + +使用 `sing-box rule-set convert --type adguard [--output .srs] .txt` 以转换为二进制规则集。 + +## 性能 + +AdGuard 将所有规则保存在内存中并按顺序匹配, +而 sing-box 选择高性能和较小的内存使用量。 +作为权衡,您无法知道匹配了哪个规则项。 + +## 兼容性 + +[AdGuardSDNSFilter](https://github.com/AdguardTeam/AdGuardSDNSFilter) +中的几乎所有规则以及 [adguard-filter-list](https://github.com/ppfeufer/adguard-filter-list) +中列出的规则集中的规则均受支持。 + +## 支持的格式 + +### AdGuard Filter + +#### 基本规则语法 + +| 语法 | 支持 | +|--------|------------------| +| `@@` | :material-check: | +| `\|\|` | :material-check: | +| `\|` | :material-check: | +| `^` | :material-check: | +| `*` | :material-check: | + +#### 主机语法 + +| 语法 | 示例 | 支持 | +|-------------|--------------------------|--------------------------| +| Scheme | `https://` | :material-alert: Ignored | +| Domain Host | `example.org` | :material-check: | +| IP Host | `1.1.1.1`, `10.0.0.` | :material-close: | +| Regexp | `/regexp/` | :material-check: | +| Port | `example.org:80` | :material-close: | +| Path | `example.org/path/ad.js` | :material-close: | + +#### 描述符语法 + +| 描述符 | 支持 | +|-----------------------|--------------------------| +| `$important` | :material-check: | +| `$dnsrewrite=0.0.0.0` | :material-alert: Ignored | +| 任何其他描述符 | :material-close: | + +### Hosts + +只有 IP 地址为 `0.0.0.0` 的条目将被接受。 + +### 简易 + +当所有行都是有效域时,它们被视为简单的逐行域规则, 与 hosts 一样,只匹配完全相同的域。 \ No newline at end of file diff --git a/docs/configuration/rule-set/headless-rule.md b/docs/configuration/rule-set/headless-rule.md index e766904b5f..81a5e9a0a4 100644 --- a/docs/configuration/rule-set/headless-rule.md +++ b/docs/configuration/rule-set/headless-rule.md @@ -1,3 +1,23 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.14.0" + + :material-plus: [package_name_regex](#package_name_regex) + :material-alert: [query_type](#query_type) + +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [network_interface_address](#network_interface_address) + :material-plus: [default_interface_address](#default_interface_address) + +!!! quote "Changes in sing-box 1.11.0" + + :material-plus: [network_type](#network_type) + :material-plus: [network_is_expensive](#network_is_expensive) + :material-plus: [network_is_constrained](#network_is_constrained) + ### Structure !!! question "Since sing-box 1.8.0" @@ -57,9 +77,28 @@ "process_path": [ "/usr/bin/curl" ], + "process_path_regex": [ + "^/usr/bin/.+" + ], "package_name": [ "com.termux" ], + "package_name_regex": [ + "^com\\.termux.*" + ], + "network_type": [ + "wifi" + ], + "network_is_expensive": false, + "network_is_constrained": false, + "network_interface_address": { + "wifi": [ + "2000::/3" + ] + }, + "default_interface_address": [ + "2000::/3" + ], "wifi_ssid": [ "My WIFI" ], @@ -94,6 +133,20 @@ #### query_type +!!! quote "Changes in sing-box 1.14.0" + + When a DNS rule references this rule-set, this field now also applies + when the DNS rule is matched from an internal domain resolution that + does not target a specific DNS server. In earlier versions, only DNS + queries received from a client evaluated this field. See + [Migration](/migration/#ip_version-and-query_type-behavior-changes-in-dns-rules) + for the full list. + + When a DNS rule references a rule-set containing this field, the DNS + rule is incompatible in the same DNS configuration with Legacy Address + Filter Fields in DNS rules, the Legacy `strategy` DNS rule action + option, and the Legacy `rule_set_ip_cidr_accept_empty` DNS rule item. + DNS query type. Values can be integers or type name strings. #### network @@ -160,10 +213,79 @@ Match process name. Match process path. +#### process_path_regex + +!!! question "Since sing-box 1.10.0" + +!!! quote "" + + Only supported on Linux, Windows, and macOS. + +Match process path using regular expression. + #### package_name Match android package name. +#### package_name_regex + +!!! question "Since sing-box 1.14.0" + +Match android package name using regular expression. + +#### network_type + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Match network type. + +Available values: `wifi`, `cellular`, `ethernet` and `other`. + +#### network_is_expensive + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Match if network is considered Metered (on Android) or considered expensive, +such as Cellular or a Personal Hotspot (on Apple platforms). + +#### network_is_constrained + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Apple platforms. + +Match if network is in Low Data Mode. + +#### network_interface_address + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Matches network interface (same values as `network_type`) address. + +#### default_interface_address + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported on Linux, Windows, and macOS. + +Match default interface address. + #### wifi_ssid !!! quote "" diff --git a/docs/configuration/rule-set/headless-rule.zh.md b/docs/configuration/rule-set/headless-rule.zh.md new file mode 100644 index 0000000000..ad78ffe449 --- /dev/null +++ b/docs/configuration/rule-set/headless-rule.zh.md @@ -0,0 +1,316 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.14.0 中的更改" + + :material-plus: [package_name_regex](#package_name_regex) + :material-alert: [query_type](#query_type) + +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [network_interface_address](#network_interface_address) + :material-plus: [default_interface_address](#default_interface_address) + +!!! quote "sing-box 1.11.0 中的更改" + + :material-plus: [network_type](#network_type) + :material-plus: [network_is_expensive](#network_is_expensive) + :material-plus: [network_is_constrained](#network_is_constrained) + +### 结构 + +!!! question "自 sing-box 1.8.0 起" + +```json +{ + "rules": [ + { + "query_type": [ + "A", + "HTTPS", + 32768 + ], + "network": [ + "tcp" + ], + "domain": [ + "test.com" + ], + "domain_suffix": [ + ".cn" + ], + "domain_keyword": [ + "test" + ], + "domain_regex": [ + "^stun\\..+" + ], + "source_ip_cidr": [ + "10.0.0.0/24", + "192.168.0.1" + ], + "ip_cidr": [ + "10.0.0.0/24", + "192.168.0.1" + ], + "source_port": [ + 12345 + ], + "source_port_range": [ + "1000:2000", + ":3000", + "4000:" + ], + "port": [ + 80, + 443 + ], + "port_range": [ + "1000:2000", + ":3000", + "4000:" + ], + "process_name": [ + "curl" + ], + "process_path": [ + "/usr/bin/curl" + ], + "process_path_regex": [ + "^/usr/bin/.+" + ], + "package_name": [ + "com.termux" + ], + "package_name_regex": [ + "^com\\.termux.*" + ], + "network_type": [ + "wifi" + ], + "network_is_expensive": false, + "network_is_constrained": false, + "network_interface_address": { + "wifi": [ + "2000::/3" + ] + }, + "default_interface_address": [ + "2000::/3" + ], + "wifi_ssid": [ + "My WIFI" + ], + "wifi_bssid": [ + "00:00:00:00:00:00" + ], + "invert": false + }, + { + "type": "logical", + "mode": "and", + "rules": [], + "invert": false + } + ] +} +``` + +!!! note "" + + 当内容只有一项时,可以忽略 JSON 数组 [] 标签。 + +### Default Fields + +!!! note "" + + 默认规则使用以下匹配逻辑: + (`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `ip_cidr`) && + (`port` || `port_range`) && + (`source_port` || `source_port_range`) && + `other fields` + +#### query_type + +!!! quote "sing-box 1.14.0 中的更改" + + 当 DNS 规则引用此规则集时,此字段现在也会在 DNS 规则被未指定具体 + DNS 服务器的内部域名解析匹配时生效。此前只有来自客户端的 DNS 查询 + 才会评估此字段。完整列表参阅 + [迁移指南](/zh/migration/#dns-规则中的-ip_version-和-query_type-行为更改)。 + + 当 DNS 规则引用了包含此字段的规则集时,该 DNS 规则在同一 DNS 配置中 + 不能与旧版地址筛选字段 (DNS 规则)、旧版 DNS 规则动作 `strategy` 选项, + 或旧版 `rule_set_ip_cidr_accept_empty` DNS 规则项共存。 + +DNS 查询类型。值可以为整数或者类型名称字符串。 + +#### network + +`tcp` 或 `udp`。 + +#### domain + +匹配完整域名。 + +#### domain_suffix + +匹配域名后缀。 + +#### domain_keyword + +匹配域名关键字。 + +#### domain_regex + +匹配域名正则表达式。 + +#### source_ip_cidr + +匹配源 IP CIDR。 + +#### ip_cidr + +匹配 IP CIDR。 + +#### source_port + +匹配源端口。 + +#### source_port_range + +匹配源端口范围。 + +#### port + +匹配端口。 + +#### port_range + +匹配端口范围。 + +#### process_name + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS。 + +匹配进程名称。 + +#### process_path + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +匹配进程路径。 + +#### process_path_regex + +!!! question "自 sing-box 1.10.0 起" + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +使用正则表达式匹配进程路径。 + +#### package_name + +匹配 Android 应用包名。 + +#### package_name_regex + +!!! question "自 sing-box 1.14.0 起" + +使用正则表达式匹配 Android 应用包名。 + +#### network_type + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配网络类型。 + +Available values: `wifi`, `cellular`, `ethernet` and `other`. + +#### network_is_expensive + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配如果网络被视为计费 (在 Android) 或被视为昂贵, +像蜂窝网络或个人热点 (在 Apple 平台)。 + +#### network_is_constrained + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Apple 平台图形客户端中支持。 + +匹配如果网络在低数据模式下。 + +#### network_interface_address + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配网络接口(可用值同 `network_type`)地址。 + +#### default_interface_address + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +匹配默认接口地址。 + +#### wifi_ssid + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配 WiFi SSID。 + +#### wifi_bssid + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +#### invert + +反选匹配结果。 + +### 逻辑字段 + +#### type + +`logical` + +#### mode + +==必填== + +`and` 或 `or` + +#### rules + +==必填== + +包括的规则。 \ No newline at end of file diff --git a/docs/configuration/rule-set/index.md b/docs/configuration/rule-set/index.md index ba2f741e4f..c6d4a17c08 100644 --- a/docs/configuration/rule-set/index.md +++ b/docs/configuration/rule-set/index.md @@ -1,48 +1,61 @@ -# Rule Set +!!! quote "Changes in sing-box 1.14.0" + + :material-plus: [http_client](#http_client) + :material-delete-clock: [download_detour](#download_detour) + +!!! quote "Changes in sing-box 1.10.0" + + :material-plus: `type: inline` + +# rule-set !!! question "Since sing-box 1.8.0" ### Structure -```json -{ - "type": "", - "tag": "", - "format": "", - - ... // Typed Fields -} -``` - -#### Local Structure - -```json -{ - "type": "local", - - ... - - "path": "" -} -``` - -#### Remote Structure - -!!! info "" - - Remote rule-set will be cached if `experimental.cache_file.enabled`. - -```json -{ - "type": "remote", - - ..., - - "url": "", - "download_detour": "", - "update_interval": "" -} -``` +=== "Inline" + + !!! question "Since sing-box 1.10.0" + + ```json + { + "type": "inline", // optional + "tag": "", + "rules": [] + } + ``` + +=== "Local File" + + ```json + { + "type": "local", + "tag": "", + "format": "source", // or binary + "path": "" + } + ``` + +=== "Remote File" + + !!! info "" + + Remote rule-set will be cached if `experimental.cache_file.enabled`. + + ```json + { + "type": "remote", + "tag": "", + "format": "source", // or binary + "url": "", + "http_client": "", // or {} + "update_interval": "", + + // Deprecated + + "download_detour": "" + } + ``` ### Fields @@ -50,19 +63,33 @@ ==Required== -Type of Rule Set, `local` or `remote`. +Type of rule-set, `local` or `remote`. #### tag ==Required== -Tag of Rule Set. +Tag of rule-set. + +### Inline Fields + +!!! question "Since sing-box 1.10.0" + +#### rules + +==Required== + +List of [Headless Rule](./headless-rule/). + +### Local or Remote Fields #### format ==Required== -Format of Rule Set, `source` or `binary`. +Format of rule-set file, `source` or `binary`. + +Optional when `path` or `url` uses `json` or `srs` as extension. ### Local Fields @@ -70,7 +97,11 @@ Format of Rule Set, `source` or `binary`. ==Required== -File path of Rule Set. +!!! note "" + + Will be automatically reloaded if file modified since sing-box 1.10.0. + +File path of rule-set. ### Remote Fields @@ -78,16 +109,28 @@ File path of Rule Set. ==Required== -Download URL of Rule Set. +Download URL of rule-set. -#### download_detour +#### http_client -Tag of the outbound to download rule-set. +!!! question "Since sing-box 1.14.0" -Default outbound will be used if empty. +HTTP Client for downloading rule-set. + +See [HTTP Client Fields](/configuration/shared/http-client/) for details. + +Default transport will be used if empty. #### update_interval -Update interval of Rule Set. +Update interval of rule-set. `1d` will be used if empty. + +#### download_detour + +!!! failure "Deprecated in sing-box 1.14.0" + + `download_detour` is deprecated in sing-box 1.14.0 and will be removed in sing-box 1.16.0, use `http_client` instead. + +Tag of the outbound to download rule-set. diff --git a/docs/configuration/rule-set/index.zh.md b/docs/configuration/rule-set/index.zh.md new file mode 100644 index 0000000000..2cd6f93793 --- /dev/null +++ b/docs/configuration/rule-set/index.zh.md @@ -0,0 +1,136 @@ +!!! quote "sing-box 1.14.0 中的更改" + + :material-plus: [http_client](#http_client) + :material-delete-clock: [download_detour](#download_detour) + +!!! quote "sing-box 1.10.0 中的更改" + + :material-plus: `type: inline` + +# 规则集 + +!!! question "自 sing-box 1.8.0 起" + +### 结构 + +=== "内联" + + !!! question "自 sing-box 1.10.0 起" + + ```json + { + "type": "inline", // 可选 + "tag": "", + "rules": [] + } + ``` + +=== "本地文件" + + ```json + { + "type": "local", + "tag": "", + "format": "source", // or binary + "path": "" + } + ``` + +=== "远程文件" + + !!! info "" + + 远程规则集将被缓存如果 `experimental.cache_file.enabled` 已启用。 + + ```json + { + "type": "remote", + "tag": "", + "format": "source", // or binary + "url": "", + "http_client": "", // 或 {} + "update_interval": "", + + // 废弃的 + + "download_detour": "" + } + ``` + +### 字段 + +#### type + +==必填== + +规则集类型, `local` 或 `remote`。 + +#### tag + +==必填== + +规则集的标签。 + +### 内联字段 + +!!! question "自 sing-box 1.10.0 起" + +#### rules + +==必填== + +一组 [无头规则](./headless-rule/). + +### 本地或远程字段 + +#### format + +==必填== + +规则集格式, `source` 或 `binary`。 + +当 `path` 或 `url` 使用 `json` 或 `srs` 作为扩展名时可选。 + +### 本地字段 + +#### path + +==必填== + +!!! note "" + + 自 sing-box 1.10.0 起,文件更改时将自动重新加载。 + +规则集的文件路径。 + +### 远程字段 + +#### url + +==必填== + +规则集的下载 URL。 + +#### http_client + +!!! question "自 sing-box 1.14.0 起" + +用于下载规则集的 HTTP 客户端。 + +参阅 [HTTP 客户端字段](/zh/configuration/shared/http-client/) 了解详情。 + +如果为空,将使用默认传输。 + +#### update_interval + +规则集的更新间隔。 + +默认使用 `1d`。 + +#### download_detour + +!!! failure "已在 sing-box 1.14.0 废弃" + + `download_detour` 已在 sing-box 1.14.0 废弃且将在 sing-box 1.16.0 中被移除,请使用 `http_client` 代替。 + +用于下载规则集的出站的标签。 diff --git a/docs/configuration/rule-set/source-format.md b/docs/configuration/rule-set/source-format.md index ee5e48e04c..47e0e24553 100644 --- a/docs/configuration/rule-set/source-format.md +++ b/docs/configuration/rule-set/source-format.md @@ -1,4 +1,22 @@ -# Source Format +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.14.0" + + :material-plus: version `5` + +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: version `4` + +!!! quote "Changes in sing-box 1.11.0" + + :material-plus: version `3` + +!!! quote "Changes in sing-box 1.10.0" + + :material-plus: version `2` !!! question "Since sing-box 1.8.0" @@ -6,7 +24,7 @@ ```json { - "version": 1, + "version": 3, "rules": [] } ``` @@ -21,10 +39,16 @@ Use `sing-box rule-set compile [--output .srs] .json` to c ==Required== -Version of Rule Set, must be `1`. +Version of rule-set. + +* 1: sing-box 1.8.0: Initial rule-set version. +* 2: sing-box 1.10.0: Optimized memory usages of `domain_suffix` rules in binary rule-sets. +* 3: sing-box 1.11.0: Added `network_type`, `network_is_expensive` and `network_is_constrainted` rule items. +* 4: sing-box 1.13.0: Added `network_interface_address` and `default_interface_address` rule items. +* 5: sing-box 1.14.0: Added `package_name_regex` rule item. #### rules ==Required== -List of [Headless Rule](./headless-rule.md/). +List of [Headless Rule](../headless-rule/). diff --git a/docs/configuration/rule-set/source-format.zh.md b/docs/configuration/rule-set/source-format.zh.md new file mode 100644 index 0000000000..3f7108647b --- /dev/null +++ b/docs/configuration/rule-set/source-format.zh.md @@ -0,0 +1,54 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.14.0 中的更改" + + :material-plus: version `5` + +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: version `4` + +!!! quote "sing-box 1.11.0 中的更改" + + :material-plus: version `3` + +!!! quote "sing-box 1.10.0 中的更改" + + :material-plus: version `2` + +!!! question "自 sing-box 1.8.0 起" + +### 结构 + +```json +{ + "version": 3, + "rules": [] +} +``` + +### 编译 + +使用 `sing-box rule-set compile [--output .srs] .json` 以编译源文件为二进制规则集。 + +### 字段 + +#### version + +==必填== + +规则集版本。 + +* 1: sing-box 1.8.0: 初始规则集版本。 +* 2: sing-box 1.10.0: 优化了二进制规则集中 `domain_suffix` 规则的内存使用。 +* 3: sing-box 1.11.0: 添加了 `network_type`、 `network_is_expensive` 和 `network_is_constrainted` 规则项。 +* 4: sing-box 1.13.0: 添加了 `network_interface_address` 和 `default_interface_address` 规则项。 +* 5: sing-box 1.14.0: 添加了 `package_name_regex` 规则项。 + +#### rules + +==必填== + +一组 [无头规则](../headless-rule/). diff --git a/docs/configuration/service/ccm.md b/docs/configuration/service/ccm.md new file mode 100644 index 0000000000..337cacb10b --- /dev/null +++ b/docs/configuration/service/ccm.md @@ -0,0 +1,131 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.13.0" + +# CCM + +CCM (Claude Code Multiplexer) service is a multiplexing service that allows you to access your local Claude Code subscription remotely through custom tokens. + +It handles OAuth authentication with Claude's API on your local machine while allowing remote Claude Code to authenticate using Auth Tokens via the `ANTHROPIC_AUTH_TOKEN` environment variable. + +### Structure + +```json +{ + "type": "ccm", + + ... // Listen Fields + + "credential_path": "", + "usages_path": "", + "users": [], + "headers": {}, + "detour": "", + "tls": {} +} +``` + +### Listen Fields + +See [Listen Fields](/configuration/shared/listen/) for details. + +### Fields + +#### credential_path + +Path to the Claude Code OAuth credentials file. + +If not specified, defaults to: +- `$CLAUDE_CONFIG_DIR/.credentials.json` if `CLAUDE_CONFIG_DIR` environment variable is set +- `~/.claude/.credentials.json` otherwise + +On macOS, credentials are read from the system keychain first, then fall back to the file if unavailable. + +Refreshed tokens are automatically written back to the same location. + +#### usages_path + +Path to the file for storing aggregated API usage statistics. + +Usage tracking is disabled if not specified. + +When enabled, the service tracks and saves comprehensive statistics including: +- Request counts +- Token usage (input, output, cache read, cache creation) +- Calculated costs in USD based on Claude API pricing + +Statistics are organized by model, context window (200k standard vs 1M premium), and optionally by user when authentication is enabled. + +The statistics file is automatically saved every minute and upon service shutdown. + +#### users + +List of authorized users for token authentication. + +If empty, no authentication is required. + +Object format: + +```json +{ + "name": "", + "token": "" +} +``` + +Object fields: + +- `name`: Username identifier for tracking purposes. +- `token`: Bearer token for authentication. Claude Code authenticates by setting the `ANTHROPIC_AUTH_TOKEN` environment variable to their token value. + +#### headers + +Custom HTTP headers to send to the Claude API. + +These headers will override any existing headers with the same name. + +#### detour + +Outbound tag for connecting to the Claude API. + +#### tls + +TLS configuration, see [TLS](/configuration/shared/tls/#inbound). + +### Example + +#### Server + +```json +{ + "services": [ + { + "type": "ccm", + "listen": "0.0.0.0", + "listen_port": 8080, + "usages_path": "./claude-usages.json", + "users": [ + { + "name": "alice", + "token": "ak-ccm-hello-world" + }, + { + "name": "bob", + "token": "ak-ccm-hello-bob" + } + ] + } + ] +} +``` + +#### Client + +```bash +export ANTHROPIC_BASE_URL="http://127.0.0.1:8080" +export ANTHROPIC_AUTH_TOKEN="ak-ccm-hello-world" + +claude +``` diff --git a/docs/configuration/service/ccm.zh.md b/docs/configuration/service/ccm.zh.md new file mode 100644 index 0000000000..f6490b5e1e --- /dev/null +++ b/docs/configuration/service/ccm.zh.md @@ -0,0 +1,131 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.13.0 起" + +# CCM + +CCM(Claude Code 多路复用器)服务是一个多路复用服务,允许您通过自定义令牌远程访问本地的 Claude Code 订阅。 + +它在本地机器上处理与 Claude API 的 OAuth 身份验证,同时允许远程 Claude Code 通过 `ANTHROPIC_AUTH_TOKEN` 环境变量使用认证令牌进行身份验证。 + +### 结构 + +```json +{ + "type": "ccm", + + ... // 监听字段 + + "credential_path": "", + "usages_path": "", + "users": [], + "headers": {}, + "detour": "", + "tls": {} +} +``` + +### 监听字段 + +参阅 [监听字段](/zh/configuration/shared/listen/) 了解详情。 + +### 字段 + +#### credential_path + +Claude Code OAuth 凭据文件的路径。 + +如果未指定,默认值为: +- 如果设置了 `CLAUDE_CONFIG_DIR` 环境变量,则使用 `$CLAUDE_CONFIG_DIR/.credentials.json` +- 否则使用 `~/.claude/.credentials.json` + +在 macOS 上,首先从系统钥匙串读取凭据,如果不可用则回退到文件。 + +刷新的令牌会自动写回相同位置。 + +#### usages_path + +用于存储聚合 API 使用统计信息的文件路径。 + +如果未指定,使用跟踪将被禁用。 + +启用后,服务会跟踪并保存全面的统计信息,包括: +- 请求计数 +- 令牌使用量(输入、输出、缓存读取、缓存创建) +- 基于 Claude API 定价计算的美元成本 + +统计信息按模型、上下文窗口(200k 标准版 vs 1M 高级版)以及可选的用户(启用身份验证时)进行组织。 + +统计文件每分钟自动保存一次,并在服务关闭时保存。 + +#### users + +用于令牌身份验证的授权用户列表。 + +如果为空,则不需要身份验证。 + +对象格式: + +```json +{ + "name": "", + "token": "" +} +``` + +对象字段: + +- `name`:用于跟踪的用户名标识符。 +- `token`:用于身份验证的 Bearer 令牌。Claude Code 通过设置 `ANTHROPIC_AUTH_TOKEN` 环境变量为其令牌值进行身份验证。 + +#### headers + +发送到 Claude API 的自定义 HTTP 头。 + +这些头会覆盖同名的现有头。 + +#### detour + +用于连接 Claude API 的出站标签。 + +#### tls + +TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#入站)。 + +### 示例 + +#### 服务端 + +```json +{ + "services": [ + { + "type": "ccm", + "listen": "0.0.0.0", + "listen_port": 8080, + "usages_path": "./claude-usages.json", + "users": [ + { + "name": "alice", + "token": "ak-ccm-hello-world" + }, + { + "name": "bob", + "token": "ak-ccm-hello-bob" + } + ] + } + ] +} +``` + +#### 客户端 + +```bash +export ANTHROPIC_BASE_URL="http://127.0.0.1:8080" +export ANTHROPIC_AUTH_TOKEN="ak-ccm-hello-world" + +claude +``` diff --git a/docs/configuration/service/derp.md b/docs/configuration/service/derp.md new file mode 100644 index 0000000000..2925db993e --- /dev/null +++ b/docs/configuration/service/derp.md @@ -0,0 +1,135 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# DERP + +DERP service is a Tailscale DERP server, similar to [derper](https://pkg.go.dev/tailscale.com/cmd/derper). + +### Structure + +```json +{ + "type": "derp", + + ... // Listen Fields + + "tls": {}, + "config_path": "", + "verify_client_endpoint": [], + "verify_client_url": [], + "home": "", + "mesh_with": [], + "mesh_psk": "", + "mesh_psk_file": "", + "stun": {} +} +``` + +### Listen Fields + +See [Listen Fields](/configuration/shared/listen/) for details. + +### Fields + +#### tls + +TLS configuration, see [TLS](/configuration/shared/tls/#inbound). + +#### config_path + +==Required== + +Derper configuration file path. + +Example: `derper.key` + +#### verify_client_endpoint + +Tailscale endpoints tags to verify clients. + +#### verify_client_url + +URL to verify clients. + +Object format: + +```json +{ + "url": "", + + ... // HTTP Client Fields +} +``` + +Setting Array value to a string `__URL__` is equivalent to configuring: + +```json +{ "url": __URL__ } +``` + +#### home + +What to serve at the root path. It may be left empty (the default, for a default homepage), `blank` for a blank page, or a URL to redirect to + +#### mesh_with + +Mesh with other DERP servers. + +Object format: + +```json +{ + "server": "", + "server_port": "", + "host": "", + "tls": {}, + + ... // Dial Fields +} +``` + +Object fields: + +- `server`: **Required** DERP server address. +- `server_port`: **Required** DERP server port. +- `host`: Custom DERP hostname. +- `tls`: [TLS](/configuration/shared/tls/#outbound) +- `Dial Fields`: [Dial Fields](/configuration/shared/dial/) + +#### mesh_psk + +Pre-shared key for DERP mesh. + +#### mesh_psk_file + +Pre-shared key file for DERP mesh. + +#### stun + +STUN server listen options. + +Object format: + +```json +{ + "enabled": true, + + ... // Listen Fields +} +``` + +Object fields: + +- `enabled`: **Required** Enable STUN server. +- `listen`: **Required** STUN server listen address, default to `::`. +- `listen_port`: **Required** STUN server listen port, default to `3478`. +- `other Listen Fields`: [Listen Fields](/configuration/shared/listen/) + +Setting `stun` value to a number `__PORT__` is equivalent to configuring: + +```json +{ "enabled": true, "listen_port": __PORT__ } +``` diff --git a/docs/configuration/service/derp.zh.md b/docs/configuration/service/derp.zh.md new file mode 100644 index 0000000000..01e2ac39fc --- /dev/null +++ b/docs/configuration/service/derp.zh.md @@ -0,0 +1,135 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# DERP + +DERP 服务是一个 Tailscale DERP 服务器,类似于 [derper](https://pkg.go.dev/tailscale.com/cmd/derper)。 + +### 结构 + +```json +{ + "type": "derp", + + ... // 监听字段 + + "tls": {}, + "config_path": "", + "verify_client_endpoint": [], + "verify_client_url": [], + "home": "", + "mesh_with": [], + "mesh_psk": "", + "mesh_psk_file": "", + "stun": {} +} +``` + +### 监听字段 + +参阅 [监听字段](/zh/configuration/shared/listen/) 了解详情。 + +### 字段 + +#### tls + +TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#入站)。 + +#### config_path + +==必填== + +Derper 配置文件路径。 + +示例:`derper.key` + +#### verify_client_endpoint + +用于验证客户端的 Tailscale 端点标签。 + +#### verify_client_url + +用于验证客户端的 URL。 + +对象格式: + +```json +{ + "url": "", + + ... // HTTP 客户端字段 +} +``` + +将数组值设置为字符串 `__URL__` 等同于配置: + +```json +{ "url": __URL__ } +``` + +#### home + +在根路径提供的内容。可以留空(默认值,显示默认主页)、`blank` 显示空白页面,或一个重定向的 URL。 + +#### mesh_with + +与其他 DERP 服务器组网。 + +对象格式: + +```json +{ + "server": "", + "server_port": "", + "host": "", + "tls": {}, + + ... // 拨号字段 +} +``` + +对象字段: + +- `server`:**必填** DERP 服务器地址。 +- `server_port`:**必填** DERP 服务器端口。 +- `host`:自定义 DERP 主机名。 +- `tls`:[TLS](/zh/configuration/shared/tls/#出站) +- `拨号字段`:[拨号字段](/zh/configuration/shared/dial/) + +#### mesh_psk + +DERP 组网的预共享密钥。 + +#### mesh_psk_file + +DERP 组网的预共享密钥文件。 + +#### stun + +STUN 服务器监听选项。 + +对象格式: + +```json +{ + "enabled": true, + + ... // 监听字段 +} +``` + +对象字段: + +- `enabled`:**必填** 启用 STUN 服务器。 +- `listen`:**必填** STUN 服务器监听地址,默认为 `::`。 +- `listen_port`:**必填** STUN 服务器监听端口,默认为 `3478`。 +- `其他监听字段`:[监听字段](/zh/configuration/shared/listen/) + +将 `stun` 值设置为数字 `__PORT__` 等同于配置: + +```json +{ "enabled": true, "listen_port": __PORT__ } +``` \ No newline at end of file diff --git a/docs/configuration/service/hysteria-realm.md b/docs/configuration/service/hysteria-realm.md new file mode 100644 index 0000000000..0fe01f1e7c --- /dev/null +++ b/docs/configuration/service/hysteria-realm.md @@ -0,0 +1,73 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.14.0" + +# Hysteria Realm + +Hysteria Realm is a rendezvous service for Hysteria2 NAT traversal. + +A Hysteria2 server behind NAT registers its STUN-discovered public addresses to a stable realm endpoint; clients query the realm to learn the server's current addresses and perform UDP hole-punching to establish a direct QUIC connection. + +The realm only carries control-plane signaling. Once hole-punching succeeds, all proxy traffic flows directly between client and server. + +### Structure + +```json +{ + "type": "hysteria-realm", + + ... // Listen Fields + + "tls": {}, + + ... // HTTP2 Fields + + "users": [ + { + "name": "", + "token": "", + "max_realms": 0 + } + ] +} +``` + +### Listen Fields + +See [Listen Fields](/configuration/shared/listen/) for details. + +### HTTP2 Fields + +See [HTTP2 Fields](/configuration/shared/http2/) for details. + +### Fields + +#### tls + +TLS configuration, see [TLS](/configuration/shared/tls/#inbound). + +When configured, the realm serves HTTP/2 over TLS; otherwise plain HTTP/1.1. + +#### users + +==Required== + +Authorized users. + +#### users.name + +==Required== + +Username, used in logs and as the quota key. + +#### users.token + +==Required== + +Bearer token presented by Hysteria2 inbounds and outbounds via `Authorization: Bearer `. + +#### users.max_realms + +Maximum number of realm slots this user may hold concurrently. diff --git a/docs/configuration/service/hysteria-realm.zh.md b/docs/configuration/service/hysteria-realm.zh.md new file mode 100644 index 0000000000..c01d744bf4 --- /dev/null +++ b/docs/configuration/service/hysteria-realm.zh.md @@ -0,0 +1,73 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.14.0 起" + +# Hysteria Realm + +Hysteria Realm 是用于 Hysteria2 NAT 穿透的会合服务。 + +位于 NAT 后面的 Hysteria2 服务器将其通过 STUN 发现的公网地址注册到一个稳定的 realm 端点;客户端从 realm 查询服务器当前的地址并执行 UDP 打洞,以建立直连的 QUIC 连接。 + +Realm 只承载控制信令。打洞成功后,所有代理流量在客户端和服务器之间直连传输。 + +### 结构 + +```json +{ + "type": "hysteria-realm", + + ... // 监听字段 + + "tls": {}, + + ... // HTTP2 字段 + + "users": [ + { + "name": "", + "token": "", + "max_realms": 0 + } + ] +} +``` + +### 监听字段 + +参阅 [监听字段](/zh/configuration/shared/listen/) 了解详情。 + +### HTTP2 字段 + +参阅 [HTTP2 字段](/zh/configuration/shared/http2/) 了解详情。 + +### 字段 + +#### tls + +TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#入站)。 + +配置后,realm 将通过 TLS 提供 HTTP/2 服务;否则提供明文 HTTP/1.1。 + +#### users + +==必填== + +授权用户。 + +#### users.name + +==必填== + +用户名,用于日志记录和配额键。 + +#### users.token + +==必填== + +Hysteria2 入站和出站通过 `Authorization: Bearer ` 出示的 Bearer 令牌。 + +#### users.max_realms + +此用户可同时持有的 realm 槽位数量上限。 diff --git a/docs/configuration/service/index.md b/docs/configuration/service/index.md new file mode 100644 index 0000000000..dea66d9712 --- /dev/null +++ b/docs/configuration/service/index.md @@ -0,0 +1,35 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# Service + +### Structure + +```json +{ + "services": [ + { + "type": "", + "tag": "" + } + ] +} +``` + +### Fields + +| Type | Format | +|-------------------|---------------------------------------| +| `ccm` | [CCM](./ccm) | +| `derp` | [DERP](./derp) | +| `hysteria-realm` | [Hysteria Realm](./hysteria-realm) | +| `ocm` | [OCM](./ocm) | +| `resolved` | [Resolved](./resolved) | +| `ssm-api` | [SSM API](./ssm-api) | + +#### tag + +The tag of the endpoint. diff --git a/docs/configuration/service/index.zh.md b/docs/configuration/service/index.zh.md new file mode 100644 index 0000000000..240dce2cd1 --- /dev/null +++ b/docs/configuration/service/index.zh.md @@ -0,0 +1,35 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# 服务 + +### 结构 + +```json +{ + "services": [ + { + "type": "", + "tag": "" + } + ] +} +``` + +### 字段 + +| 类型 | 格式 | +|-------------------|---------------------------------------| +| `ccm` | [CCM](./ccm) | +| `derp` | [DERP](./derp) | +| `hysteria-realm` | [Hysteria Realm](./hysteria-realm) | +| `ocm` | [OCM](./ocm) | +| `resolved` | [Resolved](./resolved) | +| `ssm-api` | [SSM API](./ssm-api) | + +#### tag + +端点的标签。 \ No newline at end of file diff --git a/docs/configuration/service/ocm.md b/docs/configuration/service/ocm.md new file mode 100644 index 0000000000..5fdf2b6b42 --- /dev/null +++ b/docs/configuration/service/ocm.md @@ -0,0 +1,185 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.13.0" + +# OCM + +OCM (OpenAI Codex Multiplexer) service is a multiplexing service that allows you to access your local OpenAI Codex subscription remotely through custom tokens. + +It handles OAuth authentication with OpenAI's API on your local machine while allowing remote clients to authenticate using custom tokens. + +### Structure + +```json +{ + "type": "ocm", + + ... // Listen Fields + + "credential_path": "", + "usages_path": "", + "users": [], + "headers": {}, + "detour": "", + "tls": {} +} +``` + +### Listen Fields + +See [Listen Fields](/configuration/shared/listen/) for details. + +### Fields + +#### credential_path + +Path to the OpenAI OAuth credentials file. + +If not specified, defaults to: +- `$CODEX_HOME/auth.json` if `CODEX_HOME` environment variable is set +- `~/.codex/auth.json` otherwise + +Refreshed tokens are automatically written back to the same location. + +#### usages_path + +Path to the file for storing aggregated API usage statistics. + +Usage tracking is disabled if not specified. + +When enabled, the service tracks and saves comprehensive statistics including: +- Request counts +- Token usage (input, output, cached) +- Calculated costs in USD based on OpenAI API pricing + +Statistics are organized by model and optionally by user when authentication is enabled. + +The statistics file is automatically saved every minute and upon service shutdown. + +#### users + +List of authorized users for token authentication. + +If empty, no authentication is required. + +Object format: + +```json +{ + "name": "", + "token": "" +} +``` + +Object fields: + +- `name`: Username identifier for tracking purposes. +- `token`: Bearer token for authentication. Clients authenticate by setting the `Authorization: Bearer ` header. + +#### headers + +Custom HTTP headers to send to the OpenAI API. + +These headers will override any existing headers with the same name. + +#### detour + +Outbound tag for connecting to the OpenAI API. + +#### tls + +TLS configuration, see [TLS](/configuration/shared/tls/#inbound). + +### Example + +#### Server + +```json +{ + "services": [ + { + "type": "ocm", + "listen": "127.0.0.1", + "listen_port": 8080 + } + ] +} +``` + +#### Client + +Add to `~/.codex/config.toml`: + +```toml +# profile = "ocm" # set as default profile + +[model_providers.ocm] +name = "OCM Proxy" +base_url = "http://127.0.0.1:8080/v1" +supports_websockets = true + +[profiles.ocm] +model_provider = "ocm" +# model = "gpt-5.4" # if the latest model is not yet publicly released +# model_reasoning_effort = "xhigh" +``` + +Then run: + +```bash +codex --profile ocm +``` + +### Example with Authentication + +#### Server + +```json +{ + "services": [ + { + "type": "ocm", + "listen": "0.0.0.0", + "listen_port": 8080, + "usages_path": "./codex-usages.json", + "users": [ + { + "name": "alice", + "token": "sk-ocm-hello-world" + }, + { + "name": "bob", + "token": "sk-ocm-hello-bob" + } + ] + } + ] +} +``` + +#### Client + +Add to `~/.codex/config.toml`: + +```toml +# profile = "ocm" # set as default profile + +[model_providers.ocm] +name = "OCM Proxy" +base_url = "http://127.0.0.1:8080/v1" +supports_websockets = true +experimental_bearer_token = "sk-ocm-hello-world" + +[profiles.ocm] +model_provider = "ocm" +# model = "gpt-5.4" # if the latest model is not yet publicly released +# model_reasoning_effort = "xhigh" +``` + +Then run: + +```bash +codex --profile ocm +``` diff --git a/docs/configuration/service/ocm.zh.md b/docs/configuration/service/ocm.zh.md new file mode 100644 index 0000000000..903940061f --- /dev/null +++ b/docs/configuration/service/ocm.zh.md @@ -0,0 +1,186 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.13.0 起" + +# OCM + +OCM(OpenAI Codex 多路复用器)服务是一个多路复用服务,允许您通过自定义令牌远程访问本地的 OpenAI Codex 订阅。 + +它在本地机器上处理与 OpenAI API 的 OAuth 身份验证,同时允许远程客户端使用自定义令牌进行身份验证。 + +### 结构 + +```json +{ + "type": "ocm", + + ... // 监听字段 + + "credential_path": "", + "usages_path": "", + "users": [], + "headers": {}, + "detour": "", + "tls": {} +} +``` + +### 监听字段 + +参阅 [监听字段](/zh/configuration/shared/listen/) 了解详情。 + +### 字段 + +#### credential_path + +OpenAI OAuth 凭据文件的路径。 + +如果未指定,默认值为: +- 如果设置了 `CODEX_HOME` 环境变量,则使用 `$CODEX_HOME/auth.json` +- 否则使用 `~/.codex/auth.json` + +刷新的令牌会自动写回相同位置。 + +#### usages_path + +用于存储聚合 API 使用统计信息的文件路径。 + +如果未指定,使用跟踪将被禁用。 + +启用后,服务会跟踪并保存全面的统计信息,包括: +- 请求计数 +- 令牌使用量(输入、输出、缓存) +- 基于 OpenAI API 定价计算的美元成本 + +统计信息按模型以及可选的用户(启用身份验证时)进行组织。 + +统计文件每分钟自动保存一次,并在服务关闭时保存。 + +#### users + +用于令牌身份验证的授权用户列表。 + +如果为空,则不需要身份验证。 + +对象格式: + +```json +{ + "name": "", + "token": "" +} +``` + +对象字段: + +- `name`:用于跟踪的用户名标识符。 +- `token`:用于身份验证的 Bearer 令牌。客户端通过设置 `Authorization: Bearer ` 头进行身份验证。 + +#### headers + +发送到 OpenAI API 的自定义 HTTP 头。 + +这些头会覆盖同名的现有头。 + +#### detour + +用于连接 OpenAI API 的出站标签。 + +#### tls + +TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#入站)。 + +### 示例 + +#### 服务端 + +```json +{ + "services": [ + { + "type": "ocm", + "listen": "127.0.0.1", + "listen_port": 8080 + } + ] +} +``` + +#### 客户端 + +在 `~/.codex/config.toml` 中添加: + +```toml +# profile = "ocm" # 设为默认配置 + + +[model_providers.ocm] +name = "OCM Proxy" +base_url = "http://127.0.0.1:8080/v1" +supports_websockets = true + +[profiles.ocm] +model_provider = "ocm" +# model = "gpt-5.4" # 如果最新模型尚未公开发布 +# model_reasoning_effort = "xhigh" +``` + +然后运行: + +```bash +codex --profile ocm +``` + +### 带身份验证的示例 + +#### 服务端 + +```json +{ + "services": [ + { + "type": "ocm", + "listen": "0.0.0.0", + "listen_port": 8080, + "usages_path": "./codex-usages.json", + "users": [ + { + "name": "alice", + "token": "sk-ocm-hello-world" + }, + { + "name": "bob", + "token": "sk-ocm-hello-bob" + } + ] + } + ] +} +``` + +#### 客户端 + +在 `~/.codex/config.toml` 中添加: + +```toml +# profile = "ocm" # 设为默认配置 + +[model_providers.ocm] +name = "OCM Proxy" +base_url = "http://127.0.0.1:8080/v1" +supports_websockets = true +experimental_bearer_token = "sk-ocm-hello-world" + +[profiles.ocm] +model_provider = "ocm" +# model = "gpt-5.4" # 如果最新模型尚未公开发布 +# model_reasoning_effort = "xhigh" +``` + +然后运行: + +```bash +codex --profile ocm +``` diff --git a/docs/configuration/service/resolved.md b/docs/configuration/service/resolved.md new file mode 100644 index 0000000000..47b7e25507 --- /dev/null +++ b/docs/configuration/service/resolved.md @@ -0,0 +1,44 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# Resolved + +Resolved service is a fake systemd-resolved DBUS service to receive DNS settings from other programs +(e.g. NetworkManager) and provide DNS resolution. + +See also: [Resolved DNS Server](/configuration/dns/server/resolved/) + +### Structure + +```json +{ + "type": "resolved", + + ... // Listen Fields +} +``` + +### Listen Fields + +See [Listen Fields](/configuration/shared/listen/) for details. + +### Fields + +#### listen + +==Required== + +Listen address. + +`127.0.0.53` will be used by default. + +#### listen_port + +==Required== + +Listen port. + +`53` will be used by default. diff --git a/docs/configuration/service/resolved.zh.md b/docs/configuration/service/resolved.zh.md new file mode 100644 index 0000000000..b8af4e95ac --- /dev/null +++ b/docs/configuration/service/resolved.zh.md @@ -0,0 +1,44 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# Resolved + +Resolved 服务是一个伪造的 systemd-resolved DBUS 服务,用于从其他程序 +(如 NetworkManager)接收 DNS 设置并提供 DNS 解析。 + +另请参阅:[Resolved DNS 服务器](/zh/configuration/dns/server/resolved/) + +### 结构 + +```json +{ + "type": "resolved", + + ... // 监听字段 +} +``` + +### 监听字段 + +参阅 [监听字段](/zh/configuration/shared/listen/) 了解详情。 + +### 字段 + +#### listen + +==必填== + +监听地址。 + +默认使用 `127.0.0.53`。 + +#### listen_port + +==必填== + +监听端口。 + +默认使用 `53`。 \ No newline at end of file diff --git a/docs/configuration/service/ssm-api.md b/docs/configuration/service/ssm-api.md new file mode 100644 index 0000000000..1ef9f37368 --- /dev/null +++ b/docs/configuration/service/ssm-api.md @@ -0,0 +1,58 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.12.0" + +# SSM API + +SSM API service is a RESTful API server for managing Shadowsocks servers. + +See https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2023-1-shadowsocks-server-management-api-v1.md + +### Structure + +```json +{ + "type": "ssm-api", + + ... // Listen Fields + + "servers": {}, + "cache_path": "", + "tls": {} +} +``` + +### Listen Fields + +See [Listen Fields](/configuration/shared/listen/) for details. + +### Fields + +#### servers + +==Required== + +A mapping Object from HTTP endpoints to [Shadowsocks Inbound](/configuration/inbound/shadowsocks) tags. + +Selected Shadowsocks inbounds must be configured with [managed](/configuration/inbound/shadowsocks#managed) enabled. + +Example: + +```json +{ + "servers": { + "/": "ss-in" + } +} +``` + +#### cache_path + +If set, when the server is about to stop, traffic and user state will be saved to the specified JSON file +to be restored on the next startup. + +#### tls + +TLS configuration, see [TLS](/configuration/shared/tls/#inbound). diff --git a/docs/configuration/service/ssm-api.zh.md b/docs/configuration/service/ssm-api.zh.md new file mode 100644 index 0000000000..fbe45ebb35 --- /dev/null +++ b/docs/configuration/service/ssm-api.zh.md @@ -0,0 +1,58 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# SSM API + +SSM API 服务是一个用于管理 Shadowsocks 服务器的 RESTful API 服务器。 + +参阅 https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2023-1-shadowsocks-server-management-api-v1.md + +### 结构 + +```json +{ + "type": "ssm-api", + + ... // 监听字段 + + "servers": {}, + "cache_path": "", + "tls": {} +} +``` + +### 监听字段 + +参阅 [监听字段](/zh/configuration/shared/listen/) 了解详情。 + +### 字段 + +#### servers + +==必填== + +从 HTTP 端点到 [Shadowsocks 入站](/zh/configuration/inbound/shadowsocks) 标签的映射对象。 + +选定的 Shadowsocks 入站必须配置启用 [managed](/zh/configuration/inbound/shadowsocks#managed)。 + +示例: + +```json +{ + "servers": { + "/": "ss-in" + } +} +``` + +#### cache_path + +如果设置,当服务器即将停止时,流量和用户状态将保存到指定的 JSON 文件中, +以便在下次启动时恢复。 + +#### tls + +TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#入站)。 \ No newline at end of file diff --git a/docs/configuration/shared/certificate-provider/acme.md b/docs/configuration/shared/certificate-provider/acme.md new file mode 100644 index 0000000000..30a5ba8d6d --- /dev/null +++ b/docs/configuration/shared/certificate-provider/acme.md @@ -0,0 +1,160 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.14.0" + + :material-plus: [account_key](#account_key) + :material-plus: [key_type](#key_type) + :material-plus: [profile](#profile) + :material-plus: [http_client](#http_client) + +# ACME + +!!! quote "" + + `with_acme` build tag required. + +### Structure + +```json +{ + "type": "acme", + "tag": "", + + "domain": [], + "data_directory": "", + "default_server_name": "", + "email": "", + "provider": "", + "account_key": "", + "disable_http_challenge": false, + "disable_tls_alpn_challenge": false, + "alternative_http_port": 0, + "alternative_tls_port": 0, + "external_account": { + "key_id": "", + "mac_key": "" + }, + "dns01_challenge": {}, + "key_type": "", + "profile": "", + "http_client": "" // or {} +} +``` + +### Fields + +#### domain + +==Required== + +List of domains. + +#### data_directory + +The directory to store ACME data. + +`$XDG_DATA_HOME/certmagic|$HOME/.local/share/certmagic` will be used if empty. + +#### default_server_name + +Server name to use when choosing a certificate if the ClientHello's ServerName field is empty. + +#### email + +The email address to use when creating or selecting an existing ACME server account. + +#### provider + +The ACME CA provider to use. + +| Value | Provider | +|-------------------------|---------------| +| `letsencrypt (default)` | Let's Encrypt | +| `zerossl` | ZeroSSL | +| `https://...` | Custom | + +When `provider` is `zerossl`, sing-box will automatically request ZeroSSL EAB credentials if `email` is set and +`external_account` is empty. + +When `provider` is `zerossl`, at least one of `external_account`, `email`, or `account_key` is required. + +#### account_key + +!!! question "Since sing-box 1.14.0" + +The PEM-encoded private key of an existing ACME account. + +#### disable_http_challenge + +Disable all HTTP challenges. + +#### disable_tls_alpn_challenge + +Disable all TLS-ALPN challenges + +#### alternative_http_port + +The alternate port to use for the ACME HTTP challenge; if non-empty, this port will be used instead of 80 to spin up a +listener for the HTTP challenge. + +#### alternative_tls_port + +The alternate port to use for the ACME TLS-ALPN challenge; the system must forward 443 to this port for challenge to +succeed. + +#### external_account + +EAB (External Account Binding) contains information necessary to bind or map an ACME account to some other account known +by the CA. + +External account bindings are used to associate an ACME account with an existing account in a non-ACME system, such as +a CA customer database. + +To enable ACME account binding, the CA operating the ACME server needs to provide the ACME client with a MAC key and a +key identifier, using some mechanism outside of ACME. §7.3.4 + +#### external_account.key_id + +The key identifier. + +#### external_account.mac_key + +The MAC key. + +#### dns01_challenge + +ACME DNS01 challenge field. If configured, other challenge methods will be disabled. + +See [DNS01 Challenge Fields](/configuration/shared/dns01_challenge/) for details. + +#### key_type + +!!! question "Since sing-box 1.14.0" + +The private key type to generate for new certificates. + +| Value | Type | +|------------|---------| +| `ed25519` | Ed25519 | +| `p256` | P-256 | +| `p384` | P-384 | +| `rsa2048` | RSA | +| `rsa4096` | RSA | + +#### profile + +!!! question "Since sing-box 1.14.0" + +The ACME profile to use for certificate issuance. + +When empty and `provider` is Let's Encrypt, `shortlived` will be used automatically if any domain is an IP address. + +#### http_client + +!!! question "Since sing-box 1.14.0" + +HTTP Client for all provider HTTP requests. + +See [HTTP Client Fields](/configuration/shared/http-client/) for details. diff --git a/docs/configuration/shared/certificate-provider/acme.zh.md b/docs/configuration/shared/certificate-provider/acme.zh.md new file mode 100644 index 0000000000..e01986d5bc --- /dev/null +++ b/docs/configuration/shared/certificate-provider/acme.zh.md @@ -0,0 +1,157 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.14.0 中的更改" + + :material-plus: [account_key](#account_key) + :material-plus: [key_type](#key_type) + :material-plus: [profile](#profile) + :material-plus: [http_client](#http_client) + +# ACME + +!!! quote "" + + 需要 `with_acme` 构建标签。 + +### 结构 + +```json +{ + "type": "acme", + "tag": "", + + "domain": [], + "data_directory": "", + "default_server_name": "", + "email": "", + "provider": "", + "account_key": "", + "disable_http_challenge": false, + "disable_tls_alpn_challenge": false, + "alternative_http_port": 0, + "alternative_tls_port": 0, + "external_account": { + "key_id": "", + "mac_key": "" + }, + "dns01_challenge": {}, + "key_type": "", + "profile": "", + "http_client": "" // 或 {} +} +``` + +### 字段 + +#### domain + +==必填== + +域名列表。 + +#### data_directory + +ACME 数据存储目录。 + +如果为空则使用 `$XDG_DATA_HOME/certmagic|$HOME/.local/share/certmagic`。 + +#### default_server_name + +如果 ClientHello 的 ServerName 字段为空,则选择证书时要使用的服务器名称。 + +#### email + +创建或选择现有 ACME 服务器帐户时使用的电子邮件地址。 + +#### provider + +要使用的 ACME CA 提供商。 + +| 值 | 提供商 | +|--------------------|---------------| +| `letsencrypt (默认)` | Let's Encrypt | +| `zerossl` | ZeroSSL | +| `https://...` | 自定义 | + +当 `provider` 为 `zerossl` 时,如果设置了 `email` 且未设置 `external_account`, +sing-box 会自动向 ZeroSSL 请求 EAB 凭据。 + +当 `provider` 为 `zerossl` 时,必须至少设置 `external_account`、`email` 或 `account_key` 之一。 + +#### account_key + +!!! question "自 sing-box 1.14.0 起" + +现有 ACME 帐户的 PEM 编码私钥。 + +#### disable_http_challenge + +禁用所有 HTTP 质询。 + +#### disable_tls_alpn_challenge + +禁用所有 TLS-ALPN 质询。 + +#### alternative_http_port + +用于 ACME HTTP 质询的备用端口;如果非空,将使用此端口而不是 80 来启动 HTTP 质询的侦听器。 + +#### alternative_tls_port + +用于 ACME TLS-ALPN 质询的备用端口; 系统必须将 443 转发到此端口以使质询成功。 + +#### external_account + +EAB(外部帐户绑定)包含将 ACME 帐户绑定或映射到 CA 已知的其他帐户所需的信息。 + +外部帐户绑定用于将 ACME 帐户与非 ACME 系统中的现有帐户相关联,例如 CA 客户数据库。 + +为了启用 ACME 帐户绑定,运行 ACME 服务器的 CA 需要使用 ACME 之外的某种机制向 ACME 客户端提供 MAC 密钥和密钥标识符。§7.3.4 + +#### external_account.key_id + +密钥标识符。 + +#### external_account.mac_key + +MAC 密钥。 + +#### dns01_challenge + +ACME DNS01 质询字段。如果配置,将禁用其他质询方法。 + +参阅 [DNS01 质询字段](/zh/configuration/shared/dns01_challenge/)。 + +#### key_type + +!!! question "自 sing-box 1.14.0 起" + +为新证书生成的私钥类型。 + +| 值 | 类型 | +|-----------|----------| +| `ed25519` | Ed25519 | +| `p256` | P-256 | +| `p384` | P-384 | +| `rsa2048` | RSA | +| `rsa4096` | RSA | + +#### profile + +!!! question "自 sing-box 1.14.0 起" + +用于证书签发的 ACME profile。 + +当为空且 `provider` 为 Let's Encrypt 时,如果任意域名为 IP 地址,将自动使用 `shortlived`。 + +#### http_client + +!!! question "自 sing-box 1.14.0 起" + +用于所有提供者 HTTP 请求的 HTTP 客户端。 + +参阅 [HTTP 客户端字段](/zh/configuration/shared/http-client/) 了解详情。 + +所有提供者 HTTP 请求将使用此出站。 diff --git a/docs/configuration/shared/certificate-provider/cloudflare-origin-ca.md b/docs/configuration/shared/certificate-provider/cloudflare-origin-ca.md new file mode 100644 index 0000000000..506def982d --- /dev/null +++ b/docs/configuration/shared/certificate-provider/cloudflare-origin-ca.md @@ -0,0 +1,82 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.14.0" + +# Cloudflare Origin CA + +### Structure + +```json +{ + "type": "cloudflare-origin-ca", + "tag": "", + + "domain": [], + "data_directory": "", + "api_token": "", + "origin_ca_key": "", + "request_type": "", + "requested_validity": 0, + "http_client": "" // or {} +} +``` + +### Fields + +#### domain + +==Required== + +List of domain names or wildcard domain names to include in the certificate. + +#### data_directory + +Root directory used to store the issued certificate, private key, and metadata. + +If empty, sing-box uses the same default data directory as the ACME certificate provider: +`$XDG_DATA_HOME/certmagic` or `$HOME/.local/share/certmagic`. + +#### api_token + +Cloudflare API token used to create the certificate. + +Get or create one in [Cloudflare Dashboard > My Profile > API Tokens](https://dash.cloudflare.com/profile/api-tokens). + +Requires the `Zone / SSL and Certificates / Edit` permission. + +Conflict with `origin_ca_key`. + +#### origin_ca_key + +Cloudflare Origin CA Key. + +Get it in [Cloudflare Dashboard > My Profile > API Tokens > API Keys > Origin CA Key](https://dash.cloudflare.com/profile/api-tokens). + +Conflict with `api_token`. + +#### request_type + +The signature type to request from Cloudflare. + +| Value | Type | +|----------------------|-------------| +| `origin-rsa` | RSA | +| `origin-ecc` | ECDSA P-256 | + +`origin-rsa` is used if empty. + +#### requested_validity + +The requested certificate validity in days. + +Available values: `7`, `30`, `90`, `365`, `730`, `1095`, `5475`. + +`5475` days (15 years) is used if empty. + +#### http_client + +HTTP Client for all provider HTTP requests. + +See [HTTP Client Fields](/configuration/shared/http-client/) for details. diff --git a/docs/configuration/shared/certificate-provider/cloudflare-origin-ca.zh.md b/docs/configuration/shared/certificate-provider/cloudflare-origin-ca.zh.md new file mode 100644 index 0000000000..d526be56a9 --- /dev/null +++ b/docs/configuration/shared/certificate-provider/cloudflare-origin-ca.zh.md @@ -0,0 +1,82 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.14.0 起" + +# Cloudflare Origin CA + +### 结构 + +```json +{ + "type": "cloudflare-origin-ca", + "tag": "", + + "domain": [], + "data_directory": "", + "api_token": "", + "origin_ca_key": "", + "request_type": "", + "requested_validity": 0, + "http_client": "" // 或 {} +} +``` + +### 字段 + +#### domain + +==必填== + +要写入证书的域名或通配符域名列表。 + +#### data_directory + +保存签发证书、私钥和元数据的根目录。 + +如果为空,sing-box 会使用与 ACME 证书提供者相同的默认数据目录: +`$XDG_DATA_HOME/certmagic` 或 `$HOME/.local/share/certmagic`。 + +#### api_token + +用于创建证书的 Cloudflare API Token。 + +可在 [Cloudflare Dashboard > My Profile > API Tokens](https://dash.cloudflare.com/profile/api-tokens) 获取或创建。 + +需要 `Zone / SSL and Certificates / Edit` 权限。 + +与 `origin_ca_key` 冲突。 + +#### origin_ca_key + +Cloudflare Origin CA Key。 + +可在 [Cloudflare Dashboard > My Profile > API Tokens > API Keys > Origin CA Key](https://dash.cloudflare.com/profile/api-tokens) 获取。 + +与 `api_token` 冲突。 + +#### request_type + +向 Cloudflare 请求的签名类型。 + +| 值 | 类型 | +|----------------------|-------------| +| `origin-rsa` | RSA | +| `origin-ecc` | ECDSA P-256 | + +如果为空,使用 `origin-rsa`。 + +#### requested_validity + +请求的证书有效期,单位为天。 + +可用值:`7`、`30`、`90`、`365`、`730`、`1095`、`5475`。 + +如果为空,使用 `5475` 天(15 年)。 + +#### http_client + +用于所有提供者 HTTP 请求的 HTTP 客户端。 + +参阅 [HTTP 客户端字段](/zh/configuration/shared/http-client/) 了解详情。 diff --git a/docs/configuration/shared/certificate-provider/index.md b/docs/configuration/shared/certificate-provider/index.md new file mode 100644 index 0000000000..c493550aaa --- /dev/null +++ b/docs/configuration/shared/certificate-provider/index.md @@ -0,0 +1,32 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.14.0" + +# Certificate Provider + +### Structure + +```json +{ + "certificate_providers": [ + { + "type": "", + "tag": "" + } + ] +} +``` + +### Fields + +| Type | Format | +|--------|------------------| +| `acme` | [ACME](/configuration/shared/certificate-provider/acme) | +| `tailscale` | [Tailscale](/configuration/shared/certificate-provider/tailscale) | +| `cloudflare-origin-ca` | [Cloudflare Origin CA](/configuration/shared/certificate-provider/cloudflare-origin-ca) | + +#### tag + +The tag of the certificate provider. diff --git a/docs/configuration/shared/certificate-provider/index.zh.md b/docs/configuration/shared/certificate-provider/index.zh.md new file mode 100644 index 0000000000..2df4b36387 --- /dev/null +++ b/docs/configuration/shared/certificate-provider/index.zh.md @@ -0,0 +1,32 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.14.0 起" + +# 证书提供者 + +### 结构 + +```json +{ + "certificate_providers": [ + { + "type": "", + "tag": "" + } + ] +} +``` + +### 字段 + +| 类型 | 格式 | +|--------|------------------| +| `acme` | [ACME](/zh/configuration/shared/certificate-provider/acme) | +| `tailscale` | [Tailscale](/zh/configuration/shared/certificate-provider/tailscale) | +| `cloudflare-origin-ca` | [Cloudflare Origin CA](/zh/configuration/shared/certificate-provider/cloudflare-origin-ca) | + +#### tag + +证书提供者的标签。 diff --git a/docs/configuration/shared/certificate-provider/tailscale.md b/docs/configuration/shared/certificate-provider/tailscale.md new file mode 100644 index 0000000000..045f2c5ec5 --- /dev/null +++ b/docs/configuration/shared/certificate-provider/tailscale.md @@ -0,0 +1,27 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.14.0" + +# Tailscale + +### Structure + +```json +{ + "type": "tailscale", + "tag": "ts-cert", + "endpoint": "ts-ep" +} +``` + +### Fields + +#### endpoint + +==Required== + +The tag of the [Tailscale endpoint](/configuration/endpoint/tailscale/) to reuse. + +[MagicDNS and HTTPS](https://tailscale.com/kb/1153/enabling-https) must be enabled in the Tailscale admin console. diff --git a/docs/configuration/shared/certificate-provider/tailscale.zh.md b/docs/configuration/shared/certificate-provider/tailscale.zh.md new file mode 100644 index 0000000000..1987da5084 --- /dev/null +++ b/docs/configuration/shared/certificate-provider/tailscale.zh.md @@ -0,0 +1,27 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.14.0 起" + +# Tailscale + +### 结构 + +```json +{ + "type": "tailscale", + "tag": "ts-cert", + "endpoint": "ts-ep" +} +``` + +### 字段 + +#### endpoint + +==必填== + +要复用的 [Tailscale 端点](/zh/configuration/endpoint/tailscale/) 的标签。 + +必须在 Tailscale 管理控制台中启用 [MagicDNS 和 HTTPS](https://tailscale.com/kb/1153/enabling-https)。 diff --git a/docs/configuration/shared/dial.md b/docs/configuration/shared/dial.md index 8139c7518c..2ef2fbdb6e 100644 --- a/docs/configuration/shared/dial.md +++ b/docs/configuration/shared/dial.md @@ -1,32 +1,75 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.14.0" + + :material-alert: [domain_resolver](#domain_resolver) + +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive) + :material-plus: [tcp_keep_alive](#tcp_keep_alive) + :material-plus: [tcp_keep_alive_interval](#tcp_keep_alive_interval) + :material-plus: [bind_address_no_port](#bind_address_no_port) + +!!! quote "Changes in sing-box 1.12.0" + + :material-plus: [domain_resolver](#domain_resolver) + :material-delete-clock: [domain_strategy](#domain_strategy) + :material-plus: [netns](#netns) + +!!! quote "Changes in sing-box 1.11.0" + + :material-plus: [network_strategy](#network_strategy) + :material-alert: [fallback_delay](#fallback_delay) + :material-alert: [network_type](#network_type) + :material-alert: [fallback_network_type](#fallback_network_type) + ### Structure ```json { - "detour": "upstream-out", - "bind_interface": "en0", - "inet4_bind_address": "0.0.0.0", - "inet6_bind_address": "::", - "routing_mark": 1234, + "detour": "", + "bind_interface": "", + "inet4_bind_address": "", + "inet6_bind_address": "", + "bind_address_no_port": false, + "routing_mark": 0, "reuse_addr": false, - "connect_timeout": "5s", + "netns": "", + "connect_timeout": "", "tcp_fast_open": false, "tcp_multi_path": false, + "disable_tcp_keep_alive": false, + "tcp_keep_alive": "", + "tcp_keep_alive_interval": "", "udp_fragment": false, - "domain_strategy": "prefer_ipv6", - "fallback_delay": "300ms" + + "domain_resolver": "", // or {} + "network_strategy": "", + "network_type": [], + "fallback_network_type": [], + "fallback_delay": "", + + // Deprecated + + "domain_strategy": "" } ``` -### Fields +!!! note "" + + You can ignore the JSON Array [] tag when the content is only one item -| Field | Available Context | -|------------------------------------------------------------------------------------------------------------------------------------------|-------------------| -| `bind_interface` /`*bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open` / `tcp_multi_path` / `udp_fragment` /`connect_timeout` | `detour` not set | +### Fields #### detour The tag of the upstream outbound. +If enabled, all other fields will be ignored. + #### bind_interface The network interface to bind to. @@ -39,6 +82,18 @@ The IPv4 address to bind to. The IPv6 address to bind to. +#### bind_address_no_port + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported on Linux. + +Do not reserve a port when binding to a source address. + +This allows reusing the same source port for multiple connections if the full 4-tuple (source IP, source port, destination IP, destination port) remains unique. + #### routing_mark !!! quote "" @@ -47,10 +102,31 @@ The IPv6 address to bind to. Set netfilter routing mark. +Integers (e.g. `1234`) and string hexadecimals (e.g. `"0x1234"`) are supported. + #### reuse_addr Reuse listener address. +#### netns + +!!! question "Since sing-box 1.12.0" + +!!! quote "" + + Only supported on Linux. + +Set network namespace, name or path. + +#### connect_timeout + +Connect timeout, in golang's Duration format. + +A duration string is a possibly signed sequence of +decimal numbers, each with optional fraction and a unit suffix, +such as "300ms", "-1.5h" or "2h45m". +Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". + #### tcp_fast_open Enable TCP Fast Open. @@ -63,22 +139,131 @@ Enable TCP Fast Open. Enable TCP Multi Path. +#### disable_tcp_keep_alive + +!!! question "Since sing-box 1.13.0" + +Disable TCP keep alive. + +#### tcp_keep_alive + +!!! question "Since sing-box 1.13.0" + + Default value changed from `10m` to `5m`. + +TCP keep alive initial period. + +`5m` will be used by default. + +#### tcp_keep_alive_interval + +!!! question "Since sing-box 1.13.0" + +TCP keep alive interval. + +`75s` will be used by default. + #### udp_fragment Enable UDP fragmentation. -#### connect_timeout +#### domain_resolver -Connect timeout, in golang's Duration format. +!!! warning "" -A duration string is a possibly signed sequence of -decimal numbers, each with optional fraction and a unit suffix, -such as "300ms", "-1.5h" or "2h45m". -Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". + `outbound` DNS rule items are deprecated and will be removed in sing-box 1.14.0, so this item will be required for outbound/endpoints using domain name in server address since sing-box 1.14.0. + +!!! info "" + + `domain_resolver` or `route.default_domain_resolver` is optional when only one DNS server is configured. + +Set domain resolver to use for resolving domain names. + +This option uses the same format as the [route DNS rule action](/configuration/dns/rule_action/#route) without the `action` field. + +Setting this option directly to a string is equivalent to setting `server` of this options. + +| Outbound/Endpoints | Effected domains | +|--------------------|--------------------------| +| `direct` | Domain in request | +| others | Domain in server address | + +#### network_strategy + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms with `auto_detect_interface` enabled. + +Strategy for selecting network interfaces. + +Available values: + +- `default` (default): Connect to default network or networks specified in `network_type` sequentially. +- `hybrid`: Connect to all networks or networks specified in `network_type` concurrently. +- `fallback`: Connect to default network or preferred networks specified in `network_type` concurrently, and try fallback networks when unavailable or timeout. + +For fallback, when preferred interfaces fails or times out, +it will enter a 15s fast fallback state (Connect to all preferred and fallback networks concurrently), +and exit immediately if preferred networks recover. + +Conflicts with `bind_interface`, `inet4_bind_address` and `inet6_bind_address`. + +#### network_type + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms with `auto_detect_interface` enabled. + +Network types to use when using `default` or `hybrid` network strategy or +preferred network types to use when using `fallback` network strategy. + +Available values: `wifi`, `cellular`, `ethernet`, `other`. + +Device's default network is used by default. + +#### fallback_network_type + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms with `auto_detect_interface` enabled. + +Fallback network types when preferred networks are unavailable or timeout when using `fallback` network strategy. + +All other networks expect preferred are used by default. + +#### fallback_delay + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms with `auto_detect_interface` enabled. + +The length of time to wait before spawning a RFC 6555 Fast Fallback connection. + +For `domain_strategy`, is the amount of time to wait for connection to succeed before assuming +that IPv4/IPv6 is misconfigured and falling back to other type of addresses. + +For `network_strategy`, is the amount of time to wait for connection to succeed before falling +back to other interfaces. + +Only take effect when `domain_strategy` or `network_strategy` is set. + +`300ms` is used by default. #### domain_strategy -One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`. +!!! failure "Deprecated in sing-box 1.12.0" + + `domain_strategy` is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-outbound-domain-strategy-option-to-domain-resolver). + +Available values: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`. If set, the requested domain name will be resolved to IP before connect. @@ -87,11 +272,3 @@ If set, the requested domain name will be resolved to IP before connect. | `direct` | Domain in request | Take `inbound.domain_strategy` if not set | | others | Domain in server address | / | -#### fallback_delay - -The length of time to wait before spawning a RFC 6555 Fast Fallback connection. -That is, is the amount of time to wait for connection to succeed before assuming -that IPv4/IPv6 is misconfigured and falling back to other type of addresses. -If zero, a default delay of 300ms is used. - -Only take effect when `domain_strategy` is set. \ No newline at end of file diff --git a/docs/configuration/shared/dial.zh.md b/docs/configuration/shared/dial.zh.md index 300e99ff7a..24388fecfd 100644 --- a/docs/configuration/shared/dial.zh.md +++ b/docs/configuration/shared/dial.zh.md @@ -1,28 +1,68 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.14.0 中的更改" + + :material-alert: [domain_resolver](#domain_resolver) + +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive) + :material-plus: [tcp_keep_alive](#tcp_keep_alive) + :material-plus: [tcp_keep_alive_interval](#tcp_keep_alive_interval) + :material-plus: [bind_address_no_port](#bind_address_no_port) + +!!! quote "sing-box 1.12.0 中的更改" + + :material-plus: [domain_resolver](#domain_resolver) + :material-delete-clock: [domain_strategy](#domain_strategy) + :material-plus: [netns](#netns) + +!!! quote "sing-box 1.11.0 中的更改" + + :material-plus: [network_strategy](#network_strategy) + :material-alert: [fallback_delay](#fallback_delay) + :material-alert: [network_type](#network_type) + :material-alert: [fallback_network_type](#fallback_network_type) + ### 结构 ```json { - "detour": "upstream-out", - "bind_interface": "en0", - "inet4_bind_address": "0.0.0.0", - "inet6_bind_address": "::", - "routing_mark": 1234, + "detour": "", + "bind_interface": "", + "inet4_bind_address": "", + "inet6_bind_address": "", + "bind_address_no_port": false, + "routing_mark": 0, "reuse_addr": false, - "connect_timeout": "5s", + "netns": "", + "connect_timeout": "", "tcp_fast_open": false, "tcp_multi_path": false, + "disable_tcp_keep_alive": false, + "tcp_keep_alive": "", + "tcp_keep_alive_interval": "", "udp_fragment": false, - "domain_strategy": "prefer_ipv6", - "fallback_delay": "300ms" + + "domain_resolver": "", // 或 {} + "network_strategy": "", + "network_type": [], + "fallback_network_type": [], + "fallback_delay": "", + + // 废弃的 + + "domain_strategy": "" } ``` -### 字段 +!!! note "" -| 字段 | 可用上下文 | -|------------------------------------------------------------------------------------------------------------------------------------------|--------------| -| `bind_interface` /`*bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open` / `tcp_mutli_path` / `udp_fragment` /`connect_timeout` | `detour` 未设置 | + 当内容只有一项时,可以忽略 JSON 数组 [] 标签 +### 字段 #### detour @@ -42,6 +82,18 @@ 要绑定的 IPv6 地址。 +#### bind_address_no_port + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅支持 Linux。 + +绑定到源地址时不保留端口。 + +这允许在完整的四元组(源 IP、源端口、目标 IP、目标端口)保持唯一的情况下,为多个连接复用同一源端口。 + #### routing_mark !!! quote "" @@ -50,10 +102,29 @@ 设置 netfilter 路由标记。 +支持数字 (如 `1234`) 和十六进制字符串 (如 `"0x1234"`)。 + #### reuse_addr 重用监听地址。 +#### netns + +!!! question "自 sing-box 1.12.0 起" + +!!! quote "" + + 仅支持 Linux。 + +设置网络命名空间,名称或路径。 + +#### connect_timeout + +连接超时,采用 golang 的 Duration 格式。 + +持续时间字符串是一个可能有符号的序列十进制数,每个都有可选的分数和单位后缀, 例如 "300ms"、"-1.5h" 或 "2h45m"。 +有效时间单位为 "ns"、"us"(或 "µs")、"ms"、"s"、"m"、"h"。 + #### tcp_fast_open 启用 TCP Fast Open。 @@ -66,29 +137,126 @@ 启用 TCP Multi Path。 +#### disable_tcp_keep_alive + +!!! question "自 sing-box 1.13.0 起" + +禁用 TCP keep alive。 + +#### tcp_keep_alive + +!!! question "自 sing-box 1.13.0 起" + + 默认值从 `10m` 更改为 `5m`。 + +TCP keep alive 初始周期。 + +默认使用 `5m`。 + +#### tcp_keep_alive_interval + +!!! question "自 sing-box 1.13.0 起" + +TCP keep alive 间隔。 + +默认使用 `75s`。 + #### udp_fragment 启用 UDP 分段。 -#### connect_timeout +#### domain_resolver -连接超时,采用 golang 的 Duration 格式。 +!!! warning "" -持续时间字符串是一个可能有符号的序列十进制数,每个都有可选的分数和单位后缀, 例如 "300ms"、"-1.5h" 或 "2h45m"。 -有效时间单位为 "ns"、"us"(或 "µs")、"ms"、"s"、"m"、"h"。 + `outbound` DNS 规则项已弃用,且将在 sing-box 1.14.0 中被移除。因此,从 sing-box 1.14.0 版本开始,所有在服务器地址中使用域名的出站/端点均需配置此项。 -#### domain_strategy +!!! info "" -可选值:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。 + 当只有一个 DNS 服务器已配置时,`domain_resolver` 或 `route.default_domain_resolver` 是可选的。 -如果设置,域名将在请求发出之前解析为 IP。 +用于设置解析域名的域名解析器。 + +此选项的格式与 [路由 DNS 规则动作](/zh/configuration/dns/rule_action/#route) 相同,但不包含 `action` 字段。 + +若直接将此选项设置为字符串,则等同于设置该选项的 `server` 字段。 + +| 出站/端点 | 受影响的域名 | +|----------------|---------------------------| +| `direct` | 请求中的域名 | +| 其他类型 | 服务器地址中的域名 | + +#### network_strategy -默认使用 `dns.strategy`。 +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 iOS 平台图形客户端中支持,并且需要 `route.auto_detect_interface`。 + +用于选择网络接口的策略。 + +可用值: + +- `default`(默认值):按顺序连接默认网络或 `network_type` 中指定的网络。 +- `hybrid`:同时连接所有网络或 `network_type` 中指定的网络。 +- `fallback`:同时连接默认网络或 `network_type` 中指定的首选网络,当不可用或超时时尝试回退网络。 + +对于回退模式,当首选接口失败或超时时, +将进入15秒的快速回退状态(同时连接所有首选和回退网络), +如果首选网络恢复,则立即退出。 + +与 `bind_interface`, `bind_inet4_address` 和 `bind_inet6_address` 冲突。 + +#### network_type + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 iOS 平台图形客户端中支持,并且需要 `route.auto_detect_interface`。 + +当使用 `default` 或 `hybrid` 网络策略时要使用的网络类型,或当使用 `fallback` 网络策略时要使用的首选网络类型。 + +可用值:`wifi`, `cellular`, `ethernet`, `other`。 + +默认使用设备默认网络。 + +#### fallback_network_type + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 iOS 平台图形客户端中支持,并且需要 `route.auto_detect_interface`。 + +当使用 `fallback` 网络策略时,在首选网络不可用或超时的情况下要使用的回退网络类型。 + +默认使用除首选网络外的所有其他网络。 #### fallback_delay 在生成 RFC 6555 快速回退连接之前等待的时间长度。 -也就是说,是在假设之前等待 IPv6 成功的时间量如果设置了 "prefer_ipv4",则 IPv6 配置错误并回退到 IPv4。 -如果为零,则使用 300 毫秒的默认延迟。 -仅当 `domain_strategy` 为 `prefer_ipv4` 或 `prefer_ipv6` 时生效。 +对于 `domain_strategy`,是在假设之前等待 IPv6 成功的时间量如果设置了 "prefer_ipv4",则 IPv6 配置错误并回退到 IPv4。 + +对于 `network_strategy`,对于 `network_strategy`,是在回退到其他接口之前等待连接成功的时间。 + +仅当 `domain_strategy` 或 `network_strategy` 已设置时生效。 + +默认使用 `300ms`。 + +#### domain_strategy + +!!! failure "已在 sing-box 1.12.0 废弃" + + `domain_strategy` 已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移出站域名策略选项到域名解析器)。 + +可选值:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。 + +如果设置,域名将在请求发出之前解析为 IP。 + +| 出站 | 受影响的域名 | 默认回退值 | +|----------|-----------|---------------------------| +| `direct` | 请求中的域名 | `inbound.domain_strategy` | +| others | 服务器地址中的域名 | / | \ No newline at end of file diff --git a/docs/configuration/shared/dns01_challenge.md b/docs/configuration/shared/dns01_challenge.md index f9949e169b..0157cb4596 100644 --- a/docs/configuration/shared/dns01_challenge.md +++ b/docs/configuration/shared/dns01_challenge.md @@ -1,13 +1,76 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.14.0" + + :material-plus: [ttl](#ttl) + :material-plus: [propagation_delay](#propagation_delay) + :material-plus: [propagation_timeout](#propagation_timeout) + :material-plus: [resolvers](#resolvers) + :material-plus: [override_domain](#override_domain) + +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [alidns.security_token](#security_token) + :material-plus: [cloudflare.zone_token](#zone_token) + :material-plus: [acmedns](#acmedns) + ### Structure ```json { + "ttl": "", + "propagation_delay": "", + "propagation_timeout": "", + "resolvers": [], + "override_domain": "", "provider": "", - + ... // Provider Fields } ``` +### Fields + +#### ttl + +!!! question "Since sing-box 1.14.0" + +The TTL of the temporary TXT record used for the DNS challenge. + +#### propagation_delay + +!!! question "Since sing-box 1.14.0" + +How long to wait after creating the challenge record before starting propagation checks. + +#### propagation_timeout + +!!! question "Since sing-box 1.14.0" + +The maximum time to wait for the challenge record to propagate. + +Set to `-1` to disable propagation checks. + +#### resolvers + +!!! question "Since sing-box 1.14.0" + +Preferred DNS resolvers to use for DNS propagation checks. + +#### override_domain + +!!! question "Since sing-box 1.14.0" + +Override the domain name used for the DNS challenge record. + +Useful when `_acme-challenge` is delegated to a different zone. + +#### provider + +The DNS provider. See below for provider-specific fields. + ### Provider Fields #### Alibaba Cloud DNS @@ -17,15 +80,47 @@ "provider": "alidns", "access_key_id": "", "access_key_secret": "", - "region_id": "" + "region_id": "", + "security_token": "" } ``` +##### security_token + +!!! question "Since sing-box 1.13.0" + +The Security Token for STS temporary credentials. + #### Cloudflare ```json { "provider": "cloudflare", - "api_token": "" + "api_token": "", + "zone_token": "" } -``` \ No newline at end of file +``` + +##### zone_token + +!!! question "Since sing-box 1.13.0" + +Optional API token with `Zone:Read` permission. + +When provided, allows `api_token` to be scoped to a single zone. + +#### ACME-DNS + +!!! question "Since sing-box 1.13.0" + +```json +{ + "provider": "acmedns", + "username": "", + "password": "", + "subdomain": "", + "server_url": "" +} +``` + +See [ACME-DNS](https://github.com/joohoi/acme-dns) for details. diff --git a/docs/configuration/shared/dns01_challenge.zh.md b/docs/configuration/shared/dns01_challenge.zh.md index c942fef0f1..8c582bb544 100644 --- a/docs/configuration/shared/dns01_challenge.zh.md +++ b/docs/configuration/shared/dns01_challenge.zh.md @@ -1,13 +1,76 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.14.0 中的更改" + + :material-plus: [ttl](#ttl) + :material-plus: [propagation_delay](#propagation_delay) + :material-plus: [propagation_timeout](#propagation_timeout) + :material-plus: [resolvers](#resolvers) + :material-plus: [override_domain](#override_domain) + +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [alidns.security_token](#security_token) + :material-plus: [cloudflare.zone_token](#zone_token) + :material-plus: [acmedns](#acmedns) + ### 结构 ```json { + "ttl": "", + "propagation_delay": "", + "propagation_timeout": "", + "resolvers": [], + "override_domain": "", "provider": "", - + ... // 提供商字段 } ``` +### 字段 + +#### ttl + +!!! question "自 sing-box 1.14.0 起" + +DNS 质询临时 TXT 记录的 TTL。 + +#### propagation_delay + +!!! question "自 sing-box 1.14.0 起" + +创建质询记录后,在开始传播检查前要等待的时间。 + +#### propagation_timeout + +!!! question "自 sing-box 1.14.0 起" + +等待质询记录传播完成的最长时间。 + +设为 `-1` 可禁用传播检查。 + +#### resolvers + +!!! question "自 sing-box 1.14.0 起" + +进行 DNS 传播检查时优先使用的 DNS 解析器。 + +#### override_domain + +!!! question "自 sing-box 1.14.0 起" + +覆盖 DNS 质询记录使用的域名。 + +适用于将 `_acme-challenge` 委托到其他 zone 的场景。 + +#### provider + +DNS 提供商。提供商专有字段见下文。 + ### 提供商字段 #### Alibaba Cloud DNS @@ -17,15 +80,47 @@ "provider": "alidns", "access_key_id": "", "access_key_secret": "", - "region_id": "" + "region_id": "", + "security_token": "" } ``` +##### security_token + +!!! question "自 sing-box 1.13.0 起" + +用于 STS 临时凭证的安全令牌。 + #### Cloudflare ```json { "provider": "cloudflare", - "api_token": "" + "api_token": "", + "zone_token": "" } -``` \ No newline at end of file +``` + +##### zone_token + +!!! question "自 sing-box 1.13.0 起" + +具有 `Zone:Read` 权限的可选 API 令牌。 + +提供后可将 `api_token` 限定到单个区域。 + +#### ACME-DNS + +!!! question "自 sing-box 1.13.0 起" + +```json +{ + "provider": "acmedns", + "username": "", + "password": "", + "subdomain": "", + "server_url": "" +} +``` + +参阅 [ACME-DNS](https://github.com/joohoi/acme-dns)。 diff --git a/docs/configuration/shared/http-client.md b/docs/configuration/shared/http-client.md new file mode 100644 index 0000000000..a0aa9d2308 --- /dev/null +++ b/docs/configuration/shared/http-client.md @@ -0,0 +1,114 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.14.0" + +### Structure + +A string or an object. + +When string, the tag of a shared [HTTP Client](/configuration/shared/http-client/) defined in top-level `http_clients`. + +When object: + +```json +{ + "engine": "", + "version": 0, + "disable_version_fallback": false, + "headers": {}, + + ... // HTTP2 Fields + + "tls": {}, + + ... // Dial Fields +} +``` + +### Fields + +#### engine + +HTTP engine to use. + +Values: + +* `go` (default) +* `apple` + +`apple` uses NSURLSession, only available on Apple platforms. + +!!! warning "" + + Experimental only: due to the high memory overhead of both CGO and Network.framework, + do not use in hot paths on iOS and tvOS. + +Supported fields: + +* `headers` +* `tls.server_name` (must match request host) +* `tls.insecure` +* `tls.min_version` / `tls.max_version` +* `tls.certificate` / `tls.certificate_path` +* `tls.certificate_public_key_sha256` +* Dial Fields + +Unsupported fields: + +* `version` +* `disable_version_fallback` +* HTTP2 Fields +* QUIC Fields +* `tls.engine` +* `tls.alpn` +* `tls.disable_sni` +* `tls.cipher_suites` +* `tls.curve_preferences` +* `tls.client_certificate` / `tls.client_certificate_path` / `tls.client_key` / `tls.client_key_path` +* `tls.fragment` / `tls.record_fragment` +* `tls.kernel_tx` / `tls.kernel_rx` +* `tls.ech` +* `tls.utls` +* `tls.reality` + +#### version + +HTTP version. + +Available values: `1`, `2`, `3`. + +`2` is used by default. + +When `3`, [HTTP2 Fields](#http2-fields) are replaced by [QUIC Fields](#quic-fields). + +#### disable_version_fallback + +Disable automatic fallback to lower HTTP version. + +#### headers + +Custom HTTP headers. + +`Host` header is used as request host. + +### HTTP2 Fields + +When `version` is `2` (default). + +See [HTTP2 Fields](/configuration/shared/http2/) for details. + +### QUIC Fields + +When `version` is `3`. + +See [QUIC Fields](/configuration/shared/quic/) for details. + +### TLS Fields + +See [TLS](/configuration/shared/tls/#outbound) for details. + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/shared/http-client.zh.md b/docs/configuration/shared/http-client.zh.md new file mode 100644 index 0000000000..5c05968ad4 --- /dev/null +++ b/docs/configuration/shared/http-client.zh.md @@ -0,0 +1,114 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.14.0 起" + +### 结构 + +字符串或对象。 + +当为字符串时,为顶层 `http_clients` 中定义的共享 [HTTP 客户端](/zh/configuration/shared/http-client/) 的标签。 + +当为对象时: + +```json +{ + "engine": "", + "version": 0, + "disable_version_fallback": false, + "headers": {}, + + ... // HTTP2 字段 + + "tls": {}, + + ... // 拨号字段 +} +``` + +### 字段 + +#### engine + +要使用的 HTTP 引擎。 + +可用值: + +* `go`(默认) +* `apple` + +`apple` 使用 NSURLSession,仅在 Apple 平台可用。 + +!!! warning "" + + 仅供实验用途:由于 CGO 和 Network.framework 占用的内存都很多, + 不应在 iOS 和 tvOS 的热路径中使用。 + +支持的字段: + +* `headers` +* `tls.server_name`(必须与请求主机匹配) +* `tls.insecure` +* `tls.min_version` / `tls.max_version` +* `tls.certificate` / `tls.certificate_path` +* `tls.certificate_public_key_sha256` +* 拨号字段 + +不支持的字段: + +* `version` +* `disable_version_fallback` +* HTTP2 字段 +* QUIC 字段 +* `tls.engine` +* `tls.alpn` +* `tls.disable_sni` +* `tls.cipher_suites` +* `tls.curve_preferences` +* `tls.client_certificate` / `tls.client_certificate_path` / `tls.client_key` / `tls.client_key_path` +* `tls.fragment` / `tls.record_fragment` +* `tls.kernel_tx` / `tls.kernel_rx` +* `tls.ech` +* `tls.utls` +* `tls.reality` + +#### version + +HTTP 版本。 + +可用值:`1`、`2`、`3`。 + +默认使用 `2`。 + +当为 `3` 时,[HTTP2 字段](#http2-字段) 替换为 [QUIC 字段](#quic-字段)。 + +#### disable_version_fallback + +禁用自动回退到更低的 HTTP 版本。 + +#### headers + +自定义 HTTP 标头。 + +`Host` 标头用作请求主机。 + +### HTTP2 字段 + +当 `version` 为 `2`(默认)时。 + +参阅 [HTTP2 字段](/zh/configuration/shared/http2/) 了解详情。 + +### QUIC 字段 + +当 `version` 为 `3` 时。 + +参阅 [QUIC 字段](/zh/configuration/shared/quic/) 了解详情。 + +### TLS 字段 + +参阅 [TLS](/zh/configuration/shared/tls/#出站) 了解详情。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。 diff --git a/docs/configuration/shared/http2.md b/docs/configuration/shared/http2.md new file mode 100644 index 0000000000..e0e0afb473 --- /dev/null +++ b/docs/configuration/shared/http2.md @@ -0,0 +1,43 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.14.0" + +### Structure + +```json +{ + "idle_timeout": "", + "keep_alive_period": "", + "stream_receive_window": "", + "connection_receive_window": "", + "max_concurrent_streams": 0 +} +``` + +### Fields + +#### idle_timeout + +Idle connection timeout, in golang's Duration format. + +#### keep_alive_period + +Keep alive period, in golang's Duration format. + +#### stream_receive_window + +HTTP2 stream-level flow-control receive window size. + +Accepts memory size format, e.g. `"64 MB"`. + +#### connection_receive_window + +HTTP2 connection-level flow-control receive window size. + +Accepts memory size format, e.g. `"64 MB"`. + +#### max_concurrent_streams + +Maximum concurrent streams per connection. diff --git a/docs/configuration/shared/http2.zh.md b/docs/configuration/shared/http2.zh.md new file mode 100644 index 0000000000..344e865a55 --- /dev/null +++ b/docs/configuration/shared/http2.zh.md @@ -0,0 +1,43 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.14.0 起" + +### 结构 + +```json +{ + "idle_timeout": "", + "keep_alive_period": "", + "stream_receive_window": "", + "connection_receive_window": "", + "max_concurrent_streams": 0 +} +``` + +### 字段 + +#### idle_timeout + +空闲连接超时,采用 golang 的 Duration 格式。 + +#### keep_alive_period + +Keep alive 周期,采用 golang 的 Duration 格式。 + +#### stream_receive_window + +HTTP2 流级别流控接收窗口大小。 + +接受内存大小格式,例如 `"64 MB"`。 + +#### connection_receive_window + +HTTP2 连接级别流控接收窗口大小。 + +接受内存大小格式,例如 `"64 MB"`。 + +#### max_concurrent_streams + +每个连接的最大并发流数。 diff --git a/docs/configuration/shared/listen.md b/docs/configuration/shared/listen.md index ae3ed6a403..55325564a4 100644 --- a/docs/configuration/shared/listen.md +++ b/docs/configuration/shared/listen.md @@ -1,33 +1,58 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive) + :material-alert: [tcp_keep_alive](#tcp_keep_alive) + +!!! quote "Changes in sing-box 1.12.0" + + :material-plus: [netns](#netns) + :material-plus: [bind_interface](#bind_interface) + :material-plus: [routing_mark](#routing_mark) + :material-plus: [reuse_addr](#reuse_addr) + +!!! quote "Changes in sing-box 1.11.0" + + :material-delete-clock: [sniff](#sniff) + :material-delete-clock: [sniff_override_destination](#sniff_override_destination) + :material-delete-clock: [sniff_timeout](#sniff_timeout) + :material-delete-clock: [domain_strategy](#domain_strategy) + :material-delete-clock: [udp_disable_domain_unmapping](#udp_disable_domain_unmapping) + ### Structure ```json { - "listen": "::", - "listen_port": 5353, + "listen": "", + "listen_port": 0, + "bind_interface": "", + "routing_mark": 0, + "reuse_addr": false, + "netns": "", "tcp_fast_open": false, "tcp_multi_path": false, + "disable_tcp_keep_alive": false, + "tcp_keep_alive": "", + "tcp_keep_alive_interval": "", "udp_fragment": false, - "udp_timeout": "5m", - "detour": "another-in", + "udp_timeout": "", + "detour": "", + + // Deprecated + "sniff": false, "sniff_override_destination": false, - "sniff_timeout": "300ms", - "domain_strategy": "prefer_ipv6", + "sniff_timeout": "", + "domain_strategy": "", "udp_disable_domain_unmapping": false } ``` ### Fields -| Field | Available Context | -|--------------------------------|---------------------------------------------------------| -| `listen` | Needs to listen on TCP or UDP. | -| `listen_port` | Needs to listen on TCP or UDP. | -| `tcp_fast_open` | Needs to listen on TCP. | -| `tcp_multi_path` | Needs to listen on TCP. | -| `udp_timeout` | Needs to assemble UDP connections. | -| `udp_disable_domain_unmapping` | Needs to listen on UDP and accept domain UDP addresses. | - #### listen ==Required== @@ -38,6 +63,40 @@ Listen address. Listen port. +#### bind_interface + +!!! question "Since sing-box 1.12.0" + +The network interface to bind to. + +#### routing_mark + +!!! question "Since sing-box 1.12.0" + +!!! quote "" + + Only supported on Linux. + +Set netfilter routing mark. + +Integers (e.g. `1234`) and string hexadecimals (e.g. `"0x1234"`) are supported. + +#### reuse_addr + +!!! question "Since sing-box 1.12.0" + +Reuse listener address. + +#### netns + +!!! question "Since sing-box 1.12.0" + +!!! quote "" + + Only supported on Linux. + +Set network namespace, name or path. + #### tcp_fast_open Enable TCP Fast Open. @@ -50,15 +109,37 @@ Enable TCP Fast Open. Enable TCP Multi Path. +#### disable_tcp_keep_alive + +!!! question "Since sing-box 1.13.0" + +Disable TCP keep alive. + +#### tcp_keep_alive + +!!! question "Since sing-box 1.13.0" + + Default value changed from `10m` to `5m`. + +TCP keep alive initial period. + +`5m` will be used by default. + +#### tcp_keep_alive_interval + +TCP keep alive interval. + +`75s` will be used by default. + #### udp_fragment Enable UDP fragmentation. #### udp_timeout -UDP NAT expiration time in seconds. +UDP NAT expiration time. -`5m` is used by default. +`5m` will be used by default. #### detour @@ -68,24 +149,40 @@ Requires target inbound support, see [Injectable](/configuration/inbound/#fields #### sniff +!!! failure "Deprecated in sing-box 1.11.0" + + Inbound fields are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-inbound-fields-to-rule-actions). + Enable sniffing. See [Protocol Sniff](/configuration/route/sniff/) for details. #### sniff_override_destination +!!! failure "Deprecated in sing-box 1.11.0" + + Inbound fields are deprecated and will be removed in sing-box 1.13.0. + Override the connection destination address with the sniffed domain. If the domain name is invalid (like tor), this will not work. #### sniff_timeout +!!! failure "Deprecated in sing-box 1.11.0" + + Inbound fields are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-inbound-fields-to-rule-actions). + Timeout for sniffing. -300ms is used by default. +`300ms` is used by default. #### domain_strategy +!!! failure "Deprecated in sing-box 1.11.0" + + Inbound fields are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-inbound-fields-to-rule-actions). + One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`. If set, the requested domain name will be resolved to IP before routing. @@ -94,6 +191,10 @@ If `sniff_override_destination` is in effect, its value will be taken as a fallb #### udp_disable_domain_unmapping +!!! failure "Deprecated in sing-box 1.11.0" + + Inbound fields are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-inbound-fields-to-rule-actions). + If enabled, for UDP proxy requests addressed to a domain, the original packet address will be sent in the response instead of the mapped domain. diff --git a/docs/configuration/shared/listen.zh.md b/docs/configuration/shared/listen.zh.md index 398c98c53d..0afcbc46b8 100644 --- a/docs/configuration/shared/listen.zh.md +++ b/docs/configuration/shared/listen.zh.md @@ -1,32 +1,56 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive) + :material-alert: [tcp_keep_alive](#tcp_keep_alive) + +!!! quote "sing-box 1.12.0 中的更改" + + :material-plus: [netns](#netns) + :material-plus: [bind_interface](#bind_interface) + :material-plus: [routing_mark](#routing_mark) + :material-plus: [reuse_addr](#reuse_addr) + +!!! quote "sing-box 1.11.0 中的更改" + + :material-delete-clock: [sniff](#sniff) + :material-delete-clock: [sniff_override_destination](#sniff_override_destination) + :material-delete-clock: [sniff_timeout](#sniff_timeout) + :material-delete-clock: [domain_strategy](#domain_strategy) + :material-delete-clock: [udp_disable_domain_unmapping](#udp_disable_domain_unmapping) + ### 结构 ```json { - "listen": "::", - "listen_port": 5353, + "listen": "", + "listen_port": 0, + "bind_interface": "", + "routing_mark": 0, + "reuse_addr": false, + "netns": "", "tcp_fast_open": false, "tcp_multi_path": false, + "disable_tcp_keep_alive": false, + "tcp_keep_alive": "", + "tcp_keep_alive_interval": "", "udp_fragment": false, - "udp_timeout": "5m", - "detour": "another-in", + "udp_timeout": "", + "detour": "", + + // 废弃的 + "sniff": false, "sniff_override_destination": false, - "sniff_timeout": "300ms", - "domain_strategy": "prefer_ipv6", + "sniff_timeout": "", + "domain_strategy": "", "udp_disable_domain_unmapping": false } ``` - -| 字段 | 可用上下文 | -|------------------|-----------------| -| `listen` | 需要监听 TCP 或 UDP。 | -| `listen_port` | 需要监听 TCP 或 UDP。 | -| `tcp_fast_open` | 需要监听 TCP。 | -| `tcp_multi_path` | 需要监听 TCP。 | -| `udp_timeout` | 需要组装 UDP 连接。 | -| - ### 字段 #### listen @@ -39,6 +63,40 @@ 监听端口。 +#### bind_interface + +!!! question "自 sing-box 1.12.0 起" + +要绑定到的网络接口。 + +#### routing_mark + +!!! question "自 sing-box 1.12.0 起" + +!!! quote "" + + 仅支持 Linux。 + +设置 netfilter 路由标记。 + +支持数字 (如 `1234`) 和十六进制字符串 (如 `"0x1234"`)。 + +#### reuse_addr + +!!! question "自 sing-box 1.12.0 起" + +重用监听地址。 + +#### netns + +!!! question "自 sing-box 1.12.0 起" + +!!! quote "" + + 仅支持 Linux。 + +设置网络命名空间,名称或路径。 + #### tcp_fast_open 启用 TCP Fast Open。 @@ -51,13 +109,35 @@ 启用 TCP Multi Path。 +#### disable_tcp_keep_alive + +!!! question "自 sing-box 1.13.0 起" + +禁用 TCP keep alive。 + +#### tcp_keep_alive + +!!! question "自 sing-box 1.13.0 起" + + 默认值从 `10m` 更改为 `5m`。 + +TCP keep alive 初始周期。 + +默认使用 `5m`。 + +#### tcp_keep_alive_interval + +TCP keep alive 间隔。 + +默认使用 `75s`。 + #### udp_fragment 启用 UDP 分段。 #### udp_timeout -UDP NAT 过期时间,以秒为单位。 +UDP NAT 过期时间。 默认使用 `5m`。 @@ -65,28 +145,44 @@ UDP NAT 过期时间,以秒为单位。 如果设置,连接将被转发到指定的入站。 -需要目标入站支持,参阅 [注入支持](/zh/configuration/inbound/#_3)。 +需要目标入站支持,参阅 [注入支持](/zh/configuration/inbound/#字段)。 #### sniff +!!! failure "已在 sing-box 1.11.0 废弃" + + 入站字段已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移旧的入站字段到规则动作). + 启用协议探测。 参阅 [协议探测](/zh/configuration/route/sniff/) #### sniff_override_destination +!!! failure "已在 sing-box 1.11.0 废弃" + + 入站字段已废弃且将在 sing-box 1.12.0 中被移除。 + 用探测出的域名覆盖连接目标地址。 如果域名无效(如 Tor),将不生效。 #### sniff_timeout +!!! failure "已在 sing-box 1.11.0 废弃" + + 入站字段已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移旧的入站字段到规则动作). + 探测超时时间。 默认使用 300ms。 #### domain_strategy +!!! failure "已在 sing-box 1.11.0 废弃" + + 入站字段已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移旧的入站字段到规则动作). + 可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。 如果设置,请求的域名将在路由之前解析为 IP。 @@ -95,6 +191,10 @@ UDP NAT 过期时间,以秒为单位。 #### udp_disable_domain_unmapping +!!! failure "已在 sing-box 1.11.0 废弃" + + 入站字段已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移旧的入站字段到规则动作). + 如果启用,对于地址为域的 UDP 代理请求,将在响应中发送原始包地址而不是映射的域。 此选项用于兼容不支持接收带有域地址的 UDP 包的客户端,如 Surge。 diff --git a/docs/configuration/shared/neighbor.md b/docs/configuration/shared/neighbor.md new file mode 100644 index 0000000000..a0d5f36a7d --- /dev/null +++ b/docs/configuration/shared/neighbor.md @@ -0,0 +1,51 @@ +--- +icon: material/lan +--- + +# Neighbor Resolution + +Match LAN devices by MAC address and hostname using +[`source_mac_address`](/configuration/route/rule/#source_mac_address) and +[`source_hostname`](/configuration/route/rule/#source_hostname) rule items. + +Neighbor resolution is automatically enabled when these rule items exist +or when a [local DNS server](/configuration/dns/server/local/) sets +[neighbor_domain](/configuration/dns/server/local/#neighbor_domain). +Use [`route.find_neighbor`](/configuration/route/#find_neighbor) to force enable it for logging without rules. + +## Linux + +Works natively. No special setup required. + +Hostname resolution requires DHCP lease files, +automatically detected from common DHCP servers (dnsmasq, odhcpd, ISC dhcpd, Kea). +Custom paths can be set via [`route.dhcp_lease_files`](/configuration/route/#dhcp_lease_files). + +## Android + +!!! quote "" + + Only supported in graphical clients. + +Requires Android 11 or above and ROOT. + +Must use [VPNHotspot](https://github.com/Mygod/VPNHotspot) to share the VPN connection. +ROM built-in features like "Use VPN for connected devices" can share VPN +but cannot provide MAC address or hostname information. + +Set **IP Masquerade Mode** to **None** in VPNHotspot settings. + +Only route/DNS rules are supported. TUN include/exclude routes are not supported. + +### Hostname Visibility + +Hostname is only visible in sing-box if it is visible in VPNHotspot. +For Apple devices, change **Private Wi-Fi Address** from **Rotating** to **Fixed** in the Wi-Fi settings +of the connected network. Non-Apple devices are always visible. + +## macOS + +Requires the standalone version (macOS system extension). +The App Store version can share the VPN as a hotspot but does not support MAC address or hostname reading. + +See [VPN Hotspot](/manual/misc/vpn-hotspot/#macos) for Internet Sharing setup. diff --git a/docs/configuration/shared/neighbor.zh.md b/docs/configuration/shared/neighbor.zh.md new file mode 100644 index 0000000000..7f4065ecf4 --- /dev/null +++ b/docs/configuration/shared/neighbor.zh.md @@ -0,0 +1,49 @@ +--- +icon: material/lan +--- + +# 邻居解析 + +通过 +[`source_mac_address`](/configuration/route/rule/#source_mac_address) 和 +[`source_hostname`](/configuration/route/rule/#source_hostname) 规则项匹配局域网设备的 MAC 地址和主机名。 + +当这些规则项存在,或 [local DNS 服务器](/zh/configuration/dns/server/local/) 设置了 [neighbor_domain](/zh/configuration/dns/server/local/#neighbor_domain) 时,邻居解析自动启用。 +使用 [`route.find_neighbor`](/configuration/route/#find_neighbor) 可在没有规则时强制启用以输出日志。 + +## Linux + +原生支持,无需特殊设置。 + +主机名解析需要 DHCP 租约文件, +自动从常见 DHCP 服务器(dnsmasq、odhcpd、ISC dhcpd、Kea)检测。 +可通过 [`route.dhcp_lease_files`](/configuration/route/#dhcp_lease_files) 设置自定义路径。 + +## Android + +!!! quote "" + + 仅在图形客户端中支持。 + +需要 Android 11 或以上版本和 ROOT。 + +必须使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot) 共享 VPN 连接。 +ROM 自带的「通过 VPN 共享连接」等功能可以共享 VPN, +但无法提供 MAC 地址或主机名信息。 + +在 VPNHotspot 设置中将 **IP 遮掩模式** 设为 **无**。 + +仅支持路由/DNS 规则。不支持 TUN 的 include/exclude 路由。 + +### 设备可见性 + +MAC 地址和主机名仅在 VPNHotspot 中可见时 sing-box 才能读取。 +对于 Apple 设备,需要在所连接网络的 Wi-Fi 设置中将**私有无线局域网地址**从**轮替**改为**固定**。 +非 Apple 设备始终可见。 + +## macOS + +需要独立版本(macOS 系统扩展)。 +App Store 版本可以共享 VPN 热点但不支持 MAC 地址或主机名读取。 + +参阅 [VPN 热点](/manual/misc/vpn-hotspot/#macos) 了解互联网共享设置。 diff --git a/docs/configuration/shared/pre-match.md b/docs/configuration/shared/pre-match.md new file mode 100644 index 0000000000..a0faf57729 --- /dev/null +++ b/docs/configuration/shared/pre-match.md @@ -0,0 +1,50 @@ +--- +icon: material/new-box +--- + +# Pre-match + +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [bypass](#bypass) + +Pre-match is rule matching that runs before the connection is established. + +### How it works + +When TUN receives a connection request, the connection has not yet been established, +so no connection data can be read. In this phase, sing-box runs the routing rules in pre-match mode. + +Since connection data is unavailable, only actions that do not require connection data can be executed. +When a rule matches an action that requires an established connection, pre-match stops at that rule. + +### Supported actions + +#### reject + +Reject with TCP RST / ICMP unreachable. + +See [reject](/configuration/route/rule_action/#reject) for details. + +#### route + +Route ICMP connections to the specified outbound for direct reply. + +See [route](/configuration/route/rule_action/#route) for details. + +#### bypass + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported on Linux with `auto_redirect` enabled. + +Bypass sing-box and connect directly at kernel level. + +If `outbound` is not specified, the rule only matches in pre-match from auto redirect, +and will be skipped in other contexts. + +For all other contexts, bypass with `outbound` behaves like `route` action. + +See [bypass](/configuration/route/rule_action/#bypass) for details. diff --git a/docs/configuration/shared/pre-match.zh.md b/docs/configuration/shared/pre-match.zh.md new file mode 100644 index 0000000000..06d78f1069 --- /dev/null +++ b/docs/configuration/shared/pre-match.zh.md @@ -0,0 +1,47 @@ +--- +icon: material/new-box +--- + +# 预匹配 + +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [bypass](#bypass) + +预匹配是在连接建立之前运行的规则匹配。 + +### 工作原理 + +当 TUN 收到连接请求时,连接尚未建立,因此无法读取连接数据。在此阶段,sing-box 在预匹配模式下运行路由规则。 + +由于连接数据不可用,只有不需要连接数据的动作才能执行。当规则匹配到需要已建立连接的动作时,预匹配将在该规则处停止。 + +### 支持的动作 + +#### reject + +以 TCP RST / ICMP 不可达拒绝。 + +详情参阅 [reject](/zh/configuration/route/rule_action/#reject)。 + +#### route + +将 ICMP 连接路由到指定出站以直接回复。 + +详情参阅 [route](/zh/configuration/route/rule_action/#route)。 + +#### bypass + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅支持 Linux,且需要启用 `auto_redirect`。 + +在内核层面绕过 sing-box 直接连接。 + +如果未指定 `outbound`,规则仅在来自 auto redirect 的预匹配中匹配,在其他场景中将被跳过。 + +对于其他所有场景,指定了 `outbound` 的 bypass 行为与 `route` 相同。 + +详情参阅 [bypass](/zh/configuration/route/rule_action/#bypass)。 diff --git a/docs/configuration/shared/quic.md b/docs/configuration/shared/quic.md new file mode 100644 index 0000000000..485a8ff8d8 --- /dev/null +++ b/docs/configuration/shared/quic.md @@ -0,0 +1,30 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.14.0" + +### Structure + +```json +{ + "initial_packet_size": 0, + "disable_path_mtu_discovery": false, + + ... // HTTP2 Fields +} +``` + +### Fields + +#### initial_packet_size + +Initial QUIC packet size. + +#### disable_path_mtu_discovery + +Disable QUIC path MTU discovery. + +### HTTP2 Fields + +See [HTTP2 Fields](/configuration/shared/http2/) for details. diff --git a/docs/configuration/shared/quic.zh.md b/docs/configuration/shared/quic.zh.md new file mode 100644 index 0000000000..1e840e8f58 --- /dev/null +++ b/docs/configuration/shared/quic.zh.md @@ -0,0 +1,30 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.14.0 起" + +### 结构 + +```json +{ + "initial_packet_size": 0, + "disable_path_mtu_discovery": false, + + ... // HTTP2 字段 +} +``` + +### 字段 + +#### initial_packet_size + +初始 QUIC 数据包大小。 + +#### disable_path_mtu_discovery + +禁用 QUIC 路径 MTU 发现。 + +### HTTP2 字段 + +参阅 [HTTP2 字段](/zh/configuration/shared/http2/) 了解详情。 diff --git a/docs/configuration/shared/tls.md b/docs/configuration/shared/tls.md index b1441a8abc..70e3b5c4fe 100644 --- a/docs/configuration/shared/tls.md +++ b/docs/configuration/shared/tls.md @@ -1,6 +1,41 @@ -!!! quote "Changes in sing-box 1.8.0" +--- +icon: material/new-box +--- - :material-alert-decagram: [utls](#utls) +!!! quote "Changes in sing-box 1.14.0" + + :material-plus: [certificate_provider](#certificate_provider) + :material-plus: [handshake_timeout](#handshake_timeout) + :material-plus: [spoof](#spoof) + :material-plus: [spoof_method](#spoof_method) + :material-plus: [engine](#engine) + :material-delete-clock: [acme](#acme-fields) + +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [kernel_tx](#kernel_tx) + :material-plus: [kernel_rx](#kernel_rx) + :material-plus: [curve_preferences](#curve_preferences) + :material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256) + :material-plus: [client_certificate](#client_certificate) + :material-plus: [client_certificate_path](#client_certificate_path) + :material-plus: [client_key](#client_key) + :material-plus: [client_key_path](#client_key_path) + :material-plus: [client_authentication](#client_authentication) + :material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256) + :material-plus: [ech.query_server_name](#query_server_name) + +!!! quote "Changes in sing-box 1.12.0" + + :material-plus: [fragment](#fragment) + :material-plus: [fragment_fallback_delay](#fragment_fallback_delay) + :material-plus: [record_fragment](#record_fragment) + :material-delete-clock: [ech.pq_signature_schemes_enabled](#pq_signature_schemes_enabled) + :material-delete-clock: [ech.dynamic_record_sizing_disabled](#dynamic_record_sizing_disabled) + +!!! quote "Changes in sing-box 1.10.0" + + :material-alert-decagram: [utls](#utls) ### Inbound @@ -12,10 +47,22 @@ "min_version": "", "max_version": "", "cipher_suites": [], + "curve_preferences": [], "certificate": [], "certificate_path": "", + "client_authentication": "", + "client_certificate": [], + "client_certificate_path": [], + "client_certificate_public_key_sha256": [], "key": [], "key_path": "", + "kernel_tx": false, + "kernel_rx": false, + "handshake_timeout": "", + "certificate_provider": "", + + // Deprecated + "acme": { "domain": [], "data_directory": "", @@ -34,10 +81,13 @@ }, "ech": { "enabled": false, - "pq_signature_schemes_enabled": false, - "dynamic_record_sizing_disabled": false, "key": [], - "key_path": "" + "key_path": "", + + // Deprecated + + "pq_signature_schemes_enabled": false, + "dynamic_record_sizing_disabled": false }, "reality": { "enabled": false, @@ -61,6 +111,7 @@ ```json { "enabled": true, + "engine": "", "disable_sni": false, "server_name": "", "insecure": false, @@ -68,14 +119,31 @@ "min_version": "", "max_version": "", "cipher_suites": [], + "curve_preferences": [], "certificate": "", "certificate_path": "", + "certificate_public_key_sha256": [], + "client_certificate": [], + "client_certificate_path": "", + "client_key": [], + "client_key_path": "", + "fragment": false, + "fragment_fallback_delay": "", + "record_fragment": false, + "spoof": "", + "spoof_method": "", + "kernel_tx": false, + "kernel_rx": false, + "handshake_timeout": "", "ech": { "enabled": false, - "pq_signature_schemes_enabled": false, - "dynamic_record_sizing_disabled": false, "config": [], - "config_path": "" + "config_path": "", + "query_server_name": "", + + // Deprecated + "pq_signature_schemes_enabled": false, + "dynamic_record_sizing_disabled": false }, "utls": { "enabled": false, @@ -126,6 +194,76 @@ Cipher suite values: Enable TLS. +#### engine + +!!! question "Since sing-box 1.14.0" + +==Client only== + +TLS engine to use. + +Values: + +* `go` (default) +* `apple` +* `windows` + +Supported fields: + +* `server_name` +* `insecure` +* `alpn` +* `min_version` +* `max_version` +* `certificate` / `certificate_path` +* `certificate_public_key_sha256` +* `handshake_timeout` + +Unsupported fields: + +* `disable_sni` +* `cipher_suites` +* `curve_preferences` +* `client_certificate` / `client_certificate_path` / `client_key` / `client_key_path` +* `fragment` / `record_fragment` +* `kernel_tx` / `kernel_rx` +* `ech` +* `utls` +* `reality` + +!!! note "" + + `windows` uses Schannel via SSPI. Only available on Windows build 17763 or later (Windows 10 version 1809, Windows Server 2019, or newer). + +!!! note "" + + TLS 1.3 is only negotiated on Windows 11 or Windows Server 2022 and newer. On older Windows versions, Schannel caps the connection at TLS 1.2 even when `max_version` is `1.3`. + +The default version range is TLS 1.2 to TLS 1.3, matching the `go` engine. + +Supported fields: + +* `server_name` +* `insecure` +* `alpn` +* `min_version` +* `max_version` +* `certificate` / `certificate_path` +* `certificate_public_key_sha256` +* `handshake_timeout` + +Unsupported fields: + +* `disable_sni` +* `cipher_suites` +* `curve_preferences` +* `client_certificate` / `client_certificate_path` / `client_key` / `client_key_path` +* `fragment` / `record_fragment` +* `kernel_tx` / `kernel_rx` +* `ech` +* `utls` +* `reality` + #### disable_sni ==Client only== @@ -168,17 +306,88 @@ By default, the maximum version is currently TLS 1.3. #### cipher_suites -A list of enabled TLS 1.0–1.2 cipher suites. The order of the list is ignored. Note that TLS 1.3 cipher suites are not configurable. +List of enabled TLS 1.0–1.2 cipher suites. The order of the list is ignored. +Note that TLS 1.3 cipher suites are not configurable. If empty, a safe default list is used. The default cipher suites might change over time. +#### curve_preferences + +!!! question "Since sing-box 1.13.0" + +Set of supported key exchange mechanisms. The order of the list is ignored, and key exchange mechanisms are chosen +from this list using an internal preference order by Golang. + +Available values, also the default list: + +* `P256` +* `P384` +* `P521` +* `X25519` +* `X25519MLKEM768` + #### certificate -The server certificate line array, in PEM format. +Server certificates chain line array, in PEM format. #### certificate_path -The path to the server certificate, in PEM format. +!!! note "" + + Will be automatically reloaded if file modified. + +The path to server certificate chain, in PEM format. + + +#### certificate_public_key_sha256 + +!!! question "Since sing-box 1.13.0" + +==Client only== + +List of SHA-256 hashes of server certificate public keys, in base64 format. + +To generate the SHA-256 hash for a certificate's public key, use the following commands: + +```bash +# For a certificate file +openssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64 + +# For a certificate from a remote server +echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64 +``` + +#### client_certificate + +!!! question "Since sing-box 1.13.0" + +==Client only== + +Client certificate chain line array, in PEM format. + +#### client_certificate_path + +!!! question "Since sing-box 1.13.0" + +==Client only== + +The path to client certificate chain, in PEM format. + +#### client_key + +!!! question "Since sing-box 1.13.0" + +==Client only== + +Client private key line array, in PEM format. + +#### client_key_path + +!!! question "Since sing-box 1.13.0" + +==Client only== + +The path to client private key, in PEM format. #### key @@ -190,8 +399,125 @@ The server private key line array, in PEM format. ==Server only== +!!! note "" + + Will be automatically reloaded if file modified. + The path to the server private key, in PEM format. +#### client_authentication + +!!! question "Since sing-box 1.13.0" + +==Server only== + +The type of client authentication to use. + +Available values: + +* `no` (default) +* `request` +* `require-any` +* `verify-if-given` +* `require-and-verify` + +One of `client_certificate`, `client_certificate_path`, or `client_certificate_public_key_sha256` is required +if this option is set to `verify-if-given`, or `require-and-verify`. + +#### client_certificate + +!!! question "Since sing-box 1.13.0" + +==Server only== + +Client certificate chain line array, in PEM format. + +#### client_certificate_path + +!!! question "Since sing-box 1.13.0" + +==Server only== + +!!! note "" + + Will be automatically reloaded if file modified. + +List of path to client certificate chain, in PEM format. + +#### client_certificate_public_key_sha256 + +!!! question "Since sing-box 1.13.0" + +==Server only== + +List of SHA-256 hashes of client certificate public keys, in base64 format. + +To generate the SHA-256 hash for a certificate's public key, use the following commands: + +```bash +# For a certificate file +openssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64 + +# For a certificate from a remote server +echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64 +``` + +#### kernel_tx + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported on Linux 5.1+, use a newer kernel if possible. + +!!! quote "" + + Only TLS 1.3 is supported. + +!!! warning "" + + kTLS TX may only improve performance when `splice(2)` is available (both ends must be TCP or TLS without additional protocols after handshake); otherwise, it will definitely degrade performance. + +Enable kernel TLS transmit support. + +#### kernel_rx + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported on Linux 5.1+, use a newer kernel if possible. + +!!! quote "" + + Only TLS 1.3 is supported. + +!!! failure "" + + kTLS RX will definitely degrade performance even if `splice(2)` is in use, so enabling it is not recommended. + +Enable kernel TLS receive support. + +#### handshake_timeout + +!!! question "Since sing-box 1.14.0" + +TLS handshake timeout, in golang's Duration format. + +`15s` is used by default. + +#### certificate_provider + +!!! question "Since sing-box 1.14.0" + +==Server only== + +A string or an object. + +When string, the tag of a shared [Certificate Provider](/configuration/shared/certificate-provider/). + +When object, an inline certificate provider. See [Certificate Provider](/configuration/shared/certificate-provider/) for available types and fields. + ## Custom TLS support !!! info "QUIC support" @@ -202,28 +528,34 @@ The path to the server private key, in PEM format. ==Client only== -!!! note "" +!!! failure "Not Recommended" - uTLS is poorly maintained and the effect may be unproven, use at your own risk. + uTLS has had repeated fingerprinting vulnerabilities discovered by researchers. + + uTLS is a Go library that attempts to imitate browser TLS fingerprints by copying + ClientHello structure. However, browsers use completely different TLS stacks + (Chrome uses BoringSSL, Firefox uses NSS) with distinct implementation behaviors + that cannot be replicated by simply copying the handshake format, making detection possible. + Additionally, the library lacks active maintenance and has poor code quality, + making it unsuitable for censorship circumvention. + + For TLS fingerprint resistance, use [NaiveProxy](/configuration/inbound/naive/) instead. uTLS is a fork of "crypto/tls", which provides ClientHello fingerprinting resistance. Available fingerprint values: -!!! question "Since sing-box 1.8.0" +!!! warning "Removed since sing-box 1.10.0" + + Some legacy chrome fingerprints have been removed and will fallback to chrome: - :material-plus: chrome_psk - :material-plus: chrome_psk_shuffle - :material-plus: chrome_padding_psk_shuffle - :material-plus: chrome_pq - :material-plus: chrome_pq_psk + :material-close: chrome_psk + :material-close: chrome_psk_shuffle + :material-close: chrome_padding_psk_shuffle + :material-close: chrome_pq + :material-close: chrome_pq_psk * chrome -* chrome_psk -* chrome_psk_shuffle -* chrome_padding_psk_shuffle -* chrome_pq -* chrome_pq_psk * firefox * edge * safari @@ -241,16 +573,22 @@ Chrome fingerprint will be used if empty. ECH (Encrypted Client Hello) is a TLS extension that allows a client to encrypt the first part of its ClientHello message. -The ECH key and configuration can be generated by `sing-box generate ech-keypair [--pq-signature-schemes-enabled]`. +The ECH key and configuration can be generated by `sing-box generate ech-keypair`. #### pq_signature_schemes_enabled -Enable support for post-quantum peer certificate signature schemes. +!!! failure "Deprecated in sing-box 1.12.0" + + `pq_signature_schemes_enabled` is deprecated in sing-box 1.12.0 and removed in sing-box 1.13.0. -It is recommended to match the parameters of `sing-box generate ech-keypair`. +Enable support for post-quantum peer certificate signature schemes. #### dynamic_record_sizing_disabled +!!! failure "Deprecated in sing-box 1.12.0" + + `dynamic_record_sizing_disabled` is deprecated in sing-box 1.12.0 and removed in sing-box 1.13.0. + Disables adaptive sizing of TLS records. When true, the largest possible TLS record size is always used. @@ -266,6 +604,10 @@ ECH key line array, in PEM format. ==Server only== +!!! note "" + + Will be automatically reloaded if file modified. + The path to ECH key, in PEM format. #### config @@ -284,8 +626,96 @@ The path to ECH configuration, in PEM format. If empty, load from DNS will be attempted. +#### query_server_name + +!!! question "Since sing-box 1.13.0" + +==Client only== + +Overrides the domain name used for ECH HTTPS record queries. + +If empty, `server_name` is used for queries. + +#### fragment + +!!! question "Since sing-box 1.12.0" + +==Client only== + +Fragment TLS handshakes to bypass firewalls. + +This feature is intended to circumvent simple firewalls based on **plaintext packet matching**, +and should not be used to circumvent real censorship. + +Due to poor performance, try `record_fragment` first, and only apply to server names known to be blocked. + +On Linux, Apple platforms, (administrator privileges required) Windows, +the wait time can be automatically detected. Otherwise, it will fall back to +waiting for a fixed time specified by `fragment_fallback_delay`. + +In addition, if the actual wait time is less than 20ms, it will also fall back to waiting for a fixed time, +because the target is considered to be local or behind a transparent proxy. + +#### fragment_fallback_delay + +!!! question "Since sing-box 1.12.0" + +==Client only== + +The fallback value used when TLS segmentation cannot automatically determine the wait time. + +`500ms` is used by default. + +#### record_fragment + +!!! question "Since sing-box 1.12.0" + +==Client only== + +Fragment TLS handshake into multiple TLS records to bypass firewalls. + +#### spoof + +!!! question "Since sing-box 1.14.0" + +==Client only, Linux/macOS/Windows only, requires elevated privileges== + +Inject a forged TLS ClientHello carrying a whitelisted SNI before the real one, +to fool SNI-filtering middleboxes that permit specific hostnames. + +The forged segment is a copy of the real ClientHello with only the SNI value +replaced by the value of this field, so TLS fingerprinting cannot distinguish +it from the real one. The receiving server drops the forged segment +(see `spoof_method`) while the middlebox treats it as a legitimate session. + +Requires raw-socket access (`CAP_NET_RAW` on Linux, root on macOS); +on Linux, `CAP_NET_ADMIN` is additionally required because the send sequence +number is read via `TCP_REPAIR`. +On Windows, Administrator is required to install the embedded WinDivert kernel +driver on first use. Windows on ARM64 is not supported. + +#### spoof_method + +!!! question "Since sing-box 1.14.0" + +==Client only== + +How the forged segment is rejected by the real server. + +| Value | Behavior | +|----------------------------|----------------------------------------------------------------------------------------------------------------| +| `wrong-sequence` (default) | The forged segment's TCP sequence number is placed before the server's receive window. | +| `wrong-checksum` | The forged segment's TCP checksum is deliberately invalid. | +| `wrong-ack` | The forged segment's TCP acknowledgment number is placed before the server's send window. | +| `wrong-md5` | The forged segment carries a TCP-MD5 signature option, which the server rejects since no MD5 key is negotiated. | +| `wrong-timestamp` | The forged segment carries a backdated TCP timestamp, which the server rejects as a PAWS replay. Linux/Windows only; not supported on macOS. | + ### ACME Fields +!!! failure "Deprecated in sing-box 1.14.0" + + Inline ACME options are deprecated in sing-box 1.14.0 and will be removed in sing-box 1.16.0, check [Migration](/migration/#migrate-inline-acme-to-certificate-provider). + #### domain List of domain. @@ -367,7 +797,7 @@ See [DNS01 Challenge Fields](/configuration/shared/dns01_challenge/) for details ==Required== -Handshake server address and [Dial options](/configuration/shared/dial/). +Handshake server address and [Dial Fields](/configuration/shared/dial/). #### private_key @@ -398,7 +828,3 @@ A hexadecimal string with zero to eight digits. The maximum time difference between the server and the client. Check disabled if empty. - -### Reload - -For server configuration, certificate, key and ECH key will be automatically reloaded if modified. \ No newline at end of file diff --git a/docs/configuration/shared/tls.zh.md b/docs/configuration/shared/tls.zh.md index 360c453642..40762c4f74 100644 --- a/docs/configuration/shared/tls.zh.md +++ b/docs/configuration/shared/tls.zh.md @@ -1,6 +1,41 @@ -!!! quote "sing-box 1.8.0 中的更改" +--- +icon: material/new-box +--- - :material-alert-decagram: [utls](#utls) +!!! quote "sing-box 1.14.0 中的更改" + + :material-plus: [certificate_provider](#certificate_provider) + :material-plus: [handshake_timeout](#handshake_timeout) + :material-plus: [spoof](#spoof) + :material-plus: [spoof_method](#spoof_method) + :material-plus: [engine](#engine) + :material-delete-clock: [acme](#acme-字段) + +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [kernel_tx](#kernel_tx) + :material-plus: [kernel_rx](#kernel_rx) + :material-plus: [curve_preferences](#curve_preferences) + :material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256) + :material-plus: [client_certificate](#client_certificate) + :material-plus: [client_certificate_path](#client_certificate_path) + :material-plus: [client_key](#client_key) + :material-plus: [client_key_path](#client_key_path) + :material-plus: [client_authentication](#client_authentication) + :material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256) + :material-plus: [ech.query_server_name](#query_server_name) + +!!! quote "sing-box 1.12.0 中的更改" + + :material-plus: [fragment](#fragment) + :material-plus: [fragment_fallback_delay](#fragment_fallback_delay) + :material-plus: [record_fragment](#record_fragment) + :material-delete-clock: [ech.pq_signature_schemes_enabled](#pq_signature_schemes_enabled) + :material-delete-clock: [ech.dynamic_record_sizing_disabled](#dynamic_record_sizing_disabled) + +!!! quote "sing-box 1.10.0 中的更改" + + :material-alert-decagram: [utls](#utls) ### 入站 @@ -12,10 +47,22 @@ "min_version": "", "max_version": "", "cipher_suites": [], + "curve_preferences": [], "certificate": [], "certificate_path": "", + "client_authentication": "", + "client_certificate": [], + "client_certificate_path": [], + "client_certificate_public_key_sha256": [], "key": [], "key_path": "", + "kernel_tx": false, + "kernel_rx": false, + "handshake_timeout": "", + "certificate_provider": "", + + // 废弃的 + "acme": { "domain": [], "data_directory": "", @@ -34,17 +81,20 @@ }, "ech": { "enabled": false, - "pq_signature_schemes_enabled": false, - "dynamic_record_sizing_disabled": false, "key": [], - "key_path": "" + "key_path": "", + + // 废弃的 + + "pq_signature_schemes_enabled": false, + "dynamic_record_sizing_disabled": false }, "reality": { "enabled": false, "handshake": { "server": "google.com", "server_port": 443, - + ... // 拨号字段 }, "private_key": "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc", @@ -61,6 +111,7 @@ ```json { "enabled": true, + "engine": "", "disable_sni": false, "server_name": "", "insecure": false, @@ -68,14 +119,31 @@ "min_version": "", "max_version": "", "cipher_suites": [], - "certificate": [], + "curve_preferences": [], + "certificate": "", "certificate_path": "", + "certificate_public_key_sha256": [], + "client_certificate": [], + "client_certificate_path": "", + "client_key": [], + "client_key_path": "", + "fragment": false, + "fragment_fallback_delay": "", + "record_fragment": false, + "spoof": "", + "spoof_method": "", + "kernel_tx": false, + "kernel_rx": false, + "handshake_timeout": "", "ech": { "enabled": false, - "pq_signature_schemes_enabled": false, - "dynamic_record_sizing_disabled": false, "config": [], - "config_path": "" + "config_path": "", + "query_server_name": "", + + // 废弃的 + "pq_signature_schemes_enabled": false, + "dynamic_record_sizing_disabled": false }, "utls": { "enabled": false, @@ -126,6 +194,76 @@ TLS 版本值: 启用 TLS +#### engine + +!!! question "自 sing-box 1.14.0 起" + +==仅客户端== + +要使用的 TLS 引擎。 + +可用值: + +* `go`(默认) +* `apple` +* `windows` + +支持的字段: + +* `server_name` +* `insecure` +* `alpn` +* `min_version` +* `max_version` +* `certificate` / `certificate_path` +* `certificate_public_key_sha256` +* `handshake_timeout` + +不支持的字段: + +* `disable_sni` +* `cipher_suites` +* `curve_preferences` +* `client_certificate` / `client_certificate_path` / `client_key` / `client_key_path` +* `fragment` / `record_fragment` +* `kernel_tx` / `kernel_rx` +* `ech` +* `utls` +* `reality` + +!!! note "" + + `windows` 通过 SSPI 使用 Schannel,仅在 Windows build 17763 及以上可用,包括 Windows 10 版本 1809、Windows Server 2019 及后续版本。 + +!!! note "" + + TLS 1.3 仅在 Windows 11 或 Windows Server 2022 及后续版本上协商。在更早的 Windows 版本上,即使 `max_version` 设为 `1.3`,Schannel 也会把连接上限固定在 TLS 1.2。 + +默认版本范围为 TLS 1.2 到 TLS 1.3,与 `go` 引擎一致。证书验证在 Go 侧基于 Schannel 返回的证书链执行,默认使用系统证书存储。当设置了 `certificate` 或 `certificate_path` 时,这些根证书会替代系统存储。 + +支持的字段: + +* `server_name` +* `insecure` +* `alpn` +* `min_version` +* `max_version` +* `certificate` / `certificate_path` +* `certificate_public_key_sha256` +* `handshake_timeout` + +不支持的字段: + +* `disable_sni` +* `cipher_suites` +* `curve_preferences` +* `client_certificate` / `client_certificate_path` / `client_key` / `client_key_path` +* `fragment` / `record_fragment` +* `kernel_tx` / `kernel_rx` +* `ech` +* `utls` +* `reality` + #### disable_sni ==仅客户端== @@ -166,56 +304,255 @@ TLS 版本值: #### cipher_suites -启用的 TLS 1.0-1.2密码套件的列表。列表的顺序被忽略。请注意,TLS 1.3 的密码套件是不可配置的。 +启用的 TLS 1.0–1.2 密码套件列表。列表的顺序被忽略。请注意,TLS 1.3 的密码套件是不可配置的。 如果为空,则使用安全的默认列表。默认密码套件可能会随着时间的推移而改变。 +#### curve_preferences + +!!! question "自 sing-box 1.13.0 起" + +支持的密钥交换机制集合。列表的顺序被忽略,密钥交换机制通过 Golang 的内部偏好顺序从此列表中选择。 + +可用值,同时也是默认列表: + +* `P256` +* `P384` +* `P521` +* `X25519` +* `X25519MLKEM768` + #### certificate -服务器 PEM 证书行数组。 +服务器证书链行数组,PEM 格式。 #### certificate_path -服务器 PEM 证书路径。 +!!! note "" + + 文件更改时将自动重新加载。 + +服务器证书链路径,PEM 格式。 + +#### certificate_public_key_sha256 + +!!! question "自 sing-box 1.13.0 起" + +==仅客户端== + +服务器证书公钥的 SHA-256 哈希列表,base64 格式。 + +要生成证书公钥的 SHA-256 哈希,请使用以下命令: + +```bash +# 对于证书文件 +openssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64 + +# 对于远程服务器的证书 +echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64 +``` + +#### client_certificate + +!!! question "自 sing-box 1.13.0 起" + +==仅客户端== + +客户端证书链行数组,PEM 格式。 + +#### client_certificate_path + +!!! question "自 sing-box 1.13.0 起" + +==仅客户端== + +客户端证书链路径,PEM 格式。 + +#### client_key + +!!! question "自 sing-box 1.13.0 起" + +==仅客户端== + +客户端私钥行数组,PEM 格式。 + +#### client_key_path + +!!! question "自 sing-box 1.13.0 起" + +==仅客户端== + +客户端私钥路径,PEM 格式。 #### key ==仅服务器== +!!! note "" + + 文件更改时将自动重新加载。 + 服务器 PEM 私钥行数组。 #### key_path ==仅服务器== -服务器 PEM 私钥路径。 +!!! note "" + + 文件更改时将自动重新加载。 + +服务器私钥路径,PEM 格式。 + +#### client_authentication + +!!! question "自 sing-box 1.13.0 起" + +==仅服务器== + +要使用的客户端身份验证类型。 + +可用值: + +* `no`(默认) +* `request` +* `require-any` +* `verify-if-given` +* `require-and-verify` + +如果此选项设置为 `verify-if-given` 或 `require-and-verify`, +则需要 `client_certificate`、`client_certificate_path` 或 `client_certificate_public_key_sha256` 中的一个。 + +#### client_certificate + +!!! question "自 sing-box 1.13.0 起" + +==仅服务器== + +客户端证书链行数组,PEM 格式。 + +#### client_certificate_path + +!!! question "自 sing-box 1.13.0 起" + +==仅服务器== + +!!! note "" + + 文件更改时将自动重新加载。 + +客户端证书链路径列表,PEM 格式。 + +#### client_certificate_public_key_sha256 + +!!! question "自 sing-box 1.13.0 起" + +==仅服务器== + +客户端证书公钥的 SHA-256 哈希列表,base64 格式。 + +要生成证书公钥的 SHA-256 哈希,请使用以下命令: + +```bash +# 对于证书文件 +openssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64 + +# 对于远程服务器的证书 +echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64 +``` + +#### kernel_tx + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅支持 Linux 5.1+,如果可能,使用较新的内核。 + +!!! quote "" + + 仅支持 TLS 1.3。 + +!!! warning "" + + kTLS TX 仅当 `splice(2)` 可用时(两端经过握手后必须为没有附加协议的 TCP 或 TLS)才能提高性能;否则肯定会降低性能。 + +启用内核 TLS 发送支持。 + +#### kernel_rx + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅支持 Linux 5.1+,如果可能,使用较新的内核。 + +!!! quote "" + + 仅支持 TLS 1.3。 + +!!! failure "" + + 即使使用 `splice(2)`,kTLS RX 也肯定会降低性能,因此不建议启用。 + +启用内核 TLS 接收支持。 + +#### handshake_timeout + +!!! question "自 sing-box 1.14.0 起" + +TLS 握手超时,采用 golang 的 Duration 格式。 + +默认使用 `15s`。 + +#### certificate_provider + +!!! question "自 sing-box 1.14.0 起" + +==仅服务器== + +字符串或对象。 + +为字符串时,共享[证书提供者](/zh/configuration/shared/certificate-provider/)的标签。 + +为对象时,内联的证书提供者。可用类型和字段参阅[证书提供者](/zh/configuration/shared/certificate-provider/)。 + +## 自定义 TLS 支持 + +!!! info "QUIC 支持" + + 只有 ECH 在 QUIC 中被支持. #### utls ==仅客户端== -!!! note "" +!!! failure "不推荐" + + uTLS 已被研究人员多次发现其指纹可被识别的漏洞。 - uTLS 维护不善且其效果可能未经证实,使用风险自负。 + uTLS 是一个试图通过复制 ClientHello 结构来模仿浏览器 TLS 指纹的 Go 库。 + 然而,浏览器使用完全不同的 TLS 实现(Chrome 使用 BoringSSL,Firefox 使用 NSS), + 其实现行为无法通过简单复制握手格式来复现,其行为细节必然存在差异,使得检测成为可能。 + 此外,此库缺乏积极维护,且代码质量较差,不建议用于反审查场景。 + + 如需 TLS 指纹抵抗,请改用 [NaiveProxy](/zh/configuration/inbound/naive/)。 uTLS 是 "crypto/tls" 的一个分支,它提供了 ClientHello 指纹识别阻力。 可用的指纹值: -!!! question "自 sing-box 1.8.0 起" +!!! warning "已在 sing-box 1.10.0 移除" + + 一些旧 chrome 指纹已被删除,并将会退到 chrome: - :material-plus: chrome_psk - :material-plus: chrome_psk_shuffle - :material-plus: chrome_padding_psk_shuffle - :material-plus: chrome_pq - :material-plus: chrome_pq_psk + :material-close: chrome_psk + :material-close: chrome_psk_shuffle + :material-close: chrome_padding_psk_shuffle + :material-close: chrome_pq + :material-close: chrome_pq_psk * chrome -* chrome_psk -* chrome_psk_shuffle -* chrome_padding_psk_shuffle -* chrome_pq -* chrome_pq_psk * firefox * edge * safari @@ -228,43 +565,52 @@ uTLS 是 "crypto/tls" 的一个分支,它提供了 ClientHello 指纹识别阻 默认使用 chrome 指纹。 -## ECH 字段 +### ECH 字段 -ECH (Encrypted Client Hello) 是一个 TLS 扩展,它允许客户端加密其 ClientHello 的第一部分 -信息。 +ECH (Encrypted Client Hello) 是一个 TLS 扩展,它允许客户端加密其 ClientHello 的第一部分信息。 -ECH 配置和密钥可以通过 `sing-box generate ech-keypair [--pq-signature-schemes-enabled]` 生成。 +ECH 密钥和配置可以通过 `sing-box generate ech-keypair` 生成。 #### pq_signature_schemes_enabled -启用对后量子对等证书签名方案的支持。 +!!! failure "已在 sing-box 1.12.0 废弃" -建议匹配 `sing-box generate ech-keypair` 的参数。 + `pq_signature_schemes_enabled` 已在 sing-box 1.12.0 废弃且已在 sing-box 1.13.0 中被移除。 + +启用对后量子对等证书签名方案的支持。 #### dynamic_record_sizing_disabled +!!! failure "已在 sing-box 1.12.0 废弃" + + `dynamic_record_sizing_disabled` 已在 sing-box 1.12.0 废弃且已在 sing-box 1.13.0 中被移除。 + 禁用 TLS 记录的自适应大小调整。 -如果为 true,则始终使用最大可能的 TLS 记录大小。 -如果为 false,则可能会调整 TLS 记录的大小以尝试改善延迟。 +当为 true 时,总是使用最大可能的 TLS 记录大小。 +当为 false 时,可能会调整 TLS 记录的大小以尝试改善延迟。 #### key ==仅服务器== -ECH PEM 密钥行数组 +ECH 密钥行数组,PEM 格式。 #### key_path ==仅服务器== -ECH PEM 密钥路径 +!!! note "" + + 文件更改时将自动重新加载。 + +ECH 密钥路径,PEM 格式。 #### config ==仅客户端== -ECH PEM 配置行数组 +ECH 配置行数组,PEM 格式。 如果为空,将尝试从 DNS 加载。 @@ -272,23 +618,108 @@ ECH PEM 配置行数组 ==仅客户端== -ECH PEM 配置路径 +ECH 配置路径,PEM 格式。 如果为空,将尝试从 DNS 加载。 +#### query_server_name + +!!! question "自 sing-box 1.13.0 起" + +==仅客户端== + +覆盖用于 ECH HTTPS 记录查询的域名。 + +如果为空,使用 `server_name` 查询。 + +#### fragment + +!!! question "自 sing-box 1.12.0 起" + +==仅客户端== + +通过分段 TLS 握手数据包来绕过防火墙。 + +此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真正的审查。 + +由于性能不佳,请首先尝试 `record_fragment`,且仅应用于已知被阻止的服务器名称。 + +在 Linux、Apple 平台和(需要管理员权限的)Windows 系统上, +可以自动检测等待时间。否则,将回退到 +等待 `fragment_fallback_delay` 指定的固定时间。 + +此外,如果实际等待时间少于 20ms,也会回退到等待固定时间, +因为目标被认为是本地的或在透明代理后面。 + +#### fragment_fallback_delay + +!!! question "自 sing-box 1.12.0 起" + +==仅客户端== + +当 TLS 分段无法自动确定等待时间时使用的回退值。 + +默认使用 `500ms`。 + +#### record_fragment + +!!! question "自 sing-box 1.12.0 起" + +==仅客户端== + +将 TLS 握手分段为多个 TLS 记录以绕过防火墙。 + +#### spoof + +!!! question "自 sing-box 1.14.0 起" + +==仅客户端,仅 Linux/macOS/Windows,需要提权== + +在真实 ClientHello 之前注入一个伪造的、携带白名单 SNI 的 TLS ClientHello, +以欺骗基于 SNI 过滤的中间盒放行连接。 + +伪造报文是真实 ClientHello 的副本,仅将 SNI 值替换为本字段的值, +因此 TLS 指纹无法区分伪造与真实报文。真实服务器会丢弃伪造报文(见 `spoof_method`), +而中间盒将该连接视为合法会话。 + +需要原始套接字权限(Linux 上需 `CAP_NET_RAW`,macOS 上需 root); +在 Linux 上还需 `CAP_NET_ADMIN`,因为需要通过 `TCP_REPAIR` 读取发送序列号。 +Windows 上首次使用时需要 Administrator 以安装内嵌的 WinDivert 内核驱动, +不支持 Windows ARM64。 + +#### spoof_method + +!!! question "自 sing-box 1.14.0 起" + +==仅客户端== + +控制伪造报文被真实服务器拒绝的方式。 + +| 取值 | 行为 | +|--------------------------|-------------------------------------------------------------------| +| `wrong-sequence`(默认) | 伪造报文的 TCP 序列号位于服务器接收窗口之前。 | +| `wrong-checksum` | 伪造报文的 TCP 校验和被故意设为无效。 | +| `wrong-ack` | 伪造报文的 TCP 确认号位于服务器发送窗口之前。 | +| `wrong-md5` | 伪造报文携带 TCP-MD5 签名选项,未协商 MD5 密钥的服务器将拒绝。 | +| `wrong-timestamp` | 伪造报文携带回退的 TCP 时间戳,服务器按 PAWS 规则视为重放并拒绝。仅支持 Linux/Windows,不支持 macOS。 | + ### ACME 字段 +!!! failure "已在 sing-box 1.14.0 废弃" + + 内联 ACME 选项已在 sing-box 1.14.0 废弃且将在 sing-box 1.16.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移内联-acme-到证书提供者)。 + #### domain -一组域名。 +域名列表。 -默认禁用 ACME。 +如果为空则禁用 ACME。 #### data_directory -ACME 数据目录。 +ACME 数据存储目录。 -默认使用 `$XDG_DATA_HOME/certmagic|$HOME/.local/share/certmagic`。 +如果为空则使用 `$XDG_DATA_HOME/certmagic|$HOME/.local/share/certmagic`。 #### default_server_name @@ -326,12 +757,11 @@ ACME 数据目录。 #### external_account -EAB(外部帐户绑定)包含将 ACME 帐户绑定或映射到其他已知帐户所需的信息由 CA。 +EAB(外部帐户绑定)包含将 ACME 帐户绑定或映射到 CA 已知的其他帐户所需的信息。 -外部帐户绑定“用于将 ACME 帐户与非 ACME 系统中的现有帐户相关联,例如 CA 客户数据库。 +外部帐户绑定"用于将 ACME 帐户与非 ACME 系统中的现有帐户相关联,例如 CA 客户数据库。 -为了启用 ACME 帐户绑定,运行 ACME 服务器的 CA 需要向 ACME 客户端提供 MAC 密钥和密钥标识符,使用 ACME 之外的一些机制。 -§7.3.4 +为了启用 ACME 帐户绑定,运行 ACME 服务器的 CA 需要使用 ACME 之外的某种机制向 ACME 客户端提供 MAC 密钥和密钥标识符。§7.3.4 #### external_account.key_id @@ -345,7 +775,7 @@ MAC 密钥。 ACME DNS01 验证字段。如果配置,将禁用其他验证方法。 -参阅 [DNS01 验证字段](/configuration/shared/dns01_challenge/)。 +参阅 [DNS01 验证字段](/zh/configuration/shared/dns01_challenge/)。 ### Reality 字段 @@ -381,10 +811,8 @@ ACME DNS01 验证字段。如果配置,将禁用其他验证方法。 #### max_time_difference -服务器与和客户端之间允许的最大时间差。 - -默认禁用检查。 +==仅服务器== -### 重载 +服务器和客户端之间的最大时间差。 -对于服务器配置,如果修改,证书和密钥将自动重新加载。 \ No newline at end of file +如果为空则禁用检查。 diff --git a/docs/configuration/shared/udp-over-tcp.md b/docs/configuration/shared/udp-over-tcp.md index 6bd8a68a91..71a934db8b 100644 --- a/docs/configuration/shared/udp-over-tcp.md +++ b/docs/configuration/shared/udp-over-tcp.md @@ -1,5 +1,3 @@ -# UDP over TCP - !!! warning "" It's a proprietary protocol created by SagerNet, not part of shadowsocks. @@ -33,12 +31,11 @@ The protocol version, `1` or `2`. ### Application support -| Project | UoT v1 | UoT v2 | -|--------------|----------------------|-------------------------------------------------------------------------------------------------------------------| -| sing-box | v0 (2022/08/11) | v1.2-beta9 | -| Xray-core | v1.5.7 (2022/06/05) | [f57ec13](https://github.com/XTLS/Xray-core/commit/f57ec1388084df041a2289bacab14e446bf1b357) (Not released) | -| Clash.Meta | v1.12.0 (2022/07/02) | [8cb67b6](https://github.com/MetaCubeX/Clash.Meta/commit/8cb67b6480649edfa45dcc9ac89ce0789651e8b3) (Not released) | -| Shadowrocket | v2.2.12 (2022/08/13) | / | +| Project | UoT v1 | UoT v2 | +|--------------|----------------------|----------------------| +| sing-box | v0 (2022/08/11) | v1.2-beta9 | +| Clash.Meta | v1.12.0 (2022/07/02) | v1.14.3 (2023/03/31) | +| Shadowrocket | v2.2.12 (2022/08/13) | / | ### Protocol details @@ -52,7 +49,13 @@ The client requests the magic address to the upper layer proxy protocol to indic |------|----------|-------|--------|----------| | u8 | variable | u16be | u16be | variable | -**ATYP / address / port**: Uses the SOCKS address format. +**ATYP / address / port**: Uses the SOCKS address format, but with different address types: + +| ATYP | Address type | +|--------|--------------| +| `0x00` | IPv4 Address | +| `0x01` | IPv6 Address | +| `0x02` | Domain Name | #### Protocol version 2 diff --git a/docs/configuration/shared/udp-over-tcp.zh.md b/docs/configuration/shared/udp-over-tcp.zh.md new file mode 100644 index 0000000000..fec9645ed5 --- /dev/null +++ b/docs/configuration/shared/udp-over-tcp.zh.md @@ -0,0 +1,82 @@ +!!! warning "" + + 这是 SagerNet 创建的专有协议,不是 shadowsocks 的一部分。 + +UDP over TCP 协议用于在 TCP 中传输 UDP 数据包。 + +### 结构 + +```json +{ + "enabled": true, + "version": 2 +} +``` + +!!! info "" + + 当不指定版本时,结构可以用布尔值替换。 + +### 字段 + +#### enabled + +启用 UDP over TCP 协议。 + +#### version + +协议版本,`1` 或 `2`。 + +默认使用 2。 + +### 应用程序支持 + +| 项目 | UoT v1 | UoT v2 | +|--------------|----------------------|----------------------| +| sing-box | v0 (2022/08/11) | v1.2-beta9 | +| Clash.Meta | v1.12.0 (2022/07/02) | v1.14.3 (2023/03/31) | +| Shadowrocket | v2.2.12 (2022/08/13) | / | + +### 协议详情 + +#### 协议版本 1 + +客户端向上层代理协议请求魔法地址以表示请求:`sp.udp-over-tcp.arpa` + +#### 流格式 + +| ATYP | 地址 | 端口 | 长度 | 数据 | +|------|----------|-------|--------|----------| +| u8 | 可变长 | u16be | u16be | 可变长 | + +**ATYP / 地址 / 端口**:使用 SOCKS 地址格式,但使用不同的地址类型: + +| ATYP | 地址类型 | +|--------|-----------| +| `0x00` | IPv4 地址 | +| `0x01` | IPv6 地址 | +| `0x02` | 域名 | + +#### 协议版本 2 + +协议版本 2 使用新的魔法地址:`sp.v2.udp-over-tcp.arpa` + +##### 请求格式 + +| isConnect | ATYP | 地址 | 端口 | +|-----------|------|----------|-------| +| u8 | u8 | 可变长 | u16be | + +**isConnect**:设置为 1 表示流使用连接格式,0 表示禁用。 + +**ATYP / 地址 / 端口**:请求目标,使用 SOCKS 地址格式。 + +##### 连接流格式 + +| 长度 | 数据 | +|--------|----------| +| u16be | 可变长 | + +##### 非连接流格式 + +与协议版本 1 中的流格式相同。 \ No newline at end of file diff --git a/docs/configuration/shared/v2ray-transport.zh.md b/docs/configuration/shared/v2ray-transport.zh.md index e5bd7de72e..e5ea3ed6f9 100644 --- a/docs/configuration/shared/v2ray-transport.zh.md +++ b/docs/configuration/shared/v2ray-transport.zh.md @@ -144,7 +144,7 @@ HTTP 请求的额外标头 !!! note "" - 默认安装不包含标准 gRPC (兼容性好,但性能较差), 参阅 [安装](/zh/installation/build-from-source/#_5)。 + 默认安装不包含标准 gRPC (兼容性好,但性能较差), 参阅 [安装](/zh/installation/build-from-source/#构建标记)。 ```json { diff --git a/docs/configuration/shared/wifi-state.md b/docs/configuration/shared/wifi-state.md new file mode 100644 index 0000000000..a32675b396 --- /dev/null +++ b/docs/configuration/shared/wifi-state.md @@ -0,0 +1,41 @@ +--- +icon: material/new-box +--- + +# Wi-Fi State + +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: Linux support + :material-plus: Windows support + +sing-box can monitor Wi-Fi state to enable routing rules based on `wifi_ssid` and `wifi_bssid`. + +### Platform Support + +| Platform | Support | Notes | +|-----------------|------------------|--------------------------| +| Android | :material-check: | In graphical client | +| Apple platforms | :material-check: | In graphical clients | +| Linux | :material-check: | Requires supported daemon | +| Windows | :material-check: | WLAN API | +| Others | :material-close: | | + +### Linux + +!!! question "Since sing-box 1.13.0" + +The following backends are supported and will be auto-detected in order of priority: + +| Backend | Interface | +|------------------|-------------| +| NetworkManager | D-Bus | +| IWD | D-Bus | +| wpa_supplicant | Unix socket | +| ConnMan | D-Bus | + +### Windows + +!!! question "Since sing-box 1.13.0" + +Uses Windows WLAN API. diff --git a/docs/configuration/shared/wifi-state.zh.md b/docs/configuration/shared/wifi-state.zh.md new file mode 100644 index 0000000000..c7d4db5f4f --- /dev/null +++ b/docs/configuration/shared/wifi-state.zh.md @@ -0,0 +1,41 @@ +--- +icon: material/new-box +--- + +# Wi-Fi 状态 + +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: Linux 支持 + :material-plus: Windows 支持 + +sing-box 可以监控 Wi-Fi 状态,以启用基于 `wifi_ssid` 和 `wifi_bssid` 的路由规则。 + +### 平台支持 + +| 平台 | 支持 | 备注 | +|-----------------|------------------|----------------| +| Android | :material-check: | 仅图形客户端 | +| Apple 平台 | :material-check: | 仅图形客户端 | +| Linux | :material-check: | 需要支持的守护进程 | +| Windows | :material-check: | WLAN API | +| 其他 | :material-close: | | + +### Linux + +!!! question "自 sing-box 1.13.0 起" + +支持以下后端,将按优先级顺序自动探测: + +| 后端 | 接口 | +|------------------|-------------| +| NetworkManager | D-Bus | +| IWD | D-Bus | +| wpa_supplicant | Unix socket | +| ConnMan | D-Bus | + +### Windows + +!!! question "自 sing-box 1.13.0 起" + +使用 Windows WLAN API。 diff --git a/docs/deprecated.md b/docs/deprecated.md index 270aaaea72..8e8498ae67 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -4,6 +4,150 @@ icon: material/delete-alert # Deprecated Feature List +## 1.14.0 + +#### Legacy `download_detour` remote rule-set option + +Legacy `download_detour` remote rule-set option is deprecated, +use `http_client` instead. + +Old field will be removed in sing-box 1.16.0. + +#### Implicit default HTTP client + +Implicit default HTTP client using the default outbound for remote rule-sets is deprecated. +Configure `http_clients` and `route.default_http_client` explicitly. + +Old behavior will be removed in sing-box 1.16.0. + +#### Inline ACME options in TLS + +Inline ACME options (`tls.acme`) are deprecated +and can be replaced by the ACME certificate provider, +check [Migration](../migration/#migrate-inline-acme-to-certificate-provider). + +Old fields will be removed in sing-box 1.16.0. + +#### Legacy `strategy` DNS rule action option + +Legacy `strategy` DNS rule action option is deprecated. + +Old fields will be removed in sing-box 1.16.0. + +#### Legacy `rule_set_ip_cidr_accept_empty` DNS rule item + +Legacy `rule_set_ip_cidr_accept_empty` DNS rule item is deprecated, +check [Migration](../migration/#migrate-address-filter-fields-to-response-matching). + +Old fields will be removed in sing-box 1.16.0. + +#### `independent_cache` DNS option + +`independent_cache` DNS option is deprecated. +The DNS cache now always keys by transport, making this option unnecessary, +check [Migration](../migration/#migrate-independent-dns-cache). + +Old fields will be removed in sing-box 1.16.0. + +#### `store_rdrc` cache file option + +`store_rdrc` cache file option is deprecated, +check [Migration](../migration/#migrate-store-rdrc). + +Old fields will be removed in sing-box 1.16.0. + +#### Legacy Address Filter Fields in DNS rules + +Legacy Address Filter Fields (`ip_cidr`, `ip_is_private` without `match_response`) +in DNS rules are deprecated, +check [Migration](../migration/#migrate-address-filter-fields-to-response-matching). + +Old behavior will be removed in sing-box 1.16.0. + +## 1.12.0 + +#### Legacy DNS server formats + +DNS servers are refactored, +check [Migration](../migration/#migrate-to-new-dns-server-formats). + +Old formats were removed in sing-box 1.14.0. + +#### `outbound` DNS rule item + +Legacy `outbound` DNS rules are deprecated +and can be replaced by dial fields, +check [Migration](../migration/#migrate-outbound-dns-rule-items-to-domain-resolver). + +#### Legacy ECH fields + +ECH support has been migrated to use stdlib in sing-box 1.12.0, +which does not come with support for PQ signature schemes, +so `pq_signature_schemes_enabled` has been deprecated and no longer works. + +Also, `dynamic_record_sizing_disabled` has nothing to do with ECH, +was added by mistake, has been deprecated and no longer works. + +These fields were removed in sing-box 1.13.0. + +## 1.11.0 + +#### Legacy special outbounds + +Legacy special outbounds (`block` / `dns`) are deprecated +and can be replaced by rule actions, +check [Migration](../migration/#migrate-legacy-special-outbounds-to-rule-actions). + +Old fields were removed in sing-box 1.13.0. + +#### Legacy inbound fields + +Legacy inbound fields (`inbound.` are deprecated +and can be replaced by rule actions, +check [Migration](../migration/#migrate-legacy-inbound-fields-to-rule-actions). + +Old fields were removed in sing-box 1.13.0. + +#### Destination override fields in direct outbound + +Destination override fields (`override_address` / `override_port`) in direct outbound are deprecated +and can be replaced by rule actions, +check [Migration](../migration/#migrate-destination-override-fields-to-route-options). + +Old fields were removed in sing-box 1.13.0. + +#### WireGuard outbound + +WireGuard outbound is deprecated and can be replaced by endpoint, +check [Migration](../migration/#migrate-wireguard-outbound-to-endpoint). + +Old outbound was removed in sing-box 1.13.0. + +#### GSO option in TUN + +GSO has no advantages for transparent proxy scenarios, is deprecated and no longer works in TUN. + +Old fields were removed in sing-box 1.13.0. + +## 1.10.0 + +#### TUN address fields are merged + +`inet4_address` and `inet6_address` are merged into `address`, +`inet4_route_address` and `inet6_route_address` are merged into `route_address`, +`inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`. + +Old fields were removed in sing-box 1.12.0. + +#### Match source rule items are renamed + +`rule_set_ipcidr_match_source` route and DNS rule items are renamed to +`rule_set_ip_cidr_match_source` and were removed in sing-box 1.11.0. + +#### Drop support for go1.18 and go1.19 + +Due to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile. + ## 1.8.0 #### Cache file and related features in Clash API @@ -13,23 +157,23 @@ check [Migration](/migration/#migrate-cache-file-from-clash-api-to-independent-o #### GeoIP -GeoIP is deprecated and may be removed in the future. +GeoIP is deprecated and was removed in sing-box 1.12.0. The maxmind GeoIP National Database, as an IP classification database, is not entirely suitable for traffic bypassing, and all existing implementations suffer from high memory usage and difficult management. -sing-box 1.8.0 introduces [Rule Set](/configuration/rule-set/), which can completely replace GeoIP, +sing-box 1.8.0 introduces [rule-set](/configuration/rule-set/), which can completely replace GeoIP, check [Migration](/migration/#migrate-geoip-to-rule-sets). #### Geosite -Geosite is deprecated and may be removed in the future. +Geosite is deprecated and was removed in sing-box 1.12.0. Geosite, the `domain-list-community` project maintained by V2Ray as an early traffic bypassing solution, suffers from a number of problems, including lack of maintenance, inaccurate rules, and difficult management. -sing-box 1.8.0 introduces [Rule Set](/configuration/rule-set/), which can completely replace Geosite, +sing-box 1.8.0 introduces [rule-set](/configuration/rule-set/), which can completely replace Geosite, check [Migration](/migration/#migrate-geosite-to-rule-sets). ## 1.6.0 diff --git a/docs/deprecated.zh.md b/docs/deprecated.zh.md index 69ec4bdd7f..887139bb66 100644 --- a/docs/deprecated.zh.md +++ b/docs/deprecated.zh.md @@ -4,32 +4,170 @@ icon: material/delete-alert # 废弃功能列表 +## 1.14.0 + +#### 旧版远程规则集 `download_detour` 选项 + +旧版远程规则集 `download_detour` 选项已废弃, +请使用 `http_client` 代替。 + +旧字段将在 sing-box 1.16.0 中被移除。 + +#### 隐式默认 HTTP 客户端 + +使用默认出站为远程规则集隐式创建默认 HTTP 客户端的行为已废弃。 +请显式配置 `http_clients` 和 `route.default_http_client`。 + +旧行为将在 sing-box 1.16.0 中被移除。 + +#### TLS 中的内联 ACME 选项 + +TLS 中的内联 ACME 选项(`tls.acme`)已废弃, +且可以通过 ACME 证书提供者替代, +参阅 [迁移指南](/zh/migration/#迁移内联-acme-到证书提供者)。 + +旧字段将在 sing-box 1.16.0 中被移除。 + +#### 旧版 DNS 规则动作 `strategy` 选项 + +旧版 DNS 规则动作 `strategy` 选项已废弃。 + +旧字段将在 sing-box 1.16.0 中被移除。 + +#### 旧版 `rule_set_ip_cidr_accept_empty` DNS 规则项 + +旧版 `rule_set_ip_cidr_accept_empty` DNS 规则项已废弃, +参阅[迁移指南](/zh/migration/#迁移地址筛选字段到响应匹配)。 + +旧字段将在 sing-box 1.16.0 中被移除。 + +#### `independent_cache` DNS 选项 + +`independent_cache` DNS 选项已废弃。 +DNS 缓存现在始终按传输分离,使此选项不再需要, +参阅[迁移指南](/zh/migration/#迁移-independent-dns-cache)。 + +旧字段将在 sing-box 1.16.0 中被移除。 + +#### `store_rdrc` 缓存文件选项 + +`store_rdrc` 缓存文件选项已废弃, +参阅[迁移指南](/zh/migration/#迁移-store_rdrc)。 + +旧字段将在 sing-box 1.16.0 中被移除。 + +#### 旧版地址筛选字段 (DNS 规则) + +旧版地址筛选字段(不使用 `match_response` 的 `ip_cidr`、`ip_is_private`)已废弃, +参阅[迁移指南](/zh/migration/#迁移地址筛选字段到响应匹配)。 + +旧行为将在 sing-box 1.16.0 中被移除。 + +## 1.12.0 + +#### 旧的 DNS 服务器格式 + +DNS 服务器已重构, +参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式). + +旧格式已在 sing-box 1.14.0 中被移除。 + +#### `outbound` DNS 规则项 + +旧的 `outbound` DNS 规则已废弃, +且可被拨号字段代替, +参阅 [迁移指南](/zh/migration/#迁移-outbound-dns-规则项到域解析选项). + +#### 旧的 ECH 字段 + +ECH 支持已在 sing-box 1.12.0 迁移至使用标准库,但标准库不支持后量子对等证书签名方案, +因此 `pq_signature_schemes_enabled` 已被弃用且不再工作。 + +另外,`dynamic_record_sizing_disabled` 与 ECH 无关,是错误添加的,现已弃用且不再工作。 + +相关字段已在 sing-box 1.13.0 中被移除。 + +## 1.11.0 + +#### 旧的特殊出站 + +旧的特殊出站(`block` / `dns`)已废弃且可以通过规则动作替代, +参阅 [迁移指南](/zh/migration/#迁移旧的特殊出站到规则动作)。 + +旧字段已在 sing-box 1.13.0 中被移除。 + +#### 旧的入站字段 + +旧的入站字段(`inbound.`)已废弃且可以通过规则动作替代, +参阅 [迁移指南](/zh/migration/#迁移旧的入站字段到规则动作)。 + +旧字段已在 sing-box 1.13.0 中被移除。 + +#### direct 出站中的目标地址覆盖字段 + +direct 出站中的目标地址覆盖字段(`override_address` / `override_port`)已废弃且可以通过规则动作替代, +参阅 [迁移指南](/zh/migration/#迁移-direct-出站中的目标地址覆盖字段到路由字段)。 + +旧字段已在 sing-box 1.13.0 中被移除。 + +#### WireGuard 出站 + +WireGuard 出站已废弃且可以通过端点替代, +参阅 [迁移指南](/zh/migration/#迁移-wireguard-出站到端点)。 + +旧出站已在 sing-box 1.13.0 中被移除。 + +#### TUN 的 GSO 字段 + +GSO 对透明代理场景没有优势,已废弃且在 TUN 中不再起作用。 + +旧字段已在 sing-box 1.13.0 中被移除。 + +## 1.10.0 + +#### Match source 规则项已重命名 + +`rule_set_ipcidr_match_source` 路由和 DNS 规则项已被重命名为 +`rule_set_ip_cidr_match_source` 且已在 sing-box 1.11.0 中被移除。 + +#### TUN 地址字段已合并 + +`inet4_address` 和 `inet6_address` 已合并为 `address`, +`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`, +`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。 + +旧字段已在 sing-box 1.12.0 中被移除。 + +#### 移除对 go1.18 和 go1.19 的支持 + +由于维护困难,sing-box 1.10.0 要求至少 Go 1.20 才能编译。 + ## 1.8.0 #### Clash API 中的 Cache file 及相关功能 Clash API 中的 `cache_file` 及相关功能已废弃且已迁移到独立的 `cache_file` 设置, -参阅 [迁移指南](/zh/migration/#clash-api)。 +参阅 [迁移指南](/zh/migration/#将缓存文件从-clash-api-迁移到独立选项)。 #### GeoIP -GeoIP 已废弃且可能在不久的将来移除。 +GeoIP 已废弃且已在 sing-box 1.12.0 中被移除。 maxmind GeoIP 国家数据库作为 IP 分类数据库,不完全适合流量绕过, 且现有的实现均存在内存使用大与管理困难的问题。 -sing-box 1.8.0 引入了[规则集](/configuration/rule-set/), -可以完全替代 GeoIP, 参阅 [迁移指南](/zh/migration/#geoip)。 +sing-box 1.8.0 引入了[规则集](/zh/configuration/rule-set/), +可以完全替代 GeoIP, 参阅 [迁移指南](/zh/migration/#迁移-geoip-到规则集)。 #### Geosite -Geosite 已废弃且可能在不久的将来移除。 +Geosite 已废弃且已在 sing-box 1.12.0 中被移除。 Geosite,即由 V2Ray 维护的 domain-list-community 项目,作为早期流量绕过解决方案, 存在着包括缺少维护、规则不准确和管理困难内的大量问题。 -sing-box 1.8.0 引入了[规则集](/configuration/rule-set/), -可以完全替代 Geosite,参阅 [迁移指南](/zh/migration/#geosite)。 +sing-box 1.8.0 引入了[规则集](/zh/configuration/rule-set/), +可以完全替代 Geosite,参阅 [迁移指南](/zh/migration/#迁移-geosite-到规则集)。 ## 1.6.0 diff --git a/docs/index.md b/docs/index.md index 8260109683..2af7222e7b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,12 +4,6 @@ description: Welcome to the wiki page for the sing-box project. # :material-home: Home -!!! failure "Help needed" - - Due to problems with our Apple developer account, sing-box apps on Apple platforms are temporarily unavailable for download or update. - - If your company or organization is willing to help us return to the App Store, please [contact us](mailto:contact@sagernet.org). - Welcome to the wiki page for the sing-box project. The universal proxy platform. diff --git a/docs/installation/build-from-source.md b/docs/installation/build-from-source.md index 3ee5b6c55f..48b53b178d 100644 --- a/docs/installation/build-from-source.md +++ b/docs/installation/build-from-source.md @@ -6,27 +6,18 @@ icon: material/file-code ## :material-graph: Requirements -Before sing-box 1.4.0: +### sing-box 1.11 -* Go 1.18.5 - 1.20.x +* Go 1.23.1 - ~ -Since sing-box 1.4.0: +### sing-box 1.10 -* Go 1.18.5 - ~ -* Go 1.20.0 - ~ with tag `with_quic` enabled +* Go 1.20.0 - ~ -Since sing-box 1.5.0: +### sing-box 1.9 -* Go 1.18.5 - ~ -* Go 1.20.0 - ~ with tag `with_quic` or `with_ech` enabled - -Since sing-box 1.8.0: - -* Go 1.18.5 - ~ -* Go 1.20.0 - ~ with tag `with_quic`, or `with_utls` enabled -* Go 1.21.0 - ~ with tag `with_ech` enabled - -You can download and install Go from: https://go.dev/doc/install, latest version is recommended. +* Go 1.18.5 - 1.22.x +* Go 1.20.0 - 1.22.x with tag `with_quic`, or `with_utls` enabled ## :material-fast-forward: Simple Build @@ -54,19 +45,82 @@ go build -tags "tag_a tag_b" ./cmd/sing-box ## :material-folder-settings: Build Tags -| Build Tag | Enabled by default | Description | -|------------------------------------|--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `with_quic` | :material-check: | Build with QUIC support, see [QUIC and HTTP3 DNS transports](/configuration/dns/server/), [Naive inbound](/configuration/inbound/naive/), [Hysteria Inbound](/configuration/inbound/hysteria/), [Hysteria Outbound](/configuration/outbound/hysteria/) and [V2Ray Transport#QUIC](/configuration/shared/v2ray-transport#quic). | -| `with_grpc` | :material-close:️ | Build with standard gRPC support, see [V2Ray Transport#gRPC](/configuration/shared/v2ray-transport#grpc). | -| `with_dhcp` | :material-check: | Build with DHCP support, see [DHCP DNS transport](/configuration/dns/server/). | -| `with_wireguard` | :material-check: | Build with WireGuard support, see [WireGuard outbound](/configuration/outbound/wireguard/). | -| `with_ech` | :material-check: | Build with TLS ECH extension support for TLS outbound, see [TLS](/configuration/shared/tls#ech). | -| `with_utls` | :material-check: | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/configuration/shared/tls#utls). | -| `with_reality_server` | :material-check: | Build with reality TLS server support, see [TLS](/configuration/shared/tls/). | -| `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/). | -| `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). | -| `with_v2ray_api` | :material-close:️ | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). | -| `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). | -| `with_embedded_tor` (CGO required) | :material-close:️ | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). | +| Build Tag | Enabled by default | Description | +|------------------------------------|----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `with_quic` | :material-check: | Build with QUIC support, see [QUIC and HTTP3 DNS transports](/configuration/dns/server/), [Naive inbound](/configuration/inbound/naive/), [Hysteria Inbound](/configuration/inbound/hysteria/), [Hysteria Outbound](/configuration/outbound/hysteria/) and [V2Ray Transport#QUIC](/configuration/shared/v2ray-transport#quic). | +| `with_grpc` | :material-close:️ | Build with standard gRPC support, see [V2Ray Transport#gRPC](/configuration/shared/v2ray-transport#grpc). | +| `with_dhcp` | :material-check: | Build with DHCP support, see [DHCP DNS transport](/configuration/dns/server/). | +| `with_wireguard` | :material-check: | Build with WireGuard support, see [WireGuard outbound](/configuration/outbound/wireguard/). | +| `with_utls` | :material-check: | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/configuration/shared/tls#utls). | +| `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/). | +| `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). | +| `with_v2ray_api` | :material-close:️ | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). | +| `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). | +| `with_embedded_tor` (CGO required) | :material-close:️ | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). | +| `with_tailscale` | :material-check: | Build with Tailscale support, see [Tailscale endpoint](/configuration/endpoint/tailscale). | +| `with_ccm` | :material-check: | Build with Claude Code Multiplexer service support. | +| `with_ocm` | :material-check: | Build with OpenAI Codex Multiplexer service support. | +| `with_naive_outbound` | :material-check: | Build with NaiveProxy outbound support, see [NaiveProxy outbound](/configuration/outbound/naive/). | +| `with_cloudflared` | :material-check: | Build with Cloudflare Tunnel inbound support, see [Cloudflared inbound](/configuration/inbound/cloudflared/). | +| `badlinkname` | :material-check: | Enable `go:linkname` access to internal standard library functions. Required because the Go standard library does not expose many low-level APIs needed by this project, and reimplementing them externally is impractical. Used for kTLS (kernel TLS offload) and raw TLS record manipulation. | +| `tfogo_checklinkname0` | :material-check: | Companion to `badlinkname`. Go 1.23+ enforces `go:linkname` restrictions via the linker; this tag signals the build uses `-checklinkname=0` to bypass that enforcement. | It is not recommended to change the default build tag list unless you really know what you are adding. + +## :material-wrench: Linker Flags + +The following `-ldflags` are used in official builds: + +| Flag | Description | +|-------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `-X 'internal/godebug.defaultGODEBUG=multipathtcp=0'` | Go 1.24 enabled Multipath TCP for listeners by default (`multipathtcp=2`). This may cause errors on low-level sockets, and sing-box has its own MPTCP control (`tcp_multi_path` option). This flag disables the Go default. | +| `-checklinkname=0` | Go 1.23+ linker rejects unauthorized `go:linkname` usage. This flag disables the check, required together with the `badlinkname` build tag. | + +## :material-package-variant: For Downstream Packagers + +The default build tag lists and linker flags are available as files in the repository for downstream packagers to reference directly: + +| File | Description | +|------|-------------| +| `release/DEFAULT_BUILD_TAGS` | Default for Linux (common architectures), Darwin, and Android. | +| `release/DEFAULT_BUILD_TAGS_WINDOWS` | Default for Windows (includes `with_purego`). | +| `release/DEFAULT_BUILD_TAGS_OTHERS` | Default for other platforms (no `with_naive_outbound`). | +| `release/LDFLAGS` | Required linker flags (see above). | + +## :material-layers: with_naive_outbound + +NaiveProxy outbound requires special build configurations depending on your target platform. + +### Supported Platforms + +| Platform | Architectures | Mode | Requirements | +|-----------------|--------------------------------------------------------|--------|-----------------------------------------------------------------| +| Linux | amd64, arm64 | purego | None (library included in official releases) | +| Linux | 386, amd64, arm, arm64, mipsle, mips64le, riscv64, loong64 | CGO | Chromium toolchain, glibc >= 2.31 (loong64: >= 2.36) at runtime | +| Linux (musl) | 386, amd64, arm, arm64, mipsle, riscv64, loong64 | CGO | Chromium toolchain | +| Windows | amd64, arm64 | purego | None (library included in official releases) | +| Apple platforms | * | CGO | Xcode | +| Android | * | CGO | Android NDK | + +### Windows + +Use `with_purego` tag. + +For official releases, `libcronet.dll` is included in the archive. For self-built binaries, download from [cronet-go releases](https://github.com/sagernet/cronet-go/releases) and place in the same directory as `sing-box.exe` or in a directory listed in `PATH`. + +### Linux (purego, amd64/arm64 only) + +Use `with_purego` tag. + +For official releases, `libcronet.so` is included in the archive. For self-built binaries, download from [cronet-go releases](https://github.com/sagernet/cronet-go/releases) and place in the same directory as sing-box binary or in system library path. + +### Linux (CGO) + +See [cronet-go](https://github.com/sagernet/cronet-go#linux-build-instructions). + +- **glibc build**: Requires glibc >= 2.31 at runtime +- **musl build**: Use `with_musl` tag, statically linked, no runtime requirements + +### Apple platforms / Android + +See [cronet-go](https://github.com/sagernet/cronet-go). diff --git a/docs/installation/build-from-source.zh.md b/docs/installation/build-from-source.zh.md index e5da3e3379..4ffebdc65d 100644 --- a/docs/installation/build-from-source.zh.md +++ b/docs/installation/build-from-source.zh.md @@ -6,25 +6,20 @@ icon: material/file-code ## :material-graph: 要求 -sing-box 1.4.0 前: +### sing-box 1.11 -* Go 1.18.5 - 1.20.x +* Go 1.23.1 - ~ -从 sing-box 1.4.0: +### sing-box 1.10 -* Go 1.18.5 - ~ -* Go 1.20.0 - ~ 如果启用构建标记 `with_quic` +* Go 1.20.0 - ~ +* Go 1.21.0 - ~ with tag `with_ech` enabled -从 sing-box 1.5.0: +### sing-box 1.9 -* Go 1.18.5 - ~ -* Go 1.20.0 - ~ 如果启用构建标记 `with_quic` 或 `with_ech` - -从 sing-box 1.8.0: - -* Go 1.18.5 - ~ -* Go 1.20.0 - ~ 如果启用构建标记 `with_quic` 或 `with_utls` -* Go 1.20.1 - ~ 如果启用构建标记 `with_ech` +* Go 1.18.5 - 1.22.x +* Go 1.20.0 - 1.22.x with tag `with_quic`, or `with_utls` enabled +* Go 1.21.0 - 1.22.x with tag `with_ech` enabled 您可以从 https://go.dev/doc/install 下载并安装 Go,推荐使用最新版本。 @@ -56,17 +51,80 @@ go build -tags "tag_a tag_b" ./cmd/sing-box | 构建标记 | 默认启动 | 说明 | |------------------------------------|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `with_quic` | :material-check: | Build with QUIC support, see [QUIC and HTTP3 DNS transports](/configuration/dns/server/), [Naive inbound](/configuration/inbound/naive/), [Hysteria Inbound](/configuration/inbound/hysteria/), [Hysteria Outbound](/configuration/outbound/hysteria/) and [V2Ray Transport#QUIC](/configuration/shared/v2ray-transport#quic). | -| `with_grpc` | :material-close:️ | Build with standard gRPC support, see [V2Ray Transport#gRPC](/configuration/shared/v2ray-transport#grpc). | -| `with_dhcp` | :material-check: | Build with DHCP support, see [DHCP DNS transport](/configuration/dns/server/). | -| `with_wireguard` | :material-check: | Build with WireGuard support, see [WireGuard outbound](/configuration/outbound/wireguard/). | -| `with_ech` | :material-check: | Build with TLS ECH extension support for TLS outbound, see [TLS](/configuration/shared/tls#ech). | -| `with_utls` | :material-check: | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/configuration/shared/tls#utls). | -| `with_reality_server` | :material-check: | Build with reality TLS server support, see [TLS](/configuration/shared/tls/). | -| `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/). | -| `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). | -| `with_v2ray_api` | :material-close:️ | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). | -| `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). | -| `with_embedded_tor` (CGO required) | :material-close:️ | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). | +| `with_quic` | :material-check: | Build with QUIC support, see [QUIC and HTTP3 DNS transports](/zh/configuration/dns/server/), [Naive inbound](/zh/configuration/inbound/naive/), [Hysteria Inbound](/zh/configuration/inbound/hysteria/), [Hysteria Outbound](/zh/configuration/outbound/hysteria/) and [V2Ray Transport#QUIC](/zh/configuration/shared/v2ray-transport#quic). | +| `with_grpc` | :material-close:️ | Build with standard gRPC support, see [V2Ray Transport#gRPC](/zh/configuration/shared/v2ray-transport#grpc). | +| `with_dhcp` | :material-check: | Build with DHCP support, see [DHCP DNS transport](/zh/configuration/dns/server/). | +| `with_wireguard` | :material-check: | Build with WireGuard support, see [WireGuard outbound](/zh/configuration/outbound/wireguard/). | +| `with_utls` | :material-check: | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/zh/configuration/shared/tls#utls). | +| `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/zh/configuration/shared/tls/). | +| `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/zh/configuration/experimental#clash-api-fields). | +| `with_v2ray_api` | :material-close:️ | Build with V2Ray API support, see [Experimental](/zh/configuration/experimental#v2ray-api-fields). | +| `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/zh/configuration/inbound/tun#stack) and [WireGuard outbound](/zh/configuration/outbound/wireguard#system_interface). | +| `with_embedded_tor` (CGO required) | :material-close:️ | Build with embedded Tor support, see [Tor outbound](/zh/configuration/outbound/tor/). | +| `with_tailscale` | :material-check: | 构建 Tailscale 支持,参阅 [Tailscale 端点](/zh/configuration/endpoint/tailscale)。 | +| `with_ccm` | :material-check: | 构建 Claude Code Multiplexer 服务支持。 | +| `with_ocm` | :material-check: | 构建 OpenAI Codex Multiplexer 服务支持。 | +| `with_naive_outbound` | :material-check: | 构建 NaiveProxy 出站支持,参阅 [NaiveProxy 出站](/zh/configuration/outbound/naive/)。 | +| `with_cloudflared` | :material-check: | 构建 Cloudflare Tunnel 入站支持,参阅 [Cloudflared 入站](/zh/configuration/inbound/cloudflared/)。 | +| `badlinkname` | :material-check: | 启用 `go:linkname` 以访问标准库内部函数。Go 标准库未提供本项目需要的许多底层 API,且在外部重新实现不切实际。用于 kTLS(内核 TLS 卸载)和原始 TLS 记录操作。 | +| `tfogo_checklinkname0` | :material-check: | `badlinkname` 的伴随标记。Go 1.23+ 链接器强制限制 `go:linkname` 使用;此标记表示构建使用 `-checklinkname=0` 以绕过该限制。 | 除非您确实知道您正在启用什么,否则不建议更改默认构建标签列表。 + +## :material-wrench: 链接器标志 + +以下 `-ldflags` 在官方构建中使用: + +| 标志 | 说明 | +|-------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `-X 'internal/godebug.defaultGODEBUG=multipathtcp=0'` | Go 1.24 默认为监听器启用 Multipath TCP(`multipathtcp=2`)。这可能在底层 socket 上导致错误,且 sing-box 有自己的 MPTCP 控制(`tcp_multi_path` 选项)。此标志禁用 Go 的默认行为。 | +| `-checklinkname=0` | Go 1.23+ 链接器拒绝未授权的 `go:linkname` 使用。此标志禁用该检查,需要与 `badlinkname` 构建标记一起使用。 | + +## :material-package-variant: 下游打包者 + +默认构建标签列表和链接器标志以文件形式存放在仓库中,供下游打包者直接引用: + +| 文件 | 说明 | +|------|------| +| `release/DEFAULT_BUILD_TAGS` | Linux(常见架构)、Darwin 和 Android 的默认标签。 | +| `release/DEFAULT_BUILD_TAGS_WINDOWS` | Windows 的默认标签(包含 `with_purego`)。 | +| `release/DEFAULT_BUILD_TAGS_OTHERS` | 其他平台的默认标签(不含 `with_naive_outbound`)。 | +| `release/LDFLAGS` | 必需的链接器标志(参见上文)。 | + +## :material-layers: with_naive_outbound + +NaiveProxy 出站需要根据目标平台进行特殊的构建配置。 + +### 支持的平台 + +| 平台 | 架构 | 模式 | 要求 | +|--------------|----------------------------------------------------------|--------|-----------------------------------------------------| +| Linux | amd64, arm64 | purego | 无(官方发布版本已包含库文件) | +| Linux | 386, amd64, arm, arm64, mipsle, mips64le, riscv64, loong64 | CGO | Chromium 工具链,运行时需要 glibc >= 2.31(loong64: >= 2.36) | +| Linux (musl) | 386, amd64, arm, arm64, mipsle, riscv64, loong64 | CGO | Chromium 工具链 | +| Windows | amd64, arm64 | purego | 无(官方发布版本已包含库文件) | +| Apple 平台 | * | CGO | Xcode | +| Android | * | CGO | Android NDK | + +### Windows + +使用 `with_purego` 标记。 + +官方发布版本已包含 `libcronet.dll`。自行构建时,从 [cronet-go releases](https://github.com/sagernet/cronet-go/releases) 下载并放置在 `sing-box.exe` 相同目录或 `PATH` 中的任意目录。 + +### Linux (purego, 仅 amd64/arm64) + +使用 `with_purego` 标记。 + +官方发布版本已包含 `libcronet.so`。自行构建时,从 [cronet-go releases](https://github.com/sagernet/cronet-go/releases) 下载并放置在 sing-box 二进制文件相同目录或系统库路径中。 + +### Linux (CGO) + +参阅 [cronet-go](https://github.com/sagernet/cronet-go#linux-build-instructions)。 + +- **glibc 构建**:运行时需要 glibc >= 2.31 +- **musl 构建**:使用 `with_musl` 标记,静态链接,无运行时要求 + +### Apple 平台 / Android + +参阅 [cronet-go](https://github.com/sagernet/cronet-go)。 diff --git a/docs/installation/package-manager.md b/docs/installation/package-manager.md index bb5b6abb47..fe59c73aa6 100644 --- a/docs/installation/package-manager.md +++ b/docs/installation/package-manager.md @@ -9,49 +9,56 @@ icon: material/package === ":material-debian: Debian / APT" ```bash - sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc - sudo chmod a+r /etc/apt/keyrings/sagernet.asc - echo "deb [arch=`dpkg --print-architecture` signed-by=/etc/apt/keyrings/sagernet.asc] https://deb.sagernet.org/ * *" | \ - sudo tee /etc/apt/sources.list.d/sagernet.list > /dev/null - sudo apt-get update - sudo apt-get install sing-box # or sing-box-beta + sudo mkdir -p /etc/apt/keyrings && + sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc && + sudo chmod a+r /etc/apt/keyrings/sagernet.asc && + echo ' + Types: deb + URIs: https://deb.sagernet.org/ + Suites: * + Components: * + Enabled: yes + Signed-By: /etc/apt/keyrings/sagernet.asc + ' | sudo tee /etc/apt/sources.list.d/sagernet.sources && + sudo apt-get update && + sudo apt-get install sing-box # or sing-box-beta ``` -=== ":material-redhat: Redhat / DNF" +=== ":material-redhat: Redhat / DNF 5" ```bash - sudo dnf -y install dnf-plugins-core - sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo + sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo && sudo dnf install sing-box # or sing-box-beta ``` -=== ":material-redhat: CentOS / YUM" +=== ":material-redhat: Redhat / DNF 4" ```bash - sudo yum install -y yum-utils - sudo yum-config-manager --add-repo https://sing-box.app/sing-box.repo - sudo yum install sing-box # or sing-box-beta + sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo && + sudo dnf -y install dnf-plugins-core && + sudo dnf install sing-box # or sing-box-beta ``` ## :material-download-box: Manual Installation -=== ":material-debian: Debian / DEB" +The script download and install the latest package from GitHub releases +for deb or rpm based Linux distributions, ArchLinux and OpenWrt. - ```bash - bash <(curl -fsSL https://sing-box.app/deb-install.sh) - ``` +```shell +curl -fsSL https://sing-box.app/install.sh | sh +``` -=== ":material-redhat: Redhat / RPM" +or latest beta: - ```bash - bash <(curl -fsSL https://sing-box.app/rpm-install.sh) - ``` +```shell +curl -fsSL https://sing-box.app/install.sh | sh -s -- --beta +``` -=== ":simple-archlinux: Archlinux / PKG" +or specific version: - ```bash - bash <(curl -fsSL https://sing-box.app/arch-install.sh) - ``` +```shell +curl -fsSL https://sing-box.app/install.sh | sh -s -- --version +``` ## :material-book-lock-open: Managed Installation @@ -63,6 +70,7 @@ icon: material/package | nixpkgs | NixOS | `nix-env -iA nixos.sing-box` | [![nixpkgs unstable package](https://repology.org/badge/version-for-repo/nix_unstable/sing-box.svg)][nixpkgs] | | Homebrew | macOS / Linux | `brew install sing-box` | [![Homebrew package](https://repology.org/badge/version-for-repo/homebrew/sing-box.svg)][brew] | | APK | Alpine | `apk add sing-box` | [![Alpine Linux Edge package](https://repology.org/badge/version-for-repo/alpine_edge/sing-box.svg)][alpine] | + | DEB | AOSC | `apt install sing-box` | [![AOSC package](https://repology.org/badge/version-for-repo/aosc/sing-box.svg)][aosc] | === ":material-apple: macOS" @@ -90,6 +98,22 @@ icon: material/package |------------|----------|------------------------|--------------------------------------------------------------------------------------------| | FreshPorts | FreeBSD | `pkg install sing-box` | [![FreeBSD port](https://repology.org/badge/version-for-repo/freebsd/sing-box.svg)][ports] | +## :material-alert: Problematic Sources + +| Type | Platform | Link | Promblem(s) | +|------------|----------|-------------------------------------------------------------------------------------------|-----------------------------------------| +| DEB | AOSC | [aosc-os-abbs](https://github.com/AOSC-Dev/aosc-os-abbs/tree/stable/app-network/sing-box) | Problematic build tag list modification | +| Homebrew | / | [homebrew-core][brew] | Problematic build tag list modification | +| Termux | Android | [termux-packages][termux] | Problematic build tag list modification | +| FreshPorts | FreeBSD | [FreeBSD ports][ports] | Old Go (go1.20) | + +If you are a user of them, please report issues to them: + +1. Please do not modify release build tags without full understanding of the related functionality: enabling non-default + labels may result in decreased performance; the lack of default labels may cause user confusion. +2. sing-box supports compiling with some older Go versions, but it is not recommended (especially versions that are no + longer supported by Go). + ## :material-book-multiple: Service Management For Linux systems with [systemd][systemd], usually the installation already includes a sing-box service, @@ -128,4 +152,6 @@ you can manage the service using the following command: [ports]: https://www.freshports.org/net/sing-box +[aosc]: https://packages.aosc.io/packages/sing-box + [systemd]: https://systemd.io/ diff --git a/docs/installation/package-manager.zh.md b/docs/installation/package-manager.zh.md index a786450a0d..f942e92465 100644 --- a/docs/installation/package-manager.zh.md +++ b/docs/installation/package-manager.zh.md @@ -9,70 +9,77 @@ icon: material/package === ":material-debian: Debian / APT" ```bash - sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc - sudo chmod a+r /etc/apt/keyrings/sagernet.asc - echo "deb [arch=`dpkg --print-architecture` signed-by=/etc/apt/keyrings/sagernet.asc] https://deb.sagernet.org/ * *" | \ - sudo tee /etc/apt/sources.list.d/sagernet.list > /dev/null - sudo apt-get update - sudo apt-get install sing-box # or sing-box-beta + sudo mkdir -p /etc/apt/keyrings && + sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc && + sudo chmod a+r /etc/apt/keyrings/sagernet.asc && + echo ' + Types: deb + URIs: https://deb.sagernet.org/ + Suites: * + Components: * + Enabled: yes + Signed-By: /etc/apt/keyrings/sagernet.asc + ' | sudo tee /etc/apt/sources.list.d/sagernet.sources && + sudo apt-get update && + sudo apt-get install sing-box # or sing-box-beta ``` -=== ":material-redhat: Redhat / DNF" +=== ":material-redhat: Redhat / DNF 5" ```bash - sudo dnf -y install dnf-plugins-core - sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo + sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo && sudo dnf install sing-box # or sing-box-beta ``` -=== ":material-redhat: CentOS / YUM" +=== ":material-redhat: Redhat / DNF 4" ```bash - sudo yum install -y yum-utils - sudo yum-config-manager --add-repo https://sing-box.app/sing-box.repo - sudo yum install sing-box # or sing-box-beta + sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo && + sudo dnf -y install dnf-plugins-core && + sudo dnf install sing-box # or sing-box-beta ``` ## :material-download-box: 手动安装 -=== ":material-debian: Debian / DEB" +该脚本从 GitHub 发布中下载并安装最新的软件包,适用于基于 deb 或 rpm 的 Linux 发行版、ArchLinux 和 OpenWrt。 - ```bash - bash <(curl -fsSL https://sing-box.app/deb-install.sh) - ``` +```shell +curl -fsSL https://sing-box.app/install.sh | sh +``` -=== ":material-redhat: Redhat / RPM" +或最新测试版: - ```bash - bash <(curl -fsSL https://sing-box.app/rpm-install.sh) - ``` +```shell +curl -fsSL https://sing-box.app/install.sh | sh -s -- --beta +``` -=== ":simple-archlinux: Archlinux / PKG" +或指定版本: - ```bash - bash <(curl -fsSL https://sing-box.app/arch-install.sh) - ``` +```shell +curl -fsSL https://sing-box.app/install.sh | sh -s -- --version +``` ## :material-book-lock-open: 托管安装 === ":material-linux: Linux" - | 类型 | 平台 | 链接 | 命令 | + | 类型 | 平台 | 命令 | 链接 | |----------|---------------|------------------------------|---------------------------------------------------------------------------------------------------------------| | AUR | Arch Linux | `? -S sing-box` | [![AUR package](https://repology.org/badge/version-for-repo/aur/sing-box.svg)][aur] | | nixpkgs | NixOS | `nix-env -iA nixos.sing-box` | [![nixpkgs unstable package](https://repology.org/badge/version-for-repo/nix_unstable/sing-box.svg)][nixpkgs] | | Homebrew | macOS / Linux | `brew install sing-box` | [![Homebrew package](https://repology.org/badge/version-for-repo/homebrew/sing-box.svg)][brew] | | APK | Alpine | `apk add sing-box` | [![Alpine Linux Edge package](https://repology.org/badge/version-for-repo/alpine_edge/sing-box.svg)][alpine] | + | DEB | AOSC | `apt install sing-box` | [![AOSC package](https://repology.org/badge/version-for-repo/aosc/sing-box.svg)][aosc] | === ":material-apple: macOS" - | 类型 | 平台 | 链接 | 命令 | + | 类型 | 平台 | 命令 | 链接 | |----------|-------|-------------------------|------------------------------------------------------------------------------------------------| | Homebrew | macOS | `brew install sing-box` | [![Homebrew package](https://repology.org/badge/version-for-repo/homebrew/sing-box.svg)][brew] | === ":material-microsoft-windows: Windows" - | 类型 | 平台 | 链接 | 命令 | + | 类型 | 平台 | 命令 | 链接 | |------------|---------|---------------------------|-----------------------------------------------------------------------------------------------------| | Scoop | Windows | `scoop install sing-box` | [![Scoop package](https://repology.org/badge/version-for-repo/scoop/sing-box.svg)][scoop] | | Chocolatey | Windows | `choco install sing-box` | [![Chocolatey package](https://repology.org/badge/version-for-repo/chocolatey/sing-box.svg)][choco] | @@ -80,16 +87,30 @@ icon: material/package === ":material-android: Android" - | 类型 | 平台 | 链接 | 命令 | + | 类型 | 平台 | 命令 | 链接 | |--------|---------|--------------------|----------------------------------------------------------------------------------------------| | Termux | Android | `pkg add sing-box` | [![Termux package](https://repology.org/badge/version-for-repo/termux/sing-box.svg)][termux] | === ":material-freebsd: FreeBSD" - | 类型 | 平台 | 链接 | 命令 | + | 类型 | 平台 | 命令 | 链接 | |------------|---------|------------------------|--------------------------------------------------------------------------------------------| | FreshPorts | FreeBSD | `pkg install sing-box` | [![FreeBSD port](https://repology.org/badge/version-for-repo/freebsd/sing-box.svg)][ports] | +## :material-alert: 存在问题的源 + +| 类型 | 平台 | 链接 | 原因 | +|------------|---------|-------------------------------------------------------------------------------------------|-----------------| +| DEB | AOSC | [aosc-os-abbs](https://github.com/AOSC-Dev/aosc-os-abbs/tree/stable/app-network/sing-box) | 存在问题的构建标志列表修改 | +| Homebrew | / | [homebrew-core][brew] | 存在问题的构建标志列表修改 | +| Termux | Android | [termux-packages][termux] | 存在问题的构建标志列表修改 | +| FreshPorts | FreeBSD | [FreeBSD ports][ports] | 太旧的 Go (go1.20) | + +如果您是其用户,请向他们报告问题: + +1. 在未完全了解相关功能的情况下,请勿修改发布版本标签:启用非默认标签可能会导致性能下降;缺少默认标签可能会引起用户混淆。 +2. sing-box 支持使用一些较旧的 Go 版本进行编译,但不推荐使用(特别是已不再受 Go 支持的版本)。 + ## :material-book-multiple: 服务管理 对于带有 [systemd][systemd] 的 Linux 系统,通常安装已经包含 sing-box 服务, @@ -124,4 +145,6 @@ icon: material/package [ports]: https://www.freshports.org/net/sing-box +[aosc]: https://packages.aosc.io/packages/sing-box + [systemd]: https://systemd.io/ diff --git a/docs/installation/tools/install.sh b/docs/installation/tools/install.sh new file mode 100755 index 0000000000..7bfbc365aa --- /dev/null +++ b/docs/installation/tools/install.sh @@ -0,0 +1,127 @@ +#!/bin/sh + +download_beta=false +download_version="" + +while [ $# -gt 0 ]; do + case "$1" in + --beta) + download_beta=true + shift + ;; + --version) + shift + if [ $# -eq 0 ]; then + echo "Missing argument for --version" + echo "Usage: $0 [--beta] [--version ]" + exit 1 + fi + download_version="$1" + shift + ;; + *) + echo "Unknown argument: $1" + echo "Usage: $0 [--beta] [--version ]" + exit 1 + ;; + esac +done + +if command -v pacman >/dev/null 2>&1; then + os="linux" + arch=$(uname -m) + package_suffix=".pkg.tar.zst" + package_install="pacman -U --noconfirm" +elif command -v dpkg >/dev/null 2>&1; then + os="linux" + arch=$(dpkg --print-architecture) + package_suffix=".deb" + package_install="dpkg -i" +elif command -v dnf >/dev/null 2>&1; then + os="linux" + arch=$(uname -m) + package_suffix=".rpm" + package_install="dnf install -y" +elif command -v rpm >/dev/null 2>&1; then + os="linux" + arch=$(uname -m) + package_suffix=".rpm" + package_install="rpm -i" +elif command -v apk >/dev/null 2>&1 && [ -f /etc/os-release ] && grep -q OPENWRT_ARCH /etc/os-release; then + os="openwrt" + . /etc/os-release + arch="$OPENWRT_ARCH" + package_suffix=".apk" + package_install="apk add --allow-untrusted" +elif command -v apk >/dev/null 2>&1; then + os="linux" + arch=$(apk --print-arch) + package_suffix=".apk" + package_install="apk add --allow-untrusted" +elif command -v opkg >/dev/null 2>&1; then + os="openwrt" + . /etc/os-release + arch="$OPENWRT_ARCH" + package_suffix=".ipk" + package_install="opkg update && opkg install" +else + echo "Missing supported package manager." + exit 1 +fi + +if [ -z "$download_version" ]; then + if [ "$download_beta" != "true" ]; then + if [ -n "$GITHUB_TOKEN" ]; then + latest_release=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/SagerNet/sing-box/releases/latest) + else + latest_release=$(curl -s https://api.github.com/repos/SagerNet/sing-box/releases/latest) + fi + curl_exit_status=$? + if [ $curl_exit_status -ne 0 ]; then + exit $curl_exit_status + fi + if [ "$(echo "$latest_release" | grep tag_name | wc -l)" -eq 0 ]; then + echo "$latest_release" + exit 1 + fi + download_version=$(echo "$latest_release" | grep tag_name | head -n 1 | awk -F: '{print $2}' | sed 's/[", v]//g') + else + if [ -n "$GITHUB_TOKEN" ]; then + latest_release=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/SagerNet/sing-box/releases) + else + latest_release=$(curl -s https://api.github.com/repos/SagerNet/sing-box/releases) + fi + curl_exit_status=$? + if [ $curl_exit_status -ne 0 ]; then + exit $curl_exit_status + fi + if [ "$(echo "$latest_release" | grep tag_name | wc -l)" -eq 0 ]; then + echo "$latest_release" + exit 1 + fi + download_version=$(echo "$latest_release" | grep tag_name | head -n 1 | awk -F: '{print $2}' | sed 's/[", v]//g') + fi +fi + +package_name="sing-box_${download_version}_${os}_${arch}${package_suffix}" +package_url="https://github.com/SagerNet/sing-box/releases/download/v${download_version}/${package_name}" + +echo "Downloading $package_url" +if [ -n "$GITHUB_TOKEN" ]; then + curl --fail -Lo "$package_name" -H "Authorization: token ${GITHUB_TOKEN}" "$package_url" +else + curl --fail -Lo "$package_name" "$package_url" +fi + +curl_exit_status=$? +if [ $curl_exit_status -ne 0 ]; then + exit $curl_exit_status +fi + +if command -v sudo >/dev/null 2>&1; then + package_install="sudo $package_install" +fi + +echo "$package_install $package_name" +sh -c "$package_install \"$package_name\"" +rm -f "$package_name" diff --git a/docs/manual/proxy-protocol/hysteria2.md b/docs/manual/proxy-protocol/hysteria2.md index 9cc07e6f12..fe605b4576 100644 --- a/docs/manual/proxy-protocol/hysteria2.md +++ b/docs/manual/proxy-protocol/hysteria2.md @@ -4,16 +4,17 @@ icon: material/lightning-bolt # Hysteria 2 -The most popular Chinese-made simple protocol based on QUIC, the selling point is Brutal, -a congestion control algorithm that can resist packet loss by manually specifying the required rate by the user. +Hysteria 2 is a simple, Chinese-made protocol based on QUIC. +The selling point is Brutal, a congestion control algorithm that +tries to achieve a user-defined bandwidth despite packet loss. !!! warning - Even though GFW rarely blocks UDP-based proxies, such protocols actually have far more characteristics than TCP based proxies. + Even though GFW rarely blocks UDP-based proxies, such protocols actually have far more obvious characteristics than TCP based proxies. -| Specification | Binary Characteristics | Active Detect Hiddenness | -|---------------------------------------------------------------------------|------------------------|--------------------------| -| [hysteria.network](https://v2.hysteria.network/docs/developers/Protocol/) | :material-alert: | :material-check: | +| Specification | Resists passive detection | Resists active probes | +|---------------------------------------------------------------------------|---------------------------|-----------------------| +| [hysteria.network](https://v2.hysteria.network/docs/developers/Protocol/) | :material-alert: | :material-check: | ## :material-text-box-check: Password Generator @@ -44,7 +45,7 @@ To use sing-box with the official program, you need to fill in that combination Replace `up_mbps` and `down_mbps` values with the actual bandwidth of your server. === ":material-harddisk: With local certificate" - + ```json { "inbounds": [ diff --git a/docs/manual/proxy-protocol/shadowsocks.md b/docs/manual/proxy-protocol/shadowsocks.md index 94821d83d1..19f9d4cf93 100644 --- a/docs/manual/proxy-protocol/shadowsocks.md +++ b/docs/manual/proxy-protocol/shadowsocks.md @@ -4,15 +4,18 @@ icon: material/send # Shadowsocks -As the most well-known Chinese-made proxy protocol, -Shadowsocks exists in multiple versions, -but only AEAD 2022 ciphers TCP with multiplexing is recommended. - -| Ciphers | Specification | Cryptographic Security | Binary Characteristics | Active Detect Hiddenness | -|----------------|------------------------------------------------------------|------------------------|------------------------|--------------------------| -| Stream Ciphers | [shadowsocks.org](https://shadowsocks.org/doc/stream.html) | :material-alert: | :material-alert: | :material-alert: | -| AEAD | [shadowsocks.org](https://shadowsocks.org/doc/aead.html) | :material-check: | :material-alert: | :material-alert: | -| AEAD 2022 | [shadowsocks.org](https://shadowsocks.org/doc/sip022.html) | :material-check: | :material-check: | :material-help: | +Shadowsocks is the most well-known Chinese-made proxy protocol. +It exists in multiple versions, but only AEAD 2022 ciphers +over TCP with multiplexing is recommended. + +| Ciphers | Specification | Cryptographically sound | Resists passive detection | Resists active probes | +|----------------|------------------------------------------------------------|-------------------------|---------------------------|-----------------------| +| Stream Ciphers | [shadowsocks.org](https://shadowsocks.org/doc/stream.html) | :material-alert: | :material-alert: | :material-alert: | +| AEAD | [shadowsocks.org](https://shadowsocks.org/doc/aead.html) | :material-check: | :material-alert: | :material-alert: | +| AEAD 2022 | [shadowsocks.org](https://shadowsocks.org/doc/sip022.html) | :material-check: | :material-check: | :material-help: | + +(We strongly recommend using multiplexing to send UDP traffic over TCP, because +doing otherwise is vulnerable to passive detection.) ## :material-text-box-check: Password Generator diff --git a/docs/manual/proxy-protocol/trojan.md b/docs/manual/proxy-protocol/trojan.md index 4c2e16f17e..2bd63f5c8c 100644 --- a/docs/manual/proxy-protocol/trojan.md +++ b/docs/manual/proxy-protocol/trojan.md @@ -4,15 +4,14 @@ icon: material/horse # Trojan -As the most commonly used TLS proxy made in China, Trojan can be used in various combinations, -but only the combination of uTLS and multiplexing is recommended. +Trojan is the most commonly used TLS proxy made in China. It can be used in various combinations. -| Protocol and implementation combination | Specification | Binary Characteristics | Active Detect Hiddenness | -|-----------------------------------------|----------------------------------------------------------------------|------------------------|--------------------------| -| Origin / trojan-gfw | [trojan-gfw.github.io](https://trojan-gfw.github.io/trojan/protocol) | :material-check: | :material-check: | -| Basic Go implementation | / | :material-alert: | :material-check: | -| with privates transport by V2Ray | No formal definition | :material-alert: | :material-alert: | -| with uTLS enabled | No formal definition | :material-help: | :material-check: | +| Protocol and implementation combination | Specification | Resists passive detection | Resists active probes | +|-----------------------------------------|----------------------------------------------------------------------|---------------------------|-----------------------| +| Origin / trojan-gfw | [trojan-gfw.github.io](https://trojan-gfw.github.io/trojan/protocol) | :material-check: | :material-check: | +| Basic Go implementation | / | :material-alert: | :material-check: | +| with privates transport by V2Ray | No formal definition | :material-alert: | :material-alert: | +| with uTLS enabled | No formal definition | :material-help: | :material-check: | ## :material-text-box-check: Password Generator @@ -140,11 +139,7 @@ but only the combination of uTLS and multiplexing is recommended. "password": "password", "tls": { "enabled": true, - "server_name": "example.org", - "utls": { - "enabled": true, - "fingerprint": "firefox" - } + "server_name": "example.org" }, "multiplex": { "enabled": true @@ -171,11 +166,7 @@ but only the combination of uTLS and multiplexing is recommended. "tls": { "enabled": true, "server_name": "example.org", - "certificate_path": "/path/to/certificate.pem", - "utls": { - "enabled": true, - "fingerprint": "firefox" - } + "certificate_path": "/path/to/certificate.pem" }, "multiplex": { "enabled": true @@ -198,11 +189,7 @@ but only the combination of uTLS and multiplexing is recommended. "tls": { "enabled": true, "server_name": "example.org", - "insecure": true, - "utls": { - "enabled": true, - "fingerprint": "firefox" - } + "insecure": true }, "multiplex": { "enabled": true @@ -211,4 +198,3 @@ but only the combination of uTLS and multiplexing is recommended. ] } ``` - diff --git a/docs/manual/proxy/client.md b/docs/manual/proxy/client.md index 1cf5a1ce45..e8c80523d9 100644 --- a/docs/manual/proxy/client.md +++ b/docs/manual/proxy/client.md @@ -94,18 +94,13 @@ flowchart TB "servers": [ { "tag": "google", - "address": "tls://8.8.8.8" + "type": "tls", + "server": "8.8.8.8" }, { "tag": "local", - "address": "223.5.5.5", - "detour": "direct" - } - ], - "rules": [ - { - "outbound": "any", - "server": "local" + "type": "udp", + "server": "223.5.5.5" } ], "strategy": "ipv4_only" @@ -113,9 +108,10 @@ flowchart TB "inbounds": [ { "type": "tun", - "inet4_address": "172.19.0.1/30", + "address": ["172.19.0.1/30"], "auto_route": true, - "strict_route": false + // "auto_redirect": true, // On linux + "strict_route": true } ], "outbounds": [ @@ -123,25 +119,23 @@ flowchart TB { "type": "direct", "tag": "direct" - }, - { - "type": "dns", - "tag": "dns-out" } ], "route": { "rules": [ + { + "action": "sniff" + }, { "protocol": "dns", - "outbound": "dns-out" + "action": "hijack-dns" }, { - "geoip": [ - "private" - ], + "ip_is_private": true, "outbound": "direct" } ], + "default_domain_resolver": "local", "auto_detect_interface": true } } @@ -155,28 +149,23 @@ flowchart TB "servers": [ { "tag": "google", - "address": "tls://8.8.8.8" + "type": "tls", + "server": "8.8.8.8" }, { "tag": "local", - "address": "223.5.5.5", - "detour": "direct" - } - ], - "rules": [ - { - "outbound": "any", - "server": "local" + "type": "udp", + "server": "223.5.5.5" } ] }, "inbounds": [ { "type": "tun", - "inet4_address": "172.19.0.1/30", - "inet6_address": "fdfe:dcba:9876::1/126", + "address": ["172.19.0.1/30", "fdfe:dcba:9876::1/126"], "auto_route": true, - "strict_route": false + // "auto_redirect": true, // On linux + "strict_route": true } ], "outbounds": [ @@ -184,25 +173,23 @@ flowchart TB { "type": "direct", "tag": "direct" - }, - { - "type": "dns", - "tag": "dns-out" } ], "route": { "rules": [ + { + "action": "sniff" + }, { "protocol": "dns", - "outbound": "dns-out" + "action": "hijack-dns" }, { - "geoip": [ - "private" - ], + "ip_is_private": true, "outbound": "direct" } ], + "default_domain_resolver": "local", "auto_detect_interface": true } } @@ -216,23 +203,22 @@ flowchart TB "servers": [ { "tag": "google", - "address": "tls://8.8.8.8" + "type": "tls", + "server": "8.8.8.8" }, { "tag": "local", - "address": "223.5.5.5", - "detour": "direct" + "type": "udp", + "server": "223.5.5.5" }, { "tag": "remote", - "address": "fakeip" + "type": "fakeip", + "inet4_range": "198.18.0.0/15", + "inet6_range": "fc00::/18" } ], "rules": [ - { - "outbound": "any", - "server": "local" - }, { "query_type": [ "A", @@ -241,19 +227,14 @@ flowchart TB "server": "remote" } ], - "fakeip": { - "enabled": true, - "inet4_range": "198.18.0.0/15", - "inet6_range": "fc00::/18" - }, "independent_cache": true }, "inbounds": [ { "type": "tun", - "inet4_address": "172.19.0.1/30", - "inet6_address": "fdfe:dcba:9876::1/126", + "address": ["172.19.0.1/30","fdfe:dcba:9876::1/126"], "auto_route": true, + // "auto_redirect": true, // On linux "strict_route": true } ], @@ -262,25 +243,23 @@ flowchart TB { "type": "direct", "tag": "direct" - }, - { - "type": "dns", - "tag": "dns-out" } ], "route": { "rules": [ + { + "action": "sniff" + }, { "protocol": "dns", - "outbound": "dns-out" + "action": "hijack-dns" }, { - "geoip": [ - "private" - ], + "ip_is_private": true, "outbound": "direct" } ], + "default_domain_resolver": "local", "auto_detect_interface": true } } @@ -290,54 +269,6 @@ flowchart TB === ":material-dns: DNS rules" - ```json - { - "dns": { - "servers": [ - { - "tag": "google", - "address": "tls://8.8.8.8" - }, - { - "tag": "local", - "address": "223.5.5.5", - "detour": "direct" - } - ], - "rules": [ - { - "outbound": "any", - "server": "local" - }, - { - "clash_mode": "Direct", - "server": "local" - }, - { - "clash_mode": "Global", - "server": "google" - }, - { - "rule_set": "geosite-geolocation-cn", - "server": "local" - } - ] - }, - "route": { - "rule_set": [ - { - "type": "remote", - "tag": "geosite-geolocation-cn", - "format": "binary", - "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-cn.srs" - } - ] - } - } - ``` - -=== ":material-dns: DNS rules (Enhanced, but slower) (1.9.0+)" - === ":material-shield-off: With DNS leaks" ```json @@ -346,35 +277,20 @@ flowchart TB "servers": [ { "tag": "google", - "address": "tls://8.8.8.8" + "type": "tls", + "server": "8.8.8.8" }, { "tag": "local", - "address": "https://223.5.5.5/dns-query", - "detour": "direct" + "type": "https", + "server": "223.5.5.5" } ], "rules": [ - { - "outbound": "any", - "server": "local" - }, - { - "clash_mode": "Direct", - "server": "local" - }, - { - "clash_mode": "Global", - "server": "google" - }, { "rule_set": "geosite-geolocation-cn", "server": "local" }, - { - "clash_mode": "Default", - "server": "google" - }, { "type": "logical", "mode": "and", @@ -392,6 +308,7 @@ flowchart TB ] }, "route": { + "default_domain_resolver": "local", "rule_set": [ { "type": "remote", @@ -425,35 +342,24 @@ flowchart TB } ``` - === ":material-security: Without DNS leaks, but slower (1.9.0-alpha.2+)" - + === ":material-security: Without DNS leaks, but slower" + ```json { "dns": { "servers": [ { "tag": "google", - "address": "tls://8.8.8.8" + "type": "tls", + "server": "8.8.8.8" }, { "tag": "local", - "address": "https://223.5.5.5/dns-query", - "detour": "direct" + "type": "https", + "server": "223.5.5.5" } ], "rules": [ - { - "outbound": "any", - "server": "local" - }, - { - "clash_mode": "Direct", - "server": "local" - }, - { - "clash_mode": "Global", - "server": "google" - }, { "rule_set": "geosite-geolocation-cn", "server": "local" @@ -476,6 +382,7 @@ flowchart TB ] }, "route": { + "default_domain_resolver": "local", "rule_set": [ { "type": "remote", @@ -517,14 +424,13 @@ flowchart TB { "type": "direct", "tag": "direct" - }, - { - "type": "block", - "tag": "block" } ], "route": { "rules": [ + { + "action": "sniff" + }, { "type": "logical", "mode": "or", @@ -536,20 +442,12 @@ flowchart TB "port": 53 } ], - "outbound": "dns" + "action": "hijack-dns" }, { "ip_is_private": true, "outbound": "direct" }, - { - "clash_mode": "Direct", - "outbound": "direct" - }, - { - "clash_mode": "Global", - "outbound": "default" - }, { "type": "logical", "mode": "or", @@ -565,12 +463,23 @@ flowchart TB "protocol": "stun" } ], - "outbound": "block" + "action": "reject" }, { - "rule_set": [ - "geoip-cn", - "geosite-geolocation-cn" + "rule_set": "geosite-geolocation-cn", + "outbound": "direct" + }, + { + "type": "logical", + "mode": "and", + "rules": [ + { + "rule_set": "geoip-cn" + }, + { + "rule_set": "geosite-geolocation-!cn", + "invert": true + } ], "outbound": "direct" } @@ -591,4 +500,4 @@ flowchart TB ] } } - ``` \ No newline at end of file + ``` diff --git a/docs/migration.md b/docs/migration.md index efe92dfd6a..be26827f03 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -2,6 +2,1239 @@ icon: material/arrange-bring-forward --- +## 1.14.0 + +### Migrate inline ACME to certificate provider + +Inline ACME options in TLS are deprecated and can be replaced by certificate providers. + +Most `tls.acme` fields can be moved into the ACME certificate provider unchanged. +See [ACME](/configuration/shared/certificate-provider/acme/) for fields newly added in sing-box 1.14.0. + +!!! info "References" + + [TLS](/configuration/shared/tls/#certificate_provider) / + [Certificate Provider](/configuration/shared/certificate-provider/) + +=== ":material-card-remove: Deprecated" + + ```json + { + "inbounds": [ + { + "type": "trojan", + "tls": { + "enabled": true, + "acme": { + "domain": ["example.com"], + "email": "admin@example.com" + } + } + } + ] + } + ``` + +=== ":material-card-multiple: Inline" + + ```json + { + "inbounds": [ + { + "type": "trojan", + "tls": { + "enabled": true, + "certificate_provider": { + "type": "acme", + "domain": ["example.com"], + "email": "admin@example.com" + } + } + } + ] + } + ``` + +=== ":material-card-multiple: Shared" + + ```json + { + "certificate_providers": [ + { + "type": "acme", + "tag": "my-cert", + "domain": ["example.com"], + "email": "admin@example.com" + } + ], + "inbounds": [ + { + "type": "trojan", + "tls": { + "enabled": true, + "certificate_provider": "my-cert" + } + } + ] + } + ``` + +### Migrate address filter fields to response matching + +Legacy Address Filter Fields (`ip_cidr`, `ip_is_private` without `match_response`) in DNS rules are deprecated, +along with the Legacy `rule_set_ip_cidr_accept_empty` DNS rule item. A DNS rule that references a rule-set +containing only `ip_cidr` items (for example, a GeoIP rule-set) without `match_response` is also rejected +at startup when legacy DNS mode is disabled. + +In sing-box 1.14.0, use the [`evaluate`](/configuration/dns/rule_action/#evaluate) action +to fetch a DNS response, then match against it explicitly with `match_response`. + +!!! info "References" + + [DNS Rule](/configuration/dns/rule/) / + [DNS Rule Action](/configuration/dns/rule_action/#evaluate) + +=== ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "rules": [ + { + "rule_set": "geoip-cn", + "action": "route", + "server": "local" + }, + { + "action": "route", + "server": "remote" + } + ] + } + } + ``` + +=== ":material-card-multiple: New" + + ```json + { + "dns": { + "rules": [ + { + "action": "evaluate", + "server": "remote" + }, + { + "match_response": true, + "rule_set": "geoip-cn", + "action": "route", + "server": "local" + }, + { + "action": "route", + "server": "remote" + } + ] + } + } + ``` + +### Migrate independent DNS cache + +The DNS cache now always keys by transport name, making `independent_cache` unnecessary. +Simply remove the field. + +!!! info "References" + + [DNS](/configuration/dns/) + +=== ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "independent_cache": true + } + } + ``` + +=== ":material-card-multiple: New" + + ```json + { + "dns": {} + } + ``` + +### Migrate store_rdrc + +`store_rdrc` is deprecated and can be replaced by `store_dns`, +which persists the full DNS cache to the cache file. + +!!! info "References" + + [Cache File](/configuration/experimental/cache-file/) + +=== ":material-card-remove: Deprecated" + + ```json + { + "experimental": { + "cache_file": { + "enabled": true, + "store_rdrc": true + } + } + } + ``` + +=== ":material-card-multiple: New" + + ```json + { + "experimental": { + "cache_file": { + "enabled": true, + "store_dns": true + } + } + } + ``` + +### ip_version and query_type behavior changes in DNS rules + +In sing-box 1.14.0, the behavior of +[`ip_version`](/configuration/dns/rule/#ip_version) and +[`query_type`](/configuration/dns/rule/#query_type) in DNS rules, together with +[`query_type`](/configuration/rule-set/headless-rule/#query_type) in referenced +rule-sets, changes in two ways. + +First, these fields now take effect on every DNS rule evaluation. In earlier +versions they were evaluated only for DNS queries received from a client +(for example, from a DNS inbound or intercepted by `tun`), and were silently +ignored when a DNS rule was matched from an internal domain resolution that +did not target a specific DNS server. Such internal resolutions include: + +- The [`resolve`](/configuration/route/rule_action/#resolve) route rule + action without a `server` set. +- ICMP traffic routed to a domain destination through a `direct` outbound. +- A [WireGuard](/configuration/endpoint/wireguard/) or + [Tailscale](/configuration/endpoint/tailscale/) endpoint used as an + outbound, when resolving its own destination address. +- A [SOCKS4](/configuration/outbound/socks/) outbound, which must resolve + the destination locally because the protocol has no in-protocol domain + support. +- The [DERP](/configuration/service/derp/) `bootstrap-dns` endpoint and the + [`resolved`](/configuration/service/resolved/) service (when resolving a + hostname or an SRV target). + +Resolutions that target a specific DNS server — via +[`domain_resolver`](/configuration/shared/dial/#domain_resolver) on a dial +field, [`default_domain_resolver`](/configuration/route/#default_domain_resolver) +in route options, or an explicit `server` on a DNS rule action or the +`resolve` route rule action — do not go through DNS rule matching and are +unaffected. + +Second, setting `ip_version` or `query_type` in a DNS rule, or referencing a +rule-set containing `query_type`, is no longer compatible in the same DNS +configuration with Legacy Address Filter Fields in DNS rules, the Legacy +`strategy` DNS rule action option, or the Legacy `rule_set_ip_cidr_accept_empty` +DNS rule item. Such a configuration will be rejected at startup. To combine +these fields with address-based filtering, migrate to response matching via +the [`evaluate`](/configuration/dns/rule_action/#evaluate) action and +[`match_response`](/configuration/dns/rule/#match_response), see +[Migrate address filter fields to response matching](#migrate-address-filter-fields-to-response-matching). + +!!! info "References" + + [DNS Rule](/configuration/dns/rule/) / + [Headless Rule](/configuration/rule-set/headless-rule/) / + [Route Rule Action](/configuration/route/rule_action/#resolve) + +## 1.12.0 + +### Migrate to new DNS server formats + +DNS servers are refactored for better performance and scalability. + +!!! info "References" + + [DNS Server](/configuration/dns/server/) / + [Legacy DNS Server](/configuration/dns/server/legacy/) + +=== "Local" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "local" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "local" + } + ] + } + } + ``` + +=== "TCP" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "tcp://1.1.1.1" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "tcp", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "UDP" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "1.1.1.1" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "udp", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "TLS" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "tls://1.1.1.1" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "tls", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "HTTPS" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "https://1.1.1.1/dns-query" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "https", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "QUIC" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "quic://1.1.1.1" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "quic", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "HTTP3" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "h3://1.1.1.1/dns-query" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "h3", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "DHCP" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "dhcp://auto" + }, + { + "address": "dhcp://en0" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "dhcp", + }, + { + "type": "dhcp", + "interface": "en0" + } + ] + } + } + ``` + +=== "FakeIP" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "1.1.1.1" + }, + { + "address": "fakeip", + "tag": "fakeip" + } + ], + "rules": [ + { + "query_type": [ + "A", + "AAAA" + ], + "server": "fakeip" + } + ], + "fakeip": { + "enabled": true, + "inet4_range": "198.18.0.0/15", + "inet6_range": "fc00::/18" + } + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "udp", + "server": "1.1.1.1" + }, + { + "type": "fakeip", + "tag": "fakeip", + "inet4_range": "198.18.0.0/15", + "inet6_range": "fc00::/18" + } + ], + "rules": [ + { + "query_type": [ + "A", + "AAAA" + ], + "server": "fakeip" + } + ] + } + } + ``` + +=== "RCode" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "rcode://refused" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "rules": [ + { + "domain": [ + "example.com" + ], + // other rules + + "action": "predefined", + "rcode": "REFUSED" + } + ] + } + } + ``` + +=== "Servers with domain address" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "https://dns.google/dns-query", + "address_resolver": "google" + }, + { + "tag": "google", + "address": "1.1.1.1" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "https", + "server": "dns.google", + "domain_resolver": "google" + }, + { + "type": "udp", + "tag": "google", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "Servers with strategy" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "1.1.1.1", + "strategy": "ipv4_only" + }, + { + "tag": "google", + "address": "8.8.8.8", + "strategy": "prefer_ipv6" + } + ], + "rules": [ + { + "domain": "google.com", + "server": "google" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "udp", + "server": "1.1.1.1" + }, + { + "type": "udp", + "tag": "google", + "server": "8.8.8.8" + } + ], + "rules": [ + { + "domain": "google.com", + "server": "google", + "strategy": "prefer_ipv6" + } + ], + "strategy": "ipv4_only" + } + } + ``` + +=== "Servers with client subnet" + + === ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "1.1.1.1" + }, + { + "tag": "google", + "address": "8.8.8.8", + "client_subnet": "1.1.1.1" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "udp", + "server": "1.1.1.1" + }, + { + "type": "udp", + "tag": "google", + "server": "8.8.8.8" + } + ], + "rules": [ + { + "domain": "google.com", + "server": "google", + "client_subnet": "1.1.1.1" + } + ] + } + } + ``` + +### Migrate outbound DNS rule items to domain resolver + +The legacy outbound DNS rules are deprecated and can be replaced by new domain resolver options. + +!!! info "References" + + [DNS rule](/configuration/dns/rule/#outbound) / + [Dial Fields](/configuration/shared/dial/#domain_resolver) / + [Route](/configuration/route/#domain_resolver) + +=== ":material-card-remove: Deprecated" + + ```json + { + "dns": { + "servers": [ + { + "address": "local", + "tag": "local" + } + ], + "rules": [ + { + "outbound": "any", + "server": "local" + } + ] + }, + "outbounds": [ + { + "type": "socks", + "server": "example.org", + "server_port": 2080 + } + ] + } + ``` + +=== ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "local", + "tag": "local" + } + ] + }, + "outbounds": [ + { + "type": "socks", + "server": "example.org", + "server_port": 2080, + "domain_resolver": { + "server": "local", + "rewrite_ttl": 60, + "client_subnet": "1.1.1.1" + }, + // or "domain_resolver": "local", + } + ], + + // or + + "route": { + "default_domain_resolver": { + "server": "local", + "rewrite_ttl": 60, + "client_subnet": "1.1.1.1" + } + } + } + ``` + +### Migrate outbound domain strategy option to domain resolver + +!!! info "References" + + [Dial Fields](/configuration/shared/dial/#domain_strategy) + +The `domain_strategy` option in Dial Fields has been deprecated and can be replaced with the new domain resolver option. + +Note that due to the use of Dial Fields by some of the new DNS servers introduced in sing-box 1.12, +some people mistakenly believe that `domain_strategy` is the same feature as in the legacy DNS servers. + +=== ":material-card-remove: Deprecated" + + ```json + { + "outbounds": [ + { + "type": "socks", + "server": "example.org", + "server_port": 2080, + "domain_strategy": "prefer_ipv4", + } + ] + } + ``` + +=== ":material-card-multiple: New" + + ```json + { + "dns": { + "servers": [ + { + "type": "local", + "tag": "local" + } + ] + }, + "outbounds": [ + { + "type": "socks", + "server": "example.org", + "server_port": 2080, + "domain_resolver": { + "server": "local", + "strategy": "prefer_ipv4" + } + } + ] + } + ``` + +## 1.11.0 + +### Migrate legacy special outbounds to rule actions + +Legacy special outbounds are deprecated and can be replaced by rule actions. + +!!! info "References" + + [Rule Action](/configuration/route/rule_action/) / + [Block](/configuration/outbound/block/) / + [DNS](/configuration/outbound/dns) + +=== "Block" + + === ":material-card-remove: Deprecated" + + ```json + { + "outbounds": [ + { + "type": "block", + "tag": "block" + } + ], + "route": { + "rules": [ + { + ..., + + "outbound": "block" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "route": { + "rules": [ + { + ..., + + "action": "reject" + } + ] + } + } + ``` + +=== "DNS" + + === ":material-card-remove: Deprecated" + + ```json + { + "inbound": [ + { + ..., + + "sniff": true + } + ], + "outbounds": [ + { + "tag": "dns", + "type": "dns" + } + ], + "route": { + "rules": [ + { + "protocol": "dns", + "outbound": "dns" + } + ] + } + } + ``` + + === ":material-card-multiple: New" + + ```json + { + "route": { + "rules": [ + { + "action": "sniff" + }, + { + "protocol": "dns", + "action": "hijack-dns" + } + ] + } + } + ``` + +### Migrate legacy inbound fields to rule actions + +Inbound fields are deprecated and can be replaced by rule actions. + +!!! info "References" + + [Listen Fields](/configuration/shared/listen/) / + [Rule](/configuration/route/rule/) / + [Rule Action](/configuration/route/rule_action/) / + [DNS Rule](/configuration/dns/rule/) / + [DNS Rule Action](/configuration/dns/rule_action/) + +=== ":material-card-remove: Deprecated" + + ```json + { + "inbounds": [ + { + "type": "mixed", + "sniff": true, + "sniff_timeout": "1s", + "domain_strategy": "prefer_ipv4" + } + ] + } + ``` + +=== ":material-card-multiple: New" + + ```json + { + "inbounds": [ + { + "type": "mixed", + "tag": "in" + } + ], + "route": { + "rules": [ + { + "inbound": "in", + "action": "resolve", + "strategy": "prefer_ipv4" + }, + { + "inbound": "in", + "action": "sniff", + "timeout": "1s" + } + ] + } + } + ``` + +### Migrate destination override fields to route options + +Destination override fields in direct outbound are deprecated and can be replaced by route options. + +!!! info "References" + + [Rule Action](/configuration/route/rule_action/) / + [Direct](/configuration/outbound/direct/) + +=== ":material-card-remove: Deprecated" + + ```json + { + "outbounds": [ + { + "type": "direct", + "override_address": "1.1.1.1", + "override_port": 443 + } + ] + } + ``` + +=== ":material-card-multiple: New" + + ```json + { + "route": { + "rules": [ + { + "action": "route-options", // or route + "override_address": "1.1.1.1", + "override_port": 443 + } + ] + } + ``` + +### Migrate WireGuard outbound to endpoint + +WireGuard outbound is deprecated and can be replaced by endpoint. + +!!! info "References" + + [Endpoint](/configuration/endpoint/) / + [WireGuard Endpoint](/configuration/endpoint/wireguard/) / + [WireGuard Outbound](/configuration/outbound/wireguard/) + +=== ":material-card-remove: Deprecated" + + ```json + { + "outbounds": [ + { + "type": "wireguard", + "tag": "wg-out", + + "server": "127.0.0.1", + "server_port": 10001, + "system_interface": true, + "gso": true, + "interface_name": "wg0", + "local_address": [ + "10.0.0.1/32" + ], + "private_key": "", + "peer_public_key": "", + "pre_shared_key": "", + "reserved": [0, 0, 0], + "mtu": 1408 + } + ] + } + ``` + +=== ":material-card-multiple: New" + + ```json + { + "endpoints": [ + { + "type": "wireguard", + "tag": "wg-ep", + "system": true, + "name": "wg0", + "mtu": 1408, + "address": [ + "10.0.0.2/32" + ], + "private_key": "", + "listen_port": 10000, + "peers": [ + { + "address": "127.0.0.1", + "port": 10001, + "public_key": "", + "pre_shared_key": "", + "allowed_ips": [ + "0.0.0.0/0" + ], + "persistent_keepalive_interval": 30, + "reserved": [0, 0, 0] + } + ] + } + ] + } + ``` + +## 1.10.0 + +### TUN address fields are merged + +`inet4_address` and `inet6_address` are merged into `address`, +`inet4_route_address` and `inet6_route_address` are merged into `route_address`, +`inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`. + +!!! info "References" + + [TUN](/configuration/inbound/tun/) + +=== ":material-card-remove: Deprecated" + + ```json + { + "inbounds": [ + { + "type": "tun", + "inet4_address": "172.19.0.1/30", + "inet6_address": "fdfe:dcba:9876::1/126", + "inet4_route_address": [ + "0.0.0.0/1", + "128.0.0.0/1" + ], + "inet6_route_address": [ + "::/1", + "8000::/1" + ], + "inet4_route_exclude_address": [ + "192.168.0.0/16" + ], + "inet6_route_exclude_address": [ + "fc00::/7" + ] + } + ] + } + ``` + +=== ":material-card-multiple: New" + + ```json + { + "inbounds": [ + { + "type": "tun", + "address": [ + "172.19.0.1/30", + "fdfe:dcba:9876::1/126" + ], + "route_address": [ + "0.0.0.0/1", + "128.0.0.0/1", + "::/1", + "8000::/1" + ], + "route_exclude_address": [ + "192.168.0.0/16", + "fc00::/7" + ] + } + ] + } + ``` + +## 1.9.5 + +### Bundle Identifier updates in Apple platform clients + +Due to problems with our old Apple developer account, +we can only change Bundle Identifiers to re-list sing-box apps, +which means the data will not be automatically inherited. + +For iOS, you need to back up your old data yourself (if you still have access to it); +for tvOS, you need to re-import profiles from your iPhone or iPad or create it manually; +for macOS, you can migrate the data folder using the following command: + +```bash +cd ~/Library/Group\ Containers && \ + mv group.io.nekohasekai.sfa group.io.nekohasekai.sfavt +``` + ## 1.9.0 ### `domain_suffix` behavior update @@ -60,7 +1293,7 @@ which will disrupt the existing `process_path` use cases in Windows. } ``` -### :material-checkbox-intermediate: Migrate GeoIP to rule sets +### :material-checkbox-intermediate: Migrate GeoIP to rule-sets !!! info "References" @@ -68,11 +1301,11 @@ which will disrupt the existing `process_path` use cases in Windows. [Route](/configuration/route/) / [Route Rule](/configuration/route/rule/) / [DNS Rule](/configuration/dns/rule/) / - [Rule Set](/configuration/rule-set/) + [rule-set](/configuration/rule-set/) !!! tip - `sing-box geoip` commands can help you convert custom GeoIP into rule sets. + `sing-box geoip` commands can help you convert custom GeoIP into rule-sets. === ":material-card-remove: Deprecated" @@ -139,13 +1372,13 @@ which will disrupt the existing `process_path` use cases in Windows. }, "experimental": { "cache_file": { - "enabled": true // required to save Rule Set cache + "enabled": true // required to save rule-set cache } } } ``` -### :material-checkbox-intermediate: Migrate Geosite to rule sets +### :material-checkbox-intermediate: Migrate Geosite to rule-sets !!! info "References" @@ -153,11 +1386,11 @@ which will disrupt the existing `process_path` use cases in Windows. [Route](/configuration/route/) / [Route Rule](/configuration/route/rule/) / [DNS Rule](/configuration/dns/rule/) / - [Rule Set](/configuration/rule-set/) + [rule-set](/configuration/rule-set/) !!! tip - `sing-box geosite` commands can help you convert custom Geosite into rule sets. + `sing-box geosite` commands can help you convert custom Geosite into rule-sets. === ":material-card-remove: Deprecated" @@ -200,8 +1433,8 @@ which will disrupt the existing `process_path` use cases in Windows. }, "experimental": { "cache_file": { - "enabled": true // required to save Rule Set cache + "enabled": true // required to save rule-set cache } } } - ``` \ No newline at end of file + ``` diff --git a/docs/migration.zh.md b/docs/migration.zh.md index ce23875a93..4d003e1ecd 100644 --- a/docs/migration.zh.md +++ b/docs/migration.zh.md @@ -2,6 +2,1229 @@ icon: material/arrange-bring-forward --- +## 1.14.0 + +### 迁移内联 ACME 到证书提供者 + +TLS 中的内联 ACME 选项已废弃,且可以被证书提供者替代。 + +`tls.acme` 的大多数字段都可以原样迁移到 ACME 证书提供者中。 +sing-box 1.14.0 新增字段参阅 [ACME](/zh/configuration/shared/certificate-provider/acme/) 页面。 + +!!! info "参考" + + [TLS](/zh/configuration/shared/tls/#certificate_provider) / + [证书提供者](/zh/configuration/shared/certificate-provider/) + +=== ":material-card-remove: 弃用的" + + ```json + { + "inbounds": [ + { + "type": "trojan", + "tls": { + "enabled": true, + "acme": { + "domain": ["example.com"], + "email": "admin@example.com" + } + } + } + ] + } + ``` + +=== ":material-card-multiple: 内联" + + ```json + { + "inbounds": [ + { + "type": "trojan", + "tls": { + "enabled": true, + "certificate_provider": { + "type": "acme", + "domain": ["example.com"], + "email": "admin@example.com" + } + } + } + ] + } + ``` + +=== ":material-card-multiple: 共享" + + ```json + { + "certificate_providers": [ + { + "type": "acme", + "tag": "my-cert", + "domain": ["example.com"], + "email": "admin@example.com" + } + ], + "inbounds": [ + { + "type": "trojan", + "tls": { + "enabled": true, + "certificate_provider": "my-cert" + } + } + ] + } + ``` + +### 迁移地址筛选字段到响应匹配 + +旧版地址筛选字段(不使用 `match_response` 的 `ip_cidr`、`ip_is_private`)已废弃, +旧版 `rule_set_ip_cidr_accept_empty` DNS 规则项也已废弃。当旧版 DNS 模式被禁用时, +引用仅包含 `ip_cidr` 项的规则集(例如 GeoIP 规则集)且未设置 `match_response` 的 DNS 规则 +也将在启动时被拒绝。 + +在 sing-box 1.14.0 中,请使用 [`evaluate`](/zh/configuration/dns/rule_action/#evaluate) 动作 +获取 DNS 响应,然后通过 `match_response` 显式匹配。 + +!!! info "参考" + + [DNS 规则](/zh/configuration/dns/rule/) / + [DNS 规则动作](/zh/configuration/dns/rule_action/#evaluate) + +=== ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "rules": [ + { + "rule_set": "geoip-cn", + "action": "route", + "server": "local" + }, + { + "action": "route", + "server": "remote" + } + ] + } + } + ``` + +=== ":material-card-multiple: 新的" + + ```json + { + "dns": { + "rules": [ + { + "action": "evaluate", + "server": "remote" + }, + { + "match_response": true, + "rule_set": "geoip-cn", + "action": "route", + "server": "local" + }, + { + "action": "route", + "server": "remote" + } + ] + } + } + ``` + +### 迁移 independent DNS cache + +DNS 缓存现在始终按传输名称分离,使 `independent_cache` 不再需要。 +直接移除该字段即可。 + +!!! info "参考" + + [DNS](/zh/configuration/dns/) + +=== ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "independent_cache": true + } + } + ``` + +=== ":material-card-multiple: 新的" + + ```json + { + "dns": {} + } + ``` + +### 迁移 store_rdrc + +`store_rdrc` 已废弃,且可以被 `store_dns` 替代, +后者将完整的 DNS 缓存持久化到缓存文件中。 + +!!! info "参考" + + [缓存文件](/zh/configuration/experimental/cache-file/) + +=== ":material-card-remove: 弃用的" + + ```json + { + "experimental": { + "cache_file": { + "enabled": true, + "store_rdrc": true + } + } + } + ``` + +=== ":material-card-multiple: 新的" + + ```json + { + "experimental": { + "cache_file": { + "enabled": true, + "store_dns": true + } + } + } + ``` + +### DNS 规则中的 ip_version 和 query_type 行为更改 + +在 sing-box 1.14.0 中,DNS 规则中的 +[`ip_version`](/zh/configuration/dns/rule/#ip_version) 和 +[`query_type`](/zh/configuration/dns/rule/#query_type),以及被引用规则集中的 +[`query_type`](/zh/configuration/rule-set/headless-rule/#query_type), +行为有两项更改。 + +其一,这些字段现在对每一次 DNS 规则评估都会生效。此前它们仅对来自客户端的 DNS 查询 +(例如来自 DNS 入站或被 `tun` 截获的查询)生效,当 DNS 规则被未指定具体 DNS 服务器的 +内部域名解析匹配时,会被静默忽略。此类内部解析包括: + +- 未设置 `server` 的 [`resolve`](/zh/configuration/route/rule_action/#resolve) 路由规则动作。 +- 通过 `direct` 出站路由到域名目标的 ICMP 流量。 +- 作为出站使用的 [WireGuard](/zh/configuration/endpoint/wireguard/) 或 + [Tailscale](/zh/configuration/endpoint/tailscale/) 端点在解析自身目标地址时。 +- [SOCKS4](/zh/configuration/outbound/socks/) 出站,因为协议本身不支持域名, + 必须在本地解析目标。 +- [DERP](/zh/configuration/service/derp/) 的 `bootstrap-dns` 端点,以及 + [`resolved`](/zh/configuration/service/resolved/) 服务在解析主机名或 SRV 目标时。 + +通过拨号字段中的 +[`domain_resolver`](/zh/configuration/shared/dial/#domain_resolver)、 +路由选项中的 [`default_domain_resolver`](/zh/configuration/route/#default_domain_resolver), +或 DNS 规则动作与 `resolve` 路由规则动作上显式的 `server` 指定具体 DNS 服务器的 +解析,不会经过 DNS 规则匹配,不受此次更改影响。 + +其二,设置了 `ip_version` 或 `query_type` 的 DNS 规则,或引用了包含 `query_type` 的 +规则集的 DNS 规则,在同一 DNS 配置中不再能与旧版地址筛选字段 (DNS 规则)、旧版 +DNS 规则动作 `strategy` 选项,或旧版 `rule_set_ip_cidr_accept_empty` DNS 规则项共存。 +此类配置将在启动时被拒绝。如需将这些字段与基于地址的筛选组合,请通过 +[`evaluate`](/zh/configuration/dns/rule_action/#evaluate) 动作和 +[`match_response`](/zh/configuration/dns/rule/#match_response) 迁移到响应匹配, +参阅 [迁移地址筛选字段到响应匹配](#迁移地址筛选字段到响应匹配)。 + +!!! info "参考" + + [DNS 规则](/zh/configuration/dns/rule/) / + [Headless 规则](/zh/configuration/rule-set/headless-rule/) / + [路由规则动作](/zh/configuration/route/rule_action/#resolve) + +## 1.12.0 + +### 迁移到新的 DNS 服务器格式 + +DNS 服务器已经重构。 + +!!! info "引用" + + [DNS 服务器](/zh/configuration/dns/server/) / + [旧 DNS 服务器](/zh/configuration/dns/server/legacy/) + +=== "Local" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "local" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "local" + } + ] + } + } + ``` + +=== "TCP" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "tcp://1.1.1.1" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "tcp", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "UDP" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "1.1.1.1" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "udp", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "TLS" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "tls://1.1.1.1" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "tls", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "HTTPS" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "https://1.1.1.1/dns-query" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "https", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "QUIC" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "quic://1.1.1.1" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "quic", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "HTTP3" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "h3://1.1.1.1/dns-query" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "h3", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "DHCP" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "dhcp://auto" + }, + { + "address": "dhcp://en0" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "dhcp", + }, + { + "type": "dhcp", + "interface": "en0" + } + ] + } + } + ``` + +=== "FakeIP" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "1.1.1.1" + }, + { + "address": "fakeip", + "tag": "fakeip" + } + ], + "rules": [ + { + "query_type": [ + "A", + "AAAA" + ], + "server": "fakeip" + } + ], + "fakeip": { + "enabled": true, + "inet4_range": "198.18.0.0/15", + "inet6_range": "fc00::/18" + } + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "udp", + "server": "1.1.1.1" + }, + { + "type": "fakeip", + "tag": "fakeip", + "inet4_range": "198.18.0.0/15", + "inet6_range": "fc00::/18" + } + ], + "rules": [ + { + "query_type": [ + "A", + "AAAA" + ], + "server": "fakeip" + } + ] + } + } + ``` + +=== "RCode" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "rcode://refused" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "rules": [ + { + "domain": [ + "example.com" + ], + // 其它规则 + + "action": "predefined", + "rcode": "REFUSED" + } + ] + } + } + ``` + +=== "带有域名地址的服务器" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "https://dns.google/dns-query", + "address_resolver": "google" + }, + { + "tag": "google", + "address": "1.1.1.1" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "https", + "server": "dns.google", + "domain_resolver": "google" + }, + { + "type": "udp", + "tag": "google", + "server": "1.1.1.1" + } + ] + } + } + ``` + +=== "带有域策略的服务器" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "1.1.1.1", + "strategy": "ipv4_only" + }, + { + "tag": "google", + "address": "8.8.8.8", + "strategy": "prefer_ipv6" + } + ], + "rules": [ + { + "domain": "google.com", + "server": "google" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "udp", + "server": "1.1.1.1" + }, + { + "type": "udp", + "tag": "google", + "server": "8.8.8.8" + } + ], + "rules": [ + { + "domain": "google.com", + "server": "google", + "strategy": "prefer_ipv6" + } + ], + "strategy": "ipv4_only" + } + } + ``` + +=== "带有客户端子网的服务器" + + === ":material-card-remove: 弃用的" + + ```json + { + "dns": { + "servers": [ + { + "address": "1.1.1.1" + }, + { + "tag": "google", + "address": "8.8.8.8", + "client_subnet": "1.1.1.1" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "udp", + "server": "1.1.1.1" + }, + { + "type": "udp", + "tag": "google", + "server": "8.8.8.8" + } + ], + "rules": [ + { + "domain": "google.com", + "server": "google", + "client_subnet": "1.1.1.1" + } + ] + } + } + ``` + +### 迁移 outbound DNS 规则项到域解析选项 + +旧的 `outbound` DNS 规则已废弃,且可新的域解析选项代替。 + +!!! info "参考" + + [DNS 规则](/zh/configuration/dns/rule/#outbound) / + [拨号字段](/zh/configuration/shared/dial/#domain_resolver) / + [路由](/zh/configuration/route/#default_domain_resolver) + +=== ":material-card-remove: 废弃的" + + ```json + { + "dns": { + "servers": [ + { + "address": "local", + "tag": "local" + } + ], + "rules": [ + { + "outbound": "any", + "server": "local" + } + ] + }, + "outbounds": [ + { + "type": "socks", + "server": "example.org", + "server_port": 2080 + } + ] + } + ``` + +=== ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "local", + "tag": "local" + } + ] + }, + "outbounds": [ + { + "type": "socks", + "server": "example.org", + "server_port": 2080, + "domain_resolver": { + "server": "local", + "rewrite_ttl": 60, + "client_subnet": "1.1.1.1" + }, + // 或 "domain_resolver": "local", + } + ], + + // 或 + + "route": { + "default_domain_resolver": { + "server": "local", + "rewrite_ttl": 60, + "client_subnet": "1.1.1.1" + } + } + } + ``` + +### 迁移出站域名策略选项到域名解析器 + +拨号字段中的 `domain_strategy` 选项已被弃用,可以用新的域名解析器选项替代。 + +请注意,由于 sing-box 1.12 中引入的一些新 DNS 服务器使用了拨号字段,一些人错误地认为 `domain_strategy` 与旧 DNS 服务器中的功能相同。 + +!!! info "参考" + + [拨号字段](/zh/configuration/shared/dial/#domain_strategy) + +=== ":material-card-remove: 弃用的" + + ```json + { + "outbounds": [ + { + "type": "socks", + "server": "example.org", + "server_port": 2080, + "domain_strategy": "prefer_ipv4", + } + ] + } + ``` + +=== ":material-card-multiple: 新的" + + ```json + { + "dns": { + "servers": [ + { + "type": "local", + "tag": "local" + } + ] + }, + "outbounds": [ + { + "type": "socks", + "server": "example.org", + "server_port": 2080, + "domain_resolver": { + "server": "local", + "strategy": "prefer_ipv4" + } + } + ] + } + ``` + +## 1.11.0 + +### 迁移旧的特殊出站到规则动作 + +旧的特殊出站已被弃用,且可以被规则动作替代。 + +!!! info "参考" + + [规则动作](/zh/configuration/route/rule_action/) / + [Block](/zh/configuration/outbound/block/) / + [DNS](/zh/configuration/outbound/dns) + +=== "Block" + + === ":material-card-remove: 弃用的" + + ```json + { + "outbounds": [ + { + "type": "block", + "tag": "block" + } + ], + "route": { + "rules": [ + { + ..., + + "outbound": "block" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "route": { + "rules": [ + { + ..., + + "action": "reject" + } + ] + } + } + ``` + +=== "DNS" + + === ":material-card-remove: 弃用的" + + ```json + { + "inbound": [ + { + ..., + + "sniff": true + } + ], + "outbounds": [ + { + "tag": "dns", + "type": "dns" + } + ], + "route": { + "rules": [ + { + "protocol": "dns", + "outbound": "dns" + } + ] + } + } + ``` + + === ":material-card-multiple: 新的" + + ```json + { + "route": { + "rules": [ + { + "action": "sniff" + }, + { + "protocol": "dns", + "action": "hijack-dns" + } + ] + } + } + ``` + +### 迁移旧的入站字段到规则动作 + +入站选项已被弃用,且可以被规则动作替代。 + +!!! info "参考" + + [监听字段](/zh/configuration/shared/listen/) / + [规则](/zh/configuration/route/rule/) / + [规则动作](/zh/configuration/route/rule_action/) / + [DNS 规则](/zh/configuration/dns/rule/) / + [DNS 规则动作](/zh/configuration/dns/rule_action/) + +=== ":material-card-remove: 弃用的" + + ```json + { + "inbounds": [ + { + "type": "mixed", + "sniff": true, + "sniff_timeout": "1s", + "domain_strategy": "prefer_ipv4" + } + ] + } + ``` + +=== ":material-card-multiple: 新的" + + ```json + { + "inbounds": [ + { + "type": "mixed", + "tag": "in" + } + ], + "route": { + "rules": [ + { + "inbound": "in", + "action": "resolve", + "strategy": "prefer_ipv4" + }, + { + "inbound": "in", + "action": "sniff", + "timeout": "1s" + } + ] + } + } + ``` + +### 迁移 direct 出站中的目标地址覆盖字段到路由字段 + +direct 出站中的目标地址覆盖字段已废弃,且可以被路由字段替代。 + +!!! info "参考" + + [Rule Action](/zh/configuration/route/rule_action/) / + [Direct](/zh/configuration/outbound/direct/) + +=== ":material-card-remove: 弃用的" + + ```json + { + "outbounds": [ + { + "type": "direct", + "override_address": "1.1.1.1", + "override_port": 443 + } + ] + } + ``` + +=== ":material-card-multiple: 新的" + + ```json + { + "route": { + "rules": [ + { + "action": "route-options", // 或 route + "override_address": "1.1.1.1", + "override_port": 443 + } + ] + } + } + ``` + +### 迁移 WireGuard 出站到端点 + +WireGuard 出站已被弃用,且可以被端点替代。 + +!!! info "参考" + + [端点](/zh/configuration/endpoint/) / + [WireGuard 端点](/zh/configuration/endpoint/wireguard/) / + [WireGuard 出站](/zh/configuration/outbound/wireguard/) + +=== ":material-card-remove: 弃用的" + + ```json + { + "outbounds": [ + { + "type": "wireguard", + "tag": "wg-out", + + "server": "127.0.0.1", + "server_port": 10001, + "system_interface": true, + "gso": true, + "interface_name": "wg0", + "local_address": [ + "10.0.0.1/32" + ], + "private_key": "", + "peer_public_key": "", + "pre_shared_key": "", + "reserved": [0, 0, 0], + "mtu": 1408 + } + ] + } + ``` + +=== ":material-card-multiple: 新的" + + ```json + { + "endpoints": [ + { + "type": "wireguard", + "tag": "wg-ep", + "system": true, + "name": "wg0", + "mtu": 1408, + "address": [ + "10.0.0.2/32" + ], + "private_key": "", + "listen_port": 10000, + "peers": [ + { + "address": "127.0.0.1", + "port": 10001, + "public_key": "", + "pre_shared_key": "", + "allowed_ips": [ + "0.0.0.0/0" + ], + "persistent_keepalive_interval": 30, + "reserved": [0, 0, 0] + } + ] + } + ] + } + ``` + +## 1.10.0 + +### TUN 地址字段已合并 + +`inet4_address` 和 `inet6_address` 已合并为 `address`, +`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`, +`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。 + +!!! info "参考" + + [TUN](/zh/configuration/inbound/tun/) + +=== ":material-card-remove: 弃用的" + + ```json + { + "inbounds": [ + { + "type": "tun", + "inet4_address": "172.19.0.1/30", + "inet6_address": "fdfe:dcba:9876::1/126", + "inet4_route_address": [ + "0.0.0.0/1", + "128.0.0.0/1" + ], + "inet6_route_address": [ + "::/1", + "8000::/1" + ], + "inet4_route_exclude_address": [ + "192.168.0.0/16" + ], + "inet6_route_exclude_address": [ + "fc00::/7" + ] + } + ] + } + ``` + +=== ":material-card-multiple: 新的" + + ```json + { + "inbounds": [ + { + "type": "tun", + "address": [ + "172.19.0.1/30", + "fdfe:dcba:9876::1/126" + ], + "route_address": [ + "0.0.0.0/1", + "128.0.0.0/1", + "::/1", + "8000::/1" + ], + "route_exclude_address": [ + "192.168.0.0/16", + "fc00::/7" + ] + } + ] + } + ``` + +## 1.9.5 + +### Apple 平台客户端的 Bundle Identifier 更新 + +由于我们旧的苹果开发者账户存在问题,我们只能通过更新 Bundle Identifiers +来重新上架 sing-box 应用, 这意味着数据不会自动继承。 + +对于 iOS,您需要自行备份旧的数据(如果您仍然可以访问); +对于 Apple tvOS,您需要从 iPhone 或 iPad 重新导入配置或者手动创建; +对于 macOS,您可以使用以下命令迁移数据文件夹: + +```bash +cd ~/Library/Group\ Containers && \ + mv group.io.nekohasekai.sfa group.io.nekohasekai.sfavt +``` + ## 1.9.0 ### `domain_suffix` 行为更新 @@ -138,7 +1361,7 @@ sing-box 1.9.0 使 QueryFullProcessImageNameW 输出 Win32 路径(如 `C:\fold }, "experimental": { "cache_file": { - "enabled": true // required to save Rule Set cache + "enabled": true // required to save rule-set cache } } } @@ -199,8 +1422,8 @@ sing-box 1.9.0 使 QueryFullProcessImageNameW 输出 Win32 路径(如 `C:\fold }, "experimental": { "cache_file": { - "enabled": true // required to save Rule Set cache + "enabled": true // required to save rule-set cache } } } - ``` \ No newline at end of file + ``` diff --git a/docs/sponsors.md b/docs/sponsors.md new file mode 100644 index 0000000000..4e4dd07cb4 --- /dev/null +++ b/docs/sponsors.md @@ -0,0 +1,26 @@ +--- +icon: material/hand-coin +--- + +# Sponsors + +Do you or your friends use sing-box? + +You can help keep the project bug-free and feature rich by sponsoring +the project maintainer via [GitHub Sponsors](https://github.com/sponsors/nekohasekai). + +![](https://nekohasekai.github.io/sponsor-images/sponsors.svg) + +## Special Sponsors + +> Viral Tech, Inc. + +Helping us re-list sing-box apps on the Apple Store. + +--- + +> [JetBrains](https://www.jetbrains.com) + +Free license for the amazing IDEs. + +[![JetBrains logo](https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg)](https://www.jetbrains.com) diff --git a/examples/check.sh b/examples/check.sh new file mode 100644 index 0000000000..a9bec06eb7 --- /dev/null +++ b/examples/check.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -uo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" + +BIN="$ROOT/sing-box" + +make + +passed=0 +failed=0 +skipped=0 + +skip_mkcp() { + [[ "$1" == *"/mkcp/"* ]] +} + +while IFS= read -r f; do + rel="${f#./}" + if skip_mkcp "$rel"; then + echo "SKIP $rel (mkcp transport not in this fork yet)" + skipped=$((skipped + 1)) + continue + fi + if "$BIN" check -c "$f" >/dev/null 2>&1; then + echo "PASS $rel" + passed=$((passed + 1)) + else + echo "FAIL $rel" + "$BIN" check -c "$f" 2>&1 | sed 's/^/ /' + failed=$((failed + 1)) + fi +done < <(find examples -name '*.json' | sort) + +echo "---" +echo "Passed: $passed Failed: $failed Skipped: $skipped" +exit "$failed" diff --git a/experimental/cachefile/cache.go b/experimental/cachefile/cache.go index 1027588fc9..567ba02640 100644 --- a/experimental/cachefile/cache.go +++ b/experimental/cachefile/cache.go @@ -12,9 +12,11 @@ import ( "github.com/sagernet/bbolt" bboltErrors "github.com/sagernet/bbolt/errors" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/service/filemanager" ) @@ -30,6 +32,7 @@ var ( string(bucketMode), string(bucketRuleSet), string(bucketRDRC), + string(bucketDNSCache), } cacheIDDefault = []byte("default") @@ -38,29 +41,45 @@ var ( var _ adapter.CacheFile = (*CacheFile)(nil) type CacheFile struct { - ctx context.Context - path string - cacheID []byte - storeFakeIP bool - storeRDRC bool - rdrcTimeout time.Duration - DB *bbolt.DB - saveMetadataTimer *time.Timer - saveFakeIPAccess sync.RWMutex - saveDomain map[netip.Addr]string - saveAddress4 map[string]netip.Addr - saveAddress6 map[string]netip.Addr - saveRDRCAccess sync.RWMutex - saveRDRC map[saveRDRCCacheKey]bool + ctx context.Context + logger logger.Logger + path string + cacheID []byte + storeFakeIP bool + storeRDRC bool + storeWARPConfig bool + storeMASQUEConfig bool + storeDNS bool + disableExpire bool + rdrcTimeout time.Duration + optimisticTimeout time.Duration + DB *bbolt.DB + resetAccess sync.Mutex + saveMetadataTimer *time.Timer + saveFakeIPAccess sync.RWMutex + saveDomain map[netip.Addr]string + saveAddress4 map[string]netip.Addr + saveAddress6 map[string]netip.Addr + saveRDRCAccess sync.RWMutex + saveRDRC map[saveCacheKey]bool + saveDNSCacheAccess sync.RWMutex + saveDNSCache map[saveCacheKey]saveDNSCacheEntry } -type saveRDRCCacheKey struct { +type saveCacheKey struct { TransportName string QuestionName string QType uint16 } -func New(ctx context.Context, options option.CacheFileOptions) *CacheFile { +type saveDNSCacheEntry struct { + rawMessage []byte + expireAt time.Time + sequence uint64 + saving bool +} + +func New(ctx context.Context, logger logger.Logger, options option.CacheFileOptions) *CacheFile { var path string if options.Path != "" { path = options.Path @@ -71,6 +90,9 @@ func New(ctx context.Context, options option.CacheFileOptions) *CacheFile { if options.CacheID != "" { cacheIDBytes = append([]byte{0}, []byte(options.CacheID)...) } + if options.StoreRDRC { + deprecated.Report(ctx, deprecated.OptionStoreRDRC) + } var rdrcTimeout time.Duration if options.StoreRDRC { if options.RDRCTimeout > 0 { @@ -81,15 +103,65 @@ func New(ctx context.Context, options option.CacheFileOptions) *CacheFile { } return &CacheFile{ ctx: ctx, + logger: logger, path: filemanager.BasePath(ctx, path), cacheID: cacheIDBytes, - storeFakeIP: options.StoreFakeIP, - storeRDRC: options.StoreRDRC, + storeFakeIP: options.StoreFakeIP, + storeRDRC: options.StoreRDRC, + storeWARPConfig: options.StoreWARPConfig, + storeMASQUEConfig: options.StoreMASQUEConfig, + storeDNS: options.StoreDNS, rdrcTimeout: rdrcTimeout, saveDomain: make(map[netip.Addr]string), saveAddress4: make(map[string]netip.Addr), saveAddress6: make(map[string]netip.Addr), - saveRDRC: make(map[saveRDRCCacheKey]bool), + saveRDRC: make(map[saveCacheKey]bool), + saveDNSCache: make(map[saveCacheKey]saveDNSCacheEntry), + } +} + +func (c *CacheFile) Name() string { + return "cache-file" +} + +func (c *CacheFile) Dependencies() []string { + return nil +} + +func (c *CacheFile) SetOptimisticTimeout(timeout time.Duration) { + c.optimisticTimeout = timeout +} + +func (c *CacheFile) SetDisableExpire(disableExpire bool) { + c.disableExpire = disableExpire +} + +func (c *CacheFile) Start(stage adapter.StartStage) error { + switch stage { + case adapter.StartStateInitialize: + return c.start() + case adapter.StartStateStart: + c.startCacheCleanup() + } + return nil +} + +func (c *CacheFile) startCacheCleanup() { + if c.storeDNS { + c.clearRDRC() + c.cleanupDNSCache() + interval := c.optimisticTimeout / 2 + if interval <= 0 { + interval = time.Hour + } + go c.loopCacheCleanup(interval, c.cleanupDNSCache) + } else if c.storeRDRC { + c.cleanupRDRC() + interval := c.rdrcTimeout / 2 + if interval <= 0 { + interval = time.Hour + } + go c.loopCacheCleanup(interval, c.cleanupRDRC) } } @@ -100,7 +172,7 @@ func (c *CacheFile) start() error { db *bbolt.DB err error ) - for i := 0; i < 10; i++ { + for range 10 { db, err = bbolt.Open(c.path, fileMode, &options) if err == nil { break @@ -151,14 +223,6 @@ func (c *CacheFile) start() error { return nil } -func (c *CacheFile) PreStart() error { - return c.start() -} - -func (c *CacheFile) Start() error { - return nil -} - func (c *CacheFile) Close() error { if c.DB == nil { return nil @@ -166,13 +230,55 @@ func (c *CacheFile) Close() error { return c.DB.Close() } +func (c *CacheFile) view(fn func(tx *bbolt.Tx) error) (err error) { + defer func() { + if r := recover(); r != nil { + c.resetDB() + err = E.New("database corrupted: ", r) + } + }() + return c.DB.View(fn) +} + +func (c *CacheFile) batch(fn func(tx *bbolt.Tx) error) (err error) { + defer func() { + if r := recover(); r != nil { + c.resetDB() + err = E.New("database corrupted: ", r) + } + }() + return c.DB.Batch(fn) +} + +func (c *CacheFile) update(fn func(tx *bbolt.Tx) error) (err error) { + defer func() { + if r := recover(); r != nil { + c.resetDB() + err = E.New("database corrupted: ", r) + } + }() + return c.DB.Update(fn) +} + +func (c *CacheFile) resetDB() { + c.resetAccess.Lock() + defer c.resetAccess.Unlock() + c.DB.Close() + os.Remove(c.path) + db, err := bbolt.Open(c.path, 0o666, &bbolt.Options{Timeout: time.Second}) + if err == nil { + _ = filemanager.Chown(c.ctx, c.path) + c.DB = db + } +} + func (c *CacheFile) StoreFakeIP() bool { return c.storeFakeIP } func (c *CacheFile) LoadMode() string { var mode string - c.DB.View(func(t *bbolt.Tx) error { + c.view(func(t *bbolt.Tx) error { bucket := t.Bucket(bucketMode) if bucket == nil { return nil @@ -190,7 +296,7 @@ func (c *CacheFile) LoadMode() string { } func (c *CacheFile) StoreMode(mode string) error { - return c.DB.Batch(func(t *bbolt.Tx) error { + return c.batch(func(t *bbolt.Tx) error { bucket, err := t.CreateBucketIfNotExists(bucketMode) if err != nil { return err @@ -227,7 +333,7 @@ func (c *CacheFile) createBucket(t *bbolt.Tx, key []byte) (*bbolt.Bucket, error) func (c *CacheFile) LoadSelected(group string) string { var selected string - c.DB.View(func(t *bbolt.Tx) error { + c.view(func(t *bbolt.Tx) error { bucket := c.bucket(t, bucketSelected) if bucket == nil { return nil @@ -242,7 +348,7 @@ func (c *CacheFile) LoadSelected(group string) string { } func (c *CacheFile) StoreSelected(group, selected string) error { - return c.DB.Batch(func(t *bbolt.Tx) error { + return c.batch(func(t *bbolt.Tx) error { bucket, err := c.createBucket(t, bucketSelected) if err != nil { return err @@ -252,7 +358,7 @@ func (c *CacheFile) StoreSelected(group, selected string) error { } func (c *CacheFile) LoadGroupExpand(group string) (isExpand bool, loaded bool) { - c.DB.View(func(t *bbolt.Tx) error { + c.view(func(t *bbolt.Tx) error { bucket := c.bucket(t, bucketExpand) if bucket == nil { return nil @@ -268,7 +374,7 @@ func (c *CacheFile) LoadGroupExpand(group string) (isExpand bool, loaded bool) { } func (c *CacheFile) StoreGroupExpand(group string, isExpand bool) error { - return c.DB.Batch(func(t *bbolt.Tx) error { + return c.batch(func(t *bbolt.Tx) error { bucket, err := c.createBucket(t, bucketExpand) if err != nil { return err @@ -281,9 +387,9 @@ func (c *CacheFile) StoreGroupExpand(group string, isExpand bool) error { }) } -func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedRuleSet { - var savedSet adapter.SavedRuleSet - err := c.DB.View(func(t *bbolt.Tx) error { +func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedBinary { + var savedSet adapter.SavedBinary + err := c.view(func(t *bbolt.Tx) error { bucket := c.bucket(t, bucketRuleSet) if bucket == nil { return os.ErrNotExist @@ -300,8 +406,8 @@ func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedRuleSet { return &savedSet } -func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedRuleSet) error { - return c.DB.Batch(func(t *bbolt.Tx) error { +func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedBinary) error { + return c.batch(func(t *bbolt.Tx) error { bucket, err := c.createBucket(t, bucketRuleSet) if err != nil { return err @@ -313,3 +419,19 @@ func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedRuleSet) error { return bucket.Put([]byte(tag), setBinary) }) } + +func (c *CacheFile) StoreWARPConfig() bool { + return c.storeWARPConfig +} + +func (c *CacheFile) StoreMASQUEConfig() bool { + return c.storeMASQUEConfig +} + +func (c *CacheFile) LoadBinary(tag string) *adapter.SavedBinary { + return c.LoadRuleSet(tag) +} + +func (c *CacheFile) SaveBinary(tag string, set *adapter.SavedBinary) error { + return c.SaveRuleSet(tag, set) +} diff --git a/experimental/cachefile/dns_cache.go b/experimental/cachefile/dns_cache.go new file mode 100644 index 0000000000..55718c59a1 --- /dev/null +++ b/experimental/cachefile/dns_cache.go @@ -0,0 +1,299 @@ +package cachefile + +import ( + "encoding/binary" + "time" + + "github.com/sagernet/bbolt" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/logger" +) + +var bucketDNSCache = []byte("dns_cache") + +func (c *CacheFile) StoreDNS() bool { + return c.storeDNS +} + +func (c *CacheFile) LoadDNSCache(transportName string, qName string, qType uint16) (rawMessage []byte, expireAt time.Time, loaded bool) { + c.saveDNSCacheAccess.RLock() + entry, cached := c.saveDNSCache[saveCacheKey{transportName, qName, qType}] + c.saveDNSCacheAccess.RUnlock() + if cached { + return entry.rawMessage, entry.expireAt, true + } + key := buf.Get(2 + len(qName)) + binary.BigEndian.PutUint16(key, qType) + copy(key[2:], qName) + defer buf.Put(key) + err := c.view(func(tx *bbolt.Tx) error { + bucket := c.bucket(tx, bucketDNSCache) + if bucket == nil { + return nil + } + bucket = bucket.Bucket([]byte(transportName)) + if bucket == nil { + return nil + } + content := bucket.Get(key) + if len(content) < 8 { + return nil + } + expireAt = time.Unix(int64(binary.BigEndian.Uint64(content[:8])), 0) + rawMessage = make([]byte, len(content)-8) + copy(rawMessage, content[8:]) + loaded = true + return nil + }) + if err != nil { + return nil, time.Time{}, false + } + return +} + +func (c *CacheFile) SaveDNSCache(transportName string, qName string, qType uint16, rawMessage []byte, expireAt time.Time) error { + value := buf.Get(8 + len(rawMessage)) + defer buf.Put(value) + binary.BigEndian.PutUint64(value[:8], uint64(expireAt.Unix())) + copy(value[8:], rawMessage) + return c.batch(func(tx *bbolt.Tx) error { + bucket, err := c.createBucket(tx, bucketDNSCache) + if err != nil { + return err + } + bucket, err = bucket.CreateBucketIfNotExists([]byte(transportName)) + if err != nil { + return err + } + key := buf.Get(2 + len(qName)) + binary.BigEndian.PutUint16(key, qType) + copy(key[2:], qName) + defer buf.Put(key) + return bucket.Put(key, value) + }) +} + +func (c *CacheFile) SaveDNSCacheAsync(transportName string, qName string, qType uint16, rawMessage []byte, expireAt time.Time, logger logger.Logger) { + saveKey := saveCacheKey{transportName, qName, qType} + if !c.queueDNSCacheSave(saveKey, rawMessage, expireAt) { + return + } + go c.flushPendingDNSCache(saveKey, logger) +} + +func (c *CacheFile) queueDNSCacheSave(saveKey saveCacheKey, rawMessage []byte, expireAt time.Time) bool { + c.saveDNSCacheAccess.Lock() + defer c.saveDNSCacheAccess.Unlock() + entry := c.saveDNSCache[saveKey] + entry.rawMessage = append([]byte(nil), rawMessage...) + entry.expireAt = expireAt + entry.sequence++ + startFlush := !entry.saving + entry.saving = true + c.saveDNSCache[saveKey] = entry + return startFlush +} + +func (c *CacheFile) flushPendingDNSCache(saveKey saveCacheKey, logger logger.Logger) { + c.flushPendingDNSCacheWith(saveKey, logger, func(entry saveDNSCacheEntry) error { + return c.SaveDNSCache(saveKey.TransportName, saveKey.QuestionName, saveKey.QType, entry.rawMessage, entry.expireAt) + }) +} + +func (c *CacheFile) flushPendingDNSCacheWith(saveKey saveCacheKey, logger logger.Logger, save func(saveDNSCacheEntry) error) { + for { + c.saveDNSCacheAccess.RLock() + entry, loaded := c.saveDNSCache[saveKey] + c.saveDNSCacheAccess.RUnlock() + if !loaded { + return + } + err := save(entry) + if err != nil { + logger.Warn("save DNS cache: ", err) + } + c.saveDNSCacheAccess.Lock() + currentEntry, loaded := c.saveDNSCache[saveKey] + if !loaded { + c.saveDNSCacheAccess.Unlock() + return + } + if currentEntry.sequence != entry.sequence { + c.saveDNSCacheAccess.Unlock() + continue + } + delete(c.saveDNSCache, saveKey) + c.saveDNSCacheAccess.Unlock() + return + } +} + +func (c *CacheFile) ClearDNSCache() error { + c.saveDNSCacheAccess.Lock() + clear(c.saveDNSCache) + c.saveDNSCacheAccess.Unlock() + return c.batch(func(tx *bbolt.Tx) error { + if c.cacheID == nil { + bucket := tx.Bucket(bucketDNSCache) + if bucket == nil { + return nil + } + return tx.DeleteBucket(bucketDNSCache) + } + bucket := tx.Bucket(c.cacheID) + if bucket == nil || bucket.Bucket(bucketDNSCache) == nil { + return nil + } + return bucket.DeleteBucket(bucketDNSCache) + }) +} + +func (c *CacheFile) loopCacheCleanup(interval time.Duration, cleanupFunc func()) { + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-c.ctx.Done(): + return + case <-ticker.C: + cleanupFunc() + } + } +} + +func (c *CacheFile) cleanupDNSCache() { + now := time.Now() + err := c.batch(func(tx *bbolt.Tx) error { + bucket := c.bucket(tx, bucketDNSCache) + if bucket == nil { + return nil + } + var emptyTransports [][]byte + err := bucket.ForEachBucket(func(transportName []byte) error { + transportBucket := bucket.Bucket(transportName) + if transportBucket == nil { + return nil + } + var expiredKeys [][]byte + err := transportBucket.ForEach(func(key, value []byte) error { + if len(value) < 8 { + expiredKeys = append(expiredKeys, append([]byte(nil), key...)) + return nil + } + if c.disableExpire { + return nil + } + expireAt := time.Unix(int64(binary.BigEndian.Uint64(value[:8])), 0) + if now.After(expireAt.Add(c.optimisticTimeout)) { + expiredKeys = append(expiredKeys, append([]byte(nil), key...)) + } + return nil + }) + if err != nil { + return err + } + for _, key := range expiredKeys { + err = transportBucket.Delete(key) + if err != nil { + return err + } + } + first, _ := transportBucket.Cursor().First() + if first == nil { + emptyTransports = append(emptyTransports, append([]byte(nil), transportName...)) + } + return nil + }) + if err != nil { + return err + } + for _, name := range emptyTransports { + err = bucket.DeleteBucket(name) + if err != nil { + return err + } + } + return nil + }) + if err != nil { + c.logger.Warn("cleanup DNS cache: ", err) + } +} + +func (c *CacheFile) clearRDRC() { + c.saveRDRCAccess.Lock() + clear(c.saveRDRC) + c.saveRDRCAccess.Unlock() + err := c.batch(func(tx *bbolt.Tx) error { + if c.cacheID == nil { + if tx.Bucket(bucketRDRC) == nil { + return nil + } + return tx.DeleteBucket(bucketRDRC) + } + bucket := tx.Bucket(c.cacheID) + if bucket == nil || bucket.Bucket(bucketRDRC) == nil { + return nil + } + return bucket.DeleteBucket(bucketRDRC) + }) + if err != nil { + c.logger.Warn("clear RDRC: ", err) + } +} + +func (c *CacheFile) cleanupRDRC() { + now := time.Now() + err := c.batch(func(tx *bbolt.Tx) error { + bucket := c.bucket(tx, bucketRDRC) + if bucket == nil { + return nil + } + var emptyTransports [][]byte + err := bucket.ForEachBucket(func(transportName []byte) error { + transportBucket := bucket.Bucket(transportName) + if transportBucket == nil { + return nil + } + var expiredKeys [][]byte + err := transportBucket.ForEach(func(key, value []byte) error { + if len(value) < 8 { + expiredKeys = append(expiredKeys, append([]byte(nil), key...)) + return nil + } + expiresAt := time.Unix(int64(binary.BigEndian.Uint64(value)), 0) + if now.After(expiresAt) { + expiredKeys = append(expiredKeys, append([]byte(nil), key...)) + } + return nil + }) + if err != nil { + return err + } + for _, key := range expiredKeys { + err = transportBucket.Delete(key) + if err != nil { + return err + } + } + first, _ := transportBucket.Cursor().First() + if first == nil { + emptyTransports = append(emptyTransports, append([]byte(nil), transportName...)) + } + return nil + }) + if err != nil { + return err + } + for _, name := range emptyTransports { + err = bucket.DeleteBucket(name) + if err != nil { + return err + } + } + return nil + }) + if err != nil { + c.logger.Warn("cleanup RDRC: ", err) + } +} diff --git a/experimental/cachefile/rdrc.go b/experimental/cachefile/rdrc.go index c4800951cb..c02259c389 100644 --- a/experimental/cachefile/rdrc.go +++ b/experimental/cachefile/rdrc.go @@ -21,7 +21,7 @@ func (c *CacheFile) RDRCTimeout() time.Duration { func (c *CacheFile) LoadRDRC(transportName string, qName string, qType uint16) (rejected bool) { c.saveRDRCAccess.RLock() - rejected, cached := c.saveRDRC[saveRDRCCacheKey{transportName, qName, qType}] + rejected, cached := c.saveRDRC[saveCacheKey{transportName, qName, qType}] c.saveRDRCAccess.RUnlock() if cached { return @@ -31,7 +31,7 @@ func (c *CacheFile) LoadRDRC(transportName string, qName string, qType uint16) ( copy(key[2:], qName) defer buf.Put(key) var deleteCache bool - err := c.DB.View(func(tx *bbolt.Tx) error { + err := c.view(func(tx *bbolt.Tx) error { bucket := c.bucket(tx, bucketRDRC) if bucket == nil { return nil @@ -56,7 +56,7 @@ func (c *CacheFile) LoadRDRC(transportName string, qName string, qType uint16) ( return } if deleteCache { - c.DB.Update(func(tx *bbolt.Tx) error { + c.update(func(tx *bbolt.Tx) error { bucket := c.bucket(tx, bucketRDRC) if bucket == nil { return nil @@ -72,7 +72,10 @@ func (c *CacheFile) LoadRDRC(transportName string, qName string, qType uint16) ( } func (c *CacheFile) SaveRDRC(transportName string, qName string, qType uint16) error { - return c.DB.Batch(func(tx *bbolt.Tx) error { + expiresAt := buf.Get(8) + defer buf.Put(expiresAt) + binary.BigEndian.PutUint64(expiresAt, uint64(time.Now().Add(c.rdrcTimeout).Unix())) + return c.batch(func(tx *bbolt.Tx) error { bucket, err := c.createBucket(tx, bucketRDRC) if err != nil { return err @@ -85,15 +88,12 @@ func (c *CacheFile) SaveRDRC(transportName string, qName string, qType uint16) e binary.BigEndian.PutUint16(key, qType) copy(key[2:], qName) defer buf.Put(key) - expiresAt := buf.Get(8) - defer buf.Put(expiresAt) - binary.BigEndian.PutUint64(expiresAt, uint64(time.Now().Add(c.rdrcTimeout).Unix())) return bucket.Put(key, expiresAt) }) } func (c *CacheFile) SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger) { - saveKey := saveRDRCCacheKey{transportName, qName, qType} + saveKey := saveCacheKey{transportName, qName, qType} c.saveRDRCAccess.Lock() c.saveRDRC[saveKey] = true c.saveRDRCAccess.Unlock() diff --git a/experimental/clashapi.go b/experimental/clashapi.go index 872d9b9956..4ad07c8b88 100644 --- a/experimental/clashapi.go +++ b/experimental/clashapi.go @@ -12,7 +12,7 @@ import ( "github.com/sagernet/sing/common" ) -type ClashServerConstructor = func(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) +type ClashServerConstructor = func(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) var clashServerConstructor ClashServerConstructor @@ -20,11 +20,11 @@ func RegisterClashServerConstructor(constructor ClashServerConstructor) { clashServerConstructor = constructor } -func NewClashServer(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { +func NewClashServer(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { if clashServerConstructor == nil { return nil, os.ErrInvalid } - return clashServerConstructor(ctx, router, logFactory, options) + return clashServerConstructor(ctx, logFactory, options) } func CalculateClashModeList(options option.Options) []string { diff --git a/experimental/clashapi/api_meta.go b/experimental/clashapi/api_meta.go index 29add8ae94..8b31d8a427 100644 --- a/experimental/clashapi/api_meta.go +++ b/experimental/clashapi/api_meta.go @@ -2,8 +2,10 @@ package clashapi import ( "bytes" + "context" "net" "net/http" + "runtime/debug" "time" "github.com/sagernet/sing-box/experimental/clashapi/trafficontrol" @@ -12,14 +14,23 @@ import ( "github.com/sagernet/ws/wsutil" "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/render" ) // API created by Clash.Meta func (s *Server) setupMetaAPI(r chi.Router) { - r.Get("/memory", memory(s.trafficManager)) + if s.logDebug { + r := chi.NewRouter() + r.Put("/gc", func(w http.ResponseWriter, r *http.Request) { + debug.FreeOSMemory() + }) + r.Mount("/", middleware.Profiler()) + } + r.Get("/memory", memory(s.ctx, s.trafficManager)) r.Mount("/group", groupRouter(s)) + r.Mount("/upgrade", upgradeRouter(s)) } type Memory struct { @@ -27,7 +38,7 @@ type Memory struct { OSLimit uint64 `json:"oslimit"` // maybe we need it in the future } -func memory(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) { +func memory(ctx context.Context, trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var conn net.Conn if r.Header.Get("Upgrade") == "websocket" { @@ -36,6 +47,7 @@ func memory(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r if err != nil { return } + defer conn.Close() } if conn == nil { @@ -48,7 +60,12 @@ func memory(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r buf := &bytes.Buffer{} var err error first := true - for range tick.C { + for { + select { + case <-ctx.Done(): + return + case <-tick.C: + } buf.Reset() inuse := trafficManager.Snapshot().Memory diff --git a/experimental/clashapi/api_meta_group.go b/experimental/clashapi/api_meta_group.go index 396dee7f60..5f75ee0e32 100644 --- a/experimental/clashapi/api_meta_group.go +++ b/experimental/clashapi/api_meta_group.go @@ -10,7 +10,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/urltest" - "github.com/sagernet/sing-box/outbound" + "github.com/sagernet/sing-box/protocol/group" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/batch" "github.com/sagernet/sing/common/json/badjson" @@ -23,7 +23,7 @@ func groupRouter(server *Server) http.Handler { r := chi.NewRouter() r.Get("/", getGroups(server)) r.Route("/{name}", func(r chi.Router) { - r.Use(parseProxyName, findProxyByName(server.router)) + r.Use(parseProxyName, findProxyByName(server)) r.Get("/", getGroup(server)) r.Get("/delay", getGroupDelay(server)) }) @@ -32,7 +32,7 @@ func groupRouter(server *Server) http.Handler { func getGroups(server *Server) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - groups := common.Map(common.Filter(server.router.Outbounds(), func(it adapter.Outbound) bool { + groups := common.Map(common.Filter(server.outbound.Outbounds(), func(it adapter.Outbound) bool { _, isGroup := it.(adapter.OutboundGroup) return isGroup }), func(it adapter.Outbound) *badjson.JSONObject { @@ -59,7 +59,7 @@ func getGroup(server *Server) func(w http.ResponseWriter, r *http.Request) { func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound) - group, ok := proxy.(adapter.OutboundGroup) + outboundGroup, ok := proxy.(adapter.OutboundGroup) if !ok { render.Status(r, http.StatusNotFound) render.JSON(w, r, ErrNotFound) @@ -82,11 +82,13 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) defer cancel() var result map[string]uint16 - if urlTestGroup, isURLTestGroup := group.(adapter.URLTestGroup); isURLTestGroup { + if urlTestGroup, isURLTestGroup := outboundGroup.(adapter.URLTestGroup); isURLTestGroup { result, err = urlTestGroup.URLTest(ctx) + // } else if outboundGroup.Type() == C.TypeBalancer { + // result, err = monitoring.Balancer.URLTest(ctx) } else { - outbounds := common.FilterNotNil(common.Map(group.All(), func(it string) adapter.Outbound { - itOutbound, _ := server.router.Outbound(it) + outbounds := common.FilterNotNil(common.Map(outboundGroup.All(), func(it string) adapter.Outbound { + itOutbound, _ := server.outbound.Outbound(it) return itOutbound })) b, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10)) @@ -95,12 +97,12 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) var resultAccess sync.Mutex for _, detour := range outbounds { tag := detour.Tag() - realTag := outbound.RealTag(detour) + realTag := group.RealTag(detour) if checked[realTag] { continue } checked[realTag] = true - p, loaded := server.router.Outbound(realTag) + p, loaded := server.outbound.Outbound(realTag) if !loaded { continue } @@ -111,7 +113,7 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) server.urlTestHistory.DeleteURLTestHistory(realTag) } else { server.logger.Debug("outbound ", tag, " available: ", t, "ms") - server.urlTestHistory.StoreURLTestHistory(realTag, &urltest.History{ + server.urlTestHistory.StoreURLTestHistory(realTag, &adapter.URLTestHistory{ Time: time.Now(), Delay: t, }) diff --git a/experimental/clashapi/api_meta_upgrade.go b/experimental/clashapi/api_meta_upgrade.go new file mode 100644 index 0000000000..df70088edf --- /dev/null +++ b/experimental/clashapi/api_meta_upgrade.go @@ -0,0 +1,36 @@ +package clashapi + +import ( + "net/http" + + E "github.com/sagernet/sing/common/exceptions" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" +) + +func upgradeRouter(server *Server) http.Handler { + r := chi.NewRouter() + r.Post("/ui", updateExternalUI(server)) + return r +} + +func updateExternalUI(server *Server) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + if server.externalUI == "" { + render.Status(r, http.StatusNotFound) + render.JSON(w, r, newError("external UI not enabled")) + return + } + server.logger.Info("upgrading external UI") + err := server.downloadExternalUI() + if err != nil { + server.logger.Error(E.Cause(err, "upgrade external ui")) + render.Status(r, http.StatusInternalServerError) + render.JSON(w, r, newError(err.Error())) + return + } + server.logger.Info("updated external UI") + render.JSON(w, r, render.M{"status": "ok"}) + } +} diff --git a/experimental/clashapi/cache.go b/experimental/clashapi/cache.go index 9c088a82f7..4df1f89081 100644 --- a/experimental/clashapi/cache.go +++ b/experimental/clashapi/cache.go @@ -14,6 +14,7 @@ import ( func cacheRouter(ctx context.Context) http.Handler { r := chi.NewRouter() r.Post("/fakeip/flush", flushFakeip(ctx)) + r.Post("/dns/flush", flushDNS(ctx)) return r } @@ -31,3 +32,13 @@ func flushFakeip(ctx context.Context) func(w http.ResponseWriter, r *http.Reques render.NoContent(w, r) } } + +func flushDNS(ctx context.Context) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + dnsRouter := service.FromContext[adapter.DNSRouter](ctx) + if dnsRouter != nil { + dnsRouter.ClearCache() + } + render.NoContent(w, r) + } +} diff --git a/experimental/clashapi/configs.go b/experimental/clashapi/configs.go index 9d1e6109d2..8ae1d258bb 100644 --- a/experimental/clashapi/configs.go +++ b/experimental/clashapi/configs.go @@ -18,17 +18,19 @@ func configRouter(server *Server, logFactory log.Factory) http.Handler { } type configSchema struct { - Port int `json:"port"` - SocksPort int `json:"socks-port"` - RedirPort int `json:"redir-port"` - TProxyPort int `json:"tproxy-port"` - MixedPort int `json:"mixed-port"` - AllowLan bool `json:"allow-lan"` - BindAddress string `json:"bind-address"` - Mode string `json:"mode"` - LogLevel string `json:"log-level"` - IPv6 bool `json:"ipv6"` - Tun map[string]any `json:"tun"` + Port int `json:"port"` + SocksPort int `json:"socks-port"` + RedirPort int `json:"redir-port"` + TProxyPort int `json:"tproxy-port"` + MixedPort int `json:"mixed-port"` + AllowLan bool `json:"allow-lan"` + BindAddress string `json:"bind-address"` + Mode string `json:"mode"` + // sing-box added + ModeList []string `json:"mode-list"` + LogLevel string `json:"log-level"` + IPv6 bool `json:"ipv6"` + Tun map[string]any `json:"tun"` } func getConfigs(server *Server, logFactory log.Factory) func(w http.ResponseWriter, r *http.Request) { @@ -41,6 +43,7 @@ func getConfigs(server *Server, logFactory log.Factory) func(w http.ResponseWrit } render.JSON(w, r, &configSchema{ Mode: server.mode, + ModeList: server.modeList, BindAddress: "*", LogLevel: log.FormatLevel(logLevel), }) diff --git a/experimental/clashapi/connections.go b/experimental/clashapi/connections.go index c9471207e8..e60e6622f5 100644 --- a/experimental/clashapi/connections.go +++ b/experimental/clashapi/connections.go @@ -2,6 +2,7 @@ package clashapi import ( "bytes" + "context" "net/http" "strconv" "time" @@ -14,17 +15,18 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/render" + "github.com/gofrs/uuid/v5" ) -func connectionRouter(router adapter.Router, trafficManager *trafficontrol.Manager) http.Handler { +func connectionRouter(ctx context.Context, network adapter.NetworkManager, trafficManager *trafficontrol.Manager) http.Handler { r := chi.NewRouter() - r.Get("/", getConnections(trafficManager)) - r.Delete("/", closeAllConnections(router, trafficManager)) + r.Get("/", getConnections(ctx, trafficManager)) + r.Delete("/", closeAllConnections(network, trafficManager)) r.Delete("/{id}", closeConnection(trafficManager)) return r } -func getConnections(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) { +func getConnections(ctx context.Context, trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { if r.Header.Get("Upgrade") != "websocket" { snapshot := trafficManager.Snapshot() @@ -36,6 +38,7 @@ func getConnections(trafficManager *trafficontrol.Manager) func(w http.ResponseW if err != nil { return } + defer conn.Close() intervalStr := r.URL.Query().Get("interval") interval := 1000 @@ -66,7 +69,12 @@ func getConnections(trafficManager *trafficontrol.Manager) func(w http.ResponseW tick := time.NewTicker(time.Millisecond * time.Duration(interval)) defer tick.Stop() - for range tick.C { + for { + select { + case <-ctx.Done(): + return + case <-tick.C: + } if err = sendSnapshot(); err != nil { break } @@ -76,10 +84,10 @@ func getConnections(trafficManager *trafficontrol.Manager) func(w http.ResponseW func closeConnection(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - id := chi.URLParam(r, "id") + id := uuid.FromStringOrNil(chi.URLParam(r, "id")) snapshot := trafficManager.Snapshot() for _, c := range snapshot.Connections { - if id == c.ID() { + if id == c.Metadata().ID { c.Close() break } @@ -88,13 +96,13 @@ func closeConnection(trafficManager *trafficontrol.Manager) func(w http.Response } } -func closeAllConnections(router adapter.Router, trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) { +func closeAllConnections(network adapter.NetworkManager, trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { snapshot := trafficManager.Snapshot() for _, c := range snapshot.Connections { c.Close() } - router.ResetNetwork() + network.ResetNetwork() render.NoContent(w, r) } } diff --git a/experimental/clashapi/dns.go b/experimental/clashapi/dns.go index 2a21a7c101..4f850f8287 100644 --- a/experimental/clashapi/dns.go +++ b/experimental/clashapi/dns.go @@ -13,13 +13,13 @@ import ( "github.com/miekg/dns" ) -func dnsRouter(router adapter.Router) http.Handler { +func dnsRouter(router adapter.DNSRouter) http.Handler { r := chi.NewRouter() r.Get("/query", queryDNS(router)) return r } -func queryDNS(router adapter.Router) func(w http.ResponseWriter, r *http.Request) { +func queryDNS(router adapter.DNSRouter) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") qTypeStr := r.URL.Query().Get("type") @@ -39,7 +39,7 @@ func queryDNS(router adapter.Router) func(w http.ResponseWriter, r *http.Request msg := dns.Msg{} msg.SetQuestion(dns.Fqdn(name), qType) - resp, err := router.Exchange(ctx, &msg) + resp, err := router.Exchange(ctx, &msg, adapter.DNSQueryOptions{}) if err != nil { render.Status(r, http.StatusInternalServerError) render.JSON(w, r, newError(err.Error())) diff --git a/experimental/clashapi/proxies.go b/experimental/clashapi/proxies.go index 7a807c1fae..ef88ff37c5 100644 --- a/experimental/clashapi/proxies.go +++ b/experimental/clashapi/proxies.go @@ -11,7 +11,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/urltest" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/outbound" + "github.com/sagernet/sing-box/protocol/group" "github.com/sagernet/sing/common" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json/badjson" @@ -23,10 +23,10 @@ import ( func proxyRouter(server *Server, router adapter.Router) http.Handler { r := chi.NewRouter() - r.Get("/", getProxies(server, router)) + r.Get("/", getProxies(server)) r.Route("/{name}", func(r chi.Router) { - r.Use(parseProxyName, findProxyByName(router)) + r.Use(parseProxyName, findProxyByName(server)) r.Get("/", getProxy(server)) r.Get("/delay", getProxyDelay(server)) r.Put("/", updateProxy) @@ -42,11 +42,11 @@ func parseProxyName(next http.Handler) http.Handler { }) } -func findProxyByName(router adapter.Router) func(next http.Handler) http.Handler { +func findProxyByName(server *Server) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { name := r.Context().Value(CtxKeyProxyName).(string) - proxy, exist := router.Outbound(name) + proxy, exist := server.outbound.Outbound(name) if !exist { render.Status(r, http.StatusNotFound) render.JSON(w, r, ErrNotFound) @@ -72,9 +72,9 @@ func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject { info.Put("udp", common.Contains(detour.Network(), N.NetworkUDP)) delayHistory := server.urlTestHistory.LoadURLTestHistory(adapter.OutboundTag(detour)) if delayHistory != nil { - info.Put("history", []*urltest.History{delayHistory}) + info.Put("history", []*adapter.URLTestHistory{delayHistory}) } else { - info.Put("history", []*urltest.History{}) + info.Put("history", []*adapter.URLTestHistory{}) } if group, isGroup := detour.(adapter.OutboundGroup); isGroup { info.Put("now", group.Now()) @@ -83,12 +83,17 @@ func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject { return &info } -func getProxies(server *Server, router adapter.Router) func(w http.ResponseWriter, r *http.Request) { +func getProxies(server *Server) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var proxyMap badjson.JSONObject - outbounds := common.Filter(router.Outbounds(), func(detour adapter.Outbound) bool { + outbounds := common.Filter(server.outbound.Outbounds(), func(detour adapter.Outbound) bool { return detour.Tag() != "" }) + outbounds = append(outbounds, common.Map(common.Filter(server.endpoint.Endpoints(), func(detour adapter.Endpoint) bool { + return detour.Tag() != "" + }), func(it adapter.Endpoint) adapter.Outbound { + return it + })...) allProxies := make([]string, 0, len(outbounds)) @@ -100,12 +105,7 @@ func getProxies(server *Server, router adapter.Router) func(w http.ResponseWrite allProxies = append(allProxies, detour.Tag()) } - var defaultTag string - if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil { - defaultTag = defaultOutbound.Tag() - } else { - defaultTag = allProxies[0] - } + defaultTag := server.outbound.Default().Tag() sort.SliceStable(allProxies, func(i, j int) bool { return allProxies[i] == defaultTag @@ -116,7 +116,7 @@ func getProxies(server *Server, router adapter.Router) func(w http.ResponseWrite "type": "Fallback", "name": "GLOBAL", "udp": true, - "history": []*urltest.History{}, + "history": []*adapter.URLTestHistory{}, "all": allProxies, "now": defaultTag, }) @@ -168,7 +168,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) { } proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound) - selector, ok := proxy.(*outbound.Selector) + selector, ok := proxy.(*group.Selector) if !ok { render.Status(r, http.StatusBadRequest) render.JSON(w, r, newError("Must be a Selector")) @@ -204,11 +204,11 @@ func getProxyDelay(server *Server) func(w http.ResponseWriter, r *http.Request) delay, err := urltest.URLTest(ctx, url, proxy) defer func() { - realTag := outbound.RealTag(proxy) + realTag := group.RealTag(proxy) if err != nil { server.urlTestHistory.DeleteURLTestHistory(realTag) } else { - server.urlTestHistory.StoreURLTestHistory(realTag, &urltest.History{ + server.urlTestHistory.StoreURLTestHistory(realTag, &adapter.URLTestHistory{ Time: time.Now(), Delay: delay, }) diff --git a/experimental/clashapi/rules.go b/experimental/clashapi/rules.go index 6ab5dda108..bc8fbb2bba 100644 --- a/experimental/clashapi/rules.go +++ b/experimental/clashapi/rules.go @@ -30,10 +30,9 @@ func getRules(router adapter.Router) func(w http.ResponseWriter, r *http.Request rules = append(rules, Rule{ Type: rule.Type(), Payload: rule.String(), - Proxy: rule.Outbound(), + Proxy: rule.Action().String(), }) } - render.JSON(w, r, render.M{ "rules": rules, }) diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index 1eec8448af..a41adcdf96 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -7,9 +7,12 @@ import ( "net" "net/http" "os" + "runtime" "strings" + "syscall" "time" + "github.com/sagernet/cors" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/urltest" C "github.com/sagernet/sing-box/constant" @@ -18,17 +21,17 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/cleanup" E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/observable" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/filemanager" "github.com/sagernet/ws" "github.com/sagernet/ws/wsutil" "github.com/go-chi/chi/v5" - "github.com/go-chi/cors" "github.com/go-chi/render" ) @@ -40,14 +43,21 @@ var _ adapter.ClashServer = (*Server)(nil) type Server struct { ctx context.Context + network adapter.NetworkManager router adapter.Router + dnsRouter adapter.DNSRouter + outbound adapter.OutboundManager + endpoint adapter.EndpointManager logger log.Logger httpServer *http.Server trafficManager *trafficontrol.Manager - urlTestHistory *urltest.HistoryStorage + urlTestHistory adapter.URLTestHistoryStorage + logDebug bool + cleaner *cleanup.Cleaner + mode string modeList []string - modeUpdateHook chan<- struct{} + modeUpdateHook *observable.Subscriber[struct{}] externalController bool externalUI string @@ -55,107 +65,131 @@ type Server struct { externalUIDownloadDetour string } -func NewServer(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { +func NewServer(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { trafficManager := trafficontrol.NewManager() chiRouter := chi.NewRouter() - server := &Server{ - ctx: ctx, - router: router, - logger: logFactory.NewLogger("clash-api"), + s := &Server{ + ctx: ctx, + network: service.FromContext[adapter.NetworkManager](ctx), + router: service.FromContext[adapter.Router](ctx), + dnsRouter: service.FromContext[adapter.DNSRouter](ctx), + outbound: service.FromContext[adapter.OutboundManager](ctx), + endpoint: service.FromContext[adapter.EndpointManager](ctx), + logger: logFactory.NewLogger("clash-api"), httpServer: &http.Server{ Addr: options.ExternalController, Handler: chiRouter, }, trafficManager: trafficManager, + logDebug: logFactory.Level() >= log.LevelDebug, modeList: options.ModeList, externalController: options.ExternalController != "", externalUIDownloadURL: options.ExternalUIDownloadURL, externalUIDownloadDetour: options.ExternalUIDownloadDetour, + cleaner: cleanup.Add(trafficManager.Clear), } - server.urlTestHistory = service.PtrFromContext[urltest.HistoryStorage](ctx) - if server.urlTestHistory == nil { - server.urlTestHistory = urltest.NewHistoryStorage() + s.urlTestHistory = service.FromContext[adapter.URLTestHistoryStorage](ctx) + if s.urlTestHistory == nil { + s.urlTestHistory = urltest.NewHistoryStorage() } defaultMode := "Rule" if options.DefaultMode != "" { defaultMode = options.DefaultMode } - if !common.Contains(server.modeList, defaultMode) { - server.modeList = append([]string{defaultMode}, server.modeList...) + if !common.Contains(s.modeList, defaultMode) { + s.modeList = append([]string{defaultMode}, s.modeList...) } - server.mode = defaultMode + s.mode = defaultMode //goland:noinspection GoDeprecation //nolint:staticcheck if options.StoreMode || options.StoreSelected || options.StoreFakeIP || options.CacheFile != "" || options.CacheID != "" { return nil, E.New("cache_file and related fields in Clash API is deprecated in sing-box 1.8.0, use experimental.cache_file instead.") } + allowedOrigins := options.AccessControlAllowOrigin + if len(allowedOrigins) == 0 { + allowedOrigins = []string{"*"} + } cors := cors.New(cors.Options{ - AllowedOrigins: []string{"*"}, - AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, - AllowedHeaders: []string{"Content-Type", "Authorization"}, - MaxAge: 300, + AllowedOrigins: allowedOrigins, + AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, + AllowedHeaders: []string{"Content-Type", "Authorization"}, + AllowPrivateNetwork: options.AccessControlAllowPrivateNetwork, + MaxAge: 300, }) chiRouter.Use(cors.Handler) chiRouter.Group(func(r chi.Router) { r.Use(authentication(options.Secret)) r.Get("/", hello(options.ExternalUI != "")) - r.Get("/logs", getLogs(logFactory)) - r.Get("/traffic", traffic(trafficManager)) + r.Get("/logs", getLogs(s.ctx, logFactory)) + r.Get("/traffic", traffic(s.ctx, trafficManager)) r.Get("/version", version) - r.Mount("/configs", configRouter(server, logFactory)) - r.Mount("/proxies", proxyRouter(server, router)) - r.Mount("/rules", ruleRouter(router)) - r.Mount("/connections", connectionRouter(router, trafficManager)) + r.Mount("/configs", configRouter(s, logFactory)) + r.Mount("/proxies", proxyRouter(s, s.router)) + r.Mount("/rules", ruleRouter(s.router)) + r.Mount("/connections", connectionRouter(s.ctx, s.network, trafficManager)) r.Mount("/providers/proxies", proxyProviderRouter()) r.Mount("/providers/rules", ruleProviderRouter()) r.Mount("/script", scriptRouter()) r.Mount("/profile", profileRouter()) r.Mount("/cache", cacheRouter(ctx)) - r.Mount("/dns", dnsRouter(router)) + r.Mount("/dns", dnsRouter(s.dnsRouter)) - server.setupMetaAPI(r) + s.setupMetaAPI(r) }) if options.ExternalUI != "" { - server.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI)) + s.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI)) chiRouter.Group(func(r chi.Router) { - fs := http.StripPrefix("/ui", http.FileServer(http.Dir(server.externalUI))) - r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP) - r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) { - fs.ServeHTTP(w, r) - }) + r.Get("/ui", http.RedirectHandler("/ui/", http.StatusMovedPermanently).ServeHTTP) + r.Handle("/ui/*", http.StripPrefix("/ui/", http.FileServer(Dir(s.externalUI)))) }) } - return server, nil + return s, nil } -func (s *Server) PreStart() error { - cacheFile := service.FromContext[adapter.CacheFile](s.ctx) - if cacheFile != nil { - mode := cacheFile.LoadMode() - if common.Any(s.modeList, func(it string) bool { - return strings.EqualFold(it, mode) - }) { - s.mode = mode - } - } - return nil +func (s *Server) Name() string { + return "clash server" } -func (s *Server) Start() error { - if s.externalController { - s.checkAndDownloadExternalUI() - listener, err := net.Listen("tcp", s.httpServer.Addr) - if err != nil { - return E.Cause(err, "external controller listen error") +func (s *Server) Start(stage adapter.StartStage) error { + switch stage { + case adapter.StartStateStart: + cacheFile := service.FromContext[adapter.CacheFile](s.ctx) + if cacheFile != nil { + mode := cacheFile.LoadMode() + if common.Any(s.modeList, func(it string) bool { + return strings.EqualFold(it, mode) + }) { + s.mode = mode + } } - s.logger.Info("restful api listening at ", listener.Addr()) - go func() { - err = s.httpServer.Serve(listener) - if err != nil && !errors.Is(err, http.ErrServerClosed) { - s.logger.Error("external controller serve error: ", err) + case adapter.StartStateStarted: + if s.externalController { + s.checkAndDownloadExternalUI() + var ( + listener net.Listener + err error + ) + for range 3 { + listener, err = net.Listen("tcp", s.httpServer.Addr) + if runtime.GOOS == "android" && errors.Is(err, syscall.EADDRINUSE) { + time.Sleep(100 * time.Millisecond) + continue + } + break + } + if err != nil { + return E.Cause(err, "external controller listen error") } - }() + s.logger.Info("restful api listening at ", listener.Addr()) + go func() { + err = s.httpServer.Serve(listener) + if err != nil && !errors.Is(err, http.ErrServerClosed) { + s.logger.Error("external controller serve error: ", err) + } + }() + } } + return nil } @@ -164,6 +198,7 @@ func (s *Server) Close() error { common.PtrOrNil(s.httpServer), s.trafficManager, s.urlTestHistory, + common.PtrOrNil(s.cleaner), ) } @@ -175,7 +210,7 @@ func (s *Server) ModeList() []string { return s.modeList } -func (s *Server) SetModeUpdateHook(hook chan<- struct{}) { +func (s *Server) SetModeUpdateHook(hook *observable.Subscriber[struct{}]) { s.modeUpdateHook = hook } @@ -193,12 +228,9 @@ func (s *Server) SetMode(newMode string) { } s.mode = newMode if s.modeUpdateHook != nil { - select { - case s.modeUpdateHook <- struct{}{}: - default: - } + s.modeUpdateHook.Emit(struct{}{}) } - s.router.ClearDNSCache() + s.dnsRouter.ClearCache() cacheFile := service.FromContext[adapter.CacheFile](s.ctx) if cacheFile != nil { err := cacheFile.StoreMode(newMode) @@ -209,7 +241,7 @@ func (s *Server) SetMode(newMode string) { s.logger.Info("updated mode: ", newMode) } -func (s *Server) HistoryStorage() *urltest.HistoryStorage { +func (s *Server) HistoryStorage() adapter.URLTestHistoryStorage { return s.urlTestHistory } @@ -217,57 +249,12 @@ func (s *Server) TrafficManager() *trafficontrol.Manager { return s.trafficManager } -func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule) (net.Conn, adapter.Tracker) { - tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, castMetadata(metadata), s.router, matchedRule) - return tracker, tracker +func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) net.Conn { + return trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.outbound, matchedRule, matchOutbound) } -func (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule) (N.PacketConn, adapter.Tracker) { - tracker := trafficontrol.NewUDPTracker(conn, s.trafficManager, castMetadata(metadata), s.router, matchedRule) - return tracker, tracker -} - -func castMetadata(metadata adapter.InboundContext) trafficontrol.Metadata { - var inbound string - if metadata.Inbound != "" { - inbound = metadata.InboundType + "/" + metadata.Inbound - } else { - inbound = metadata.InboundType - } - var domain string - if metadata.Domain != "" { - domain = metadata.Domain - } else { - domain = metadata.Destination.Fqdn - } - var processPath string - if metadata.ProcessInfo != nil { - if metadata.ProcessInfo.ProcessPath != "" { - processPath = metadata.ProcessInfo.ProcessPath - } else if metadata.ProcessInfo.PackageName != "" { - processPath = metadata.ProcessInfo.PackageName - } - if processPath == "" { - if metadata.ProcessInfo.UserId != -1 { - processPath = F.ToString(metadata.ProcessInfo.UserId) - } - } else if metadata.ProcessInfo.User != "" { - processPath = F.ToString(processPath, " (", metadata.ProcessInfo.User, ")") - } else if metadata.ProcessInfo.UserId != -1 { - processPath = F.ToString(processPath, " (", metadata.ProcessInfo.UserId, ")") - } - } - return trafficontrol.Metadata{ - NetWork: metadata.Network, - Type: inbound, - SrcIP: metadata.Source.Addr, - DstIP: metadata.Destination.Addr, - SrcPort: F.ToString(metadata.Source.Port), - DstPort: F.ToString(metadata.Destination.Port), - Host: domain, - DNSMode: "normal", - ProcessPath: processPath, - } +func (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) N.PacketConn { + return trafficontrol.NewUDPTracker(conn, s.trafficManager, metadata, s.outbound, matchedRule, matchOutbound) } func authentication(serverSecret string) func(next http.Handler) http.Handler { @@ -308,10 +295,11 @@ func authentication(serverSecret string) func(next http.Handler) http.Handler { func hello(redirect bool) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - if redirect { - http.Redirect(w, r, "/ui/", http.StatusTemporaryRedirect) - } else { + contentType := r.Header.Get("Content-Type") + if !redirect || contentType == "application/json" { render.JSON(w, r, render.M{"hello": "clash"}) + } else { + http.Redirect(w, r, "/ui/", http.StatusTemporaryRedirect) } } } @@ -321,7 +309,7 @@ type Traffic struct { Down int64 `json:"down"` } -func traffic(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) { +func traffic(ctx context.Context, trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var conn net.Conn if r.Header.Get("Upgrade") == "websocket" { @@ -341,27 +329,34 @@ func traffic(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, tick := time.NewTicker(time.Second) defer tick.Stop() buf := &bytes.Buffer{} - var err error - for range tick.C { + uploadTotal, downloadTotal := trafficManager.Total() + for { + select { + case <-ctx.Done(): + return + case <-tick.C: + } buf.Reset() - up, down := trafficManager.Now() - if err := json.NewEncoder(buf).Encode(Traffic{ - Up: up, - Down: down, - }); err != nil { + uploadTotalNew, downloadTotalNew := trafficManager.Total() + err := json.NewEncoder(buf).Encode(Traffic{ + Up: uploadTotalNew - uploadTotal, + Down: downloadTotalNew - downloadTotal, + }) + if err != nil { break } - if conn == nil { _, err = w.Write(buf.Bytes()) w.(http.Flusher).Flush() } else { err = wsutil.WriteServerText(conn, buf.Bytes()) } - if err != nil { break } + + uploadTotal = uploadTotalNew + downloadTotal = downloadTotalNew } } } @@ -371,7 +366,7 @@ type Log struct { Payload string `json:"payload"` } -func getLogs(logFactory log.ObservableFactory) func(w http.ResponseWriter, r *http.Request) { +func getLogs(ctx context.Context, logFactory log.ObservableFactory) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { levelText := r.URL.Query().Get("level") if levelText == "" { @@ -410,6 +405,8 @@ func getLogs(logFactory log.ObservableFactory) func(w http.ResponseWriter, r *ht var logEntry log.Entry for { select { + case <-ctx.Done(): + return case <-done: return case logEntry = <-subscription: diff --git a/experimental/clashapi/server_fs.go b/experimental/clashapi/server_fs.go new file mode 100644 index 0000000000..afb4cca5e2 --- /dev/null +++ b/experimental/clashapi/server_fs.go @@ -0,0 +1,18 @@ +package clashapi + +import "net/http" + +type Dir http.Dir + +func (d Dir) Open(name string) (http.File, error) { + file, err := http.Dir(d).Open(name) + if err != nil { + return nil, err + } + return &fileWrapper{file}, nil +} + +// workaround for #2345 #2596 +type fileWrapper struct { + http.File +} diff --git a/experimental/clashapi/server_resources.go b/experimental/clashapi/server_resources.go index d6d22b5390..ad9fff5369 100644 --- a/experimental/clashapi/server_resources.go +++ b/experimental/clashapi/server_resources.go @@ -3,19 +3,20 @@ package clashapi import ( "archive/zip" "context" + "crypto/tls" "io" "net" "net/http" "os" "path/filepath" "strings" - "time" "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/service/filemanager" ) @@ -42,28 +43,29 @@ func (s *Server) downloadExternalUI() error { } else { downloadURL = "https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip" } - s.logger.Info("downloading external ui") var detour adapter.Outbound if s.externalUIDownloadDetour != "" { - outbound, loaded := s.router.Outbound(s.externalUIDownloadDetour) + outbound, loaded := s.outbound.Outbound(s.externalUIDownloadDetour) if !loaded { return E.New("detour outbound not found: ", s.externalUIDownloadDetour) } detour = outbound } else { - outbound, err := s.router.DefaultOutbound(N.NetworkTCP) - if err != nil { - return err - } + outbound := s.outbound.Default() detour = outbound } + s.logger.Info("downloading external ui using outbound/", detour.Type(), "[", detour.Tag(), "]") httpClient := &http.Client{ Transport: &http.Transport{ ForceAttemptHTTP2: true, - TLSHandshakeTimeout: 5 * time.Second, + TLSHandshakeTimeout: C.TCPTimeout, DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { return detour.DialContext(ctx, network, M.ParseSocksaddr(addr)) }, + TLSClientConfig: &tls.Config{ + Time: ntp.TimeFuncFromContext(s.ctx), + RootCAs: adapter.RootPoolFromContext(s.ctx), + }, }, } defer httpClient.CloseIdleConnections() @@ -75,15 +77,15 @@ func (s *Server) downloadExternalUI() error { if response.StatusCode != http.StatusOK { return E.New("download external ui failed: ", response.Status) } - err = s.downloadZIP(filepath.Base(downloadURL), response.Body, s.externalUI) + err = s.downloadZIP(response.Body, s.externalUI) if err != nil { removeAllInDirectory(s.externalUI) } return err } -func (s *Server) downloadZIP(name string, body io.Reader, output string) error { - tempFile, err := filemanager.CreateTemp(s.ctx, name) +func (s *Server) downloadZIP(body io.Reader, output string) error { + tempFile, err := filemanager.CreateTemp(s.ctx, "external-ui.zip") if err != nil { return err } diff --git a/experimental/clashapi/trafficontrol/manager.go b/experimental/clashapi/trafficontrol/manager.go index eac7aee4d8..407ab37d26 100644 --- a/experimental/clashapi/trafficontrol/manager.go +++ b/experimental/clashapi/trafficontrol/manager.go @@ -2,71 +2,169 @@ package trafficontrol import ( "runtime" + "sync" + "sync/atomic" "time" - "github.com/sagernet/sing-box/experimental/clashapi/compatible" - "github.com/sagernet/sing/common/atomic" + "github.com/sagernet/sing-box/common/compatible" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/observable" + "github.com/sagernet/sing/common/x/list" + + "github.com/gofrs/uuid/v5" ) -type Manager struct { - uploadTemp atomic.Int64 - downloadTemp atomic.Int64 - uploadBlip atomic.Int64 - downloadBlip atomic.Int64 - uploadTotal atomic.Int64 - downloadTotal atomic.Int64 +type ConnectionEventType int + +const ( + ConnectionEventNew ConnectionEventType = iota + ConnectionEventUpdate + ConnectionEventClosed +) - connections compatible.Map[string, tracker] - ticker *time.Ticker - done chan struct{} - // process *process.Process - memory uint64 +type ConnectionEvent struct { + Type ConnectionEventType + ID uuid.UUID + Metadata *TrackerMetadata + UplinkDelta int64 + DownlinkDelta int64 + ClosedAt time.Time +} + +const closedConnectionsLimit = 1000 + +type Manager struct { + uploadTotal atomic.Int64 + downloadTotal atomic.Int64 + outboundUploadTotal sync.Map + outboundDownloadTotal sync.Map + connections compatible.Map[uuid.UUID, Tracker] + closedConnectionsAccess sync.Mutex + closedConnections list.List[TrackerMetadata] + memory uint64 + + eventSubscriber *observable.Subscriber[ConnectionEvent] } func NewManager() *Manager { - manager := &Manager{ - ticker: time.NewTicker(time.Second), - done: make(chan struct{}), - // process: &process.Process{Pid: int32(os.Getpid())}, - } - go manager.handle() - return manager + return &Manager{} } -func (m *Manager) Join(c tracker) { - m.connections.Store(c.ID(), c) +func (m *Manager) SetEventHook(subscriber *observable.Subscriber[ConnectionEvent]) { + m.eventSubscriber = subscriber } -func (m *Manager) Leave(c tracker) { - m.connections.Delete(c.ID()) +func (m *Manager) Join(c Tracker) { + metadata := c.Metadata() + m.connections.Store(metadata.ID, c) + if m.eventSubscriber != nil { + m.eventSubscriber.Emit(ConnectionEvent{ + Type: ConnectionEventNew, + ID: metadata.ID, + Metadata: metadata, + }) + } +} + +func (m *Manager) Leave(c Tracker) { + metadata := c.Metadata() + _, loaded := m.connections.LoadAndDelete(metadata.ID) + if loaded { + closedAt := time.Now() + metadata.ClosedAt = closedAt + metadataCopy := *metadata + m.closedConnectionsAccess.Lock() + if m.closedConnections.Len() >= closedConnectionsLimit { + m.closedConnections.PopFront() + } + m.closedConnections.PushBack(metadataCopy) + m.closedConnectionsAccess.Unlock() + if m.eventSubscriber != nil { + m.eventSubscriber.Emit(ConnectionEvent{ + Type: ConnectionEventClosed, + ID: metadata.ID, + Metadata: &metadataCopy, + ClosedAt: closedAt, + }) + } + } } func (m *Manager) PushUploaded(size int64) { - m.uploadTemp.Add(size) m.uploadTotal.Add(size) } func (m *Manager) PushDownloaded(size int64) { - m.downloadTemp.Add(size) m.downloadTotal.Add(size) } -func (m *Manager) Now() (up int64, down int64) { - return m.uploadBlip.Load(), m.downloadBlip.Load() +func (m *Manager) PushOutboundUploaded(outbound string, size int64) { + v, _ := m.outboundUploadTotal.LoadOrStore(outbound, &atomic.Int64{}) + v.(*atomic.Int64).Add(size) +} + +func (m *Manager) PushOutboundDownloaded(outbound string, size int64) { + v, _ := m.outboundDownloadTotal.LoadOrStore(outbound, &atomic.Int64{}) + v.(*atomic.Int64).Add(size) } func (m *Manager) Total() (up int64, down int64) { return m.uploadTotal.Load(), m.downloadTotal.Load() } -func (m *Manager) Connections() int { +func (m *Manager) OutboundUsage(outbound string) (up int64, down int64) { + if vUp, ok := m.outboundUploadTotal.Load(outbound); ok { + up = vUp.(*atomic.Int64).Load() + } + if vDown, ok := m.outboundDownloadTotal.Load(outbound); ok { + down = vDown.(*atomic.Int64).Load() + } + return +} + +func (m *Manager) ConnectionsLen() int { return m.connections.Len() } +func (m *Manager) Connections() []*TrackerMetadata { + var connections []*TrackerMetadata + m.connections.Range(func(_ uuid.UUID, value Tracker) bool { + connections = append(connections, value.Metadata()) + return true + }) + return connections +} + +func (m *Manager) ClosedConnections() []*TrackerMetadata { + m.closedConnectionsAccess.Lock() + values := m.closedConnections.Array() + m.closedConnectionsAccess.Unlock() + if len(values) == 0 { + return nil + } + connections := make([]*TrackerMetadata, len(values)) + for i := range values { + connections[i] = &values[i] + } + return connections +} + +func (m *Manager) Connection(id uuid.UUID) Tracker { + connection, loaded := m.connections.Load(id) + if !loaded { + return nil + } + return connection +} + func (m *Manager) Snapshot() *Snapshot { - var connections []tracker - m.connections.Range(func(_ string, value tracker) bool { - connections = append(connections, value) + var connections []Tracker + m.connections.Range(func(_ uuid.UUID, value Tracker) bool { + if value.Metadata().OutboundType != C.TypeDNS { + connections = append(connections, value) + } return true }) @@ -75,47 +173,44 @@ func (m *Manager) Snapshot() *Snapshot { m.memory = memStats.StackInuse + memStats.HeapInuse + memStats.HeapIdle - memStats.HeapReleased return &Snapshot{ - UploadTotal: m.uploadTotal.Load(), - DownloadTotal: m.downloadTotal.Load(), - Connections: connections, - Memory: m.memory, + Upload: m.uploadTotal.Load(), + Download: m.downloadTotal.Load(), + Connections: connections, + Memory: m.memory, } } func (m *Manager) ResetStatistic() { - m.uploadTemp.Store(0) - m.uploadBlip.Store(0) m.uploadTotal.Store(0) - m.downloadTemp.Store(0) - m.downloadBlip.Store(0) m.downloadTotal.Store(0) + m.outboundUploadTotal.Range(func(key, value any) bool { + m.outboundUploadTotal.Delete(key) + return true + }) + m.outboundDownloadTotal.Range(func(key, value any) bool { + m.outboundDownloadTotal.Delete(key) + return true + }) } -func (m *Manager) handle() { - var uploadTemp int64 - var downloadTemp int64 - for { - select { - case <-m.done: - return - case <-m.ticker.C: - } - uploadTemp = m.uploadTemp.Swap(0) - downloadTemp = m.downloadTemp.Swap(0) - m.uploadBlip.Store(uploadTemp) - m.downloadBlip.Store(downloadTemp) - } +func (m *Manager) Clear() { + m.closedConnectionsAccess.Lock() + defer m.closedConnectionsAccess.Unlock() + m.closedConnections.Init() } -func (m *Manager) Close() error { - m.ticker.Stop() - close(m.done) - return nil +type Snapshot struct { + Download int64 + Upload int64 + Connections []Tracker + Memory uint64 } -type Snapshot struct { - DownloadTotal int64 `json:"downloadTotal"` - UploadTotal int64 `json:"uploadTotal"` - Connections []tracker `json:"connections"` - Memory uint64 `json:"memory"` +func (s *Snapshot) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]any{ + "downloadTotal": s.Download, + "uploadTotal": s.Upload, + "connections": common.Map(s.Connections, func(t Tracker) *TrackerMetadata { return t.Metadata() }), + "memory": s.Memory, + }) } diff --git a/experimental/clashapi/trafficontrol/tracker.go b/experimental/clashapi/trafficontrol/tracker.go index 4e635d1257..be83f4eb8d 100644 --- a/experimental/clashapi/trafficontrol/tracker.go +++ b/experimental/clashapi/trafficontrol/tracker.go @@ -2,235 +2,265 @@ package trafficontrol import ( "net" - "net/netip" + "sync/atomic" "time" + "github.com/gofrs/uuid/v5" "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/bufio" + F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" N "github.com/sagernet/sing/common/network" - - "github.com/gofrs/uuid/v5" ) -type Metadata struct { - NetWork string `json:"network"` - Type string `json:"type"` - SrcIP netip.Addr `json:"sourceIP"` - DstIP netip.Addr `json:"destinationIP"` - SrcPort string `json:"sourcePort"` - DstPort string `json:"destinationPort"` - Host string `json:"host"` - DNSMode string `json:"dnsMode"` - ProcessPath string `json:"processPath"` +type TrackerMetadata struct { + ID uuid.UUID + Metadata adapter.InboundContext + CreatedAt time.Time + ClosedAt time.Time + Upload *atomic.Int64 + Download *atomic.Int64 + Chain []string + Rule adapter.Rule + Outbound string + OutboundType string } -type tracker interface { - ID() string - Close() error - Leave() -} - -type trackerInfo struct { - UUID uuid.UUID `json:"id"` - Metadata Metadata `json:"metadata"` - UploadTotal *atomic.Int64 `json:"upload"` - DownloadTotal *atomic.Int64 `json:"download"` - Start time.Time `json:"start"` - Chain []string `json:"chains"` - Rule string `json:"rule"` - RulePayload string `json:"rulePayload"` -} - -func (t trackerInfo) MarshalJSON() ([]byte, error) { +func (t TrackerMetadata) MarshalJSON() ([]byte, error) { + var inbound string + if t.Metadata.Inbound != "" { + inbound = t.Metadata.InboundType + "/" + t.Metadata.Inbound + } else { + inbound = t.Metadata.InboundType + } + var domain string + if t.Metadata.Domain != "" { + domain = t.Metadata.Domain + } else { + domain = t.Metadata.Destination.Fqdn + } + var processPath string + if t.Metadata.ProcessInfo != nil { + if t.Metadata.ProcessInfo.ProcessPath != "" { + processPath = t.Metadata.ProcessInfo.ProcessPath + } else if len(t.Metadata.ProcessInfo.AndroidPackageNames) > 0 { + processPath = t.Metadata.ProcessInfo.AndroidPackageNames[0] + } + if processPath == "" { + if t.Metadata.ProcessInfo.UserId != -1 { + processPath = F.ToString(t.Metadata.ProcessInfo.UserId) + } + } else if t.Metadata.ProcessInfo.UserName != "" { + processPath = F.ToString(processPath, " (", t.Metadata.ProcessInfo.UserName, ")") + } else if t.Metadata.ProcessInfo.UserId != -1 { + processPath = F.ToString(processPath, " (", t.Metadata.ProcessInfo.UserId, ")") + } + } + var rule string + if t.Rule != nil { + rule = F.ToString(t.Rule, " => ", t.Rule.Action()) + } else { + rule = "final" + } + chains := t.Chain + if t.OutboundType == C.TypeBalancer { + chains = make([]string, len(t.Chain)+1) + chains[0] = t.Metadata.GetRealOutbound() + copy(chains[1:], t.Chain) + } return json.Marshal(map[string]any{ - "id": t.UUID.String(), - "metadata": t.Metadata, - "upload": t.UploadTotal.Load(), - "download": t.DownloadTotal.Load(), - "start": t.Start, + "id": t.ID, + "metadata": map[string]any{ + "network": t.Metadata.Network, + "type": inbound, + "sourceIP": t.Metadata.Source.Addr, + "destinationIP": t.Metadata.Destination.Addr, + "sourcePort": F.ToString(t.Metadata.Source.Port), + "destinationPort": F.ToString(t.Metadata.Destination.Port), + "host": domain, + "dnsMode": "normal", + "processPath": processPath, + }, + "upload": t.Upload.Load(), + "download": t.Download.Load(), + "start": t.CreatedAt, "chains": t.Chain, - "rule": t.Rule, - "rulePayload": t.RulePayload, + "rule": rule, + "rulePayload": "", }) } -type tcpTracker struct { - N.ExtendedConn `json:"-"` - *trackerInfo - manager *Manager +type Tracker interface { + Metadata() *TrackerMetadata + Close() error } -func (tt *tcpTracker) ID() string { - return tt.UUID.String() +type TCPConn struct { + N.ExtendedConn + metadata TrackerMetadata + manager *Manager } -func (tt *tcpTracker) Close() error { - tt.manager.Leave(tt) - return tt.ExtendedConn.Close() +func (tt *TCPConn) Metadata() *TrackerMetadata { + return &tt.metadata } -func (tt *tcpTracker) Leave() { +func (tt *TCPConn) Close() error { tt.manager.Leave(tt) + return tt.ExtendedConn.Close() } -func (tt *tcpTracker) Upstream() any { +func (tt *TCPConn) Upstream() any { return tt.ExtendedConn } -func (tt *tcpTracker) ReaderReplaceable() bool { +func (tt *TCPConn) ReaderReplaceable() bool { return true } -func (tt *tcpTracker) WriterReplaceable() bool { +func (tt *TCPConn) WriterReplaceable() bool { return true } -func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router adapter.Router, rule adapter.Rule) *tcpTracker { - uuid, _ := uuid.NewV4() - - var chain []string - var next string - if rule == nil { - if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil { - next = defaultOutbound.Tag() - } +func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundContext, outboundManager adapter.OutboundManager, matchRule adapter.Rule, matchOutbound adapter.Outbound) *TCPConn { + id, _ := uuid.NewV4() + var ( + chain []string + next string + outbound string + outboundType string + ) + + if matchOutbound != nil { + next = matchOutbound.Tag() } else { - next = rule.Outbound() + next = outboundManager.Default().Tag() } + for { - chain = append(chain, next) - detour, loaded := router.Outbound(next) + detour, loaded := outboundManager.Outbound(next) if !loaded { break } + chain = append(chain, next) + outbound = detour.Tag() + outboundType = detour.Type() group, isGroup := detour.(adapter.OutboundGroup) if !isGroup { break } next = group.Now() } - upload := new(atomic.Int64) download := new(atomic.Int64) - - t := &tcpTracker{ + tracker := &TCPConn{ ExtendedConn: bufio.NewCounterConn(conn, []N.CountFunc{func(n int64) { upload.Add(n) manager.PushUploaded(n) + manager.PushOutboundUploaded(outbound, n) }}, []N.CountFunc{func(n int64) { download.Add(n) manager.PushDownloaded(n) + manager.PushOutboundDownloaded(outbound, n) }}), - manager: manager, - trackerInfo: &trackerInfo{ - UUID: uuid, - Start: time.Now(), - Metadata: metadata, - Chain: common.Reverse(chain), - Rule: "", - UploadTotal: upload, - DownloadTotal: download, + metadata: TrackerMetadata{ + ID: id, + Metadata: metadata, + CreatedAt: time.Now(), + Upload: upload, + Download: download, + Chain: common.Reverse(chain), + Rule: matchRule, + Outbound: outbound, + OutboundType: outboundType, }, + manager: manager, } - - if rule != nil { - t.trackerInfo.Rule = rule.String() + " => " + rule.Outbound() - } else { - t.trackerInfo.Rule = "final" - } - - manager.Join(t) - return t + manager.Join(tracker) + return tracker } -type udpTracker struct { +type UDPConn struct { N.PacketConn `json:"-"` - *trackerInfo - manager *Manager + metadata TrackerMetadata + manager *Manager } -func (ut *udpTracker) ID() string { - return ut.UUID.String() +func (ut *UDPConn) Metadata() *TrackerMetadata { + return &ut.metadata } -func (ut *udpTracker) Close() error { +func (ut *UDPConn) Close() error { ut.manager.Leave(ut) return ut.PacketConn.Close() } -func (ut *udpTracker) Leave() { - ut.manager.Leave(ut) -} - -func (ut *udpTracker) Upstream() any { +func (ut *UDPConn) Upstream() any { return ut.PacketConn } -func (ut *udpTracker) ReaderReplaceable() bool { +func (ut *UDPConn) ReaderReplaceable() bool { return true } -func (ut *udpTracker) WriterReplaceable() bool { +func (ut *UDPConn) WriterReplaceable() bool { return true } -func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, router adapter.Router, rule adapter.Rule) *udpTracker { - uuid, _ := uuid.NewV4() - - var chain []string - var next string - if rule == nil { - if defaultOutbound, err := router.DefaultOutbound(N.NetworkUDP); err == nil { - next = defaultOutbound.Tag() - } +func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.InboundContext, outboundManager adapter.OutboundManager, matchRule adapter.Rule, matchOutbound adapter.Outbound) *UDPConn { + id, _ := uuid.NewV4() + var ( + chain []string + next string + outbound string + outboundType string + ) + if matchOutbound != nil { + next = matchOutbound.Tag() } else { - next = rule.Outbound() + next = outboundManager.Default().Tag() } for { - chain = append(chain, next) - detour, loaded := router.Outbound(next) + detour, loaded := outboundManager.Outbound(next) if !loaded { break } + chain = append(chain, next) + outbound = detour.Tag() + outboundType = detour.Type() group, isGroup := detour.(adapter.OutboundGroup) if !isGroup { break } next = group.Now() } - upload := new(atomic.Int64) download := new(atomic.Int64) - - ut := &udpTracker{ + trackerConn := &UDPConn{ PacketConn: bufio.NewCounterPacketConn(conn, []N.CountFunc{func(n int64) { upload.Add(n) manager.PushUploaded(n) + manager.PushOutboundUploaded(outbound, n) }}, []N.CountFunc{func(n int64) { download.Add(n) manager.PushDownloaded(n) + manager.PushOutboundDownloaded(outbound, n) }}), - manager: manager, - trackerInfo: &trackerInfo{ - UUID: uuid, - Start: time.Now(), - Metadata: metadata, - Chain: common.Reverse(chain), - Rule: "", - UploadTotal: upload, - DownloadTotal: download, + metadata: TrackerMetadata{ + ID: id, + Metadata: metadata, + CreatedAt: time.Now(), + Upload: upload, + Download: download, + Chain: common.Reverse(chain), + Rule: matchRule, + Outbound: outbound, + OutboundType: outboundType, }, + manager: manager, } - - if rule != nil { - ut.trackerInfo.Rule = rule.String() + " => " + rule.Outbound() - } else { - ut.trackerInfo.Rule = "final" - } - - manager.Join(ut) - return ut + manager.Join(trackerConn) + return trackerConn } diff --git a/experimental/deprecated/constants.go b/experimental/deprecated/constants.go new file mode 100644 index 0000000000..106514caae --- /dev/null +++ b/experimental/deprecated/constants.go @@ -0,0 +1,178 @@ +package deprecated + +import ( + "fmt" + + "github.com/sagernet/sing-box/common/badversion" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/experimental/locale" + F "github.com/sagernet/sing/common/format" + + "golang.org/x/mod/semver" +) + +type Note struct { + Name string + Description string + DeprecatedVersion string + ScheduledVersion string + EnvName string + MigrationLink string +} + +func (n Note) Impending() bool { + if n.ScheduledVersion == "" { + return false + } + if !semver.IsValid("v" + C.Version) { + return false + } + versionCurrent := badversion.Parse(C.Version) + versionMinor := badversion.Parse(n.ScheduledVersion).Minor - versionCurrent.Minor + if versionCurrent.PreReleaseIdentifier == "" && versionMinor < 0 { + panic("invalid deprecated note: " + n.Name) + } + return versionMinor <= 1 +} + +func (n Note) Message() string { + if n.MigrationLink != "" { + return fmt.Sprintf(locale.Current().DeprecatedMessage, n.Description, n.DeprecatedVersion, n.ScheduledVersion) + } else { + return fmt.Sprintf(locale.Current().DeprecatedMessageNoLink, n.Description, n.DeprecatedVersion, n.ScheduledVersion) + } +} + +func (n Note) MessageWithLink() string { + if n.MigrationLink != "" { + return F.ToString( + n.Description, " is deprecated in sing-box ", n.DeprecatedVersion, + " and will be removed in sing-box ", n.ScheduledVersion, ", checkout documentation for migration: ", n.MigrationLink, + ) + } else { + return F.ToString( + n.Description, " is deprecated in sing-box ", n.DeprecatedVersion, + " and will be removed in sing-box ", n.ScheduledVersion, ".", + ) + } +} + +var OptionOutboundDNSRuleItem = Note{ + Name: "outbound-dns-rule-item", + Description: "outbound DNS rule item", + DeprecatedVersion: "1.12.0", + ScheduledVersion: "1.14.0", + EnvName: "OUTBOUND_DNS_RULE_ITEM", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-outbound-dns-rule-items-to-domain-resolver", +} + +var OptionMissingDomainResolver = Note{ + Name: "missing-domain-resolver", + Description: "missing `route.default_domain_resolver` or `domain_resolver` in dial fields", + DeprecatedVersion: "1.12.0", + ScheduledVersion: "1.14.0", + EnvName: "MISSING_DOMAIN_RESOLVER", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-outbound-dns-rule-items-to-domain-resolver", +} + +var OptionLegacyDomainStrategyOptions = Note{ + Name: "legacy-domain-strategy-options", + Description: "legacy domain strategy options", + DeprecatedVersion: "1.12.0", + ScheduledVersion: "1.14.0", + EnvName: "LEGACY_DOMAIN_STRATEGY_OPTIONS", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-domain-strategy-options", +} + +var OptionInlineACME = Note{ + Name: "inline-acme-options", + Description: "inline ACME options in TLS", + DeprecatedVersion: "1.14.0", + ScheduledVersion: "1.16.0", + EnvName: "INLINE_ACME_OPTIONS", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-inline-acme-to-certificate-provider", +} + +var OptionLegacyRuleSetDownloadDetour = Note{ + Name: "legacy-rule-set-download-detour", + Description: "legacy `download_detour` remote rule-set option", + DeprecatedVersion: "1.14.0", + ScheduledVersion: "1.16.0", + EnvName: "LEGACY_RULE_SET_DOWNLOAD_DETOUR", +} + +var OptionLegacyTailscaleEndpointDialer = Note{ + Name: "legacy-tailscale-endpoint-dialer", + Description: "legacy dialer options in Tailscale endpoint", + DeprecatedVersion: "1.14.0", + ScheduledVersion: "1.16.0", + EnvName: "LEGACY_TAILSCALE_ENDPOINT_DIALER", +} + +var OptionRuleSetIPCIDRAcceptEmpty = Note{ + Name: "dns-rule-rule-set-ip-cidr-accept-empty", + Description: "Legacy `rule_set_ip_cidr_accept_empty` DNS rule item", + DeprecatedVersion: "1.14.0", + ScheduledVersion: "1.16.0", + EnvName: "DNS_RULE_RULE_SET_IP_CIDR_ACCEPT_EMPTY", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-address-filter-fields-to-response-matching", +} + +var OptionLegacyDNSAddressFilter = Note{ + Name: "legacy-dns-address-filter", + Description: "Legacy Address Filter Fields in DNS rules", + DeprecatedVersion: "1.14.0", + ScheduledVersion: "1.16.0", + EnvName: "LEGACY_DNS_ADDRESS_FILTER", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-address-filter-fields-to-response-matching", +} + +var OptionLegacyDNSRuleStrategy = Note{ + Name: "legacy-dns-rule-strategy", + Description: "Legacy `strategy` DNS rule action option", + DeprecatedVersion: "1.14.0", + ScheduledVersion: "1.16.0", + EnvName: "LEGACY_DNS_RULE_STRATEGY", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-dns-rule-action-strategy-to-rule-items", +} + +var OptionIndependentDNSCache = Note{ + Name: "independent-dns-cache", + Description: "`independent_cache` DNS option", + DeprecatedVersion: "1.14.0", + ScheduledVersion: "1.16.0", + EnvName: "INDEPENDENT_DNS_CACHE", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-independent-dns-cache", +} + +var OptionStoreRDRC = Note{ + Name: "store-rdrc", + Description: "`store_rdrc` cache file option", + DeprecatedVersion: "1.14.0", + ScheduledVersion: "1.16.0", + EnvName: "STORE_RDRC", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-store-rdrc", +} + +var OptionImplicitDefaultHTTPClient = Note{ + Name: "implicit-default-http-client", + Description: "implicit default HTTP client using default outbound for remote rule-sets", + DeprecatedVersion: "1.14.0", + ScheduledVersion: "1.16.0", + EnvName: "IMPLICIT_DEFAULT_HTTP_CLIENT", +} + +var Options = []Note{ + OptionOutboundDNSRuleItem, + OptionMissingDomainResolver, + OptionLegacyDomainStrategyOptions, + OptionInlineACME, + OptionLegacyRuleSetDownloadDetour, + OptionLegacyTailscaleEndpointDialer, + OptionRuleSetIPCIDRAcceptEmpty, + OptionLegacyDNSAddressFilter, + OptionLegacyDNSRuleStrategy, + OptionIndependentDNSCache, + OptionStoreRDRC, + OptionImplicitDefaultHTTPClient, +} diff --git a/experimental/deprecated/manager.go b/experimental/deprecated/manager.go new file mode 100644 index 0000000000..d12acf48fa --- /dev/null +++ b/experimental/deprecated/manager.go @@ -0,0 +1,19 @@ +package deprecated + +import ( + "context" + + "github.com/sagernet/sing/service" +) + +type Manager interface { + ReportDeprecated(feature Note) +} + +func Report(ctx context.Context, feature Note) { + manager := service.FromContext[Manager](ctx) + if manager == nil { + return + } + manager.ReportDeprecated(feature) +} diff --git a/experimental/deprecated/stderr.go b/experimental/deprecated/stderr.go new file mode 100644 index 0000000000..a999baea85 --- /dev/null +++ b/experimental/deprecated/stderr.go @@ -0,0 +1,46 @@ +package deprecated + +import ( + "os" + "strconv" + "sync" + + "github.com/sagernet/sing/common/logger" +) + +type stderrManager struct { + access sync.Mutex + logger logger.Logger + reported map[string]bool +} + +func NewStderrManager(logger logger.Logger) Manager { + return &stderrManager{ + logger: logger, + reported: make(map[string]bool), + } +} + +func (f *stderrManager) ReportDeprecated(feature Note) { + f.access.Lock() + defer f.access.Unlock() + if f.reported[feature.Name] { + return + } + f.reported[feature.Name] = true + if !feature.Impending() { + f.logger.Warn(feature.MessageWithLink()) + return + } + if feature.EnvName != "" { + enable, enableErr := strconv.ParseBool(os.Getenv("ENABLE_DEPRECATED_" + feature.EnvName)) + if enableErr == nil && enable { + f.logger.Warn(feature.MessageWithLink()) + return + } + f.logger.Error(feature.MessageWithLink()) + f.logger.Fatal("to continuing using this feature, set environment variable ENABLE_DEPRECATED_" + feature.EnvName + "=true") + } else { + f.logger.Error(feature.MessageWithLink()) + } +} diff --git a/experimental/libbox/command.go b/experimental/libbox/command.go index 7915419d64..8a43bc9545 100644 --- a/experimental/libbox/command.go +++ b/experimental/libbox/command.go @@ -3,15 +3,8 @@ package libbox const ( CommandLog int32 = iota CommandStatus - CommandServiceReload - CommandServiceClose - CommandCloseConnections CommandGroup - CommandSelectOutbound - CommandURLTest - CommandGroupExpand CommandClashMode - CommandSetClashMode - CommandGetSystemProxyStatus - CommandSetSystemProxyEnabled + CommandConnections + CommandOutbounds ) diff --git a/experimental/libbox/command_clash_mode.go b/experimental/libbox/command_clash_mode.go deleted file mode 100644 index 3377ae3afd..0000000000 --- a/experimental/libbox/command_clash_mode.go +++ /dev/null @@ -1,132 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "io" - "net" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/experimental/clashapi" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/rw" -) - -func (c *CommandClient) SetClashMode(newMode string) error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandSetClashMode)) - if err != nil { - return err - } - err = rw.WriteVString(conn, newMode) - if err != nil { - return err - } - return readError(conn) -} - -func (s *CommandServer) handleSetClashMode(conn net.Conn) error { - newMode, err := rw.ReadVString(conn) - if err != nil { - return err - } - service := s.service - if service == nil { - return writeError(conn, E.New("service not ready")) - } - clashServer := service.instance.Router().ClashServer() - if clashServer == nil { - return writeError(conn, E.New("Clash API disabled")) - } - clashServer.(*clashapi.Server).SetMode(newMode) - return writeError(conn, nil) -} - -func (c *CommandClient) handleModeConn(conn net.Conn) { - defer conn.Close() - - for { - newMode, err := rw.ReadVString(conn) - if err != nil { - c.handler.Disconnected(err.Error()) - return - } - c.handler.UpdateClashMode(newMode) - } -} - -func (s *CommandServer) handleModeConn(conn net.Conn) error { - ctx := connKeepAlive(conn) - for s.service == nil { - select { - case <-time.After(time.Second): - continue - case <-ctx.Done(): - return ctx.Err() - } - } - clashServer := s.service.instance.Router().ClashServer() - if clashServer == nil { - return binary.Write(conn, binary.BigEndian, uint16(0)) - } - err := writeClashModeList(conn, clashServer) - if err != nil { - return err - } - for { - select { - case <-s.modeUpdate: - err = rw.WriteVString(conn, clashServer.Mode()) - if err != nil { - return err - } - case <-ctx.Done(): - return ctx.Err() - } - } -} - -func readClashModeList(reader io.Reader) (modeList []string, currentMode string, err error) { - var modeListLength uint16 - err = binary.Read(reader, binary.BigEndian, &modeListLength) - if err != nil { - return - } - if modeListLength == 0 { - return - } - modeList = make([]string, modeListLength) - for i := 0; i < int(modeListLength); i++ { - modeList[i], err = rw.ReadVString(reader) - if err != nil { - return - } - } - currentMode, err = rw.ReadVString(reader) - return -} - -func writeClashModeList(writer io.Writer, clashServer adapter.ClashServer) error { - modeList := clashServer.ModeList() - err := binary.Write(writer, binary.BigEndian, uint16(len(modeList))) - if err != nil { - return err - } - if len(modeList) > 0 { - for _, mode := range modeList { - err = rw.WriteVString(writer, mode) - if err != nil { - return err - } - } - err = rw.WriteVString(writer, clashServer.Mode()) - if err != nil { - return err - } - } - return nil -} diff --git a/experimental/libbox/command_client.go b/experimental/libbox/command_client.go index f3c9ad2a19..9624c2e21e 100644 --- a/experimental/libbox/command_client.go +++ b/experimental/libbox/command_client.go @@ -1,40 +1,84 @@ package libbox import ( - "encoding/binary" + "context" + "io" "net" "os" "path/filepath" + "strconv" + "sync" "time" + "github.com/sagernet/sing-box/daemon" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" ) type CommandClient struct { - handler CommandClientHandler - conn net.Conn - options CommandClientOptions + handler CommandClientHandler + grpcConn *grpc.ClientConn + grpcClient daemon.StartedServiceClient + options CommandClientOptions + ctx context.Context + cancel context.CancelFunc + clientMutex sync.RWMutex + standalone bool } type CommandClientOptions struct { - Command int32 + commands []int32 StatusInterval int64 } +func (o *CommandClientOptions) AddCommand(command int32) { + o.commands = append(o.commands, command) +} + type CommandClientHandler interface { Connected() Disconnected(message string) - ClearLog() - WriteLog(message string) + SetDefaultLogLevel(level int32) + ClearLogs() + WriteLogs(messageList LogIterator) WriteStatus(message *StatusMessage) WriteGroups(message OutboundGroupIterator) + WriteOutbounds(message OutboundGroupItemIterator) InitializeClashMode(modeList StringIterator, currentMode string) UpdateClashMode(newMode string) + WriteConnectionEvents(events *ConnectionEvents) +} + +type LogEntry struct { + Level int32 + Message string +} + +type LogIterator interface { + Len() int32 + HasNext() bool + Next() *LogEntry +} + +type XPCDialer interface { + DialXPC() (int32, error) +} + +var sXPCDialer XPCDialer + +func SetXPCDialer(dialer XPCDialer) { + sXPCDialer = dialer } func NewStandaloneCommandClient() *CommandClient { - return new(CommandClient) + return &CommandClient{standalone: true} } func NewCommandClient(handler CommandClientHandler, options *CommandClientOptions) *CommandClient { @@ -44,82 +88,972 @@ func NewCommandClient(handler CommandClientHandler, options *CommandClientOption } } -func (c *CommandClient) directConnect() (net.Conn, error) { - if !sTVOS { - return net.DialUnix("unix", nil, &net.UnixAddr{ - Name: filepath.Join(sBasePath, "command.sock"), - Net: "unix", - }) - } else { - return net.Dial("tcp", "127.0.0.1:8964") +func unaryClientAuthInterceptor(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + if sCommandServerSecret != "" { + ctx = metadata.AppendToOutgoingContext(ctx, "x-command-secret", sCommandServerSecret) + } + return invoker(ctx, method, req, reply, cc, opts...) +} + +func streamClientAuthInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { + if sCommandServerSecret != "" { + ctx = metadata.AppendToOutgoingContext(ctx, "x-command-secret", sCommandServerSecret) + } + return streamer(ctx, desc, cc, method, opts...) +} + +const ( + commandClientDialAttempts = 10 + commandClientDialBaseDelay = 100 * time.Millisecond + commandClientDialStepDelay = 50 * time.Millisecond +) + +func commandClientDialDelay(attempt int) time.Duration { + return commandClientDialBaseDelay + time.Duration(attempt)*commandClientDialStepDelay +} + +func dialTarget() (string, func(context.Context, string) (net.Conn, error)) { + if sXPCDialer != nil { + return "passthrough:///xpc", func(ctx context.Context, _ string) (net.Conn, error) { + fileDescriptor, err := sXPCDialer.DialXPC() + if err != nil { + return nil, E.Cause(err, "dial xpc") + } + return networkConnectionFromFileDescriptor(fileDescriptor) + } + } + if sCommandServerListenPort == 0 { + socketPath := filepath.Join(sBasePath, "command.sock") + return "passthrough:///command-socket", func(ctx context.Context, _ string) (net.Conn, error) { + var networkDialer net.Dialer + return networkDialer.DialContext(ctx, "unix", socketPath) + } } + return net.JoinHostPort("127.0.0.1", strconv.Itoa(int(sCommandServerListenPort))), nil } -func (c *CommandClient) directConnectWithRetry() (net.Conn, error) { - var ( - conn net.Conn - err error - ) - for i := 0; i < 10; i++ { - conn, err = c.directConnect() +func networkConnectionFromFileDescriptor(fileDescriptor int32) (net.Conn, error) { + file := os.NewFile(uintptr(fileDescriptor), "xpc-command-socket") + if file == nil { + return nil, E.New("invalid file descriptor") + } + networkConnection, err := net.FileConn(file) + if err != nil { + file.Close() + return nil, E.Cause(err, "create connection from fd") + } + file.Close() + return networkConnection, nil +} + +func (c *CommandClient) dialWithRetry(target string, contextDialer func(context.Context, string) (net.Conn, error), retryDial bool) (*grpc.ClientConn, daemon.StartedServiceClient, error) { + var connection *grpc.ClientConn + var client daemon.StartedServiceClient + var lastError error + + for attempt := range commandClientDialAttempts { + if connection == nil { + options := []grpc.DialOption{ + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithUnaryInterceptor(unaryClientAuthInterceptor), + grpc.WithStreamInterceptor(streamClientAuthInterceptor), + } + if contextDialer != nil { + options = append(options, grpc.WithContextDialer(contextDialer)) + } + var err error + connection, err = grpc.NewClient(target, options...) + if err != nil { + lastError = err + if !retryDial { + return nil, nil, E.Cause(err, "create command client") + } + time.Sleep(commandClientDialDelay(attempt)) + continue + } + client = daemon.NewStartedServiceClient(connection) + } + waitDuration := commandClientDialDelay(attempt) + ctx, cancel := context.WithTimeout(context.Background(), waitDuration) + _, err := client.GetStartedAt(ctx, &emptypb.Empty{}, grpc.WaitForReady(true)) + cancel() if err == nil { - return conn, nil + return connection, client, nil } - time.Sleep(time.Duration(100+i*50) * time.Millisecond) + lastError = err } - return nil, err + + if connection != nil { + connection.Close() + } + return nil, nil, E.Cause(lastError, "probe command server") } func (c *CommandClient) Connect() error { - common.Close(c.conn) - conn, err := c.directConnectWithRetry() + c.clientMutex.Lock() + common.Close(common.PtrOrNil(c.grpcConn)) + + target, contextDialer := dialTarget() + connection, client, err := c.dialWithRetry(target, contextDialer, true) + if err != nil { + c.clientMutex.Unlock() + return err + } + c.grpcConn = connection + c.grpcClient = client + c.ctx, c.cancel = context.WithCancel(context.Background()) + c.clientMutex.Unlock() + + c.handler.Connected() + return c.dispatchCommands() +} + +func (c *CommandClient) ConnectWithFD(fd int32) error { + c.clientMutex.Lock() + common.Close(common.PtrOrNil(c.grpcConn)) + + networkConnection, err := networkConnectionFromFileDescriptor(fd) if err != nil { + c.clientMutex.Unlock() return err } - c.conn = conn - err = binary.Write(conn, binary.BigEndian, uint8(c.options.Command)) + connection, client, err := c.dialWithRetry("passthrough:///xpc", func(ctx context.Context, _ string) (net.Conn, error) { + return networkConnection, nil + }, false) if err != nil { + networkConnection.Close() + c.clientMutex.Unlock() return err } - switch c.options.Command { - case CommandLog: - c.handler.Connected() - go c.handleLogConn(conn) - case CommandStatus: - err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval) + c.grpcConn = connection + c.grpcClient = client + c.ctx, c.cancel = context.WithCancel(context.Background()) + c.clientMutex.Unlock() + + c.handler.Connected() + return c.dispatchCommands() +} + +func (c *CommandClient) dispatchCommands() error { + for _, command := range c.options.commands { + switch command { + case CommandLog: + go c.handleLogStream() + case CommandStatus: + go c.handleStatusStream() + case CommandGroup: + go c.handleGroupStream() + case CommandClashMode: + go c.handleClashModeStream() + case CommandConnections: + go c.handleConnectionsStream() + case CommandOutbounds: + go c.handleOutboundsStream() + default: + return E.New("unknown command: ", command) + } + } + return nil +} + +func (c *CommandClient) Disconnect() error { + c.clientMutex.Lock() + defer c.clientMutex.Unlock() + if c.cancel != nil { + c.cancel() + } + return common.Close(common.PtrOrNil(c.grpcConn)) +} + +func (c *CommandClient) getClientForCall() (daemon.StartedServiceClient, context.Context, error) { + c.clientMutex.RLock() + if c.grpcClient != nil { + defer c.clientMutex.RUnlock() + return c.grpcClient, c.ctx, nil + } + c.clientMutex.RUnlock() + + c.clientMutex.Lock() + defer c.clientMutex.Unlock() + + if c.grpcClient != nil { + return c.grpcClient, c.ctx, nil + } + + target, contextDialer := dialTarget() + connection, client, err := c.dialWithRetry(target, contextDialer, true) + if err != nil { + return nil, nil, E.Cause(err, "get command client") + } + c.grpcConn = connection + c.grpcClient = client + if c.ctx == nil { + c.ctx, c.cancel = context.WithCancel(context.Background()) + } + return c.grpcClient, c.ctx, nil +} + +func (c *CommandClient) closeConnection() { + c.clientMutex.Lock() + defer c.clientMutex.Unlock() + if c.grpcConn != nil { + c.grpcConn.Close() + c.grpcConn = nil + c.grpcClient = nil + } +} + +func callWithResult[T any](c *CommandClient, call func(ctx context.Context, client daemon.StartedServiceClient) (T, error)) (T, error) { + client, ctx, err := c.getClientForCall() + if err != nil { + var zero T + return zero, err + } + if c.standalone { + defer c.closeConnection() + } + return call(ctx, client) +} + +func (c *CommandClient) getStreamContext() (daemon.StartedServiceClient, context.Context) { + c.clientMutex.RLock() + defer c.clientMutex.RUnlock() + return c.grpcClient, c.ctx +} + +func (c *CommandClient) handleLogStream() { + client, ctx := c.getStreamContext() + stream, err := client.SubscribeLog(ctx, &emptypb.Empty{}) + if err != nil { + c.handler.Disconnected(E.Cause(err, "subscribe log").Error()) + return + } + defaultLogLevel, err := client.GetDefaultLogLevel(ctx, &emptypb.Empty{}) + if err != nil { + c.handler.Disconnected(E.Cause(err, "get default log level").Error()) + return + } + c.handler.SetDefaultLogLevel(int32(defaultLogLevel.Level)) + for { + logMessage, err := stream.Recv() + if err != nil { + c.handler.Disconnected(E.Cause(err, "log stream recv").Error()) + return + } + if logMessage.Reset_ { + c.handler.ClearLogs() + } + var messages []*LogEntry + for _, msg := range logMessage.Messages { + messages = append(messages, &LogEntry{ + Level: int32(msg.Level), + Message: msg.Message, + }) + } + c.handler.WriteLogs(newIterator(messages)) + } +} + +func (c *CommandClient) handleStatusStream() { + client, ctx := c.getStreamContext() + interval := c.options.StatusInterval + + stream, err := client.SubscribeStatus(ctx, &daemon.SubscribeStatusRequest{ + Interval: interval, + }) + if err != nil { + c.handler.Disconnected(E.Cause(err, "subscribe status").Error()) + return + } + + for { + status, err := stream.Recv() + if err != nil { + c.handler.Disconnected(E.Cause(err, "status stream recv").Error()) + return + } + c.handler.WriteStatus(statusMessageFromGRPC(status)) + } +} + +func (c *CommandClient) handleGroupStream() { + client, ctx := c.getStreamContext() + + stream, err := client.SubscribeGroups(ctx, &emptypb.Empty{}) + if err != nil { + c.handler.Disconnected(E.Cause(err, "subscribe groups").Error()) + return + } + + for { + groups, err := stream.Recv() if err != nil { - return E.Cause(err, "write interval") + c.handler.Disconnected(E.Cause(err, "groups stream recv").Error()) + return } - c.handler.Connected() - go c.handleStatusConn(conn) - case CommandGroup: - err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval) + c.handler.WriteGroups(outboundGroupIteratorFromGRPC(groups)) + } +} + +func (c *CommandClient) handleClashModeStream() { + client, ctx := c.getStreamContext() + + modeStatus, err := client.GetClashModeStatus(ctx, &emptypb.Empty{}) + if err != nil { + c.handler.Disconnected(E.Cause(err, "get clash mode status").Error()) + return + } + + if sFixAndroidStack { + go func() { + c.handler.InitializeClashMode(newIterator(modeStatus.ModeList), modeStatus.CurrentMode) + if len(modeStatus.ModeList) == 0 { + c.handler.Disconnected(E.Cause(os.ErrInvalid, "empty clash mode list").Error()) + } + }() + } else { + c.handler.InitializeClashMode(newIterator(modeStatus.ModeList), modeStatus.CurrentMode) + if len(modeStatus.ModeList) == 0 { + c.handler.Disconnected(E.Cause(os.ErrInvalid, "empty clash mode list").Error()) + return + } + } + + if len(modeStatus.ModeList) == 0 { + return + } + + stream, err := client.SubscribeClashMode(ctx, &emptypb.Empty{}) + if err != nil { + c.handler.Disconnected(E.Cause(err, "subscribe clash mode").Error()) + return + } + + for { + mode, err := stream.Recv() if err != nil { - return E.Cause(err, "write interval") - } - c.handler.Connected() - go c.handleGroupConn(conn) - case CommandClashMode: - var ( - modeList []string - currentMode string - ) - modeList, currentMode, err = readClashModeList(conn) + c.handler.Disconnected(E.Cause(err, "clash mode stream recv").Error()) + return + } + c.handler.UpdateClashMode(mode.Mode) + } +} + +func (c *CommandClient) handleConnectionsStream() { + client, ctx := c.getStreamContext() + interval := c.options.StatusInterval + + stream, err := client.SubscribeConnections(ctx, &daemon.SubscribeConnectionsRequest{ + Interval: interval, + }) + if err != nil { + c.handler.Disconnected(E.Cause(err, "subscribe connections").Error()) + return + } + + for { + events, err := stream.Recv() if err != nil { - return err + c.handler.Disconnected(E.Cause(err, "connections stream recv").Error()) + return } - c.handler.Connected() - c.handler.InitializeClashMode(newIterator(modeList), currentMode) - if len(modeList) == 0 { - conn.Close() - c.handler.Disconnected(os.ErrInvalid.Error()) - return nil + libboxEvents := connectionEventsFromGRPC(events) + c.handler.WriteConnectionEvents(libboxEvents) + } +} + +func (c *CommandClient) handleOutboundsStream() { + client, ctx := c.getStreamContext() + + stream, err := client.SubscribeOutbounds(ctx, &emptypb.Empty{}) + if err != nil { + c.handler.Disconnected(E.Cause(err, "subscribe outbounds").Error()) + return + } + + for { + list, err := stream.Recv() + if err != nil { + c.handler.Disconnected(E.Cause(err, "outbounds stream recv").Error()) + return } - go c.handleModeConn(conn) + c.handler.WriteOutbounds(outboundGroupItemListFromGRPC(list)) + } +} + +func (c *CommandClient) SelectOutbound(groupTag string, outboundTag string) error { + _, err := callWithResult(c, func(ctx context.Context, client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.SelectOutbound(ctx, &daemon.SelectOutboundRequest{ + GroupTag: groupTag, + OutboundTag: outboundTag, + }) + }) + if err != nil { + return E.Cause(err, "select outbound") } return nil } -func (c *CommandClient) Disconnect() error { - return common.Close(c.conn) +func (c *CommandClient) URLTest(groupTag string) error { + _, err := callWithResult(c, func(ctx context.Context, client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.URLTest(ctx, &daemon.URLTestRequest{ + OutboundTag: groupTag, + }) + }) + if err != nil { + return E.Cause(err, "url test") + } + return nil +} + +func (c *CommandClient) SetClashMode(newMode string) error { + _, err := callWithResult(c, func(ctx context.Context, client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.SetClashMode(ctx, &daemon.ClashMode{ + Mode: newMode, + }) + }) + if err != nil { + return E.Cause(err, "set clash mode") + } + return nil +} + +func (c *CommandClient) CloseConnection(connId string) error { + _, err := callWithResult(c, func(ctx context.Context, client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.CloseConnection(ctx, &daemon.CloseConnectionRequest{ + Id: connId, + }) + }) + if err != nil { + return E.Cause(err, "close connection") + } + return nil +} + +func (c *CommandClient) CloseConnections() error { + _, err := callWithResult(c, func(ctx context.Context, client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.CloseAllConnections(ctx, &emptypb.Empty{}) + }) + if err != nil { + return E.Cause(err, "close all connections") + } + return nil +} + +func (c *CommandClient) ServiceReload() error { + _, err := callWithResult(c, func(ctx context.Context, client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.ReloadService(ctx, &emptypb.Empty{}) + }) + if err != nil { + return E.Cause(err, "reload service") + } + return nil +} + +func (c *CommandClient) ServiceClose() error { + _, err := callWithResult(c, func(ctx context.Context, client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.StopService(ctx, &emptypb.Empty{}) + }) + if err != nil { + return E.Cause(err, "stop service") + } + return nil +} + +func (c *CommandClient) ClearLogs() error { + _, err := callWithResult(c, func(ctx context.Context, client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.ClearLogs(ctx, &emptypb.Empty{}) + }) + if err != nil { + return E.Cause(err, "clear logs") + } + return nil +} + +func (c *CommandClient) GetSystemProxyStatus() (*SystemProxyStatus, error) { + return callWithResult(c, func(ctx context.Context, client daemon.StartedServiceClient) (*SystemProxyStatus, error) { + status, err := client.GetSystemProxyStatus(ctx, &emptypb.Empty{}) + if err != nil { + return nil, E.Cause(err, "get system proxy status") + } + return systemProxyStatusFromGRPC(status), nil + }) +} + +func (c *CommandClient) SetSystemProxyEnabled(isEnabled bool) error { + _, err := callWithResult(c, func(ctx context.Context, client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.SetSystemProxyEnabled(ctx, &daemon.SetSystemProxyEnabledRequest{ + Enabled: isEnabled, + }) + }) + if err != nil { + return E.Cause(err, "set system proxy enabled") + } + return nil +} + +func (c *CommandClient) TriggerGoCrash() error { + _, err := callWithResult(c, func(ctx context.Context, client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.TriggerDebugCrash(ctx, &daemon.DebugCrashRequest{ + Type: daemon.DebugCrashRequest_GO, + }) + }) + if err != nil { + return E.Cause(err, "trigger debug crash") + } + return nil +} + +func (c *CommandClient) TriggerNativeCrash() error { + _, err := callWithResult(c, func(ctx context.Context, client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.TriggerDebugCrash(ctx, &daemon.DebugCrashRequest{ + Type: daemon.DebugCrashRequest_NATIVE, + }) + }) + if err != nil { + return E.Cause(err, "trigger native crash") + } + return nil +} + +func (c *CommandClient) TriggerOOMReport() error { + _, err := callWithResult(c, func(ctx context.Context, client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.TriggerOOMReport(ctx, &emptypb.Empty{}) + }) + if err != nil { + return E.Cause(err, "trigger oom report") + } + return nil +} + +func (c *CommandClient) GetDeprecatedNotes() (DeprecatedNoteIterator, error) { + return callWithResult(c, func(ctx context.Context, client daemon.StartedServiceClient) (DeprecatedNoteIterator, error) { + warnings, err := client.GetDeprecatedWarnings(ctx, &emptypb.Empty{}) + if err != nil { + return nil, E.Cause(err, "get deprecated warnings") + } + var notes []*DeprecatedNote + for _, warning := range warnings.Warnings { + notes = append(notes, &DeprecatedNote{ + Description: warning.Description, + DeprecatedVersion: warning.DeprecatedVersion, + ScheduledVersion: warning.ScheduledVersion, + MigrationLink: warning.MigrationLink, + }) + } + return newIterator(notes), nil + }) +} + +func (c *CommandClient) GetStartedAt() (int64, error) { + return callWithResult(c, func(ctx context.Context, client daemon.StartedServiceClient) (int64, error) { + startedAt, err := client.GetStartedAt(ctx, &emptypb.Empty{}) + if err != nil { + return 0, E.Cause(err, "get started at") + } + return startedAt.StartedAt, nil + }) +} + +func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error { + _, err := callWithResult(c, func(ctx context.Context, client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.SetGroupExpand(ctx, &daemon.SetGroupExpandRequest{ + GroupTag: groupTag, + IsExpand: isExpand, + }) + }) + if err != nil { + return E.Cause(err, "set group expand") + } + return nil +} + +func (c *CommandClient) StartNetworkQualityTest(configURL string, outboundTag string, serial bool, maxRuntimeSeconds int32, http3 bool, handler NetworkQualityTestHandler) (*NetworkQualityTestSession, error) { + client, parentCtx, err := c.getClientForCall() + if err != nil { + return nil, E.Cause(err, "start network quality test") + } + + streamCtx, cancel := context.WithCancel(parentCtx) + session := &NetworkQualityTestSession{ + streamSession: streamSession{ + ctx: streamCtx, + cancel: cancel, + closeDone: make(chan struct{}), + }, + } + + failStart := func(cause error, message string) (*NetworkQualityTestSession, error) { + cancel() + if c.standalone { + c.closeConnection() + } + return nil, E.Cause(cause, message) + } + + stream, err := client.StartNetworkQualityTest(streamCtx, &daemon.NetworkQualityTestRequest{ + ConfigURL: configURL, + OutboundTag: outboundTag, + Serial: serial, + MaxRuntimeSeconds: maxRuntimeSeconds, + Http3: http3, + }) + if err != nil { + return failStart(err, "start network quality test") + } + + standalone := c.standalone + go func() { + defer func() { + close(session.closeDone) + if standalone { + c.closeConnection() + } + }() + for { + event, recvErr := stream.Recv() + if recvErr != nil { + if session.ctx.Err() != nil { + return + } + handler.OnError(E.Cause(recvErr, "network quality test recv").Error()) + return + } + if event.IsFinal { + if event.Error != "" { + handler.OnError(event.Error) + } else { + handler.OnResult(&NetworkQualityResult{ + DownloadCapacity: event.DownloadCapacity, + UploadCapacity: event.UploadCapacity, + DownloadRPM: event.DownloadRPM, + UploadRPM: event.UploadRPM, + IdleLatencyMs: event.IdleLatencyMs, + DownloadCapacityAccuracy: event.DownloadCapacityAccuracy, + UploadCapacityAccuracy: event.UploadCapacityAccuracy, + DownloadRPMAccuracy: event.DownloadRPMAccuracy, + UploadRPMAccuracy: event.UploadRPMAccuracy, + }) + } + return + } + handler.OnProgress(networkQualityProgressFromGRPC(event)) + } + }() + + return session, nil +} + +func (c *CommandClient) StartSTUNTest(server string, outboundTag string, handler STUNTestHandler) (*STUNTestSession, error) { + client, parentCtx, err := c.getClientForCall() + if err != nil { + return nil, E.Cause(err, "start stun test") + } + + streamCtx, cancel := context.WithCancel(parentCtx) + session := &STUNTestSession{ + streamSession: streamSession{ + ctx: streamCtx, + cancel: cancel, + closeDone: make(chan struct{}), + }, + } + + failStart := func(cause error, message string) (*STUNTestSession, error) { + cancel() + if c.standalone { + c.closeConnection() + } + return nil, E.Cause(cause, message) + } + + stream, err := client.StartSTUNTest(streamCtx, &daemon.STUNTestRequest{ + Server: server, + OutboundTag: outboundTag, + }) + if err != nil { + return failStart(err, "start stun test") + } + + standalone := c.standalone + go func() { + defer func() { + close(session.closeDone) + if standalone { + c.closeConnection() + } + }() + for { + event, recvErr := stream.Recv() + if recvErr != nil { + if session.ctx.Err() != nil { + return + } + handler.OnError(E.Cause(recvErr, "stun test recv").Error()) + return + } + if event.IsFinal { + if event.Error != "" { + handler.OnError(event.Error) + } else { + handler.OnResult(&STUNTestResult{ + ExternalAddr: event.ExternalAddr, + LatencyMs: event.LatencyMs, + NATMapping: event.NatMapping, + NATFiltering: event.NatFiltering, + NATTypeSupported: event.NatTypeSupported, + }) + } + return + } + handler.OnProgress(stunTestProgressFromGRPC(event)) + } + }() + + return session, nil +} + +func (c *CommandClient) SubscribeTailscaleStatus(handler TailscaleStatusHandler) (*TailscaleStatusSubscription, error) { + client, parentCtx, err := c.getClientForCall() + if err != nil { + return nil, E.Cause(err, "subscribe tailscale status") + } + + streamCtx, cancel := context.WithCancel(parentCtx) + session := &TailscaleStatusSubscription{ + streamSession: streamSession{ + ctx: streamCtx, + cancel: cancel, + closeDone: make(chan struct{}), + }, + } + + failStart := func(cause error, message string) (*TailscaleStatusSubscription, error) { + cancel() + if c.standalone { + c.closeConnection() + } + return nil, E.Cause(cause, message) + } + + stream, err := client.SubscribeTailscaleStatus(streamCtx, &emptypb.Empty{}) + if err != nil { + return failStart(err, "subscribe tailscale status") + } + + standalone := c.standalone + go func() { + defer func() { + close(session.closeDone) + if standalone { + c.closeConnection() + } + }() + for { + event, recvErr := stream.Recv() + if recvErr != nil { + if session.ctx.Err() != nil { + return + } + if status.Code(recvErr) == codes.NotFound || status.Code(recvErr) == codes.Unavailable { + return + } + handler.OnError(E.Cause(recvErr, "tailscale status recv").Error()) + return + } + handler.OnStatusUpdate(tailscaleStatusUpdateFromGRPC(event)) + } + }() + + return session, nil +} + +func (c *CommandClient) SetTailscaleExitNode(endpointTag string, stableID string) error { + _, err := callWithResult(c, func(ctx context.Context, client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.SetTailscaleExitNode(ctx, &daemon.SetTailscaleExitNodeRequest{ + EndpointTag: endpointTag, + StableID: stableID, + }) + }) + if err != nil { + return E.Cause(err, "set tailscale exit node") + } + return nil +} + +func (c *CommandClient) StartTailscalePing(endpointTag string, peerIP string, handler TailscalePingHandler) (*TailscalePingSession, error) { + client, parentCtx, err := c.getClientForCall() + if err != nil { + return nil, E.Cause(err, "start tailscale ping") + } + + streamCtx, cancel := context.WithCancel(parentCtx) + session := &TailscalePingSession{ + streamSession: streamSession{ + ctx: streamCtx, + cancel: cancel, + closeDone: make(chan struct{}), + }, + } + + failStart := func(cause error, message string) (*TailscalePingSession, error) { + cancel() + if c.standalone { + c.closeConnection() + } + return nil, E.Cause(cause, message) + } + + stream, err := client.StartTailscalePing(streamCtx, &daemon.TailscalePingRequest{ + EndpointTag: endpointTag, + PeerIP: peerIP, + }) + if err != nil { + return failStart(err, "start tailscale ping") + } + + standalone := c.standalone + go func() { + defer func() { + close(session.closeDone) + if standalone { + c.closeConnection() + } + }() + for { + event, recvErr := stream.Recv() + if recvErr != nil { + if session.ctx.Err() != nil { + return + } + handler.OnError(E.Cause(recvErr, "tailscale ping recv").Error()) + return + } + handler.OnPingResult(tailscalePingResultFromGRPC(event)) + } + }() + + return session, nil +} + +func (c *CommandClient) StartTailscaleSSHSession(opts *TailscaleSSHOptions, handler TailscaleSSHHandler) (*TailscaleSSHSession, error) { + client, parentCtx, err := c.getClientForCall() + if err != nil { + return nil, E.Cause(err, "start tailscale ssh session") + } + + streamCtx, cancel := context.WithCancel(parentCtx) + failStart := func(cause error, message string) (*TailscaleSSHSession, error) { + cancel() + if c.standalone { + c.closeConnection() + } + return nil, E.Cause(cause, message) + } + + stream, err := client.StartTailscaleSSHSession(streamCtx) + if err != nil { + return failStart(err, "start tailscale ssh session") + } + + sendErr := stream.Send(&daemon.TailscaleSSHClientMessage{ + Message: &daemon.TailscaleSSHClientMessage_Start{Start: &daemon.TailscaleSSHStart{ + EndpointTag: opts.EndpointTag, + PeerAddress: opts.PeerAddress, + Username: opts.Username, + TerminalType: opts.TerminalType, + Columns: opts.Columns, + Rows: opts.Rows, + WidthPixels: opts.WidthPixels, + HeightPixels: opts.HeightPixels, + HostKeys: iteratorToArray[string](opts.HostKeys), + ForwardAgent: opts.ForwardAgent, + }}, + }) + if sendErr != nil { + return failStart(sendErr, "send tailscale ssh start") + } + + session := &TailscaleSSHSession{ + stream: stream, + inputCh: make(chan []byte, 8), + resizeCh: make(chan tailscaleSSHResize, 1), + ctx: streamCtx, + cancel: cancel, + closeDone: make(chan struct{}), + } + + session.wg.Add(1) + go func() { + defer session.wg.Done() + for { + select { + case <-streamCtx.Done(): + return + case data := <-session.inputCh: + sendErr := stream.Send(&daemon.TailscaleSSHClientMessage{ + Message: &daemon.TailscaleSSHClientMessage_Input{Input: &daemon.TailscaleSSHInput{Data: data}}, + }) + if sendErr != nil { + cancel() + return + } + case resize := <-session.resizeCh: + sendErr := stream.Send(&daemon.TailscaleSSHClientMessage{ + Message: &daemon.TailscaleSSHClientMessage_Resize{Resize: &daemon.TailscaleSSHResize{ + Columns: resize.columns, + Rows: resize.rows, + WidthPixels: resize.widthPixels, + HeightPixels: resize.heightPixels, + }}, + }) + if sendErr != nil { + cancel() + return + } + } + } + }() + + session.wg.Add(1) + go func() { + defer session.wg.Done() + for { + msg, recvErr := stream.Recv() + if recvErr == io.EOF { + cancel() + return + } + if recvErr != nil { + handler.OnError(E.Cause(recvErr, "tailscale ssh recv").Error()) + cancel() + return + } + switch payload := msg.GetMessage().(type) { + case *daemon.TailscaleSSHServerMessage_AuthBanner: + handler.OnAuthBanner(payload.AuthBanner.Message) + case *daemon.TailscaleSSHServerMessage_Ready: + handler.OnReady() + case *daemon.TailscaleSSHServerMessage_Output: + handler.OnOutput(payload.Output.Data) + case *daemon.TailscaleSSHServerMessage_Exit: + handler.OnExit(payload.Exit.ExitCode, payload.Exit.Signal, payload.Exit.ErrorMessage) + cancel() + return + case *daemon.TailscaleSSHServerMessage_Error: + handler.OnError(payload.Error.Message) + } + } + }() + + standalone := c.standalone + go func() { + session.wg.Wait() + close(session.closeDone) + if standalone { + c.closeConnection() + } + }() + + return session, nil } diff --git a/experimental/libbox/command_conntrack.go b/experimental/libbox/command_conntrack.go deleted file mode 100644 index cf8389a67e..0000000000 --- a/experimental/libbox/command_conntrack.go +++ /dev/null @@ -1,28 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "net" - runtimeDebug "runtime/debug" - "time" - - "github.com/sagernet/sing-box/common/conntrack" -) - -func (c *CommandClient) CloseConnections() error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - return binary.Write(conn, binary.BigEndian, uint8(CommandCloseConnections)) -} - -func (s *CommandServer) handleCloseConnections(conn net.Conn) error { - conntrack.Close() - go func() { - time.Sleep(time.Second) - runtimeDebug.FreeOSMemory() - }() - return nil -} diff --git a/experimental/libbox/command_group.go b/experimental/libbox/command_group.go deleted file mode 100644 index 21fd39d29a..0000000000 --- a/experimental/libbox/command_group.go +++ /dev/null @@ -1,304 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "io" - "net" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/urltest" - "github.com/sagernet/sing-box/outbound" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/rw" - "github.com/sagernet/sing/service" -) - -type OutboundGroup struct { - Tag string - Type string - Selectable bool - Selected string - IsExpand bool - items []*OutboundGroupItem -} - -func (g *OutboundGroup) GetItems() OutboundGroupItemIterator { - return newIterator(g.items) -} - -type OutboundGroupIterator interface { - Next() *OutboundGroup - HasNext() bool -} - -type OutboundGroupItem struct { - Tag string - Type string - URLTestTime int64 - URLTestDelay int32 -} - -type OutboundGroupItemIterator interface { - Next() *OutboundGroupItem - HasNext() bool -} - -func (c *CommandClient) handleGroupConn(conn net.Conn) { - defer conn.Close() - - for { - groups, err := readGroups(conn) - if err != nil { - c.handler.Disconnected(err.Error()) - return - } - c.handler.WriteGroups(groups) - } -} - -func (s *CommandServer) handleGroupConn(conn net.Conn) error { - var interval int64 - err := binary.Read(conn, binary.BigEndian, &interval) - if err != nil { - return E.Cause(err, "read interval") - } - ticker := time.NewTicker(time.Duration(interval)) - defer ticker.Stop() - ctx := connKeepAlive(conn) - for { - service := s.service - if service != nil { - err := writeGroups(conn, service) - if err != nil { - return err - } - } else { - err := binary.Write(conn, binary.BigEndian, uint16(0)) - if err != nil { - return err - } - } - select { - case <-ctx.Done(): - return ctx.Err() - case <-ticker.C: - } - select { - case <-ctx.Done(): - return ctx.Err() - case <-s.urlTestUpdate: - } - } -} - -func readGroups(reader io.Reader) (OutboundGroupIterator, error) { - var groupLength uint16 - err := binary.Read(reader, binary.BigEndian, &groupLength) - if err != nil { - return nil, err - } - - groups := make([]*OutboundGroup, 0, groupLength) - for i := 0; i < int(groupLength); i++ { - var group OutboundGroup - group.Tag, err = rw.ReadVString(reader) - if err != nil { - return nil, err - } - - group.Type, err = rw.ReadVString(reader) - if err != nil { - return nil, err - } - - err = binary.Read(reader, binary.BigEndian, &group.Selectable) - if err != nil { - return nil, err - } - - group.Selected, err = rw.ReadVString(reader) - if err != nil { - return nil, err - } - - err = binary.Read(reader, binary.BigEndian, &group.IsExpand) - if err != nil { - return nil, err - } - - var itemLength uint16 - err = binary.Read(reader, binary.BigEndian, &itemLength) - if err != nil { - return nil, err - } - - group.items = make([]*OutboundGroupItem, itemLength) - for j := 0; j < int(itemLength); j++ { - var item OutboundGroupItem - item.Tag, err = rw.ReadVString(reader) - if err != nil { - return nil, err - } - - item.Type, err = rw.ReadVString(reader) - if err != nil { - return nil, err - } - - err = binary.Read(reader, binary.BigEndian, &item.URLTestTime) - if err != nil { - return nil, err - } - - err = binary.Read(reader, binary.BigEndian, &item.URLTestDelay) - if err != nil { - return nil, err - } - - group.items[j] = &item - } - groups = append(groups, &group) - } - return newIterator(groups), nil -} - -func writeGroups(writer io.Writer, boxService *BoxService) error { - historyStorage := service.PtrFromContext[urltest.HistoryStorage](boxService.ctx) - cacheFile := service.FromContext[adapter.CacheFile](boxService.ctx) - outbounds := boxService.instance.Router().Outbounds() - var iGroups []adapter.OutboundGroup - for _, it := range outbounds { - if group, isGroup := it.(adapter.OutboundGroup); isGroup { - iGroups = append(iGroups, group) - } - } - var groups []OutboundGroup - for _, iGroup := range iGroups { - var group OutboundGroup - group.Tag = iGroup.Tag() - group.Type = iGroup.Type() - _, group.Selectable = iGroup.(*outbound.Selector) - group.Selected = iGroup.Now() - if cacheFile != nil { - if isExpand, loaded := cacheFile.LoadGroupExpand(group.Tag); loaded { - group.IsExpand = isExpand - } - } - - for _, itemTag := range iGroup.All() { - itemOutbound, isLoaded := boxService.instance.Router().Outbound(itemTag) - if !isLoaded { - continue - } - - var item OutboundGroupItem - item.Tag = itemTag - item.Type = itemOutbound.Type() - if history := historyStorage.LoadURLTestHistory(adapter.OutboundTag(itemOutbound)); history != nil { - item.URLTestTime = history.Time.Unix() - item.URLTestDelay = int32(history.Delay) - } - group.items = append(group.items, &item) - } - if len(group.items) < 2 { - continue - } - groups = append(groups, group) - } - - err := binary.Write(writer, binary.BigEndian, uint16(len(groups))) - if err != nil { - return err - } - for _, group := range groups { - err = rw.WriteVString(writer, group.Tag) - if err != nil { - return err - } - err = rw.WriteVString(writer, group.Type) - if err != nil { - return err - } - err = binary.Write(writer, binary.BigEndian, group.Selectable) - if err != nil { - return err - } - err = rw.WriteVString(writer, group.Selected) - if err != nil { - return err - } - err = binary.Write(writer, binary.BigEndian, group.IsExpand) - if err != nil { - return err - } - err = binary.Write(writer, binary.BigEndian, uint16(len(group.items))) - if err != nil { - return err - } - for _, item := range group.items { - err = rw.WriteVString(writer, item.Tag) - if err != nil { - return err - } - err = rw.WriteVString(writer, item.Type) - if err != nil { - return err - } - err = binary.Write(writer, binary.BigEndian, item.URLTestTime) - if err != nil { - return err - } - err = binary.Write(writer, binary.BigEndian, item.URLTestDelay) - if err != nil { - return err - } - } - } - return nil -} - -func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandGroupExpand)) - if err != nil { - return err - } - err = rw.WriteVString(conn, groupTag) - if err != nil { - return err - } - err = binary.Write(conn, binary.BigEndian, isExpand) - if err != nil { - return err - } - return readError(conn) -} - -func (s *CommandServer) handleSetGroupExpand(conn net.Conn) error { - groupTag, err := rw.ReadVString(conn) - if err != nil { - return err - } - var isExpand bool - err = binary.Read(conn, binary.BigEndian, &isExpand) - if err != nil { - return err - } - serviceNow := s.service - if serviceNow == nil { - return writeError(conn, E.New("service not ready")) - } - cacheFile := service.FromContext[adapter.CacheFile](serviceNow.ctx) - if cacheFile != nil { - err = cacheFile.StoreGroupExpand(groupTag, isExpand) - if err != nil { - return writeError(conn, err) - } - } - return writeError(conn, nil) -} diff --git a/experimental/libbox/command_log.go b/experimental/libbox/command_log.go deleted file mode 100644 index ce72010dd0..0000000000 --- a/experimental/libbox/command_log.go +++ /dev/null @@ -1,131 +0,0 @@ -package libbox - -import ( - "context" - "encoding/binary" - "io" - "net" -) - -func (s *CommandServer) WriteMessage(message string) { - s.subscriber.Emit(message) - s.access.Lock() - s.savedLines.PushBack(message) - if s.savedLines.Len() > s.maxLines { - s.savedLines.Remove(s.savedLines.Front()) - } - s.access.Unlock() -} - -func readLog(reader io.Reader) ([]byte, error) { - var messageLength uint16 - err := binary.Read(reader, binary.BigEndian, &messageLength) - if err != nil { - return nil, err - } - if messageLength == 0 { - return nil, nil - } - data := make([]byte, messageLength) - _, err = io.ReadFull(reader, data) - if err != nil { - return nil, err - } - return data, nil -} - -func writeLog(writer io.Writer, message []byte) error { - err := binary.Write(writer, binary.BigEndian, uint8(0)) - if err != nil { - return err - } - err = binary.Write(writer, binary.BigEndian, uint16(len(message))) - if err != nil { - return err - } - if len(message) > 0 { - _, err = writer.Write(message) - } - return err -} - -func writeClearLog(writer io.Writer) error { - return binary.Write(writer, binary.BigEndian, uint8(1)) -} - -func (s *CommandServer) handleLogConn(conn net.Conn) error { - var savedLines []string - s.access.Lock() - savedLines = make([]string, 0, s.savedLines.Len()) - for element := s.savedLines.Front(); element != nil; element = element.Next() { - savedLines = append(savedLines, element.Value) - } - s.access.Unlock() - subscription, done, err := s.observer.Subscribe() - if err != nil { - return err - } - defer s.observer.UnSubscribe(subscription) - for _, line := range savedLines { - err = writeLog(conn, []byte(line)) - if err != nil { - return err - } - } - ctx := connKeepAlive(conn) - for { - select { - case <-ctx.Done(): - return ctx.Err() - case message := <-subscription: - err = writeLog(conn, []byte(message)) - if err != nil { - return err - } - case <-s.logReset: - err = writeClearLog(conn) - if err != nil { - return err - } - case <-done: - return nil - } - } -} - -func (c *CommandClient) handleLogConn(conn net.Conn) { - for { - var messageType uint8 - err := binary.Read(conn, binary.BigEndian, &messageType) - if err != nil { - c.handler.Disconnected(err.Error()) - return - } - var message []byte - switch messageType { - case 0: - message, err = readLog(conn) - if err != nil { - c.handler.Disconnected(err.Error()) - return - } - c.handler.WriteLog(string(message)) - case 1: - c.handler.ClearLog() - } - } -} - -func connKeepAlive(reader io.Reader) context.Context { - ctx, cancel := context.WithCancelCause(context.Background()) - go func() { - for { - _, err := readLog(reader) - if err != nil { - cancel(err) - return - } - } - }() - return ctx -} diff --git a/experimental/libbox/command_power.go b/experimental/libbox/command_power.go deleted file mode 100644 index 619cb57b50..0000000000 --- a/experimental/libbox/command_power.go +++ /dev/null @@ -1,84 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "net" - - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/rw" -) - -func (c *CommandClient) ServiceReload() error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandServiceReload)) - if err != nil { - return err - } - var hasError bool - err = binary.Read(conn, binary.BigEndian, &hasError) - if err != nil { - return err - } - if hasError { - errorMessage, err := rw.ReadVString(conn) - if err != nil { - return err - } - return E.New(errorMessage) - } - return nil -} - -func (s *CommandServer) handleServiceReload(conn net.Conn) error { - rErr := s.handler.ServiceReload() - err := binary.Write(conn, binary.BigEndian, rErr != nil) - if err != nil { - return err - } - if rErr != nil { - return rw.WriteVString(conn, rErr.Error()) - } - return nil -} - -func (c *CommandClient) ServiceClose() error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandServiceClose)) - if err != nil { - return err - } - var hasError bool - err = binary.Read(conn, binary.BigEndian, &hasError) - if err != nil { - return nil - } - if hasError { - errorMessage, err := rw.ReadVString(conn) - if err != nil { - return nil - } - return E.New(errorMessage) - } - return nil -} - -func (s *CommandServer) handleServiceClose(conn net.Conn) error { - rErr := s.service.Close() - s.handler.PostServiceClose() - err := binary.Write(conn, binary.BigEndian, rErr != nil) - if err != nil { - return err - } - if rErr != nil { - return rw.WriteVString(conn, rErr.Error()) - } - return nil -} diff --git a/experimental/libbox/command_select.go b/experimental/libbox/command_select.go deleted file mode 100644 index e7d5b08f86..0000000000 --- a/experimental/libbox/command_select.go +++ /dev/null @@ -1,58 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "net" - - "github.com/sagernet/sing-box/outbound" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/rw" -) - -func (c *CommandClient) SelectOutbound(groupTag string, outboundTag string) error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandSelectOutbound)) - if err != nil { - return err - } - err = rw.WriteVString(conn, groupTag) - if err != nil { - return err - } - err = rw.WriteVString(conn, outboundTag) - if err != nil { - return err - } - return readError(conn) -} - -func (s *CommandServer) handleSelectOutbound(conn net.Conn) error { - groupTag, err := rw.ReadVString(conn) - if err != nil { - return err - } - outboundTag, err := rw.ReadVString(conn) - if err != nil { - return err - } - service := s.service - if service == nil { - return writeError(conn, E.New("service not ready")) - } - outboundGroup, isLoaded := service.instance.Router().Outbound(groupTag) - if !isLoaded { - return writeError(conn, E.New("selector not found: ", groupTag)) - } - selector, isSelector := outboundGroup.(*outbound.Selector) - if !isSelector { - return writeError(conn, E.New("outbound is not a selector: ", groupTag)) - } - if !selector.SelectOutbound(outboundTag) { - return writeError(conn, E.New("outbound not found in selector: ", outboundTag)) - } - return writeError(conn, nil) -} diff --git a/experimental/libbox/command_server.go b/experimental/libbox/command_server.go index da931ef552..a67b010e61 100644 --- a/experimental/libbox/command_server.go +++ b/experimental/libbox/command_server.go @@ -1,182 +1,293 @@ package libbox import ( - "encoding/binary" + "context" + "errors" "net" "os" "path/filepath" - "sync" + "strconv" + "syscall" + "time" - "github.com/sagernet/sing-box/common/urltest" - "github.com/sagernet/sing-box/experimental/clashapi" + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/daemon" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/debug" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/observable" - "github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/service" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" ) type CommandServer struct { - listener net.Listener - handler CommandServerHandler - - access sync.Mutex - savedLines list.List[string] - maxLines int - subscriber *observable.Subscriber[string] - observer *observable.Observer[string] - service *BoxService - - // These channels only work with a single client. if multi-client support is needed, replace with Subscriber/Observer - urlTestUpdate chan struct{} - modeUpdate chan struct{} - logReset chan struct{} + *daemon.StartedService + handler CommandServerHandler + platformInterface PlatformInterface + platformWrapper *platformInterfaceWrapper + grpcServer *grpc.Server + listener net.Listener + endPauseTimer *time.Timer } type CommandServerHandler interface { + ServiceStop() error ServiceReload() error - PostServiceClose() - GetSystemProxyStatus() *SystemProxyStatus - SetSystemProxyEnabled(isEnabled bool) error + GetSystemProxyStatus() (*SystemProxyStatus, error) + SetSystemProxyEnabled(enabled bool) error + TriggerNativeCrash() error + WriteDebugMessage(message string) + ConnectSSHAgent() (int32, error) } -func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServer { +func NewCommandServer(handler CommandServerHandler, platformInterface PlatformInterface) (*CommandServer, error) { + ctx := baseContext(platformInterface) + platformWrapper := &platformInterfaceWrapper{ + iif: platformInterface, + useProcFS: platformInterface.UseProcFS(), + } + service.MustRegister[adapter.PlatformInterface](ctx, platformWrapper) server := &CommandServer{ - handler: handler, - maxLines: int(maxLines), - subscriber: observable.NewSubscriber[string](128), - urlTestUpdate: make(chan struct{}, 1), - modeUpdate: make(chan struct{}, 1), - logReset: make(chan struct{}, 1), + handler: handler, + platformInterface: platformInterface, + platformWrapper: platformWrapper, } - server.observer = observable.NewObserver[string](server.subscriber, 64) - return server + server.StartedService = daemon.NewStartedService(daemon.ServiceOptions{ + Context: ctx, + // Platform: platformWrapper, + Handler: (*platformHandler)(server), + Debug: sDebug, + LogMaxLines: sLogMaxLines, + OOMKillerEnabled: sOOMKillerEnabled, + OOMKillerDisabled: sOOMKillerDisabled, + OOMMemoryLimit: uint64(sOOMMemoryLimit), + // WorkingDirectory: sWorkingPath, + // TempDirectory: sTempPath, + // UserID: sUserID, + // GroupID: sGroupID, + // SystemProxyEnabled: false, + }) + return server, nil } -func (s *CommandServer) SetService(newService *BoxService) { - if newService != nil { - service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate) - newService.instance.Router().ClashServer().(*clashapi.Server).SetModeUpdateHook(s.modeUpdate) +func unaryAuthInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + if sCommandServerSecret == "" { + return handler(ctx, req) } - s.service = newService - s.notifyURLTestUpdate() -} - -func (s *CommandServer) ResetLog() { - s.savedLines.Init() - select { - case s.logReset <- struct{}{}: - default: + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return nil, status.Error(codes.Unauthenticated, "missing metadata") + } + values := md.Get("x-command-secret") + if len(values) == 0 { + return nil, status.Error(codes.Unauthenticated, "missing authentication secret") + } + if values[0] != sCommandServerSecret { + return nil, status.Error(codes.Unauthenticated, "invalid authentication secret") } + return handler(ctx, req) } -func (s *CommandServer) notifyURLTestUpdate() { - select { - case s.urlTestUpdate <- struct{}{}: - default: +func streamAuthInterceptor(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + if sCommandServerSecret == "" { + return handler(srv, ss) } + md, ok := metadata.FromIncomingContext(ss.Context()) + if !ok { + return status.Error(codes.Unauthenticated, "missing metadata") + } + values := md.Get("x-command-secret") + if len(values) == 0 { + return status.Error(codes.Unauthenticated, "missing authentication secret") + } + if values[0] != sCommandServerSecret { + return status.Error(codes.Unauthenticated, "invalid authentication secret") + } + return handler(srv, ss) } func (s *CommandServer) Start() error { - if !sTVOS { - return s.listenUNIX() + var ( + listener net.Listener + err error + ) + if sCommandServerListenPort == 0 { + sockPath := filepath.Join(sBasePath, "command.sock") + os.Remove(sockPath) + for range 30 { + listener, err = net.ListenUnix("unix", &net.UnixAddr{ + Name: sockPath, + Net: "unix", + }) + if err == nil { + break + } + if !errors.Is(err, syscall.EROFS) { + break + } + time.Sleep(time.Second) + } + if err != nil { + return E.Cause(err, "listen command server") + } + if sUserID != os.Getuid() { + err = os.Chown(sockPath, sUserID, sGroupID) + if err != nil { + listener.Close() + os.Remove(sockPath) + return E.Cause(err, "chown") + } + } } else { - return s.listenTCP() + listener, err = net.Listen("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(int(sCommandServerListenPort)))) + if err != nil { + return E.Cause(err, "listen command server") + } } + s.listener = listener + serverOptions := []grpc.ServerOption{ + grpc.UnaryInterceptor(unaryAuthInterceptor), + grpc.StreamInterceptor(streamAuthInterceptor), + } + s.grpcServer = grpc.NewServer(serverOptions...) + daemon.RegisterStartedServiceServer(s.grpcServer, s.StartedService) + go s.grpcServer.Serve(listener) + return nil } -func (s *CommandServer) listenUNIX() error { - sockPath := filepath.Join(sBasePath, "command.sock") - os.Remove(sockPath) - listener, err := net.ListenUnix("unix", &net.UnixAddr{ - Name: sockPath, - Net: "unix", - }) - if err != nil { - return E.Cause(err, "listen ", sockPath) +func (s *CommandServer) Close() { + if s.grpcServer != nil { + s.grpcServer.Stop() } - err = os.Chown(sockPath, sUserID, sGroupID) + common.Close(s.listener) + s.StartedService.Close() +} + +type OverrideOptions struct { + AutoRedirect bool + IncludePackage StringIterator + ExcludePackage StringIterator +} + +func (s *CommandServer) StartOrReloadService(configContent string, options *OverrideOptions) error { + saveConfigSnapshot(configContent) + err := s.StartedService.StartOrReloadService(configContent, &daemon.OverrideOptions{ + AutoRedirect: options.AutoRedirect, + IncludePackage: iteratorToArray(options.IncludePackage), + ExcludePackage: iteratorToArray(options.ExcludePackage), + }) if err != nil { - listener.Close() - os.Remove(sockPath) - return E.Cause(err, "chown") + return E.Cause(err, "start or reload service") } - s.listener = listener - go s.loopConnection(listener) return nil } -func (s *CommandServer) listenTCP() error { - listener, err := net.Listen("tcp", "127.0.0.1:8964") - if err != nil { - return E.Cause(err, "listen") +func (s *CommandServer) CloseService() error { + return s.StartedService.CloseService() +} + +func (s *CommandServer) WriteMessage(level int32, message string) { + s.StartedService.WriteMessage(log.Level(level), message) +} + +func (s *CommandServer) SetError(message string) { + s.StartedService.SetError(E.New(message)) +} + +func (s *CommandServer) NeedWIFIState() bool { + instance := s.StartedService.Instance() + if instance == nil || instance.Box() == nil { + return false } - s.listener = listener - go s.loopConnection(listener) - return nil + return instance.Box().Network().NeedWIFIState() } -func (s *CommandServer) Close() error { - return common.Close( - s.listener, - s.observer, - ) +func (s *CommandServer) NeedFindProcess() bool { + instance := s.StartedService.Instance() + if instance == nil || instance.Box() == nil { + return false + } + return instance.Box().Router().NeedFindProcess() } -func (s *CommandServer) loopConnection(listener net.Listener) { - for { - conn, err := listener.Accept() - if err != nil { - return +func (s *CommandServer) Pause() { + instance := s.StartedService.Instance() + if instance == nil || instance.PauseManager() == nil { + return + } + instance.PauseManager().DevicePause() + if C.IsIos { + if s.endPauseTimer == nil { + s.endPauseTimer = time.AfterFunc(time.Minute, instance.PauseManager().DeviceWake) + } else { + s.endPauseTimer.Reset(time.Minute) } - go func() { - hErr := s.handleConnection(conn) - if hErr != nil && !E.IsClosed(err) { - if debug.Enabled { - log.Warn("log-server: process connection: ", hErr) - } - } - }() } } -func (s *CommandServer) handleConnection(conn net.Conn) error { - defer conn.Close() - var command uint8 - err := binary.Read(conn, binary.BigEndian, &command) +func (s *CommandServer) Wake() { + instance := s.StartedService.Instance() + if instance == nil || instance.PauseManager() == nil { + return + } + if !C.IsIos { + instance.PauseManager().DeviceWake() + } +} + +func (s *CommandServer) ResetNetwork() { + instance := s.StartedService.Instance() + if instance == nil || instance.Box() == nil { + return + } + instance.Box().Network().ResetNetwork() +} + +func (s *CommandServer) UpdateWIFIState() { + instance := s.StartedService.Instance() + if instance == nil || instance.Box() == nil { + return + } + instance.Box().Network().UpdateWIFIState() +} + +type platformHandler CommandServer + +func (h *platformHandler) ServiceStop() error { + return (*CommandServer)(h).handler.ServiceStop() +} + +func (h *platformHandler) ServiceReload() error { + return (*CommandServer)(h).handler.ServiceReload() +} + +func (h *platformHandler) SystemProxyStatus() (*daemon.SystemProxyStatus, error) { + status, err := (*CommandServer)(h).handler.GetSystemProxyStatus() if err != nil { - return E.Cause(err, "read command") - } - switch int32(command) { - case CommandLog: - return s.handleLogConn(conn) - case CommandStatus: - return s.handleStatusConn(conn) - case CommandServiceReload: - return s.handleServiceReload(conn) - case CommandServiceClose: - return s.handleServiceClose(conn) - case CommandCloseConnections: - return s.handleCloseConnections(conn) - case CommandGroup: - return s.handleGroupConn(conn) - case CommandSelectOutbound: - return s.handleSelectOutbound(conn) - case CommandURLTest: - return s.handleURLTest(conn) - case CommandGroupExpand: - return s.handleSetGroupExpand(conn) - case CommandClashMode: - return s.handleModeConn(conn) - case CommandSetClashMode: - return s.handleSetClashMode(conn) - case CommandGetSystemProxyStatus: - return s.handleGetSystemProxyStatus(conn) - case CommandSetSystemProxyEnabled: - return s.handleSetSystemProxyEnabled(conn) - default: - return E.New("unknown command: ", command) + return nil, E.Cause(err, "get system proxy status") } + return &daemon.SystemProxyStatus{ + Enabled: status.Enabled, + Available: status.Available, + }, nil +} + +func (h *platformHandler) SetSystemProxyEnabled(enabled bool) error { + return (*CommandServer)(h).handler.SetSystemProxyEnabled(enabled) +} + +func (h *platformHandler) TriggerNativeCrash() error { + return (*CommandServer)(h).handler.TriggerNativeCrash() +} + +func (h *platformHandler) WriteDebugMessage(message string) { + (*CommandServer)(h).handler.WriteDebugMessage(message) +} + +func (h *platformHandler) ConnectSSHAgent() (int32, error) { + return (*CommandServer)(h).handler.ConnectSSHAgent() } diff --git a/experimental/libbox/command_shared.go b/experimental/libbox/command_shared.go deleted file mode 100644 index ecad78dd8a..0000000000 --- a/experimental/libbox/command_shared.go +++ /dev/null @@ -1,39 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "io" - - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/rw" -) - -func readError(reader io.Reader) error { - var hasError bool - err := binary.Read(reader, binary.BigEndian, &hasError) - if err != nil { - return err - } - if hasError { - errorMessage, err := rw.ReadVString(reader) - if err != nil { - return err - } - return E.New(errorMessage) - } - return nil -} - -func writeError(writer io.Writer, wErr error) error { - err := binary.Write(writer, binary.BigEndian, wErr != nil) - if err != nil { - return err - } - if wErr != nil { - err = rw.WriteVString(writer, wErr.Error()) - if err != nil { - return err - } - } - return nil -} diff --git a/experimental/libbox/command_status.go b/experimental/libbox/command_status.go deleted file mode 100644 index 7f1eca8c03..0000000000 --- a/experimental/libbox/command_status.go +++ /dev/null @@ -1,78 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "net" - "runtime" - "time" - - "github.com/sagernet/sing-box/common/conntrack" - "github.com/sagernet/sing-box/experimental/clashapi" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/memory" -) - -type StatusMessage struct { - Memory int64 - Goroutines int32 - ConnectionsIn int32 - ConnectionsOut int32 - TrafficAvailable bool - Uplink int64 - Downlink int64 - UplinkTotal int64 - DownlinkTotal int64 -} - -func (s *CommandServer) readStatus() StatusMessage { - var message StatusMessage - message.Memory = int64(memory.Inuse()) - message.Goroutines = int32(runtime.NumGoroutine()) - message.ConnectionsOut = int32(conntrack.Count()) - - if s.service != nil { - if clashServer := s.service.instance.Router().ClashServer(); clashServer != nil { - message.TrafficAvailable = true - trafficManager := clashServer.(*clashapi.Server).TrafficManager() - message.Uplink, message.Downlink = trafficManager.Now() - message.UplinkTotal, message.DownlinkTotal = trafficManager.Total() - message.ConnectionsIn = int32(trafficManager.Connections()) - } - } - - return message -} - -func (s *CommandServer) handleStatusConn(conn net.Conn) error { - var interval int64 - err := binary.Read(conn, binary.BigEndian, &interval) - if err != nil { - return E.Cause(err, "read interval") - } - ticker := time.NewTicker(time.Duration(interval)) - defer ticker.Stop() - ctx := connKeepAlive(conn) - for { - err = binary.Write(conn, binary.BigEndian, s.readStatus()) - if err != nil { - return err - } - select { - case <-ctx.Done(): - return ctx.Err() - case <-ticker.C: - } - } -} - -func (c *CommandClient) handleStatusConn(conn net.Conn) { - for { - var message StatusMessage - err := binary.Read(conn, binary.BigEndian, &message) - if err != nil { - c.handler.Disconnected(err.Error()) - return - } - c.handler.WriteStatus(&message) - } -} diff --git a/experimental/libbox/command_system_proxy.go b/experimental/libbox/command_system_proxy.go deleted file mode 100644 index 8a534ae8cb..0000000000 --- a/experimental/libbox/command_system_proxy.go +++ /dev/null @@ -1,80 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "net" -) - -type SystemProxyStatus struct { - Available bool - Enabled bool -} - -func (c *CommandClient) GetSystemProxyStatus() (*SystemProxyStatus, error) { - conn, err := c.directConnectWithRetry() - if err != nil { - return nil, err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandGetSystemProxyStatus)) - if err != nil { - return nil, err - } - var status SystemProxyStatus - err = binary.Read(conn, binary.BigEndian, &status.Available) - if err != nil { - return nil, err - } - if status.Available { - err = binary.Read(conn, binary.BigEndian, &status.Enabled) - if err != nil { - return nil, err - } - } - return &status, nil -} - -func (s *CommandServer) handleGetSystemProxyStatus(conn net.Conn) error { - status := s.handler.GetSystemProxyStatus() - err := binary.Write(conn, binary.BigEndian, status.Available) - if err != nil { - return err - } - if status.Available { - err = binary.Write(conn, binary.BigEndian, status.Enabled) - if err != nil { - return err - } - } - return nil -} - -func (c *CommandClient) SetSystemProxyEnabled(isEnabled bool) error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandSetSystemProxyEnabled)) - if err != nil { - return err - } - err = binary.Write(conn, binary.BigEndian, isEnabled) - if err != nil { - return err - } - return readError(conn) -} - -func (s *CommandServer) handleSetSystemProxyEnabled(conn net.Conn) error { - var isEnabled bool - err := binary.Read(conn, binary.BigEndian, &isEnabled) - if err != nil { - return err - } - err = s.handler.SetSystemProxyEnabled(isEnabled) - if err != nil { - return writeError(conn, err) - } - return writeError(conn, nil) -} diff --git a/experimental/libbox/command_types.go b/experimental/libbox/command_types.go new file mode 100644 index 0000000000..5be81a2283 --- /dev/null +++ b/experimental/libbox/command_types.go @@ -0,0 +1,453 @@ +package libbox + +import ( + "context" + "slices" + "strings" + "sync" + "time" + + "github.com/sagernet/sing-box/daemon" + M "github.com/sagernet/sing/common/metadata" +) + +type streamSession struct { + ctx context.Context + cancel context.CancelFunc + closeOnce sync.Once + closeDone chan struct{} +} + +func (s *streamSession) Close() error { + s.closeOnce.Do(func() { + s.cancel() + }) + <-s.closeDone + return nil +} + +type StatusMessage struct { + Memory int64 + Goroutines int32 + ConnectionsIn int32 + ConnectionsOut int32 + TrafficAvailable bool + Uplink int64 + Downlink int64 + UplinkTotal int64 + DownlinkTotal int64 +} + +type SystemProxyStatus struct { + Available bool + Enabled bool +} + +type OutboundGroup struct { + Tag string + Type string + Selectable bool + Selected string + IsExpand bool + itemList []*OutboundGroupItem +} + +func (g *OutboundGroup) GetItems() OutboundGroupItemIterator { + return newIterator(g.itemList) +} + +type OutboundGroupIterator interface { + Next() *OutboundGroup + HasNext() bool +} + +type OutboundGroupItem struct { + Tag string + Type string + URLTestTime int64 + URLTestDelay int32 +} + +type OutboundGroupItemIterator interface { + Next() *OutboundGroupItem + HasNext() bool +} + +const ( + ConnectionStateAll = iota + ConnectionStateActive + ConnectionStateClosed +) + +const ( + ConnectionEventNew = iota + ConnectionEventUpdate + ConnectionEventClosed +) + +const ( + closedConnectionMaxAge = int64((5 * time.Minute) / time.Millisecond) +) + +type ConnectionEvent struct { + Type int32 + ID string + Connection *Connection + UplinkDelta int64 + DownlinkDelta int64 + ClosedAt int64 +} + +type ConnectionEvents struct { + Reset bool + events []*ConnectionEvent +} + +func (c *ConnectionEvents) Iterator() ConnectionEventIterator { + return newIterator(c.events) +} + +type ConnectionEventIterator interface { + Next() *ConnectionEvent + HasNext() bool +} + +type Connections struct { + connectionMap map[string]*Connection + input []Connection + filtered []Connection + filterState int32 + filterApplied bool +} + +func NewConnections() *Connections { + return &Connections{ + connectionMap: make(map[string]*Connection), + } +} + +func (c *Connections) ApplyEvents(events *ConnectionEvents) { + if events == nil { + return + } + if events.Reset { + c.connectionMap = make(map[string]*Connection) + } + + for _, event := range events.events { + switch event.Type { + case ConnectionEventNew: + if event.Connection != nil { + conn := *event.Connection + c.connectionMap[event.ID] = &conn + } + case ConnectionEventUpdate: + if conn, ok := c.connectionMap[event.ID]; ok { + conn.Uplink = event.UplinkDelta + conn.Downlink = event.DownlinkDelta + conn.UplinkTotal += event.UplinkDelta + conn.DownlinkTotal += event.DownlinkDelta + } + case ConnectionEventClosed: + if event.Connection != nil { + conn := *event.Connection + conn.ClosedAt = event.ClosedAt + conn.Uplink = 0 + conn.Downlink = 0 + c.connectionMap[event.ID] = &conn + continue + } + if conn, ok := c.connectionMap[event.ID]; ok { + conn.ClosedAt = event.ClosedAt + conn.Uplink = 0 + conn.Downlink = 0 + } + } + } + + c.evictClosedConnections(time.Now().UnixMilli()) + c.input = c.input[:0] + for _, conn := range c.connectionMap { + c.input = append(c.input, *conn) + } + if c.filterApplied { + c.FilterState(c.filterState) + } else { + c.filtered = c.filtered[:0] + c.filtered = append(c.filtered, c.input...) + } +} + +func (c *Connections) evictClosedConnections(nowMilliseconds int64) { + for id, conn := range c.connectionMap { + if conn.ClosedAt == 0 { + continue + } + if nowMilliseconds-conn.ClosedAt > closedConnectionMaxAge { + delete(c.connectionMap, id) + } + } +} + +func (c *Connections) FilterState(state int32) { + c.filterApplied = true + c.filterState = state + c.filtered = c.filtered[:0] + switch state { + case ConnectionStateAll: + c.filtered = append(c.filtered, c.input...) + case ConnectionStateActive: + for _, connection := range c.input { + if connection.ClosedAt == 0 { + c.filtered = append(c.filtered, connection) + } + } + case ConnectionStateClosed: + for _, connection := range c.input { + if connection.ClosedAt != 0 { + c.filtered = append(c.filtered, connection) + } + } + } +} + +func (c *Connections) SortByDate() { + slices.SortStableFunc(c.filtered, func(x, y Connection) int { + if x.CreatedAt < y.CreatedAt { + return 1 + } else if x.CreatedAt > y.CreatedAt { + return -1 + } else { + return strings.Compare(y.ID, x.ID) + } + }) +} + +func (c *Connections) SortByTraffic() { + slices.SortStableFunc(c.filtered, func(x, y Connection) int { + xTraffic := x.Uplink + x.Downlink + yTraffic := y.Uplink + y.Downlink + if xTraffic < yTraffic { + return 1 + } else if xTraffic > yTraffic { + return -1 + } else { + return strings.Compare(y.ID, x.ID) + } + }) +} + +func (c *Connections) SortByTrafficTotal() { + slices.SortStableFunc(c.filtered, func(x, y Connection) int { + xTraffic := x.UplinkTotal + x.DownlinkTotal + yTraffic := y.UplinkTotal + y.DownlinkTotal + if xTraffic < yTraffic { + return 1 + } else if xTraffic > yTraffic { + return -1 + } else { + return strings.Compare(y.ID, x.ID) + } + }) +} + +func (c *Connections) Iterator() ConnectionIterator { + return newPtrIterator(c.filtered) +} + +type ProcessInfo struct { + ProcessID int64 + UserID int32 + UserName string + ProcessPath string + packageNames []string +} + +func (p *ProcessInfo) PackageNames() StringIterator { + return newIterator(p.packageNames) +} + +type Connection struct { + ID string + Inbound string + InboundType string + IPVersion int32 + Network string + Source string + Destination string + Domain string + Protocol string + User string + FromOutbound string + CreatedAt int64 + ClosedAt int64 + Uplink int64 + Downlink int64 + UplinkTotal int64 + DownlinkTotal int64 + Rule string + Outbound string + OutboundType string + chainList []string + ProcessInfo *ProcessInfo +} + +func (c *Connection) Chain() StringIterator { + return newIterator(c.chainList) +} + +func (c *Connection) DisplayDestination() string { + destination := M.ParseSocksaddr(c.Destination) + if destination.IsIP() && c.Domain != "" { + destination = M.Socksaddr{ + Fqdn: c.Domain, + Port: destination.Port, + } + return destination.String() + } + return c.Destination +} + +type ConnectionIterator interface { + Next() *Connection + HasNext() bool +} + +func statusMessageFromGRPC(status *daemon.Status) *StatusMessage { + if status == nil { + return nil + } + return &StatusMessage{ + Memory: int64(status.Memory), + Goroutines: status.Goroutines, + ConnectionsIn: status.ConnectionsIn, + ConnectionsOut: status.ConnectionsOut, + TrafficAvailable: status.TrafficAvailable, + Uplink: status.Uplink, + Downlink: status.Downlink, + UplinkTotal: status.UplinkTotal, + DownlinkTotal: status.DownlinkTotal, + } +} + +func outboundGroupIteratorFromGRPC(groups *daemon.Groups) OutboundGroupIterator { + if groups == nil || len(groups.Group) == 0 { + return newIterator([]*OutboundGroup{}) + } + var libboxGroups []*OutboundGroup + for _, g := range groups.Group { + libboxGroup := &OutboundGroup{ + Tag: g.Tag, + Type: g.Type, + Selectable: g.Selectable, + Selected: g.Selected, + IsExpand: g.IsExpand, + } + for _, item := range g.Items { + libboxGroup.itemList = append(libboxGroup.itemList, &OutboundGroupItem{ + Tag: item.Tag, + Type: item.Type, + URLTestTime: item.UrlTestTime, + URLTestDelay: item.UrlTestDelay, + }) + } + libboxGroups = append(libboxGroups, libboxGroup) + } + return newIterator(libboxGroups) +} + +func outboundGroupItemListFromGRPC(list *daemon.OutboundList) OutboundGroupItemIterator { + if list == nil || len(list.Outbounds) == 0 { + return newIterator([]*OutboundGroupItem{}) + } + var items []*OutboundGroupItem + for _, ob := range list.Outbounds { + items = append(items, &OutboundGroupItem{ + Tag: ob.Tag, + Type: ob.Type, + URLTestTime: ob.UrlTestTime, + URLTestDelay: ob.UrlTestDelay, + }) + } + return newIterator(items) +} + +func connectionFromGRPC(conn *daemon.Connection) Connection { + var processInfo *ProcessInfo + if conn.ProcessInfo != nil { + processInfo = &ProcessInfo{ + ProcessID: int64(conn.ProcessInfo.ProcessId), + UserID: conn.ProcessInfo.UserId, + UserName: conn.ProcessInfo.UserName, + ProcessPath: conn.ProcessInfo.ProcessPath, + packageNames: conn.ProcessInfo.PackageNames, + } + } + return Connection{ + ID: conn.Id, + Inbound: conn.Inbound, + InboundType: conn.InboundType, + IPVersion: conn.IpVersion, + Network: conn.Network, + Source: conn.Source, + Destination: conn.Destination, + Domain: conn.Domain, + Protocol: conn.Protocol, + User: conn.User, + FromOutbound: conn.FromOutbound, + CreatedAt: conn.CreatedAt, + ClosedAt: conn.ClosedAt, + Uplink: conn.Uplink, + Downlink: conn.Downlink, + UplinkTotal: conn.UplinkTotal, + DownlinkTotal: conn.DownlinkTotal, + Rule: conn.Rule, + Outbound: conn.Outbound, + OutboundType: conn.OutboundType, + chainList: conn.ChainList, + ProcessInfo: processInfo, + } +} + +func connectionEventFromGRPC(event *daemon.ConnectionEvent) *ConnectionEvent { + if event == nil { + return nil + } + libboxEvent := &ConnectionEvent{ + Type: int32(event.Type), + ID: event.Id, + UplinkDelta: event.UplinkDelta, + DownlinkDelta: event.DownlinkDelta, + ClosedAt: event.ClosedAt, + } + if event.Connection != nil { + conn := connectionFromGRPC(event.Connection) + libboxEvent.Connection = &conn + } + return libboxEvent +} + +func connectionEventsFromGRPC(events *daemon.ConnectionEvents) *ConnectionEvents { + if events == nil { + return nil + } + libboxEvents := &ConnectionEvents{ + Reset: events.Reset_, + } + for _, event := range events.Events { + if libboxEvent := connectionEventFromGRPC(event); libboxEvent != nil { + libboxEvents.events = append(libboxEvents.events, libboxEvent) + } + } + return libboxEvents +} + +func systemProxyStatusFromGRPC(status *daemon.SystemProxyStatus) *SystemProxyStatus { + if status == nil { + return nil + } + return &SystemProxyStatus{ + Available: status.Available, + Enabled: status.Enabled, + } +} diff --git a/experimental/libbox/command_types_nq.go b/experimental/libbox/command_types_nq.go new file mode 100644 index 0000000000..17d53ff64e --- /dev/null +++ b/experimental/libbox/command_types_nq.go @@ -0,0 +1,55 @@ +package libbox + +import "github.com/sagernet/sing-box/daemon" + +type NetworkQualityProgress struct { + Phase int32 + DownloadCapacity int64 + UploadCapacity int64 + DownloadRPM int32 + UploadRPM int32 + IdleLatencyMs int32 + ElapsedMs int64 + DownloadCapacityAccuracy int32 + UploadCapacityAccuracy int32 + DownloadRPMAccuracy int32 + UploadRPMAccuracy int32 +} + +type NetworkQualityResult struct { + DownloadCapacity int64 + UploadCapacity int64 + DownloadRPM int32 + UploadRPM int32 + IdleLatencyMs int32 + DownloadCapacityAccuracy int32 + UploadCapacityAccuracy int32 + DownloadRPMAccuracy int32 + UploadRPMAccuracy int32 +} + +type NetworkQualityTestHandler interface { + OnProgress(progress *NetworkQualityProgress) + OnResult(result *NetworkQualityResult) + OnError(message string) +} + +type NetworkQualityTestSession struct { + streamSession +} + +func networkQualityProgressFromGRPC(event *daemon.NetworkQualityTestProgress) *NetworkQualityProgress { + return &NetworkQualityProgress{ + Phase: event.Phase, + DownloadCapacity: event.DownloadCapacity, + UploadCapacity: event.UploadCapacity, + DownloadRPM: event.DownloadRPM, + UploadRPM: event.UploadRPM, + IdleLatencyMs: event.IdleLatencyMs, + ElapsedMs: event.ElapsedMs, + DownloadCapacityAccuracy: event.DownloadCapacityAccuracy, + UploadCapacityAccuracy: event.UploadCapacityAccuracy, + DownloadRPMAccuracy: event.DownloadRPMAccuracy, + UploadRPMAccuracy: event.UploadRPMAccuracy, + } +} diff --git a/experimental/libbox/command_types_stun.go b/experimental/libbox/command_types_stun.go new file mode 100644 index 0000000000..f3ba169053 --- /dev/null +++ b/experimental/libbox/command_types_stun.go @@ -0,0 +1,39 @@ +package libbox + +import "github.com/sagernet/sing-box/daemon" + +type STUNTestProgress struct { + Phase int32 + ExternalAddr string + LatencyMs int32 + NATMapping int32 + NATFiltering int32 +} + +type STUNTestResult struct { + ExternalAddr string + LatencyMs int32 + NATMapping int32 + NATFiltering int32 + NATTypeSupported bool +} + +type STUNTestHandler interface { + OnProgress(progress *STUNTestProgress) + OnResult(result *STUNTestResult) + OnError(message string) +} + +type STUNTestSession struct { + streamSession +} + +func stunTestProgressFromGRPC(event *daemon.STUNTestProgress) *STUNTestProgress { + return &STUNTestProgress{ + Phase: event.Phase, + ExternalAddr: event.ExternalAddr, + LatencyMs: event.LatencyMs, + NATMapping: event.NatMapping, + NATFiltering: event.NatFiltering, + } +} diff --git a/experimental/libbox/command_types_tailscale.go b/experimental/libbox/command_types_tailscale.go new file mode 100644 index 0000000000..ffb9b242f2 --- /dev/null +++ b/experimental/libbox/command_types_tailscale.go @@ -0,0 +1,154 @@ +package libbox + +import "github.com/sagernet/sing-box/daemon" + +type TailscaleStatusUpdate struct { + endpoints []*TailscaleEndpointStatus +} + +func (u *TailscaleStatusUpdate) Endpoints() TailscaleEndpointStatusIterator { + return newIterator(u.endpoints) +} + +type TailscaleEndpointStatusIterator interface { + Next() *TailscaleEndpointStatus + HasNext() bool +} + +type TailscaleEndpointStatus struct { + EndpointTag string + BackendState string + AuthURL string + NetworkName string + MagicDNSSuffix string + Self *TailscalePeer + ExitNode *TailscalePeer + userGroups []*TailscaleUserGroup +} + +func (s *TailscaleEndpointStatus) UserGroups() TailscaleUserGroupIterator { + return newIterator(s.userGroups) +} + +type TailscaleUserGroupIterator interface { + Next() *TailscaleUserGroup + HasNext() bool +} + +type TailscaleUserGroup struct { + UserID int64 + LoginName string + DisplayName string + ProfilePicURL string + peers []*TailscalePeer +} + +func (g *TailscaleUserGroup) Peers() TailscalePeerIterator { + return newIterator(g.peers) +} + +type TailscalePeerIterator interface { + Next() *TailscalePeer + HasNext() bool +} + +type TailscalePeer struct { + StableID string + HostName string + DNSName string + OS string + tailscaleIPs []string + sshHostKeys []string + Online bool + ExitNode bool + ExitNodeOption bool + ShareeNode bool + Expired bool + Active bool + RxBytes int64 + TxBytes int64 + KeyExpiry int64 + LastSeen int64 +} + +func (p *TailscalePeer) TailscaleIPs() StringIterator { + return newIterator(p.tailscaleIPs) +} + +func (p *TailscalePeer) SSHHostKeys() StringIterator { + return newIterator(p.sshHostKeys) +} + +type TailscaleStatusHandler interface { + OnStatusUpdate(status *TailscaleStatusUpdate) + OnError(message string) +} + +type TailscaleStatusSubscription struct { + streamSession +} + +func tailscaleStatusUpdateFromGRPC(update *daemon.TailscaleStatusUpdate) *TailscaleStatusUpdate { + endpoints := make([]*TailscaleEndpointStatus, len(update.Endpoints)) + for i, endpoint := range update.Endpoints { + endpoints[i] = tailscaleEndpointStatusFromGRPC(endpoint) + } + return &TailscaleStatusUpdate{endpoints: endpoints} +} + +func tailscaleEndpointStatusFromGRPC(status *daemon.TailscaleEndpointStatus) *TailscaleEndpointStatus { + userGroups := make([]*TailscaleUserGroup, len(status.UserGroups)) + for i, group := range status.UserGroups { + userGroups[i] = tailscaleUserGroupFromGRPC(group) + } + result := &TailscaleEndpointStatus{ + EndpointTag: status.EndpointTag, + BackendState: status.BackendState, + AuthURL: status.AuthURL, + NetworkName: status.NetworkName, + MagicDNSSuffix: status.MagicDNSSuffix, + userGroups: userGroups, + } + if status.Self != nil { + result.Self = tailscalePeerFromGRPC(status.Self) + } + if status.ExitNode != nil { + result.ExitNode = tailscalePeerFromGRPC(status.ExitNode) + } + return result +} + +func tailscaleUserGroupFromGRPC(group *daemon.TailscaleUserGroup) *TailscaleUserGroup { + peers := make([]*TailscalePeer, len(group.Peers)) + for i, peer := range group.Peers { + peers[i] = tailscalePeerFromGRPC(peer) + } + return &TailscaleUserGroup{ + UserID: group.UserID, + LoginName: group.LoginName, + DisplayName: group.DisplayName, + ProfilePicURL: group.ProfilePicURL, + peers: peers, + } +} + +func tailscalePeerFromGRPC(peer *daemon.TailscalePeer) *TailscalePeer { + return &TailscalePeer{ + StableID: peer.StableID, + HostName: peer.HostName, + DNSName: peer.DnsName, + OS: peer.Os, + tailscaleIPs: peer.TailscaleIPs, + sshHostKeys: peer.SshHostKeys, + Online: peer.Online, + ExitNode: peer.ExitNode, + ExitNodeOption: peer.ExitNodeOption, + ShareeNode: peer.ShareeNode, + Expired: peer.Expired, + Active: peer.Active, + RxBytes: peer.RxBytes, + TxBytes: peer.TxBytes, + KeyExpiry: peer.KeyExpiry, + LastSeen: peer.LastSeen, + } +} diff --git a/experimental/libbox/command_types_tailscale_ping.go b/experimental/libbox/command_types_tailscale_ping.go new file mode 100644 index 0000000000..5b8ebc2f02 --- /dev/null +++ b/experimental/libbox/command_types_tailscale_ping.go @@ -0,0 +1,32 @@ +package libbox + +import "github.com/sagernet/sing-box/daemon" + +type TailscalePingResult struct { + LatencyMs float64 + IsDirect bool + Endpoint string + DERPRegionID int32 + DERPRegionCode string + Error string +} + +type TailscalePingHandler interface { + OnPingResult(result *TailscalePingResult) + OnError(message string) +} + +type TailscalePingSession struct { + streamSession +} + +func tailscalePingResultFromGRPC(response *daemon.TailscalePingResponse) *TailscalePingResult { + return &TailscalePingResult{ + LatencyMs: response.LatencyMs, + IsDirect: response.IsDirect, + Endpoint: response.Endpoint, + DERPRegionID: response.DerpRegionID, + DERPRegionCode: response.DerpRegionCode, + Error: response.Error, + } +} diff --git a/experimental/libbox/command_types_tailscale_ssh.go b/experimental/libbox/command_types_tailscale_ssh.go new file mode 100644 index 0000000000..fd5754e01c --- /dev/null +++ b/experimental/libbox/command_types_tailscale_ssh.go @@ -0,0 +1,82 @@ +package libbox + +import ( + "bytes" + "context" + "os" + "sync" + + "github.com/sagernet/sing-box/daemon" +) + +type TailscaleSSHOptions struct { + EndpointTag string + PeerAddress string + Username string + TerminalType string + Columns int32 + Rows int32 + WidthPixels int32 + HeightPixels int32 + HostKeys StringIterator + ForwardAgent bool +} + +type TailscaleSSHHandler interface { + OnReady() + OnOutput(data []byte) + OnAuthBanner(message string) + OnExit(exitCode int32, signal string, errorMessage string) + OnError(message string) +} + +type tailscaleSSHResize struct { + columns int32 + rows int32 + widthPixels int32 + heightPixels int32 +} + +type TailscaleSSHSession struct { + stream daemon.StartedService_StartTailscaleSSHSessionClient + inputCh chan []byte + resizeCh chan tailscaleSSHResize + ctx context.Context + cancel context.CancelFunc + wg sync.WaitGroup + closeOnce sync.Once + closeDone chan struct{} +} + +func (s *TailscaleSSHSession) SendInput(data []byte) error { + select { + case <-s.ctx.Done(): + return os.ErrClosed + case s.inputCh <- bytes.Clone(data): + return nil + } +} + +func (s *TailscaleSSHSession) SendResize(columns int32, rows int32, widthPixels int32, heightPixels int32) error { + resize := tailscaleSSHResize{ + columns: columns, + rows: rows, + widthPixels: widthPixels, + heightPixels: heightPixels, + } + select { + case s.resizeCh <- resize: + return nil + case <-s.ctx.Done(): + return os.ErrClosed + } +} + +func (s *TailscaleSSHSession) Close() error { + s.closeOnce.Do(func() { + s.cancel() + _ = s.stream.CloseSend() + }) + <-s.closeDone + return nil +} diff --git a/experimental/libbox/command_urltest.go b/experimental/libbox/command_urltest.go deleted file mode 100644 index 19ddf3da3c..0000000000 --- a/experimental/libbox/command_urltest.go +++ /dev/null @@ -1,89 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "net" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/urltest" - "github.com/sagernet/sing-box/outbound" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/batch" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/rw" - "github.com/sagernet/sing/service" -) - -func (c *CommandClient) URLTest(groupTag string) error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandURLTest)) - if err != nil { - return err - } - err = rw.WriteVString(conn, groupTag) - if err != nil { - return err - } - return readError(conn) -} - -func (s *CommandServer) handleURLTest(conn net.Conn) error { - groupTag, err := rw.ReadVString(conn) - if err != nil { - return err - } - serviceNow := s.service - if serviceNow == nil { - return nil - } - abstractOutboundGroup, isLoaded := serviceNow.instance.Router().Outbound(groupTag) - if !isLoaded { - return writeError(conn, E.New("outbound group not found: ", groupTag)) - } - outboundGroup, isOutboundGroup := abstractOutboundGroup.(adapter.OutboundGroup) - if !isOutboundGroup { - return writeError(conn, E.New("outbound is not a group: ", groupTag)) - } - urlTest, isURLTest := abstractOutboundGroup.(*outbound.URLTest) - if isURLTest { - go urlTest.CheckOutbounds() - } else { - historyStorage := service.PtrFromContext[urltest.HistoryStorage](serviceNow.ctx) - outbounds := common.Filter(common.Map(outboundGroup.All(), func(it string) adapter.Outbound { - itOutbound, _ := serviceNow.instance.Router().Outbound(it) - return itOutbound - }), func(it adapter.Outbound) bool { - if it == nil { - return false - } - _, isGroup := it.(adapter.OutboundGroup) - if isGroup { - return false - } - return true - }) - b, _ := batch.New(serviceNow.ctx, batch.WithConcurrencyNum[any](10)) - for _, detour := range outbounds { - outboundToTest := detour - outboundTag := outboundToTest.Tag() - b.Go(outboundTag, func() (any, error) { - t, err := urltest.URLTest(serviceNow.ctx, "", outboundToTest) - if err != nil { - historyStorage.DeleteURLTestHistory(outboundTag) - } else { - historyStorage.StoreURLTestHistory(outboundTag, &urltest.History{ - Time: time.Now(), - Delay: t, - }) - } - return nil, nil - }) - } - } - return writeError(conn, nil) -} diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index b7731143cb..e4200aef1f 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -6,20 +6,52 @@ import ( "net/netip" "os" - "github.com/sagernet/sing-box" + box "github.com/sagernet/sing-box" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/process" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/include" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-tun" + "github.com/sagernet/sing-box/service/oomkiller" + tun "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service" + "github.com/sagernet/sing/service/filemanager" ) -func parseConfig(configContent string) (option.Options, error) { - options, err := json.UnmarshalExtended[option.Options]([]byte(configContent)) +var sOOMReporter oomkiller.OOMReporter + +func BaseContext(platformInterface PlatformInterface) context.Context { + return baseContext(platformInterface) +} + +func baseContext(platformInterface PlatformInterface) context.Context { + return baseContextWithParent(context.Background(), platformInterface) +} + +func baseContextWithParent(ctx context.Context, platformInterface PlatformInterface) context.Context { + dnsRegistry := include.DNSTransportRegistry() + if platformInterface != nil { + if localTransport := platformInterface.LocalDNSTransport(); localTransport != nil { + dns.RegisterTransport[option.LocalDNSServerOptions](dnsRegistry, C.DNSTypeLocal, func(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) { + return newPlatformTransport(localTransport, tag, options), nil + }) + } + } + ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID) + if sOOMReporter != nil { + ctx = service.ContextWith[oomkiller.OOMReporter](ctx, sOOMReporter) + } + return box.Context(ctx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry, include.ServiceRegistry(), include.CertificateProviderRegistry()) +} + +func parseConfig(ctx context.Context, configContent string) (option.Options, error) { + options, err := json.UnmarshalExtendedContext[option.Options](ctx, []byte(configContent)) if err != nil { return option.Options{}, E.Cause(err, "decode config") } @@ -27,16 +59,25 @@ func parseConfig(configContent string) (option.Options, error) { } func CheckConfig(configContent string) error { - options, err := parseConfig(configContent) + ctx := baseContext(nil) + options, err := parseConfig(ctx, configContent) if err != nil { return err } - ctx, cancel := context.WithCancel(context.Background()) + return CheckConfigOptions(&options) +} + +func CheckConfigOptions(options *option.Options) error { + if options == nil { + return os.ErrInvalid + } + ctx := baseContext(nil) + ctx, cancel := context.WithCancel(ctx) defer cancel() + ctx = service.ContextWith[adapter.PlatformInterface](ctx, (*platformInterfaceStub)(nil)) instance, err := box.New(box.Options{ - Context: ctx, - Options: options, - PlatformInterface: (*platformInterfaceStub)(nil), + Context: ctx, + Options: *options, }) if err == nil { instance.Close() @@ -46,7 +87,7 @@ func CheckConfig(configContent string) error { type platformInterfaceStub struct{} -func (s *platformInterfaceStub) Initialize(ctx context.Context, router adapter.Router) error { +func (s *platformInterfaceStub) Initialize(networkManager adapter.NetworkManager) error { return nil } @@ -54,11 +95,15 @@ func (s *platformInterfaceStub) UsePlatformAutoDetectInterfaceControl() bool { return true } -func (s *platformInterfaceStub) AutoDetectInterfaceControl() control.Func { +func (s *platformInterfaceStub) AutoDetectInterfaceControl(fd int) error { return nil } -func (s *platformInterfaceStub) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) { +func (s *platformInterfaceStub) UsePlatformInterface() bool { + return false +} + +func (s *platformInterfaceStub) OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) { return nil, os.ErrInvalid } @@ -70,11 +115,11 @@ func (s *platformInterfaceStub) CreateDefaultInterfaceMonitor(logger logger.Logg return (*interfaceMonitorStub)(nil) } -func (s *platformInterfaceStub) UsePlatformInterfaceGetter() bool { - return true +func (s *platformInterfaceStub) UsePlatformNetworkInterfaces() bool { + return false } -func (s *platformInterfaceStub) Interfaces() ([]control.Interface, error) { +func (s *platformInterfaceStub) NetworkInterfaces() ([]adapter.NetworkInterface, error) { return nil, os.ErrInvalid } @@ -82,41 +127,81 @@ func (s *platformInterfaceStub) UnderNetworkExtension() bool { return false } -func (s *platformInterfaceStub) IncludeAllNetworks() bool { +func (s *platformInterfaceStub) NetworkExtensionIncludeAllNetworks() bool { return false } func (s *platformInterfaceStub) ClearDNSCache() { } +func (s *platformInterfaceStub) RequestPermissionForWIFIState() error { + return nil +} + +func (s *platformInterfaceStub) UsePlatformWIFIMonitor() bool { + return false +} + func (s *platformInterfaceStub) ReadWIFIState() adapter.WIFIState { return adapter.WIFIState{} } -func (s *platformInterfaceStub) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*process.Info, error) { +func (s *platformInterfaceStub) SystemCertificates() []string { + return nil +} + +func (s *platformInterfaceStub) UsePlatformConnectionOwnerFinder() bool { + return false +} + +func (s *platformInterfaceStub) FindConnectionOwner(request *adapter.FindConnectionOwnerRequest) (*adapter.ConnectionOwner, error) { return nil, os.ErrInvalid } -type interfaceMonitorStub struct{} +func (s *platformInterfaceStub) UsePlatformNotification() bool { + return false +} -func (s *interfaceMonitorStub) Start() error { - return os.ErrInvalid +func (s *platformInterfaceStub) SendNotification(notification *adapter.Notification) error { + return nil } -func (s *interfaceMonitorStub) Close() error { +func (s *platformInterfaceStub) MyInterfaceAddress() []netip.Addr { + return nil +} + +func (s *platformInterfaceStub) UsePlatformNeighborResolver() bool { + return false +} + +func (s *platformInterfaceStub) StartNeighborMonitor(listener adapter.NeighborUpdateListener) error { return os.ErrInvalid } -func (s *interfaceMonitorStub) DefaultInterfaceName(destination netip.Addr) string { - return "" +func (s *platformInterfaceStub) CloseNeighborMonitor(listener adapter.NeighborUpdateListener) error { + return nil +} + +func (s *platformInterfaceStub) UsePlatformLocalDNSTransport() bool { + return false +} + +func (s *platformInterfaceStub) LocalDNSTransport() dns.TransportConstructorFunc[option.LocalDNSServerOptions] { + return nil +} + +type interfaceMonitorStub struct{} + +func (s *interfaceMonitorStub) Start() error { + return os.ErrInvalid } -func (s *interfaceMonitorStub) DefaultInterfaceIndex(destination netip.Addr) int { - return -1 +func (s *interfaceMonitorStub) Close() error { + return os.ErrInvalid } -func (s *interfaceMonitorStub) DefaultInterface(destination netip.Addr) (string, int) { - return "", -1 +func (s *interfaceMonitorStub) DefaultInterface() *control.Interface { + return nil } func (s *interfaceMonitorStub) OverrideAndroidVPN() bool { @@ -134,17 +219,24 @@ func (s *interfaceMonitorStub) RegisterCallback(callback tun.DefaultInterfaceUpd func (s *interfaceMonitorStub) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) { } -func FormatConfig(configContent string) (string, error) { - options, err := parseConfig(configContent) +func (s *interfaceMonitorStub) RegisterMyInterface(interfaceName string) { +} + +func (s *interfaceMonitorStub) MyInterface() string { + return "" +} + +func FormatConfig(configContent string) (*StringBox, error) { + options, err := parseConfig(baseContext(nil), configContent) if err != nil { - return "", err + return nil, err } var buffer bytes.Buffer encoder := json.NewEncoder(&buffer) encoder.SetIndent("", " ") err = encoder.Encode(options) if err != nil { - return "", err + return nil, err } - return buffer.String(), nil + return wrapString(buffer.String()), nil } diff --git a/experimental/libbox/connection_owner_darwin.go b/experimental/libbox/connection_owner_darwin.go new file mode 100644 index 0000000000..2022010679 --- /dev/null +++ b/experimental/libbox/connection_owner_darwin.go @@ -0,0 +1,57 @@ +package libbox + +import ( + "net/netip" + "os/user" + "syscall" + + "github.com/sagernet/sing-box/common/process" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" +) + +func FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (*ConnectionOwner, error) { + source, err := parseConnectionOwnerAddrPort(sourceAddress, sourcePort) + if err != nil { + return nil, E.Cause(err, "parse source") + } + destination, err := parseConnectionOwnerAddrPort(destinationAddress, destinationPort) + if err != nil { + return nil, E.Cause(err, "parse destination") + } + var network string + switch ipProtocol { + case syscall.IPPROTO_TCP: + network = "tcp" + case syscall.IPPROTO_UDP: + network = "udp" + default: + return nil, E.New("unknown protocol: ", ipProtocol) + } + owner, err := process.FindDarwinConnectionOwner(network, source, destination) + if err != nil { + return nil, err + } + result := &ConnectionOwner{ + UserId: owner.UserId, + ProcessPath: owner.ProcessPath, + } + if owner.UserId != -1 && owner.UserName == "" { + osUser, _ := user.LookupId(F.ToString(owner.UserId)) + if osUser != nil { + result.UserName = osUser.Username + } + } + return result, nil +} + +func parseConnectionOwnerAddrPort(address string, port int32) (netip.AddrPort, error) { + if port < 0 || port > 65535 { + return netip.AddrPort{}, E.New("invalid port: ", port) + } + addr, err := netip.ParseAddr(address) + if err != nil { + return netip.AddrPort{}, err + } + return netip.AddrPortFrom(addr.Unmap(), uint16(port)), nil +} diff --git a/experimental/libbox/debug.go b/experimental/libbox/debug.go new file mode 100644 index 0000000000..75942976f6 --- /dev/null +++ b/experimental/libbox/debug.go @@ -0,0 +1,12 @@ +package libbox + +import ( + "time" + "unsafe" +) + +func TriggerGoPanic() { + time.AfterFunc(200*time.Millisecond, func() { + *(*int)(unsafe.Pointer(uintptr(0))) = 0 + }) +} diff --git a/experimental/libbox/deprecated.go b/experimental/libbox/deprecated.go new file mode 100644 index 0000000000..0c2f8d8aba --- /dev/null +++ b/experimental/libbox/deprecated.go @@ -0,0 +1,33 @@ +package libbox + +import ( + "github.com/sagernet/sing-box/experimental/deprecated" +) + +var _ = deprecated.Note(DeprecatedNote{}) + +type DeprecatedNote struct { + Name string + Description string + DeprecatedVersion string + ScheduledVersion string + EnvName string + MigrationLink string +} + +func (n DeprecatedNote) Impending() bool { + return deprecated.Note(n).Impending() +} + +func (n DeprecatedNote) Message() string { + return deprecated.Note(n).Message() +} + +func (n DeprecatedNote) MessageWithLink() string { + return deprecated.Note(n).MessageWithLink() +} + +type DeprecatedNoteIterator interface { + HasNext() bool + Next() *DeprecatedNote +} diff --git a/experimental/libbox/dns.go b/experimental/libbox/dns.go index e1f8bcc3b6..b7b3b0f67d 100644 --- a/experimental/libbox/dns.go +++ b/experimental/libbox/dns.go @@ -6,7 +6,10 @@ import ( "strings" "syscall" - "github.com/sagernet/sing-dns" + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" @@ -21,106 +24,88 @@ type LocalDNSTransport interface { Exchange(ctx *ExchangeContext, message []byte) error } -func RegisterLocalDNSTransport(transport LocalDNSTransport) { - if transport == nil { - dns.RegisterTransport([]string{"local"}, func(options dns.TransportOptions) (dns.Transport, error) { - return dns.NewLocalTransport(options), nil - }) - } else { - dns.RegisterTransport([]string{"local"}, func(options dns.TransportOptions) (dns.Transport, error) { - return &platformLocalDNSTransport{ - iif: transport, - }, nil - }) - } -} - -var _ dns.Transport = (*platformLocalDNSTransport)(nil) +var _ adapter.DNSTransport = (*platformTransport)(nil) -type platformLocalDNSTransport struct { +type platformTransport struct { + dns.TransportAdapter iif LocalDNSTransport } -func (p *platformLocalDNSTransport) Name() string { - return "local" +func newPlatformTransport(iif LocalDNSTransport, tag string, options option.LocalDNSServerOptions) *platformTransport { + return &platformTransport{ + TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options), + iif: iif, + } } -func (p *platformLocalDNSTransport) Start() error { +func (p *platformTransport) Start(stage adapter.StartStage) error { return nil } -func (p *platformLocalDNSTransport) Reset() { -} - -func (p *platformLocalDNSTransport) Close() error { +func (p *platformTransport) Close() error { return nil } -func (p *platformLocalDNSTransport) Raw() bool { - return p.iif.Raw() +func (p *platformTransport) Reset() { } -func (p *platformLocalDNSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { - messageBytes, err := message.Pack() - if err != nil { - return nil, err - } +func (p *platformTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { response := &ExchangeContext{ context: ctx, } - var responseMessage *mDNS.Msg - return responseMessage, task.Run(ctx, func() error { - err = p.iif.Exchange(response, messageBytes) + if p.iif.Raw() { + messageBytes, err := message.Pack() if err != nil { - return err + return nil, err } - if response.error != nil { - return response.error - } - responseMessage = &response.message - return nil - }) -} - -func (p *platformLocalDNSTransport) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) { - var network string - switch strategy { - case dns.DomainStrategyUseIPv4: - network = "ip4" - case dns.DomainStrategyPreferIPv6: - network = "ip6" - default: - network = "ip" - } - response := &ExchangeContext{ - context: ctx, - } - var responseAddr []netip.Addr - return responseAddr, task.Run(ctx, func() error { - err := p.iif.Lookup(response, network, domain) + var responseMessage *mDNS.Msg + var group task.Group + group.Append0(func(ctx context.Context) error { + err = p.iif.Exchange(response, messageBytes) + if err != nil { + return err + } + if response.error != nil { + return response.error + } + responseMessage = &response.message + return nil + }) + err = group.Run(ctx) if err != nil { - return err - } - if response.error != nil { - return response.error + return nil, err } - switch strategy { - case dns.DomainStrategyUseIPv4: - responseAddr = common.Filter(response.addresses, func(it netip.Addr) bool { - return it.Is4() - }) - case dns.DomainStrategyPreferIPv6: - responseAddr = common.Filter(response.addresses, func(it netip.Addr) bool { - return it.Is6() - }) + return responseMessage, nil + } else { + question := message.Question[0] + var network string + switch question.Qtype { + case mDNS.TypeA: + network = "ip4" + case mDNS.TypeAAAA: + network = "ip6" default: - responseAddr = response.addresses + return nil, E.New("only IP queries are supported by current version of Android") } - /*if len(responseAddr) == 0 { - response.error = dns.RCodeSuccess - }*/ - return nil - }) + var responseAddrs []netip.Addr + var group task.Group + group.Append0(func(ctx context.Context) error { + err := p.iif.Lookup(response, network, question.Name) + if err != nil { + return err + } + if response.error != nil { + return response.error + } + responseAddrs = response.addresses + return nil + }) + err := group.Run(ctx) + if err != nil { + return nil, err + } + return dns.FixedResponse(message.Id, question, responseAddrs, C.DefaultDNSTTL), nil + } } type Func interface { @@ -157,7 +142,7 @@ func (c *ExchangeContext) RawSuccess(result []byte) { } func (c *ExchangeContext) ErrorCode(code int32) { - c.error = dns.RCodeError(code) + c.error = dns.RcodeError(code) } func (c *ExchangeContext) ErrnoCode(code int32) { diff --git a/experimental/libbox/export.go b/experimental/libbox/export.go new file mode 100644 index 0000000000..7aa17f6b55 --- /dev/null +++ b/experimental/libbox/export.go @@ -0,0 +1,44 @@ +package libbox + +import ( + "context" + "math" + "runtime/debug" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/service/oomkiller" +) + +func FromContext(ctx context.Context, platformInterface PlatformInterface) context.Context { + if ctx == nil { + ctx = context.Background() + } + return baseContextWithParent(ctx, platformInterface) +} + +func WrapPlatformInterface(platformInterface PlatformInterface) adapter.PlatformInterface { + if platformInterface == nil { + return nil + } + return &platformInterfaceWrapper{ + iif: platformInterface, + useProcFS: platformInterface.UseProcFS(), + } +} + +func SetMemoryLimit(enabled bool) { + if !enabled { + debug.SetMemoryLimit(math.MaxInt64) + return + } + limit := sOOMMemoryLimit + if limit == 0 && C.IsIos { + limit = oomkiller.DefaultAppleNetworkExtensionMemoryLimit + } + if limit > 0 { + debug.SetMemoryLimit(limit * 3 / 4) + } else { + debug.SetMemoryLimit(math.MaxInt64) + } +} diff --git a/experimental/libbox/fdroid.go b/experimental/libbox/fdroid.go new file mode 100644 index 0000000000..d574ffd863 --- /dev/null +++ b/experimental/libbox/fdroid.go @@ -0,0 +1,493 @@ +package libbox + +import ( + "archive/zip" + "bytes" + "crypto/tls" + "encoding/json" + "io" + "net" + "net/http" + "net/url" + "os" + "path/filepath" + "sort" + "strconv" + "strings" + "sync" + "time" + + E "github.com/sagernet/sing/common/exceptions" +) + +const fdroidUserAgent = "F-Droid 1.21.1" + +type FDroidUpdateInfo struct { + VersionCode int32 + VersionName string + DownloadURL string + FileSize int64 + FileSHA256 string +} + +type FDroidPingResult struct { + URL string + LatencyMs int32 + Error string +} + +type FDroidPingResultIterator interface { + Len() int32 + HasNext() bool + Next() *FDroidPingResult +} + +type fdroidAPIResponse struct { + PackageName string `json:"packageName"` + SuggestedVersionCode int32 `json:"suggestedVersionCode"` + Packages []fdroidAPIPackage `json:"packages"` +} + +type fdroidAPIPackage struct { + VersionName string `json:"versionName"` + VersionCode int32 `json:"versionCode"` +} + +type fdroidEntry struct { + Timestamp int64 `json:"timestamp"` + Version int `json:"version"` + Index fdroidEntryFile `json:"index"` + Diffs map[string]fdroidEntryFile `json:"diffs"` +} + +type fdroidEntryFile struct { + Name string `json:"name"` + SHA256 string `json:"sha256"` + Size int64 `json:"size"` + NumPackages int `json:"numPackages"` +} + +type fdroidIndexV2 struct { + Packages map[string]fdroidV2Package `json:"packages"` +} + +type fdroidV2Package struct { + Versions map[string]fdroidV2Version `json:"versions"` +} + +type fdroidV2Version struct { + Manifest fdroidV2Manifest `json:"manifest"` + File fdroidV2File `json:"file"` +} + +type fdroidV2Manifest struct { + VersionCode int32 `json:"versionCode"` + VersionName string `json:"versionName"` +} + +type fdroidV2File struct { + Name string `json:"name"` + SHA256 string `json:"sha256"` + Size int64 `json:"size"` +} + +type fdroidIndexV1 struct { + Packages map[string][]fdroidV1Package `json:"packages"` +} + +type fdroidV1Package struct { + VersionCode int32 `json:"versionCode"` + VersionName string `json:"versionName"` + ApkName string `json:"apkName"` + Size int64 `json:"size"` + Hash string `json:"hash"` + HashType string `json:"hashType"` +} + +type fdroidCache struct { + MirrorURL string `json:"mirrorURL"` + Timestamp int64 `json:"timestamp"` + ETag string `json:"etag"` + IsV1 bool `json:"isV1,omitempty"` +} + +func CheckFDroidUpdate(mirrorURL, packageName string, currentVersionCode int32, cachePath string) (*FDroidUpdateInfo, error) { + mirrorURL = strings.TrimRight(mirrorURL, "/") + if strings.Contains(mirrorURL, "f-droid.org") { + return checkFDroidAPI(mirrorURL, packageName, currentVersionCode) + } + client := newFDroidHTTPClient() + defer client.CloseIdleConnections() + cache := loadFDroidCache(cachePath, mirrorURL) + if cache != nil && cache.IsV1 { + return checkFDroidV1(client, mirrorURL, packageName, currentVersionCode, cachePath, cache) + } + return checkFDroidV2(client, mirrorURL, packageName, currentVersionCode, cachePath, cache) +} + +func PingFDroidMirrors(mirrorURLs string) (FDroidPingResultIterator, error) { + urls := strings.Split(mirrorURLs, ",") + results := make([]*FDroidPingResult, len(urls)) + var waitGroup sync.WaitGroup + for i, rawURL := range urls { + waitGroup.Add(1) + go func(index int, target string) { + defer waitGroup.Done() + target = strings.TrimSpace(target) + result := &FDroidPingResult{URL: target} + latency, err := pingTLS(target) + if err != nil { + result.LatencyMs = -1 + result.Error = err.Error() + } else { + result.LatencyMs = int32(latency.Milliseconds()) + } + results[index] = result + }(i, rawURL) + } + waitGroup.Wait() + sort.Slice(results, func(i, j int) bool { + if results[i].LatencyMs < 0 { + return false + } + if results[j].LatencyMs < 0 { + return true + } + return results[i].LatencyMs < results[j].LatencyMs + }) + return newIterator(results), nil +} + +func PingFDroidMirror(mirrorURL string) *FDroidPingResult { + mirrorURL = strings.TrimSpace(mirrorURL) + result := &FDroidPingResult{URL: mirrorURL} + latency, err := pingTLS(mirrorURL) + if err != nil { + result.LatencyMs = -1 + result.Error = err.Error() + } else { + result.LatencyMs = int32(latency.Milliseconds()) + } + return result +} + +func newFDroidHTTPClient() *http.Client { + return &http.Client{ + Timeout: 30 * time.Second, + } +} + +func newFDroidRequest(requestURL string) (*http.Request, error) { + request, err := http.NewRequest("GET", requestURL, nil) + if err != nil { + return nil, err + } + request.Header.Set("User-Agent", fdroidUserAgent) + return request, nil +} + +func checkFDroidAPI(mirrorURL, packageName string, currentVersionCode int32) (*FDroidUpdateInfo, error) { + client := newFDroidHTTPClient() + defer client.CloseIdleConnections() + + apiURL := "https://f-droid.org/api/v1/packages/" + packageName + request, err := newFDroidRequest(apiURL) + if err != nil { + return nil, err + } + + response, err := client.Do(request) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return nil, E.New("HTTP ", response.Status) + } + + body, err := io.ReadAll(response.Body) + if err != nil { + return nil, err + } + + var apiResponse fdroidAPIResponse + err = json.Unmarshal(body, &apiResponse) + if err != nil { + return nil, err + } + + var bestCode int32 + var bestName string + for _, pkg := range apiResponse.Packages { + if pkg.VersionCode > currentVersionCode && pkg.VersionCode > bestCode { + bestCode = pkg.VersionCode + bestName = pkg.VersionName + } + } + + if bestCode == 0 { + return nil, nil + } + + return &FDroidUpdateInfo{ + VersionCode: bestCode, + VersionName: bestName, + DownloadURL: "https://f-droid.org/repo/" + packageName + "_" + strconv.FormatInt(int64(bestCode), 10) + ".apk", + }, nil +} + +func checkFDroidV2(client *http.Client, mirrorURL, packageName string, currentVersionCode int32, cachePath string, cache *fdroidCache) (*FDroidUpdateInfo, error) { + entryURL := mirrorURL + "/entry.jar" + request, err := newFDroidRequest(entryURL) + if err != nil { + return nil, err + } + if cache != nil && cache.ETag != "" { + request.Header.Set("If-None-Match", cache.ETag) + } + + response, err := client.Do(request) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode == http.StatusNotModified { + return nil, nil + } + if response.StatusCode == http.StatusNotFound { + writeFDroidCache(cachePath, mirrorURL, 0, "", true) + return checkFDroidV1(client, mirrorURL, packageName, currentVersionCode, cachePath, nil) + } + if response.StatusCode != http.StatusOK { + return nil, E.New("HTTP ", response.Status, ": ", entryURL) + } + + jarData, err := io.ReadAll(response.Body) + if err != nil { + return nil, err + } + etag := response.Header.Get("ETag") + + var entry fdroidEntry + err = readJSONFromJar(jarData, "entry.json", &entry) + if err != nil { + return nil, E.Cause(err, "read entry.jar") + } + + if entry.Timestamp == 0 { + return nil, E.New("entry.json not found in entry.jar") + } + + if cache != nil && cache.Timestamp == entry.Timestamp { + writeFDroidCache(cachePath, mirrorURL, entry.Timestamp, etag, false) + return nil, nil + } + + var indexURL string + if cache != nil { + cachedTimestamp := strconv.FormatInt(cache.Timestamp, 10) + if diff, ok := entry.Diffs[cachedTimestamp]; ok { + indexURL = mirrorURL + "/" + diff.Name + } + } + if indexURL == "" { + indexURL = mirrorURL + "/" + entry.Index.Name + } + + indexRequest, err := newFDroidRequest(indexURL) + if err != nil { + return nil, err + } + + indexResponse, err := client.Do(indexRequest) + if err != nil { + return nil, err + } + defer indexResponse.Body.Close() + + if indexResponse.StatusCode != http.StatusOK { + return nil, E.New("HTTP ", indexResponse.Status, ": ", indexURL) + } + + indexData, err := io.ReadAll(indexResponse.Body) + if err != nil { + return nil, err + } + + var index fdroidIndexV2 + err = json.Unmarshal(indexData, &index) + if err != nil { + return nil, err + } + + writeFDroidCache(cachePath, mirrorURL, entry.Timestamp, etag, false) + + pkg, ok := index.Packages[packageName] + if !ok { + return nil, nil + } + + var bestCode int32 + var bestVersion fdroidV2Version + for _, version := range pkg.Versions { + if version.Manifest.VersionCode > currentVersionCode && version.Manifest.VersionCode > bestCode { + bestCode = version.Manifest.VersionCode + bestVersion = version + } + } + + if bestCode == 0 { + return nil, nil + } + + return &FDroidUpdateInfo{ + VersionCode: bestCode, + VersionName: bestVersion.Manifest.VersionName, + DownloadURL: mirrorURL + "/" + bestVersion.File.Name, + FileSize: bestVersion.File.Size, + FileSHA256: bestVersion.File.SHA256, + }, nil +} + +func checkFDroidV1(client *http.Client, mirrorURL, packageName string, currentVersionCode int32, cachePath string, cache *fdroidCache) (*FDroidUpdateInfo, error) { + indexURL := mirrorURL + "/index-v1.jar" + + request, err := newFDroidRequest(indexURL) + if err != nil { + return nil, err + } + if cache != nil && cache.ETag != "" { + request.Header.Set("If-None-Match", cache.ETag) + } + + response, err := client.Do(request) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode == http.StatusNotModified { + return nil, nil + } + if response.StatusCode != http.StatusOK { + return nil, E.New("HTTP ", response.Status, ": ", indexURL) + } + + jarData, err := io.ReadAll(response.Body) + if err != nil { + return nil, err + } + etag := response.Header.Get("ETag") + + var index fdroidIndexV1 + err = readJSONFromJar(jarData, "index-v1.json", &index) + if err != nil { + return nil, E.Cause(err, "read index-v1.jar") + } + + writeFDroidCache(cachePath, mirrorURL, 0, etag, true) + + packages, ok := index.Packages[packageName] + if !ok { + return nil, nil + } + + var bestCode int32 + var bestPackage fdroidV1Package + for _, pkg := range packages { + if pkg.VersionCode > currentVersionCode && pkg.VersionCode > bestCode { + bestCode = pkg.VersionCode + bestPackage = pkg + } + } + + if bestCode == 0 { + return nil, nil + } + + return &FDroidUpdateInfo{ + VersionCode: bestCode, + VersionName: bestPackage.VersionName, + DownloadURL: mirrorURL + "/" + bestPackage.ApkName, + FileSize: bestPackage.Size, + FileSHA256: bestPackage.Hash, + }, nil +} + +func readJSONFromJar(jarData []byte, fileName string, destination any) error { + zipReader, err := zip.NewReader(bytes.NewReader(jarData), int64(len(jarData))) + if err != nil { + return err + } + for _, file := range zipReader.File { + if file.Name != fileName { + continue + } + reader, err := file.Open() + if err != nil { + return err + } + data, err := io.ReadAll(reader) + reader.Close() + if err != nil { + return err + } + return json.Unmarshal(data, destination) + } + return nil +} + +func pingTLS(mirrorURL string) (time.Duration, error) { + parsed, err := url.Parse(mirrorURL) + if err != nil { + return 0, err + } + host := parsed.Host + if !strings.Contains(host, ":") { + host = host + ":443" + } + + dialer := &net.Dialer{Timeout: 5 * time.Second} + start := time.Now() + conn, err := tls.DialWithDialer(dialer, "tcp", host, &tls.Config{}) + if err != nil { + return 0, err + } + latency := time.Since(start) + conn.Close() + return latency, nil +} + +func loadFDroidCache(cachePath, mirrorURL string) *fdroidCache { + cacheFile := filepath.Join(cachePath, "fdroid_cache.json") + data, err := os.ReadFile(cacheFile) + if err != nil { + return nil + } + var cache fdroidCache + err = json.Unmarshal(data, &cache) + if err != nil { + return nil + } + if cache.MirrorURL != mirrorURL { + return nil + } + return &cache +} + +func writeFDroidCache(cachePath, mirrorURL string, timestamp int64, etag string, isV1 bool) { + cache := fdroidCache{ + MirrorURL: mirrorURL, + Timestamp: timestamp, + ETag: etag, + IsV1: isV1, + } + data, err := json.Marshal(cache) + if err != nil { + return + } + os.MkdirAll(cachePath, 0o755) + os.WriteFile(filepath.Join(cachePath, "fdroid_cache.json"), data, 0o644) +} diff --git a/experimental/libbox/fdroid_mirrors.go b/experimental/libbox/fdroid_mirrors.go new file mode 100644 index 0000000000..4ca8255562 --- /dev/null +++ b/experimental/libbox/fdroid_mirrors.go @@ -0,0 +1,92 @@ +package libbox + +type FDroidMirror struct { + URL string + Country string + Name string +} + +type FDroidMirrorIterator interface { + Len() int32 + HasNext() bool + Next() *FDroidMirror +} + +var builtinFDroidMirrors = []FDroidMirror{ + // Official + {URL: "https://f-droid.org/repo", Country: "Official", Name: "f-droid.org"}, + {URL: "https://cloudflare.f-droid.org/repo", Country: "Official", Name: "Cloudflare CDN"}, + + // China + {URL: "https://mirrors.tuna.tsinghua.edu.cn/fdroid/repo", Country: "China", Name: "Tsinghua TUNA"}, + {URL: "https://mirrors.nju.edu.cn/fdroid/repo", Country: "China", Name: "Nanjing University"}, + {URL: "https://mirror.iscas.ac.cn/fdroid/repo", Country: "China", Name: "ISCAS"}, + {URL: "https://mirror.nyist.edu.cn/fdroid/repo", Country: "China", Name: "NYIST"}, + {URL: "https://mirrors.cqupt.edu.cn/fdroid/repo", Country: "China", Name: "CQUPT"}, + {URL: "https://mirrors.shanghaitech.edu.cn/fdroid/repo", Country: "China", Name: "ShanghaiTech"}, + + // India + {URL: "https://mirror.hyd.albony.in/fdroid/repo", Country: "India", Name: "Albony Hyderabad"}, + {URL: "https://mirror.del2.albony.in/fdroid/repo", Country: "India", Name: "Albony Delhi"}, + + // Taiwan + {URL: "https://mirror.ossplanet.net/fdroid/repo", Country: "Taiwan", Name: "OSSPlanet"}, + + // France + {URL: "https://fdroid.tetaneutral.net/fdroid/repo", Country: "France", Name: "tetaneutral.net"}, + {URL: "https://mirror.freedif.org/fdroid/repo", Country: "France", Name: "FreeDif"}, + + // Germany + {URL: "https://ftp.fau.de/fdroid/repo", Country: "Germany", Name: "FAU Erlangen"}, + {URL: "https://ftp.agdsn.de/fdroid/repo", Country: "Germany", Name: "AGDSN Dresden"}, + {URL: "https://ftp.gwdg.de/pub/android/fdroid/repo", Country: "Germany", Name: "GWDG"}, + {URL: "https://mirror.level66.network/fdroid/repo", Country: "Germany", Name: "Level66"}, + {URL: "https://mirror.mci-1.serverforge.org/fdroid/repo", Country: "Germany", Name: "ServerForge"}, + + // Netherlands + {URL: "https://ftp.snt.utwente.nl/pub/software/fdroid/repo", Country: "Netherlands", Name: "University of Twente"}, + + // Sweden + {URL: "https://ftp.lysator.liu.se/pub/fdroid/repo", Country: "Sweden", Name: "Lysator"}, + + // Denmark + {URL: "https://mirrors.dotsrc.org/fdroid/repo", Country: "Denmark", Name: "dotsrc.org"}, + + // Austria + {URL: "https://mirror.kumi.systems/fdroid/repo", Country: "Austria", Name: "Kumi Systems"}, + + // Switzerland + {URL: "https://mirror.init7.net/fdroid/repo", Country: "Switzerland", Name: "Init7"}, + + // Romania + {URL: "https://mirrors.hostico.ro/fdroid/repo", Country: "Romania", Name: "Hostico"}, + {URL: "https://mirrors.chroot.ro/fdroid/repo", Country: "Romania", Name: "Chroot"}, + {URL: "https://ftp.lug.ro/fdroid/repo", Country: "Romania", Name: "LUG Romania"}, + + // US + {URL: "https://plug-mirror.rcac.purdue.edu/fdroid/repo", Country: "US", Name: "Purdue"}, + {URL: "https://mirror.fcix.net/fdroid/repo", Country: "US", Name: "FCIX"}, + {URL: "https://opencolo.mm.fcix.net/fdroid/repo", Country: "US", Name: "OpenColo"}, + {URL: "https://forksystems.mm.fcix.net/fdroid/repo", Country: "US", Name: "Fork Systems"}, + {URL: "https://southfront.mm.fcix.net/fdroid/repo", Country: "US", Name: "South Front"}, + {URL: "https://ziply.mm.fcix.net/fdroid/repo", Country: "US", Name: "Ziply"}, + + // Canada + {URL: "https://mirror.quantum5.ca/fdroid/repo", Country: "Canada", Name: "Quantum5"}, + + // Australia + {URL: "https://mirror.aarnet.edu.au/fdroid/repo", Country: "Australia", Name: "AARNet"}, + + // Other + {URL: "https://mirror.cyberbits.eu/fdroid/repo", Country: "Europe", Name: "Cyberbits EU"}, + {URL: "https://mirror.eu.ossplanet.net/fdroid/repo", Country: "Europe", Name: "OSSPlanet EU"}, + {URL: "https://mirror.cyberbits.asia/fdroid/repo", Country: "Asia", Name: "Cyberbits Asia"}, + {URL: "https://mirrors.jevincanders.net/fdroid/repo", Country: "US", Name: "Jevincanders"}, + {URL: "https://mirrors.komogoto.com/fdroid/repo", Country: "US", Name: "Komogoto"}, + {URL: "https://fdroid.rasp.sh/fdroid/repo", Country: "Europe", Name: "rasp.sh"}, + {URL: "https://mirror.gofoss.xyz/fdroid/repo", Country: "Europe", Name: "GoFOSS"}, +} + +func GetFDroidMirrors() FDroidMirrorIterator { + return newPtrIterator(builtinFDroidMirrors) +} diff --git a/experimental/libbox/ffi.json b/experimental/libbox/ffi.json new file mode 100644 index 0000000000..81fae27dac --- /dev/null +++ b/experimental/libbox/ffi.json @@ -0,0 +1,257 @@ +{ + "version": 1, + "variables": { + "VERSION": "$(go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest)", + "WORKSPACE_ROOT": "../../..", + "DEPLOY_ANDROID": "${WORKSPACE_ROOT}/sing-box-for-android/app/libs", + "DEPLOY_APPLE": "${WORKSPACE_ROOT}/sing-box-for-apple", + "DEPLOY_WINDOWS": "${WORKSPACE_ROOT}/sing-box-for-windows/local-packages" + }, + "packages": [ + { + "id": "libbox", + "path": ".", + "java_package": "io.nekohasekai.libbox", + "csharp_namespace": "SagerNet", + "csharp_entrypoint": "Libbox", + "apple_prefix": "Libbox" + } + ], + "builds": [ + { + "id": "android-main", + "packages": ["libbox"], + "default": { + "tags": [ + "with_gvisor", + "with_quic", + "with_wireguard", + "with_utls", + "with_naive_outbound", + "with_clash_api", + "badlinkname", + "tfogo_checklinkname0", + "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" + ], + "ldflags": "-X github.com/sagernet/sing-box/constant.Version=${VERSION} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0", + "trimpath": true + } + }, + { + "id": "android-legacy", + "packages": ["libbox"], + "default": { + "tags": [ + "with_gvisor", + "with_quic", + "with_wireguard", + "with_utls", + "with_clash_api", + "badlinkname", + "tfogo_checklinkname0", + "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" + ], + "ldflags": "-X github.com/sagernet/sing-box/constant.Version=${VERSION} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0", + "trimpath": true + } + }, + { + "id": "apple", + "packages": ["libbox"], + "default": { + "tags": [ + "with_gvisor", + "with_quic", + "with_wireguard", + "with_utls", + "with_naive_outbound", + "with_clash_api", + "badlinkname", + "tfogo_checklinkname0", + "with_dhcp", + "grpcnotrace", + "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" + ], + "ldflags": "-X github.com/sagernet/sing-box/constant.Version=${VERSION} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0", + "trimpath": true + }, + "overrides": [ + { + "match": { "os": "ios" }, + "tags_append": ["with_low_memory"] + }, + { + "match": { "os": "tvos" }, + "tags_append": ["with_low_memory"] + } + ] + }, + { + "id": "windows", + "packages": ["libbox"], + "default": { + "tags": [ + "with_gvisor", + "with_quic", + "with_wireguard", + "with_utls", + "with_naive_outbound", + "with_purego", + "with_clash_api", + "badlinkname", + "tfogo_checklinkname0", + "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" + ], + "ldflags": "-X github.com/sagernet/sing-box/constant.Version=${VERSION} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0", + "trimpath": true + } + } + ], + "platforms": [ + { + "type": "android", + "build": "android-main", + "min_sdk": 23, + "ndk_version": "28.0.13004108", + "lib_name": "box", + "languages": [{ "type": "java" }], + "artifacts": [ + { + "type": "aar", + "output_path": "libbox.aar", + "execute_after": [ + "if [ -d \"${DEPLOY_ANDROID}\" ]; then", + " rm -f \"${DEPLOY_ANDROID}/$$(basename \"${OUTPUT_PATH}\")\"", + " mv \"${OUTPUT_PATH}\" \"${DEPLOY_ANDROID}/\"", + "fi" + ] + } + ] + }, + { + "type": "android", + "build": "android-legacy", + "min_sdk": 21, + "ndk_version": "28.0.13004108", + "lib_name": "box", + "languages": [{ "type": "java" }], + "artifacts": [ + { + "type": "aar", + "output_path": "libbox-legacy.aar", + "execute_after": [ + "if [ -d \"${DEPLOY_ANDROID}\" ]; then", + " rm -f \"${DEPLOY_ANDROID}/$$(basename \"${OUTPUT_PATH}\")\"", + " mv \"${OUTPUT_PATH}\" \"${DEPLOY_ANDROID}/\"", + "fi" + ] + } + ] + }, + { + "type": "apple", + "build": "apple", + "targets": [ + "ios/arm64", + "ios/simulator/arm64", + "ios/simulator/amd64", + "tvos/arm64", + "tvos/simulator/arm64", + "tvos/simulator/amd64", + "macos/arm64", + "macos/amd64" + ], + "languages": [{ "type": "objc" }], + "artifacts": [ + { + "type": "xcframework", + "module_name": "Libbox", + "execute_after": [ + "if [ -d \"${DEPLOY_APPLE}\" ]; then", + " rm -rf \"${DEPLOY_APPLE}/${MODULE_NAME}.xcframework\"", + " mv \"${OUTPUT_PATH}\" \"${DEPLOY_APPLE}/\"", + "fi" + ] + } + ] + }, + { + "type": "csharp", + "build": "windows", + "targets": [ + "windows/amd64" + ], + "languages": [{ "type": "csharp" }], + "artifacts": [ + { + "type": "nuget", + "package_id": "SagerNet.Libbox", + "package_version": "0.0.0-local", + "execute_after": { + "windows": [ + "$$deployPath = '${DEPLOY_WINDOWS}'", + "if (Test-Path $$deployPath) {", + " Remove-Item \"$$deployPath\\${PACKAGE_ID}.*.nupkg\" -ErrorAction SilentlyContinue", + " Move-Item -Force '${OUTPUT_PATH}' \"$$deployPath\\\"", + " $$cachePath = if ($$env:NUGET_PACKAGES) { $$env:NUGET_PACKAGES } else { \"$$env:USERPROFILE\\.nuget\\packages\" }", + " Remove-Item -Recurse -Force \"$$cachePath\\sagernet.libbox\\${PACKAGE_VERSION}\" -ErrorAction SilentlyContinue", + "}" + ], + "default": [ + "if [ -d \"${DEPLOY_WINDOWS}\" ]; then", + " rm -f \"${DEPLOY_WINDOWS}/${PACKAGE_ID}.*.nupkg\"", + " mv \"${OUTPUT_PATH}\" \"${DEPLOY_WINDOWS}/\"", + " cache_path=\"$${NUGET_PACKAGES:-$${HOME}/.nuget/packages}\"", + " rm -rf \"$${cache_path}/sagernet.libbox/${PACKAGE_VERSION}\"", + "fi" + ] + } + } + ] + } + ] +} diff --git a/experimental/libbox/http.go b/experimental/libbox/http.go index 87c5bed626..69a23d264f 100644 --- a/experimental/libbox/http.go +++ b/experimental/libbox/http.go @@ -17,8 +17,8 @@ import ( "os" "strconv" "sync" - "time" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" @@ -50,9 +50,13 @@ type HTTPRequest interface { } type HTTPResponse interface { - GetContent() ([]byte, error) - GetContentString() (string, error) + GetContent() (*StringBox, error) WriteTo(path string) error + WriteToWithProgress(path string, handler HTTPResponseWriteToProgressHandler) error +} + +type HTTPResponseWriteToProgressHandler interface { + Update(progress int64, total int64) } var ( @@ -69,30 +73,36 @@ type httpClient struct { func NewHTTPClient() HTTPClient { client := new(httpClient) - client.client.Timeout = 15 * time.Second client.client.Transport = &client.transport + client.transport.ForceAttemptHTTP2 = true + client.transport.TLSHandshakeTimeout = C.TCPTimeout client.transport.TLSClientConfig = &client.tls client.transport.DisableKeepAlives = true return client } func (c *httpClient) ModernTLS() { - c.tls.MinVersion = tls.VersionTLS12 - c.tls.CipherSuites = common.Map(tls.CipherSuites(), func(it *tls.CipherSuite) uint16 { return it.ID }) + c.setTLSVersion(tls.VersionTLS12, 0, func(suite *tls.CipherSuite) bool { return true }) } func (c *httpClient) RestrictedTLS() { - c.tls.MinVersion = tls.VersionTLS13 - c.tls.CipherSuites = common.Map(common.Filter(tls.CipherSuites(), func(it *tls.CipherSuite) bool { - return common.Contains(it.SupportedVersions, uint16(tls.VersionTLS13)) - }), func(it *tls.CipherSuite) uint16 { + c.setTLSVersion(tls.VersionTLS13, 0, func(suite *tls.CipherSuite) bool { + return common.Contains(suite.SupportedVersions, uint16(tls.VersionTLS13)) + }) +} + +func (c *httpClient) setTLSVersion(minVersion, maxVersion uint16, filter func(*tls.CipherSuite) bool) { + c.tls.MinVersion = minVersion + if maxVersion != 0 { + c.tls.MaxVersion = maxVersion + } + c.tls.CipherSuites = common.Map(common.Filter(tls.CipherSuites(), filter), func(it *tls.CipherSuite) uint16 { return it.ID }) } func (c *httpClient) PinnedTLS12() { - c.tls.MinVersion = tls.VersionTLS12 - c.tls.MaxVersion = tls.VersionTLS12 + c.setTLSVersion(tls.VersionTLS12, tls.VersionTLS12, func(suite *tls.CipherSuite) bool { return true }) } func (c *httpClient) PinnedSHA256(sumHex string) { @@ -127,7 +137,6 @@ func (c *httpClient) TrySocks5(port int32) { } func (c *httpClient) KeepAlive() { - c.transport.ForceAttemptHTTP2 = true c.transport.DisableKeepAlives = false } @@ -179,9 +188,7 @@ func (r *httpRequest) SetUserAgent(userAgent string) { } func (r *httpRequest) SetContent(content []byte) { - buffer := bytes.Buffer{} - buffer.Write(content) - r.request.Body = io.NopCloser(bytes.NewReader(buffer.Bytes())) + r.request.Body = io.NopCloser(bytes.NewReader(content)) r.request.ContentLength = int64(len(content)) } @@ -210,35 +217,58 @@ type httpResponse struct { } func (h *httpResponse) errorString() string { - content, err := h.GetContentString() + content, err := h.GetContent() if err != nil { return fmt.Sprint("HTTP ", h.Status) } return fmt.Sprint("HTTP ", h.Status, ": ", content) } -func (h *httpResponse) GetContent() ([]byte, error) { +func (h *httpResponse) GetContent() (*StringBox, error) { h.getContentOnce.Do(func() { defer h.Body.Close() h.content, h.contentError = io.ReadAll(h.Body) }) - return h.content, h.contentError + if h.contentError != nil { + return nil, h.contentError + } + return wrapString(string(h.content)), nil } -func (h *httpResponse) GetContentString() (string, error) { - content, err := h.GetContent() +func (h *httpResponse) WriteTo(path string) error { + defer h.Body.Close() + file, err := os.Create(path) if err != nil { - return "", err + return err } - return string(content), nil + defer file.Close() + return common.Error(bufio.Copy(file, h.Body)) } -func (h *httpResponse) WriteTo(path string) error { +func (h *httpResponse) WriteToWithProgress(path string, handler HTTPResponseWriteToProgressHandler) error { defer h.Body.Close() file, err := os.Create(path) if err != nil { return err } defer file.Close() - return common.Error(bufio.Copy(file, h.Body)) + return common.Error(bufio.Copy(&progressWriter{ + writer: file, + handler: handler, + total: h.ContentLength, + }, h.Body)) +} + +type progressWriter struct { + writer io.Writer + handler HTTPResponseWriteToProgressHandler + total int64 + written int64 +} + +func (w *progressWriter) Write(p []byte) (int, error) { + n, err := w.writer.Write(p) + w.written += int64(n) + w.handler.Update(w.written, w.total) + return n, err } diff --git a/experimental/libbox/internal/oomprofile/builder.go b/experimental/libbox/internal/oomprofile/builder.go new file mode 100644 index 0000000000..9f95456f26 --- /dev/null +++ b/experimental/libbox/internal/oomprofile/builder.go @@ -0,0 +1,386 @@ +//go:build darwin || linux || windows + +package oomprofile + +import ( + "fmt" + "io" + "runtime" + "time" +) + +const ( + tagProfile_SampleType = 1 + tagProfile_Sample = 2 + tagProfile_Mapping = 3 + tagProfile_Location = 4 + tagProfile_Function = 5 + tagProfile_StringTable = 6 + tagProfile_TimeNanos = 9 + tagProfile_PeriodType = 11 + tagProfile_Period = 12 + tagProfile_DefaultSampleType = 14 + + tagValueType_Type = 1 + tagValueType_Unit = 2 + + tagSample_Location = 1 + tagSample_Value = 2 + tagSample_Label = 3 + + tagLabel_Key = 1 + tagLabel_Str = 2 + tagLabel_Num = 3 + + tagMapping_ID = 1 + tagMapping_Start = 2 + tagMapping_Limit = 3 + tagMapping_Offset = 4 + tagMapping_Filename = 5 + tagMapping_BuildID = 6 + tagMapping_HasFunctions = 7 + tagMapping_HasFilenames = 8 + tagMapping_HasLineNumbers = 9 + tagMapping_HasInlineFrames = 10 + + tagLocation_ID = 1 + tagLocation_MappingID = 2 + tagLocation_Address = 3 + tagLocation_Line = 4 + + tagLine_FunctionID = 1 + tagLine_Line = 2 + + tagFunction_ID = 1 + tagFunction_Name = 2 + tagFunction_SystemName = 3 + tagFunction_Filename = 4 + tagFunction_StartLine = 5 +) + +type memMap struct { + start uintptr + end uintptr + offset uint64 + file string + buildID string + funcs symbolizeFlag + fake bool +} + +type symbolizeFlag uint8 + +const ( + lookupTried symbolizeFlag = 1 << iota + lookupFailed +) + +func newProfileBuilder(w io.Writer) *profileBuilder { + builder := &profileBuilder{ + start: time.Now(), + w: w, + strings: []string{""}, + stringMap: map[string]int{"": 0}, + locs: map[uintptr]locInfo{}, + funcs: map[string]int{}, + } + builder.readMapping() + return builder +} + +func (b *profileBuilder) stringIndex(s string) int64 { + id, ok := b.stringMap[s] + if !ok { + id = len(b.strings) + b.strings = append(b.strings, s) + b.stringMap[s] = id + } + return int64(id) +} + +func (b *profileBuilder) flush() { + const dataFlush = 4096 + if b.err != nil || b.pb.nest != 0 || len(b.pb.data) <= dataFlush { + return + } + + _, b.err = b.w.Write(b.pb.data) + b.pb.data = b.pb.data[:0] +} + +func (b *profileBuilder) pbValueType(tag int, typ string, unit string) { + start := b.pb.startMessage() + b.pb.int64(tagValueType_Type, b.stringIndex(typ)) + b.pb.int64(tagValueType_Unit, b.stringIndex(unit)) + b.pb.endMessage(tag, start) +} + +func (b *profileBuilder) pbSample(values []int64, locs []uint64, labels func()) { + start := b.pb.startMessage() + b.pb.int64s(tagSample_Value, values) + b.pb.uint64s(tagSample_Location, locs) + if labels != nil { + labels() + } + b.pb.endMessage(tagProfile_Sample, start) + b.flush() +} + +func (b *profileBuilder) pbLabel(tag int, key string, str string, num int64) { + start := b.pb.startMessage() + b.pb.int64Opt(tagLabel_Key, b.stringIndex(key)) + b.pb.int64Opt(tagLabel_Str, b.stringIndex(str)) + b.pb.int64Opt(tagLabel_Num, num) + b.pb.endMessage(tag, start) +} + +func (b *profileBuilder) pbLine(tag int, funcID uint64, line int64) { + start := b.pb.startMessage() + b.pb.uint64Opt(tagLine_FunctionID, funcID) + b.pb.int64Opt(tagLine_Line, line) + b.pb.endMessage(tag, start) +} + +func (b *profileBuilder) pbMapping(tag int, id uint64, base uint64, limit uint64, offset uint64, file string, buildID string, hasFuncs bool) { + start := b.pb.startMessage() + b.pb.uint64Opt(tagMapping_ID, id) + b.pb.uint64Opt(tagMapping_Start, base) + b.pb.uint64Opt(tagMapping_Limit, limit) + b.pb.uint64Opt(tagMapping_Offset, offset) + b.pb.int64Opt(tagMapping_Filename, b.stringIndex(file)) + b.pb.int64Opt(tagMapping_BuildID, b.stringIndex(buildID)) + if hasFuncs { + b.pb.bool(tagMapping_HasFunctions, true) + } + b.pb.endMessage(tag, start) +} + +func (b *profileBuilder) build() error { + if b.err != nil { + return b.err + } + + b.pb.int64Opt(tagProfile_TimeNanos, b.start.UnixNano()) + for i, mapping := range b.mem { + hasFunctions := mapping.funcs == lookupTried + b.pbMapping(tagProfile_Mapping, uint64(i+1), uint64(mapping.start), uint64(mapping.end), mapping.offset, mapping.file, mapping.buildID, hasFunctions) + } + b.pb.strings(tagProfile_StringTable, b.strings) + if b.err != nil { + return b.err + } + _, err := b.w.Write(b.pb.data) + return err +} + +func allFrames(addr uintptr) ([]runtime.Frame, symbolizeFlag) { + frames := runtime.CallersFrames([]uintptr{addr}) + frame, more := frames.Next() + if frame.Function == "runtime.goexit" { + return nil, 0 + } + + result := lookupTried + if frame.PC == 0 || frame.Function == "" || frame.File == "" || frame.Line == 0 { + result |= lookupFailed + } + if frame.PC == 0 { + frame.PC = addr - 1 + } + + ret := []runtime.Frame{frame} + for frame.Function != "runtime.goexit" && more { + frame, more = frames.Next() + ret = append(ret, frame) + } + return ret, result +} + +type locInfo struct { + id uint64 + + pcs []uintptr + + firstPCFrames []runtime.Frame + firstPCSymbolizeResult symbolizeFlag +} + +func (b *profileBuilder) appendLocsForStack(locs []uint64, stk []uintptr) []uint64 { + b.deck.reset() + origStk := stk + stk = runtimeExpandFinalInlineFrame(stk) + + for len(stk) > 0 { + addr := stk[0] + if loc, ok := b.locs[addr]; ok { + if len(b.deck.pcs) > 0 { + if b.deck.tryAdd(addr, loc.firstPCFrames, loc.firstPCSymbolizeResult) { + stk = stk[1:] + continue + } + } + if id := b.emitLocation(); id > 0 { + locs = append(locs, id) + } + locs = append(locs, loc.id) + if len(loc.pcs) > len(stk) { + panic(fmt.Sprintf("stack too short to match cached location; stk = %#x, loc.pcs = %#x, original stk = %#x", stk, loc.pcs, origStk)) + } + stk = stk[len(loc.pcs):] + continue + } + + frames, symbolizeResult := allFrames(addr) + if len(frames) == 0 { + if id := b.emitLocation(); id > 0 { + locs = append(locs, id) + } + stk = stk[1:] + continue + } + + if b.deck.tryAdd(addr, frames, symbolizeResult) { + stk = stk[1:] + continue + } + if id := b.emitLocation(); id > 0 { + locs = append(locs, id) + } + + if loc, ok := b.locs[addr]; ok { + locs = append(locs, loc.id) + stk = stk[len(loc.pcs):] + } else { + b.deck.tryAdd(addr, frames, symbolizeResult) + stk = stk[1:] + } + } + if id := b.emitLocation(); id > 0 { + locs = append(locs, id) + } + return locs +} + +type pcDeck struct { + pcs []uintptr + frames []runtime.Frame + symbolizeResult symbolizeFlag + + firstPCFrames int + firstPCSymbolizeResult symbolizeFlag +} + +func (d *pcDeck) reset() { + d.pcs = d.pcs[:0] + d.frames = d.frames[:0] + d.symbolizeResult = 0 + d.firstPCFrames = 0 + d.firstPCSymbolizeResult = 0 +} + +func (d *pcDeck) tryAdd(pc uintptr, frames []runtime.Frame, symbolizeResult symbolizeFlag) bool { + if existing := len(d.frames); existing > 0 { + newFrame := frames[0] + last := d.frames[existing-1] + if last.Func != nil { + return false + } + if last.Entry == 0 || newFrame.Entry == 0 { + return false + } + if last.Entry != newFrame.Entry { + return false + } + if runtimeFrameSymbolName(&last) == runtimeFrameSymbolName(&newFrame) { + return false + } + } + + d.pcs = append(d.pcs, pc) + d.frames = append(d.frames, frames...) + d.symbolizeResult |= symbolizeResult + if len(d.pcs) == 1 { + d.firstPCFrames = len(d.frames) + d.firstPCSymbolizeResult = symbolizeResult + } + return true +} + +func (b *profileBuilder) emitLocation() uint64 { + if len(b.deck.pcs) == 0 { + return 0 + } + defer b.deck.reset() + + addr := b.deck.pcs[0] + firstFrame := b.deck.frames[0] + + type newFunc struct { + id uint64 + name string + file string + startLine int64 + } + + newFuncs := make([]newFunc, 0, 8) + id := uint64(len(b.locs)) + 1 + b.locs[addr] = locInfo{ + id: id, + pcs: append([]uintptr{}, b.deck.pcs...), + firstPCFrames: append([]runtime.Frame{}, b.deck.frames[:b.deck.firstPCFrames]...), + firstPCSymbolizeResult: b.deck.firstPCSymbolizeResult, + } + + start := b.pb.startMessage() + b.pb.uint64Opt(tagLocation_ID, id) + b.pb.uint64Opt(tagLocation_Address, uint64(firstFrame.PC)) + for _, frame := range b.deck.frames { + funcName := runtimeFrameSymbolName(&frame) + funcID := uint64(b.funcs[funcName]) + if funcID == 0 { + funcID = uint64(len(b.funcs)) + 1 + b.funcs[funcName] = int(funcID) + newFuncs = append(newFuncs, newFunc{ + id: funcID, + name: funcName, + file: frame.File, + startLine: int64(runtimeFrameStartLine(&frame)), + }) + } + b.pbLine(tagLocation_Line, funcID, int64(frame.Line)) + } + for i := range b.mem { + if (b.mem[i].start <= addr && addr < b.mem[i].end) || b.mem[i].fake { + b.pb.uint64Opt(tagLocation_MappingID, uint64(i+1)) + mapping := b.mem[i] + mapping.funcs |= b.deck.symbolizeResult + b.mem[i] = mapping + break + } + } + b.pb.endMessage(tagProfile_Location, start) + + for _, fn := range newFuncs { + start := b.pb.startMessage() + b.pb.uint64Opt(tagFunction_ID, fn.id) + b.pb.int64Opt(tagFunction_Name, b.stringIndex(fn.name)) + b.pb.int64Opt(tagFunction_SystemName, b.stringIndex(fn.name)) + b.pb.int64Opt(tagFunction_Filename, b.stringIndex(fn.file)) + b.pb.int64Opt(tagFunction_StartLine, fn.startLine) + b.pb.endMessage(tagProfile_Function, start) + } + + b.flush() + return id +} + +func (b *profileBuilder) addMappingEntry(lo uint64, hi uint64, offset uint64, file string, buildID string, fake bool) { + b.mem = append(b.mem, memMap{ + start: uintptr(lo), + end: uintptr(hi), + offset: offset, + file: file, + buildID: buildID, + fake: fake, + }) +} diff --git a/experimental/libbox/internal/oomprofile/defs_darwin_amd64.go b/experimental/libbox/internal/oomprofile/defs_darwin_amd64.go new file mode 100644 index 0000000000..8a30074ca2 --- /dev/null +++ b/experimental/libbox/internal/oomprofile/defs_darwin_amd64.go @@ -0,0 +1,24 @@ +//go:build darwin && amd64 + +package oomprofile + +type machVMRegionBasicInfoData struct { + Protection int32 + MaxProtection int32 + Inheritance uint32 + Shared uint32 + Reserved uint32 + Offset [8]byte + Behavior int32 + UserWiredCount uint16 + PadCgo1 [2]byte +} + +const ( + _VM_PROT_READ = 0x1 + _VM_PROT_EXECUTE = 0x4 + + _MACH_SEND_INVALID_DEST = 0x10000003 + + _MAXPATHLEN = 0x400 +) diff --git a/experimental/libbox/internal/oomprofile/defs_darwin_arm64.go b/experimental/libbox/internal/oomprofile/defs_darwin_arm64.go new file mode 100644 index 0000000000..2fd4659001 --- /dev/null +++ b/experimental/libbox/internal/oomprofile/defs_darwin_arm64.go @@ -0,0 +1,24 @@ +//go:build darwin && arm64 + +package oomprofile + +type machVMRegionBasicInfoData struct { + Protection int32 + MaxProtection int32 + Inheritance uint32 + Shared int32 + Reserved int32 + Offset [8]byte + Behavior int32 + UserWiredCount uint16 + PadCgo1 [2]byte +} + +const ( + _VM_PROT_READ = 0x1 + _VM_PROT_EXECUTE = 0x4 + + _MACH_SEND_INVALID_DEST = 0x10000003 + + _MAXPATHLEN = 0x400 +) diff --git a/experimental/libbox/internal/oomprofile/linkname.go b/experimental/libbox/internal/oomprofile/linkname.go new file mode 100644 index 0000000000..f7ab271798 --- /dev/null +++ b/experimental/libbox/internal/oomprofile/linkname.go @@ -0,0 +1,46 @@ +//go:build darwin || linux || windows + +package oomprofile + +import ( + "runtime" + _ "runtime/pprof" + "unsafe" + _ "unsafe" +) + +//go:linkname runtimeMemProfileInternal runtime.pprof_memProfileInternal +func runtimeMemProfileInternal(p []memProfileRecord, inuseZero bool) (n int, ok bool) + +//go:linkname runtimeBlockProfileInternal runtime.pprof_blockProfileInternal +func runtimeBlockProfileInternal(p []blockProfileRecord) (n int, ok bool) + +//go:linkname runtimeMutexProfileInternal runtime.pprof_mutexProfileInternal +func runtimeMutexProfileInternal(p []blockProfileRecord) (n int, ok bool) + +//go:linkname runtimeThreadCreateInternal runtime.pprof_threadCreateInternal +func runtimeThreadCreateInternal(p []stackRecord) (n int, ok bool) + +//go:linkname runtimeGoroutineProfileWithLabels runtime.pprof_goroutineProfileWithLabels +func runtimeGoroutineProfileWithLabels(p []stackRecord, labels []unsafe.Pointer) (n int, ok bool) + +//go:linkname runtimeCyclesPerSecond runtime/pprof.runtime_cyclesPerSecond +func runtimeCyclesPerSecond() int64 + +//go:linkname runtimeMakeProfStack runtime.pprof_makeProfStack +func runtimeMakeProfStack() []uintptr + +//go:linkname runtimeFrameStartLine runtime/pprof.runtime_FrameStartLine +func runtimeFrameStartLine(f *runtime.Frame) int + +//go:linkname runtimeFrameSymbolName runtime/pprof.runtime_FrameSymbolName +func runtimeFrameSymbolName(f *runtime.Frame) string + +//go:linkname runtimeExpandFinalInlineFrame runtime/pprof.runtime_expandFinalInlineFrame +func runtimeExpandFinalInlineFrame(stk []uintptr) []uintptr + +//go:linkname stdParseProcSelfMaps runtime/pprof.parseProcSelfMaps +func stdParseProcSelfMaps(data []byte, addMapping func(lo uint64, hi uint64, offset uint64, file string, buildID string)) + +//go:linkname stdELFBuildID runtime/pprof.elfBuildID +func stdELFBuildID(file string) (string, error) diff --git a/experimental/libbox/internal/oomprofile/mapping_darwin.go b/experimental/libbox/internal/oomprofile/mapping_darwin.go new file mode 100644 index 0000000000..a686a04857 --- /dev/null +++ b/experimental/libbox/internal/oomprofile/mapping_darwin.go @@ -0,0 +1,59 @@ +//go:build darwin + +package oomprofile + +import ( + "encoding/binary" + "os" + "unsafe" + _ "unsafe" +) + +func isExecutable(protection int32) bool { + return (protection&_VM_PROT_EXECUTE) != 0 && (protection&_VM_PROT_READ) != 0 +} + +func (b *profileBuilder) readMapping() { + added := machVMInfo(func(lo, hi, offset uint64, file, buildID string) { + b.addMappingEntry(lo, hi, offset, file, buildID, false) + }) + if !added { + b.addMappingEntry(0, 0, 0, "", "", true) + } +} + +func machVMInfo(addMapping func(lo uint64, hi uint64, off uint64, file string, buildID string)) bool { + added := false + addr := uint64(0x1) + for { + var regionSize uint64 + var info machVMRegionBasicInfoData + kr := machVMRegion(&addr, ®ionSize, unsafe.Pointer(&info)) + if kr != 0 { + if kr == _MACH_SEND_INVALID_DEST { + return true + } + return added + } + if isExecutable(info.Protection) { + addMapping(addr, addr+regionSize, binary.LittleEndian.Uint64(info.Offset[:]), regionFilename(addr), "") + added = true + } + addr += regionSize + } +} + +func regionFilename(address uint64) string { + buf := make([]byte, _MAXPATHLEN) + n := procRegionFilename(os.Getpid(), address, unsafe.SliceData(buf), int64(cap(buf))) + if n == 0 { + return "" + } + return string(buf[:n]) +} + +//go:linkname machVMRegion runtime/pprof.mach_vm_region +func machVMRegion(address *uint64, regionSize *uint64, info unsafe.Pointer) int32 + +//go:linkname procRegionFilename runtime/pprof.proc_regionfilename +func procRegionFilename(pid int, address uint64, buf *byte, buflen int64) int32 diff --git a/experimental/libbox/internal/oomprofile/mapping_linux.go b/experimental/libbox/internal/oomprofile/mapping_linux.go new file mode 100644 index 0000000000..ddb2eaaaba --- /dev/null +++ b/experimental/libbox/internal/oomprofile/mapping_linux.go @@ -0,0 +1,15 @@ +//go:build linux + +package oomprofile + +import "os" + +func (b *profileBuilder) readMapping() { + data, _ := os.ReadFile("/proc/self/maps") + stdParseProcSelfMaps(data, func(lo, hi, offset uint64, file, buildID string) { + b.addMappingEntry(lo, hi, offset, file, buildID, false) + }) + if len(b.mem) == 0 { + b.addMappingEntry(0, 0, 0, "", "", true) + } +} diff --git a/experimental/libbox/internal/oomprofile/mapping_windows.go b/experimental/libbox/internal/oomprofile/mapping_windows.go new file mode 100644 index 0000000000..68303d895d --- /dev/null +++ b/experimental/libbox/internal/oomprofile/mapping_windows.go @@ -0,0 +1,58 @@ +//go:build windows + +package oomprofile + +import ( + "errors" + "os" + + "golang.org/x/sys/windows" +) + +func (b *profileBuilder) readMapping() { + snapshot, err := createModuleSnapshot() + if err != nil { + b.addMappingEntry(0, 0, 0, "", "", true) + return + } + defer windows.CloseHandle(snapshot) + + var module windows.ModuleEntry32 + module.Size = uint32(windows.SizeofModuleEntry32) + err = windows.Module32First(snapshot, &module) + if err != nil { + b.addMappingEntry(0, 0, 0, "", "", true) + return + } + for err == nil { + exe := windows.UTF16ToString(module.ExePath[:]) + b.addMappingEntry( + uint64(module.ModBaseAddr), + uint64(module.ModBaseAddr)+uint64(module.ModBaseSize), + 0, + exe, + peBuildID(exe), + false, + ) + err = windows.Module32Next(snapshot, &module) + } +} + +func createModuleSnapshot() (windows.Handle, error) { + for { + snapshot, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE|windows.TH32CS_SNAPMODULE32, uint32(windows.GetCurrentProcessId())) + var errno windows.Errno + if err != nil && errors.As(err, &errno) && errno == windows.ERROR_BAD_LENGTH { + continue + } + return snapshot, err + } +} + +func peBuildID(file string) string { + info, err := os.Stat(file) + if err != nil { + return file + } + return file + info.ModTime().String() +} diff --git a/experimental/libbox/internal/oomprofile/oomprofile.go b/experimental/libbox/internal/oomprofile/oomprofile.go new file mode 100644 index 0000000000..0728af8f47 --- /dev/null +++ b/experimental/libbox/internal/oomprofile/oomprofile.go @@ -0,0 +1,383 @@ +//go:build darwin || linux || windows + +package oomprofile + +import ( + "fmt" + "io" + "math" + "os" + "path/filepath" + "runtime" + "sort" + "strings" + "time" + "unsafe" +) + +type stackRecord struct { + Stack []uintptr +} + +type memProfileRecord struct { + AllocBytes, FreeBytes int64 + AllocObjects, FreeObjects int64 + Stack []uintptr +} + +func (r *memProfileRecord) InUseBytes() int64 { + return r.AllocBytes - r.FreeBytes +} + +func (r *memProfileRecord) InUseObjects() int64 { + return r.AllocObjects - r.FreeObjects +} + +type blockProfileRecord struct { + Count int64 + Cycles int64 + Stack []uintptr +} + +type label struct { + key string + value string +} + +type labelSet struct { + list []label +} + +type labelMap struct { + labelSet +} + +func WriteFile(destPath string, name string) (string, error) { + writer, ok := profileWriters[name] + if !ok { + return "", fmt.Errorf("unsupported profile %q", name) + } + + filePath := filepath.Join(destPath, name+".pb") + file, err := os.Create(filePath) + if err != nil { + return "", err + } + defer file.Close() + + if err := writer(file); err != nil { + _ = os.Remove(filePath) + return "", err + } + if err := file.Close(); err != nil { + _ = os.Remove(filePath) + return "", err + } + return filePath, nil +} + +var profileWriters = map[string]func(io.Writer) error{ + "allocs": writeAlloc, + "block": writeBlock, + "goroutine": writeGoroutine, + "heap": writeHeap, + "mutex": writeMutex, + "threadcreate": writeThreadCreate, +} + +func writeHeap(w io.Writer) error { + return writeHeapInternal(w, "") +} + +func writeAlloc(w io.Writer) error { + return writeHeapInternal(w, "alloc_space") +} + +func writeHeapInternal(w io.Writer, defaultSampleType string) error { + var profile []memProfileRecord + n, _ := runtimeMemProfileInternal(nil, true) + var ok bool + for { + profile = make([]memProfileRecord, n+50) + n, ok = runtimeMemProfileInternal(profile, true) + if ok { + profile = profile[:n] + break + } + } + return writeHeapProto(w, profile, int64(runtime.MemProfileRate), defaultSampleType) +} + +func writeGoroutine(w io.Writer) error { + return writeRuntimeProfile(w, "goroutine", runtimeGoroutineProfileWithLabels) +} + +func writeThreadCreate(w io.Writer) error { + return writeRuntimeProfile(w, "threadcreate", func(p []stackRecord, _ []unsafe.Pointer) (int, bool) { + return runtimeThreadCreateInternal(p) + }) +} + +func writeRuntimeProfile(w io.Writer, name string, fetch func([]stackRecord, []unsafe.Pointer) (int, bool)) error { + var profile []stackRecord + var labels []unsafe.Pointer + + n, _ := fetch(nil, nil) + var ok bool + for { + profile = make([]stackRecord, n+10) + labels = make([]unsafe.Pointer, n+10) + n, ok = fetch(profile, labels) + if ok { + profile = profile[:n] + labels = labels[:n] + break + } + } + + return writeCountProfile(w, name, &runtimeProfile{profile, labels}) +} + +func writeBlock(w io.Writer) error { + return writeCycleProfile(w, "contentions", "delay", runtimeBlockProfileInternal) +} + +func writeMutex(w io.Writer) error { + return writeCycleProfile(w, "contentions", "delay", runtimeMutexProfileInternal) +} + +func writeCycleProfile(w io.Writer, countName string, cycleName string, fetch func([]blockProfileRecord) (int, bool)) error { + var profile []blockProfileRecord + n, _ := fetch(nil) + var ok bool + for { + profile = make([]blockProfileRecord, n+50) + n, ok = fetch(profile) + if ok { + profile = profile[:n] + break + } + } + + sort.Slice(profile, func(i, j int) bool { + return profile[i].Cycles > profile[j].Cycles + }) + + builder := newProfileBuilder(w) + builder.pbValueType(tagProfile_PeriodType, countName, "count") + builder.pb.int64Opt(tagProfile_Period, 1) + builder.pbValueType(tagProfile_SampleType, countName, "count") + builder.pbValueType(tagProfile_SampleType, cycleName, "nanoseconds") + + cpuGHz := float64(runtimeCyclesPerSecond()) / 1e9 + values := []int64{0, 0} + var locs []uint64 + expandedStack := runtimeMakeProfStack() + for _, record := range profile { + values[0] = record.Count + if cpuGHz > 0 { + values[1] = int64(float64(record.Cycles) / cpuGHz) + } else { + values[1] = 0 + } + n := expandInlinedFrames(expandedStack, record.Stack) + locs = builder.appendLocsForStack(locs[:0], expandedStack[:n]) + builder.pbSample(values, locs, nil) + } + + return builder.build() +} + +type countProfile interface { + Len() int + Stack(i int) []uintptr + Label(i int) *labelMap +} + +type runtimeProfile struct { + stk []stackRecord + labels []unsafe.Pointer +} + +func (p *runtimeProfile) Len() int { + return len(p.stk) +} + +func (p *runtimeProfile) Stack(i int) []uintptr { + return p.stk[i].Stack +} + +func (p *runtimeProfile) Label(i int) *labelMap { + return (*labelMap)(p.labels[i]) +} + +func writeCountProfile(w io.Writer, name string, profile countProfile) error { + var buf strings.Builder + key := func(stk []uintptr, labels *labelMap) string { + buf.Reset() + buf.WriteByte('@') + for _, pc := range stk { + fmt.Fprintf(&buf, " %#x", pc) + } + if labels != nil { + buf.WriteString("\n# labels:") + for _, label := range labels.list { + fmt.Fprintf(&buf, " %q:%q", label.key, label.value) + } + } + return buf.String() + } + + counts := make(map[string]int) + index := make(map[string]int) + var keys []string + for i := 0; i < profile.Len(); i++ { + k := key(profile.Stack(i), profile.Label(i)) + if counts[k] == 0 { + index[k] = i + keys = append(keys, k) + } + counts[k]++ + } + + sort.Sort(&keysByCount{keys: keys, count: counts}) + + builder := newProfileBuilder(w) + builder.pbValueType(tagProfile_PeriodType, name, "count") + builder.pb.int64Opt(tagProfile_Period, 1) + builder.pbValueType(tagProfile_SampleType, name, "count") + + values := []int64{0} + var locs []uint64 + for _, k := range keys { + values[0] = int64(counts[k]) + idx := index[k] + locs = builder.appendLocsForStack(locs[:0], profile.Stack(idx)) + + var labels func() + if profile.Label(idx) != nil { + labels = func() { + for _, label := range profile.Label(idx).list { + builder.pbLabel(tagSample_Label, label.key, label.value, 0) + } + } + } + builder.pbSample(values, locs, labels) + } + + return builder.build() +} + +type keysByCount struct { + keys []string + count map[string]int +} + +func (x *keysByCount) Len() int { + return len(x.keys) +} + +func (x *keysByCount) Swap(i int, j int) { + x.keys[i], x.keys[j] = x.keys[j], x.keys[i] +} + +func (x *keysByCount) Less(i int, j int) bool { + ki, kj := x.keys[i], x.keys[j] + ci, cj := x.count[ki], x.count[kj] + if ci != cj { + return ci > cj + } + return ki < kj +} + +func expandInlinedFrames(dst []uintptr, pcs []uintptr) int { + frames := runtime.CallersFrames(pcs) + var n int + for n < len(dst) { + frame, more := frames.Next() + dst[n] = frame.PC + 1 + n++ + if !more { + break + } + } + return n +} + +func writeHeapProto(w io.Writer, profile []memProfileRecord, rate int64, defaultSampleType string) error { + builder := newProfileBuilder(w) + builder.pbValueType(tagProfile_PeriodType, "space", "bytes") + builder.pb.int64Opt(tagProfile_Period, rate) + builder.pbValueType(tagProfile_SampleType, "alloc_objects", "count") + builder.pbValueType(tagProfile_SampleType, "alloc_space", "bytes") + builder.pbValueType(tagProfile_SampleType, "inuse_objects", "count") + builder.pbValueType(tagProfile_SampleType, "inuse_space", "bytes") + if defaultSampleType != "" { + builder.pb.int64Opt(tagProfile_DefaultSampleType, builder.stringIndex(defaultSampleType)) + } + + values := []int64{0, 0, 0, 0} + var locs []uint64 + for _, record := range profile { + hideRuntime := true + for range 2 { + stk := record.Stack + if hideRuntime { + for i, addr := range stk { + if f := runtime.FuncForPC(addr); f != nil && (strings.HasPrefix(f.Name(), "runtime.") || strings.HasPrefix(f.Name(), "internal/runtime/")) { + continue + } + stk = stk[i:] + break + } + } + locs = builder.appendLocsForStack(locs[:0], stk) + if len(locs) > 0 { + break + } + hideRuntime = false + } + + values[0], values[1] = scaleHeapSample(record.AllocObjects, record.AllocBytes, rate) + values[2], values[3] = scaleHeapSample(record.InUseObjects(), record.InUseBytes(), rate) + + var blockSize int64 + if record.AllocObjects > 0 { + blockSize = record.AllocBytes / record.AllocObjects + } + builder.pbSample(values, locs, func() { + if blockSize != 0 { + builder.pbLabel(tagSample_Label, "bytes", "", blockSize) + } + }) + } + + return builder.build() +} + +func scaleHeapSample(count int64, size int64, rate int64) (int64, int64) { + if count == 0 || size == 0 { + return 0, 0 + } + if rate <= 1 { + return count, size + } + + avgSize := float64(size) / float64(count) + scale := 1 / (1 - math.Exp(-avgSize/float64(rate))) + return int64(float64(count) * scale), int64(float64(size) * scale) +} + +type profileBuilder struct { + start time.Time + w io.Writer + err error + + pb protobuf + strings []string + stringMap map[string]int + locs map[uintptr]locInfo + funcs map[string]int + mem []memMap + deck pcDeck +} diff --git a/experimental/libbox/internal/oomprofile/protobuf.go b/experimental/libbox/internal/oomprofile/protobuf.go new file mode 100644 index 0000000000..ed60df21c2 --- /dev/null +++ b/experimental/libbox/internal/oomprofile/protobuf.go @@ -0,0 +1,120 @@ +//go:build darwin || linux || windows + +package oomprofile + +type protobuf struct { + data []byte + tmp [16]byte + nest int +} + +func (b *protobuf) varint(x uint64) { + for x >= 128 { + b.data = append(b.data, byte(x)|0x80) + x >>= 7 + } + b.data = append(b.data, byte(x)) +} + +func (b *protobuf) length(tag int, length int) { + b.varint(uint64(tag)<<3 | 2) + b.varint(uint64(length)) +} + +func (b *protobuf) uint64(tag int, x uint64) { + b.varint(uint64(tag) << 3) + b.varint(x) +} + +func (b *protobuf) uint64s(tag int, x []uint64) { + if len(x) > 2 { + n1 := len(b.data) + for _, u := range x { + b.varint(u) + } + n2 := len(b.data) + b.length(tag, n2-n1) + n3 := len(b.data) + copy(b.tmp[:], b.data[n2:n3]) + copy(b.data[n1+(n3-n2):], b.data[n1:n2]) + copy(b.data[n1:], b.tmp[:n3-n2]) + return + } + for _, u := range x { + b.uint64(tag, u) + } +} + +func (b *protobuf) uint64Opt(tag int, x uint64) { + if x == 0 { + return + } + b.uint64(tag, x) +} + +func (b *protobuf) int64(tag int, x int64) { + b.uint64(tag, uint64(x)) +} + +func (b *protobuf) int64Opt(tag int, x int64) { + if x == 0 { + return + } + b.int64(tag, x) +} + +func (b *protobuf) int64s(tag int, x []int64) { + if len(x) > 2 { + n1 := len(b.data) + for _, u := range x { + b.varint(uint64(u)) + } + n2 := len(b.data) + b.length(tag, n2-n1) + n3 := len(b.data) + copy(b.tmp[:], b.data[n2:n3]) + copy(b.data[n1+(n3-n2):], b.data[n1:n2]) + copy(b.data[n1:], b.tmp[:n3-n2]) + return + } + for _, u := range x { + b.int64(tag, u) + } +} + +func (b *protobuf) bool(tag int, x bool) { + if x { + b.uint64(tag, 1) + } else { + b.uint64(tag, 0) + } +} + +func (b *protobuf) string(tag int, x string) { + b.length(tag, len(x)) + b.data = append(b.data, x...) +} + +func (b *protobuf) strings(tag int, x []string) { + for _, s := range x { + b.string(tag, s) + } +} + +type msgOffset int + +func (b *protobuf) startMessage() msgOffset { + b.nest++ + return msgOffset(len(b.data)) +} + +func (b *protobuf) endMessage(tag int, start msgOffset) { + n1 := int(start) + n2 := len(b.data) + b.length(tag, n2-n1) + n3 := len(b.data) + copy(b.tmp[:], b.data[n2:n3]) + copy(b.data[n1+(n3-n2):], b.data[n1:n2]) + copy(b.data[n1:], b.tmp[:n3-n2]) + b.nest-- +} diff --git a/experimental/libbox/iterator.go b/experimental/libbox/iterator.go index db64a25978..32cbbddb21 100644 --- a/experimental/libbox/iterator.go +++ b/experimental/libbox/iterator.go @@ -3,8 +3,15 @@ package libbox import "github.com/sagernet/sing/common" type StringIterator interface { + Len() int32 + HasNext() bool Next() string +} + +type Int32Iterator interface { + Len() int32 HasNext() bool + Next() int32 } var _ StringIterator = (*iterator[string])(nil) @@ -17,6 +24,19 @@ func newIterator[T any](values []T) *iterator[T] { return &iterator[T]{values} } +//go:noinline +func newPtrIterator[T any](values []T) *iterator[*T] { + return &iterator[*T]{common.Map(values, func(value T) *T { return &value })} +} + +func (i *iterator[T]) Len() int32 { + return int32(len(i.values)) +} + +func (i *iterator[T]) HasNext() bool { + return len(i.values) > 0 +} + func (i *iterator[T]) Next() T { if len(i.values) == 0 { return common.DefaultValue[T]() @@ -26,10 +46,6 @@ func (i *iterator[T]) Next() T { return nextValue } -func (i *iterator[T]) HasNext() bool { - return len(i.values) > 0 -} - type abstractIterator[T any] interface { Next() T HasNext() bool diff --git a/experimental/libbox/link_flags_stub.go b/experimental/libbox/link_flags_stub.go new file mode 100644 index 0000000000..ce3d1eb805 --- /dev/null +++ b/experimental/libbox/link_flags_stub.go @@ -0,0 +1,11 @@ +//go:build !unix + +package libbox + +import ( + "net" +) + +func linkFlags(rawFlags uint32) net.Flags { + panic("stub!") +} diff --git a/experimental/libbox/link_flags_unix.go b/experimental/libbox/link_flags_unix.go new file mode 100644 index 0000000000..04f41d641f --- /dev/null +++ b/experimental/libbox/link_flags_unix.go @@ -0,0 +1,32 @@ +//go:build unix + +package libbox + +import ( + "net" + "syscall" +) + +// copied from net.linkFlags +func linkFlags(rawFlags uint32) net.Flags { + var f net.Flags + if rawFlags&syscall.IFF_UP != 0 { + f |= net.FlagUp + } + if rawFlags&syscall.IFF_RUNNING != 0 { + f |= net.FlagRunning + } + if rawFlags&syscall.IFF_BROADCAST != 0 { + f |= net.FlagBroadcast + } + if rawFlags&syscall.IFF_LOOPBACK != 0 { + f |= net.FlagLoopback + } + if rawFlags&syscall.IFF_POINTOPOINT != 0 { + f |= net.FlagPointToPoint + } + if rawFlags&syscall.IFF_MULTICAST != 0 { + f |= net.FlagMulticast + } + return f +} diff --git a/experimental/libbox/log.go b/experimental/libbox/log.go index d9c4c712b9..9a3ebf707f 100644 --- a/experimental/libbox/log.go +++ b/experimental/libbox/log.go @@ -1,25 +1,80 @@ -//go:build darwin || linux +//go:build darwin || linux || windows package libbox import ( + "archive/zip" + "io" + "io/fs" "os" + "path/filepath" "runtime" - - "golang.org/x/sys/unix" + "runtime/debug" + "time" ) -var stderrFile *os.File +type crashReportMetadata struct { + reportMetadata + CrashedAt string `json:"crashedAt,omitempty"` + SignalName string `json:"signalName,omitempty"` + SignalCode string `json:"signalCode,omitempty"` + ExceptionName string `json:"exceptionName,omitempty"` + ExceptionReason string `json:"exceptionReason,omitempty"` +} -func RedirectStderr(path string) error { - if stats, err := os.Stat(path); err == nil && stats.Size() > 0 { - _ = os.Rename(path, path+".old") +func archiveCrashReport(path string, crashReportsDir string) { + content, err := os.ReadFile(path) + if err != nil || len(content) == 0 { + return + } + + info, _ := os.Stat(path) + crashTime := time.Now().UTC() + if info != nil { + crashTime = info.ModTime().UTC() } + + initReportDir(crashReportsDir) + destPath, err := nextAvailableReportPath(crashReportsDir, crashTime) + if err != nil { + return + } + initReportDir(destPath) + + writeReportFile(destPath, "go.log", content) + metadata := crashReportMetadata{ + reportMetadata: baseReportMetadata(), + CrashedAt: crashTime.Format(time.RFC3339), + } + writeReportMetadata(destPath, metadata) + os.Remove(path) + copyConfigSnapshot(destPath) +} + +func configSnapshotPath() string { + return filepath.Join(sBasePath, "configuration.json") +} + +func saveConfigSnapshot(configContent string) { + snapshotPath := configSnapshotPath() + os.WriteFile(snapshotPath, []byte(configContent), 0o666) + chownReport(snapshotPath) +} + +func RedirectStderr(path string) error { + return redirectStderr(path) +} + +func redirectStderr(path string) error { + crashReportsDir := filepath.Join(sWorkingPath, "crash_reports") + archiveCrashReport(path, crashReportsDir) + archiveCrashReport(path+".old", crashReportsDir) + outputFile, err := os.Create(path) if err != nil { return err } - if runtime.GOOS != "android" { + if runtime.GOOS != "android" && runtime.GOOS != "windows" { err = outputFile.Chown(sUserID, sGroupID) if err != nil { outputFile.Close() @@ -27,12 +82,88 @@ func RedirectStderr(path string) error { return err } } - err = unix.Dup2(int(outputFile.Fd()), int(os.Stderr.Fd())) + + err = debug.SetCrashOutput(outputFile, debug.CrashOptions{}) if err != nil { outputFile.Close() os.Remove(outputFile.Name()) return err } - stderrFile = outputFile + _ = outputFile.Close() return nil } + +func CreateZipArchive(sourcePath string, destinationPath string) error { + sourceInfo, err := os.Stat(sourcePath) + if err != nil { + return err + } + if !sourceInfo.IsDir() { + return os.ErrInvalid + } + + destinationFile, err := os.Create(destinationPath) + if err != nil { + return err + } + defer func() { + _ = destinationFile.Close() + }() + + zipWriter := zip.NewWriter(destinationFile) + + rootName := filepath.Base(sourcePath) + err = filepath.WalkDir(sourcePath, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + relativePath, err := filepath.Rel(sourcePath, path) + if err != nil { + return err + } + if relativePath == "." { + return nil + } + + archivePath := filepath.ToSlash(filepath.Join(rootName, relativePath)) + if d.IsDir() { + _, err = zipWriter.Create(archivePath + "/") + return err + } + + fileInfo, err := d.Info() + if err != nil { + return err + } + header, err := zip.FileInfoHeader(fileInfo) + if err != nil { + return err + } + header.Name = archivePath + header.Method = zip.Deflate + + writer, err := zipWriter.CreateHeader(header) + if err != nil { + return err + } + + sourceFile, err := os.Open(path) + if err != nil { + return err + } + + _, err = io.Copy(writer, sourceFile) + closeErr := sourceFile.Close() + if err != nil { + return err + } + return closeErr + }) + if err != nil { + _ = zipWriter.Close() + return err + } + + return zipWriter.Close() +} diff --git a/experimental/libbox/memory.go b/experimental/libbox/memory.go deleted file mode 100644 index b10c670143..0000000000 --- a/experimental/libbox/memory.go +++ /dev/null @@ -1,23 +0,0 @@ -package libbox - -import ( - "math" - runtimeDebug "runtime/debug" - - "github.com/sagernet/sing-box/common/conntrack" -) - -func SetMemoryLimit(enabled bool) { - const memoryLimit = 45 * 1024 * 1024 - const memoryLimitGo = memoryLimit / 1.5 - if enabled { - runtimeDebug.SetGCPercent(10) - runtimeDebug.SetMemoryLimit(memoryLimitGo) - conntrack.KillerEnabled = true - conntrack.MemoryLimit = memoryLimit - } else { - runtimeDebug.SetGCPercent(100) - runtimeDebug.SetMemoryLimit(math.MaxInt64) - conntrack.KillerEnabled = false - } -} diff --git a/experimental/libbox/monitor.go b/experimental/libbox/monitor.go index 60fc2b8e75..62f916130b 100644 --- a/experimental/libbox/monitor.go +++ b/experimental/libbox/monitor.go @@ -1,15 +1,10 @@ package libbox import ( - "net" - "net/netip" - "sync" - - "github.com/sagernet/sing-tun" - "github.com/sagernet/sing/common" + tun "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" - M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/x/list" ) @@ -20,19 +15,9 @@ var ( type platformDefaultInterfaceMonitor struct { *platformInterfaceWrapper - networkAddresses []networkAddress - defaultInterfaceName string - defaultInterfaceIndex int - element *list.Element[tun.NetworkUpdateCallback] - access sync.Mutex - callbacks list.List[tun.DefaultInterfaceUpdateCallback] - logger logger.Logger -} - -type networkAddress struct { - interfaceName string - interfaceIndex int - addresses []netip.Prefix + logger logger.Logger + callbacks list.List[tun.DefaultInterfaceUpdateCallback] + myInterface string } func (m *platformDefaultInterfaceMonitor) Start() error { @@ -43,37 +28,10 @@ func (m *platformDefaultInterfaceMonitor) Close() error { return m.iif.CloseDefaultInterfaceMonitor(m) } -func (m *platformDefaultInterfaceMonitor) DefaultInterfaceName(destination netip.Addr) string { - for _, address := range m.networkAddresses { - for _, prefix := range address.addresses { - if prefix.Contains(destination) { - return address.interfaceName - } - } - } - return m.defaultInterfaceName -} - -func (m *platformDefaultInterfaceMonitor) DefaultInterfaceIndex(destination netip.Addr) int { - for _, address := range m.networkAddresses { - for _, prefix := range address.addresses { - if prefix.Contains(destination) { - return address.interfaceIndex - } - } - } - return m.defaultInterfaceIndex -} - -func (m *platformDefaultInterfaceMonitor) DefaultInterface(destination netip.Addr) (string, int) { - for _, address := range m.networkAddresses { - for _, prefix := range address.addresses { - if prefix.Contains(destination) { - return address.interfaceName, address.interfaceIndex - } - } - } - return m.defaultInterfaceName, m.defaultInterfaceIndex +func (m *platformDefaultInterfaceMonitor) DefaultInterface() *control.Interface { + m.defaultInterfaceAccess.Lock() + defer m.defaultInterfaceAccess.Unlock() + return m.defaultInterface } func (m *platformDefaultInterfaceMonitor) OverrideAndroidVPN() bool { @@ -85,96 +43,74 @@ func (m *platformDefaultInterfaceMonitor) AndroidVPNEnabled() bool { } func (m *platformDefaultInterfaceMonitor) RegisterCallback(callback tun.DefaultInterfaceUpdateCallback) *list.Element[tun.DefaultInterfaceUpdateCallback] { - m.access.Lock() - defer m.access.Unlock() + m.defaultInterfaceAccess.Lock() + defer m.defaultInterfaceAccess.Unlock() return m.callbacks.PushBack(callback) } func (m *platformDefaultInterfaceMonitor) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) { - m.access.Lock() - defer m.access.Unlock() + m.defaultInterfaceAccess.Lock() + defer m.defaultInterfaceAccess.Unlock() m.callbacks.Remove(element) } -func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32) { - if interfaceName == "" || interfaceIndex32 == -1 { - m.defaultInterfaceName = "" - m.defaultInterfaceIndex = -1 - m.access.Lock() +func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32, isExpensive bool, isConstrained bool) { + if sFixAndroidStack { + done := make(chan struct{}) + go func() { + m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained) + close(done) + }() + <-done + } else { + m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained) + } +} + +func (m *platformDefaultInterfaceMonitor) updateDefaultInterface(interfaceName string, interfaceIndex32 int32, isExpensive bool, isConstrained bool) { + m.isExpensive = isExpensive + m.isConstrained = isConstrained + err := m.networkManager.UpdateInterfaces() + if err != nil { + m.logger.Error(E.Cause(err, "update interfaces")) + } + m.defaultInterfaceAccess.Lock() + if interfaceIndex32 == -1 { + m.defaultInterface = nil callbacks := m.callbacks.Array() - m.access.Unlock() + m.defaultInterfaceAccess.Unlock() for _, callback := range callbacks { - callback(tun.EventNoRoute) + callback(nil, 0) } return } - var err error - if m.iif.UsePlatformInterfaceGetter() { - err = m.updateInterfacesPlatform() - } else { - err = m.updateInterfaces() - } - if err == nil { - err = m.router.UpdateInterfaces() - } + oldInterface := m.defaultInterface + newInterface, err := m.networkManager.InterfaceFinder().ByIndex(int(interfaceIndex32)) if err != nil { - m.logger.Error(E.Cause(err, "update interfaces")) + m.defaultInterfaceAccess.Unlock() + m.logger.Error(E.Cause(err, "find updated interface: ", interfaceName)) + return } - interfaceIndex := int(interfaceIndex32) - if m.defaultInterfaceName == interfaceName && m.defaultInterfaceIndex == interfaceIndex { + m.defaultInterface = newInterface + if oldInterface != nil && oldInterface.Name == m.defaultInterface.Name && oldInterface.Index == m.defaultInterface.Index { + m.defaultInterfaceAccess.Unlock() return } - m.defaultInterfaceName = interfaceName - m.defaultInterfaceIndex = interfaceIndex - m.access.Lock() callbacks := m.callbacks.Array() - m.access.Unlock() + m.defaultInterfaceAccess.Unlock() for _, callback := range callbacks { - callback(tun.EventInterfaceUpdate) + callback(newInterface, 0) } } -func (m *platformDefaultInterfaceMonitor) updateInterfaces() error { - interfaces, err := net.Interfaces() - if err != nil { - return err - } - var addresses []networkAddress - for _, iif := range interfaces { - var netAddresses []net.Addr - netAddresses, err = iif.Addrs() - if err != nil { - return err - } - var address networkAddress - address.interfaceName = iif.Name - address.interfaceIndex = iif.Index - address.addresses = common.Map(common.FilterIsInstance(netAddresses, func(it net.Addr) (*net.IPNet, bool) { - value, loaded := it.(*net.IPNet) - return value, loaded - }), func(it *net.IPNet) netip.Prefix { - bits, _ := it.Mask.Size() - return netip.PrefixFrom(M.AddrFromIP(it.IP), bits) - }) - addresses = append(addresses, address) - } - m.networkAddresses = addresses - return nil +func (m *platformDefaultInterfaceMonitor) RegisterMyInterface(interfaceName string) { + m.defaultInterfaceAccess.Lock() + defer m.defaultInterfaceAccess.Unlock() + m.myInterface = interfaceName } -func (m *platformDefaultInterfaceMonitor) updateInterfacesPlatform() error { - interfaces, err := m.Interfaces() - if err != nil { - return err - } - var addresses []networkAddress - for _, iif := range interfaces { - var address networkAddress - address.interfaceName = iif.Name - address.interfaceIndex = iif.Index - // address.addresses = common.Map(iif.Addresses, netip.MustParsePrefix) - addresses = append(addresses, address) - } - m.networkAddresses = addresses - return nil +func (m *platformDefaultInterfaceMonitor) MyInterface() string { + m.defaultInterfaceAccess.Lock() + defer m.defaultInterfaceAccess.Unlock() + return m.myInterface } diff --git a/experimental/libbox/neighbor.go b/experimental/libbox/neighbor.go new file mode 100644 index 0000000000..b5cbe77962 --- /dev/null +++ b/experimental/libbox/neighbor.go @@ -0,0 +1,20 @@ +package libbox + +type NeighborEntry struct { + Address string + MacAddress string + Hostname string +} + +type NeighborEntryIterator interface { + Next() *NeighborEntry + HasNext() bool +} + +type NeighborSubscription struct { + done chan struct{} +} + +func (s *NeighborSubscription) Close() { + close(s.done) +} diff --git a/experimental/libbox/neighbor_darwin.go b/experimental/libbox/neighbor_darwin.go new file mode 100644 index 0000000000..d7484a69b4 --- /dev/null +++ b/experimental/libbox/neighbor_darwin.go @@ -0,0 +1,123 @@ +//go:build darwin + +package libbox + +import ( + "net" + "net/netip" + "os" + "slices" + "time" + + "github.com/sagernet/sing-box/route" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + + xroute "golang.org/x/net/route" + "golang.org/x/sys/unix" +) + +func SubscribeNeighborTable(listener NeighborUpdateListener) (*NeighborSubscription, error) { + entries, err := route.ReadNeighborEntries() + if err != nil { + return nil, E.Cause(err, "initial neighbor dump") + } + table := make(map[netip.Addr]net.HardwareAddr) + for _, entry := range entries { + table[entry.Address] = entry.MACAddress + } + listener.UpdateNeighborTable(tableToIterator(table)) + routeSocket, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0) + if err != nil { + return nil, E.Cause(err, "open route socket") + } + err = unix.SetNonblock(routeSocket, true) + if err != nil { + unix.Close(routeSocket) + return nil, E.Cause(err, "set route socket nonblock") + } + subscription := &NeighborSubscription{ + done: make(chan struct{}), + } + go subscription.loop(listener, routeSocket, table) + return subscription, nil +} + +func (s *NeighborSubscription) loop(listener NeighborUpdateListener, routeSocket int, table map[netip.Addr]net.HardwareAddr) { + routeSocketFile := os.NewFile(uintptr(routeSocket), "route") + defer routeSocketFile.Close() + buffer := buf.NewPacket() + defer buffer.Release() + for { + select { + case <-s.done: + return + default: + } + tv := unix.NsecToTimeval(int64(3 * time.Second)) + _ = unix.SetsockoptTimeval(routeSocket, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv) + n, err := routeSocketFile.Read(buffer.FreeBytes()) + if err != nil { + if nerr, ok := err.(net.Error); ok && nerr.Timeout() { + continue + } + select { + case <-s.done: + return + default: + } + continue + } + messages, err := xroute.ParseRIB(xroute.RIBTypeRoute, buffer.FreeBytes()[:n]) + if err != nil { + continue + } + changed := false + for _, message := range messages { + routeMessage, isRouteMessage := message.(*xroute.RouteMessage) + if !isRouteMessage { + continue + } + if routeMessage.Flags&unix.RTF_LLINFO == 0 { + continue + } + address, mac, isDelete, ok := route.ParseRouteNeighborMessage(routeMessage) + if !ok { + continue + } + if isDelete { + if _, exists := table[address]; exists { + delete(table, address) + changed = true + } + } else { + existing, exists := table[address] + if !exists || !slices.Equal(existing, mac) { + table[address] = mac + changed = true + } + } + } + if changed { + listener.UpdateNeighborTable(tableToIterator(table)) + } + } +} + +func ReadBootpdLeases() NeighborEntryIterator { + leaseIPToMAC, ipToHostname, macToHostname := route.ReloadLeaseFiles([]string{"/var/db/dhcpd_leases"}) + entries := make([]*NeighborEntry, 0, len(leaseIPToMAC)) + for address, mac := range leaseIPToMAC { + entry := &NeighborEntry{ + Address: address.String(), + MacAddress: mac.String(), + } + hostname, found := ipToHostname[address] + if !found { + hostname = macToHostname[mac.String()] + } + entry.Hostname = hostname + entries = append(entries, entry) + } + return &neighborEntryIterator{entries} +} diff --git a/experimental/libbox/neighbor_linux.go b/experimental/libbox/neighbor_linux.go new file mode 100644 index 0000000000..ae10bdd2ee --- /dev/null +++ b/experimental/libbox/neighbor_linux.go @@ -0,0 +1,88 @@ +//go:build linux + +package libbox + +import ( + "net" + "net/netip" + "slices" + "time" + + "github.com/sagernet/sing-box/route" + E "github.com/sagernet/sing/common/exceptions" + + "github.com/mdlayher/netlink" + "golang.org/x/sys/unix" +) + +func SubscribeNeighborTable(listener NeighborUpdateListener) (*NeighborSubscription, error) { + entries, err := route.ReadNeighborEntries() + if err != nil { + return nil, E.Cause(err, "initial neighbor dump") + } + table := make(map[netip.Addr]net.HardwareAddr) + for _, entry := range entries { + table[entry.Address] = entry.MACAddress + } + listener.UpdateNeighborTable(tableToIterator(table)) + connection, err := netlink.Dial(unix.NETLINK_ROUTE, &netlink.Config{ + Groups: 1 << (unix.RTNLGRP_NEIGH - 1), + }) + if err != nil { + return nil, E.Cause(err, "subscribe neighbor updates") + } + subscription := &NeighborSubscription{ + done: make(chan struct{}), + } + go subscription.loop(listener, connection, table) + return subscription, nil +} + +func (s *NeighborSubscription) loop(listener NeighborUpdateListener, connection *netlink.Conn, table map[netip.Addr]net.HardwareAddr) { + defer connection.Close() + for { + select { + case <-s.done: + return + default: + } + err := connection.SetReadDeadline(time.Now().Add(3 * time.Second)) + if err != nil { + return + } + messages, err := connection.Receive() + if err != nil { + if nerr, ok := err.(net.Error); ok && nerr.Timeout() { + continue + } + select { + case <-s.done: + return + default: + } + continue + } + changed := false + for _, message := range messages { + address, mac, isDelete, ok := route.ParseNeighborMessage(message) + if !ok { + continue + } + if isDelete { + if _, exists := table[address]; exists { + delete(table, address) + changed = true + } + } else { + existing, exists := table[address] + if !exists || !slices.Equal(existing, mac) { + table[address] = mac + changed = true + } + } + } + if changed { + listener.UpdateNeighborTable(tableToIterator(table)) + } + } +} diff --git a/experimental/libbox/neighbor_stub.go b/experimental/libbox/neighbor_stub.go new file mode 100644 index 0000000000..d465bc7bb0 --- /dev/null +++ b/experimental/libbox/neighbor_stub.go @@ -0,0 +1,9 @@ +//go:build !linux && !darwin + +package libbox + +import "os" + +func SubscribeNeighborTable(_ NeighborUpdateListener) (*NeighborSubscription, error) { + return nil, os.ErrInvalid +} diff --git a/experimental/libbox/neighbor_unix.go b/experimental/libbox/neighbor_unix.go new file mode 100644 index 0000000000..c854d0f9a6 --- /dev/null +++ b/experimental/libbox/neighbor_unix.go @@ -0,0 +1,36 @@ +//go:build linux || darwin + +package libbox + +import ( + "net" + "net/netip" +) + +func tableToIterator(table map[netip.Addr]net.HardwareAddr) NeighborEntryIterator { + entries := make([]*NeighborEntry, 0, len(table)) + for address, mac := range table { + entries = append(entries, &NeighborEntry{ + Address: address.String(), + MacAddress: mac.String(), + }) + } + return &neighborEntryIterator{entries} +} + +type neighborEntryIterator struct { + entries []*NeighborEntry +} + +func (i *neighborEntryIterator) HasNext() bool { + return len(i.entries) > 0 +} + +func (i *neighborEntryIterator) Next() *NeighborEntry { + if len(i.entries) == 0 { + return nil + } + entry := i.entries[0] + i.entries = i.entries[1:] + return entry +} diff --git a/experimental/libbox/networkquality.go b/experimental/libbox/networkquality.go new file mode 100644 index 0000000000..fcbe6f3a66 --- /dev/null +++ b/experimental/libbox/networkquality.go @@ -0,0 +1,74 @@ +package libbox + +import ( + "context" + "time" + + "github.com/sagernet/sing-box/common/networkquality" +) + +type NetworkQualityTest struct { + ctx context.Context + cancel context.CancelFunc +} + +func NewNetworkQualityTest() *NetworkQualityTest { + ctx, cancel := context.WithCancel(context.Background()) + return &NetworkQualityTest{ctx: ctx, cancel: cancel} +} + +func (t *NetworkQualityTest) Start(configURL string, serial bool, maxRuntimeSeconds int32, http3 bool, handler NetworkQualityTestHandler) { + go func() { + httpClient := networkquality.NewHTTPClient(nil) + defer httpClient.CloseIdleConnections() + + measurementClientFactory, err := networkquality.NewOptionalHTTP3Factory(nil, http3) + if err != nil { + handler.OnError(err.Error()) + return + } + + result, err := networkquality.Run(networkquality.Options{ + ConfigURL: configURL, + HTTPClient: httpClient, + NewMeasurementClient: measurementClientFactory, + Serial: serial, + MaxRuntime: time.Duration(maxRuntimeSeconds) * time.Second, + Context: t.ctx, + OnProgress: func(p networkquality.Progress) { + handler.OnProgress(&NetworkQualityProgress{ + Phase: int32(p.Phase), + DownloadCapacity: p.DownloadCapacity, + UploadCapacity: p.UploadCapacity, + DownloadRPM: p.DownloadRPM, + UploadRPM: p.UploadRPM, + IdleLatencyMs: p.IdleLatencyMs, + ElapsedMs: p.ElapsedMs, + DownloadCapacityAccuracy: int32(p.DownloadCapacityAccuracy), + UploadCapacityAccuracy: int32(p.UploadCapacityAccuracy), + DownloadRPMAccuracy: int32(p.DownloadRPMAccuracy), + UploadRPMAccuracy: int32(p.UploadRPMAccuracy), + }) + }, + }) + if err != nil { + handler.OnError(err.Error()) + return + } + handler.OnResult(&NetworkQualityResult{ + DownloadCapacity: result.DownloadCapacity, + UploadCapacity: result.UploadCapacity, + DownloadRPM: result.DownloadRPM, + UploadRPM: result.UploadRPM, + IdleLatencyMs: result.IdleLatencyMs, + DownloadCapacityAccuracy: int32(result.DownloadCapacityAccuracy), + UploadCapacityAccuracy: int32(result.UploadCapacityAccuracy), + DownloadRPMAccuracy: int32(result.DownloadRPMAccuracy), + UploadRPMAccuracy: int32(result.UploadRPMAccuracy), + }) + }() +} + +func (t *NetworkQualityTest) Cancel() { + t.cancel() +} diff --git a/experimental/libbox/oom_report.go b/experimental/libbox/oom_report.go new file mode 100644 index 0000000000..64afc4b523 --- /dev/null +++ b/experimental/libbox/oom_report.go @@ -0,0 +1,218 @@ +//go:build darwin || linux || windows + +package libbox + +import ( + "os" + "path/filepath" + "runtime" + "strings" + "time" + + "github.com/sagernet/sing-box/experimental/libbox/internal/oomprofile" + "github.com/sagernet/sing-box/service/oomkiller" + "github.com/sagernet/sing/common/byteformats" + "github.com/sagernet/sing/common/memory" +) + +func init() { + sOOMReporter = &oomReporter{} +} + +var oomReportProfiles = []string{ + "allocs", + "block", + "goroutine", + "heap", + "mutex", + "threadcreate", +} + +type oomReportMetadata struct { + reportMetadata + RecordedAt string `json:"recordedAt"` + MemoryUsage string `json:"memoryUsage"` + AvailableMemory string `json:"availableMemory,omitempty"` + // Heap + HeapAlloc string `json:"heapAlloc,omitempty"` + HeapObjects uint64 `json:"heapObjects,omitempty,string"` + HeapInuse string `json:"heapInuse,omitempty"` + HeapIdle string `json:"heapIdle,omitempty"` + HeapReleased string `json:"heapReleased,omitempty"` + HeapSys string `json:"heapSys,omitempty"` + // Stack + StackInuse string `json:"stackInuse,omitempty"` + StackSys string `json:"stackSys,omitempty"` + // Runtime metadata + MSpanInuse string `json:"mSpanInuse,omitempty"` + MSpanSys string `json:"mSpanSys,omitempty"` + MCacheSys string `json:"mCacheSys,omitempty"` + BuckHashSys string `json:"buckHashSys,omitempty"` + GCSys string `json:"gcSys,omitempty"` + OtherSys string `json:"otherSys,omitempty"` + Sys string `json:"sys,omitempty"` + // GC & runtime + TotalAlloc string `json:"totalAlloc,omitempty"` + NumGC uint32 `json:"numGC,omitempty,string"` + NumGoroutine int `json:"numGoroutine,omitempty,string"` + NextGC string `json:"nextGC,omitempty"` + LastGC string `json:"lastGC,omitempty"` +} + +type oomReporter struct{} + +var _ oomkiller.OOMReporter = (*oomReporter)(nil) + +func (r *oomReporter) WriteReport(memoryUsage uint64) error { + draftPath := filepath.Join(sWorkingPath, "oom_draft") + draftInfo, err := os.Stat(draftPath) + if err != nil { + if !os.IsNotExist(err) { + return err + } + draftInfo = nil + } + reportsDir := filepath.Join(sWorkingPath, "oom_reports") + err = os.MkdirAll(reportsDir, 0o777) + if err != nil { + return err + } + chownReport(reportsDir) + + destPath, err := nextAvailableReportPath(reportsDir, time.Now().UTC()) + if err != nil { + return err + } + err = r.writeSnapshot(destPath, memoryUsage) + if err != nil { + return err + } + return discardDraftIfCurrent(draftPath, draftInfo) +} + +func (r *oomReporter) WriteDraft(memoryUsage uint64) error { + draftPath := filepath.Join(sWorkingPath, "oom_draft") + os.RemoveAll(draftPath) + return r.writeSnapshot(draftPath, memoryUsage) +} + +func (r *oomReporter) DiscardDraft() error { + draftPath := filepath.Join(sWorkingPath, "oom_draft") + return os.RemoveAll(draftPath) +} + +func discardDraftIfCurrent(draftPath string, draftInfo os.FileInfo) error { + if draftInfo == nil { + return nil + } + currentInfo, err := os.Stat(draftPath) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + if !os.SameFile(draftInfo, currentInfo) { + return nil + } + return os.RemoveAll(draftPath) +} + +func (r *oomReporter) writeSnapshot(destPath string, memoryUsage uint64) error { + now := time.Now().UTC() + err := os.MkdirAll(destPath, 0o777) + if err != nil { + return err + } + chownReport(destPath) + + for _, name := range oomReportProfiles { + writeOOMProfile(destPath, name) + } + + writeReportFile(destPath, "cmdline", []byte(strings.Join(os.Args, "\000"))) + + var memStats runtime.MemStats + runtime.ReadMemStats(&memStats) + + metadata := oomReportMetadata{ + reportMetadata: baseReportMetadata(), + RecordedAt: now.Format(time.RFC3339), + MemoryUsage: byteformats.FormatMemoryBytes(memoryUsage), + // Heap + HeapAlloc: byteformats.FormatMemoryBytes(memStats.HeapAlloc), + HeapObjects: memStats.HeapObjects, + HeapInuse: byteformats.FormatMemoryBytes(memStats.HeapInuse), + HeapIdle: byteformats.FormatMemoryBytes(memStats.HeapIdle), + HeapReleased: byteformats.FormatMemoryBytes(memStats.HeapReleased), + HeapSys: byteformats.FormatMemoryBytes(memStats.HeapSys), + // Stack + StackInuse: byteformats.FormatMemoryBytes(memStats.StackInuse), + StackSys: byteformats.FormatMemoryBytes(memStats.StackSys), + // Runtime metadata + MSpanInuse: byteformats.FormatMemoryBytes(memStats.MSpanInuse), + MSpanSys: byteformats.FormatMemoryBytes(memStats.MSpanSys), + MCacheSys: byteformats.FormatMemoryBytes(memStats.MCacheSys), + BuckHashSys: byteformats.FormatMemoryBytes(memStats.BuckHashSys), + GCSys: byteformats.FormatMemoryBytes(memStats.GCSys), + OtherSys: byteformats.FormatMemoryBytes(memStats.OtherSys), + Sys: byteformats.FormatMemoryBytes(memStats.Sys), + // GC & runtime + TotalAlloc: byteformats.FormatMemoryBytes(memStats.TotalAlloc), + NumGC: memStats.NumGC, + NumGoroutine: runtime.NumGoroutine(), + NextGC: byteformats.FormatMemoryBytes(memStats.NextGC), + } + if memStats.LastGC > 0 { + metadata.LastGC = time.Unix(0, int64(memStats.LastGC)).UTC().Format(time.RFC3339) + } + availableMemory := memory.Available() + if availableMemory > 0 { + metadata.AvailableMemory = byteformats.FormatMemoryBytes(availableMemory) + } + writeReportMetadata(destPath, metadata) + copyConfigSnapshot(destPath) + + return nil +} + +func writeOOMProfile(destPath string, name string) { + filePath, err := oomprofile.WriteFile(destPath, name) + if err != nil { + return + } + chownReport(filePath) +} + +func promoteOOMDraftAt(workingPath string) { + draftPath := filepath.Join(workingPath, "oom_draft") + info, err := os.Stat(draftPath) + if err != nil || !info.IsDir() { + return + } + reportsDir := filepath.Join(workingPath, "oom_reports") + initReportDir(reportsDir) + destPath, err := nextAvailableReportPath(reportsDir, info.ModTime().UTC()) + if err != nil { + os.RemoveAll(draftPath) + return + } + err = os.Rename(draftPath, destPath) + if err != nil { + os.RemoveAll(draftPath) + return + } + chownReport(destPath) +} + +func promoteOOMDraft() { + promoteOOMDraftAt(sWorkingPath) +} + +func PromoteOOMDraft() { + promoteOOMDraft() +} + +func PromoteOOMDraftAt(workingPath string) { + promoteOOMDraftAt(workingPath) +} diff --git a/experimental/libbox/panic.go b/experimental/libbox/panic.go new file mode 100644 index 0000000000..62fe132657 --- /dev/null +++ b/experimental/libbox/panic.go @@ -0,0 +1,12 @@ +package libbox + +// https://github.com/golang/go/issues/46893 +// TODO: remove after `bulkBarrierPreWrite: unaligned arguments` fixed + +type StringBox struct { + Value string +} + +func wrapString(value string) *StringBox { + return &StringBox{Value: value} +} diff --git a/experimental/libbox/pidfd_android.go b/experimental/libbox/pidfd_android.go new file mode 100644 index 0000000000..cc7cdd7a00 --- /dev/null +++ b/experimental/libbox/pidfd_android.go @@ -0,0 +1,19 @@ +package libbox + +import ( + "os" + _ "unsafe" +) + +// https://github.com/SagerNet/sing-box/issues/3233 +// https://github.com/golang/go/issues/70508 +// https://github.com/tailscale/tailscale/issues/13452 + +//go:linkname checkPidfdOnce os.checkPidfdOnce +var checkPidfdOnce func() error + +func init() { + checkPidfdOnce = func() error { + return os.ErrInvalid + } +} diff --git a/experimental/libbox/platform.go b/experimental/libbox/platform.go index 4078140f8c..cd364862d9 100644 --- a/experimental/libbox/platform.go +++ b/experimental/libbox/platform.go @@ -1,43 +1,68 @@ package libbox -import ( - "github.com/sagernet/sing-box/option" -) +import C "github.com/sagernet/sing-box/constant" type PlatformInterface interface { + LocalDNSTransport() LocalDNSTransport UsePlatformAutoDetectInterfaceControl() bool AutoDetectInterfaceControl(fd int32) error OpenTun(options TunOptions) (int32, error) - WriteLog(message string) UseProcFS() bool - FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error) - PackageNameByUid(uid int32) (string, error) - UIDByPackageName(packageName string) (int32, error) - UsePlatformDefaultInterfaceMonitor() bool + FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (*ConnectionOwner, error) StartDefaultInterfaceMonitor(listener InterfaceUpdateListener) error CloseDefaultInterfaceMonitor(listener InterfaceUpdateListener) error - UsePlatformInterfaceGetter() bool GetInterfaces() (NetworkInterfaceIterator, error) UnderNetworkExtension() bool IncludeAllNetworks() bool ReadWIFIState() *WIFIState + SystemCertificates() StringIterator ClearDNSCache() + SendNotification(notification *Notification) error + StartNeighborMonitor(listener NeighborUpdateListener) error + CloseNeighborMonitor(listener NeighborUpdateListener) error + RegisterMyInterface(name string) +} + +type NeighborUpdateListener interface { + UpdateNeighborTable(entries NeighborEntryIterator) +} + +type ConnectionOwner struct { + UserId int32 + UserName string + ProcessPath string + androidPackageNames []string +} + +func (c *ConnectionOwner) SetAndroidPackageNames(names StringIterator) { + c.androidPackageNames = iteratorToArray[string](names) } -type TunInterface interface { - FileDescriptor() int32 - Close() error +func (c *ConnectionOwner) AndroidPackageNames() StringIterator { + return newIterator(c.androidPackageNames) } type InterfaceUpdateListener interface { - UpdateDefaultInterface(interfaceName string, interfaceIndex int32) + UpdateDefaultInterface(interfaceName string, interfaceIndex int32, isExpensive bool, isConstrained bool) } +const ( + InterfaceTypeWIFI = int32(C.InterfaceTypeWIFI) + InterfaceTypeCellular = int32(C.InterfaceTypeCellular) + InterfaceTypeEthernet = int32(C.InterfaceTypeEthernet) + InterfaceTypeOther = int32(C.InterfaceTypeOther) +) + type NetworkInterface struct { Index int32 MTU int32 Name string Addresses StringIterator + Flags int32 + + Type int32 + DNSServer StringIterator + Metered bool } type WIFIState struct { @@ -54,6 +79,16 @@ type NetworkInterfaceIterator interface { HasNext() bool } +type Notification struct { + Identifier string + TypeName string + TypeID int32 + Title string + Subtitle string + Body string + OpenURL string +} + type OnDemandRule interface { Target() int32 DNSSearchDomainMatch() StringIterator @@ -67,37 +102,3 @@ type OnDemandRuleIterator interface { Next() OnDemandRule HasNext() bool } - -type onDemandRule struct { - option.OnDemandRule -} - -func (r *onDemandRule) Target() int32 { - if r.OnDemandRule.Action == nil { - return -1 - } - return int32(*r.OnDemandRule.Action) -} - -func (r *onDemandRule) DNSSearchDomainMatch() StringIterator { - return newIterator(r.OnDemandRule.DNSSearchDomainMatch) -} - -func (r *onDemandRule) DNSServerAddressMatch() StringIterator { - return newIterator(r.OnDemandRule.DNSServerAddressMatch) -} - -func (r *onDemandRule) InterfaceTypeMatch() int32 { - if r.OnDemandRule.InterfaceTypeMatch == nil { - return -1 - } - return int32(*r.OnDemandRule.InterfaceTypeMatch) -} - -func (r *onDemandRule) SSIDMatch() StringIterator { - return newIterator(r.OnDemandRule.SSIDMatch) -} - -func (r *onDemandRule) ProbeURL() string { - return r.OnDemandRule.ProbeURL -} diff --git a/experimental/libbox/platform/interface.go b/experimental/libbox/platform/interface.go deleted file mode 100644 index 3bec13fa5b..0000000000 --- a/experimental/libbox/platform/interface.go +++ /dev/null @@ -1,28 +0,0 @@ -package platform - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/process" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-tun" - "github.com/sagernet/sing/common/control" - "github.com/sagernet/sing/common/logger" -) - -type Interface interface { - Initialize(ctx context.Context, router adapter.Router) error - UsePlatformAutoDetectInterfaceControl() bool - AutoDetectInterfaceControl() control.Func - OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) - UsePlatformDefaultInterfaceMonitor() bool - CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor - UsePlatformInterfaceGetter() bool - Interfaces() ([]control.Interface, error) - UnderNetworkExtension() bool - IncludeAllNetworks() bool - ClearDNSCache() - ReadWIFIState() adapter.WIFIState - process.Searcher -} diff --git a/experimental/libbox/profile_import.go b/experimental/libbox/profile_import.go index 75ddb06dcf..17671e560f 100644 --- a/experimental/libbox/profile_import.go +++ b/experimental/libbox/profile_import.go @@ -1,13 +1,13 @@ package libbox import ( + "bufio" "bytes" "compress/gzip" "encoding/binary" - "io" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/rw" + "github.com/sagernet/sing/common/varbin" ) func EncodeChunkedMessage(data []byte) []byte { @@ -35,13 +35,13 @@ type ErrorMessage struct { func (e *ErrorMessage) Encode() []byte { var buffer bytes.Buffer buffer.WriteByte(MessageTypeError) - rw.WriteVString(&buffer, e.Message) + varbin.Write(&buffer, binary.BigEndian, e.Message) return buffer.Bytes() } func DecodeErrorMessage(data []byte) (*ErrorMessage, error) { reader := bytes.NewReader(data) - messageType, err := rw.ReadByte(reader) + messageType, err := reader.ReadByte() if err != nil { return nil, err } @@ -49,7 +49,7 @@ func DecodeErrorMessage(data []byte) (*ErrorMessage, error) { return nil, E.New("invalid message") } var message ErrorMessage - message.Message, err = rw.ReadVString(reader) + message.Message, err = varbin.ReadValue[string](reader, binary.BigEndian) if err != nil { return nil, err } @@ -87,7 +87,7 @@ func (e *ProfileEncoder) Encode() []byte { binary.Write(&buffer, binary.BigEndian, uint16(len(e.profiles))) for _, preview := range e.profiles { binary.Write(&buffer, binary.BigEndian, preview.ProfileID) - rw.WriteVString(&buffer, preview.Name) + varbin.Write(&buffer, binary.BigEndian, preview.Name) binary.Write(&buffer, binary.BigEndian, preview.Type) } return buffer.Bytes() @@ -117,7 +117,7 @@ func (d *ProfileDecoder) Decode(data []byte) error { if err != nil { return err } - profile.Name, err = rw.ReadVString(reader) + profile.Name, err = varbin.ReadValue[string](reader, binary.BigEndian) if err != nil { return err } @@ -147,7 +147,7 @@ func (r *ProfileContentRequest) Encode() []byte { func DecodeProfileContentRequest(data []byte) (*ProfileContentRequest, error) { reader := bytes.NewReader(data) - messageType, err := rw.ReadByte(reader) + messageType, err := reader.ReadByte() if err != nil { return nil, err } @@ -176,12 +176,13 @@ func (c *ProfileContent) Encode() []byte { buffer := new(bytes.Buffer) buffer.WriteByte(MessageTypeProfileContent) buffer.WriteByte(1) - writer := gzip.NewWriter(buffer) - rw.WriteVString(writer, c.Name) + gWriter := gzip.NewWriter(buffer) + writer := bufio.NewWriter(gWriter) + varbin.Write(writer, binary.BigEndian, c.Name) binary.Write(writer, binary.BigEndian, c.Type) - rw.WriteVString(writer, c.Config) + varbin.Write(writer, binary.BigEndian, c.Config) if c.Type != ProfileTypeLocal { - rw.WriteVString(writer, c.RemotePath) + varbin.Write(writer, binary.BigEndian, c.RemotePath) } if c.Type == ProfileTypeRemote { binary.Write(writer, binary.BigEndian, c.AutoUpdate) @@ -189,58 +190,60 @@ func (c *ProfileContent) Encode() []byte { binary.Write(writer, binary.BigEndian, c.LastUpdated) } writer.Flush() - writer.Close() + gWriter.Flush() + gWriter.Close() return buffer.Bytes() } func DecodeProfileContent(data []byte) (*ProfileContent, error) { - var reader io.Reader = bytes.NewReader(data) - messageType, err := rw.ReadByte(reader) + reader := bytes.NewReader(data) + messageType, err := reader.ReadByte() if err != nil { return nil, err } if messageType != MessageTypeProfileContent { return nil, E.New("invalid message") } - version, err := rw.ReadByte(reader) + version, err := reader.ReadByte() if err != nil { return nil, err } - reader, err = gzip.NewReader(reader) + gReader, err := gzip.NewReader(reader) if err != nil { return nil, E.Cause(err, "unsupported profile") } + bReader := varbin.StubReader(gReader) var content ProfileContent - content.Name, err = rw.ReadVString(reader) + content.Name, err = varbin.ReadValue[string](bReader, binary.BigEndian) if err != nil { return nil, err } - err = binary.Read(reader, binary.BigEndian, &content.Type) + err = binary.Read(bReader, binary.BigEndian, &content.Type) if err != nil { return nil, err } - content.Config, err = rw.ReadVString(reader) + content.Config, err = varbin.ReadValue[string](bReader, binary.BigEndian) if err != nil { return nil, err } if content.Type != ProfileTypeLocal { - content.RemotePath, err = rw.ReadVString(reader) + content.RemotePath, err = varbin.ReadValue[string](bReader, binary.BigEndian) if err != nil { return nil, err } } if content.Type == ProfileTypeRemote || (version == 0 && content.Type != ProfileTypeLocal) { - err = binary.Read(reader, binary.BigEndian, &content.AutoUpdate) + err = binary.Read(bReader, binary.BigEndian, &content.AutoUpdate) if err != nil { return nil, err } if version >= 1 { - err = binary.Read(reader, binary.BigEndian, &content.AutoUpdateInterval) + err = binary.Read(bReader, binary.BigEndian, &content.AutoUpdateInterval) if err != nil { return nil, err } } - err = binary.Read(reader, binary.BigEndian, &content.LastUpdated) + err = binary.Read(bReader, binary.BigEndian, &content.LastUpdated) if err != nil { return nil, err } diff --git a/experimental/libbox/report.go b/experimental/libbox/report.go new file mode 100644 index 0000000000..816dcac425 --- /dev/null +++ b/experimental/libbox/report.go @@ -0,0 +1,97 @@ +//go:build darwin || linux || windows + +package libbox + +import ( + "bytes" + "encoding/json" + "os" + "path/filepath" + "runtime" + "strconv" + "time" + + C "github.com/sagernet/sing-box/constant" + E "github.com/sagernet/sing/common/exceptions" +) + +type reportMetadata struct { + Source string `json:"source,omitempty"` + BundleIdentifier string `json:"bundleIdentifier,omitempty"` + ProcessName string `json:"processName,omitempty"` + ProcessPath string `json:"processPath,omitempty"` + StartedAt string `json:"startedAt,omitempty"` + AppVersion string `json:"appVersion,omitempty"` + AppMarketingVersion string `json:"appMarketingVersion,omitempty"` + CoreVersion string `json:"coreVersion,omitempty"` + GoVersion string `json:"goVersion,omitempty"` +} + +func baseReportMetadata() reportMetadata { + processPath, _ := os.Executable() + processName := filepath.Base(processPath) + if processName == "." { + processName = "" + } + return reportMetadata{ + Source: sCrashReportSource, + ProcessName: processName, + ProcessPath: processPath, + CoreVersion: C.Version, + GoVersion: GoVersion(), + } +} + +func writeReportFile(destPath string, name string, content []byte) { + filePath := filepath.Join(destPath, name) + os.WriteFile(filePath, content, 0o666) + chownReport(filePath) +} + +func writeReportMetadata(destPath string, metadata any) { + data, err := json.Marshal(metadata) + if err != nil { + return + } + writeReportFile(destPath, "metadata.json", data) +} + +func copyConfigSnapshot(destPath string) { + snapshotPath := configSnapshotPath() + content, err := os.ReadFile(snapshotPath) + if err != nil { + return + } + if len(bytes.TrimSpace(content)) == 0 { + return + } + writeReportFile(destPath, "configuration.json", content) +} + +func initReportDir(path string) { + os.MkdirAll(path, 0o777) + chownReport(path) +} + +func chownReport(path string) { + if runtime.GOOS != "android" && runtime.GOOS != "windows" { + os.Chown(path, sUserID, sGroupID) + } +} + +func nextAvailableReportPath(reportsDir string, timestamp time.Time) (string, error) { + destName := timestamp.Format("2006-01-02T15-04-05") + destPath := filepath.Join(reportsDir, destName) + _, err := os.Stat(destPath) + if os.IsNotExist(err) { + return destPath, nil + } + for i := 1; i <= 1000; i++ { + suffixedPath := filepath.Join(reportsDir, destName+"-"+strconv.Itoa(i)) + _, err = os.Stat(suffixedPath) + if os.IsNotExist(err) { + return suffixedPath, nil + } + } + return "", E.New("no available report path for ", destName) +} diff --git a/experimental/libbox/semver.go b/experimental/libbox/semver.go new file mode 100644 index 0000000000..b0919222c4 --- /dev/null +++ b/experimental/libbox/semver.go @@ -0,0 +1,27 @@ +package libbox + +import ( + "strings" + + "golang.org/x/mod/semver" +) + +func CompareSemver(left string, right string) bool { + normalizedLeft := normalizeSemver(left) + if !semver.IsValid(normalizedLeft) { + return false + } + normalizedRight := normalizeSemver(right) + if !semver.IsValid(normalizedRight) { + return false + } + return semver.Compare(normalizedLeft, normalizedRight) > 0 +} + +func normalizeSemver(version string) string { + trimmedVersion := strings.TrimSpace(version) + if strings.HasPrefix(trimmedVersion, "v") { + return trimmedVersion + } + return "v" + trimmedVersion +} diff --git a/experimental/libbox/semver_test.go b/experimental/libbox/semver_test.go new file mode 100644 index 0000000000..f76093b4a5 --- /dev/null +++ b/experimental/libbox/semver_test.go @@ -0,0 +1,16 @@ +package libbox + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestCompareSemver(t *testing.T) { + t.Parallel() + + require.False(t, CompareSemver("1.13.0-rc.4", "1.13.0")) + require.True(t, CompareSemver("1.13.1", "1.13.0")) + require.False(t, CompareSemver("v1.13.0", "1.13.0")) + require.False(t, CompareSemver("1.13.0-", "1.13.0")) +} diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 0a54d7abd8..0aaa51a556 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -1,112 +1,43 @@ package libbox import ( - "context" + "crypto/rand" + "encoding/hex" + "errors" + "net" "net/netip" - "os" "runtime" - runtimeDebug "runtime/debug" + "strconv" + "sync" "syscall" - "time" - "github.com/sagernet/sing-box" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/process" - "github.com/sagernet/sing-box/common/urltest" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental/libbox/internal/procfs" - "github.com/sagernet/sing-box/experimental/libbox/platform" - "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-tun" + tun "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/service" - "github.com/sagernet/sing/service/filemanager" - "github.com/sagernet/sing/service/pause" ) -type BoxService struct { - ctx context.Context - cancel context.CancelFunc - instance *box.Box - pauseManager pause.Manager - urlTestHistoryStorage *urltest.HistoryStorage - - servicePauseFields -} - -func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) { - options, err := parseConfig(configContent) - if err != nil { - return nil, err - } - runtimeDebug.FreeOSMemory() - ctx, cancel := context.WithCancel(context.Background()) - ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID) - urlTestHistoryStorage := urltest.NewHistoryStorage() - ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage) - platformWrapper := &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()} - instance, err := box.New(box.Options{ - Context: ctx, - Options: options, - PlatformInterface: platformWrapper, - PlatformLogWriter: platformWrapper, - }) - if err != nil { - cancel() - return nil, E.Cause(err, "create service") - } - runtimeDebug.FreeOSMemory() - return &BoxService{ - ctx: ctx, - cancel: cancel, - instance: instance, - urlTestHistoryStorage: urlTestHistoryStorage, - pauseManager: service.FromContext[pause.Manager](ctx), - }, nil -} - -func (s *BoxService) Start() error { - return s.instance.Start() -} - -func (s *BoxService) Close() error { - done := make(chan struct{}) - defer close(done) - go func() { - select { - case <-done: - return - case <-time.After(C.FatalStopTimeout): - os.Exit(1) - } - }() - s.cancel() - s.urlTestHistoryStorage.Close() - return s.instance.Close() -} - -func (s *BoxService) NeedWIFIState() bool { - return s.instance.Router().NeedWIFIState() -} - -var ( - _ platform.Interface = (*platformInterfaceWrapper)(nil) - _ log.PlatformWriter = (*platformInterfaceWrapper)(nil) -) +var _ adapter.PlatformInterface = (*platformInterfaceWrapper)(nil) type platformInterfaceWrapper struct { - iif PlatformInterface - useProcFS bool - router adapter.Router + iif PlatformInterface + useProcFS bool + networkManager adapter.NetworkManager + myTunName string + myTunAddress []netip.Addr + defaultInterfaceAccess sync.Mutex + defaultInterface *control.Interface + isExpensive bool + isConstrained bool } -func (w *platformInterfaceWrapper) Initialize(ctx context.Context, router adapter.Router) error { - w.router = router +func (w *platformInterfaceWrapper) Initialize(networkManager adapter.NetworkManager) error { + w.networkManager = networkManager return nil } @@ -114,20 +45,20 @@ func (w *platformInterfaceWrapper) UsePlatformAutoDetectInterfaceControl() bool return w.iif.UsePlatformAutoDetectInterfaceControl() } -func (w *platformInterfaceWrapper) AutoDetectInterfaceControl() control.Func { - return func(network, address string, conn syscall.RawConn) error { - return control.Raw(conn, func(fd uintptr) error { - return w.iif.AutoDetectInterfaceControl(int32(fd)) - }) - } +func (w *platformInterfaceWrapper) AutoDetectInterfaceControl(fd int) error { + return w.iif.AutoDetectInterfaceControl(int32(fd)) +} + +func (w *platformInterfaceWrapper) UsePlatformInterface() bool { + return true } -func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) { +func (w *platformInterfaceWrapper) OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) { if len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 { - return nil, E.New("android: unsupported uid options") + return nil, E.New("platform: unsupported uid options") } if len(options.IncludeAndroidUser) > 0 { - return nil, E.New("android: unsupported android_user option") + return nil, E.New("platform: unsupported android_user option") } routeRanges, err := options.BuildAutoRouteRanges(true) if err != nil { @@ -141,71 +72,78 @@ func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions if err != nil { return nil, E.Cause(err, "query tun name") } + options.InterfaceMonitor.RegisterMyInterface(options.Name) dupFd, err := dup(int(tunFd)) if err != nil { return nil, E.Cause(err, "dup tun file descriptor") } options.FileDescriptor = dupFd + w.myTunName = options.Name + w.myTunAddress = myTunAddress(options) + w.iif.RegisterMyInterface(options.Name) return tun.New(*options) } -func (w *platformInterfaceWrapper) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*process.Info, error) { - var uid int32 - if w.useProcFS { - uid = procfs.ResolveSocketByProcSearch(network, source, destination) - if uid == -1 { - return nil, E.New("procfs: not found") - } - } else { - var ipProtocol int32 - switch N.NetworkName(network) { - case N.NetworkTCP: - ipProtocol = syscall.IPPROTO_TCP - case N.NetworkUDP: - ipProtocol = syscall.IPPROTO_UDP - default: - return nil, E.New("unknown network: ", network) - } - var err error - uid, err = w.iif.FindConnectionOwner(ipProtocol, source.Addr().String(), int32(source.Port()), destination.Addr().String(), int32(destination.Port())) - if err != nil { - return nil, err - } +func myTunAddress(options *tun.Options) []netip.Addr { + addresses := make([]netip.Addr, 0, len(options.Inet4Address)+len(options.Inet6Address)) + for _, prefix := range options.Inet4Address { + addresses = append(addresses, prefix.Addr()) + } + for _, prefix := range options.Inet6Address { + addresses = append(addresses, prefix.Addr()) } - packageName, _ := w.iif.PackageNameByUid(uid) - return &process.Info{UserId: uid, PackageName: packageName}, nil + return addresses +} + +func (w *platformInterfaceWrapper) MyInterfaceAddress() []netip.Addr { + return w.myTunAddress } func (w *platformInterfaceWrapper) UsePlatformDefaultInterfaceMonitor() bool { - return w.iif.UsePlatformDefaultInterfaceMonitor() + return true } func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor { return &platformDefaultInterfaceMonitor{ platformInterfaceWrapper: w, - defaultInterfaceIndex: -1, logger: logger, } } -func (w *platformInterfaceWrapper) UsePlatformInterfaceGetter() bool { - return w.iif.UsePlatformInterfaceGetter() +func (w *platformInterfaceWrapper) UsePlatformNetworkInterfaces() bool { + return true } -func (w *platformInterfaceWrapper) Interfaces() ([]control.Interface, error) { +func (w *platformInterfaceWrapper) NetworkInterfaces() ([]adapter.NetworkInterface, error) { interfaceIterator, err := w.iif.GetInterfaces() if err != nil { return nil, err } - var interfaces []control.Interface + var interfaces []adapter.NetworkInterface for _, netInterface := range iteratorToArray[*NetworkInterface](interfaceIterator) { - interfaces = append(interfaces, control.Interface{ - Index: int(netInterface.Index), - MTU: int(netInterface.MTU), - Name: netInterface.Name, - Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix), + w.defaultInterfaceAccess.Lock() + // (GOOS=windows) SA4006: this value of `isDefault` is never used + // Why not used? + //nolint:staticcheck + isDefault := netInterface.Name != w.myTunName && w.defaultInterface != nil && int(netInterface.Index) == w.defaultInterface.Index + w.defaultInterfaceAccess.Unlock() + interfaces = append(interfaces, adapter.NetworkInterface{ + Interface: control.Interface{ + Index: int(netInterface.Index), + MTU: int(netInterface.MTU), + Name: netInterface.Name, + Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix), + Flags: linkFlags(uint32(netInterface.Flags)), + }, + Type: C.InterfaceType(netInterface.Type), + DNSServers: iteratorToArray[string](netInterface.DNSServer), + Expensive: netInterface.Metered || isDefault && w.isExpensive, + Constrained: isDefault && w.isConstrained, }) } + interfaces = common.UniqBy(interfaces, func(it adapter.NetworkInterface) string { + return it.Name + }) return interfaces, nil } @@ -213,7 +151,7 @@ func (w *platformInterfaceWrapper) UnderNetworkExtension() bool { return w.iif.UnderNetworkExtension() } -func (w *platformInterfaceWrapper) IncludeAllNetworks() bool { +func (w *platformInterfaceWrapper) NetworkExtensionIncludeAllNetworks() bool { return w.iif.IncludeAllNetworks() } @@ -221,6 +159,14 @@ func (w *platformInterfaceWrapper) ClearDNSCache() { w.iif.ClearDNSCache() } +func (w *platformInterfaceWrapper) RequestPermissionForWIFIState() error { + return nil +} + +func (w *platformInterfaceWrapper) UsePlatformWIFIMonitor() bool { + return true +} + func (w *platformInterfaceWrapper) ReadWIFIState() adapter.WIFIState { wifiState := w.iif.ReadWIFIState() if wifiState == nil { @@ -229,10 +175,128 @@ func (w *platformInterfaceWrapper) ReadWIFIState() adapter.WIFIState { return (adapter.WIFIState)(*wifiState) } +func (w *platformInterfaceWrapper) SystemCertificates() []string { + return iteratorToArray[string](w.iif.SystemCertificates()) +} + +func (w *platformInterfaceWrapper) UsePlatformConnectionOwnerFinder() bool { + return true +} + +func (w *platformInterfaceWrapper) FindConnectionOwner(request *adapter.FindConnectionOwnerRequest) (*adapter.ConnectionOwner, error) { + if w.useProcFS { + var source netip.AddrPort + var destination netip.AddrPort + sourceAddr, _ := netip.ParseAddr(request.SourceAddress) + source = netip.AddrPortFrom(sourceAddr, uint16(request.SourcePort)) + destAddr, _ := netip.ParseAddr(request.DestinationAddress) + destination = netip.AddrPortFrom(destAddr, uint16(request.DestinationPort)) + + var network string + switch request.IpProtocol { + case int32(syscall.IPPROTO_TCP): + network = "tcp" + case int32(syscall.IPPROTO_UDP): + network = "udp" + default: + return nil, E.New("unknown protocol: ", request.IpProtocol) + } + + uid := procfs.ResolveSocketByProcSearch(network, source, destination) + if uid == -1 { + return nil, E.New("procfs: not found") + } + return &adapter.ConnectionOwner{ + UserId: uid, + }, nil + } + + result, err := w.iif.FindConnectionOwner(request.IpProtocol, request.SourceAddress, request.SourcePort, request.DestinationAddress, request.DestinationPort) + if err != nil { + return nil, err + } + return &adapter.ConnectionOwner{ + UserId: result.UserId, + UserName: result.UserName, + ProcessPath: result.ProcessPath, + AndroidPackageNames: result.androidPackageNames, + }, nil +} + func (w *platformInterfaceWrapper) DisableColors() bool { return runtime.GOOS != "android" } -func (w *platformInterfaceWrapper) WriteMessage(level log.Level, message string) { - w.iif.WriteLog(message) +func (w *platformInterfaceWrapper) UsePlatformNotification() bool { + return true +} + +func (w *platformInterfaceWrapper) SendNotification(notification *adapter.Notification) error { + return w.iif.SendNotification((*Notification)(notification)) +} + +func (w *platformInterfaceWrapper) UsePlatformNeighborResolver() bool { + return true +} + +func (w *platformInterfaceWrapper) StartNeighborMonitor(listener adapter.NeighborUpdateListener) error { + return w.iif.StartNeighborMonitor(&neighborUpdateListenerWrapper{listener: listener}) +} + +func (w *platformInterfaceWrapper) CloseNeighborMonitor(listener adapter.NeighborUpdateListener) error { + return w.iif.CloseNeighborMonitor(nil) +} + +type neighborUpdateListenerWrapper struct { + listener adapter.NeighborUpdateListener +} + +func (w *neighborUpdateListenerWrapper) UpdateNeighborTable(entries NeighborEntryIterator) { + var result []adapter.NeighborEntry + for entries.HasNext() { + entry := entries.Next() + if entry == nil { + continue + } + address, err := netip.ParseAddr(entry.Address) + if err != nil { + continue + } + macAddress, err := net.ParseMAC(entry.MacAddress) + if err != nil { + continue + } + result = append(result, adapter.NeighborEntry{ + Address: address, + MACAddress: macAddress, + Hostname: entry.Hostname, + }) + } + w.listener.UpdateNeighborTable(result) +} + +func AvailablePort(startPort int32) (int32, error) { + for port := int(startPort); ; port++ { + if port > 65535 { + return 0, E.New("no available port found") + } + listener, err := net.Listen("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(int(port)))) + if err != nil { + if errors.Is(err, syscall.EADDRINUSE) { + continue + } + return 0, E.Cause(err, "find available port") + } + err = listener.Close() + if err != nil { + return 0, E.Cause(err, "close listener") + } + return int32(port), nil + } +} + +func RandomHex(length int32) *StringBox { + bytes := make([]byte, length) + common.Must1(rand.Read(bytes)) + return wrapString(hex.EncodeToString(bytes)) } diff --git a/experimental/libbox/service_error.go b/experimental/libbox/service_error.go deleted file mode 100644 index 2f22574a7b..0000000000 --- a/experimental/libbox/service_error.go +++ /dev/null @@ -1,32 +0,0 @@ -package libbox - -import ( - "os" - "path/filepath" -) - -func serviceErrorPath() string { - return filepath.Join(sWorkingPath, "network_extension_error") -} - -func ClearServiceError() { - os.Remove(serviceErrorPath()) -} - -func ReadServiceError() (string, error) { - data, err := os.ReadFile(serviceErrorPath()) - if err == nil { - os.Remove(serviceErrorPath()) - } - return string(data), err -} - -func WriteServiceError(message string) error { - errorFile, err := os.Create(serviceErrorPath()) - if err != nil { - return err - } - errorFile.WriteString(message) - errorFile.Chown(sUserID, sGroupID) - return errorFile.Close() -} diff --git a/experimental/libbox/service_pause.go b/experimental/libbox/service_pause.go deleted file mode 100644 index c4aa8daaff..0000000000 --- a/experimental/libbox/service_pause.go +++ /dev/null @@ -1,33 +0,0 @@ -package libbox - -import ( - "sync" - "time" -) - -type servicePauseFields struct { - pauseAccess sync.Mutex - pauseTimer *time.Timer -} - -func (s *BoxService) Pause() { - s.pauseAccess.Lock() - defer s.pauseAccess.Unlock() - if s.pauseTimer != nil { - s.pauseTimer.Stop() - } - s.pauseTimer = time.AfterFunc(3*time.Second, s.ResetNetwork) -} - -func (s *BoxService) Wake() { - s.pauseAccess.Lock() - defer s.pauseAccess.Unlock() - if s.pauseTimer != nil { - s.pauseTimer.Stop() - } - s.pauseTimer = time.AfterFunc(3*time.Minute, s.ResetNetwork) -} - -func (s *BoxService) ResetNetwork() { - _ = s.instance.Router().ResetNetwork() -} diff --git a/experimental/libbox/setup.go b/experimental/libbox/setup.go index ea468f391c..9f8aa03cf9 100644 --- a/experimental/libbox/setup.go +++ b/experimental/libbox/setup.go @@ -1,49 +1,114 @@ package libbox import ( + "math" "os" - "os/user" - "strconv" + "path/filepath" + "runtime" + "runtime/debug" + "strings" + "time" - "github.com/sagernet/sing-box/common/humanize" + "github.com/sagernet/sing-box/common/networkquality" + "github.com/sagernet/sing-box/common/stun" C "github.com/sagernet/sing-box/constant" - _ "github.com/sagernet/sing-box/include" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/experimental/locale" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/service/oomkiller" + "github.com/sagernet/sing/common/byteformats" + E "github.com/sagernet/sing/common/exceptions" ) var ( - sBasePath string - sWorkingPath string - sTempPath string - sUserID int - sGroupID int - sTVOS bool + sBasePath string + sWorkingPath string + sTempPath string + sUserID int + sGroupID int + sFixAndroidStack bool + sCommandServerListenPort uint16 + sCommandServerSecret string + sLogMaxLines int + sDebug bool + sCrashReportSource string + sOOMKillerEnabled bool + sOOMKillerDisabled bool + sOOMMemoryLimit int64 ) -func Setup(basePath string, workingPath string, tempPath string, isTVOS bool) { - sBasePath = basePath - sWorkingPath = workingPath - sTempPath = tempPath +func init() { + debug.SetPanicOnFault(true) + debug.SetTraceback("all") +} + +type SetupOptions struct { + BasePath string + WorkingPath string + TempPath string + FixAndroidStack bool + CommandServerListenPort int32 + CommandServerSecret string + LogMaxLines int + Debug bool + CrashReportSource string + OomKillerEnabled bool + OomKillerDisabled bool + OomMemoryLimit int64 +} + +func applySetupOptions(options *SetupOptions) { + sBasePath = options.BasePath + sWorkingPath = options.WorkingPath + sTempPath = options.TempPath + sUserID = os.Getuid() sGroupID = os.Getgid() - sTVOS = isTVOS - os.MkdirAll(sWorkingPath, 0o777) - os.MkdirAll(sTempPath, 0o777) + + // TODO: remove after fixed + // https://github.com/golang/go/issues/68760 + sFixAndroidStack = options.FixAndroidStack + + sCommandServerListenPort = uint16(options.CommandServerListenPort) + sCommandServerSecret = options.CommandServerSecret + sLogMaxLines = options.LogMaxLines + sDebug = options.Debug + sCrashReportSource = options.CrashReportSource + ReloadSetupOptions(options) } -func SetupWithUsername(basePath string, workingPath string, tempPath string, username string) error { - sBasePath = basePath - sWorkingPath = workingPath - sTempPath = tempPath - sUser, err := user.Lookup(username) - if err != nil { - return err +func ReloadSetupOptions(options *SetupOptions) { + sOOMKillerEnabled = options.OomKillerEnabled + sOOMKillerDisabled = options.OomKillerDisabled + sOOMMemoryLimit = options.OomMemoryLimit + if sOOMKillerEnabled { + if sOOMMemoryLimit == 0 && C.IsIos { + sOOMMemoryLimit = oomkiller.DefaultAppleNetworkExtensionMemoryLimit + } + if sOOMMemoryLimit > 0 { + debug.SetMemoryLimit(sOOMMemoryLimit * 3 / 4) + } else { + debug.SetMemoryLimit(math.MaxInt64) + } + } else { + debug.SetMemoryLimit(math.MaxInt64) } - sUserID, _ = strconv.Atoi(sUser.Uid) - sGroupID, _ = strconv.Atoi(sUser.Gid) +} + +func Setup(options *SetupOptions) error { + applySetupOptions(options) os.MkdirAll(sWorkingPath, 0o777) os.MkdirAll(sTempPath, 0o777) - os.Chown(sWorkingPath, sUserID, sGroupID) - os.Chown(sTempPath, sUserID, sGroupID) + return redirectStderr(filepath.Join(sWorkingPath, "CrashReport-"+sCrashReportSource+".log")) +} + +func SetLocale(localeId string) error { + if strings.Contains(localeId, "@") { + localeId = strings.Split(localeId, "@")[0] + } + if !locale.Set(localeId) { + return E.New("unsupported locale: ", localeId) + } return nil } @@ -51,12 +116,74 @@ func Version() string { return C.Version } +func GoVersion() string { + return runtime.Version() + ", " + runtime.GOOS + "/" + runtime.GOARCH +} + func FormatBytes(length int64) string { - return humanize.Bytes(uint64(length)) + return byteformats.FormatKBytes(uint64(length)) } func FormatMemoryBytes(length int64) string { - return humanize.MemoryBytes(uint64(length)) + return byteformats.FormatMemoryKBytes(uint64(length)) +} + +func FormatDuration(duration int64) string { + return log.FormatDuration(time.Duration(duration) * time.Millisecond) +} + +func FormatBitrate(bps int64) string { + return networkquality.FormatBitrate(bps) +} + +const NetworkQualityDefaultConfigURL = networkquality.DefaultConfigURL + +const NetworkQualityDefaultMaxRuntimeSeconds = int32(networkquality.DefaultMaxRuntime / time.Second) + +const ( + NetworkQualityAccuracyLow = int32(networkquality.AccuracyLow) + NetworkQualityAccuracyMedium = int32(networkquality.AccuracyMedium) + NetworkQualityAccuracyHigh = int32(networkquality.AccuracyHigh) +) + +const ( + NetworkQualityPhaseIdle = int32(networkquality.PhaseIdle) + NetworkQualityPhaseDownload = int32(networkquality.PhaseDownload) + NetworkQualityPhaseUpload = int32(networkquality.PhaseUpload) + NetworkQualityPhaseDone = int32(networkquality.PhaseDone) +) + +const STUNDefaultServer = stun.DefaultServer + +const ( + STUNPhaseBinding = int32(stun.PhaseBinding) + STUNPhaseNATMapping = int32(stun.PhaseNATMapping) + STUNPhaseNATFiltering = int32(stun.PhaseNATFiltering) + STUNPhaseDone = int32(stun.PhaseDone) +) + +const ( + NATMappingEndpointIndependent = int32(stun.NATMappingEndpointIndependent) + NATMappingAddressDependent = int32(stun.NATMappingAddressDependent) + NATMappingAddressAndPortDependent = int32(stun.NATMappingAddressAndPortDependent) +) + +const ( + NATFilteringEndpointIndependent = int32(stun.NATFilteringEndpointIndependent) + NATFilteringAddressDependent = int32(stun.NATFilteringAddressDependent) + NATFilteringAddressAndPortDependent = int32(stun.NATFilteringAddressAndPortDependent) +) + +func FormatNATMapping(value int32) string { + return stun.NATMapping(value).String() +} + +func FormatNATFiltering(value int32) string { + return stun.NATFiltering(value).String() +} + +func FormatFQDN(fqdn string) string { + return dns.FqdnToDomain(fqdn) } func ProxyDisplayType(proxyType string) string { diff --git a/experimental/libbox/signal_handler_darwin.go b/experimental/libbox/signal_handler_darwin.go new file mode 100644 index 0000000000..a60ddd90fe --- /dev/null +++ b/experimental/libbox/signal_handler_darwin.go @@ -0,0 +1,146 @@ +//go:build darwin && badlinkname + +package libbox + +/* +#include +#include +#include + +static struct sigaction _go_sa[32]; +static struct sigaction _plcrash_sa[32]; +static int _saved = 0; + +static int _signals[] = {SIGSEGV, SIGBUS, SIGFPE, SIGILL, SIGTRAP}; +static const int _signal_count = sizeof(_signals) / sizeof(_signals[0]); + +static void _save_go_handlers(void) { + if (_saved) return; + for (int i = 0; i < _signal_count; i++) + sigaction(_signals[i], NULL, &_go_sa[_signals[i]]); + _saved = 1; +} + +static void _combined_handler(int sig, siginfo_t *info, void *uap) { + // Step 1: PLCrashReporter writes .plcrash, resets all handlers to SIG_DFL, + // and calls raise(sig) which pends (signal is blocked, no SA_NODEFER). + if ((_plcrash_sa[sig].sa_flags & SA_SIGINFO) && + (uintptr_t)_plcrash_sa[sig].sa_sigaction > 1) + _plcrash_sa[sig].sa_sigaction(sig, info, uap); + + // SIGTRAP does not rely on sigreturn -> sigpanic. Once Go's trap trampoline + // is force-installed, we can chain into it directly after PLCrashReporter. + if (sig == SIGTRAP && + (_go_sa[sig].sa_flags & SA_SIGINFO) && + (uintptr_t)_go_sa[sig].sa_sigaction > 1) { + _go_sa[sig].sa_sigaction(sig, info, uap); + return; + } + + // Step 2: Restore Go's handler via sigaction (overwrites PLCrashReporter's SIG_DFL). + // Do NOT call Go's handler directly — Go's preparePanic only modifies the + // ucontext and returns. The actual crash output is written by sigpanic, which + // only runs when the KERNEL restores the modified ucontext via sigreturn. + // A direct C function call has no sigreturn, so sigpanic would never execute. + sigaction(sig, &_go_sa[sig], NULL); + + // Step 3: Return. The kernel restores the original ucontext and re-executes + // the faulting instruction. Two signals are now pending/imminent: + // a) PLCrashReporter's raise() (SI_USER) — Go's handler ignores it + // (sighandler: sigFromUser() → return). + // b) The re-executed fault (SEGV_MAPERR) — Go's handler processes it: + // preparePanic → kernel sigreturn → sigpanic → crash output written + // via debug.SetCrashOutput. +} + +static void _reinstall_handlers(void) { + if (!_saved) return; + for (int i = 0; i < _signal_count; i++) { + int sig = _signals[i]; + struct sigaction current; + sigaction(sig, NULL, ¤t); + // Only save the handler if it's not one of ours + if (current.sa_sigaction != _combined_handler) { + // If current handler is still Go's, PLCrashReporter wasn't installed + if ((current.sa_flags & SA_SIGINFO) && + (uintptr_t)current.sa_sigaction > 1 && + current.sa_sigaction == _go_sa[sig].sa_sigaction) + memset(&_plcrash_sa[sig], 0, sizeof(_plcrash_sa[sig])); + else + _plcrash_sa[sig] = current; + } + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = _combined_handler; + sa.sa_flags = SA_SIGINFO | SA_ONSTACK; + sigemptyset(&sa.sa_mask); + sigaction(sig, &sa, NULL); + } +} +*/ +import "C" + +import ( + "reflect" + _ "unsafe" +) + +const ( + _sigtrap = 5 + _nsig = 32 +) + +//go:linkname runtimeGetsig runtime.getsig +func runtimeGetsig(i uint32) uintptr + +//go:linkname runtimeSetsig runtime.setsig +func runtimeSetsig(i uint32, fn uintptr) + +//go:linkname runtimeCgoSigtramp runtime.cgoSigtramp +func runtimeCgoSigtramp() + +//go:linkname runtimeFwdSig runtime.fwdSig +var runtimeFwdSig [_nsig]uintptr + +//go:linkname runtimeHandlingSig runtime.handlingSig +var runtimeHandlingSig [_nsig]uint32 + +func forceGoSIGTRAPHandler() { + runtimeFwdSig[_sigtrap] = runtimeGetsig(_sigtrap) + runtimeHandlingSig[_sigtrap] = 1 + runtimeSetsig(_sigtrap, reflect.ValueOf(runtimeCgoSigtramp).Pointer()) +} + +// PrepareCrashSignalHandlers captures Go's original synchronous signal handlers. +// +// In gomobile/c-archive embeddings, package init runs on the first Go entry. +// That means a native crash reporter installed before the first Go call would +// otherwise be captured as the "Go" handler and break handler restoration on +// SIGSEGV. Go skips SIGTRAP in c-archive mode, so install its trap trampoline +// before saving handlers. Call this before installing PLCrashReporter. +func PrepareCrashSignalHandlers() { + forceGoSIGTRAPHandler() + C._save_go_handlers() +} + +// ReinstallCrashSignalHandlers installs a combined signal handler that chains +// PLCrashReporter (native crash report) and Go's runtime handler (Go crash log). +// +// Call PrepareCrashSignalHandlers before installing PLCrashReporter, then call +// this after PLCrashReporter has been installed. +// +// Flow on SIGSEGV: +// 1. Combined handler calls PLCrashReporter's saved handler → .plcrash written +// 2. Combined handler restores Go's handler via sigaction +// 3. Combined handler returns — kernel re-executes faulting instruction +// 4. PLCrashReporter's pending raise() (SI_USER) is ignored by Go's handler +// 5. Hardware fault → Go's handler → preparePanic → kernel sigreturn → +// sigpanic → crash output via debug.SetCrashOutput +// +// Flow on SIGTRAP: +// 1. PrepareCrashSignalHandlers force-installs Go's cgo trap trampoline +// 2. Combined handler calls PLCrashReporter's saved handler → .plcrash written +// 3. Combined handler directly calls the saved Go trap trampoline +func ReinstallCrashSignalHandlers() { + C._reinstall_handlers() +} diff --git a/experimental/libbox/signal_handler_stub.go b/experimental/libbox/signal_handler_stub.go new file mode 100644 index 0000000000..2ac68b869d --- /dev/null +++ b/experimental/libbox/signal_handler_stub.go @@ -0,0 +1,7 @@ +//go:build !darwin || !badlinkname + +package libbox + +func PrepareCrashSignalHandlers() {} + +func ReinstallCrashSignalHandlers() {} diff --git a/experimental/libbox/stun.go b/experimental/libbox/stun.go new file mode 100644 index 0000000000..3f38815d79 --- /dev/null +++ b/experimental/libbox/stun.go @@ -0,0 +1,50 @@ +package libbox + +import ( + "context" + + "github.com/sagernet/sing-box/common/stun" +) + +type STUNTest struct { + ctx context.Context + cancel context.CancelFunc +} + +func NewSTUNTest() *STUNTest { + ctx, cancel := context.WithCancel(context.Background()) + return &STUNTest{ctx: ctx, cancel: cancel} +} + +func (t *STUNTest) Start(server string, handler STUNTestHandler) { + go func() { + result, err := stun.Run(stun.Options{ + Server: server, + Context: t.ctx, + OnProgress: func(p stun.Progress) { + handler.OnProgress(&STUNTestProgress{ + Phase: int32(p.Phase), + ExternalAddr: p.ExternalAddr, + LatencyMs: p.LatencyMs, + NATMapping: int32(p.NATMapping), + NATFiltering: int32(p.NATFiltering), + }) + }, + }) + if err != nil { + handler.OnError(err.Error()) + return + } + handler.OnResult(&STUNTestResult{ + ExternalAddr: result.ExternalAddr, + LatencyMs: result.LatencyMs, + NATMapping: int32(result.NATMapping), + NATFiltering: int32(result.NATFiltering), + NATTypeSupported: result.NATTypeSupported, + }) + }() +} + +func (t *STUNTest) Cancel() { + t.cancel() +} diff --git a/experimental/libbox/tun.go b/experimental/libbox/tun.go index 5c6e3370c2..fef66b91c1 100644 --- a/experimental/libbox/tun.go +++ b/experimental/libbox/tun.go @@ -5,15 +5,21 @@ import ( "net/netip" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-tun" + tun "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" +) + +const ( + DNSModeDisabled = tun.DNSModeDisabled + DNSModeNative = tun.DNSModeNative + DNSModeHijack = tun.DNSModeHijack ) type TunOptions interface { GetInet4Address() RoutePrefixIterator GetInet6Address() RoutePrefixIterator - GetDNSServerAddress() (string, error) + GetDNSMode() *StringBox + GetDNSServerAddress() (StringIterator, error) GetMTU() int32 GetAutoRoute() bool GetStrictRoute() bool @@ -89,11 +95,16 @@ func (o *tunOptions) GetInet6Address() RoutePrefixIterator { return mapRoutePrefix(o.Inet6Address) } -func (o *tunOptions) GetDNSServerAddress() (string, error) { - if len(o.Inet4Address) == 0 || o.Inet4Address[0].Bits() == 32 { - return "", E.New("need one more IPv4 address for DNS hijacking") +func (o *tunOptions) GetDNSMode() *StringBox { + return wrapString(o.Options.DNSMode) +} + +func (o *tunOptions) GetDNSServerAddress() (StringIterator, error) { + dnsServers, err := o.Options.DNSServerAddress() + if err != nil { + return nil, err } - return o.Inet4Address[0].Addr().Next().String(), nil + return newIterator(common.Map(dnsServers, netip.Addr.String)), nil } func (o *tunOptions) GetMTU() int32 { diff --git a/experimental/libbox/tun_darwin.go b/experimental/libbox/tun_darwin.go index e312cb9165..b6c6d56cbd 100644 --- a/experimental/libbox/tun_darwin.go +++ b/experimental/libbox/tun_darwin.go @@ -11,7 +11,7 @@ const utunControlName = "com.apple.net.utun_control" func GetTunnelFileDescriptor() int32 { ctlInfo := &unix.CtlInfo{} copy(ctlInfo.Name[:], utunControlName) - for fd := 0; fd < 1024; fd++ { + for fd := range 1024 { addr, err := unix.Getpeername(fd) if err != nil { continue diff --git a/experimental/locale/locale.go b/experimental/locale/locale.go new file mode 100644 index 0000000000..e5575af4eb --- /dev/null +++ b/experimental/locale/locale.go @@ -0,0 +1,32 @@ +package locale + +var ( + localeRegistry = make(map[string]*Locale) + current = defaultLocal +) + +type Locale struct { + // deprecated messages for graphical clients + Locale string + DeprecatedMessage string + DeprecatedMessageNoLink string +} + +var defaultLocal = &Locale{ + Locale: "en_US", + DeprecatedMessage: "%s is deprecated in sing-box %s and will be removed in sing-box %s please checkout documentation for migration.", + DeprecatedMessageNoLink: "%s is deprecated in sing-box %s and will be removed in sing-box %s.", +} + +func Current() *Locale { + return current +} + +func Set(localeId string) bool { + locale, loaded := localeRegistry[localeId] + if !loaded { + return false + } + current = locale + return true +} diff --git a/experimental/locale/locale_zh_CN.go b/experimental/locale/locale_zh_CN.go new file mode 100644 index 0000000000..f5605d9d16 --- /dev/null +++ b/experimental/locale/locale_zh_CN.go @@ -0,0 +1,11 @@ +package locale + +var warningMessageForEndUsers = "\n\n如果您不明白此消息意味着什么:您的配置文件已过时,且将很快不可用。请联系您的配置提供者以更新配置。" + +func init() { + localeRegistry["zh_CN"] = &Locale{ + Locale: "zh_CN", + DeprecatedMessage: "%s 已在 sing-box %s 中被弃用,且将在 sing-box %s 中被移除,请参阅迁移指南。" + warningMessageForEndUsers, + DeprecatedMessageNoLink: "%s 已在 sing-box %s 中被弃用,且将在 sing-box %s 中被移除。" + warningMessageForEndUsers, + } +} diff --git a/experimental/v2rayapi/server.go b/experimental/v2rayapi/server.go index 8b4b43855e..8ebae1c4f7 100644 --- a/experimental/v2rayapi/server.go +++ b/experimental/v2rayapi/server.go @@ -44,7 +44,14 @@ func NewServer(logger log.Logger, options option.V2RayAPIOptions) (adapter.V2Ray return server, nil } -func (s *Server) Start() error { +func (s *Server) Name() string { + return "v2ray server" +} + +func (s *Server) Start(stage adapter.StartStage) error { + if stage != adapter.StartStatePostStart { + return nil + } listener, err := net.Listen("tcp", s.listen) if err != nil { return err @@ -70,6 +77,6 @@ func (s *Server) Close() error { ) } -func (s *Server) StatsService() adapter.V2RayStatsService { +func (s *Server) StatsService() adapter.ConnectionTracker { return s.statsService } diff --git a/experimental/v2rayapi/stats.go b/experimental/v2rayapi/stats.go index 38b9a301fd..c7b2b49f34 100644 --- a/experimental/v2rayapi/stats.go +++ b/experimental/v2rayapi/stats.go @@ -7,11 +7,11 @@ import ( "runtime" "strings" "sync" + "sync/atomic" "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" N "github.com/sagernet/sing/common/network" @@ -22,7 +22,7 @@ func init() { } var ( - _ adapter.V2RayStatsService = (*StatsService)(nil) + _ adapter.ConnectionTracker = (*StatsService)(nil) _ StatsServiceServer = (*StatsService)(nil) ) @@ -60,7 +60,10 @@ func NewStatsService(options option.V2RayStatsServiceOptions) *StatsService { } } -func (s *StatsService) RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn { +func (s *StatsService) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) net.Conn { + inbound := metadata.Inbound + user := metadata.User + outbound := matchOutbound.Tag() var readCounter []*atomic.Int64 var writeCounter []*atomic.Int64 countInbound := inbound != "" && s.inbounds[inbound] @@ -86,7 +89,10 @@ func (s *StatsService) RoutedConnection(inbound string, outbound string, user st return bufio.NewInt64CounterConn(conn, readCounter, writeCounter) } -func (s *StatsService) RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn { +func (s *StatsService) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) N.PacketConn { + inbound := metadata.Inbound + user := metadata.User + outbound := matchOutbound.Tag() var readCounter []*atomic.Int64 var writeCounter []*atomic.Int64 countInbound := inbound != "" && s.inbounds[inbound] @@ -109,7 +115,7 @@ func (s *StatsService) RoutedPacketConnection(inbound string, outbound string, u writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink")) } s.access.Unlock() - return bufio.NewInt64CounterPacketConn(conn, readCounter, writeCounter) + return bufio.NewInt64CounterPacketConn(conn, readCounter, nil, writeCounter, nil) } func (s *StatsService) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) { @@ -186,7 +192,7 @@ func (s *StatsService) GetSysStats(ctx context.Context, request *SysStatsRequest var rtm runtime.MemStats runtime.ReadMemStats(&rtm) response := &SysStatsResponse{ - Uptime: uint32(time.Now().Sub(s.createdAt).Seconds()), + Uptime: uint32(time.Since(s.createdAt).Seconds()), NumGoroutine: uint32(runtime.NumGoroutine()), Alloc: rtm.Alloc, TotalAlloc: rtm.TotalAlloc, diff --git a/experimental/v2rayapi/stats.pb.go b/experimental/v2rayapi/stats.pb.go index 45cde82441..586b9a7fdf 100644 --- a/experimental/v2rayapi/stats.pb.go +++ b/experimental/v2rayapi/stats.pb.go @@ -3,6 +3,7 @@ package v2rayapi import ( reflect "reflect" sync "sync" + unsafe "unsafe" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" @@ -16,23 +17,20 @@ const ( ) type GetStatsRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Name of the stat counter. Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // Whether or not to reset the counter to fetching its value. - Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"` + Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *GetStatsRequest) Reset() { *x = GetStatsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_experimental_v2rayapi_stats_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_experimental_v2rayapi_stats_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetStatsRequest) String() string { @@ -43,7 +41,7 @@ func (*GetStatsRequest) ProtoMessage() {} func (x *GetStatsRequest) ProtoReflect() protoreflect.Message { mi := &file_experimental_v2rayapi_stats_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -73,21 +71,18 @@ func (x *GetStatsRequest) GetReset_() bool { } type Stat struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Value int64 `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"` unknownFields protoimpl.UnknownFields - - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Value int64 `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"` + sizeCache protoimpl.SizeCache } func (x *Stat) Reset() { *x = Stat{} - if protoimpl.UnsafeEnabled { - mi := &file_experimental_v2rayapi_stats_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_experimental_v2rayapi_stats_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Stat) String() string { @@ -98,7 +93,7 @@ func (*Stat) ProtoMessage() {} func (x *Stat) ProtoReflect() protoreflect.Message { mi := &file_experimental_v2rayapi_stats_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -128,20 +123,17 @@ func (x *Stat) GetValue() int64 { } type GetStatsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Stat *Stat `protobuf:"bytes,1,opt,name=stat,proto3" json:"stat,omitempty"` unknownFields protoimpl.UnknownFields - - Stat *Stat `protobuf:"bytes,1,opt,name=stat,proto3" json:"stat,omitempty"` + sizeCache protoimpl.SizeCache } func (x *GetStatsResponse) Reset() { *x = GetStatsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_experimental_v2rayapi_stats_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_experimental_v2rayapi_stats_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetStatsResponse) String() string { @@ -152,7 +144,7 @@ func (*GetStatsResponse) ProtoMessage() {} func (x *GetStatsResponse) ProtoReflect() protoreflect.Message { mi := &file_experimental_v2rayapi_stats_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -175,24 +167,21 @@ func (x *GetStatsResponse) GetStat() *Stat { } type QueryStatsRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Deprecated, use Patterns instead - Pattern string `protobuf:"bytes,1,opt,name=pattern,proto3" json:"pattern,omitempty"` - Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"` - Patterns []string `protobuf:"bytes,3,rep,name=patterns,proto3" json:"patterns,omitempty"` - Regexp bool `protobuf:"varint,4,opt,name=regexp,proto3" json:"regexp,omitempty"` + Pattern string `protobuf:"bytes,1,opt,name=pattern,proto3" json:"pattern,omitempty"` + Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"` + Patterns []string `protobuf:"bytes,3,rep,name=patterns,proto3" json:"patterns,omitempty"` + Regexp bool `protobuf:"varint,4,opt,name=regexp,proto3" json:"regexp,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *QueryStatsRequest) Reset() { *x = QueryStatsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_experimental_v2rayapi_stats_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_experimental_v2rayapi_stats_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *QueryStatsRequest) String() string { @@ -203,7 +192,7 @@ func (*QueryStatsRequest) ProtoMessage() {} func (x *QueryStatsRequest) ProtoReflect() protoreflect.Message { mi := &file_experimental_v2rayapi_stats_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -247,20 +236,17 @@ func (x *QueryStatsRequest) GetRegexp() bool { } type QueryStatsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Stat []*Stat `protobuf:"bytes,1,rep,name=stat,proto3" json:"stat,omitempty"` unknownFields protoimpl.UnknownFields - - Stat []*Stat `protobuf:"bytes,1,rep,name=stat,proto3" json:"stat,omitempty"` + sizeCache protoimpl.SizeCache } func (x *QueryStatsResponse) Reset() { *x = QueryStatsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_experimental_v2rayapi_stats_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_experimental_v2rayapi_stats_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *QueryStatsResponse) String() string { @@ -271,7 +257,7 @@ func (*QueryStatsResponse) ProtoMessage() {} func (x *QueryStatsResponse) ProtoReflect() protoreflect.Message { mi := &file_experimental_v2rayapi_stats_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -294,18 +280,16 @@ func (x *QueryStatsResponse) GetStat() []*Stat { } type SysStatsRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *SysStatsRequest) Reset() { *x = SysStatsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_experimental_v2rayapi_stats_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_experimental_v2rayapi_stats_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SysStatsRequest) String() string { @@ -316,7 +300,7 @@ func (*SysStatsRequest) ProtoMessage() {} func (x *SysStatsRequest) ProtoReflect() protoreflect.Message { mi := &file_experimental_v2rayapi_stats_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -332,29 +316,26 @@ func (*SysStatsRequest) Descriptor() ([]byte, []int) { } type SysStatsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + NumGoroutine uint32 `protobuf:"varint,1,opt,name=NumGoroutine,proto3" json:"NumGoroutine,omitempty"` + NumGC uint32 `protobuf:"varint,2,opt,name=NumGC,proto3" json:"NumGC,omitempty"` + Alloc uint64 `protobuf:"varint,3,opt,name=Alloc,proto3" json:"Alloc,omitempty"` + TotalAlloc uint64 `protobuf:"varint,4,opt,name=TotalAlloc,proto3" json:"TotalAlloc,omitempty"` + Sys uint64 `protobuf:"varint,5,opt,name=Sys,proto3" json:"Sys,omitempty"` + Mallocs uint64 `protobuf:"varint,6,opt,name=Mallocs,proto3" json:"Mallocs,omitempty"` + Frees uint64 `protobuf:"varint,7,opt,name=Frees,proto3" json:"Frees,omitempty"` + LiveObjects uint64 `protobuf:"varint,8,opt,name=LiveObjects,proto3" json:"LiveObjects,omitempty"` + PauseTotalNs uint64 `protobuf:"varint,9,opt,name=PauseTotalNs,proto3" json:"PauseTotalNs,omitempty"` + Uptime uint32 `protobuf:"varint,10,opt,name=Uptime,proto3" json:"Uptime,omitempty"` unknownFields protoimpl.UnknownFields - - NumGoroutine uint32 `protobuf:"varint,1,opt,name=NumGoroutine,proto3" json:"NumGoroutine,omitempty"` - NumGC uint32 `protobuf:"varint,2,opt,name=NumGC,proto3" json:"NumGC,omitempty"` - Alloc uint64 `protobuf:"varint,3,opt,name=Alloc,proto3" json:"Alloc,omitempty"` - TotalAlloc uint64 `protobuf:"varint,4,opt,name=TotalAlloc,proto3" json:"TotalAlloc,omitempty"` - Sys uint64 `protobuf:"varint,5,opt,name=Sys,proto3" json:"Sys,omitempty"` - Mallocs uint64 `protobuf:"varint,6,opt,name=Mallocs,proto3" json:"Mallocs,omitempty"` - Frees uint64 `protobuf:"varint,7,opt,name=Frees,proto3" json:"Frees,omitempty"` - LiveObjects uint64 `protobuf:"varint,8,opt,name=LiveObjects,proto3" json:"LiveObjects,omitempty"` - PauseTotalNs uint64 `protobuf:"varint,9,opt,name=PauseTotalNs,proto3" json:"PauseTotalNs,omitempty"` - Uptime uint32 `protobuf:"varint,10,opt,name=Uptime,proto3" json:"Uptime,omitempty"` + sizeCache protoimpl.SizeCache } func (x *SysStatsResponse) Reset() { *x = SysStatsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_experimental_v2rayapi_stats_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_experimental_v2rayapi_stats_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SysStatsResponse) String() string { @@ -365,7 +346,7 @@ func (*SysStatsResponse) ProtoMessage() {} func (x *SysStatsResponse) ProtoReflect() protoreflect.Message { mi := &file_experimental_v2rayapi_stats_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -452,94 +433,60 @@ func (x *SysStatsResponse) GetUptime() uint32 { var File_experimental_v2rayapi_stats_proto protoreflect.FileDescriptor -var file_experimental_v2rayapi_stats_proto_rawDesc = []byte{ - 0x0a, 0x21, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x2f, 0x76, - 0x32, 0x72, 0x61, 0x79, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x12, 0x15, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, - 0x6c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x61, 0x70, 0x69, 0x22, 0x3b, 0x0a, 0x0f, 0x47, 0x65, - 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x22, 0x30, 0x0a, 0x04, 0x53, 0x74, 0x61, 0x74, 0x12, - 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x43, 0x0a, 0x10, 0x47, 0x65, 0x74, - 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, - 0x04, 0x73, 0x74, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x65, 0x78, - 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, - 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x52, 0x04, 0x73, 0x74, 0x61, 0x74, 0x22, 0x77, - 0x0a, 0x11, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x12, 0x14, 0x0a, - 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x72, 0x65, - 0x73, 0x65, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x73, 0x18, - 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x73, 0x12, - 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x06, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x22, 0x45, 0x0a, 0x12, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, - 0x04, 0x73, 0x74, 0x61, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x65, 0x78, - 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, - 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x52, 0x04, 0x73, 0x74, 0x61, 0x74, 0x22, 0x11, - 0x0a, 0x0f, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x22, 0xa2, 0x02, 0x0a, 0x10, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x4e, 0x75, 0x6d, 0x47, 0x6f, 0x72, - 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x4e, 0x75, - 0x6d, 0x47, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x75, - 0x6d, 0x47, 0x43, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x4e, 0x75, 0x6d, 0x47, 0x43, - 0x12, 0x14, 0x0a, 0x05, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x05, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x41, - 0x6c, 0x6c, 0x6f, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x54, 0x6f, 0x74, 0x61, - 0x6c, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x12, 0x10, 0x0a, 0x03, 0x53, 0x79, 0x73, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x03, 0x53, 0x79, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x4d, 0x61, 0x6c, 0x6c, - 0x6f, 0x63, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x4d, 0x61, 0x6c, 0x6c, 0x6f, - 0x63, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x46, 0x72, 0x65, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x05, 0x46, 0x72, 0x65, 0x65, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x4c, 0x69, 0x76, 0x65, - 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x4c, - 0x69, 0x76, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x50, 0x61, - 0x75, 0x73, 0x65, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x0c, 0x50, 0x61, 0x75, 0x73, 0x65, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x73, 0x12, 0x16, - 0x0a, 0x06, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, - 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x32, 0xb4, 0x02, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x73, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5d, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x53, 0x74, - 0x61, 0x74, 0x73, 0x12, 0x26, 0x2e, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, - 0x61, 0x6c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x53, - 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x65, 0x78, - 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, - 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x63, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, - 0x74, 0x61, 0x74, 0x73, 0x12, 0x28, 0x2e, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, - 0x74, 0x61, 0x6c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x61, 0x70, 0x69, 0x2e, 0x51, 0x75, 0x65, - 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, - 0x2e, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x2e, 0x76, 0x32, - 0x72, 0x61, 0x79, 0x61, 0x70, 0x69, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x60, 0x0a, 0x0b, 0x47, - 0x65, 0x74, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x26, 0x2e, 0x65, 0x78, 0x70, - 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x61, - 0x70, 0x69, 0x2e, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, - 0x6c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x79, 0x73, 0x53, 0x74, - 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x34, 0x5a, - 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x61, 0x67, 0x65, - 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x73, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x6f, 0x78, 0x2f, 0x65, 0x78, - 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, - 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +const file_experimental_v2rayapi_stats_proto_rawDesc = "" + + "\n" + + "!experimental/v2rayapi/stats.proto\x12\x15experimental.v2rayapi\";\n" + + "\x0fGetStatsRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + + "\x05reset\x18\x02 \x01(\bR\x05reset\"0\n" + + "\x04Stat\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + + "\x05value\x18\x02 \x01(\x03R\x05value\"C\n" + + "\x10GetStatsResponse\x12/\n" + + "\x04stat\x18\x01 \x01(\v2\x1b.experimental.v2rayapi.StatR\x04stat\"w\n" + + "\x11QueryStatsRequest\x12\x18\n" + + "\apattern\x18\x01 \x01(\tR\apattern\x12\x14\n" + + "\x05reset\x18\x02 \x01(\bR\x05reset\x12\x1a\n" + + "\bpatterns\x18\x03 \x03(\tR\bpatterns\x12\x16\n" + + "\x06regexp\x18\x04 \x01(\bR\x06regexp\"E\n" + + "\x12QueryStatsResponse\x12/\n" + + "\x04stat\x18\x01 \x03(\v2\x1b.experimental.v2rayapi.StatR\x04stat\"\x11\n" + + "\x0fSysStatsRequest\"\xa2\x02\n" + + "\x10SysStatsResponse\x12\"\n" + + "\fNumGoroutine\x18\x01 \x01(\rR\fNumGoroutine\x12\x14\n" + + "\x05NumGC\x18\x02 \x01(\rR\x05NumGC\x12\x14\n" + + "\x05Alloc\x18\x03 \x01(\x04R\x05Alloc\x12\x1e\n" + + "\n" + + "TotalAlloc\x18\x04 \x01(\x04R\n" + + "TotalAlloc\x12\x10\n" + + "\x03Sys\x18\x05 \x01(\x04R\x03Sys\x12\x18\n" + + "\aMallocs\x18\x06 \x01(\x04R\aMallocs\x12\x14\n" + + "\x05Frees\x18\a \x01(\x04R\x05Frees\x12 \n" + + "\vLiveObjects\x18\b \x01(\x04R\vLiveObjects\x12\"\n" + + "\fPauseTotalNs\x18\t \x01(\x04R\fPauseTotalNs\x12\x16\n" + + "\x06Uptime\x18\n" + + " \x01(\rR\x06Uptime2\xb4\x02\n" + + "\fStatsService\x12]\n" + + "\bGetStats\x12&.experimental.v2rayapi.GetStatsRequest\x1a'.experimental.v2rayapi.GetStatsResponse\"\x00\x12c\n" + + "\n" + + "QueryStats\x12(.experimental.v2rayapi.QueryStatsRequest\x1a).experimental.v2rayapi.QueryStatsResponse\"\x00\x12`\n" + + "\vGetSysStats\x12&.experimental.v2rayapi.SysStatsRequest\x1a'.experimental.v2rayapi.SysStatsResponse\"\x00B4Z2github.com/sagernet/sing-box/experimental/v2rayapib\x06proto3" var ( file_experimental_v2rayapi_stats_proto_rawDescOnce sync.Once - file_experimental_v2rayapi_stats_proto_rawDescData = file_experimental_v2rayapi_stats_proto_rawDesc + file_experimental_v2rayapi_stats_proto_rawDescData []byte ) func file_experimental_v2rayapi_stats_proto_rawDescGZIP() []byte { file_experimental_v2rayapi_stats_proto_rawDescOnce.Do(func() { - file_experimental_v2rayapi_stats_proto_rawDescData = protoimpl.X.CompressGZIP(file_experimental_v2rayapi_stats_proto_rawDescData) + file_experimental_v2rayapi_stats_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_experimental_v2rayapi_stats_proto_rawDesc), len(file_experimental_v2rayapi_stats_proto_rawDesc))) }) return file_experimental_v2rayapi_stats_proto_rawDescData } var ( file_experimental_v2rayapi_stats_proto_msgTypes = make([]protoimpl.MessageInfo, 7) - file_experimental_v2rayapi_stats_proto_goTypes = []interface{}{ + file_experimental_v2rayapi_stats_proto_goTypes = []any{ (*GetStatsRequest)(nil), // 0: experimental.v2rayapi.GetStatsRequest (*Stat)(nil), // 1: experimental.v2rayapi.Stat (*GetStatsResponse)(nil), // 2: experimental.v2rayapi.GetStatsResponse @@ -571,97 +518,11 @@ func file_experimental_v2rayapi_stats_proto_init() { if File_experimental_v2rayapi_stats_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_experimental_v2rayapi_stats_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_experimental_v2rayapi_stats_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Stat); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_experimental_v2rayapi_stats_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_experimental_v2rayapi_stats_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*QueryStatsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_experimental_v2rayapi_stats_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*QueryStatsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_experimental_v2rayapi_stats_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SysStatsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_experimental_v2rayapi_stats_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SysStatsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_experimental_v2rayapi_stats_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_experimental_v2rayapi_stats_proto_rawDesc), len(file_experimental_v2rayapi_stats_proto_rawDesc)), NumEnums: 0, NumMessages: 7, NumExtensions: 0, @@ -672,7 +533,6 @@ func file_experimental_v2rayapi_stats_proto_init() { MessageInfos: file_experimental_v2rayapi_stats_proto_msgTypes, }.Build() File_experimental_v2rayapi_stats_proto = out.File - file_experimental_v2rayapi_stats_proto_rawDesc = nil file_experimental_v2rayapi_stats_proto_goTypes = nil file_experimental_v2rayapi_stats_proto_depIdxs = nil } diff --git a/experimental/v2rayapi/stats_grpc.pb.go b/experimental/v2rayapi/stats_grpc.pb.go index ee95028747..0745899f04 100644 --- a/experimental/v2rayapi/stats_grpc.pb.go +++ b/experimental/v2rayapi/stats_grpc.pb.go @@ -10,8 +10,8 @@ import ( // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 const ( StatsService_GetStats_FullMethodName = "/experimental.v2rayapi.StatsService/GetStats" @@ -37,8 +37,9 @@ func NewStatsServiceClient(cc grpc.ClientConnInterface) StatsServiceClient { } func (c *statsServiceClient) GetStats(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetStatsResponse) - err := c.cc.Invoke(ctx, StatsService_GetStats_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, StatsService_GetStats_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -46,8 +47,9 @@ func (c *statsServiceClient) GetStats(ctx context.Context, in *GetStatsRequest, } func (c *statsServiceClient) QueryStats(ctx context.Context, in *QueryStatsRequest, opts ...grpc.CallOption) (*QueryStatsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(QueryStatsResponse) - err := c.cc.Invoke(ctx, StatsService_QueryStats_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, StatsService_QueryStats_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -55,8 +57,9 @@ func (c *statsServiceClient) QueryStats(ctx context.Context, in *QueryStatsReque } func (c *statsServiceClient) GetSysStats(ctx context.Context, in *SysStatsRequest, opts ...grpc.CallOption) (*SysStatsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(SysStatsResponse) - err := c.cc.Invoke(ctx, StatsService_GetSysStats_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, StatsService_GetSysStats_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -65,7 +68,7 @@ func (c *statsServiceClient) GetSysStats(ctx context.Context, in *SysStatsReques // StatsServiceServer is the server API for StatsService service. // All implementations must embed UnimplementedStatsServiceServer -// for forward compatibility +// for forward compatibility. type StatsServiceServer interface { GetStats(context.Context, *GetStatsRequest) (*GetStatsResponse, error) QueryStats(context.Context, *QueryStatsRequest) (*QueryStatsResponse, error) @@ -73,21 +76,26 @@ type StatsServiceServer interface { mustEmbedUnimplementedStatsServiceServer() } -// UnimplementedStatsServiceServer must be embedded to have forward compatible implementations. +// UnimplementedStatsServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. type UnimplementedStatsServiceServer struct{} func (UnimplementedStatsServiceServer) GetStats(context.Context, *GetStatsRequest) (*GetStatsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetStats not implemented") + return nil, status.Error(codes.Unimplemented, "method GetStats not implemented") } func (UnimplementedStatsServiceServer) QueryStats(context.Context, *QueryStatsRequest) (*QueryStatsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method QueryStats not implemented") + return nil, status.Error(codes.Unimplemented, "method QueryStats not implemented") } func (UnimplementedStatsServiceServer) GetSysStats(context.Context, *SysStatsRequest) (*SysStatsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetSysStats not implemented") + return nil, status.Error(codes.Unimplemented, "method GetSysStats not implemented") } func (UnimplementedStatsServiceServer) mustEmbedUnimplementedStatsServiceServer() {} +func (UnimplementedStatsServiceServer) testEmbeddedByValue() {} // UnsafeStatsServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to StatsServiceServer will @@ -97,6 +105,13 @@ type UnsafeStatsServiceServer interface { } func RegisterStatsServiceServer(s grpc.ServiceRegistrar, srv StatsServiceServer) { + // If the following call panics, it indicates UnimplementedStatsServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } s.RegisterService(&StatsService_ServiceDesc, srv) } diff --git a/go.mod b/go.mod index 9999f4744e..ae5e97465f 100644 --- a/go.mod +++ b/go.mod @@ -1,97 +1,289 @@ module github.com/sagernet/sing-box -go 1.20 +go 1.25.6 + +require github.com/amnezia-vpn/amneziawg-go v0.2.17-0.20251219021448-449d7cffd4ad require ( - berty.tech/go-libtor v1.0.385 - github.com/caddyserver/certmagic v0.20.0 - github.com/cloudflare/circl v1.3.7 + github.com/Diniboy1123/connect-ip-go v0.0.0-20260409225322-8d7bb0a858a2 + github.com/Psiphon-Labs/psiphon-tunnel-core v1.0.11-0.20260126173038-c86a1497a760 + github.com/anthropics/anthropic-sdk-go v1.26.0 + github.com/anytls/sing-anytls v0.0.11 + github.com/caddyserver/certmagic v0.25.2 + github.com/caddyserver/zerossl v0.1.5 + github.com/coder/websocket v1.8.14 github.com/cretz/bine v0.2.0 - github.com/fsnotify/fsnotify v1.7.0 - github.com/go-chi/chi/v5 v5.0.12 - github.com/go-chi/cors v1.2.1 + github.com/database64128/tfo-go/v2 v2.3.2 + github.com/enfein/mieru/v3 v3.27.0 + github.com/go-chi/chi/v5 v5.2.5 github.com/go-chi/render v1.0.3 - github.com/gofrs/uuid/v5 v5.2.0 - github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 - github.com/libdns/alidns v1.0.3 - github.com/libdns/cloudflare v0.1.1 + github.com/godbus/dbus/v5 v5.2.2 + github.com/gofrs/uuid/v5 v5.4.0 + github.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91 + github.com/jsimonetti/rtnetlink v1.4.0 + github.com/keybase/go-keychain v0.0.1 + github.com/kianmhz/GooseRelayVPN v0.0.0-20260429125124-0e68c2a3ae4c + github.com/libdns/acmedns v0.5.0 + github.com/libdns/alidns v1.0.6 + github.com/libdns/cloudflare v0.2.2 + github.com/libdns/libdns v1.1.1 github.com/logrusorgru/aurora v2.0.3+incompatible - github.com/mholt/acmez v1.2.0 - github.com/miekg/dns v1.1.59 - github.com/ooni/go-libtor v1.1.8 - github.com/oschwald/maxminddb-golang v1.12.0 + github.com/mdlayher/netlink v1.9.0 + github.com/metacubex/utls v1.8.4 + github.com/mholt/acmez/v3 v3.1.6 + github.com/miekg/dns v1.1.72 + github.com/net2share/vaydns v0.2.6 + github.com/openai/openai-go/v3 v3.26.0 + github.com/oschwald/geoip2-golang v1.9.0 + github.com/oschwald/maxminddb-golang v1.13.1 + github.com/pires/go-proxyproto v0.8.1 + github.com/reF1nd/sing-snell v0.0.5 + github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a - github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 - github.com/sagernet/gomobile v0.1.3 - github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f - github.com/sagernet/quic-go v0.46.0-beta.4 - github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.4.2 - github.com/sagernet/sing-dns v0.2.3 - github.com/sagernet/sing-mux v0.2.0 - github.com/sagernet/sing-quic v0.2.2 - github.com/sagernet/sing-shadowsocks v0.2.7 - github.com/sagernet/sing-shadowsocks2 v0.2.0 - github.com/sagernet/sing-shadowtls v0.1.4 - github.com/sagernet/sing-tun v0.3.2 - github.com/sagernet/sing-vmess v0.1.12 - github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 - github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 - github.com/sagernet/utls v1.5.4 - github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 + github.com/sagernet/cors v1.2.1 + github.com/sagernet/cronet-go v0.0.0-20260413093659-e4926ba205fa + github.com/sagernet/cronet-go/all v0.0.0-20260413093659-e4926ba205fa + github.com/sagernet/fswatch v0.1.1 + github.com/sagernet/gomobile v0.1.12 + github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 + github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 + github.com/sagernet/sing v0.8.11-0.20260514110501-905ad103a4df + github.com/sagernet/sing-cloudflared v0.0.0-20260416083718-efa6ab16dba9 + github.com/sagernet/sing-mux v0.3.4 + github.com/sagernet/sing-quic v0.6.2-0.20260525051024-9467ede27fb7 + github.com/sagernet/sing-shadowsocks v0.2.8 + github.com/sagernet/sing-shadowsocks2 v0.2.1 + github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 + github.com/sagernet/sing-tun v0.8.10-0.20260519125758-eb58efc8915d + github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 + github.com/sagernet/smux v1.5.50-sing-box-mod.1 + github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7 + github.com/sagernet/wireguard-go v0.0.3 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 - github.com/spf13/cobra v1.8.0 - github.com/stretchr/testify v1.9.0 - go.uber.org/zap v1.27.0 + github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 + github.com/spf13/cobra v1.10.2 + github.com/stretchr/testify v1.11.1 + github.com/tidwall/gjson v1.18.0 + github.com/vishvananda/netns v0.0.5 + github.com/yosida95/uritemplate/v3 v3.0.2 + go.uber.org/zap v1.27.1 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.23.0 - golang.org/x/net v0.25.0 - golang.org/x/sys v0.21.0 - golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 - google.golang.org/grpc v1.63.2 - google.golang.org/protobuf v1.33.0 + golang.org/x/crypto v0.48.0 + golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 + golang.org/x/mod v0.33.0 + golang.org/x/net v0.50.0 + golang.org/x/sys v0.41.0 + golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 + google.golang.org/grpc v1.79.1 + google.golang.org/protobuf v1.36.11 howett.net/plist v1.0.1 ) +require ( + github.com/dunglas/httpsfv v1.0.2 // indirect + github.com/quic-go/quic-go v0.59.0 // indirect +) + +require ( + github.com/AdguardTeam/golibs v0.32.7 // indirect + github.com/ameshkov/dnscrypt/v2 v2.4.0 + github.com/ameshkov/dnsstamps v1.0.3 // indirect + github.com/biter777/countries v1.7.5 + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + golang.org/x/sync v0.19.0 + golang.org/x/text v0.34.0 // indirect + golang.org/x/tools v0.42.0 // indirect +) + //replace github.com/sagernet/sing => ../sing require ( + filippo.io/bigmod v0.0.1 // indirect + filippo.io/edwards25519 v1.1.0 // indirect + filippo.io/keygen v0.0.0-20230306160926-5201437acf8e // indirect + github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect + github.com/Jigsaw-Code/outline-sdk v0.0.20 // indirect + github.com/Psiphon-Labs/bolt v0.0.0-20200624191537-23cedaef7ad7 // indirect + github.com/Psiphon-Labs/consistent v0.0.0-20240322131436-20aaa4e05737 // indirect + github.com/Psiphon-Labs/goptlib v0.0.0-20200406165125-c0e32a7a3464 // indirect + github.com/Psiphon-Labs/psiphon-tls v0.0.0-20250318183125-2a2fae2db378 // indirect + github.com/Psiphon-Labs/quic-go v0.0.0-20250527153145-79fe45fb83b1 // indirect + github.com/Psiphon-Labs/utls v0.0.0-20260129182755-24497d415a8d // indirect github.com/ajg/form v1.5.1 // indirect - github.com/andybalholm/brotli v1.0.6 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/gaukas/godicttls v0.0.4 // indirect + github.com/akutz/memconn v0.1.0 // indirect + github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect + github.com/andybalholm/brotli v1.2.0 // indirect + github.com/armon/go-proxyproto v0.0.0-20180202201750-5b7edb60ff5f // indirect + github.com/axiomhq/hyperloglog v0.2.6 // indirect + github.com/bifurcation/mint v0.0.0-20180306135233-198357931e61 // indirect + github.com/bits-and-blooms/bitset v1.10.0 // indirect + github.com/bits-and-blooms/bloom/v3 v3.6.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9 // indirect + github.com/cloudflare/circl v1.6.1 // indirect + github.com/cognusion/go-cache-lru v0.0.0-20170419142635-f73e2280ecea // indirect + github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect + github.com/coreos/go-oidc/v3 v3.17.0 // indirect + github.com/database64128/netx-go v0.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect + github.com/dchest/siphash v1.2.3 // indirect + github.com/dgraph-io/badger v1.5.4-0.20180815194500-3a87f6d9c273 // indirect + github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect + github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da // indirect + github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect + github.com/ebitengine/purego v0.9.1 // indirect + github.com/florianl/go-nfqueue/v2 v2.0.2 // indirect + github.com/flynn/noise v1.1.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/gaissmai/bart v0.18.0 // indirect + github.com/go-jose/go-jose/v4 v4.1.3 // indirect + github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect - github.com/google/btree v1.1.2 // indirect - github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect - github.com/hashicorp/yamux v0.1.1 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grafov/m3u8 v0.0.0-20171211212457-6ab8f28ed427 // indirect + github.com/hashicorp/yamux v0.1.2 // indirect + github.com/hdevalence/ed25519consensus v0.2.0 // indirect + github.com/hiddify/hmrd_multi_resolver_dns v0.0.0-20260429114007-8d809dc33d0e github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/josharian/native v1.1.0 // indirect - github.com/klauspost/compress v1.17.4 // indirect - github.com/klauspost/cpuid/v2 v2.2.5 // indirect - github.com/libdns/libdns v0.2.2 // indirect - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect - github.com/onsi/ginkgo/v2 v2.9.7 // indirect - github.com/pierrec/lz4/v4 v4.1.14 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-20 v0.4.1 // indirect - github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect - github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect - github.com/zeebo/blake3 v0.2.3 // indirect + github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect + github.com/kamstrup/intmap v0.5.2 // indirect + github.com/klauspost/compress v1.18.3 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 + github.com/klauspost/reedsolomon v1.13.0 // indirect + github.com/libp2p/go-reuseport v0.4.0 // indirect + github.com/marusama/semaphore v0.0.0-20171214154724-565ffd8e868a // indirect + github.com/mdlayher/socket v0.5.1 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/mroth/weightedrand v1.0.0 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/philhofer/fwd v1.2.0 // indirect + github.com/pierrec/lz4/v4 v4.1.21 // indirect + github.com/pion/datachannel v1.5.5 // indirect + github.com/pion/dtls/v2 v2.2.7 // indirect + github.com/pion/ice/v2 v2.3.24 // indirect + github.com/pion/interceptor v0.1.25 // indirect + github.com/pion/logging v0.2.2 // indirect + github.com/pion/mdns v0.0.12 // indirect + github.com/pion/randutil v0.1.0 // indirect + github.com/pion/rtcp v1.2.12 // indirect + github.com/pion/rtp v1.8.5 // indirect + github.com/pion/sctp v1.8.16 // indirect + github.com/pion/sdp/v3 v3.0.9 // indirect + github.com/pion/srtp/v2 v2.0.18 // indirect + github.com/pion/stun v0.6.1 // indirect + github.com/pion/transport/v2 v2.2.4 // indirect + github.com/pion/turn/v2 v2.1.3 // indirect + github.com/pion/webrtc/v3 v3.2.40 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus-community/pro-bing v0.4.0 // indirect + github.com/quic-go/qpack v0.6.0 // indirect + github.com/refraction-networking/conjure v0.7.11-0.20240130155008-c8df96195ab2 // indirect + github.com/refraction-networking/ed25519 v0.1.2 // indirect + github.com/refraction-networking/gotapdance v1.7.10 // indirect + github.com/refraction-networking/obfs4 v0.1.2 // indirect + github.com/refraction-networking/utls v1.8.2 // indirect + github.com/safchain/ethtool v0.3.0 // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect + github.com/sagernet/nftables v0.3.0-mod.2 // indirect + github.com/sergeyfrolov/bsbuffer v0.0.0-20180903213811-94e85abb8507 // indirect + github.com/shadowsocks/go-shadowsocks2 v0.1.5 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect + github.com/spf13/pflag v1.0.9 // indirect + github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect + github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect + github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect + github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect + github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect + github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect + github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect + github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect + github.com/things-go/go-socks5 v0.1.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect + github.com/wader/filtertransport v0.0.0-20200316221534-bdd9e61eee78 // indirect + github.com/wlynxg/anet v0.0.5 // indirect + github.com/x448/float16 v0.8.4 // indirect + github.com/xtaci/kcp-go/v5 v5.6.70 // indirect + github.com/xtaci/smux v1.5.50 // indirect + github.com/zeebo/blake3 v0.2.4 // indirect + gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect - golang.org/x/mod v0.18.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/text v0.16.0 // indirect - golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect + go.uber.org/zap/exp v0.3.0 // indirect + go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/term v0.40.0 // indirect + golang.org/x/time v0.14.0 // indirect + golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect + golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect + golang.zx2c4.com/wireguard/windows v0.5.3 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - lukechampine.com/blake3 v1.3.0 // indirect + gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 // indirect + lukechampine.com/blake3 v1.4.1 + tailscale.com v1.58.2 // indirect + zombiezen.com/go/capnproto2 v2.18.2+incompatible // indirect ) + +replace github.com/sagernet/sing-vmess => github.com/starifly/sing-vmess v0.2.7-mod.9 + +replace github.com/Diniboy1123/connect-ip-go => github.com/shtorm-7/connect-ip-go v1.0.0-extended-1.0.0 + +replace github.com/sagernet/sing-dns => github.com/shtorm-7/sing-dns v0.4.6-extended-1.0.0 + +replace github.com/ameshkov/dnscrypt/v2 => github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0 + +// replace github.com/sagernet/wireguard-go => github.com/hiddify/wireguard-go v0.0.0-20260207195137-b12022450359 +replace github.com/sagernet/wireguard-go => ./replace/wireguard-go + +replace github.com/sagernet/tailscale => ./replace/tailscale + +replace github.com/Psiphon-Labs/quic-go => ./replace/psiphon-quic-go + +replace github.com/Psiphon-Labs/psiphon-tls => ./replace/psiphon-tls + +replace github.com/net2share/vaydns => github.com/hiddify/vaydns v0.0.0-20260401180616-890dc987a6a9 + +replace github.com/kianmhz/GooseRelayVPN => github.com/hiddify/GooseRelayVPN v0.0.0-20260429125124-0e68c2a3ae4c diff --git a/go.sum b/go.sum index 78b7cb63a1..614319a679 100644 --- a/go.sum +++ b/go.sum @@ -1,218 +1,806 @@ -berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw= -berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE= +code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM= +filippo.io/bigmod v0.0.1 h1:OaEqDr3gEbofpnHbGqZweSL/bLMhy1pb54puiCDeuOA= +filippo.io/bigmod v0.0.1/go.mod h1:KyzqAbH7bRH6MOuOF1TPfUjvLoi0mRF2bIyD2ouRNQI= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +filippo.io/keygen v0.0.0-20230306160926-5201437acf8e h1:+xwUCyMiCWKWsI0RowhzB4sngpUdMHgU6lLuWJCX5Dg= +filippo.io/keygen v0.0.0-20230306160926-5201437acf8e/go.mod h1:ZGSiF/b2hd6MRghF/cid0vXw8pXykRTmIu+JSPw/NCQ= +github.com/AdguardTeam/golibs v0.32.7 h1:3dmGlAVgmvquCCwHsvEl58KKcRAK3z1UnjMnwSIeDH4= +github.com/AdguardTeam/golibs v0.32.7/go.mod h1:bE8KV1zqTzgZjmjFyBJ9f9O5DEKO717r7e57j1HclJA= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/Jigsaw-Code/outline-sdk v0.0.20 h1:4ep7MK9lFmcyPIRIbn4xrP1VKdJNsqR6+iJEOHDKnNg= +github.com/Jigsaw-Code/outline-sdk v0.0.20/go.mod h1:CFDKyGZA4zatKE4vMLe8TyQpZCyINOeRFbMAmYHxodw= +github.com/Jigsaw-Code/outline-ss-server v1.8.0 h1:6h7CZsyl1vQLz3nvxmL9FbhDug4QxJ1YTxm534eye1E= +github.com/Jigsaw-Code/outline-ss-server v1.8.0/go.mod h1:slnHH3OZsQmZx/DRKhxvvaGE/8+n3Lkd6363h1ev71E= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e h1:NPfqIbzmijrl0VclX2t8eO5EPBhqe47LLGKpRrcVjXk= +github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e/go.mod h1:ZdY5pBfat/WVzw3eXbIf7N1nZN0XD5H5+X8ZMDWbCs4= +github.com/Psiphon-Inc/uds-ipc v1.0.1 h1:K3Z0cS1XfzDdhxWTIwh/hiLrkRR83ZxUo2bqgBOGuZE= +github.com/Psiphon-Inc/uds-ipc v1.0.1/go.mod h1:R8TGG+OXumorJdpAcSFE4SVTT2frMFEGY43Zc/44sNk= +github.com/Psiphon-Labs/bolt v0.0.0-20200624191537-23cedaef7ad7 h1:Hx/NCZTnvoKZuIBwSmxE58KKoNLXIGG6hBJYN7pj9Ag= +github.com/Psiphon-Labs/bolt v0.0.0-20200624191537-23cedaef7ad7/go.mod h1:alTtZBo3j4AWFvUrAH6F5ZaHcTj4G5Y01nHz8dkU6vU= +github.com/Psiphon-Labs/consistent v0.0.0-20240322131436-20aaa4e05737 h1:QTMy7Uc2Xc7fz6O/Khy1xi0VBND13GqzLUE2mHw6HUU= +github.com/Psiphon-Labs/consistent v0.0.0-20240322131436-20aaa4e05737/go.mod h1:Enj/Gszv2zCbuRbHbabmNvfO9EM+5kmaGj8CyjwNPlY= +github.com/Psiphon-Labs/goptlib v0.0.0-20200406165125-c0e32a7a3464 h1:VmnMMMheFXwLV0noxYhbJbLmkV4iaVW3xNnj6xcCNHo= +github.com/Psiphon-Labs/goptlib v0.0.0-20200406165125-c0e32a7a3464/go.mod h1:Pe5BqN2DdIdChorAXl6bDaQd/wghpCleJfid2NoSli0= +github.com/Psiphon-Labs/psiphon-tunnel-core v1.0.11-0.20260126173038-c86a1497a760 h1:R4O6UNd88eb1s/txuBTg99U/BZrDOGQgJ4pSL2MRBEs= +github.com/Psiphon-Labs/psiphon-tunnel-core v1.0.11-0.20260126173038-c86a1497a760/go.mod h1:FcBjozoGjC2I7ZID0lXFlVQcy/jx6irCzlPxapGebEY= +github.com/Psiphon-Labs/utls v0.0.0-20260129182755-24497d415a8d h1:PlKwrArEuQOVqEmThSs9KsXMiBduP8MSu9rlWmQ4jgE= +github.com/Psiphon-Labs/utls v0.0.0-20260129182755-24497d415a8d/go.mod h1:1vv0gVAzq9e2XYkW8HAKrmtuuZrBdDixQFx5H22KAjI= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= -github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= -github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg= -github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= -github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw= +github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A= +github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw= +github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= +github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= +github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo= +github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A= +github.com/amnezia-vpn/amneziawg-go v0.2.17-0.20251219021448-449d7cffd4ad h1:O3bvp109FZxwf3lWDd5lxmQz8NwQJgvk1nd60vnXMgw= +github.com/amnezia-vpn/amneziawg-go v0.2.17-0.20251219021448-449d7cffd4ad/go.mod h1:aMgOk9MuX0xI7b5TKAYp8pLM54RlXcOPzDvYw3YEO5A= +github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= +github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/anthropics/anthropic-sdk-go v1.26.0 h1:oUTzFaUpAevfuELAP1sjL6CQJ9HHAfT7CoSYSac11PY= +github.com/anthropics/anthropic-sdk-go v1.26.0/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q= +github.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc= +github.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= +github.com/armon/go-proxyproto v0.0.0-20180202201750-5b7edb60ff5f h1:SaJ6yqg936TshyeFZqQE+N+9hYkIeL9AMr7S4voCl10= +github.com/armon/go-proxyproto v0.0.0-20180202201750-5b7edb60ff5f/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU= +github.com/axiomhq/hyperloglog v0.2.6 h1:sRhvvF3RIXWQgAXaTphLp4yJiX4S0IN3MWTaAgZoRJw= +github.com/axiomhq/hyperloglog v0.2.6/go.mod h1:YjX/dQqCR/7QYX0g8mu8UZAjpIenz1FKM71UEsjFoTo= +github.com/bifurcation/mint v0.0.0-20180306135233-198357931e61 h1:BU+NxuoaYPIvvp8NNkNlLr8aA0utGyuunf4Q3LJ0bh0= +github.com/bifurcation/mint v0.0.0-20180306135233-198357931e61/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU= +github.com/biter777/countries v1.7.5 h1:MJ+n3+rSxWQdqVJU8eBy9RqcdH6ePPn4PJHocVWUa+Q= +github.com/biter777/countries v1.7.5/go.mod h1:1HSpZ526mYqKJcpT5Ti1kcGQ0L0SrXWIaptUWjFfv2E= +github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= +github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bits-and-blooms/bloom/v3 v3.6.0 h1:dTU0OVLJSoOhz9m68FTXMFfA39nR8U/nTCs1zb26mOI= +github.com/bits-and-blooms/bloom/v3 v3.6.0/go.mod h1:VKlUSvp0lFIYqxJjzdnSsZEw4iHb1kOL2tfHTgyJBHg= +github.com/caddyserver/certmagic v0.25.2 h1:D7xcS7ggX/WEY54x0czj7ioTkmDWKIgxtIi2OcQclUc= +github.com/caddyserver/certmagic v0.25.2/go.mod h1:llW/CvsNmza8S6hmsuggsZeiX+uS27dkqY27wDIuBWg= +github.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE= +github.com/caddyserver/zerossl v0.1.5/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9 h1:a1zrFsLFac2xoM6zG1u72DWJwZG3ayttYLfmLbxVETk= +github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= +github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk= +github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= +github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= +github.com/cognusion/go-cache-lru v0.0.0-20170419142635-f73e2280ecea h1:9C2rdYRp8Vzwhm3sbFX0yYfB+70zKFRjn7cnPCucHSw= +github.com/cognusion/go-cache-lru v0.0.0-20170419142635-f73e2280ecea/go.mod h1:MdyNkAe06D7xmJsf+MsLvbZKYNXuOHLKJrvw+x4LlcQ= +github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0= +github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= +github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc= +github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= +github.com/database64128/netx-go v0.1.1 h1:dT5LG7Gs7zFZBthFBbzWE6K8wAHjSNAaK7wCYZT7NzM= +github.com/database64128/netx-go v0.1.1/go.mod h1:LNlYVipaYkQArRFDNNJ02VkNV+My9A5XR/IGS7sIBQc= +github.com/database64128/tfo-go/v2 v2.3.2 h1:UhZMKiMq3swZGUiETkLBDzQnZBPSAeBMClpJGlnJ5Fw= +github.com/database64128/tfo-go/v2 v2.3.2/go.mod h1:GC3uB5oa4beGpCUbRb2ZOWP73bJJFmMyAVgQSO7r724= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk= +github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ= +github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA= +github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc= +github.com/deckarep/golang-set v0.0.0-20171013212420-1d4478f51bed h1:njG8LmGD6JCWJu4bwIKmkOHvch70UOEIqczl5vp7Gok= +github.com/deckarep/golang-set v0.0.0-20171013212420-1d4478f51bed/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= +github.com/dgraph-io/badger v1.5.4-0.20180815194500-3a87f6d9c273 h1:45qZ7jowabqhyi3l9Ervox4dhQvLGB5BJPdC8w0a77k= +github.com/dgraph-io/badger v1.5.4-0.20180815194500-3a87f6d9c273/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= +github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbYd8tQGRWacE9kU= +github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 h1:ucRHb6/lvW/+mTEIGbvhcYU3S8+uSNkuMjx/qZFfhtM= +github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/dunglas/httpsfv v1.0.2 h1:iERDp/YAfnojSDJ7PW3dj1AReJz4MrwbECSSE59JWL0= +github.com/dunglas/httpsfv v1.0.2/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg= +github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v0.0.0-20200809112317-0581fc3aee2d h1:rtM8HsT3NG37YPjz8sYSbUSdElP9lUsQENYzJDZDUBE= +github.com/elazarl/goproxy v0.0.0-20200809112317-0581fc3aee2d/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy/ext v0.0.0-20200809112317-0581fc3aee2d h1:st1tmvy+4duoRj+RaeeJoECWCWM015fBtf/4aR+hhqk= +github.com/elazarl/goproxy/ext v0.0.0-20200809112317-0581fc3aee2d/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= +github.com/enfein/mieru/v3 v3.27.0 h1:+E/1TF7OfimS2h582atEXQxPtJMyvqUTFBJUgzn1rxg= +github.com/enfein/mieru/v3 v3.27.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/florianl/go-nfqueue v1.1.1-0.20200829120558-a2f196e98ab0 h1:7ZJyJV4KiWBijCCzUPvVaqxsDxO36+KD0XKBdEN3I+8= +github.com/florianl/go-nfqueue v1.1.1-0.20200829120558-a2f196e98ab0/go.mod h1:2z3Tfqwv2ueuK6h563xUHRcCh1mv38wS9EjiWiesk84= +github.com/florianl/go-nfqueue/v2 v2.0.2 h1:FL5lQTeetgpCvac1TRwSfgaXUn0YSO7WzGvWNIp3JPE= +github.com/florianl/go-nfqueue/v2 v2.0.2/go.mod h1:VA09+iPOT43OMoCKNfXHyzujQUty2xmzyCRkBOlmabc= +github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= +github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= +github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= +github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= -github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= -github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= -github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= -github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/gaissmai/bart v0.18.0 h1:jQLBT/RduJu0pv/tLwXE+xKPgtWJejbxuXAR+wLJafo= +github.com/gaissmai/bart v0.18.0/go.mod h1:JJzMAhNF5Rjo4SF4jWBrANuJfqY+FvsFhW7t1UZJ+XY= +github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= +github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo= +github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= +github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced h1:Q311OHjMh/u5E2TITc++WlTP5We0xNseRMkHDyvhW7I= +github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gobwas/glob v0.2.4-0.20180402141543-f00a7392b439 h1:T6zlOdzrYuHf6HUKujm9bzkzbZ5Iv/xf6rs8BHZDpoI= +github.com/gobwas/glob v0.2.4-0.20180402141543-f00a7392b439/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gofrs/uuid/v5 v5.2.0 h1:qw1GMx6/y8vhVsx626ImfKMuS5CvJmhIKKtuyvfajMM= -github.com/gofrs/uuid/v5 v5.2.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0= +github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= -github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk= -github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= -github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= -github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= +github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI= +github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grafov/m3u8 v0.0.0-20171211212457-6ab8f28ed427 h1:xh96CCAZTX8LJPFoOVRgTwZbn2DvJl8fyCyivohhSIg= +github.com/grafov/m3u8 v0.0.0-20171211212457-6ab8f28ed427/go.mod h1:PdjzaU/pJUo4jTIn2rcgMFs+HqBGl/sPJLr8BI0Xq/I= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= +github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= +github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= +github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= +github.com/hiddify/GooseRelayVPN v0.0.0-20260429125124-0e68c2a3ae4c h1:9Zc0hJ14G+SNJvGxtaSCSsvGB/RdLltrgjgR//9zgTQ= +github.com/hiddify/GooseRelayVPN v0.0.0-20260429125124-0e68c2a3ae4c/go.mod h1:LI/1qA7FDU3MumegfyFOHGZCXcOw6rjIk7NPXga3/pQ= +github.com/hiddify/hmrd_multi_resolver_dns v0.0.0-20260429114007-8d809dc33d0e h1:GsHpSccRtSFe1l3hq0WtBjqsKjJdU+an2D+uCsXm+xk= +github.com/hiddify/hmrd_multi_resolver_dns v0.0.0-20260429114007-8d809dc33d0e/go.mod h1:7u9Ece+I3gvMFzcpqhROWaykNUyoE21g/fYQMBasAWY= +github.com/hiddify/vaydns v0.0.0-20260401180616-890dc987a6a9 h1:KXnaABX8hHmkcL0jbL769hEIGI5+z/DajCrlO+Bkzcc= +github.com/hiddify/vaydns v0.0.0-20260401180616-890dc987a6a9/go.mod h1:+8kEfQsZJn7/4aIppVekrSuqhrKjGBIgnacTJkdAlS8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= -github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= +github.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91 h1:u9i04mGE3iliBh0EFuWaKsmcwrLacqGmq1G3XoaM7gY= +github.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91/go.mod h1:qfvBmyDNp+/liLEYWRvqny/PEz9hGe2Dz833eXILSmo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= -github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= -github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk= +github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8= +github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= +github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= +github.com/kamstrup/intmap v0.5.2 h1:qnwBm1mh4XAnW9W9Ue9tZtTff8pS6+s6iKF6JRIV2Dk= +github.com/kamstrup/intmap v0.5.2/go.mod h1:gWUVWHKzWj8xpJVFf5GC0O26bWmv3GqdnIX/LMT6Aq4= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= +github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= +github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/reedsolomon v1.13.0 h1:E0Cmgf2kMuhZTj6eefnvpKC4/Q4jhCi9YIjcZjK4arc= +github.com/klauspost/reedsolomon v1.13.0/go.mod h1:ggJT9lc71Vu+cSOPBlxGvBN6TfAS77qB4fp8vJ05NSA= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ= -github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE= -github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054= -github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU= -github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= -github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= -github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU= +github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk= +github.com/letsencrypt/pebble/v2 v2.10.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U= +github.com/letsencrypt/pebble/v2 v2.10.0/go.mod h1:Sk8cmUIPcIdv2nINo+9PB4L+ZBhzY+F9A1a/h/xmWiQ= +github.com/libdns/acmedns v0.5.0 h1:5pRtmUj4Lb/QkNJSl1xgOGBUJTWW7RjpNaIhjpDXjPE= +github.com/libdns/acmedns v0.5.0/go.mod h1:X7UAFP1Ep9NpTwWpVlrZzJLR7epynAy0wrIxSPFgKjQ= +github.com/libdns/alidns v1.0.6 h1:/Ii428ty6WHFJmE24rZxq2taq++gh7rf9jhgLfp8PmM= +github.com/libdns/alidns v1.0.6/go.mod h1:RECwyQ88e9VqQVtSrvX76o1ux3gQUKGzMgxICi+u7Ec= +github.com/libdns/cloudflare v0.2.2 h1:XWHv+C1dDcApqazlh08Q6pjytYLgR2a+Y3xrXFu0vsI= +github.com/libdns/cloudflare v0.2.2/go.mod h1:w9uTmRCDlAoafAsTPnn2nJ0XHK/eaUMh86DUk8BWi60= +github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U= +github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s= +github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= -github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= -github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= -github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs= -github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= -github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= -github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= -github.com/ooni/go-libtor v1.1.8 h1:Wo3V3DVTxl5vZdxtQakqYP+DAHx7pPtAFSl1bnAa08w= -github.com/ooni/go-libtor v1.1.8/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI= -github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= -github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= -github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= -github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/marusama/semaphore v0.0.0-20171214154724-565ffd8e868a h1:6SRny9FLB1eWasPyDUqBQnMi9NhXU01XIlB0ao89YoI= +github.com/marusama/semaphore v0.0.0-20171214154724-565ffd8e868a/go.mod h1:TmeOqAKoDinfPfSohs14CO3VcEf7o+Bem6JiNe05yrQ= +github.com/mdlayher/netlink v1.9.0 h1:G8+GLq2x3v4D4MVIqDdNUhTUC7TKiCy/6MDkmItfKco= +github.com/mdlayher/netlink v1.9.0/go.mod h1:YBnl5BXsCoRuwBjKKlZ+aYmEoq0r12FDA/3JC+94KDg= +github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= +github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= +github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg= +github.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko= +github.com/mholt/acmez/v3 v3.1.6 h1:eGVQNObP0pBN4sxqrXeg7MYqTOWyoiYpQqITVWlrevk= +github.com/mholt/acmez/v3 v3.1.6/go.mod h1:5nTPosTGosLxF3+LU4ygbgMRFDhbAVpqMI4+a4aHLBY= +github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= +github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/mroth/weightedrand v1.0.0 h1:V8JeHChvl2MP1sAoXq4brElOcza+jxLkRuwvtQu8L3E= +github.com/mroth/weightedrand v1.0.0/go.mod h1:3p2SIcC8al1YMzGhAIoXD+r9olo/g/cdJgAD905gyNE= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI= +github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= +github.com/openai/openai-go/v3 v3.26.0 h1:bRt6H/ozMNt/dDkN4gobnLqaEGrRGBzmbVs0xxJEnQE= +github.com/openai/openai-go/v3 v3.26.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo= +github.com/oschwald/geoip2-golang v1.9.0 h1:uvD3O6fXAXs+usU+UGExshpdP13GAqp4GBrzN7IgKZc= +github.com/oschwald/geoip2-golang v1.9.0/go.mod h1:BHK6TvDyATVQhKNbQBdrj9eAvuwOMi2zSFXizL3K81Y= +github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= +github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pebbe/zmq4 v1.2.10 h1:wQkqRZ3CZeABIeidr3e8uQZMMH5YAykA/WN0L5zkd1c= +github.com/pebbe/zmq4 v1.2.10/go.mod h1:nqnPueOapVhE2wItZ0uOErngczsJdLOGkebMxaO8r48= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= +github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8= +github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0= +github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= +github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= +github.com/pion/ice/v2 v2.3.24 h1:RYgzhH/u5lH0XO+ABatVKCtRd+4U1GEaCXSMjNr13tI= +github.com/pion/ice/v2 v2.3.24/go.mod h1:KXJJcZK7E8WzrBEYnV4UtqEZsGeWfHxsNqhVcVvgjxw= +github.com/pion/interceptor v0.1.25 h1:pwY9r7P6ToQ3+IF0bajN0xmk/fNw/suTgaTdlwTDmhc= +github.com/pion/interceptor v0.1.25/go.mod h1:wkbPYAak5zKsfpVDYMtEfWEy8D4zL+rpxCxPImLOg3Y= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/mdns v0.0.12 h1:CiMYlY+O0azojWDmxdNr7ADGrnZ+V6Ilfner+6mSVK8= +github.com/pion/mdns v0.0.12/go.mod h1:VExJjv8to/6Wqm1FXK+Ii/Z9tsVk/F5sD/N70cnYFbk= +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= +github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I= +github.com/pion/rtcp v1.2.12 h1:bKWiX93XKgDZENEXCijvHRU/wRifm6JV5DGcH6twtSM= +github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4= +github.com/pion/rtp v1.8.2/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= +github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= +github.com/pion/rtp v1.8.5 h1:uYzINfaK+9yWs7r537z/Rc1SvT8ILjBcmDOpJcTB+OU= +github.com/pion/rtp v1.8.5/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= +github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0= +github.com/pion/sctp v1.8.16 h1:PKrMs+o9EMLRvFfXq59WFsC+V8mN1wnKzqrv+3D/gYY= +github.com/pion/sctp v1.8.16/go.mod h1:P6PbDVA++OJMrVNg2AL3XtYHV4uD6dvfyOovCgMs0PE= +github.com/pion/sdp/v3 v3.0.9 h1:pX++dCHoHUwq43kuwf3PyJfHlwIj4hXA7Vrifiq0IJY= +github.com/pion/sdp/v3 v3.0.9/go.mod h1:B5xmvENq5IXJimIO4zfp6LAe1fD9N+kFv+V/1lOdz8M= +github.com/pion/srtp/v2 v2.0.18 h1:vKpAXfawO9RtTRKZJbG4y0v1b11NZxQnxRl85kGuUlo= +github.com/pion/srtp/v2 v2.0.18/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA= +github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4= +github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8= +github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40= +github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI= +github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= +github.com/pion/transport/v2 v2.2.2/go.mod h1:OJg3ojoBJopjEeECq2yJdXH9YVrUJ1uQ++NjXLOUorc= +github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= +github.com/pion/transport/v2 v2.2.4 h1:41JJK6DZQYSeVLxILA2+F4ZkKb4Xd/tFJZRFZQ9QAlo= +github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= +github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0= +github.com/pion/transport/v3 v3.0.2 h1:r+40RJR25S9w3jbA6/5uEPTzcdn7ncyU44RWCbHkLg4= +github.com/pion/transport/v3 v3.0.2/go.mod h1:nIToODoOlb5If2jF9y2Igfx3PFYWfuXi37m0IlWa/D0= +github.com/pion/turn/v2 v2.1.3 h1:pYxTVWG2gpC97opdRc5IGsQ1lJ9O/IlNhkzj7MMrGAA= +github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY= +github.com/pion/webrtc/v3 v3.2.40 h1:Wtfi6AZMQg+624cvCXUuSmrKWepSB7zfgYDOYqsSOVU= +github.com/pion/webrtc/v3 v3.2.40/go.mod h1:M1RAe3TNTD1tzyvqHrbVODfwdPGSXOUo/OgpoGGJqFY= +github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0= +github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= -github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= -github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4= +github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= +github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +github.com/reF1nd/sing-snell v0.0.5 h1:UwbSB2GOz6flb/hBOvB978l4SRH/fdx7wmvug0IGlIg= +github.com/reF1nd/sing-snell v0.0.5/go.mod h1:9GeHBibP7RGZFoj4lgen8Tp5MbfOezw8nYeL6GHS/XA= +github.com/refraction-networking/conjure v0.7.11-0.20240130155008-c8df96195ab2 h1:m2ZH6WV69otVmBpWbk8et3MypHFsjcYXTNrknQKS/PY= +github.com/refraction-networking/conjure v0.7.11-0.20240130155008-c8df96195ab2/go.mod h1:7KuAtYfSL0K0WpCScjN9YKiOZ4AQ/8IzSjUtVwWbSv8= +github.com/refraction-networking/ed25519 v0.1.2 h1:08kJZUkAlY7a7cZGosl1teGytV+QEoNxPO7NnRvAB+g= +github.com/refraction-networking/ed25519 v0.1.2/go.mod h1:nxYLUAYt/hmNpAh64PNSQ/tQ9gTIB89wCaGKJlRtZ9I= +github.com/refraction-networking/gotapdance v1.7.10 h1:vPtvuihP95SqrnnpX//KI1PTqrXCvNnOQslrG4gxsRY= +github.com/refraction-networking/gotapdance v1.7.10/go.mod h1:N7Xmt+/bLv+1VctiBHtsaL6YBknW2ox5LRLzPTISMzY= +github.com/refraction-networking/obfs4 v0.1.2 h1:J842O4fGSkd2W8ogYj0KN6gqVVY+Cpqodw9qFGL7wVU= +github.com/refraction-networking/obfs4 v0.1.2/go.mod h1:wAl/+gWiLsrcykJA3nKJHx89f5/gXGM8UKvty7+mvbM= +github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo= +github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM= +github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= +github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE= +github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0= +github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs= +github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 h1:qi+ijeREa0yfAaO+NOcZ81gv4uzOfALUIdhkiIFvmG4= +github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1/go.mod h1:JULDuzTMn2gyZFcjpTVZP4/UuwAdbHJ0bum2RdjXojU= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= -github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY= -github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k= -github.com/sagernet/gomobile v0.1.3 h1:ohjIb1Ou2+1558PnZour3od69suSuvkdSVOlO1tC4B8= -github.com/sagernet/gomobile v0.1.3/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E= -github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y/6ZHJWrnNLoiNnSJaow6DPb8VW2I= -github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0= -github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba h1:EY5AS7CCtfmARNv2zXUOrsEMPFDGYxaw65JzA2p51Vk= -github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= -github.com/sagernet/quic-go v0.46.0-beta.4 h1:k9f7VSKaM47AY6MPND0Qf1KRN7HwimPg9zdOFTXTiCk= -github.com/sagernet/quic-go v0.46.0-beta.4/go.mod h1:zJmVdJUNqEDXfubf4KtIOUHHerggjBduiGRLNzJspcM= -github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= -github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= -github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= -github.com/sagernet/sing v0.4.2 h1:jzGNJdZVRI0xlAfFugsIQUPvyB9SuWvbJK7zQCXc4QM= -github.com/sagernet/sing v0.4.2/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls= -github.com/sagernet/sing-dns v0.2.3 h1:YzeBUn2tR38F7HtvGEQ0kLRLmZWMEgi/+7wqa4Twb1k= -github.com/sagernet/sing-dns v0.2.3/go.mod h1:BJpJv6XLnrUbSyIntOT6DG9FW0f4fETmPAHvNjOprLg= -github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo= -github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ= -github.com/sagernet/sing-quic v0.2.2 h1:Ryp02zMhHh/ZDrG7MdLsmhuBU8+BEpOdJonFQiqIopo= -github.com/sagernet/sing-quic v0.2.2/go.mod h1:YLV1dUDv8Eyp/8e55O/EvfsrwxOgEDVgDCIoPqmDREE= -github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8= -github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE= -github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg= -github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= -github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= -github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/sing-tun v0.3.2 h1:z0bLUT/YXH9RrJS9DsIpB0Bb9afl2hVJOmHd0zA3HJY= -github.com/sagernet/sing-tun v0.3.2/go.mod h1:DxLIyhjWU/HwGYoX0vNGg2c5QgTQIakphU1MuERR5tQ= -github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= -github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= -github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= -github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo= -github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 h1:z3SJQhVyU63FT26Wn/UByW6b7q8QKB0ZkPqsyqcz2PI= -github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6/go.mod h1:73xRZuxwkFk4aiLw28hG8W6o9cr2UPrGL9pdY2UTbvY= -github.com/sagernet/utls v1.5.4 h1:KmsEGbB2dKUtCNC+44NwAdNAqnqQ6GA4pTO0Yik56co= -github.com/sagernet/utls v1.5.4/go.mod h1:CTGxPWExIloRipK3XFpYv0OVyhO8kk3XCGW/ieyTh1s= -github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 h1:R0OMYAScomNAVpTfbHFpxqJpvwuhxSRi+g6z7gZhABs= -github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8/go.mod h1:K4J7/npM+VAMUeUmTa2JaA02JmyheP0GpRBOUvn3ecc= +github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= +github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= +github.com/sagernet/cronet-go v0.0.0-20260413093659-e4926ba205fa h1:7SehNSF1UHbLZa5dk+1rW1aperffJzl5r6TCJIXtAaY= +github.com/sagernet/cronet-go v0.0.0-20260413093659-e4926ba205fa/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= +github.com/sagernet/cronet-go/all v0.0.0-20260413093659-e4926ba205fa h1:ijk5v9N/akiMgqu734yMpv7Pk9F4Qmjh8Vfdcb4uJHE= +github.com/sagernet/cronet-go/all v0.0.0-20260413093659-e4926ba205fa/go.mod h1:+FENo4+0AOvH9e3oY6/iO7yy7USNt61dgbnI5W0TDZ0= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260413092954-cd09eb3e271b h1:O+PkYT88ayVWESX5tqxeMeS9OnzC3ZTic8gYiPJNXT8= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260413092954-cd09eb3e271b h1:o0MsgbsJwYkbqlbfaCvmAwb8/LAXeoSP8NE/aNvR/yY= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260413092954-cd09eb3e271b h1:JEQnc7cRMUahWJFtWY6n0hs1LE0KgyRv3pD0RWS8Yo8= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260413092954-cd09eb3e271b h1:69+AKzuUW9hzw2nU79c2DWfuzrIZ3PJm1KAwXh+7xr0= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260413092954-cd09eb3e271b h1:jp9FHUVTCJQ67Ecw3Inoct6/z1VTFXPtNYpXt47pa4E= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260413092954-cd09eb3e271b h1:WN3DZoECd2UbhmYQGpOA4jx4QBXiZuN1DvL/35NT61g= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260413092954-cd09eb3e271b h1:H4RKicwrIa4PwTXZOmXOg85hiCrpeFja4daOlX180pE= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260413092954-cd09eb3e271b h1:Rwi+Cu+Hgwj28F1lh837gGqSqn7oU8+r5i3UJyLPkKc= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260413092954-cd09eb3e271b h1:v2wcnPX3gt0PngFYXjXYAiarFckwx3pVAP6ETSpbSWE= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260413092954-cd09eb3e271b h1:Bl0zZ3QZq6pPJMbQlYHDhhaGngVefRlFzxWc0p48eHo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260413092954-cd09eb3e271b h1:vf+MbGv6RvvmXUNvganykBOnDIVXxy8XgtKOOqOcxtE= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260413092954-cd09eb3e271b h1:2IAc1bVFYF+B6hof34ChQKVhw7LElBxEEx7S0n+7o78= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260413092954-cd09eb3e271b h1:NrJaiOS0VLmWTbUHhXDsLTqelmCW4y3xJqptPs4Sx0s= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260413092954-cd09eb3e271b h1:A+ubSkca1nl2cT8pYUqCo1O7M41suNrKpWhZKCM/aIQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260413092954-cd09eb3e271b h1:WrhGH5FDXlCAoXwN6N44yCMvy6EbIurmTmptkz3mmms= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260413092954-cd09eb3e271b h1:kgwB5p5e0gdVX5iYRE7VbZS/On4qnb4UKonkGPwhkDI= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260413092954-cd09eb3e271b h1:Z3dOeFlRIOeQhSh+mCYDHui1yR3S/Uw8eupczzBvxqw= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260413092954-cd09eb3e271b h1:LPi6jz1k11Q67hm3Pw6aaPJ/Z6e3VtNhzrRjr5/5AQo= +github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:Wt5uFdU3tnmm8YzobYewwdF7Mt6SucRQg6xeTNWC3Tk= +github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260413092954-cd09eb3e271b h1:55sqihyfXWN7y7p7gOEgtUz9cm1mV3SDQ90/v6ROFaA= +github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:lyIF6wKBLwWa5ZXaAKbAoewewl+yCHo2iYev39Mbj4E= +github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260413092954-cd09eb3e271b h1:OTA1cbv5YIDVsYA8AAXHC4NgEc7b6pDiY+edujLWfJU= +github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:H46PnSTTZNcZokLLiDeMDaHiS1l14PH3tzWi0eykjD8= +github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260413092954-cd09eb3e271b h1:B/rdD/1A+RgqUYUZcoGhLeMqijnBd1mUt8+5LhOH7j8= +github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:RBhSUDAKWq7fswtV4nQUQhuaTLcX3ettR7teA7/yf2w= +github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260413092954-cd09eb3e271b h1:QFRWi6FucrODS4xQ8e9GYIzGSeMFO/DAMtTCVeJiCvM= +github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:wRzoIOGG4xbpp3Gh3triLKwMwYriScXzFtunLYhY4w0= +github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260413092954-cd09eb3e271b h1:2WJjPKZHLNIB4D17c3o9S+SP9kb3Qh0D26oWlun1+pE= +github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:LNiZXmWil1OPwKCheqQjtakZlJuKGFz+iv2eGF76Hhs= +github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260413092954-cd09eb3e271b h1:cUNTe4gNncRpYL28jzQf6qcJej40zzGQsH0o6CLUGws= +github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:YFDGKTkpkJGc5+hnX/RYosZyTWg9h+68VB55fYRRLYc= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260413092954-cd09eb3e271b h1:+sc1LJF0FjU2hVO5xBqqT+8qzoU08J2uHwxSle2m/Hw= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260413092954-cd09eb3e271b h1:+D/uhFxllI/KTLpeNEl8dwF3omPGmUFbrqt5tJkAyp0= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260413092954-cd09eb3e271b h1:nSUzzTUAZdqjGGckayk64sz+F0TGJPHvauTiAn27UKk= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260413092954-cd09eb3e271b h1:PE/fYBiHzB52gnQMg0soBfQyJCzmWHti48kCe2TBt9w= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260413092954-cd09eb3e271b h1:hy/3lPV11pKAAojDFnb95l9NpwOym6kME7FxS9p8sXs= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= +github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= +github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= +github.com/sagernet/gomobile v0.1.12 h1:XwzjZaclFF96deLqwAgK8gU3w0M2A8qxgDmhV+A0wjg= +github.com/sagernet/gomobile v0.1.12/go.mod h1:A8l3FlHi2D/+mfcd4HHvk5DGFPW/ShFb9jHP5VmSiDY= +github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 h1:AzCE2RhBjLJ4WIWc/GejpNh+z30d5H1hwaB0nD9eY3o= +github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1/go.mod h1:NJKBtm9nVEK3iyOYWsUlrDQuoGh4zJ4KOPhSYVidvQ4= +github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= +github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= +github.com/sagernet/nftables v0.3.0-mod.2 h1:ck2KMU02OxL1eDFgGaWYglMDpoOZ7OHzxje+vW5Q0OQ= +github.com/sagernet/nftables v0.3.0-mod.2/go.mod h1:8kslHG4VvYNihcco+i6uxIX7qbT8A56T0y5q7U44ZaQ= +github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgjHoKxLo3c+MDhNwc3o= +github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= +github.com/sagernet/sing v0.8.11-0.20260514110501-905ad103a4df h1:zSDw1g1JhIEqE/XPC3hW1wMdFPzbVgVJZiV86FAJoNQ= +github.com/sagernet/sing v0.8.11-0.20260514110501-905ad103a4df/go.mod h1:olXxWQNqRW/l2Q6JI3b2Qmz8iQnIFlOeeH8bx6JhgUA= +github.com/sagernet/sing-cloudflared v0.0.0-20260416083718-efa6ab16dba9 h1:wWJexq4V7ope1c4XYSqT/hydqd9omdtbDRvSZIOhjfY= +github.com/sagernet/sing-cloudflared v0.0.0-20260416083718-efa6ab16dba9/go.mod h1:bH2NKX+NpDTY1Zkxfboxw6MXB/ZywaNLmrDJYgKMJ2Y= +github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= +github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= +github.com/sagernet/sing-quic v0.6.2-0.20260525051024-9467ede27fb7 h1:hFLPJ21uNZSbRnzhOKz4Zv0b4F93mpDorWyN93BeRcM= +github.com/sagernet/sing-quic v0.6.2-0.20260525051024-9467ede27fb7/go.mod h1:+oqD54aHel4ALKkp1hVXWCgLU/EjLojvm6AUzDfvj0I= +github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= +github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= +github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= +github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= +github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= +github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= +github.com/sagernet/sing-tun v0.8.10-0.20260519125758-eb58efc8915d h1:7XmzSeoDAX01aPOKnvUbUHVUJ9fukjFDmBFh1hB4KQA= +github.com/sagernet/sing-tun v0.8.10-0.20260519125758-eb58efc8915d/go.mod h1:QvarqUtHfj1ULaRR+6kZOS/OoCE+pYGq67A5tyIy+dQ= +github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= +github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/sergeyfrolov/bsbuffer v0.0.0-20180903213811-94e85abb8507 h1:ML7ZNtcln5UBo5Wv7RIv9Xg3Pr5VuRCWLFXEwda54Y4= +github.com/sergeyfrolov/bsbuffer v0.0.0-20180903213811-94e85abb8507/go.mod h1:DbI1gxrXI2jRGw7XGEUZQOOMd6PsnKzRrCKabvvMrwM= +github.com/shadowsocks/go-shadowsocks2 v0.1.5 h1:PDSQv9y2S85Fl7VBeOMF9StzeXZyK1HakRm86CUbr28= +github.com/shadowsocks/go-shadowsocks2 v0.1.5/go.mod h1:AGGpIoek4HRno4xzyFiAtLHkOpcoznZEkAccaI/rplM= +github.com/shirou/gopsutil/v4 v4.24.5 h1:gGsArG5K6vmsh5hcFOHaPm87UD003CaDMkAOweSQjhM= +github.com/shirou/gopsutil/v4 v4.24.5/go.mod h1:aoebb2vxetJ/yIDZISmduFvVNPHqXQ9SEJwRXxkf0RA= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shtorm-7/connect-ip-go v1.0.0-extended-1.0.0 h1:ws7BIsYLd31Wjifq88BYCHRVlgO+07iwil39s6ERba8= +github.com/shtorm-7/connect-ip-go v1.0.0-extended-1.0.0/go.mod h1:mRwx4w32qQxsWB2kThuHpbo7iNjJiq1jYWubgqEPjHA= +github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0 h1:e5s7RKBd2rIPR0StbvZ2vTVtJ5jDTsTk5wtIIapZTRg= +github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0/go.mod h1:WpEFV2uhebXb8Jhes/5/fSdpmhGV8TL22RDaeWwV6hI= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8= +github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/starifly/sing-vmess v0.2.7-mod.9 h1:xobAmejSbBQ0A3f/EtJ9cJd3m6gK7dDPccPdeGz7tXY= +github.com/starifly/sing-vmess v0.2.7-mod.9/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= -github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ= +github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4= +github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4= +github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg= +github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio= +github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8= +github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= +github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= +github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU= +github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= +github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA= +github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc= +github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14= +github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= +github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA= +github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk= +github.com/things-go/go-socks5 v0.1.1 h1:48hy9cHEXPKeG91G/g4n8zW4uynzPUQy/FkcrJ7r5AY= +github.com/things-go/go-socks5 v0.1.1/go.mod h1:1YBHVYG7Oli5ae+Pwkp630cPAwY1pjUPmohO1n0Emg0= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s= +github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg= +github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= +github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= +github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= +github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +github.com/wader/filtertransport v0.0.0-20200316221534-bdd9e61eee78 h1:9sreu9e9KOihf2Y0NbpyfWhd1XFDcL4GTkPYL4IvMrg= +github.com/wader/filtertransport v0.0.0-20200316221534-bdd9e61eee78/go.mod h1:HazXTRLhXFyq80TQp7PUXi6BKE6mS+ydEdzEqNBKopQ= +github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= +github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xtaci/kcp-go/v5 v5.6.70 h1:AYX0QZl6PqmNj2IdYGZGuBfZuDUkUfl+eHYNijCqaO0= +github.com/xtaci/kcp-go/v5 v5.6.70/go.mod h1:9O3D8WR+cyyUjGiTILYfg17vn72otWuXK2AFfqIe6CM= +github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM= +github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= +github.com/xtaci/smux v1.5.50 h1:y/1DlWQC9bnMeZzsyk4oL2hbLK6uVk4BKTz5BeQqUEA= +github.com/xtaci/smux v1.5.50/go.mod h1:IGQ9QYrBphmb/4aTnLEcJby0TNr3NV+OslIOMrX825Q= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= -github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= +github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= +github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= +gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib v1.5.0 h1:rzdY78Ox2T+VlXcxGxELF+6VyUXlZBhmRqZu5etLm+c= +gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib v1.5.0/go.mod h1:70bhd4JKW/+1HLfm+TMrgHJsUHG4coelMWwiVEJ2gAg= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= +go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= +go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek= +go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= -golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= -golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0= +golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= +golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= +golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE= -golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= -google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= +golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4= +golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= +golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdIg1ozBNLgPy4SLT84nfcBjr6rhGtXYtrkWLU= +golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ= +golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= +golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 h1:ze1vwAdliUAr68RQ5NtufWaXaOg8WUO2OACzEV+TNdE= +gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= -lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= -lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= +lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg= +lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo= +software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= +software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= +tailscale.com v1.58.2 h1:5trkhh/fpUn7f6TUcGUQYJ0GokdNNfNrjh9ONJhoc5A= +tailscale.com v1.58.2/go.mod h1:faWR8XaXemnSKCDjHC7SAQzaagkUjA5x4jlLWiwxtuk= +zombiezen.com/go/capnproto2 v2.18.2+incompatible h1:v3BD1zbruvffn7zjJUU5Pn8nZAB11bhZSQC4W+YnnKo= +zombiezen.com/go/capnproto2 v2.18.2+incompatible/go.mod h1:XO5Pr2SbXgqZwn0m0Ru54QBqpOf4K5AYBO+8LAOBQEQ= diff --git a/hiddify/ipinfo/freeipapi.go b/hiddify/ipinfo/freeipapi.go new file mode 100644 index 0000000000..f119ff0d76 --- /dev/null +++ b/hiddify/ipinfo/freeipapi.go @@ -0,0 +1,52 @@ +package ipinfo + +import ( + "context" + + N "github.com/sagernet/sing/common/network" +) + +// FreeIpApiProvider struct and implementation. +type FreeIpApiProvider struct { + BaseProvider +} + +// NewFreeIpApiProvider initializes a new FreeIpApiProvider. +func NewFreeIpApiProvider() *FreeIpApiProvider { + return &FreeIpApiProvider{ + BaseProvider: BaseProvider{URL: "https://free.freeipapi.com/api/json/"}, + } +} + +// GetIPInfo fetches and parses IP information from freeipapi.com. +func (p *FreeIpApiProvider) GetIPInfo(ctx context.Context, dialer N.Dialer) (*IpInfo, uint16, error) { + info := &IpInfo{} + data, t, err := p.fetchData(ctx, dialer) + if err != nil { + return nil, t, err + } + + if ip, ok := data["ipAddress"].(string); ok { + info.IP = ip + } + if countryCode, ok := data["countryCode"].(string); ok { + info.CountryCode = countryCode + } + if region, ok := data["regionName"].(string); ok { + info.Region = region + } + if city, ok := data["cityName"].(string); ok { + info.City = city + } + if latitude, ok := data["latitude"].(float64); ok { + info.Latitude = latitude + } + if longitude, ok := data["longitude"].(float64); ok { + info.Longitude = longitude + } + if postalCode, ok := data["zipCode"].(string); ok { + info.PostalCode = postalCode + } + + return info, t, nil +} diff --git a/hiddify/ipinfo/ipapico.go b/hiddify/ipinfo/ipapico.go new file mode 100644 index 0000000000..0dcfe66c5f --- /dev/null +++ b/hiddify/ipinfo/ipapico.go @@ -0,0 +1,62 @@ +package ipinfo + +import ( + "context" + "strconv" + "strings" + + N "github.com/sagernet/sing/common/network" +) + +// IpApiCoProvider struct and implementation. +type IpApiCoProvider struct { + BaseProvider +} + +func NewIpApiCoProvider() *IpApiCoProvider { + return &IpApiCoProvider{ + BaseProvider: BaseProvider{URL: "https://ipapi.co/json/", UserAgent: "curl/8.7.1"}, + } +} + +func (p *IpApiCoProvider) GetIPInfo(ctx context.Context, dialer N.Dialer) (*IpInfo, uint16, error) { + info := &IpInfo{} + data, t, err := p.fetchData(ctx, dialer) + if err != nil { + return nil, t, err + } + + if ip, ok := data["ip"].(string); ok { + info.IP = ip + } + if countryCode, ok := data["country_code"].(string); ok { + info.CountryCode = countryCode + } + if region, ok := data["region"].(string); ok { + info.Region = region + } + if city, ok := data["city"].(string); ok { + info.City = city + } + if asnstr, ok := data["asn"].(string); ok { + if strings.HasPrefix(asnstr, "AS") { + if asn, ok := strconv.ParseInt(strings.TrimPrefix(asnstr, "AS"), 10, 64); ok == nil { + info.ASN = int(asn) + } + } + } + if org, ok := data["org"].(string); ok { + info.Org = org + } + if latitude, ok := data["latitude"].(float64); ok { + info.Latitude = latitude + } + if longitude, ok := data["longitude"].(float64); ok { + info.Longitude = longitude + } + + if postalCode, ok := data["postal"].(string); ok { + info.PostalCode = postalCode + } + return info, t, nil +} diff --git a/hiddify/ipinfo/ipapicom.go b/hiddify/ipinfo/ipapicom.go new file mode 100644 index 0000000000..88bb3875a8 --- /dev/null +++ b/hiddify/ipinfo/ipapicom.go @@ -0,0 +1,63 @@ +package ipinfo + +import ( + "context" + "strconv" + "strings" + + N "github.com/sagernet/sing/common/network" +) + +type IpApiProvider struct { + BaseProvider +} + +// NewIpApiProvider initializes a new IpApiProvider. +func NewIpApiProvider() *IpApiProvider { + return &IpApiProvider{ + BaseProvider: BaseProvider{URL: "http://ip-api.com/json/"}, + } +} + +// GetIPInfo fetches and parses IP information from ip-api.com. +func (p *IpApiProvider) GetIPInfo(ctx context.Context, dialer N.Dialer) (*IpInfo, uint16, error) { + info := &IpInfo{} + data, t, err := p.fetchData(ctx, dialer) + if err != nil { + return nil, t, err + } + + if status, ok := data["status"].(string); ok && status == "success" { + if ip, ok := data["query"].(string); ok { + info.IP = ip + } + if countryCode, ok := data["countryCode"].(string); ok { + info.CountryCode = countryCode + } + if region, ok := data["region"].(string); ok { + info.Region = region + } + if city, ok := data["city"].(string); ok { + info.City = city + } + if zip, ok := data["zip"].(string); ok { + info.PostalCode = zip + } + if latitude, ok := data["lat"].(float64); ok { + info.Latitude = latitude + } + if longitude, ok := data["lon"].(float64); ok { + info.Longitude = longitude + } + if org, ok := data["org"].(string); ok { + info.Org = org + } + if asnStr, ok := data["as"].(string); ok && strings.HasPrefix(asnStr, "AS") { + if asn, err := strconv.ParseInt(strings.TrimPrefix(strings.Fields(asnStr)[0], "AS"), 10, 64); err == nil { + info.ASN = int(asn) + } + } + } + + return info, t, nil +} diff --git a/hiddify/ipinfo/ipinfo.go b/hiddify/ipinfo/ipinfo.go new file mode 100644 index 0000000000..d30c9d31b3 --- /dev/null +++ b/hiddify/ipinfo/ipinfo.go @@ -0,0 +1,223 @@ +package ipinfo + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "math/rand" + "net" + "net/http" + "net/url" + + C "github.com/sagernet/sing-box/constant" + + "github.com/sagernet/sing-box/log" + + "time" + + "github.com/sagernet/sing/common" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +var providers = []Provider{ + NewFreeIpApiProvider(), + NewIpApiCoProvider(), + NewIpApiProvider(), + NewIpInfoIoProvider(), + NewIpSbProvider(), + NewIpWhoIsProvider(), + // NewCloudflareTraceProvider(), + NewCountryIsProvider(), + NewMyIPExpertProvider(), + NewMyIPioProvider(), + NewReallyFreeGeoIPProvider(), +} + +var fallbackProviders = []Provider{ + NewMyIPProvider(), + NewCountryIsProvider(), + // NewCloudflareTraceProvider(), +} + +func GetAllIPCheckerDomainsDomains() []string { + var domains []string + for _, provider := range providers { + d, err := url.Parse(provider.GetName()) + if err == nil { + if net.ParseIP(d.Hostname()) == nil { + domains = append(domains, d.Hostname()) + } + + } + } + return domains +} + +// Provider interface for all IP providers. +type Provider interface { + GetIPInfo(ctx context.Context, dialer N.Dialer) (*IpInfo, uint16, error) + GetName() string +} + +// IpInfo stores the IP information from the API response. +type IpInfo struct { + IP string `json:"ip"` + CountryCode string `json:"country_code"` + Region string `json:"region,omitempty"` + City string `json:"city,omitempty"` + ASN int `json:"asn,omitempty"` + Org string `json:"org,omitempty"` + Latitude float64 `json:"latitude,omitempty"` + Longitude float64 `json:"longitude,omitempty"` + PostalCode string `json:"postal_code,omitempty"` +} + +func (ip *IpInfo) String() string { + return fmt.Sprintf("IP: %s, Country: %s, Region: %s, City: %s, ASN: %d, Org: %s, Latitude: %.6f, Longitude: %.6f, Postal Code: %s", + ip.IP, ip.CountryCode, ip.Region, ip.City, ip.ASN, ip.Org, ip.Latitude, ip.Longitude, ip.PostalCode) +} + +// BaseProvider struct to handle common logic (HTTP request). +type BaseProvider struct { + URL string + UserAgent string +} + +func (p *BaseProvider) GetName() string { + return p.URL +} + +// fetchData retrieves the data from the provider's URL with a custom user agent and dialer. +func (p *BaseProvider) fetchData(ctx context.Context, detour N.Dialer) (map[string]interface{}, uint16, error) { + link := p.URL + linkURL, err := url.Parse(link) + if err != nil { + return nil, 65535, err + } + hostname := linkURL.Hostname() + port := linkURL.Port() + if port == "" { + switch linkURL.Scheme { + case "http": + port = "80" + case "https": + port = "443" + } + } + + req, err := http.NewRequest(http.MethodGet, link, nil) + if err != nil { + return nil, 65535, err + } + if p.UserAgent == "" { + p.UserAgent = C.DefaultBrowserAgent + } + req.Header.Set("User-Agent", p.UserAgent) + start := time.Now() + var client http.Client + if detour != nil { + instance, err := detour.DialContext(ctx, "tcp", M.ParseSocksaddrHostPortStr(hostname, port)) + if err != nil { + return nil, 65535, err + } + defer instance.Close() + if earlyConn, isEarlyConn := common.Cast[N.EarlyConn](instance); isEarlyConn && earlyConn.NeedHandshake() { + start = time.Now() + } + + client = http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return instance, nil + }, + }, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } + } else { + client = http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } + } + defer client.CloseIdleConnections() + resp, err := client.Do(req.WithContext(ctx)) + if err != nil { + return nil, 65535, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, 65535, fmt.Errorf("non-200 response from [%s]: %d", p.URL, resp.StatusCode) + } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, 65535, fmt.Errorf("failed to read response from [%s]: %v", p.URL, err) + } + t := uint16(time.Since(start) / time.Millisecond) + var jsonResponse map[string]interface{} + err = json.Unmarshal(body, &jsonResponse) + if err != nil { + return nil, 65535, fmt.Errorf("failed to parse JSON from [%s]: %v", p.URL, err) + } + + return jsonResponse, t, nil + +} + +// getCurrentIpInfo iterates over the providers to fetch and parse IP information. +func GetIpInfo(logger log.Logger, ctx context.Context, detour N.Dialer) (*IpInfo, uint16, error) { + var lastErr error + startIndex := rand.Intn(len(providers)) + for i := 0; i < len(providers); i++ { + select { + case <-ctx.Done(): + return nil, 65535, ctx.Err() + default: + } + provider := providers[(i+startIndex)%len(providers)] + testCtx, cancel := context.WithTimeout(ctx, C.TCPTimeout) + ipInfo, t, err := provider.GetIPInfo(testCtx, detour) + cancel() + if err != nil { + logger.Warn("Failed try ", i, " to get IP info: ", provider.GetName(), " ", err) + lastErr = err + continue + } + return ipInfo, t, nil + } + startIndex = rand.Intn(len(fallbackProviders)) + for i := 0; i < len(fallbackProviders); i++ { + select { + case <-ctx.Done(): + return nil, 65535, ctx.Err() + default: + } + provider := fallbackProviders[(i+startIndex)%len(fallbackProviders)] + testCtx, cancel := context.WithTimeout(ctx, C.TCPTimeout) + ipInfo, t, err := provider.GetIPInfo(testCtx, detour) + cancel() + if err != nil { + logger.Warn("Failed try ", i, " to get IP info: ", provider.GetName(), " ", err) + continue + } + return ipInfo, t, nil + } + return nil, 65535, fmt.Errorf("unable to retrieve IP info: %v", lastErr) +} + +// func init() { +// // Instantiate the providers. + +// for _, provider := range providers { +// x, _, err := provider.GetIPInfo(context.Background(), nil) +// fmt.Printf("%s: %++v\n%++v\n", provider, x, err) +// } +// // Get IP information. + +// os.Exit(0) +// } diff --git a/hiddify/ipinfo/ipinfoio.go b/hiddify/ipinfo/ipinfoio.go new file mode 100644 index 0000000000..1749dc40a0 --- /dev/null +++ b/hiddify/ipinfo/ipinfoio.go @@ -0,0 +1,70 @@ +package ipinfo + +import ( + "context" + "strconv" + "strings" + + N "github.com/sagernet/sing/common/network" +) + +// IpInfoIoProvider struct and implementation. +type IpInfoIoProvider struct { + BaseProvider +} + +func NewIpInfoIoProvider() *IpInfoIoProvider { + return &IpInfoIoProvider{ + BaseProvider: BaseProvider{URL: "http://ipinfo.io/json"}, + } +} + +func (p *IpInfoIoProvider) GetIPInfo(ctx context.Context, dialer N.Dialer) (*IpInfo, uint16, error) { + info := &IpInfo{} + data, t, err := p.fetchData(ctx, dialer) + if err != nil { + return nil, t, err + } + + if ip, ok := data["ip"].(string); ok { + info.IP = ip + } + if city, ok := data["city"].(string); ok { + info.City = city + } + if region, ok := data["region"].(string); ok { + info.Region = region + } + if country, ok := data["country"].(string); ok { + info.CountryCode = country + } + if loc, ok := data["loc"].(string); ok { + // Split loc into latitude and longitude + coords := strings.Split(loc, ",") + if len(coords) == 2 { + if latitude, err := strconv.ParseFloat(coords[0], 64); err == nil { + info.Latitude = latitude + } + if longitude, err := strconv.ParseFloat(coords[1], 64); err == nil { + info.Longitude = longitude + } + } + } + if org, ok := data["org"].(string); ok { + // Split the org string to extract ASN and Organization + orgParts := strings.SplitN(org, " ", 2) // Split into 2 parts + if len(orgParts) > 0 { + if strings.HasPrefix(orgParts[0], "AS") { + if asn, ok := strconv.ParseInt(strings.TrimPrefix(orgParts[0], "AS"), 10, 64); ok == nil { + info.ASN = int(asn) + } + } + } + info.Org = orgParts[len(orgParts)-1] + } + if postal, ok := data["postal"].(string); ok { + info.PostalCode = postal + } + + return info, t, nil +} diff --git a/hiddify/ipinfo/ipsb.go b/hiddify/ipinfo/ipsb.go new file mode 100644 index 0000000000..17c5a3dcd6 --- /dev/null +++ b/hiddify/ipinfo/ipsb.go @@ -0,0 +1,55 @@ +package ipinfo + +import ( + "context" + + N "github.com/sagernet/sing/common/network" +) + +// IpSbProvider struct and implementation. +type IpSbProvider struct { + BaseProvider +} + +func NewIpSbProvider() *IpSbProvider { + return &IpSbProvider{ + BaseProvider: BaseProvider{URL: "https://api.ip.sb/geoip/"}, + } +} + +func (p *IpSbProvider) GetIPInfo(ctx context.Context, dialer N.Dialer) (*IpInfo, uint16, error) { + info := &IpInfo{} + data, t, err := p.fetchData(ctx, dialer) + if err != nil { + return nil, t, err + } + + if ip, ok := data["ip"].(string); ok { + info.IP = ip + } + if countryCode, ok := data["country_code"].(string); ok { + info.CountryCode = countryCode + } + if region, ok := data["region"].(string); ok { + info.Region = region + } + if city, ok := data["city"].(string); ok { + info.City = city + } + if asn, ok := data["asn"].(float64); ok { + info.ASN = int(asn) + } + if org, ok := data["asn_organization"].(string); ok { + info.Org = org + } + if latitude, ok := data["latitude"].(float64); ok { + info.Latitude = latitude + } + if longitude, ok := data["longitude"].(float64); ok { + info.Longitude = longitude + } + if postalCode, ok := data["postal_code"].(string); ok { + info.PostalCode = postalCode + } + return info, t, nil +} diff --git a/hiddify/ipinfo/ipwhois.go b/hiddify/ipinfo/ipwhois.go new file mode 100644 index 0000000000..509a36dcc5 --- /dev/null +++ b/hiddify/ipinfo/ipwhois.go @@ -0,0 +1,59 @@ +package ipinfo + +import ( + "context" + + N "github.com/sagernet/sing/common/network" +) + +// IpWhoIsProvider struct and implementation. +type IpWhoIsProvider struct { + BaseProvider +} + +func NewIpWhoIsProvider() *IpWhoIsProvider { + return &IpWhoIsProvider{ + BaseProvider: BaseProvider{URL: "http://ipwho.is/"}, + } +} + +func (p *IpWhoIsProvider) GetIPInfo(ctx context.Context, dialer N.Dialer) (*IpInfo, uint16, error) { + info := &IpInfo{} + data, t, err := p.fetchData(ctx, dialer) + if err != nil { + return nil, t, err + } + + if ip, ok := data["ip"].(string); ok { + info.IP = ip + } + if countryCode, ok := data["country_code"].(string); ok { + info.CountryCode = countryCode + } + if region, ok := data["region"].(string); ok { + info.Region = region + } + if city, ok := data["city"].(string); ok { + info.City = city + } + + if connection, ok := data["connection"].(map[string]interface{}); ok { + if asn, ok := connection["asn"].(float64); ok { + info.ASN = int(asn) + } + if org, ok := connection["org"].(string); ok { + info.Org = org + } + } + + if latitude, ok := data["latitude"].(float64); ok { + info.Latitude = latitude + } + if longitude, ok := data["longitude"].(float64); ok { + info.Longitude = longitude + } + if postalCode, ok := data["postal"].(string); ok { + info.PostalCode = postalCode + } + return info, t, nil +} diff --git a/hiddify/ipinfo/limited_cloudflare.go b/hiddify/ipinfo/limited_cloudflare.go new file mode 100644 index 0000000000..b5c9affe2b --- /dev/null +++ b/hiddify/ipinfo/limited_cloudflare.go @@ -0,0 +1,39 @@ +package ipinfo + +// // CloudflareTraceProvider struct and implementation. +// type CloudflareTraceProvider struct { +// BaseProvider +// } + +// // NewCloudflareTraceProvider initializes a new CloudflareTraceProvider. +// func NewCloudflareTraceProvider() *CloudflareTraceProvider { +// return &CloudflareTraceProvider{ +// BaseProvider: BaseProvider{URL: "https://cloudflare.com/cdn-cgi/trace"}, +// } +// } + +// // GetIPInfo fetches and parses IP information from Cloudflare's trace endpoint. +// func (p *CloudflareTraceProvider) GetIPInfo(ctx context.Context, dialer N.Dialer) (*IpInfo, uint16, error) { +// info := &IpInfo{} +// data, t, err := p.fetchData(ctx, dialer) +// if err != nil { +// return nil, t, err +// } + +// // Parse the plain text response. +// for _, line := range strings.Split(string(data), "\n") { +// parts := strings.SplitN(line, "=", 2) +// if len(parts) != 2 { +// continue +// } +// key, value := parts[0], parts[1] +// switch key { +// case "ip": +// info.IP = value +// case "loc": +// info.CountryCode = value +// } +// } + +// return info, t, nil +// } diff --git a/hiddify/ipinfo/limited_countryis.go b/hiddify/ipinfo/limited_countryis.go new file mode 100644 index 0000000000..a5bbdeddb5 --- /dev/null +++ b/hiddify/ipinfo/limited_countryis.go @@ -0,0 +1,37 @@ +package ipinfo + +import ( + "context" + + N "github.com/sagernet/sing/common/network" +) + +// CountryIsProvider struct and implementation. +type CountryIsProvider struct { + BaseProvider +} + +// NewCountryIsProvider initializes a new CountryIsProvider. +func NewCountryIsProvider() *CountryIsProvider { + return &CountryIsProvider{ + BaseProvider: BaseProvider{URL: "https://api.country.is/"}, + } +} + +// GetIPInfo fetches and parses IP information from country.is. +func (p *CountryIsProvider) GetIPInfo(ctx context.Context, dialer N.Dialer) (*IpInfo, uint16, error) { + info := &IpInfo{} + data, t, err := p.fetchData(ctx, dialer) + if err != nil { + return nil, t, err + } + + if ip, ok := data["ip"].(string); ok { + info.IP = ip + } + if country, ok := data["country"].(string); ok { + info.CountryCode = country + } + + return info, t, nil +} diff --git a/hiddify/ipinfo/limited_myip.go b/hiddify/ipinfo/limited_myip.go new file mode 100644 index 0000000000..84f9331894 --- /dev/null +++ b/hiddify/ipinfo/limited_myip.go @@ -0,0 +1,37 @@ +package ipinfo + +import ( + "context" + + N "github.com/sagernet/sing/common/network" +) + +// MyIPProvider struct and implementation. +type MyIPProvider struct { + BaseProvider +} + +// NewMyIPProvider initializes a new MyIPProvider. +func NewMyIPProvider() *MyIPProvider { + return &MyIPProvider{ + BaseProvider: BaseProvider{URL: "https://api.myip.com"}, + } +} + +// GetIPInfo fetches and parses IP information from api.myip.com. +func (p *MyIPProvider) GetIPInfo(ctx context.Context, dialer N.Dialer) (*IpInfo, uint16, error) { + info := &IpInfo{} + data, t, err := p.fetchData(ctx, dialer) + if err != nil { + return nil, t, err + } + + if ip, ok := data["ip"].(string); ok { + info.IP = ip + } + if countryCode, ok := data["cc"].(string); ok { + info.CountryCode = countryCode + } + + return info, t, nil +} diff --git a/hiddify/ipinfo/myipexpert.go b/hiddify/ipinfo/myipexpert.go new file mode 100644 index 0000000000..91b90f0e43 --- /dev/null +++ b/hiddify/ipinfo/myipexpert.go @@ -0,0 +1,66 @@ +package ipinfo + +import ( + "context" + "strconv" + "strings" + + "github.com/biter777/countries" + N "github.com/sagernet/sing/common/network" +) + +// MyIPExpertProvider struct and implementation. +type MyIPExpertProvider struct { + BaseProvider +} + +// NewMyIPExpertProvider initializes a new MyIPExpertProvider. +func NewMyIPExpertProvider() *MyIPExpertProvider { + return &MyIPExpertProvider{ + BaseProvider: BaseProvider{URL: "https://myip.expert/api/"}, + } +} + +// GetIPInfo fetches and parses IP information from myip.expert. +func (p *MyIPExpertProvider) GetIPInfo(ctx context.Context, dialer N.Dialer) (*IpInfo, uint16, error) { + info := &IpInfo{} + data, t, err := p.fetchData(ctx, dialer) + if err != nil { + return nil, t, err + } + + if ip, ok := data["userIp"].(string); ok { + info.IP = ip + } + if countryCode, ok := data["userCountryCode"].(string); ok { + country := countries.ByName(countryCode) + + info.CountryCode = country.Alpha2() + } + if region, ok := data["userRegion"].(string); ok { + info.Region = region + } + + if city, ok := data["userCity"].(string); ok { + info.City = city + } + if latitude, ok := data["userLatitude"].(float64); ok { + info.Latitude = latitude + } + if longitude, ok := data["userLongitude"].(float64); ok { + info.Longitude = longitude + } + // if isp, ok := data["userIsp"].(string); ok { + // info.ISP = isp + // } + if org, ok := data["userOrg"].(string); ok { + info.Org = org + } + if userHost, ok := data["userHost"].(string); ok && strings.HasPrefix(userHost, "AS") { + if asn, err := strconv.ParseInt(strings.TrimPrefix(strings.Fields(userHost)[0], "AS"), 10, 64); err == nil { + info.ASN = int(asn) + } + } + + return info, t, nil +} diff --git a/hiddify/ipinfo/myipio.go b/hiddify/ipinfo/myipio.go new file mode 100644 index 0000000000..bdc757ed45 --- /dev/null +++ b/hiddify/ipinfo/myipio.go @@ -0,0 +1,63 @@ +package ipinfo + +import ( + "context" + + N "github.com/sagernet/sing/common/network" +) + +// MyIPioProvider struct and implementation. +type MyIPioProvider struct { + BaseProvider +} + +// NewMyIPioProvider initializes a new MyIPioProvider. +func NewMyIPioProvider() *MyIPioProvider { + return &MyIPioProvider{ + BaseProvider: BaseProvider{URL: "https://api.my-ip.io/v2/ip.json"}, + } +} + +// GetIPInfo fetches and parses IP information from api.my-ip.io. +func (p *MyIPioProvider) GetIPInfo(ctx context.Context, dialer N.Dialer) (*IpInfo, uint16, error) { + info := &IpInfo{} + data, t, err := p.fetchData(ctx, dialer) + if err != nil { + return nil, t, err + } + + if success, ok := data["success"].(bool); ok && success { + if ip, ok := data["ip"].(string); ok { + info.IP = ip + } + if country, ok := data["country"].(map[string]interface{}); ok { + if code, ok := country["code"].(string); ok { + info.CountryCode = code + } + } + if region, ok := data["region"].(string); ok { + info.Region = region + } + if city, ok := data["city"].(string); ok { + info.City = city + } + if location, ok := data["location"].(map[string]interface{}); ok { + if lat, ok := location["lat"].(float64); ok { + info.Latitude = lat + } + if lon, ok := location["lon"].(float64); ok { + info.Longitude = lon + } + } + if asn, ok := data["asn"].(map[string]interface{}); ok { + if asnNumber, ok := asn["number"].(float64); ok { + info.ASN = int(asnNumber) + } + if asnName, ok := asn["name"].(string); ok { + info.Org = asnName + } + } + } + + return info, t, nil +} diff --git a/hiddify/ipinfo/realyfreegeoip.go b/hiddify/ipinfo/realyfreegeoip.go new file mode 100644 index 0000000000..bcdc0cbcf4 --- /dev/null +++ b/hiddify/ipinfo/realyfreegeoip.go @@ -0,0 +1,52 @@ +package ipinfo + +import ( + "context" + + N "github.com/sagernet/sing/common/network" +) + +// ReallyFreeGeoIPProvider struct and implementation. +type ReallyFreeGeoIPProvider struct { + BaseProvider +} + +// NewReallyFreeGeoIPProvider initializes a new ReallyFreeGeoIPProvider. +func NewReallyFreeGeoIPProvider() *ReallyFreeGeoIPProvider { + return &ReallyFreeGeoIPProvider{ + BaseProvider: BaseProvider{URL: "https://reallyfreegeoip.org/json/"}, + } +} + +// GetIPInfo fetches and parses IP information from reallyfreegeoip.org. +func (p *ReallyFreeGeoIPProvider) GetIPInfo(ctx context.Context, dialer N.Dialer) (*IpInfo, uint16, error) { + info := &IpInfo{} + data, t, err := p.fetchData(ctx, dialer) + if err != nil { + return nil, t, err + } + + if ip, ok := data["ip"].(string); ok { + info.IP = ip + } + if countryCode, ok := data["country_code"].(string); ok { + info.CountryCode = countryCode + } + if regionCode, ok := data["region_name"].(string); ok { + info.Region = regionCode + } + if city, ok := data["city"].(string); ok { + info.City = city + } + if zipCode, ok := data["zip_code"].(string); ok { + info.PostalCode = zipCode + } + if latitude, ok := data["latitude"].(float64); ok { + info.Latitude = latitude + } + if longitude, ok := data["longitude"].(float64); ok { + info.Longitude = longitude + } + + return info, t, nil +} diff --git a/inbound/builder.go b/inbound/builder.go deleted file mode 100644 index 513b016f79..0000000000 --- a/inbound/builder.go +++ /dev/null @@ -1,54 +0,0 @@ -package inbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/libbox/platform" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" -) - -func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.Inbound, platformInterface platform.Interface) (adapter.Inbound, error) { - if options.Type == "" { - return nil, E.New("missing inbound type") - } - switch options.Type { - case C.TypeTun: - return NewTun(ctx, router, logger, options.Tag, options.TunOptions, platformInterface) - case C.TypeRedirect: - return NewRedirect(ctx, router, logger, options.Tag, options.RedirectOptions), nil - case C.TypeTProxy: - return NewTProxy(ctx, router, logger, options.Tag, options.TProxyOptions), nil - case C.TypeDirect: - return NewDirect(ctx, router, logger, options.Tag, options.DirectOptions), nil - case C.TypeSOCKS: - return NewSocks(ctx, router, logger, options.Tag, options.SocksOptions), nil - case C.TypeHTTP: - return NewHTTP(ctx, router, logger, options.Tag, options.HTTPOptions) - case C.TypeMixed: - return NewMixed(ctx, router, logger, options.Tag, options.MixedOptions), nil - case C.TypeShadowsocks: - return NewShadowsocks(ctx, router, logger, options.Tag, options.ShadowsocksOptions) - case C.TypeVMess: - return NewVMess(ctx, router, logger, options.Tag, options.VMessOptions) - case C.TypeTrojan: - return NewTrojan(ctx, router, logger, options.Tag, options.TrojanOptions) - case C.TypeNaive: - return NewNaive(ctx, router, logger, options.Tag, options.NaiveOptions) - case C.TypeHysteria: - return NewHysteria(ctx, router, logger, options.Tag, options.HysteriaOptions) - case C.TypeShadowTLS: - return NewShadowTLS(ctx, router, logger, options.Tag, options.ShadowTLSOptions) - case C.TypeVLESS: - return NewVLESS(ctx, router, logger, options.Tag, options.VLESSOptions) - case C.TypeTUIC: - return NewTUIC(ctx, router, logger, options.Tag, options.TUICOptions) - case C.TypeHysteria2: - return NewHysteria2(ctx, router, logger, options.Tag, options.Hysteria2Options) - default: - return nil, E.New("unknown inbound type: ", options.Type) - } -} diff --git a/inbound/default.go b/inbound/default.go deleted file mode 100644 index 44c580deb9..0000000000 --- a/inbound/default.go +++ /dev/null @@ -1,196 +0,0 @@ -package inbound - -import ( - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/settings" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/atomic" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -var _ adapter.Inbound = (*myInboundAdapter)(nil) - -type myInboundAdapter struct { - protocol string - network []string - ctx context.Context - router adapter.ConnectionRouter - logger log.ContextLogger - tag string - listenOptions option.ListenOptions - connHandler adapter.ConnectionHandler - packetHandler adapter.PacketHandler - oobPacketHandler adapter.OOBPacketHandler - packetUpstream any - - // http mixed - - setSystemProxy bool - systemProxy settings.SystemProxy - - // internal - - tcpListener net.Listener - udpConn *net.UDPConn - udpAddr M.Socksaddr - packetOutboundClosed chan struct{} - packetOutbound chan *myInboundPacket - - inShutdown atomic.Bool -} - -func (a *myInboundAdapter) Type() string { - return a.protocol -} - -func (a *myInboundAdapter) Tag() string { - return a.tag -} - -func (a *myInboundAdapter) Network() []string { - return a.network -} - -func (a *myInboundAdapter) Start() error { - var err error - if common.Contains(a.network, N.NetworkTCP) { - _, err = a.ListenTCP() - if err != nil { - return err - } - go a.loopTCPIn() - } - if common.Contains(a.network, N.NetworkUDP) { - _, err = a.ListenUDP() - if err != nil { - return err - } - a.packetOutboundClosed = make(chan struct{}) - a.packetOutbound = make(chan *myInboundPacket) - if a.oobPacketHandler != nil { - if _, threadUnsafeHandler := common.Cast[N.ThreadUnsafeWriter](a.packetUpstream); !threadUnsafeHandler { - go a.loopUDPOOBIn() - } else { - go a.loopUDPOOBInThreadSafe() - } - } else { - if _, threadUnsafeHandler := common.Cast[N.ThreadUnsafeWriter](a.packetUpstream); !threadUnsafeHandler { - go a.loopUDPIn() - } else { - go a.loopUDPInThreadSafe() - } - go a.loopUDPOut() - } - } - if a.setSystemProxy { - listenPort := M.SocksaddrFromNet(a.tcpListener.Addr()).Port - var listenAddrString string - listenAddr := a.listenOptions.Listen.Build() - if listenAddr.IsUnspecified() { - listenAddrString = "127.0.0.1" - } else { - listenAddrString = listenAddr.String() - } - var systemProxy settings.SystemProxy - systemProxy, err = settings.NewSystemProxy(a.ctx, M.ParseSocksaddrHostPort(listenAddrString, listenPort), a.protocol == C.TypeMixed) - if err != nil { - return E.Cause(err, "initialize system proxy") - } - err = systemProxy.Enable() - if err != nil { - return E.Cause(err, "set system proxy") - } - a.systemProxy = systemProxy - } - return nil -} - -func (a *myInboundAdapter) Close() error { - a.inShutdown.Store(true) - var err error - if a.systemProxy != nil && a.systemProxy.IsEnabled() { - err = a.systemProxy.Disable() - } - return E.Errors(err, common.Close( - a.tcpListener, - common.PtrOrNil(a.udpConn), - )) -} - -func (a *myInboundAdapter) upstreamHandler(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapter { - return adapter.NewUpstreamHandler(metadata, a.newConnection, a.streamPacketConnection, a) -} - -func (a *myInboundAdapter) upstreamContextHandler() adapter.UpstreamHandlerAdapter { - return adapter.NewUpstreamContextHandler(a.newConnection, a.newPacketConnection, a) -} - -func (a *myInboundAdapter) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - a.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - return a.router.RouteConnection(ctx, conn, metadata) -} - -func (a *myInboundAdapter) streamPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - return a.router.RoutePacketConnection(ctx, conn, metadata) -} - -func (a *myInboundAdapter) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - ctx = log.ContextWithNewID(ctx) - a.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) - a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - return a.router.RoutePacketConnection(ctx, conn, metadata) -} - -func (a *myInboundAdapter) createMetadata(conn net.Conn, metadata adapter.InboundContext) adapter.InboundContext { - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundDetour = a.listenOptions.Detour - metadata.InboundOptions = a.listenOptions.InboundOptions - if !metadata.Source.IsValid() { - metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap() - } - if !metadata.Destination.IsValid() { - metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() - } - if tcpConn, isTCP := common.Cast[*net.TCPConn](conn); isTCP { - metadata.OriginDestination = M.SocksaddrFromNet(tcpConn.LocalAddr()).Unwrap() - } - return metadata -} - -func (a *myInboundAdapter) createPacketMetadata(conn N.PacketConn, metadata adapter.InboundContext) adapter.InboundContext { - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundDetour = a.listenOptions.Detour - metadata.InboundOptions = a.listenOptions.InboundOptions - if !metadata.Destination.IsValid() { - metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() - } - return metadata -} - -func (a *myInboundAdapter) newError(err error) { - a.logger.Error(err) -} - -func (a *myInboundAdapter) NewError(ctx context.Context, err error) { - NewError(a.logger, ctx, err) -} - -func NewError(logger log.ContextLogger, ctx context.Context, err error) { - common.Close(err) - if E.IsClosedOrCanceled(err) { - logger.DebugContext(ctx, "connection closed: ", err) - return - } - logger.ErrorContext(ctx, err) -} diff --git a/inbound/default_tcp.go b/inbound/default_tcp.go deleted file mode 100644 index d680c6951a..0000000000 --- a/inbound/default_tcp.go +++ /dev/null @@ -1,88 +0,0 @@ -package inbound - -import ( - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing/common/control" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -func (a *myInboundAdapter) ListenTCP() (net.Listener, error) { - var err error - bindAddr := M.SocksaddrFrom(a.listenOptions.Listen.Build(), a.listenOptions.ListenPort) - var tcpListener net.Listener - var listenConfig net.ListenConfig - // TODO: Add an option to customize the keep alive period - listenConfig.KeepAlive = C.TCPKeepAliveInitial - listenConfig.Control = control.Append(listenConfig.Control, control.SetKeepAlivePeriod(C.TCPKeepAliveInitial, C.TCPKeepAliveInterval)) - if a.listenOptions.TCPMultiPath { - if !go121Available { - return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.") - } - setMultiPathTCP(&listenConfig) - } - if a.listenOptions.TCPFastOpen { - if !go120Available { - return nil, E.New("TCP Fast Open requires go1.20, please recompile your binary.") - } - tcpListener, err = listenTFO(listenConfig, a.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String()) - } else { - tcpListener, err = listenConfig.Listen(a.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String()) - } - if err == nil { - a.logger.Info("tcp server started at ", tcpListener.Addr()) - } - if a.listenOptions.ProxyProtocol || a.listenOptions.ProxyProtocolAcceptNoHeader { - return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0") - } - a.tcpListener = tcpListener - return tcpListener, err -} - -func (a *myInboundAdapter) loopTCPIn() { - tcpListener := a.tcpListener - for { - conn, err := tcpListener.Accept() - if err != nil { - //goland:noinspection GoDeprecation - //nolint:staticcheck - if netError, isNetError := err.(net.Error); isNetError && netError.Temporary() { - a.logger.Error(err) - continue - } - if a.inShutdown.Load() && E.IsClosed(err) { - return - } - a.tcpListener.Close() - a.logger.Error("serve error: ", err) - continue - } - go a.injectTCP(conn, adapter.InboundContext{}) - } -} - -func (a *myInboundAdapter) injectTCP(conn net.Conn, metadata adapter.InboundContext) { - ctx := log.ContextWithNewID(a.ctx) - metadata = a.createMetadata(conn, metadata) - a.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) - hErr := a.connHandler.NewConnection(ctx, conn, metadata) - if hErr != nil { - conn.Close() - a.NewError(ctx, E.Cause(hErr, "process connection from ", metadata.Source)) - } -} - -func (a *myInboundAdapter) routeTCP(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) { - a.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) - hErr := a.newConnection(ctx, conn, metadata) - if hErr != nil { - conn.Close() - a.NewError(ctx, E.Cause(hErr, "process connection from ", metadata.Source)) - } -} diff --git a/inbound/default_tcp_go1.20.go b/inbound/default_tcp_go1.20.go deleted file mode 100644 index ee3731ecda..0000000000 --- a/inbound/default_tcp_go1.20.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build go1.20 - -package inbound - -import ( - "context" - "net" - - "github.com/sagernet/tfo-go" -) - -const go120Available = true - -func listenTFO(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.Listener, error) { - var tfoConfig tfo.ListenConfig - tfoConfig.ListenConfig = listenConfig - return tfoConfig.Listen(ctx, network, address) -} diff --git a/inbound/default_tcp_go1.21.go b/inbound/default_tcp_go1.21.go deleted file mode 100644 index 906818cbe2..0000000000 --- a/inbound/default_tcp_go1.21.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build go1.21 - -package inbound - -import "net" - -const go121Available = true - -func setMultiPathTCP(listenConfig *net.ListenConfig) { - listenConfig.SetMultipathTCP(true) -} diff --git a/inbound/default_tcp_nongo1.20.go b/inbound/default_tcp_nongo1.20.go deleted file mode 100644 index e7a026bc85..0000000000 --- a/inbound/default_tcp_nongo1.20.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build !go1.20 - -package inbound - -import ( - "context" - "net" - "os" -) - -const go120Available = false - -func listenTFO(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.Listener, error) { - return nil, os.ErrInvalid -} diff --git a/inbound/default_tcp_nongo1.21.go b/inbound/default_tcp_nongo1.21.go deleted file mode 100644 index d19adb1957..0000000000 --- a/inbound/default_tcp_nongo1.21.go +++ /dev/null @@ -1,10 +0,0 @@ -//go:build !go1.21 - -package inbound - -import "net" - -const go121Available = false - -func setMultiPathTCP(listenConfig *net.ListenConfig) { -} diff --git a/inbound/default_udp.go b/inbound/default_udp.go deleted file mode 100644 index 8e39471ac7..0000000000 --- a/inbound/default_udp.go +++ /dev/null @@ -1,229 +0,0 @@ -package inbound - -import ( - "net" - "os" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/control" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -func (a *myInboundAdapter) ListenUDP() (net.PacketConn, error) { - bindAddr := M.SocksaddrFrom(a.listenOptions.Listen.Build(), a.listenOptions.ListenPort) - var lc net.ListenConfig - var udpFragment bool - if a.listenOptions.UDPFragment != nil { - udpFragment = *a.listenOptions.UDPFragment - } else { - udpFragment = a.listenOptions.UDPFragmentDefault - } - if !udpFragment { - lc.Control = control.Append(lc.Control, control.DisableUDPFragment()) - } - udpConn, err := lc.ListenPacket(a.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String()) - if err != nil { - return nil, err - } - a.udpConn = udpConn.(*net.UDPConn) - a.udpAddr = bindAddr - a.logger.Info("udp server started at ", udpConn.LocalAddr()) - return udpConn, err -} - -func (a *myInboundAdapter) loopUDPIn() { - defer close(a.packetOutboundClosed) - buffer := buf.NewPacket() - defer buffer.Release() - buffer.IncRef() - defer buffer.DecRef() - packetService := (*myInboundPacketAdapter)(a) - for { - buffer.Reset() - n, addr, err := a.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) - if err != nil { - return - } - buffer.Truncate(n) - var metadata adapter.InboundContext - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundOptions = a.listenOptions.InboundOptions - metadata.Source = M.SocksaddrFromNetIP(addr).Unwrap() - metadata.OriginDestination = a.udpAddr - err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata) - if err != nil { - a.newError(E.Cause(err, "process packet from ", metadata.Source)) - } - } -} - -func (a *myInboundAdapter) loopUDPOOBIn() { - defer close(a.packetOutboundClosed) - buffer := buf.NewPacket() - defer buffer.Release() - buffer.IncRef() - defer buffer.DecRef() - packetService := (*myInboundPacketAdapter)(a) - oob := make([]byte, 1024) - for { - buffer.Reset() - n, oobN, _, addr, err := a.udpConn.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob) - if err != nil { - return - } - buffer.Truncate(n) - var metadata adapter.InboundContext - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundOptions = a.listenOptions.InboundOptions - metadata.Source = M.SocksaddrFromNetIP(addr).Unwrap() - metadata.OriginDestination = a.udpAddr - err = a.oobPacketHandler.NewPacket(a.ctx, packetService, buffer, oob[:oobN], metadata) - if err != nil { - a.newError(E.Cause(err, "process packet from ", metadata.Source)) - } - } -} - -func (a *myInboundAdapter) loopUDPInThreadSafe() { - defer close(a.packetOutboundClosed) - packetService := (*myInboundPacketAdapter)(a) - for { - buffer := buf.NewPacket() - n, addr, err := a.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) - if err != nil { - buffer.Release() - return - } - buffer.Truncate(n) - var metadata adapter.InboundContext - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundOptions = a.listenOptions.InboundOptions - metadata.Source = M.SocksaddrFromNetIP(addr).Unwrap() - metadata.OriginDestination = a.udpAddr - err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata) - if err != nil { - buffer.Release() - a.newError(E.Cause(err, "process packet from ", metadata.Source)) - } - } -} - -func (a *myInboundAdapter) loopUDPOOBInThreadSafe() { - defer close(a.packetOutboundClosed) - packetService := (*myInboundPacketAdapter)(a) - oob := make([]byte, 1024) - for { - buffer := buf.NewPacket() - n, oobN, _, addr, err := a.udpConn.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob) - if err != nil { - buffer.Release() - return - } - buffer.Truncate(n) - var metadata adapter.InboundContext - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundOptions = a.listenOptions.InboundOptions - metadata.Source = M.SocksaddrFromNetIP(addr).Unwrap() - metadata.OriginDestination = a.udpAddr - err = a.oobPacketHandler.NewPacket(a.ctx, packetService, buffer, oob[:oobN], metadata) - if err != nil { - buffer.Release() - a.newError(E.Cause(err, "process packet from ", metadata.Source)) - } - } -} - -func (a *myInboundAdapter) loopUDPOut() { - for { - select { - case packet := <-a.packetOutbound: - err := a.writePacket(packet.buffer, packet.destination) - if err != nil && !E.IsClosed(err) { - a.newError(E.New("write back udp: ", err)) - } - continue - case <-a.packetOutboundClosed: - } - for { - select { - case packet := <-a.packetOutbound: - packet.buffer.Release() - default: - return - } - } - } -} - -func (a *myInboundAdapter) writePacket(buffer *buf.Buffer, destination M.Socksaddr) error { - defer buffer.Release() - if destination.IsFqdn() { - udpAddr, err := net.ResolveUDPAddr(N.NetworkUDP, destination.String()) - if err != nil { - return err - } - return common.Error(a.udpConn.WriteTo(buffer.Bytes(), udpAddr)) - } - return common.Error(a.udpConn.WriteToUDPAddrPort(buffer.Bytes(), destination.AddrPort())) -} - -type myInboundPacketAdapter myInboundAdapter - -func (s *myInboundPacketAdapter) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) { - n, addr, err := s.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) - if err != nil { - return M.Socksaddr{}, err - } - buffer.Truncate(n) - return M.SocksaddrFromNetIP(addr), nil -} - -func (s *myInboundPacketAdapter) WriteIsThreadUnsafe() { -} - -type myInboundPacket struct { - buffer *buf.Buffer - destination M.Socksaddr -} - -func (s *myInboundPacketAdapter) Upstream() any { - return s.udpConn -} - -func (s *myInboundPacketAdapter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { - select { - case s.packetOutbound <- &myInboundPacket{buffer, destination}: - return nil - case <-s.packetOutboundClosed: - return os.ErrClosed - } -} - -func (s *myInboundPacketAdapter) Close() error { - return s.udpConn.Close() -} - -func (s *myInboundPacketAdapter) LocalAddr() net.Addr { - return s.udpConn.LocalAddr() -} - -func (s *myInboundPacketAdapter) SetDeadline(t time.Time) error { - return s.udpConn.SetDeadline(t) -} - -func (s *myInboundPacketAdapter) SetReadDeadline(t time.Time) error { - return s.udpConn.SetReadDeadline(t) -} - -func (s *myInboundPacketAdapter) SetWriteDeadline(t time.Time) error { - return s.udpConn.SetWriteDeadline(t) -} diff --git a/inbound/direct.go b/inbound/direct.go deleted file mode 100644 index 7079a9f24f..0000000000 --- a/inbound/direct.go +++ /dev/null @@ -1,104 +0,0 @@ -package inbound - -import ( - "context" - "net" - "net/netip" - "time" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common/buf" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/udpnat" -) - -var _ adapter.Inbound = (*Direct)(nil) - -type Direct struct { - myInboundAdapter - udpNat *udpnat.Service[netip.AddrPort] - overrideOption int - overrideDestination M.Socksaddr -} - -func NewDirect(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectInboundOptions) *Direct { - options.UDPFragmentDefault = true - inbound := &Direct{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeDirect, - network: options.Network.Build(), - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - } - if options.OverrideAddress != "" && options.OverridePort != 0 { - inbound.overrideOption = 1 - inbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort) - } else if options.OverrideAddress != "" { - inbound.overrideOption = 2 - inbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort) - } else if options.OverridePort != 0 { - inbound.overrideOption = 3 - inbound.overrideDestination = M.Socksaddr{Port: options.OverridePort} - } - var udpTimeout time.Duration - if options.UDPTimeout != 0 { - udpTimeout = time.Duration(options.UDPTimeout) - } else { - udpTimeout = C.UDPTimeout - } - inbound.udpNat = udpnat.New[netip.AddrPort](int64(udpTimeout.Seconds()), adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound)) - inbound.connHandler = inbound - inbound.packetHandler = inbound - inbound.packetUpstream = inbound.udpNat - return inbound -} - -func (d *Direct) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - switch d.overrideOption { - case 1: - metadata.Destination = d.overrideDestination - case 2: - destination := d.overrideDestination - destination.Port = metadata.Destination.Port - metadata.Destination = destination - case 3: - metadata.Destination.Port = d.overrideDestination.Port - } - d.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - return d.router.RouteConnection(ctx, conn, metadata) -} - -func (d *Direct) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error { - switch d.overrideOption { - case 1: - metadata.Destination = d.overrideDestination - case 2: - destination := d.overrideDestination - destination.Port = metadata.Destination.Port - metadata.Destination = destination - case 3: - metadata.Destination.Port = d.overrideDestination.Port - } - d.udpNat.NewContextPacket(ctx, metadata.Source.AddrPort(), buffer, adapter.UpstreamMetadata(metadata), func(natConn N.PacketConn) (context.Context, N.PacketWriter) { - return adapter.WithContext(log.ContextWithNewID(ctx), &metadata), &udpnat.DirectBackWriter{Source: conn, Nat: natConn} - }) - return nil -} - -func (d *Direct) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return d.router.RouteConnection(ctx, conn, metadata) -} - -func (d *Direct) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - ctx = log.ContextWithNewID(ctx) - d.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) - return d.router.RoutePacketConnection(ctx, conn, metadata) -} diff --git a/inbound/http.go b/inbound/http.go deleted file mode 100644 index b466331995..0000000000 --- a/inbound/http.go +++ /dev/null @@ -1,114 +0,0 @@ -package inbound - -import ( - std_bufio "bufio" - "context" - "net" - "os" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/tls" - "github.com/sagernet/sing-box/common/uot" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/auth" - E "github.com/sagernet/sing/common/exceptions" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/protocol/http" -) - -var ( - _ adapter.Inbound = (*HTTP)(nil) - _ adapter.InjectableInbound = (*HTTP)(nil) -) - -type HTTP struct { - myInboundAdapter - authenticator *auth.Authenticator - tlsConfig tls.ServerConfig -} - -func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) (*HTTP, error) { - inbound := &HTTP{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeHTTP, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - setSystemProxy: options.SetSystemProxy, - }, - authenticator: auth.NewAuthenticator(options.Users), - } - if options.TLS != nil { - tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) - if err != nil { - return nil, err - } - inbound.tlsConfig = tlsConfig - } - inbound.connHandler = inbound - return inbound, nil -} - -func (h *HTTP) Start() error { - if h.tlsConfig != nil { - err := h.tlsConfig.Start() - if err != nil { - return E.Cause(err, "create TLS config") - } - } - return h.myInboundAdapter.Start() -} - -func (h *HTTP) Close() error { - return common.Close( - &h.myInboundAdapter, - h.tlsConfig, - ) -} - -func (h *HTTP) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - var err error - if h.tlsConfig != nil { - conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) - if err != nil { - return err - } - } - return http.HandleConnection(ctx, conn, std_bufio.NewReader(conn), h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata)) -} - -func (h *HTTP) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid -} - -func (a *myInboundAdapter) upstreamUserHandler(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapter { - return adapter.NewUpstreamHandler(metadata, a.newUserConnection, a.streamUserPacketConnection, a) -} - -func (a *myInboundAdapter) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - user, loaded := auth.UserFromContext[string](ctx) - if !loaded { - a.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - return a.router.RouteConnection(ctx, conn, metadata) - } - metadata.User = user - a.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) - return a.router.RouteConnection(ctx, conn, metadata) -} - -func (a *myInboundAdapter) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - user, loaded := auth.UserFromContext[string](ctx) - if !loaded { - a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - return a.router.RoutePacketConnection(ctx, conn, metadata) - } - metadata.User = user - a.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) - return a.router.RoutePacketConnection(ctx, conn, metadata) -} diff --git a/inbound/hysteria2.go b/inbound/hysteria2.go deleted file mode 100644 index c13e9531f5..0000000000 --- a/inbound/hysteria2.go +++ /dev/null @@ -1,175 +0,0 @@ -//go:build with_quic - -package inbound - -import ( - "context" - "net" - "net/http" - "net/http/httputil" - "net/url" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/tls" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-quic/hysteria" - "github.com/sagernet/sing-quic/hysteria2" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/auth" - E "github.com/sagernet/sing/common/exceptions" - N "github.com/sagernet/sing/common/network" -) - -var _ adapter.Inbound = (*Hysteria2)(nil) - -type Hysteria2 struct { - myInboundAdapter - tlsConfig tls.ServerConfig - service *hysteria2.Service[int] - userNameList []string -} - -func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (*Hysteria2, error) { - options.UDPFragmentDefault = true - if options.TLS == nil || !options.TLS.Enabled { - return nil, C.ErrTLSRequired - } - tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) - if err != nil { - return nil, err - } - var salamanderPassword string - if options.Obfs != nil { - if options.Obfs.Password == "" { - return nil, E.New("missing obfs password") - } - switch options.Obfs.Type { - case hysteria2.ObfsTypeSalamander: - salamanderPassword = options.Obfs.Password - default: - return nil, E.New("unknown obfs type: ", options.Obfs.Type) - } - } - var masqueradeHandler http.Handler - if options.Masquerade != "" { - masqueradeURL, err := url.Parse(options.Masquerade) - if err != nil { - return nil, E.Cause(err, "parse masquerade URL") - } - switch masqueradeURL.Scheme { - case "file": - masqueradeHandler = http.FileServer(http.Dir(masqueradeURL.Path)) - case "http", "https": - masqueradeHandler = &httputil.ReverseProxy{ - Rewrite: func(r *httputil.ProxyRequest) { - r.SetURL(masqueradeURL) - r.Out.Host = r.In.Host - }, - ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) { - w.WriteHeader(http.StatusBadGateway) - }, - } - default: - return nil, E.New("unknown masquerade URL scheme: ", masqueradeURL.Scheme) - } - } - inbound := &Hysteria2{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeHysteria2, - network: []string{N.NetworkUDP}, - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - tlsConfig: tlsConfig, - } - var udpTimeout time.Duration - if options.UDPTimeout != 0 { - udpTimeout = time.Duration(options.UDPTimeout) - } else { - udpTimeout = C.UDPTimeout - } - service, err := hysteria2.NewService[int](hysteria2.ServiceOptions{ - Context: ctx, - Logger: logger, - BrutalDebug: options.BrutalDebug, - SendBPS: uint64(options.UpMbps * hysteria.MbpsToBps), - ReceiveBPS: uint64(options.DownMbps * hysteria.MbpsToBps), - SalamanderPassword: salamanderPassword, - TLSConfig: tlsConfig, - IgnoreClientBandwidth: options.IgnoreClientBandwidth, - UDPTimeout: udpTimeout, - Handler: adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, nil), - MasqueradeHandler: masqueradeHandler, - }) - if err != nil { - return nil, err - } - userList := make([]int, 0, len(options.Users)) - userNameList := make([]string, 0, len(options.Users)) - userPasswordList := make([]string, 0, len(options.Users)) - for index, user := range options.Users { - userList = append(userList, index) - userNameList = append(userNameList, user.Name) - userPasswordList = append(userPasswordList, user.Password) - } - service.UpdateUsers(userList, userPasswordList) - inbound.service = service - inbound.userNameList = userNameList - return inbound, nil -} - -func (h *Hysteria2) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - ctx = log.ContextWithNewID(ctx) - metadata = h.createMetadata(conn, metadata) - h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) - userID, _ := auth.UserFromContext[int](ctx) - if userName := h.userNameList[userID]; userName != "" { - metadata.User = userName - h.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", metadata.Destination) - } else { - h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - } - return h.router.RouteConnection(ctx, conn, metadata) -} - -func (h *Hysteria2) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - ctx = log.ContextWithNewID(ctx) - metadata = h.createPacketMetadata(conn, metadata) - h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) - userID, _ := auth.UserFromContext[int](ctx) - if userName := h.userNameList[userID]; userName != "" { - metadata.User = userName - h.logger.InfoContext(ctx, "[", userName, "] inbound packet connection to ", metadata.Destination) - } else { - h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - } - return h.router.RoutePacketConnection(ctx, conn, metadata) -} - -func (h *Hysteria2) Start() error { - if h.tlsConfig != nil { - err := h.tlsConfig.Start() - if err != nil { - return err - } - } - packetConn, err := h.myInboundAdapter.ListenUDP() - if err != nil { - return err - } - return h.service.Start(packetConn) -} - -func (h *Hysteria2) Close() error { - return common.Close( - &h.myInboundAdapter, - h.tlsConfig, - common.PtrOrNil(h.service), - ) -} diff --git a/inbound/hysteria_stub.go b/inbound/hysteria_stub.go deleted file mode 100644 index fab86bb56f..0000000000 --- a/inbound/hysteria_stub.go +++ /dev/null @@ -1,20 +0,0 @@ -//go:build !with_quic - -package inbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" -) - -func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (adapter.Inbound, error) { - return nil, C.ErrQUICNotIncluded -} - -func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (adapter.Inbound, error) { - return nil, C.ErrQUICNotIncluded -} diff --git a/inbound/mixed.go b/inbound/mixed.go deleted file mode 100644 index 982842efeb..0000000000 --- a/inbound/mixed.go +++ /dev/null @@ -1,68 +0,0 @@ -package inbound - -import ( - std_bufio "bufio" - "context" - "net" - "os" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/uot" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common/auth" - "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/bufio" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/rw" - "github.com/sagernet/sing/protocol/http" - "github.com/sagernet/sing/protocol/socks" - "github.com/sagernet/sing/protocol/socks/socks4" - "github.com/sagernet/sing/protocol/socks/socks5" -) - -var ( - _ adapter.Inbound = (*Mixed)(nil) - _ adapter.InjectableInbound = (*Mixed)(nil) -) - -type Mixed struct { - myInboundAdapter - authenticator *auth.Authenticator -} - -func NewMixed(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) *Mixed { - inbound := &Mixed{ - myInboundAdapter{ - protocol: C.TypeMixed, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - setSystemProxy: options.SetSystemProxy, - }, - auth.NewAuthenticator(options.Users), - } - inbound.connHandler = inbound - return inbound -} - -func (h *Mixed) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - headerType, err := rw.ReadByte(conn) - if err != nil { - return err - } - switch headerType { - case socks4.Version, socks5.Version: - return socks.HandleConnection0(ctx, conn, headerType, h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata)) - } - reader := std_bufio.NewReader(bufio.NewCachedReader(conn, buf.As([]byte{headerType}))) - return http.HandleConnection(ctx, conn, reader, h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata)) -} - -func (h *Mixed) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid -} diff --git a/inbound/naive.go b/inbound/naive.go deleted file mode 100644 index 07328c09f4..0000000000 --- a/inbound/naive.go +++ /dev/null @@ -1,632 +0,0 @@ -package inbound - -import ( - "context" - "encoding/binary" - "io" - "math/rand" - "net" - "net/http" - "os" - "strings" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/tls" - "github.com/sagernet/sing-box/common/uot" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/auth" - "github.com/sagernet/sing/common/buf" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/rw" - sHttp "github.com/sagernet/sing/protocol/http" -) - -var _ adapter.Inbound = (*Naive)(nil) - -type Naive struct { - myInboundAdapter - authenticator *auth.Authenticator - tlsConfig tls.ServerConfig - httpServer *http.Server - h3Server any -} - -func NewNaive(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NaiveInboundOptions) (*Naive, error) { - inbound := &Naive{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeNaive, - network: options.Network.Build(), - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - authenticator: auth.NewAuthenticator(options.Users), - } - if common.Contains(inbound.network, N.NetworkUDP) { - if options.TLS == nil || !options.TLS.Enabled { - return nil, E.New("TLS is required for QUIC server") - } - } - if len(options.Users) == 0 { - return nil, E.New("missing users") - } - if options.TLS != nil { - tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) - if err != nil { - return nil, err - } - inbound.tlsConfig = tlsConfig - } - return inbound, nil -} - -func (n *Naive) Start() error { - var tlsConfig *tls.STDConfig - if n.tlsConfig != nil { - err := n.tlsConfig.Start() - if err != nil { - return E.Cause(err, "create TLS config") - } - tlsConfig, err = n.tlsConfig.Config() - if err != nil { - return err - } - } - - if common.Contains(n.network, N.NetworkTCP) { - tcpListener, err := n.ListenTCP() - if err != nil { - return err - } - n.httpServer = &http.Server{ - Handler: n, - TLSConfig: tlsConfig, - BaseContext: func(listener net.Listener) context.Context { - return n.ctx - }, - } - go func() { - var sErr error - if tlsConfig != nil { - sErr = n.httpServer.ServeTLS(tcpListener, "", "") - } else { - sErr = n.httpServer.Serve(tcpListener) - } - if sErr != nil && !E.IsClosedOrCanceled(sErr) { - n.logger.Error("http server serve error: ", sErr) - } - }() - } - - if common.Contains(n.network, N.NetworkUDP) { - err := n.configureHTTP3Listener() - if !C.WithQUIC && len(n.network) > 1 { - n.logger.Warn(E.Cause(err, "naive http3 disabled")) - } else if err != nil { - return err - } - } - - return nil -} - -func (n *Naive) Close() error { - return common.Close( - &n.myInboundAdapter, - common.PtrOrNil(n.httpServer), - n.h3Server, - n.tlsConfig, - ) -} - -func (n *Naive) ServeHTTP(writer http.ResponseWriter, request *http.Request) { - ctx := log.ContextWithNewID(request.Context()) - if request.Method != "CONNECT" { - rejectHTTP(writer, http.StatusBadRequest) - n.badRequest(ctx, request, E.New("not CONNECT request")) - return - } else if request.Header.Get("Padding") == "" { - rejectHTTP(writer, http.StatusBadRequest) - n.badRequest(ctx, request, E.New("missing naive padding")) - return - } - userName, password, authOk := sHttp.ParseBasicAuth(request.Header.Get("Proxy-Authorization")) - if authOk { - authOk = n.authenticator.Verify(userName, password) - } - if !authOk { - rejectHTTP(writer, http.StatusProxyAuthRequired) - n.badRequest(ctx, request, E.New("authorization failed")) - return - } - writer.Header().Set("Padding", generateNaivePaddingHeader()) - writer.WriteHeader(http.StatusOK) - writer.(http.Flusher).Flush() - - hostPort := request.URL.Host - if hostPort == "" { - hostPort = request.Host - } - source := sHttp.SourceAddress(request) - destination := M.ParseSocksaddr(hostPort) - - if hijacker, isHijacker := writer.(http.Hijacker); isHijacker { - conn, _, err := hijacker.Hijack() - if err != nil { - n.badRequest(ctx, request, E.New("hijack failed")) - return - } - n.newConnection(ctx, &naiveH1Conn{Conn: conn}, userName, source, destination) - } else { - n.newConnection(ctx, &naiveH2Conn{reader: request.Body, writer: writer, flusher: writer.(http.Flusher)}, userName, source, destination) - } -} - -func (n *Naive) newConnection(ctx context.Context, conn net.Conn, userName string, source, destination M.Socksaddr) { - if userName != "" { - n.logger.InfoContext(ctx, "[", userName, "] inbound connection from ", source) - n.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", destination) - } else { - n.logger.InfoContext(ctx, "inbound connection from ", source) - n.logger.InfoContext(ctx, "inbound connection to ", destination) - } - hErr := n.router.RouteConnection(ctx, conn, n.createMetadata(conn, adapter.InboundContext{ - Source: source, - Destination: destination, - User: userName, - })) - if hErr != nil { - conn.Close() - n.NewError(ctx, E.Cause(hErr, "process connection from ", source)) - } -} - -func (n *Naive) badRequest(ctx context.Context, request *http.Request, err error) { - n.NewError(ctx, E.Cause(err, "process connection from ", request.RemoteAddr)) -} - -func rejectHTTP(writer http.ResponseWriter, statusCode int) { - hijacker, ok := writer.(http.Hijacker) - if !ok { - writer.WriteHeader(statusCode) - return - } - conn, _, err := hijacker.Hijack() - if err != nil { - writer.WriteHeader(statusCode) - return - } - if tcpConn, isTCP := common.Cast[*net.TCPConn](conn); isTCP { - tcpConn.SetLinger(0) - } - conn.Close() -} - -func generateNaivePaddingHeader() string { - paddingLen := rand.Intn(32) + 30 - padding := make([]byte, paddingLen) - bits := rand.Uint64() - for i := 0; i < 16; i++ { - // Codes that won't be Huffman coded. - padding[i] = "!#$()+<>?@[]^`{}"[bits&15] - bits >>= 4 - } - for i := 16; i < paddingLen; i++ { - padding[i] = '~' - } - return string(padding) -} - -const kFirstPaddings = 8 - -type naiveH1Conn struct { - net.Conn - readPadding int - writePadding int - readRemaining int - paddingRemaining int -} - -func (c *naiveH1Conn) Read(p []byte) (n int, err error) { - n, err = c.read(p) - return n, wrapHttpError(err) -} - -func (c *naiveH1Conn) read(p []byte) (n int, err error) { - if c.readRemaining > 0 { - if len(p) > c.readRemaining { - p = p[:c.readRemaining] - } - n, err = c.Conn.Read(p) - if err != nil { - return - } - c.readRemaining -= n - return - } - if c.paddingRemaining > 0 { - err = rw.SkipN(c.Conn, c.paddingRemaining) - if err != nil { - return - } - c.paddingRemaining = 0 - } - if c.readPadding < kFirstPaddings { - var paddingHdr []byte - if len(p) >= 3 { - paddingHdr = p[:3] - } else { - paddingHdr = make([]byte, 3) - } - _, err = io.ReadFull(c.Conn, paddingHdr) - if err != nil { - return - } - originalDataSize := int(binary.BigEndian.Uint16(paddingHdr[:2])) - paddingSize := int(paddingHdr[2]) - if len(p) > originalDataSize { - p = p[:originalDataSize] - } - n, err = c.Conn.Read(p) - if err != nil { - return - } - c.readPadding++ - c.readRemaining = originalDataSize - n - c.paddingRemaining = paddingSize - return - } - return c.Conn.Read(p) -} - -func (c *naiveH1Conn) Write(p []byte) (n int, err error) { - for pLen := len(p); pLen > 0; { - var data []byte - if pLen > 65535 { - data = p[:65535] - p = p[65535:] - pLen -= 65535 - } else { - data = p - pLen = 0 - } - var writeN int - writeN, err = c.write(data) - n += writeN - if err != nil { - break - } - } - return n, wrapHttpError(err) -} - -func (c *naiveH1Conn) write(p []byte) (n int, err error) { - if c.writePadding < kFirstPaddings { - paddingSize := rand.Intn(256) - - buffer := buf.NewSize(3 + len(p) + paddingSize) - defer buffer.Release() - header := buffer.Extend(3) - binary.BigEndian.PutUint16(header, uint16(len(p))) - header[2] = byte(paddingSize) - - common.Must1(buffer.Write(p)) - _, err = c.Conn.Write(buffer.Bytes()) - if err == nil { - n = len(p) - } - c.writePadding++ - return - } - return c.Conn.Write(p) -} - -func (c *naiveH1Conn) FrontHeadroom() int { - if c.writePadding < kFirstPaddings { - return 3 - } - return 0 -} - -func (c *naiveH1Conn) RearHeadroom() int { - if c.writePadding < kFirstPaddings { - return 255 - } - return 0 -} - -func (c *naiveH1Conn) WriterMTU() int { - if c.writePadding < kFirstPaddings { - return 65535 - } - return 0 -} - -func (c *naiveH1Conn) WriteBuffer(buffer *buf.Buffer) error { - defer buffer.Release() - if c.writePadding < kFirstPaddings { - bufferLen := buffer.Len() - if bufferLen > 65535 { - return common.Error(c.Write(buffer.Bytes())) - } - paddingSize := rand.Intn(256) - header := buffer.ExtendHeader(3) - binary.BigEndian.PutUint16(header, uint16(bufferLen)) - header[2] = byte(paddingSize) - buffer.Extend(paddingSize) - c.writePadding++ - } - return wrapHttpError(common.Error(c.Conn.Write(buffer.Bytes()))) -} - -// FIXME -/*func (c *naiveH1Conn) WriteTo(w io.Writer) (n int64, err error) { - if c.readPadding < kFirstPaddings { - n, err = bufio.WriteToN(c, w, kFirstPaddings-c.readPadding) - } else { - n, err = bufio.Copy(w, c.Conn) - } - return n, wrapHttpError(err) -} - -func (c *naiveH1Conn) ReadFrom(r io.Reader) (n int64, err error) { - if c.writePadding < kFirstPaddings { - n, err = bufio.ReadFromN(c, r, kFirstPaddings-c.writePadding) - } else { - n, err = bufio.Copy(c.Conn, r) - } - return n, wrapHttpError(err) -} -*/ - -func (c *naiveH1Conn) Upstream() any { - return c.Conn -} - -func (c *naiveH1Conn) ReaderReplaceable() bool { - return c.readPadding == kFirstPaddings -} - -func (c *naiveH1Conn) WriterReplaceable() bool { - return c.writePadding == kFirstPaddings -} - -type naiveH2Conn struct { - reader io.Reader - writer io.Writer - flusher http.Flusher - rAddr net.Addr - readPadding int - writePadding int - readRemaining int - paddingRemaining int -} - -func (c *naiveH2Conn) Read(p []byte) (n int, err error) { - n, err = c.read(p) - return n, wrapHttpError(err) -} - -func (c *naiveH2Conn) read(p []byte) (n int, err error) { - if c.readRemaining > 0 { - if len(p) > c.readRemaining { - p = p[:c.readRemaining] - } - n, err = c.reader.Read(p) - if err != nil { - return - } - c.readRemaining -= n - return - } - if c.paddingRemaining > 0 { - err = rw.SkipN(c.reader, c.paddingRemaining) - if err != nil { - return - } - c.paddingRemaining = 0 - } - if c.readPadding < kFirstPaddings { - var paddingHdr []byte - if len(p) >= 3 { - paddingHdr = p[:3] - } else { - paddingHdr = make([]byte, 3) - } - _, err = io.ReadFull(c.reader, paddingHdr) - if err != nil { - return - } - originalDataSize := int(binary.BigEndian.Uint16(paddingHdr[:2])) - paddingSize := int(paddingHdr[2]) - if len(p) > originalDataSize { - p = p[:originalDataSize] - } - n, err = c.reader.Read(p) - if err != nil { - return - } - c.readPadding++ - c.readRemaining = originalDataSize - n - c.paddingRemaining = paddingSize - return - } - return c.reader.Read(p) -} - -func (c *naiveH2Conn) Write(p []byte) (n int, err error) { - for pLen := len(p); pLen > 0; { - var data []byte - if pLen > 65535 { - data = p[:65535] - p = p[65535:] - pLen -= 65535 - } else { - data = p - pLen = 0 - } - var writeN int - writeN, err = c.write(data) - n += writeN - if err != nil { - break - } - } - if err == nil { - c.flusher.Flush() - } - return n, wrapHttpError(err) -} - -func (c *naiveH2Conn) write(p []byte) (n int, err error) { - if c.writePadding < kFirstPaddings { - paddingSize := rand.Intn(256) - - buffer := buf.NewSize(3 + len(p) + paddingSize) - defer buffer.Release() - header := buffer.Extend(3) - binary.BigEndian.PutUint16(header, uint16(len(p))) - header[2] = byte(paddingSize) - - common.Must1(buffer.Write(p)) - _, err = c.writer.Write(buffer.Bytes()) - if err == nil { - n = len(p) - } - c.writePadding++ - return - } - return c.writer.Write(p) -} - -func (c *naiveH2Conn) FrontHeadroom() int { - if c.writePadding < kFirstPaddings { - return 3 - } - return 0 -} - -func (c *naiveH2Conn) RearHeadroom() int { - if c.writePadding < kFirstPaddings { - return 255 - } - return 0 -} - -func (c *naiveH2Conn) WriterMTU() int { - if c.writePadding < kFirstPaddings { - return 65535 - } - return 0 -} - -func (c *naiveH2Conn) WriteBuffer(buffer *buf.Buffer) error { - defer buffer.Release() - if c.writePadding < kFirstPaddings { - bufferLen := buffer.Len() - if bufferLen > 65535 { - return common.Error(c.Write(buffer.Bytes())) - } - paddingSize := rand.Intn(256) - header := buffer.ExtendHeader(3) - binary.BigEndian.PutUint16(header, uint16(bufferLen)) - header[2] = byte(paddingSize) - buffer.Extend(paddingSize) - c.writePadding++ - } - err := common.Error(c.writer.Write(buffer.Bytes())) - if err == nil { - c.flusher.Flush() - } - return wrapHttpError(err) -} - -// FIXME -/*func (c *naiveH2Conn) WriteTo(w io.Writer) (n int64, err error) { - if c.readPadding < kFirstPaddings { - n, err = bufio.WriteToN(c, w, kFirstPaddings-c.readPadding) - } else { - n, err = bufio.Copy(w, c.reader) - } - return n, wrapHttpError(err) -} - -func (c *naiveH2Conn) ReadFrom(r io.Reader) (n int64, err error) { - if c.writePadding < kFirstPaddings { - n, err = bufio.ReadFromN(c, r, kFirstPaddings-c.writePadding) - } else { - n, err = bufio.Copy(c.writer, r) - } - return n, wrapHttpError(err) -}*/ - -func (c *naiveH2Conn) Close() error { - return common.Close( - c.reader, - c.writer, - ) -} - -func (c *naiveH2Conn) LocalAddr() net.Addr { - return M.Socksaddr{} -} - -func (c *naiveH2Conn) RemoteAddr() net.Addr { - return c.rAddr -} - -func (c *naiveH2Conn) SetDeadline(t time.Time) error { - return os.ErrInvalid -} - -func (c *naiveH2Conn) SetReadDeadline(t time.Time) error { - return os.ErrInvalid -} - -func (c *naiveH2Conn) SetWriteDeadline(t time.Time) error { - return os.ErrInvalid -} - -func (c *naiveH2Conn) NeedAdditionalReadDeadline() bool { - return true -} - -func (c *naiveH2Conn) UpstreamReader() any { - return c.reader -} - -func (c *naiveH2Conn) UpstreamWriter() any { - return c.writer -} - -func (c *naiveH2Conn) ReaderReplaceable() bool { - return c.readPadding == kFirstPaddings -} - -func (c *naiveH2Conn) WriterReplaceable() bool { - return c.writePadding == kFirstPaddings -} - -func wrapHttpError(err error) error { - if err == nil { - return err - } - if strings.Contains(err.Error(), "client disconnected") { - return net.ErrClosed - } - if strings.Contains(err.Error(), "body closed by handler") { - return net.ErrClosed - } - if strings.Contains(err.Error(), "canceled with error code 268") { - return io.EOF - } - return err -} diff --git a/inbound/naive_quic.go b/inbound/naive_quic.go deleted file mode 100644 index 9f99bf278b..0000000000 --- a/inbound/naive_quic.go +++ /dev/null @@ -1,47 +0,0 @@ -//go:build with_quic - -package inbound - -import ( - "github.com/sagernet/quic-go" - "github.com/sagernet/quic-go/http3" - "github.com/sagernet/sing-quic" - E "github.com/sagernet/sing/common/exceptions" -) - -func (n *Naive) configureHTTP3Listener() error { - err := qtls.ConfigureHTTP3(n.tlsConfig) - if err != nil { - return err - } - - udpConn, err := n.ListenUDP() - if err != nil { - return err - } - - quicListener, err := qtls.ListenEarly(udpConn, n.tlsConfig, &quic.Config{ - MaxIncomingStreams: 1 << 60, - Allow0RTT: true, - }) - if err != nil { - udpConn.Close() - return err - } - - h3Server := &http3.Server{ - Port: int(n.listenOptions.ListenPort), - Handler: n, - } - - go func() { - sErr := h3Server.ServeListener(quicListener) - udpConn.Close() - if sErr != nil && !E.IsClosedOrCanceled(sErr) { - n.logger.Error("http3 server serve error: ", sErr) - } - }() - - n.h3Server = h3Server - return nil -} diff --git a/inbound/naive_quic_stub.go b/inbound/naive_quic_stub.go deleted file mode 100644 index 90f697e46d..0000000000 --- a/inbound/naive_quic_stub.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build !with_quic - -package inbound - -import ( - C "github.com/sagernet/sing-box/constant" -) - -func (n *Naive) configureHTTP3Listener() error { - return C.ErrQUICNotIncluded -} diff --git a/inbound/redirect.go b/inbound/redirect.go deleted file mode 100644 index 4c7cf1d585..0000000000 --- a/inbound/redirect.go +++ /dev/null @@ -1,44 +0,0 @@ -package inbound - -import ( - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/redir" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -type Redirect struct { - myInboundAdapter -} - -func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.RedirectInboundOptions) *Redirect { - redirect := &Redirect{ - myInboundAdapter{ - protocol: C.TypeRedirect, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - } - redirect.connHandler = redirect - return redirect -} - -func (r *Redirect) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - destination, err := redir.GetOriginalDestination(conn) - if err != nil { - return E.Cause(err, "get redirect destination") - } - metadata.Destination = M.SocksaddrFromNetIP(destination) - return r.newConnection(ctx, conn, metadata) -} diff --git a/inbound/shadowsocks.go b/inbound/shadowsocks.go deleted file mode 100644 index ca15b8d8e8..0000000000 --- a/inbound/shadowsocks.go +++ /dev/null @@ -1,99 +0,0 @@ -package inbound - -import ( - "context" - "net" - "os" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/mux" - "github.com/sagernet/sing-box/common/uot" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-shadowsocks" - "github.com/sagernet/sing-shadowsocks/shadowaead" - "github.com/sagernet/sing-shadowsocks/shadowaead_2022" - "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/common/ntp" -) - -func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) { - if len(options.Users) > 0 && len(options.Destinations) > 0 { - return nil, E.New("users and destinations options must not be combined") - } - if len(options.Users) > 0 { - return newShadowsocksMulti(ctx, router, logger, tag, options) - } else if len(options.Destinations) > 0 { - return newShadowsocksRelay(ctx, router, logger, tag, options) - } else { - return newShadowsocks(ctx, router, logger, tag, options) - } -} - -var ( - _ adapter.Inbound = (*Shadowsocks)(nil) - _ adapter.InjectableInbound = (*Shadowsocks)(nil) -) - -type Shadowsocks struct { - myInboundAdapter - service shadowsocks.Service -} - -func newShadowsocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*Shadowsocks, error) { - inbound := &Shadowsocks{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeShadowsocks, - network: options.Network.Build(), - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - } - - inbound.connHandler = inbound - inbound.packetHandler = inbound - var err error - inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) - if err != nil { - return nil, err - } - - var udpTimeout time.Duration - if options.UDPTimeout != 0 { - udpTimeout = time.Duration(options.UDPTimeout) - } else { - udpTimeout = C.UDPTimeout - } - switch { - case options.Method == shadowsocks.MethodNone: - inbound.service = shadowsocks.NewNoneService(int64(udpTimeout.Seconds()), inbound.upstreamContextHandler()) - case common.Contains(shadowaead.List, options.Method): - inbound.service, err = shadowaead.NewService(options.Method, nil, options.Password, int64(udpTimeout.Seconds()), inbound.upstreamContextHandler()) - case common.Contains(shadowaead_2022.List, options.Method): - inbound.service, err = shadowaead_2022.NewServiceWithPassword(options.Method, options.Password, int64(udpTimeout.Seconds()), inbound.upstreamContextHandler(), ntp.TimeFuncFromContext(ctx)) - default: - err = E.New("unsupported method: ", options.Method) - } - inbound.packetUpstream = inbound.service - return inbound, err -} - -func (h *Shadowsocks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) -} - -func (h *Shadowsocks) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error { - return h.service.NewPacket(adapter.WithContext(ctx, &metadata), conn, buffer, adapter.UpstreamMetadata(metadata)) -} - -func (h *Shadowsocks) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid -} diff --git a/inbound/shadowsocks_multi.go b/inbound/shadowsocks_multi.go deleted file mode 100644 index a291af4acb..0000000000 --- a/inbound/shadowsocks_multi.go +++ /dev/null @@ -1,139 +0,0 @@ -package inbound - -import ( - "context" - "net" - "os" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/mux" - "github.com/sagernet/sing-box/common/uot" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-shadowsocks" - "github.com/sagernet/sing-shadowsocks/shadowaead" - "github.com/sagernet/sing-shadowsocks/shadowaead_2022" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/auth" - "github.com/sagernet/sing/common/buf" - E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/ntp" -) - -var ( - _ adapter.Inbound = (*ShadowsocksMulti)(nil) - _ adapter.InjectableInbound = (*ShadowsocksMulti)(nil) -) - -type ShadowsocksMulti struct { - myInboundAdapter - service shadowsocks.MultiService[int] - users []option.ShadowsocksUser -} - -func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*ShadowsocksMulti, error) { - inbound := &ShadowsocksMulti{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeShadowsocks, - network: options.Network.Build(), - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - } - inbound.connHandler = inbound - inbound.packetHandler = inbound - var err error - inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) - if err != nil { - return nil, err - } - var udpTimeout time.Duration - if options.UDPTimeout != 0 { - udpTimeout = time.Duration(options.UDPTimeout) - } else { - udpTimeout = C.UDPTimeout - } - var service shadowsocks.MultiService[int] - if common.Contains(shadowaead_2022.List, options.Method) { - service, err = shadowaead_2022.NewMultiServiceWithPassword[int]( - options.Method, - options.Password, - int64(udpTimeout.Seconds()), - adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound), - ntp.TimeFuncFromContext(ctx), - ) - } else if common.Contains(shadowaead.List, options.Method) { - service, err = shadowaead.NewMultiService[int]( - options.Method, - int64(udpTimeout.Seconds()), - adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound)) - } else { - return nil, E.New("unsupported method: " + options.Method) - } - if err != nil { - return nil, err - } - err = service.UpdateUsersWithPasswords(common.MapIndexed(options.Users, func(index int, user option.ShadowsocksUser) int { - return index - }), common.Map(options.Users, func(user option.ShadowsocksUser) string { - return user.Password - })) - if err != nil { - return nil, err - } - inbound.service = service - inbound.packetUpstream = service - inbound.users = options.Users - return inbound, err -} - -func (h *ShadowsocksMulti) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) -} - -func (h *ShadowsocksMulti) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error { - return h.service.NewPacket(adapter.WithContext(ctx, &metadata), conn, buffer, adapter.UpstreamMetadata(metadata)) -} - -func (h *ShadowsocksMulti) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid -} - -func (h *ShadowsocksMulti) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - userIndex, loaded := auth.UserFromContext[int](ctx) - if !loaded { - return os.ErrInvalid - } - user := h.users[userIndex].Name - if user == "" { - user = F.ToString(userIndex) - } else { - metadata.User = user - } - h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) - return h.router.RouteConnection(ctx, conn, metadata) -} - -func (h *ShadowsocksMulti) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - userIndex, loaded := auth.UserFromContext[int](ctx) - if !loaded { - return os.ErrInvalid - } - user := h.users[userIndex].Name - if user == "" { - user = F.ToString(userIndex) - } else { - metadata.User = user - } - ctx = log.ContextWithNewID(ctx) - h.logger.InfoContext(ctx, "[", user, "] inbound packet connection from ", metadata.Source) - h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) - return h.router.RoutePacketConnection(ctx, conn, metadata) -} diff --git a/inbound/shadowsocks_relay.go b/inbound/shadowsocks_relay.go deleted file mode 100644 index fbc2838ade..0000000000 --- a/inbound/shadowsocks_relay.go +++ /dev/null @@ -1,124 +0,0 @@ -package inbound - -import ( - "context" - "net" - "os" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/mux" - "github.com/sagernet/sing-box/common/uot" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-shadowsocks/shadowaead_2022" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/auth" - "github.com/sagernet/sing/common/buf" - F "github.com/sagernet/sing/common/format" - N "github.com/sagernet/sing/common/network" -) - -var ( - _ adapter.Inbound = (*ShadowsocksRelay)(nil) - _ adapter.InjectableInbound = (*ShadowsocksRelay)(nil) -) - -type ShadowsocksRelay struct { - myInboundAdapter - service *shadowaead_2022.RelayService[int] - destinations []option.ShadowsocksDestination -} - -func newShadowsocksRelay(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*ShadowsocksRelay, error) { - inbound := &ShadowsocksRelay{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeShadowsocks, - network: options.Network.Build(), - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - destinations: options.Destinations, - } - inbound.connHandler = inbound - inbound.packetHandler = inbound - var err error - inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) - if err != nil { - return nil, err - } - var udpTimeout time.Duration - if options.UDPTimeout != 0 { - udpTimeout = time.Duration(options.UDPTimeout) - } else { - udpTimeout = C.UDPTimeout - } - service, err := shadowaead_2022.NewRelayServiceWithPassword[int]( - options.Method, - options.Password, - int64(udpTimeout.Seconds()), - adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound), - ) - if err != nil { - return nil, err - } - err = service.UpdateUsersWithPasswords(common.MapIndexed(options.Destinations, func(index int, user option.ShadowsocksDestination) int { - return index - }), common.Map(options.Destinations, func(user option.ShadowsocksDestination) string { - return user.Password - }), common.Map(options.Destinations, option.ShadowsocksDestination.Build)) - if err != nil { - return nil, err - } - inbound.service = service - inbound.packetUpstream = service - return inbound, err -} - -func (h *ShadowsocksRelay) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) -} - -func (h *ShadowsocksRelay) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error { - return h.service.NewPacket(adapter.WithContext(ctx, &metadata), conn, buffer, adapter.UpstreamMetadata(metadata)) -} - -func (h *ShadowsocksRelay) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid -} - -func (h *ShadowsocksRelay) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - destinationIndex, loaded := auth.UserFromContext[int](ctx) - if !loaded { - return os.ErrInvalid - } - destination := h.destinations[destinationIndex].Name - if destination == "" { - destination = F.ToString(destinationIndex) - } else { - metadata.User = destination - } - h.logger.InfoContext(ctx, "[", destination, "] inbound connection to ", metadata.Destination) - return h.router.RouteConnection(ctx, conn, metadata) -} - -func (h *ShadowsocksRelay) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - destinationIndex, loaded := auth.UserFromContext[int](ctx) - if !loaded { - return os.ErrInvalid - } - destination := h.destinations[destinationIndex].Name - if destination == "" { - destination = F.ToString(destinationIndex) - } else { - metadata.User = destination - } - ctx = log.ContextWithNewID(ctx) - h.logger.InfoContext(ctx, "[", destination, "] inbound packet connection from ", metadata.Source) - h.logger.InfoContext(ctx, "[", destination, "] inbound packet connection to ", metadata.Destination) - return h.router.RoutePacketConnection(ctx, conn, metadata) -} diff --git a/inbound/shadowtls.go b/inbound/shadowtls.go deleted file mode 100644 index 358564f221..0000000000 --- a/inbound/shadowtls.go +++ /dev/null @@ -1,93 +0,0 @@ -package inbound - -import ( - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/dialer" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-shadowtls" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/auth" - N "github.com/sagernet/sing/common/network" -) - -type ShadowTLS struct { - myInboundAdapter - service *shadowtls.Service -} - -func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSInboundOptions) (*ShadowTLS, error) { - inbound := &ShadowTLS{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeShadowTLS, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - } - - if options.Version == 0 { - options.Version = 1 - } - - var handshakeForServerName map[string]shadowtls.HandshakeConfig - if options.Version > 1 { - handshakeForServerName = make(map[string]shadowtls.HandshakeConfig) - for serverName, serverOptions := range options.HandshakeForServerName { - handshakeDialer, err := dialer.New(router, serverOptions.DialerOptions) - if err != nil { - return nil, err - } - handshakeForServerName[serverName] = shadowtls.HandshakeConfig{ - Server: serverOptions.ServerOptions.Build(), - Dialer: handshakeDialer, - } - } - } - handshakeDialer, err := dialer.New(router, options.Handshake.DialerOptions) - if err != nil { - return nil, err - } - service, err := shadowtls.NewService(shadowtls.ServiceConfig{ - Version: options.Version, - Password: options.Password, - Users: common.Map(options.Users, func(it option.ShadowTLSUser) shadowtls.User { - return (shadowtls.User)(it) - }), - Handshake: shadowtls.HandshakeConfig{ - Server: options.Handshake.ServerOptions.Build(), - Dialer: handshakeDialer, - }, - HandshakeForServerName: handshakeForServerName, - StrictMode: options.StrictMode, - Handler: adapter.NewUpstreamContextHandler(inbound.newConnection, nil, inbound), - Logger: logger, - }) - if err != nil { - return nil, err - } - inbound.service = service - inbound.connHandler = inbound - return inbound, nil -} - -func (h *ShadowTLS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) -} - -func (h *ShadowTLS) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - if userName, _ := auth.UserFromContext[string](ctx); userName != "" { - metadata.User = userName - h.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", metadata.Destination) - } else { - h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - } - return h.router.RouteConnection(ctx, conn, metadata) -} diff --git a/inbound/socks.go b/inbound/socks.go deleted file mode 100644 index 7c5183c9f7..0000000000 --- a/inbound/socks.go +++ /dev/null @@ -1,51 +0,0 @@ -package inbound - -import ( - "context" - "net" - "os" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/uot" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common/auth" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/protocol/socks" -) - -var ( - _ adapter.Inbound = (*Socks)(nil) - _ adapter.InjectableInbound = (*Socks)(nil) -) - -type Socks struct { - myInboundAdapter - authenticator *auth.Authenticator -} - -func NewSocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SocksInboundOptions) *Socks { - inbound := &Socks{ - myInboundAdapter{ - protocol: C.TypeSOCKS, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - auth.NewAuthenticator(options.Users), - } - inbound.connHandler = inbound - return inbound -} - -func (h *Socks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return socks.HandleConnection(ctx, conn, h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata)) -} - -func (h *Socks) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid -} diff --git a/inbound/tproxy.go b/inbound/tproxy.go deleted file mode 100644 index a074eb495b..0000000000 --- a/inbound/tproxy.go +++ /dev/null @@ -1,130 +0,0 @@ -package inbound - -import ( - "context" - "net" - "net/netip" - "syscall" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/redir" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/control" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/udpnat" -) - -type TProxy struct { - myInboundAdapter - udpNat *udpnat.Service[netip.AddrPort] -} - -func NewTProxy(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TProxyInboundOptions) *TProxy { - tproxy := &TProxy{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeTProxy, - network: options.Network.Build(), - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - } - var udpTimeout time.Duration - if options.UDPTimeout != 0 { - udpTimeout = time.Duration(options.UDPTimeout) - } else { - udpTimeout = C.UDPTimeout - } - tproxy.connHandler = tproxy - tproxy.oobPacketHandler = tproxy - tproxy.udpNat = udpnat.New[netip.AddrPort](int64(udpTimeout.Seconds()), tproxy.upstreamContextHandler()) - tproxy.packetUpstream = tproxy.udpNat - return tproxy -} - -func (t *TProxy) Start() error { - err := t.myInboundAdapter.Start() - if err != nil { - return err - } - if t.tcpListener != nil { - err = control.Conn(common.MustCast[syscall.Conn](t.tcpListener), func(fd uintptr) error { - return redir.TProxy(fd, M.SocksaddrFromNet(t.tcpListener.Addr()).Addr.Is6()) - }) - if err != nil { - return E.Cause(err, "configure tproxy TCP listener") - } - } - if t.udpConn != nil { - err = control.Conn(t.udpConn, func(fd uintptr) error { - return redir.TProxy(fd, M.SocksaddrFromNet(t.udpConn.LocalAddr()).Addr.Is6()) - }) - if err != nil { - return E.Cause(err, "configure tproxy UDP listener") - } - } - return nil -} - -func (t *TProxy) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() - return t.newConnection(ctx, conn, metadata) -} - -func (t *TProxy) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata adapter.InboundContext) error { - destination, err := redir.GetOriginalDestinationFromOOB(oob) - if err != nil { - return E.Cause(err, "get tproxy destination") - } - metadata.Destination = M.SocksaddrFromNetIP(destination).Unwrap() - t.udpNat.NewContextPacket(ctx, metadata.Source.AddrPort(), buffer, adapter.UpstreamMetadata(metadata), func(natConn N.PacketConn) (context.Context, N.PacketWriter) { - return adapter.WithContext(log.ContextWithNewID(ctx), &metadata), &tproxyPacketWriter{ctx: ctx, source: natConn, destination: metadata.Destination} - }) - return nil -} - -type tproxyPacketWriter struct { - ctx context.Context - source N.PacketConn - destination M.Socksaddr - conn *net.UDPConn -} - -func (w *tproxyPacketWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { - defer buffer.Release() - conn := w.conn - if w.destination == destination && conn != nil { - _, err := conn.WriteToUDPAddrPort(buffer.Bytes(), M.AddrPortFromNet(w.source.LocalAddr())) - if err != nil { - w.conn = nil - } - return err - } - var listener net.ListenConfig - listener.Control = control.Append(listener.Control, control.ReuseAddr()) - listener.Control = control.Append(listener.Control, redir.TProxyWriteBack()) - packetConn, err := listener.ListenPacket(w.ctx, "udp", destination.String()) - if err != nil { - return err - } - udpConn := packetConn.(*net.UDPConn) - if w.destination == destination { - w.conn = udpConn - } else { - defer udpConn.Close() - } - return common.Error(udpConn.WriteToUDPAddrPort(buffer.Bytes(), M.AddrPortFromNet(w.source.LocalAddr()))) -} - -func (w *tproxyPacketWriter) Close() error { - return common.Close(common.PtrOrNil(w.conn)) -} diff --git a/inbound/tuic_stub.go b/inbound/tuic_stub.go deleted file mode 100644 index bfd402ab45..0000000000 --- a/inbound/tuic_stub.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build !with_quic - -package inbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" -) - -func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICInboundOptions) (adapter.Inbound, error) { - return nil, C.ErrQUICNotIncluded -} diff --git a/inbound/tun.go b/inbound/tun.go deleted file mode 100644 index 7bd700d348..0000000000 --- a/inbound/tun.go +++ /dev/null @@ -1,246 +0,0 @@ -package inbound - -import ( - "context" - "net" - "strconv" - "strings" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/taskmonitor" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/libbox/platform" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-tun" - "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/ranges" -) - -var _ adapter.Inbound = (*Tun)(nil) - -type Tun struct { - tag string - ctx context.Context - router adapter.Router - logger log.ContextLogger - inboundOptions option.InboundOptions - tunOptions tun.Options - endpointIndependentNat bool - udpTimeout int64 - stack string - tunIf tun.Tun - tunStack tun.Stack - platformInterface platform.Interface - platformOptions option.TunPlatformOptions -} - -func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) { - tunMTU := options.MTU - if tunMTU == 0 { - tunMTU = 9000 - } - var udpTimeout time.Duration - if options.UDPTimeout != 0 { - udpTimeout = time.Duration(options.UDPTimeout) - } else { - udpTimeout = C.UDPTimeout - } - includeUID := uidToRange(options.IncludeUID) - if len(options.IncludeUIDRange) > 0 { - var err error - includeUID, err = parseRange(includeUID, options.IncludeUIDRange) - if err != nil { - return nil, E.Cause(err, "parse include_uid_range") - } - } - excludeUID := uidToRange(options.ExcludeUID) - if len(options.ExcludeUIDRange) > 0 { - var err error - excludeUID, err = parseRange(excludeUID, options.ExcludeUIDRange) - if err != nil { - return nil, E.Cause(err, "parse exclude_uid_range") - } - } - return &Tun{ - tag: tag, - ctx: ctx, - router: router, - logger: logger, - inboundOptions: options.InboundOptions, - tunOptions: tun.Options{ - Name: options.InterfaceName, - MTU: tunMTU, - GSO: options.GSO, - Inet4Address: options.Inet4Address, - Inet6Address: options.Inet6Address, - AutoRoute: options.AutoRoute, - StrictRoute: options.StrictRoute, - IncludeInterface: options.IncludeInterface, - ExcludeInterface: options.ExcludeInterface, - Inet4RouteAddress: options.Inet4RouteAddress, - Inet6RouteAddress: options.Inet6RouteAddress, - Inet4RouteExcludeAddress: options.Inet4RouteExcludeAddress, - Inet6RouteExcludeAddress: options.Inet6RouteExcludeAddress, - IncludeUID: includeUID, - ExcludeUID: excludeUID, - IncludeAndroidUser: options.IncludeAndroidUser, - IncludePackage: options.IncludePackage, - ExcludePackage: options.ExcludePackage, - InterfaceMonitor: router.InterfaceMonitor(), - TableIndex: 2022, - }, - endpointIndependentNat: options.EndpointIndependentNat, - udpTimeout: int64(udpTimeout.Seconds()), - stack: options.Stack, - platformInterface: platformInterface, - platformOptions: common.PtrValueOrDefault(options.Platform), - }, nil -} - -func uidToRange(uidList option.Listable[uint32]) []ranges.Range[uint32] { - return common.Map(uidList, func(uid uint32) ranges.Range[uint32] { - return ranges.NewSingle(uid) - }) -} - -func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges.Range[uint32], error) { - for _, uidRange := range rangeList { - if !strings.Contains(uidRange, ":") { - return nil, E.New("missing ':' in range: ", uidRange) - } - subIndex := strings.Index(uidRange, ":") - if subIndex == 0 { - return nil, E.New("missing range start: ", uidRange) - } else if subIndex == len(uidRange)-1 { - return nil, E.New("missing range end: ", uidRange) - } - var start, end uint64 - var err error - start, err = strconv.ParseUint(uidRange[:subIndex], 10, 32) - if err != nil { - return nil, E.Cause(err, "parse range start") - } - end, err = strconv.ParseUint(uidRange[subIndex+1:], 10, 32) - if err != nil { - return nil, E.Cause(err, "parse range end") - } - uidRanges = append(uidRanges, ranges.New(uint32(start), uint32(end))) - } - return uidRanges, nil -} - -func (t *Tun) Type() string { - return C.TypeTun -} - -func (t *Tun) Tag() string { - return t.tag -} - -func (t *Tun) Start() error { - if C.IsAndroid && t.platformInterface == nil { - t.tunOptions.BuildAndroidRules(t.router.PackageManager(), t) - } - if t.tunOptions.Name == "" { - t.tunOptions.Name = tun.CalculateInterfaceName("") - } - var ( - tunInterface tun.Tun - err error - ) - monitor := taskmonitor.New(t.logger, C.StartTimeout) - monitor.Start("open tun interface") - if t.platformInterface != nil { - tunInterface, err = t.platformInterface.OpenTun(&t.tunOptions, t.platformOptions) - } else { - tunInterface, err = tun.New(t.tunOptions) - } - monitor.Finish() - if err != nil { - return E.Cause(err, "configure tun interface") - } - t.logger.Trace("creating stack") - t.tunIf = tunInterface - var ( - forwarderBindInterface bool - includeAllNetworks bool - ) - if t.platformInterface != nil { - forwarderBindInterface = true - includeAllNetworks = t.platformInterface.IncludeAllNetworks() - } - tunStack, err := tun.NewStack(t.stack, tun.StackOptions{ - Context: t.ctx, - Tun: tunInterface, - TunOptions: t.tunOptions, - EndpointIndependentNat: t.endpointIndependentNat, - UDPTimeout: t.udpTimeout, - Handler: t, - Logger: t.logger, - ForwarderBindInterface: forwarderBindInterface, - InterfaceFinder: t.router.InterfaceFinder(), - IncludeAllNetworks: includeAllNetworks, - }) - if err != nil { - return err - } - monitor.Start("initiating tun stack") - err = tunStack.Start() - monitor.Finish() - t.tunStack = tunStack - if err != nil { - return err - } - t.logger.Info("started at ", t.tunOptions.Name) - return nil -} - -func (t *Tun) Close() error { - return common.Close( - t.tunStack, - t.tunIf, - ) -} - -func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata M.Metadata) error { - ctx = log.ContextWithNewID(ctx) - var metadata adapter.InboundContext - metadata.Inbound = t.tag - metadata.InboundType = C.TypeTun - metadata.Source = upstreamMetadata.Source - metadata.Destination = upstreamMetadata.Destination - metadata.InboundOptions = t.inboundOptions - t.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) - t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - err := t.router.RouteConnection(ctx, conn, metadata) - if err != nil { - t.NewError(ctx, err) - } - return nil -} - -func (t *Tun) NewPacketConnection(ctx context.Context, conn N.PacketConn, upstreamMetadata M.Metadata) error { - ctx = log.ContextWithNewID(ctx) - var metadata adapter.InboundContext - metadata.Inbound = t.tag - metadata.InboundType = C.TypeTun - metadata.Source = upstreamMetadata.Source - metadata.Destination = upstreamMetadata.Destination - metadata.InboundOptions = t.inboundOptions - t.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) - t.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - err := t.router.RoutePacketConnection(ctx, conn, metadata) - if err != nil { - t.NewError(ctx, err) - } - return nil -} - -func (t *Tun) NewError(ctx context.Context, err error) { - NewError(t.logger, ctx, err) -} diff --git a/inbound/vless.go b/inbound/vless.go deleted file mode 100644 index 69ed042bc3..0000000000 --- a/inbound/vless.go +++ /dev/null @@ -1,197 +0,0 @@ -package inbound - -import ( - "context" - "net" - "os" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/mux" - "github.com/sagernet/sing-box/common/tls" - "github.com/sagernet/sing-box/common/uot" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-box/transport/v2ray" - "github.com/sagernet/sing-vmess" - "github.com/sagernet/sing-vmess/packetaddr" - "github.com/sagernet/sing-vmess/vless" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/auth" - E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -var ( - _ adapter.Inbound = (*VLESS)(nil) - _ adapter.InjectableInbound = (*VLESS)(nil) -) - -type VLESS struct { - myInboundAdapter - ctx context.Context - users []option.VLESSUser - service *vless.Service[int] - tlsConfig tls.ServerConfig - transport adapter.V2RayServerTransport -} - -func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSInboundOptions) (*VLESS, error) { - inbound := &VLESS{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeVLESS, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - ctx: ctx, - users: options.Users, - } - var err error - inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) - if err != nil { - return nil, err - } - service := vless.NewService[int](logger, adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound)) - service.UpdateUsers(common.MapIndexed(inbound.users, func(index int, _ option.VLESSUser) int { - return index - }), common.Map(inbound.users, func(it option.VLESSUser) string { - return it.UUID - }), common.Map(inbound.users, func(it option.VLESSUser) string { - return it.Flow - })) - inbound.service = service - if options.TLS != nil { - inbound.tlsConfig, err = tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) - if err != nil { - return nil, err - } - } - if options.Transport != nil { - inbound.transport, err = v2ray.NewServerTransport(ctx, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*vlessTransportHandler)(inbound)) - if err != nil { - return nil, E.Cause(err, "create server transport: ", options.Transport.Type) - } - } - inbound.connHandler = inbound - return inbound, nil -} - -func (h *VLESS) Start() error { - err := common.Start( - h.service, - h.tlsConfig, - ) - if err != nil { - return err - } - if h.transport == nil { - return h.myInboundAdapter.Start() - } - if common.Contains(h.transport.Network(), N.NetworkTCP) { - tcpListener, err := h.myInboundAdapter.ListenTCP() - if err != nil { - return err - } - go func() { - sErr := h.transport.Serve(tcpListener) - if sErr != nil && !E.IsClosed(sErr) { - h.logger.Error("transport serve error: ", sErr) - } - }() - } - if common.Contains(h.transport.Network(), N.NetworkUDP) { - udpConn, err := h.myInboundAdapter.ListenUDP() - if err != nil { - return err - } - go func() { - sErr := h.transport.ServePacket(udpConn) - if sErr != nil && !E.IsClosed(sErr) { - h.logger.Error("transport serve error: ", sErr) - } - }() - } - return nil -} - -func (h *VLESS) Close() error { - return common.Close( - h.service, - &h.myInboundAdapter, - h.tlsConfig, - h.transport, - ) -} - -func (h *VLESS) newTransportConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - h.injectTCP(conn, metadata) - return nil -} - -func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - var err error - if h.tlsConfig != nil && h.transport == nil { - conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) - if err != nil { - return err - } - } - return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) -} - -func (h *VLESS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid -} - -func (h *VLESS) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - userIndex, loaded := auth.UserFromContext[int](ctx) - if !loaded { - return os.ErrInvalid - } - user := h.users[userIndex].Name - if user == "" { - user = F.ToString(userIndex) - } else { - metadata.User = user - } - h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) - return h.router.RouteConnection(ctx, conn, metadata) -} - -func (h *VLESS) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - userIndex, loaded := auth.UserFromContext[int](ctx) - if !loaded { - return os.ErrInvalid - } - user := h.users[userIndex].Name - if user == "" { - user = F.ToString(userIndex) - } else { - metadata.User = user - } - if metadata.Destination.Fqdn == packetaddr.SeqPacketMagicAddress { - metadata.Destination = M.Socksaddr{} - conn = packetaddr.NewConn(conn.(vmess.PacketConn), metadata.Destination) - h.logger.InfoContext(ctx, "[", user, "] inbound packet addr connection") - } else { - h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) - } - return h.router.RoutePacketConnection(ctx, conn, metadata) -} - -var _ adapter.V2RayServerTransportHandler = (*vlessTransportHandler)(nil) - -type vlessTransportHandler VLESS - -func (t *vlessTransportHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { - return (*VLESS)(t).newTransportConnection(ctx, conn, adapter.InboundContext{ - Source: metadata.Source, - Destination: metadata.Destination, - }) -} diff --git a/include/acme.go b/include/acme.go new file mode 100644 index 0000000000..093fd50823 --- /dev/null +++ b/include/acme.go @@ -0,0 +1,12 @@ +//go:build with_acme + +package include + +import ( + "github.com/sagernet/sing-box/adapter/certificate" + "github.com/sagernet/sing-box/service/acme" +) + +func registerACMECertificateProvider(registry *certificate.Registry) { + acme.RegisterCertificateProvider(registry) +} diff --git a/include/acme_stub.go b/include/acme_stub.go new file mode 100644 index 0000000000..bceab3d731 --- /dev/null +++ b/include/acme_stub.go @@ -0,0 +1,20 @@ +//go:build !with_acme + +package include + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/certificate" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func registerACMECertificateProvider(registry *certificate.Registry) { + certificate.Register[option.ACMECertificateProviderOptions](registry, C.TypeACME, func(ctx context.Context, logger log.ContextLogger, tag string, options option.ACMECertificateProviderOptions) (adapter.CertificateProviderService, error) { + return nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`) + }) +} diff --git a/include/awg.go b/include/awg.go new file mode 100644 index 0000000000..9e37665cb3 --- /dev/null +++ b/include/awg.go @@ -0,0 +1,12 @@ +//go:build with_awg + +package include + +import ( + "github.com/sagernet/sing-box/adapter/endpoint" + "github.com/sagernet/sing-box/protocol/awg" +) + +func registerAwgEndpoint(registry *endpoint.Registry) { + awg.RegisterEndpoint(registry) +} diff --git a/include/awg_stub.go b/include/awg_stub.go new file mode 100644 index 0000000000..8c24fc6ff0 --- /dev/null +++ b/include/awg_stub.go @@ -0,0 +1,20 @@ +//go:build !with_awg + +package include + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/endpoint" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func registerAwgEndpoint(registry *endpoint.Registry) { + endpoint.Register(registry, C.TypeAwg, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.AwgEndpointOptions) (adapter.Endpoint, error) { + return nil, E.New(`Awg is not included in this build, rebuild with -tags with_awg`) + }) +} diff --git a/include/ccm.go b/include/ccm.go new file mode 100644 index 0000000000..a752014850 --- /dev/null +++ b/include/ccm.go @@ -0,0 +1,12 @@ +//go:build with_ccm && (!darwin || cgo) + +package include + +import ( + "github.com/sagernet/sing-box/adapter/service" + "github.com/sagernet/sing-box/service/ccm" +) + +func registerCCMService(registry *service.Registry) { + ccm.RegisterService(registry) +} diff --git a/include/ccm_stub.go b/include/ccm_stub.go new file mode 100644 index 0000000000..eac29eebdd --- /dev/null +++ b/include/ccm_stub.go @@ -0,0 +1,20 @@ +//go:build !with_ccm + +package include + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/service" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func registerCCMService(registry *service.Registry) { + service.Register[option.CCMServiceOptions](registry, C.TypeCCM, func(ctx context.Context, logger log.ContextLogger, tag string, options option.CCMServiceOptions) (adapter.Service, error) { + return nil, E.New(`CCM is not included in this build, rebuild with -tags with_CCM`) + }) +} diff --git a/include/ccm_stub_darwin.go b/include/ccm_stub_darwin.go new file mode 100644 index 0000000000..f2ad738168 --- /dev/null +++ b/include/ccm_stub_darwin.go @@ -0,0 +1,20 @@ +//go:build with_ccm && darwin && !cgo + +package include + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/service" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func registerCCMService(registry *service.Registry) { + service.Register[option.CCMServiceOptions](registry, C.TypeCCM, func(ctx context.Context, logger log.ContextLogger, tag string, options option.CCMServiceOptions) (adapter.Service, error) { + return nil, E.New(`CCM requires CGO on darwin, rebuild with CGO_ENABLED=1`) + }) +} diff --git a/include/clashapi_stub.go b/include/clashapi_stub.go index 26e199b314..e7d5304fa1 100644 --- a/include/clashapi_stub.go +++ b/include/clashapi_stub.go @@ -13,7 +13,7 @@ import ( ) func init() { - experimental.RegisterClashServerConstructor(func(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { + experimental.RegisterClashServerConstructor(func(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { return nil, E.New(`clash api is not included in this build, rebuild with -tags with_clash_api`) }) } diff --git a/include/cloudflared.go b/include/cloudflared.go new file mode 100644 index 0000000000..6320010825 --- /dev/null +++ b/include/cloudflared.go @@ -0,0 +1,12 @@ +//go:build with_cloudflared + +package include + +import ( + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/protocol/cloudflare" +) + +func registerCloudflaredInbound(registry *inbound.Registry) { + cloudflare.RegisterInbound(registry) +} diff --git a/include/cloudflared_stub.go b/include/cloudflared_stub.go new file mode 100644 index 0000000000..8f49aecc69 --- /dev/null +++ b/include/cloudflared_stub.go @@ -0,0 +1,20 @@ +//go:build !with_cloudflared + +package include + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func registerCloudflaredInbound(registry *inbound.Registry) { + inbound.Register[option.CloudflaredInboundOptions](registry, C.TypeCloudflared, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.CloudflaredInboundOptions) (adapter.Inbound, error) { + return nil, E.New(`Cloudflared is not included in this build, rebuild with -tags with_cloudflared`) + }) +} diff --git a/include/dhcp.go b/include/dhcp.go index 0e4b4ccf49..8cf074becd 100644 --- a/include/dhcp.go +++ b/include/dhcp.go @@ -2,4 +2,11 @@ package include -import _ "github.com/sagernet/sing-box/transport/dhcp" +import ( + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/dns/transport/dhcp" +) + +func registerDHCPTransport(registry *dns.TransportRegistry) { + dhcp.RegisterTransport(registry) +} diff --git a/include/dhcp_stub.go b/include/dhcp_stub.go index 47a19d2e57..272f313aaf 100644 --- a/include/dhcp_stub.go +++ b/include/dhcp_stub.go @@ -3,12 +3,18 @@ package include import ( - "github.com/sagernet/sing-dns" + "context" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" ) -func init() { - dns.RegisterTransport([]string{"dhcp"}, func(options dns.TransportOptions) (dns.Transport, error) { +func registerDHCPTransport(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.DHCPDNSServerOptions](registry, C.DNSTypeDHCP, func(ctx context.Context, logger log.ContextLogger, tag string, options option.DHCPDNSServerOptions) (adapter.DNSTransport, error) { return nil, E.New(`DHCP is not included in this build, rebuild with -tags with_dhcp`) }) } diff --git a/include/masque.go b/include/masque.go new file mode 100644 index 0000000000..fdd75d9883 --- /dev/null +++ b/include/masque.go @@ -0,0 +1,12 @@ +//go:build with_masque + +package include + +import ( + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/protocol/masque" +) + +func registerMASQUEOutbound(registry *outbound.Registry) { + masque.RegisterOutbound(registry) +} diff --git a/include/masque_stub.go b/include/masque_stub.go new file mode 100644 index 0000000000..fc31da68f0 --- /dev/null +++ b/include/masque_stub.go @@ -0,0 +1,20 @@ +//go:build !with_masque + +package include + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func registerMASQUEOutbound(registry *outbound.Registry) { + outbound.Register[option.MASQUEOutboundOptions](registry, C.TypeMASQUE, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.MASQUEOutboundOptions) (adapter.Outbound, error) { + return nil, E.New(`MASQUE outbound is not included in this build, rebuild with -tags with_masque`) + }) +} diff --git a/include/naive_outbound.go b/include/naive_outbound.go new file mode 100644 index 0000000000..d15d0450c6 --- /dev/null +++ b/include/naive_outbound.go @@ -0,0 +1,12 @@ +//go:build with_naive_outbound + +package include + +import ( + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/protocol/naive" +) + +func registerNaiveOutbound(registry *outbound.Registry) { + naive.RegisterOutbound(registry) +} diff --git a/include/naive_outbound_stub.go b/include/naive_outbound_stub.go new file mode 100644 index 0000000000..cf892091d6 --- /dev/null +++ b/include/naive_outbound_stub.go @@ -0,0 +1,20 @@ +//go:build !with_naive_outbound + +package include + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func registerNaiveOutbound(registry *outbound.Registry) { + outbound.Register[option.NaiveOutboundOptions](registry, C.TypeNaive, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NaiveOutboundOptions) (adapter.Outbound, error) { + return nil, E.New(`naive outbound is not included in this build, rebuild with -tags with_naive_outbound`) + }) +} diff --git a/include/ocm.go b/include/ocm.go new file mode 100644 index 0000000000..cdea9eeaea --- /dev/null +++ b/include/ocm.go @@ -0,0 +1,12 @@ +//go:build with_ocm + +package include + +import ( + "github.com/sagernet/sing-box/adapter/service" + "github.com/sagernet/sing-box/service/ocm" +) + +func registerOCMService(registry *service.Registry) { + ocm.RegisterService(registry) +} diff --git a/include/ocm_stub.go b/include/ocm_stub.go new file mode 100644 index 0000000000..d5a94fcba9 --- /dev/null +++ b/include/ocm_stub.go @@ -0,0 +1,20 @@ +//go:build !with_ocm + +package include + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/service" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func registerOCMService(registry *service.Registry) { + service.Register[option.OCMServiceOptions](registry, C.TypeOCM, func(ctx context.Context, logger log.ContextLogger, tag string, options option.OCMServiceOptions) (adapter.Service, error) { + return nil, E.New(`OCM is not included in this build, rebuild with -tags with_ocm`) + }) +} diff --git a/include/oom_killer.go b/include/oom_killer.go new file mode 100644 index 0000000000..3f70d9d0e1 --- /dev/null +++ b/include/oom_killer.go @@ -0,0 +1,10 @@ +package include + +import ( + "github.com/sagernet/sing-box/adapter/service" + "github.com/sagernet/sing-box/service/oomkiller" +) + +func registerOOMKillerService(registry *service.Registry) { + oomkiller.RegisterService(registry) +} diff --git a/include/quic.go b/include/quic.go index 1bcc0fbc90..42182c299d 100644 --- a/include/quic.go +++ b/include/quic.go @@ -3,6 +3,35 @@ package include import ( + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/adapter/service" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/dns/transport/quic" + "github.com/sagernet/sing-box/protocol/hysteria" + "github.com/sagernet/sing-box/protocol/hysteria2" + _ "github.com/sagernet/sing-box/protocol/naive/quic" + "github.com/sagernet/sing-box/protocol/tuic" _ "github.com/sagernet/sing-box/transport/v2rayquic" - _ "github.com/sagernet/sing-dns/quic" ) + +func registerQUICInbounds(registry *inbound.Registry) { + hysteria.RegisterInbound(registry) + tuic.RegisterInbound(registry) + hysteria2.RegisterInbound(registry) +} + +func registerQUICOutbounds(registry *outbound.Registry) { + hysteria.RegisterOutbound(registry) + tuic.RegisterOutbound(registry) + hysteria2.RegisterOutbound(registry) +} + +func registerQUICTransports(registry *dns.TransportRegistry) { + quic.RegisterTransport(registry) + quic.RegisterHTTP3Transport(registry) +} + +func registerQUICServices(registry *service.Registry) { + hysteria2.RegisterRealmService(registry) +} diff --git a/include/quic_stub.go b/include/quic_stub.go index ddf9723f9a..b603cf48d0 100644 --- a/include/quic_stub.go +++ b/include/quic_stub.go @@ -4,23 +4,29 @@ package include import ( "context" + "io" + "net/http" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/adapter/service" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/protocol/naive" "github.com/sagernet/sing-box/transport/v2ray" - "github.com/sagernet/sing-dns" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) func init() { - dns.RegisterTransport([]string{"quic", "h3"}, func(options dns.TransportOptions) (dns.Transport, error) { - return nil, C.ErrQUICNotIncluded - }) v2ray.RegisterQUICConstructor( - func(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { + func(ctx context.Context, logger logger.ContextLogger, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { return nil, C.ErrQUICNotIncluded }, func(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayQUICOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) { @@ -28,3 +34,45 @@ func init() { }, ) } + +func registerQUICInbounds(registry *inbound.Registry) { + inbound.Register[option.HysteriaInboundOptions](registry, C.TypeHysteria, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (adapter.Inbound, error) { + return nil, C.ErrQUICNotIncluded + }) + inbound.Register[option.TUICInboundOptions](registry, C.TypeTUIC, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICInboundOptions) (adapter.Inbound, error) { + return nil, C.ErrQUICNotIncluded + }) + inbound.Register[option.Hysteria2InboundOptions](registry, C.TypeHysteria2, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (adapter.Inbound, error) { + return nil, C.ErrQUICNotIncluded + }) + naive.ConfigureHTTP3ListenerFunc = func(ctx context.Context, logger logger.Logger, listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, options option.NaiveInboundOptions) (io.Closer, error) { + return nil, C.ErrQUICNotIncluded + } +} + +func registerQUICOutbounds(registry *outbound.Registry) { + outbound.Register[option.HysteriaOutboundOptions](registry, C.TypeHysteria, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (adapter.Outbound, error) { + return nil, C.ErrQUICNotIncluded + }) + outbound.Register[option.TUICOutboundOptions](registry, C.TypeTUIC, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (adapter.Outbound, error) { + return nil, C.ErrQUICNotIncluded + }) + outbound.Register[option.Hysteria2OutboundOptions](registry, C.TypeHysteria2, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2OutboundOptions) (adapter.Outbound, error) { + return nil, C.ErrQUICNotIncluded + }) +} + +func registerQUICTransports(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.RemoteTLSDNSServerOptions](registry, C.DNSTypeQUIC, func(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteTLSDNSServerOptions) (adapter.DNSTransport, error) { + return nil, C.ErrQUICNotIncluded + }) + dns.RegisterTransport[option.RemoteHTTPSDNSServerOptions](registry, C.DNSTypeHTTP3, func(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteHTTPSDNSServerOptions) (adapter.DNSTransport, error) { + return nil, C.ErrQUICNotIncluded + }) +} + +func registerQUICServices(registry *service.Registry) { + service.Register[option.HysteriaRealmServiceOptions](registry, C.TypeHysteriaRealm, func(ctx context.Context, logger log.ContextLogger, tag string, options option.HysteriaRealmServiceOptions) (adapter.Service, error) { + return nil, C.ErrQUICNotIncluded + }) +} diff --git a/include/registry.go b/include/registry.go new file mode 100644 index 0000000000..5c73467b09 --- /dev/null +++ b/include/registry.go @@ -0,0 +1,203 @@ +package include + +import ( + "context" + + box "github.com/sagernet/sing-box" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/certificate" + "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/service" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/dns/transport" + "github.com/sagernet/sing-box/dns/transport/fakeip" + "github.com/sagernet/sing-box/dns/transport/hosts" + "github.com/sagernet/sing-box/dns/transport/local" + "github.com/sagernet/sing-box/dns/transport/multi" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/protocol/anytls" + "github.com/sagernet/sing-box/protocol/block" + "github.com/sagernet/sing-box/protocol/direct" + "github.com/sagernet/sing-box/protocol/group" + "github.com/sagernet/sing-box/protocol/group/balancer" + "github.com/sagernet/sing-box/protocol/hiddify/dnstt" + "github.com/sagernet/sing-box/protocol/hiddify/gooserelay" + "github.com/sagernet/sing-box/protocol/hiddify/hinvalid" + + "github.com/sagernet/sing-box/protocol/hiddify/xray" + "github.com/sagernet/sing-box/protocol/http" + "github.com/sagernet/sing-box/protocol/mieru" + "github.com/sagernet/sing-box/protocol/mixed" + "github.com/sagernet/sing-box/protocol/naive" + "github.com/sagernet/sing-box/protocol/psiphon" + "github.com/sagernet/sing-box/protocol/redirect" + "github.com/sagernet/sing-box/protocol/shadowsocks" + "github.com/sagernet/sing-box/protocol/shadowtls" + snellprotocol "github.com/sagernet/sing-box/protocol/snell" + "github.com/sagernet/sing-box/protocol/socks" + "github.com/sagernet/sing-box/protocol/ssh" + "github.com/sagernet/sing-box/protocol/tor" + "github.com/sagernet/sing-box/protocol/trojan" + "github.com/sagernet/sing-box/protocol/tun" + "github.com/sagernet/sing-box/protocol/firebasetunnel" + "github.com/sagernet/sing-box/protocol/tunnel" + "github.com/sagernet/sing-box/protocol/vless" + "github.com/sagernet/sing-box/protocol/vmess" + originca "github.com/sagernet/sing-box/service/origin_ca" + "github.com/sagernet/sing-box/service/resolved" + smartdnspool "github.com/sagernet/sing-box/service/smart_dns_pool" + "github.com/sagernet/sing-box/service/ssmapi" + E "github.com/sagernet/sing/common/exceptions" +) + +func Context(ctx context.Context) context.Context { + return box.Context(ctx, InboundRegistry(), OutboundRegistry(), EndpointRegistry(), DNSTransportRegistry(), ServiceRegistry(), CertificateProviderRegistry()) +} + +func InboundRegistry() *inbound.Registry { + registry := inbound.NewRegistry() + + tun.RegisterInbound(registry) + redirect.RegisterRedirect(registry) + redirect.RegisterTProxy(registry) + direct.RegisterInbound(registry) + + socks.RegisterInbound(registry) + http.RegisterInbound(registry) + mixed.RegisterInbound(registry) + + shadowsocks.RegisterInbound(registry) + vmess.RegisterInbound(registry) + trojan.RegisterInbound(registry) + naive.RegisterInbound(registry) + shadowtls.RegisterInbound(registry) + vless.RegisterInbound(registry) + anytls.RegisterInbound(registry) + mieru.RegisterInbound(registry) + ssh.RegisterInbound(registry) + snellprotocol.RegisterInbound(registry) + + registerQUICInbounds(registry) + registerCloudflaredInbound(registry) + registerStubForRemovedInbounds(registry) + + return registry +} + +func OutboundRegistry() *outbound.Registry { + registry := outbound.NewRegistry() + + direct.RegisterOutbound(registry) + + block.RegisterOutbound(registry) + + group.RegisterSelector(registry) + group.RegisterURLTest(registry) + + socks.RegisterOutbound(registry) + http.RegisterOutbound(registry) + shadowsocks.RegisterOutbound(registry) + vmess.RegisterOutbound(registry) + trojan.RegisterOutbound(registry) + registerNaiveOutbound(registry) + tor.RegisterOutbound(registry) + ssh.RegisterOutbound(registry) + shadowtls.RegisterOutbound(registry) + vless.RegisterOutbound(registry) + psiphon.RegisterOutbound(registry) + mieru.RegisterOutbound(registry) + anytls.RegisterOutbound(registry) + hinvalid.RegisterOutbound(registry) + xray.RegisterOutbound(registry) + dnstt.RegisterOutbound(registry) + gooserelay.RegisterOutbound(registry) + balancer.RegisterLoadBalance(registry) + snellprotocol.RegisterOutbound(registry) + + registerMASQUEOutbound(registry) + registerQUICOutbounds(registry) + registerStubForRemovedOutbounds(registry) + + return registry +} + +func EndpointRegistry() *endpoint.Registry { + registry := endpoint.NewRegistry() + + tunnel.RegisterServerEndpoint(registry) + tunnel.RegisterClientEndpoint(registry) + firebasetunnel.RegisterEndpoint(registry) + + registerWireGuardEndpoint(registry) + registerWarpEndpoint(registry) + registerTailscaleEndpoint(registry) + registerAwgEndpoint(registry) + + return registry +} + +func DNSTransportRegistry() *dns.TransportRegistry { + registry := dns.NewTransportRegistry() + + transport.RegisterTCP(registry) + transport.RegisterUDP(registry) + transport.RegisterTLS(registry) + transport.RegisterHTTPS(registry) + transport.RegisterSDNS(registry) + + multi.RegisterTransport(registry) //H + hosts.RegisterTransport(registry) + local.RegisterTransport(registry) + fakeip.RegisterTransport(registry) + resolved.RegisterTransport(registry) + + registerQUICTransports(registry) + registerDHCPTransport(registry) + registerTailscaleTransport(registry) + + return registry +} + +func ServiceRegistry() *service.Registry { + registry := service.NewRegistry() + + resolved.RegisterService(registry) + ssmapi.RegisterService(registry) + smartdnspool.RegisterService(registry) //H + + registerDERPService(registry) + registerCCMService(registry) + registerOCMService(registry) + registerOOMKillerService(registry) + + return registry +} + +func CertificateProviderRegistry() *certificate.Registry { + registry := certificate.NewRegistry() + + registerACMECertificateProvider(registry) + registerTailscaleCertificateProvider(registry) + originca.RegisterCertificateProvider(registry) + + return registry +} + +func registerStubForRemovedInbounds(registry *inbound.Registry) { + inbound.Register[option.ShadowsocksInboundOptions](registry, C.TypeShadowsocksR, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) { + return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0") + }) +} + +func registerStubForRemovedOutbounds(registry *outbound.Registry) { + outbound.Register[option.ShadowsocksROutboundOptions](registry, C.TypeShadowsocksR, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (adapter.Outbound, error) { + return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0") + }) + outbound.Register[option.StubOptions](registry, C.TypeWireGuard, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.StubOptions) (adapter.Outbound, error) { + return nil, E.New("WireGuard outbound is deprecated in sing-box 1.11.0 and removed in sing-box 1.13.0, use WireGuard endpoint instead") + }) +} diff --git a/include/tailscale.go b/include/tailscale.go new file mode 100644 index 0000000000..6f85aaac14 --- /dev/null +++ b/include/tailscale.go @@ -0,0 +1,28 @@ +//go:build with_tailscale + +package include + +import ( + "github.com/sagernet/sing-box/adapter/certificate" + "github.com/sagernet/sing-box/adapter/endpoint" + "github.com/sagernet/sing-box/adapter/service" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/protocol/tailscale" + "github.com/sagernet/sing-box/service/derp" +) + +func registerTailscaleEndpoint(registry *endpoint.Registry) { + tailscale.RegisterEndpoint(registry) +} + +func registerTailscaleTransport(registry *dns.TransportRegistry) { + tailscale.RegistryTransport(registry) +} + +func registerTailscaleCertificateProvider(registry *certificate.Registry) { + tailscale.RegisterCertificateProvider(registry) +} + +func registerDERPService(registry *service.Registry) { + derp.Register(registry) +} diff --git a/include/tailscale_stub.go b/include/tailscale_stub.go new file mode 100644 index 0000000000..e6f97f1eab --- /dev/null +++ b/include/tailscale_stub.go @@ -0,0 +1,41 @@ +//go:build !with_tailscale + +package include + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/certificate" + "github.com/sagernet/sing-box/adapter/endpoint" + "github.com/sagernet/sing-box/adapter/service" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func registerTailscaleEndpoint(registry *endpoint.Registry) { + endpoint.Register[option.TailscaleEndpointOptions](registry, C.TypeTailscale, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TailscaleEndpointOptions) (adapter.Endpoint, error) { + return nil, E.New(`Tailscale is not included in this build, rebuild with -tags with_tailscale`) + }) +} + +func registerTailscaleTransport(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.TailscaleDNSServerOptions](registry, C.DNSTypeTailscale, func(ctx context.Context, logger log.ContextLogger, tag string, options option.TailscaleDNSServerOptions) (adapter.DNSTransport, error) { + return nil, E.New(`Tailscale is not included in this build, rebuild with -tags with_tailscale`) + }) +} + +func registerTailscaleCertificateProvider(registry *certificate.Registry) { + certificate.Register[option.TailscaleCertificateProviderOptions](registry, C.TypeTailscale, func(ctx context.Context, logger log.ContextLogger, tag string, options option.TailscaleCertificateProviderOptions) (adapter.CertificateProviderService, error) { + return nil, E.New(`Tailscale is not included in this build, rebuild with -tags with_tailscale`) + }) +} + +func registerDERPService(registry *service.Registry) { + service.Register[option.DERPServiceOptions](registry, C.TypeDERP, func(ctx context.Context, logger log.ContextLogger, tag string, options option.DERPServiceOptions) (adapter.Service, error) { + return nil, E.New(`DERP is not included in this build, rebuild with -tags with_tailscale`) + }) +} diff --git a/include/warp.go b/include/warp.go new file mode 100644 index 0000000000..d636281b72 --- /dev/null +++ b/include/warp.go @@ -0,0 +1,12 @@ +//go:build with_wireguard + +package include + +import ( + "github.com/sagernet/sing-box/adapter/endpoint" + "github.com/sagernet/sing-box/protocol/warp" +) + +func registerWarpEndpoint(registry *endpoint.Registry) { + warp.RegisterWARPEndpoint(registry) +} diff --git a/include/warp_stub.go b/include/warp_stub.go new file mode 100644 index 0000000000..b3769382b1 --- /dev/null +++ b/include/warp_stub.go @@ -0,0 +1,20 @@ +//go:build !with_wireguard + +package include + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/endpoint" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func registerWarpEndpoint(registry *endpoint.Registry) { + endpoint.Register[option.WARPEndpointOptions](registry, C.TypeWARP, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WARPEndpointOptions) (adapter.Endpoint, error) { + return nil, E.New(`WARP is not included in this build, rebuild with -tags with_wireguard`) + }) +} diff --git a/include/wireguard.go b/include/wireguard.go new file mode 100644 index 0000000000..fa7cfe6fd8 --- /dev/null +++ b/include/wireguard.go @@ -0,0 +1,12 @@ +//go:build with_wireguard + +package include + +import ( + "github.com/sagernet/sing-box/adapter/endpoint" + "github.com/sagernet/sing-box/protocol/wireguard" +) + +func registerWireGuardEndpoint(registry *endpoint.Registry) { + wireguard.RegisterEndpoint(registry) +} diff --git a/include/wireguard_stub.go b/include/wireguard_stub.go new file mode 100644 index 0000000000..e03a9d9c0a --- /dev/null +++ b/include/wireguard_stub.go @@ -0,0 +1,20 @@ +//go:build !with_wireguard + +package include + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/endpoint" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func registerWireGuardEndpoint(registry *endpoint.Registry) { + endpoint.Register[option.WireGuardEndpointOptions](registry, C.TypeWireGuard, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardEndpointOptions) (adapter.Endpoint, error) { + return nil, E.New(`WireGuard is not included in this build, rebuild with -tags with_wireguard`) + }) +} diff --git a/log/format.go b/log/format.go index 6fb91d3157..6f4347b12a 100644 --- a/log/format.go +++ b/log/format.go @@ -43,7 +43,7 @@ func (f Formatter) Format(ctx context.Context, level Level, tag string, message id, hasId = IDFromContext(ctx) } if hasId { - activeDuration := formatDuration(time.Since(id.CreatedAt)) + activeDuration := FormatDuration(time.Since(id.CreatedAt)) if !f.DisableColors { var color aurora.Color color = aurora.Color(uint8(id.ID)) @@ -113,7 +113,7 @@ func (f Formatter) FormatWithSimple(ctx context.Context, level Level, tag string id, hasId = IDFromContext(ctx) } if hasId { - activeDuration := formatDuration(time.Since(id.CreatedAt)) + activeDuration := FormatDuration(time.Since(id.CreatedAt)) if !f.DisableColors { var color aurora.Color color = aurora.Color(uint8(id.ID)) @@ -163,7 +163,7 @@ func xd(value int, x int) string { return message } -func formatDuration(duration time.Duration) string { +func FormatDuration(duration time.Duration) string { if duration < time.Second { return F.ToString(duration.Milliseconds(), "ms") } else if duration < time.Minute { diff --git a/log/id.go b/log/id.go index 19e19d5599..e23ed3d5e8 100644 --- a/log/id.go +++ b/log/id.go @@ -4,14 +4,8 @@ import ( "context" "math/rand" "time" - - "github.com/sagernet/sing/common/random" ) -func init() { - random.InitializeSeed() -} - type idKey struct{} type ID struct { @@ -20,12 +14,16 @@ type ID struct { } func ContextWithNewID(ctx context.Context) context.Context { - return context.WithValue(ctx, (*idKey)(nil), ID{ + return ContextWithID(ctx, ID{ ID: rand.Uint32(), CreatedAt: time.Now(), }) } +func ContextWithID(ctx context.Context, id ID) context.Context { + return context.WithValue(ctx, (*idKey)(nil), id) +} + func IDFromContext(ctx context.Context) (ID, bool) { id, loaded := ctx.Value((*idKey)(nil)).(ID) return id, loaded diff --git a/log/log.go b/log/log.go index 7b8f284350..3a1c6537ab 100644 --- a/log/log.go +++ b/log/log.go @@ -40,6 +40,7 @@ func New(options Options) (Factory, error) { case "stdout": logWriter = os.Stdout default: + logWriter = io.Discard logFilePath = logOptions.Output } logFormatter := Formatter{ diff --git a/log/observable.go b/log/observable.go index eea1df84d0..768942bd47 100644 --- a/log/observable.go +++ b/log/observable.go @@ -50,9 +50,9 @@ func NewDefaultFactory( level: LevelTrace, subscriber: observable.NewSubscriber[Entry](128), } - if platformWriter != nil { + /*if platformWriter != nil { factory.platformFormatter.DisableColors = platformWriter.DisableColors() - } + }*/ if needObservable { factory.observer = observable.NewObserver[Entry](factory.subscriber, 64) } @@ -111,28 +111,30 @@ type observableLogger struct { func (l *observableLogger) Log(ctx context.Context, level Level, args []any) { level = OverrideLevelFromContext(level, ctx) - if level > l.level { + if level > l.level && l.platformWriter == nil { return } nowTime := time.Now() - if l.needObservable { - message, messageSimple := l.formatter.FormatWithSimple(ctx, level, l.tag, F.ToString(args...), nowTime) - if level == LevelPanic { - panic(message) - } - l.writer.Write([]byte(message)) - if level == LevelFatal { - os.Exit(1) - } - l.subscriber.Emit(Entry{level, messageSimple}) - } else { - message := l.formatter.Format(ctx, level, l.tag, F.ToString(args...), nowTime) - if level == LevelPanic { - panic(message) - } - l.writer.Write([]byte(message)) - if level == LevelFatal { - os.Exit(1) + if level <= l.level { + if l.needObservable { + message, messageSimple := l.formatter.FormatWithSimple(ctx, level, l.tag, F.ToString(args...), nowTime) + if level == LevelPanic { + panic(message) + } + l.writer.Write([]byte(message)) + if level == LevelFatal { + os.Exit(1) + } + l.subscriber.Emit(Entry{level, messageSimple}) + } else { + message := l.formatter.Format(ctx, level, l.tag, F.ToString(args...), nowTime) + if level == LevelPanic { + panic(message) + } + l.writer.Write([]byte(message)) + if level == LevelFatal { + os.Exit(1) + } } } if l.platformWriter != nil { diff --git a/log/platform.go b/log/platform.go index c6a9e525a1..a8881d4cc0 100644 --- a/log/platform.go +++ b/log/platform.go @@ -1,6 +1,5 @@ package log type PlatformWriter interface { - DisableColors() bool WriteMessage(level Level, message string) } diff --git a/mkdocs.yml b/mkdocs.yml index d5218f4d53..d0836c1eb3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,4 +1,5 @@ site_name: sing-box +site_url: https://sing-box.sagernet.org/ site_author: nekohasekai repo_url: https://github.com/SagerNet/sing-box repo_name: SagerNet/sing-box @@ -11,18 +12,22 @@ theme: logo: assets/icon.svg favicon: assets/icon.svg palette: + - media: "(prefers-color-scheme)" + toggle: + icon: material/link + name: Switch to light mode - media: "(prefers-color-scheme: light)" scheme: default primary: white toggle: - icon: material/brightness-7 + icon: material/toggle-switch name: Switch to dark mode - media: "(prefers-color-scheme: dark)" scheme: slate primary: black toggle: - icon: material/brightness-4 - name: Switch to light mode + icon: material/toggle-switch-off + name: Switch to system preference features: # - navigation.instant - navigation.tracking @@ -44,6 +49,7 @@ nav: - Migration: migration.md - Deprecated: deprecated.md - Support: support.md + - Sponsors: sponsors.md - Installation: - Package Manager: installation/package-manager.md - Docker: installation/docker.md @@ -75,21 +81,39 @@ nav: - configuration/log/index.md - DNS: - configuration/dns/index.md - - DNS Server: configuration/dns/server.md + - DNS Server: + - configuration/dns/server/index.md + - Legacy: configuration/dns/server/legacy.md + - Local: configuration/dns/server/local.md + - Hosts: configuration/dns/server/hosts.md + - TCP: configuration/dns/server/tcp.md + - UDP: configuration/dns/server/udp.md + - TLS: configuration/dns/server/tls.md + - QUIC: configuration/dns/server/quic.md + - HTTPS: configuration/dns/server/https.md + - HTTP3: configuration/dns/server/http3.md + - DHCP: configuration/dns/server/dhcp.md + - mDNS: configuration/dns/server/mdns.md + - FakeIP: configuration/dns/server/fakeip.md + - Tailscale: configuration/dns/server/tailscale.md + - Resolved: configuration/dns/server/resolved.md - DNS Rule: configuration/dns/rule.md + - DNS Rule Action: configuration/dns/rule_action.md - FakeIP: configuration/dns/fakeip.md - - NTP: - - configuration/ntp/index.md + - NTP: configuration/ntp/index.md + - Certificate: configuration/certificate/index.md - Route: - configuration/route/index.md - GeoIP: configuration/route/geoip.md - Geosite: configuration/route/geosite.md - Route Rule: configuration/route/rule.md + - Rule Action: configuration/route/rule_action.md - Protocol Sniff: configuration/route/sniff.md - Rule Set: - configuration/rule-set/index.md - Source Format: configuration/rule-set/source-format.md - Headless Rule: configuration/rule-set/headless-rule.md + - AdGuard DNS Filer: configuration/rule-set/adguard.md - Experimental: - configuration/experimental/index.md - Cache File: configuration/experimental/cache-file.md @@ -99,11 +123,26 @@ nav: - Listen Fields: configuration/shared/listen.md - Dial Fields: configuration/shared/dial.md - TLS: configuration/shared/tls.md + - HTTP Client: configuration/shared/http-client.md + - HTTP2 Fields: configuration/shared/http2.md + - QUIC Fields: configuration/shared/quic.md + - Certificate Provider: + - configuration/shared/certificate-provider/index.md + - ACME: configuration/shared/certificate-provider/acme.md + - Tailscale: configuration/shared/certificate-provider/tailscale.md + - Cloudflare Origin CA: configuration/shared/certificate-provider/cloudflare-origin-ca.md - DNS01 Challenge Fields: configuration/shared/dns01_challenge.md + - Pre-match: configuration/shared/pre-match.md - Multiplex: configuration/shared/multiplex.md - V2Ray Transport: configuration/shared/v2ray-transport.md - UDP over TCP: configuration/shared/udp-over-tcp.md - TCP Brutal: configuration/shared/tcp-brutal.md + - Wi-Fi State: configuration/shared/wifi-state.md + - Neighbor Resolution: configuration/shared/neighbor.md + - Endpoint: + - configuration/endpoint/index.md + - WireGuard: configuration/endpoint/wireguard.md + - Tailscale: configuration/endpoint/tailscale.md - Inbound: - configuration/inbound/index.md - Direct: configuration/inbound/direct.md @@ -111,6 +150,7 @@ nav: - SOCKS: configuration/inbound/socks.md - HTTP: configuration/inbound/http.md - Shadowsocks: configuration/inbound/shadowsocks.md + - Snell: configuration/inbound/snell.md - VMess: configuration/inbound/vmess.md - Trojan: configuration/inbound/trojan.md - Naive: configuration/inbound/naive.md @@ -119,9 +159,11 @@ nav: - VLESS: configuration/inbound/vless.md - TUIC: configuration/inbound/tuic.md - Hysteria2: configuration/inbound/hysteria2.md + - AnyTLS: configuration/inbound/anytls.md - Tun: configuration/inbound/tun.md - Redirect: configuration/inbound/redirect.md - TProxy: configuration/inbound/tproxy.md + - Cloudflared: configuration/inbound/cloudflared.md - Outbound: - configuration/outbound/index.md - Direct: configuration/outbound/direct.md @@ -129,20 +171,35 @@ nav: - SOCKS: configuration/outbound/socks.md - HTTP: configuration/outbound/http.md - Shadowsocks: configuration/outbound/shadowsocks.md + - Snell: configuration/outbound/snell.md - VMess: configuration/outbound/vmess.md - Trojan: configuration/outbound/trojan.md + - Naive: configuration/outbound/naive.md - WireGuard: configuration/outbound/wireguard.md - Hysteria: configuration/outbound/hysteria.md - ShadowTLS: configuration/outbound/shadowtls.md - VLESS: configuration/outbound/vless.md - TUIC: configuration/outbound/tuic.md - Hysteria2: configuration/outbound/hysteria2.md + - AnyTLS: configuration/outbound/anytls.md - Tor: configuration/outbound/tor.md - SSH: configuration/outbound/ssh.md - DNS: configuration/outbound/dns.md - Selector: configuration/outbound/selector.md - URLTest: configuration/outbound/urltest.md + - Service: + - configuration/service/index.md + - DERP: configuration/service/derp.md + - Resolved: configuration/service/resolved.md + - SSM API: configuration/service/ssm-api.md + - CCM: configuration/service/ccm.md + - OCM: configuration/service/ocm.md + - Hysteria Realm: configuration/service/hysteria-realm.md markdown_extensions: + - toc: + slugify: !!python/object/apply:pymdownx.slugs.slugify + kwds: + case: lower - pymdownx.inlinehilite - pymdownx.snippets - pymdownx.superfences @@ -212,9 +269,11 @@ plugins: Log: 日志 DNS Server: DNS 服务器 DNS Rule: DNS 规则 + DNS Rule Action: DNS 规则动作 Route: 路由 Route Rule: 路由规则 + Rule Action: 规则动作 Protocol Sniff: 协议探测 Rule Set: 规则集 @@ -227,12 +286,16 @@ plugins: Shared: 通用 Listen Fields: 监听字段 Dial Fields: 拨号字段 + Certificate Provider Fields: 证书提供者字段 DNS01 Challenge Fields: DNS01 验证字段 Multiplex: 多路复用 V2Ray Transport: V2Ray 传输层 + Wi-Fi State: Wi-Fi 状态 + Endpoint: 端点 Inbound: 入站 Outbound: 出站 + Certificate Provider: 证书提供者 Manual: 手册 reconfigure_material: true diff --git a/option/acme.go b/option/acme.go new file mode 100644 index 0000000000..31efffce14 --- /dev/null +++ b/option/acme.go @@ -0,0 +1,107 @@ +package option + +import ( + "strings" + + C "github.com/sagernet/sing-box/constant" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" +) + +type ACMECertificateProviderOptions struct { + Domain badoption.Listable[string] `json:"domain,omitempty"` + DataDirectory string `json:"data_directory,omitempty"` + DefaultServerName string `json:"default_server_name,omitempty"` + Email string `json:"email,omitempty"` + Provider string `json:"provider,omitempty"` + AccountKey string `json:"account_key,omitempty"` + DisableHTTPChallenge bool `json:"disable_http_challenge,omitempty"` + DisableTLSALPNChallenge bool `json:"disable_tls_alpn_challenge,omitempty"` + AlternativeHTTPPort uint16 `json:"alternative_http_port,omitempty"` + AlternativeTLSPort uint16 `json:"alternative_tls_port,omitempty"` + ExternalAccount *ACMEExternalAccountOptions `json:"external_account,omitempty"` + DNS01Challenge *ACMEProviderDNS01ChallengeOptions `json:"dns01_challenge,omitempty"` + KeyType ACMEKeyType `json:"key_type,omitempty"` + Profile string `json:"profile,omitempty"` + HTTPClient *HTTPClientOptions `json:"http_client,omitempty"` +} + +type _ACMEProviderDNS01ChallengeOptions struct { + TTL badoption.Duration `json:"ttl,omitempty"` + PropagationDelay badoption.Duration `json:"propagation_delay,omitempty"` + PropagationTimeout badoption.Duration `json:"propagation_timeout,omitempty"` + Resolvers badoption.Listable[string] `json:"resolvers,omitempty"` + OverrideDomain string `json:"override_domain,omitempty"` + Provider string `json:"provider,omitempty"` + AliDNSOptions ACMEDNS01AliDNSOptions `json:"-"` + CloudflareOptions ACMEDNS01CloudflareOptions `json:"-"` + ACMEDNSOptions ACMEDNS01ACMEDNSOptions `json:"-"` +} + +type ACMEProviderDNS01ChallengeOptions _ACMEProviderDNS01ChallengeOptions + +func (o ACMEProviderDNS01ChallengeOptions) MarshalJSON() ([]byte, error) { + var v any + switch o.Provider { + case C.DNSProviderAliDNS: + v = o.AliDNSOptions + case C.DNSProviderCloudflare: + v = o.CloudflareOptions + case C.DNSProviderACMEDNS: + v = o.ACMEDNSOptions + case "": + return nil, E.New("missing provider type") + default: + return nil, E.New("unknown provider type: ", o.Provider) + } + return badjson.MarshallObjects((_ACMEProviderDNS01ChallengeOptions)(o), v) +} + +func (o *ACMEProviderDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error { + err := json.Unmarshal(bytes, (*_ACMEProviderDNS01ChallengeOptions)(o)) + if err != nil { + return err + } + var v any + switch o.Provider { + case C.DNSProviderAliDNS: + v = &o.AliDNSOptions + case C.DNSProviderCloudflare: + v = &o.CloudflareOptions + case C.DNSProviderACMEDNS: + v = &o.ACMEDNSOptions + case "": + return E.New("missing provider type") + default: + return E.New("unknown provider type: ", o.Provider) + } + return badjson.UnmarshallExcluded(bytes, (*_ACMEProviderDNS01ChallengeOptions)(o), v) +} + +type ACMEKeyType string + +const ( + ACMEKeyTypeED25519 = ACMEKeyType("ed25519") + ACMEKeyTypeP256 = ACMEKeyType("p256") + ACMEKeyTypeP384 = ACMEKeyType("p384") + ACMEKeyTypeRSA2048 = ACMEKeyType("rsa2048") + ACMEKeyTypeRSA4096 = ACMEKeyType("rsa4096") +) + +func (t *ACMEKeyType) UnmarshalJSON(data []byte) error { + var value string + err := json.Unmarshal(data, &value) + if err != nil { + return err + } + value = strings.ToLower(value) + switch ACMEKeyType(value) { + case "", ACMEKeyTypeED25519, ACMEKeyTypeP256, ACMEKeyTypeP384, ACMEKeyTypeRSA2048, ACMEKeyTypeRSA4096: + *t = ACMEKeyType(value) + default: + return E.New("unknown ACME key type: ", value) + } + return nil +} diff --git a/option/anytls.go b/option/anytls.go new file mode 100644 index 0000000000..0f78526327 --- /dev/null +++ b/option/anytls.go @@ -0,0 +1,25 @@ +package option + +import "github.com/sagernet/sing/common/json/badoption" + +type AnyTLSInboundOptions struct { + ListenOptions + InboundTLSOptionsContainer + Users []AnyTLSUser `json:"users,omitempty"` + PaddingScheme badoption.Listable[string] `json:"padding_scheme,omitempty"` +} + +type AnyTLSUser struct { + Name string `json:"name,omitempty"` + Password string `json:"password,omitempty"` +} + +type AnyTLSOutboundOptions struct { + DialerOptions + ServerOptions + OutboundTLSOptionsContainer + Password string `json:"password,omitempty"` + IdleSessionCheckInterval badoption.Duration `json:"idle_session_check_interval,omitempty"` + IdleSessionTimeout badoption.Duration `json:"idle_session_timeout,omitempty"` + MinIdleSession int `json:"min_idle_session,omitempty"` +} diff --git a/option/awg.go b/option/awg.go new file mode 100644 index 0000000000..9216b85c01 --- /dev/null +++ b/option/awg.go @@ -0,0 +1,53 @@ +package option + +import ( + "net/netip" + + "github.com/sagernet/sing/common/json/badoption" +) + +type AwgEndpointOptions struct { + UseIntegratedTun bool `json:"useIntegratedTun"` + PrivateKey string `json:"private_key"` + Address badoption.Listable[netip.Prefix] `json:"address"` + MTU uint32 `json:"mtu,omitempty"` + ListenPort uint16 `json:"listen_port,omitempty"` + Awg AwgOptions `json:"awg,omitempty"` + Peers []AwgPeerOptions `json:"peers,omitempty"` + DialerOptions +} + +type AwgOptions struct { + Jc int `json:"jc,omitempty"` + Jmin int `json:"jmin,omitempty"` + Jmax int `json:"jmax,omitempty"` + S1 int `json:"s1,omitempty"` + S2 int `json:"s2,omitempty"` + S3 int `json:"s3,omitempty"` + S4 int `json:"s4,omitempty"` + H1 string `json:"h1,omitempty"` + H2 string `json:"h2,omitempty"` + H3 string `json:"h3,omitempty"` + H4 string `json:"h4,omitempty"` + I1 string `json:"i1,omitempty"` + I2 string `json:"i2,omitempty"` + I3 string `json:"i3,omitempty"` + I4 string `json:"i4,omitempty"` + I5 string `json:"i5,omitempty"` +} + +func (o *AwgOptions) IsAvailble() bool { + if o == nil { + return false + } + return o.Jc != 0 || o.Jmin != 0 || o.Jmax != 0 || o.S1 != 0 || o.S2 != 0 || o.S3 != 0 || o.S4 != 0 || o.H1 != "" || o.H2 != "" || o.H3 != "" || o.H4 != "" || o.I1 != "" || o.I2 != "" || o.I3 != "" || o.I4 != "" || o.I5 != "" +} + +type AwgPeerOptions struct { + Address string `json:"address,omitempty"` + Port uint16 `json:"port,omitempty"` + PublicKey string `json:"public_key,omitempty"` + PresharedKey string `json:"preshared_key,omitempty"` + AllowedIPs badoption.Listable[netip.Prefix] `json:"allowed_ips,omitempty"` + PersistentKeepaliveInterval uint16 `json:"persistent_keepalive_interval,omitempty"` +} diff --git a/option/balancer.go b/option/balancer.go new file mode 100644 index 0000000000..0c9e7ae4b6 --- /dev/null +++ b/option/balancer.go @@ -0,0 +1,13 @@ +package option + +import "github.com/sagernet/sing/common/json/badoption" + +type BalancerOutboundOptions struct { + Outbounds []string `json:"outbounds"` + Tolerance uint16 `json:"tolerance,omitempty"` //not implemented yet + InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"` + Strategy string `json:"strategy,omitempty"` + DelayAcceptableRatio float64 `json:"delay_acceptable_ratio,omitempty"` + TTL badoption.Duration `json:"ttl,omitempty"` + MaxRetry int `json:"max_retry,omitempty"` //not implemented yet +} diff --git a/option/ccm.go b/option/ccm.go new file mode 100644 index 0000000000..c916aaf221 --- /dev/null +++ b/option/ccm.go @@ -0,0 +1,20 @@ +package option + +import ( + "github.com/sagernet/sing/common/json/badoption" +) + +type CCMServiceOptions struct { + ListenOptions + InboundTLSOptionsContainer + CredentialPath string `json:"credential_path,omitempty"` + Users []CCMUser `json:"users,omitempty"` + Headers badoption.HTTPHeader `json:"headers,omitempty"` + Detour string `json:"detour,omitempty"` + UsagesPath string `json:"usages_path,omitempty"` +} + +type CCMUser struct { + Name string `json:"name,omitempty"` + Token string `json:"token,omitempty"` +} diff --git a/option/certificate.go b/option/certificate.go new file mode 100644 index 0000000000..ab524b9921 --- /dev/null +++ b/option/certificate.go @@ -0,0 +1,36 @@ +package option + +import ( + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badoption" +) + +type _CertificateOptions struct { + Store string `json:"store,omitempty"` + Certificate badoption.Listable[string] `json:"certificate,omitempty"` + CertificatePath badoption.Listable[string] `json:"certificate_path,omitempty"` + CertificateDirectoryPath badoption.Listable[string] `json:"certificate_directory_path,omitempty"` +} + +type CertificateOptions _CertificateOptions + +func (o CertificateOptions) MarshalJSON() ([]byte, error) { + switch o.Store { + case C.CertificateStoreSystem: + o.Store = "" + } + return json.Marshal((*_CertificateOptions)(&o)) +} + +func (o *CertificateOptions) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, (*_CertificateOptions)(o)) + if err != nil { + return err + } + switch o.Store { + case C.CertificateStoreSystem, "": + o.Store = C.CertificateStoreSystem + } + return nil +} diff --git a/option/certificate_provider.go b/option/certificate_provider.go new file mode 100644 index 0000000000..a24abdc570 --- /dev/null +++ b/option/certificate_provider.go @@ -0,0 +1,100 @@ +package option + +import ( + "context" + + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/service" +) + +type CertificateProviderOptionsRegistry interface { + CreateOptions(providerType string) (any, bool) +} + +type _CertificateProvider struct { + Type string `json:"type"` + Tag string `json:"tag,omitempty"` + Options any `json:"-"` +} + +type CertificateProvider _CertificateProvider + +func (h *CertificateProvider) MarshalJSONContext(ctx context.Context) ([]byte, error) { + return badjson.MarshallObjectsContext(ctx, (*_CertificateProvider)(h), h.Options) +} + +func (h *CertificateProvider) UnmarshalJSONContext(ctx context.Context, content []byte) error { + err := json.UnmarshalContext(ctx, content, (*_CertificateProvider)(h)) + if err != nil { + return err + } + registry := service.FromContext[CertificateProviderOptionsRegistry](ctx) + if registry == nil { + return E.New("missing certificate provider options registry in context") + } + options, loaded := registry.CreateOptions(h.Type) + if !loaded { + return E.New("unknown certificate provider type: ", h.Type) + } + err = badjson.UnmarshallExcludedContext(ctx, content, (*_CertificateProvider)(h), options) + if err != nil { + return err + } + h.Options = options + return nil +} + +type CertificateProviderOptions struct { + Tag string `json:"-"` + Type string `json:"-"` + Options any `json:"-"` +} + +type _CertificateProviderInline struct { + Type string `json:"type"` +} + +func (o *CertificateProviderOptions) MarshalJSONContext(ctx context.Context) ([]byte, error) { + if o.Tag != "" { + return json.Marshal(o.Tag) + } + return badjson.MarshallObjectsContext(ctx, _CertificateProviderInline{Type: o.Type}, o.Options) +} + +func (o *CertificateProviderOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error { + if len(content) == 0 { + return E.New("empty certificate_provider value") + } + if content[0] == '"' { + return json.UnmarshalContext(ctx, content, &o.Tag) + } + var inline _CertificateProviderInline + err := json.UnmarshalContext(ctx, content, &inline) + if err != nil { + return err + } + o.Type = inline.Type + if o.Type == "" { + return E.New("missing certificate provider type") + } + registry := service.FromContext[CertificateProviderOptionsRegistry](ctx) + if registry == nil { + return E.New("missing certificate provider options registry in context") + } + options, loaded := registry.CreateOptions(o.Type) + if !loaded { + return E.New("unknown certificate provider type: ", o.Type) + } + err = badjson.UnmarshallExcludedContext(ctx, content, &inline, options) + if err != nil { + return err + } + o.Options = options + return nil +} + +func (o *CertificateProviderOptions) IsShared() bool { + return o.Tag != "" +} diff --git a/option/cloudflare.go b/option/cloudflare.go new file mode 100644 index 0000000000..6fbd08058a --- /dev/null +++ b/option/cloudflare.go @@ -0,0 +1,9 @@ +package option + +type CloudflareProfile struct { + ID string `json:"id,omitempty"` + AuthToken string `json:"auth_token,omitempty"` + PrivateKey string `json:"private_key,omitempty"` + Recreate bool `json:"recreate,omitempty"` + Detour string `json:"detour,omitempty"` +} diff --git a/option/cloudflared.go b/option/cloudflared.go new file mode 100644 index 0000000000..e94a20fefd --- /dev/null +++ b/option/cloudflared.go @@ -0,0 +1,16 @@ +package option + +import "github.com/sagernet/sing/common/json/badoption" + +type CloudflaredInboundOptions struct { + Token string `json:"token,omitempty"` + HighAvailabilityConnections int `json:"ha_connections,omitempty"` + Protocol string `json:"protocol,omitempty"` + PostQuantum bool `json:"post_quantum,omitempty"` + EdgeIPVersion int `json:"edge_ip_version,omitempty"` + DatagramVersion string `json:"datagram_version,omitempty"` + GracePeriod badoption.Duration `json:"grace_period,omitempty"` + Region string `json:"region,omitempty"` + ControlDialer DialerOptions `json:"control_dialer,omitempty"` + TunnelDialer DialerOptions `json:"tunnel_dialer,omitempty"` +} diff --git a/option/config.go b/option/config.go deleted file mode 100644 index 3f5d7602c0..0000000000 --- a/option/config.go +++ /dev/null @@ -1,40 +0,0 @@ -package option - -import ( - "bytes" - - "github.com/sagernet/sing/common/json" -) - -type _Options struct { - RawMessage json.RawMessage `json:"-"` - Schema string `json:"$schema,omitempty"` - Log *LogOptions `json:"log,omitempty"` - DNS *DNSOptions `json:"dns,omitempty"` - NTP *NTPOptions `json:"ntp,omitempty"` - Inbounds []Inbound `json:"inbounds,omitempty"` - Outbounds []Outbound `json:"outbounds,omitempty"` - Route *RouteOptions `json:"route,omitempty"` - Experimental *ExperimentalOptions `json:"experimental,omitempty"` -} - -type Options _Options - -func (o *Options) UnmarshalJSON(content []byte) error { - decoder := json.NewDecoder(bytes.NewReader(content)) - decoder.DisallowUnknownFields() - err := decoder.Decode((*_Options)(o)) - if err != nil { - return err - } - o.RawMessage = content - return nil -} - -type LogOptions struct { - Disabled bool `json:"disabled,omitempty"` - Level string `json:"level,omitempty"` - Output string `json:"output,omitempty"` - Timestamp bool `json:"timestamp,omitempty"` - DisableColor bool `json:"-"` -} diff --git a/option/debug.go b/option/debug.go index 0b0b825a82..3dfef7b0ca 100644 --- a/option/debug.go +++ b/option/debug.go @@ -1,43 +1,14 @@ package option -import ( - "github.com/sagernet/sing-box/common/humanize" - "github.com/sagernet/sing/common/json" -) +import "github.com/sagernet/sing/common/byteformats" type DebugOptions struct { - Listen string `json:"listen,omitempty"` - GCPercent *int `json:"gc_percent,omitempty"` - MaxStack *int `json:"max_stack,omitempty"` - MaxThreads *int `json:"max_threads,omitempty"` - PanicOnFault *bool `json:"panic_on_fault,omitempty"` - TraceBack string `json:"trace_back,omitempty"` - MemoryLimit MemoryBytes `json:"memory_limit,omitempty"` - OOMKiller *bool `json:"oom_killer,omitempty"` -} - -type MemoryBytes uint64 - -func (l MemoryBytes) MarshalJSON() ([]byte, error) { - return json.Marshal(humanize.MemoryBytes(uint64(l))) -} - -func (l *MemoryBytes) UnmarshalJSON(bytes []byte) error { - var valueInteger int64 - err := json.Unmarshal(bytes, &valueInteger) - if err == nil { - *l = MemoryBytes(valueInteger) - return nil - } - var valueString string - err = json.Unmarshal(bytes, &valueString) - if err != nil { - return err - } - parsedValue, err := humanize.ParseMemoryBytes(valueString) - if err != nil { - return err - } - *l = MemoryBytes(parsedValue) - return nil + Listen string `json:"listen,omitempty"` + GCPercent *int `json:"gc_percent,omitempty"` + MaxStack *int `json:"max_stack,omitempty"` + MaxThreads *int `json:"max_threads,omitempty"` + PanicOnFault *bool `json:"panic_on_fault,omitempty"` + TraceBack string `json:"trace_back,omitempty"` + MemoryLimit *byteformats.MemoryBytes `json:"memory_limit,omitempty"` + OOMKiller *bool `json:"oom_killer,omitempty"` } diff --git a/option/direct.go b/option/direct.go index 8624846dbc..a03f98d412 100644 --- a/option/direct.go +++ b/option/direct.go @@ -1,5 +1,12 @@ package option +import ( + "context" + + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" +) + type DirectInboundOptions struct { ListenOptions Network NetworkList `json:"network,omitempty"` @@ -7,9 +14,26 @@ type DirectInboundOptions struct { OverridePort uint16 `json:"override_port,omitempty"` } -type DirectOutboundOptions struct { +type _DirectOutboundOptions struct { DialerOptions + // Deprecated: Use Route Action instead OverrideAddress string `json:"override_address,omitempty"` - OverridePort uint16 `json:"override_port,omitempty"` - ProxyProtocol uint8 `json:"proxy_protocol,omitempty"` + // Deprecated: Use Route Action instead + OverridePort uint16 `json:"override_port,omitempty"` + // Deprecated: removed + ProxyProtocol uint8 `json:"proxy_protocol,omitempty"` +} + +type DirectOutboundOptions _DirectOutboundOptions + +func (d *DirectOutboundOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error { + err := json.UnmarshalDisallowUnknownFields(content, (*_DirectOutboundOptions)(d)) + if err != nil { + return err + } + //nolint:staticcheck + if d.OverrideAddress != "" || d.OverridePort != 0 { + return E.New("destination override fields in direct outbound are deprecated in sing-box 1.11.0 and removed in sing-box 1.13.0, use route options instead") + } + return nil } diff --git a/option/dns.go b/option/dns.go index be947583a0..8334de61f8 100644 --- a/option/dns.go +++ b/option/dns.go @@ -1,37 +1,202 @@ package option -import "net/netip" +import ( + "context" + "net/netip" -type DNSOptions struct { + C "github.com/sagernet/sing-box/constant" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/service" +) + +type RawDNSOptions struct { Servers []DNSServerOptions `json:"servers,omitempty"` Rules []DNSRule `json:"rules,omitempty"` Final string `json:"final,omitempty"` ReverseMapping bool `json:"reverse_mapping,omitempty"` - FakeIP *DNSFakeIPOptions `json:"fakeip,omitempty"` DNSClientOptions } -type DNSServerOptions struct { - Tag string `json:"tag,omitempty"` - Address string `json:"address"` - AddressResolver string `json:"address_resolver,omitempty"` - AddressStrategy DomainStrategy `json:"address_strategy,omitempty"` - AddressFallbackDelay Duration `json:"address_fallback_delay,omitempty"` - Strategy DomainStrategy `json:"strategy,omitempty"` - Detour string `json:"detour,omitempty"` - ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` +type DNSOptions struct { + RawDNSOptions +} + +const ( + legacyDNSFakeIPRemovedMessage = "legacy DNS fakeip options are deprecated in sing-box 1.12.0 and removed in sing-box 1.14.0, checkout migration: https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats" + legacyDNSServerRemovedMessage = "legacy DNS server formats are deprecated in sing-box 1.12.0 and removed in sing-box 1.14.0, checkout migration: https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats" +) + +type removedLegacyDNSOptions struct { + FakeIP json.RawMessage `json:"fakeip,omitempty"` +} + +func (o *DNSOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error { + var legacyOptions removedLegacyDNSOptions + err := json.UnmarshalContext(ctx, content, &legacyOptions) + if err != nil { + return err + } + if len(legacyOptions.FakeIP) != 0 { + return E.New(legacyDNSFakeIPRemovedMessage) + } + return badjson.UnmarshallExcludedContext(ctx, content, legacyOptions, &o.RawDNSOptions) } type DNSClientOptions struct { - Strategy DomainStrategy `json:"strategy,omitempty"` - DisableCache bool `json:"disable_cache,omitempty"` - DisableExpire bool `json:"disable_expire,omitempty"` - IndependentCache bool `json:"independent_cache,omitempty"` - ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` + Strategy DomainStrategy `json:"strategy,omitempty"` + DisableCache bool `json:"disable_cache,omitempty"` + DisableExpire bool `json:"disable_expire,omitempty"` + IndependentCache bool `json:"independent_cache,omitempty"` + CacheCapacity uint32 `json:"cache_capacity,omitempty"` + Optimistic *OptimisticDNSOptions `json:"optimistic,omitempty"` + ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` +} + +type _OptimisticDNSOptions struct { + Enabled bool `json:"enabled,omitempty"` + Timeout badoption.Duration `json:"timeout,omitempty"` +} + +type OptimisticDNSOptions _OptimisticDNSOptions + +func (o OptimisticDNSOptions) MarshalJSON() ([]byte, error) { + if o.Timeout == 0 { + return json.Marshal(o.Enabled) + } + return json.Marshal((_OptimisticDNSOptions)(o)) +} + +func (o *OptimisticDNSOptions) UnmarshalJSON(bytes []byte) error { + err := json.Unmarshal(bytes, &o.Enabled) + if err == nil { + return nil + } + return json.UnmarshalDisallowUnknownFields(bytes, (*_OptimisticDNSOptions)(o)) +} + +type DNSTransportOptionsRegistry interface { + CreateOptions(transportType string) (any, bool) +} +type _DNSServerOptions struct { + Type string `json:"type,omitempty"` + Tag string `json:"tag,omitempty"` + Options any `json:"-"` +} + +type DNSServerOptions _DNSServerOptions + +func (o *DNSServerOptions) MarshalJSONContext(ctx context.Context) ([]byte, error) { + return badjson.MarshallObjectsContext(ctx, (*_DNSServerOptions)(o), o.Options) +} + +func (o *DNSServerOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error { + err := json.UnmarshalContext(ctx, content, (*_DNSServerOptions)(o)) + if err != nil { + return err + } + registry := service.FromContext[DNSTransportOptionsRegistry](ctx) + if registry == nil { + return E.New("missing DNS transport options registry in context") + } + var options any + switch o.Type { + case "", C.DNSTypeLegacy: + return E.New(legacyDNSServerRemovedMessage) + default: + var loaded bool + options, loaded = registry.CreateOptions(o.Type) + if !loaded { + return E.New("unknown transport type: ", o.Type) + } + } + err = badjson.UnmarshallExcludedContext(ctx, content, (*_DNSServerOptions)(o), options) + if err != nil { + return err + } + o.Options = options + return nil +} + +type DNSServerAddressOptions struct { + Server string `json:"server"` + ServerPort uint16 `json:"server_port,omitempty"` +} + +func (o DNSServerAddressOptions) Build() M.Socksaddr { + return M.ParseSocksaddrHostPort(o.Server, o.ServerPort) +} + +func (o DNSServerAddressOptions) ServerIsDomain() bool { + return o.Build().IsDomain() +} + +func (o *DNSServerAddressOptions) TakeServerOptions() ServerOptions { + return ServerOptions(*o) +} + +func (o *DNSServerAddressOptions) ReplaceServerOptions(options ServerOptions) { + *o = DNSServerAddressOptions(options) +} + +type HostsDNSServerOptions struct { + Path badoption.Listable[string] `json:"path,omitempty"` + Predefined *badjson.TypedMap[string, badoption.Listable[netip.Addr]] `json:"predefined,omitempty"` +} + +type RawLocalDNSServerOptions struct { + DialerOptions +} + +type LocalDNSServerOptions struct { + RawLocalDNSServerOptions + PreferGo bool `json:"prefer_go,omitempty"` + NeighborDomain badoption.Listable[string] `json:"neighbor_domain,omitempty"` +} + +type RemoteDNSServerOptions struct { + RawLocalDNSServerOptions + DNSServerAddressOptions +} + +type RemoteTLSDNSServerOptions struct { + RemoteDNSServerOptions + OutboundTLSOptionsContainer +} + +type RemoteHTTPSDNSServerOptions struct { + RemoteTLSDNSServerOptions + Path string `json:"path,omitempty"` + Method string `json:"method,omitempty"` + Headers badoption.HTTPHeader `json:"headers,omitempty"` +} + +type FakeIPDNSServerOptions struct { + Inet4Range *badoption.Prefix `json:"inet4_range,omitempty"` + Inet6Range *badoption.Prefix `json:"inet6_range,omitempty"` +} + +type DHCPDNSServerOptions struct { + LocalDNSServerOptions + Interface string `json:"interface,omitempty"` +} + +type MDNSDNSServerOptions struct { + LocalDNSServerOptions + Interface badoption.Listable[string] `json:"interface,omitempty"` +} + +type SDNSDNSServerOptions struct { + RemoteDNSServerOptions + Stamp string `json:"stamp"` } -type DNSFakeIPOptions struct { - Enabled bool `json:"enabled,omitempty"` - Inet4Range *netip.Prefix `json:"inet4_range,omitempty"` - Inet6Range *netip.Prefix `json:"inet6_range,omitempty"` +type MultiDNSServerOptions struct { + RawLocalDNSServerOptions + Servers []string `json:"servers,omitempty"` + Parallel bool `json:"parallel,omitempty"` + IgnoreRanges []badoption.Prefix `json:"ignore_ranges,omitempty"` } diff --git a/option/dns_record.go b/option/dns_record.go new file mode 100644 index 0000000000..f10e03d9b6 --- /dev/null +++ b/option/dns_record.go @@ -0,0 +1,125 @@ +package option + +import ( + "encoding/base64" + "strings" + + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + M "github.com/sagernet/sing/common/metadata" + + "github.com/miekg/dns" +) + +const defaultDNSRecordTTL uint32 = 3600 + +type DNSRCode int + +func (r DNSRCode) MarshalJSON() ([]byte, error) { + rCodeValue, loaded := dns.RcodeToString[int(r)] + if loaded { + return json.Marshal(rCodeValue) + } + return json.Marshal(int(r)) +} + +func (r *DNSRCode) UnmarshalJSON(bytes []byte) error { + var intValue int + err := json.Unmarshal(bytes, &intValue) + if err == nil { + *r = DNSRCode(intValue) + return nil + } + var stringValue string + err = json.Unmarshal(bytes, &stringValue) + if err != nil { + return err + } + rCodeValue, loaded := dns.StringToRcode[stringValue] + if !loaded { + return E.New("unknown rcode: " + stringValue) + } + *r = DNSRCode(rCodeValue) + return nil +} + +func (r *DNSRCode) Build() int { + if r == nil { + return dns.RcodeSuccess + } + return int(*r) +} + +type DNSRecordOptions struct { + dns.RR + fromBase64 bool +} + +func (o DNSRecordOptions) MarshalJSON() ([]byte, error) { + if o.fromBase64 { + buffer := buf.Get(dns.Len(o.RR)) + defer buf.Put(buffer) + offset, err := dns.PackRR(o.RR, buffer, 0, nil, false) + if err != nil { + return nil, err + } + return json.Marshal(base64.StdEncoding.EncodeToString(buffer[:offset])) + } + return json.Marshal(o.RR.String()) +} + +func (o *DNSRecordOptions) UnmarshalJSON(data []byte) error { + var stringValue string + err := json.Unmarshal(data, &stringValue) + if err != nil { + return err + } + binary, err := base64.StdEncoding.DecodeString(stringValue) + if err == nil { + return o.unmarshalBase64(binary) + } + record, err := parseDNSRecord(stringValue) + if err != nil { + return err + } + if record == nil { + return E.New("empty DNS record") + } + if a, isA := record.(*dns.A); isA { + a.A = M.AddrFromIP(a.A).Unmap().AsSlice() + } + o.RR = record + return nil +} + +func parseDNSRecord(stringValue string) (dns.RR, error) { + if len(stringValue) > 0 && stringValue[len(stringValue)-1] != '\n' { + stringValue += "\n" + } + parser := dns.NewZoneParser(strings.NewReader(stringValue), "", "") + parser.SetDefaultTTL(defaultDNSRecordTTL) + record, _ := parser.Next() + return record, parser.Err() +} + +func (o *DNSRecordOptions) unmarshalBase64(binary []byte) error { + record, _, err := dns.UnpackRR(binary, 0) + if err != nil { + return E.New("parse binary DNS record") + } + o.RR = record + o.fromBase64 = true + return nil +} + +func (o DNSRecordOptions) Build() dns.RR { + return o.RR +} + +func (o DNSRecordOptions) Match(record dns.RR) bool { + if o.RR == nil || record == nil { + return false + } + return dns.IsDuplicate(o.RR, record) +} diff --git a/option/dns_record_test.go b/option/dns_record_test.go new file mode 100644 index 0000000000..759ef5fc5a --- /dev/null +++ b/option/dns_record_test.go @@ -0,0 +1,40 @@ +package option + +import ( + "testing" + + "github.com/miekg/dns" + "github.com/stretchr/testify/require" +) + +func mustRecordOptions(t *testing.T, record string) DNSRecordOptions { + t.Helper() + var value DNSRecordOptions + require.NoError(t, value.UnmarshalJSON([]byte(`"`+record+`"`))) + return value +} + +func TestDNSRecordOptionsUnmarshalJSONRejectsRelativeNames(t *testing.T) { + t.Parallel() + + for _, record := range []string{ + "@ IN A 1.1.1.1", + "www IN CNAME example.com.", + "example.com. IN CNAME @", + "example.com. IN CNAME www", + } { + var value DNSRecordOptions + err := value.UnmarshalJSON([]byte(`"` + record + `"`)) + require.Error(t, err) + } +} + +func TestDNSRecordOptionsMatchIgnoresTTL(t *testing.T) { + t.Parallel() + + expected := mustRecordOptions(t, "example.com. 600 IN A 1.1.1.1") + record, err := dns.NewRR("example.com. 60 IN A 1.1.1.1") + require.NoError(t, err) + + require.True(t, expected.Match(record)) +} diff --git a/option/dns_test.go b/option/dns_test.go new file mode 100644 index 0000000000..4e7bf9a92b --- /dev/null +++ b/option/dns_test.go @@ -0,0 +1,54 @@ +package option + +import ( + "context" + "testing" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/service" + + "github.com/stretchr/testify/require" +) + +type stubDNSTransportOptionsRegistry struct{} + +func (stubDNSTransportOptionsRegistry) CreateOptions(transportType string) (any, bool) { + switch transportType { + case C.DNSTypeUDP: + return new(RemoteDNSServerOptions), true + case C.DNSTypeFakeIP: + return new(FakeIPDNSServerOptions), true + default: + return nil, false + } +} + +func TestDNSOptionsRejectsLegacyFakeIPOptions(t *testing.T) { + t.Parallel() + + ctx := service.ContextWith[DNSTransportOptionsRegistry](context.Background(), stubDNSTransportOptionsRegistry{}) + var options DNSOptions + err := json.UnmarshalContext(ctx, []byte(`{ + "fakeip": { + "enabled": true, + "inet4_range": "198.18.0.0/15" + } + }`), &options) + require.EqualError(t, err, legacyDNSFakeIPRemovedMessage) +} + +func TestDNSServerOptionsRejectsLegacyFormats(t *testing.T) { + t.Parallel() + + ctx := service.ContextWith[DNSTransportOptionsRegistry](context.Background(), stubDNSTransportOptionsRegistry{}) + testCases := []string{ + `{"address":"1.1.1.1"}`, + `{"type":"legacy","address":"1.1.1.1"}`, + } + for _, content := range testCases { + var options DNSServerOptions + err := json.UnmarshalContext(ctx, []byte(content), &options) + require.EqualError(t, err, legacyDNSServerRemovedMessage) + } +} diff --git a/option/dnstt.go b/option/dnstt.go new file mode 100644 index 0000000000..29e0bd6168 --- /dev/null +++ b/option/dnstt.go @@ -0,0 +1,49 @@ +package option + +import ( + "github.com/sagernet/sing/common/json/badoption" +) + +type DnsttOptions struct { + DialerOptions + + PublicKey string `json:"pubkey,omitempty"` + Domain string `json:"domain,omitempty"` + PreTestDomain string `json:"pretest-domain,omitempty"` + PreTestRecordType string `json:"pretest-record-type,omitempty"` + + RecordType string `json:"record-type,omitempty"` + UTLSClientHelloID string `json:"utls,omitempty"` + + Resolvers []string `json:"resolvers,omitempty"` + DeprecatedTunnelPerResolver int `json:"tunnel-per-resolver,omitempty"` + SingleResolver bool `json:"single-resolver,omitempty"` + + DnsttCompat bool `json:"dnstt-compat,omitempty"` + ClientIDSize *int `json:"clientid-size,omitempty"` + MaxQnameLen *int `json:"max-qname-len,omitempty"` + MaxNumLabels *int `json:"max-num-labels,omitempty"` + RPS *float64 `json:"rps,omitempty"` + + MTU *int `json:"mtu,omitempty"` + + IdleTimeout *badoption.Duration `json:"idle-timeout,omitempty"` + KeepAlive *badoption.Duration `json:"keepalive,omitempty"` + OpenStreamTimeout *badoption.Duration `json:"open-stream-timeout,omitempty"` + MaxStreams *int `json:"max-streams,omitempty"` + ReconnectMinDelay *badoption.Duration `json:"reconnect-min,omitempty"` + ReconnectMaxDelay *badoption.Duration `json:"reconnect-max,omitempty"` + SessionCheckInterval *badoption.Duration `json:"session-check-interval,omitempty"` + HandshakeTimeout *badoption.Duration `json:"handshake-timeout,omitempty"` + + UdpAcceptErrors bool `json:"udp-accept-errors,omitempty"` + UdpSharedSocket bool `json:"udp-shared-socket,omitempty"` + UdpTimeout *badoption.Duration `json:"udp-timeout,omitempty"` + UdpWorkers *int `json:"udp-workers,omitempty"` + + // SmartPool routes every resolver registered via the dnstt outbound's + // addResolver flow through an internal hmrd_multi_resolver_dns pool + // (deadline-aware failover, AIMD throttling, recovery probing) instead + // of using each resolver as its own tunnel. + SmartPool bool `json:"smart_pool,omitempty"` +} diff --git a/option/endpoint.go b/option/endpoint.go new file mode 100644 index 0000000000..45c4f8311f --- /dev/null +++ b/option/endpoint.go @@ -0,0 +1,47 @@ +package option + +import ( + "context" + + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/service" +) + +type EndpointOptionsRegistry interface { + CreateOptions(endpointType string) (any, bool) +} + +type _Endpoint struct { + Type string `json:"type"` + Tag string `json:"tag,omitempty"` + Options any `json:"-"` +} + +type Endpoint _Endpoint + +func (h *Endpoint) MarshalJSONContext(ctx context.Context) ([]byte, error) { + return badjson.MarshallObjectsContext(ctx, (*_Endpoint)(h), h.Options) +} + +func (h *Endpoint) UnmarshalJSONContext(ctx context.Context, content []byte) error { + err := json.UnmarshalContext(ctx, content, (*_Endpoint)(h)) + if err != nil { + return err + } + registry := service.FromContext[EndpointOptionsRegistry](ctx) + if registry == nil { + return E.New("missing endpoint fields registry in context") + } + options, loaded := registry.CreateOptions(h.Type) + if !loaded { + return E.New("unknown endpoint type: ", h.Type) + } + err = badjson.UnmarshallExcludedContext(ctx, content, (*_Endpoint)(h), options) + if err != nil { + return err + } + h.Options = options + return nil +} diff --git a/option/experimental.go b/option/experimental.go index 9f6071baee..102a06a4f0 100644 --- a/option/experimental.go +++ b/option/experimental.go @@ -1,29 +1,41 @@ package option +import ( + "github.com/sagernet/sing/common/json/badoption" +) + type ExperimentalOptions struct { - CacheFile *CacheFileOptions `json:"cache_file,omitempty"` - ClashAPI *ClashAPIOptions `json:"clash_api,omitempty"` - V2RayAPI *V2RayAPIOptions `json:"v2ray_api,omitempty"` - Debug *DebugOptions `json:"debug,omitempty"` + CacheFile *CacheFileOptions `json:"cache_file,omitempty"` + ClashAPI *ClashAPIOptions `json:"clash_api,omitempty"` + V2RayAPI *V2RayAPIOptions `json:"v2ray_api,omitempty"` + UnifiedDelay *UnifiedDelayOptions `json:"unified_delay,omitempty"` + Debug *DebugOptions `json:"debug,omitempty"` + + Monitoring *MonitoringOptions `json:"monitoring,omitempty"` } type CacheFileOptions struct { - Enabled bool `json:"enabled,omitempty"` - Path string `json:"path,omitempty"` - CacheID string `json:"cache_id,omitempty"` - StoreFakeIP bool `json:"store_fakeip,omitempty"` - StoreRDRC bool `json:"store_rdrc,omitempty"` - RDRCTimeout Duration `json:"rdrc_timeout,omitempty"` + Enabled bool `json:"enabled,omitempty"` + Path string `json:"path,omitempty"` + CacheID string `json:"cache_id,omitempty"` + StoreFakeIP bool `json:"store_fakeip,omitempty"` + StoreRDRC bool `json:"store_rdrc,omitempty"` + StoreWARPConfig bool `json:"store_warp_config,omitempty"` + StoreMASQUEConfig bool `json:"store_masque_config,omitempty"` + RDRCTimeout badoption.Duration `json:"rdrc_timeout,omitempty"` + StoreDNS bool `json:"store_dns,omitempty"` } type ClashAPIOptions struct { - ExternalController string `json:"external_controller,omitempty"` - ExternalUI string `json:"external_ui,omitempty"` - ExternalUIDownloadURL string `json:"external_ui_download_url,omitempty"` - ExternalUIDownloadDetour string `json:"external_ui_download_detour,omitempty"` - Secret string `json:"secret,omitempty"` - DefaultMode string `json:"default_mode,omitempty"` - ModeList []string `json:"-"` + ExternalController string `json:"external_controller,omitempty"` + ExternalUI string `json:"external_ui,omitempty"` + ExternalUIDownloadURL string `json:"external_ui_download_url,omitempty"` + ExternalUIDownloadDetour string `json:"external_ui_download_detour,omitempty"` + Secret string `json:"secret,omitempty"` + DefaultMode string `json:"default_mode,omitempty"` + ModeList []string `json:"-"` + AccessControlAllowOrigin badoption.Listable[string] `json:"access_control_allow_origin,omitempty"` + AccessControlAllowPrivateNetwork bool `json:"access_control_allow_private_network,omitempty"` // Deprecated: migrated to global cache file CacheFile string `json:"cache_file,omitempty"` @@ -48,3 +60,7 @@ type V2RayStatsServiceOptions struct { Outbounds []string `json:"outbounds,omitempty"` Users []string `json:"users,omitempty"` } + +type UnifiedDelayOptions struct { + Enabled bool `json:"enabled,omitempty"` +} diff --git a/option/firebasetunnel.go b/option/firebasetunnel.go new file mode 100644 index 0000000000..67946bac96 --- /dev/null +++ b/option/firebasetunnel.go @@ -0,0 +1,68 @@ +package option + +import "github.com/sagernet/sing/common/json/badoption" + +// FirebaseTunnelUser identifies a client allowed to connect when this +// endpoint acts as a server. Name is used as a traffic-accounting label +// (surfaced via the SSM traffic manager). If PSK is set the server also +// requires the client's chunks to decrypt successfully before accepting +// the label. +type FirebaseTunnelUser struct { + Name string `json:"name"` + // PSK authenticates this user and encrypts their relayed payload bytes + // against a passive reader of the Firebase project. Optional: omit to + // relay cleartext (anyone who can read the project can see the data). + PSK string `json:"psk,omitempty"` +} + +// FirebaseTunnelServerConfig holds options that are only meaningful when +// this endpoint acts as a server (inbound). Its presence in +// FirebaseTunnelOptions switches the endpoint into server mode; omitting +// it (nil) selects client (outbound) mode. +type FirebaseTunnelServerConfig struct { + Users []FirebaseTunnelUser `json:"users"` + PollInterval badoption.Duration `json:"poll_interval,omitempty"` + SessionTimeout badoption.Duration `json:"session_timeout,omitempty"` + MaxSessions int `json:"max_sessions,omitempty"` + MaxSessionsPerUser int `json:"max_sessions_per_user,omitempty"` + // MaxSessionsPerSecondPerUser rate-limits new session creation per user + // (token bucket). Zero → built-in default (5/s). + MaxSessionsPerSecondPerUser int `json:"max_sessions_per_second_per_user,omitempty"` +} + +// FirebaseTunnelClientConfig holds options that are only meaningful when +// this endpoint acts as a client (outbound). +type FirebaseTunnelClientConfig struct { + // User is this client's self-reported identity, verified by PSK if set. + User string `json:"user"` + PSK string `json:"psk,omitempty"` + BatchInterval badoption.Duration `json:"batch_interval,omitempty"` + BatchMaxBytes int `json:"batch_max_bytes,omitempty"` + ActivationTimeout badoption.Duration `json:"activation_timeout,omitempty"` +} + +// FirebaseTunnelOptions configures a Firebase Realtime Database relay tunnel +// (adapted from github.com/Hiddify2/Firebase-Tunnel). +// +// Role is determined by which sub-config is present: +// - Server != nil → server (inbound) mode: listens for pending sessions +// written to the Firebase project and routes them through sing-box. +// - Client != nil → client (outbound) mode: dials by writing a session +// request to Firebase and waiting for the server to activate it. +// +// Exactly one of Server or Client must be set. +// +// firebase_secret is the legacy Firebase Database Secret appended as +// ?auth= to every REST call. Anyone holding it has full read/write +// access to the entire Firebase project — prefer firebase_auth_token for +// anything beyond personal/test use. +type FirebaseTunnelOptions struct { + FirebaseURLs badoption.Listable[string] `json:"firebase_urls"` + FirebaseSecret string `json:"firebase_secret,omitempty"` + FirebaseAuthToken string `json:"firebase_auth_token,omitempty"` + RetryLimit uint32 `json:"retry_limit,omitempty"` + + // Exactly one must be non-nil. + Server *FirebaseTunnelServerConfig `json:"server,omitempty"` + Client *FirebaseTunnelClientConfig `json:"client,omitempty"` +} diff --git a/option/fragment.go b/option/fragment.go new file mode 100644 index 0000000000..f1b420b2e9 --- /dev/null +++ b/option/fragment.go @@ -0,0 +1,21 @@ +package option + +import ( + "math/rand" +) + +type TLSFragmentOptions struct { + Enabled bool `json:"enabled,omitempty"` + Size string `json:"size,omitempty"` // Fragment size in Bytes + Sleep string `json:"sleep,omitempty"` // Time to sleep between sending the fragments in milliseconds + Method string `json:"method,omitempty"` // Wether to fragment only clientHello or a range of TCP packets. Valid options: ['tlsHello', 'range'] + Range string `json:"range,omitempty"` // Ra +} + +func RandBetween(min int, max int) int { + if max == min { + return min + } + return rand.Intn(max-min+1) + min + +} diff --git a/option/gooserelay.go b/option/gooserelay.go new file mode 100644 index 0000000000..16cf007e70 --- /dev/null +++ b/option/gooserelay.go @@ -0,0 +1,19 @@ +package option + +import ( + "github.com/sagernet/sing/common/json/badoption" +) + +type GooseRelayOptions struct { + DialerOptions + + ScriptKeys []string `json:"script_keys,omitempty"` + TunnelKey string `json:"tunnel_key,omitempty"` + GoogleHost string `json:"google_host,omitempty"` + SNI []string `json:"sni,omitempty"` + DebugTiming bool `json:"debug_timing,omitempty"` + + UDPOverTCP *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"` + + HandshakeTimeout *badoption.Duration `json:"handshake_timeout,omitempty"` +} diff --git a/option/group.go b/option/group.go index 72a0f63702..979f1d31f5 100644 --- a/option/group.go +++ b/option/group.go @@ -1,5 +1,7 @@ package option +import "github.com/sagernet/sing/common/json/badoption" + type SelectorOutboundOptions struct { Outbounds []string `json:"outbounds"` Default string `json:"default,omitempty"` @@ -7,10 +9,11 @@ type SelectorOutboundOptions struct { } type URLTestOutboundOptions struct { - Outbounds []string `json:"outbounds"` - URL string `json:"url,omitempty"` - Interval Duration `json:"interval,omitempty"` - Tolerance uint16 `json:"tolerance,omitempty"` - IdleTimeout Duration `json:"idle_timeout,omitempty"` - InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"` + Outbounds []string `json:"outbounds"` + URL string `json:"url,omitempty"` + URLs []string `json:"urls,omitempty"` //H + Interval badoption.Duration `json:"interval,omitempty"` + Tolerance uint16 `json:"tolerance,omitempty"` + IdleTimeout badoption.Duration `json:"idle_timeout,omitempty"` + InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"` } diff --git a/option/hinvalid.go b/option/hinvalid.go new file mode 100644 index 0000000000..0d3c0d1496 --- /dev/null +++ b/option/hinvalid.go @@ -0,0 +1,6 @@ +package option + +type HInvalidOptions struct { + InvalidConfig any + Err error +} diff --git a/option/http.go b/option/http.go new file mode 100644 index 0000000000..1a97270443 --- /dev/null +++ b/option/http.go @@ -0,0 +1,127 @@ +package option + +import ( + "reflect" + + "github.com/sagernet/sing/common/byteformats" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" +) + +type HTTP2Options struct { + IdleTimeout badoption.Duration `json:"idle_timeout,omitempty"` + KeepAlivePeriod badoption.Duration `json:"keep_alive_period,omitempty"` + StreamReceiveWindow byteformats.MemoryBytes `json:"stream_receive_window,omitempty"` + ConnectionReceiveWindow byteformats.MemoryBytes `json:"connection_receive_window,omitempty"` + MaxConcurrentStreams int `json:"max_concurrent_streams,omitempty"` +} + +type QUICOptions struct { + HTTP2Options + InitialPacketSize int `json:"initial_packet_size,omitempty"` + DisablePathMTUDiscovery bool `json:"disable_path_mtu_discovery,omitempty"` +} + +type _HTTPClientOptions struct { + Tag string `json:"tag,omitempty"` + Engine string `json:"engine,omitempty"` + Version int `json:"version,omitempty"` + DisableVersionFallback bool `json:"disable_version_fallback,omitempty"` + Headers badoption.HTTPHeader `json:"headers,omitempty"` + HTTP2Options HTTP2Options `json:"-"` + HTTP3Options QUICOptions `json:"-"` + DefaultOutbound bool `json:"-"` + DisableEmptyDirectCheck bool `json:"-"` + ResolveOnDetour bool `json:"-"` + DirectResolver bool `json:"-"` + OutboundTLSOptionsContainer + DialerOptions +} + +type ( + HTTPClient _HTTPClientOptions + HTTPClientOptions _HTTPClientOptions +) + +func (h HTTPClient) Options() HTTPClientOptions { + options := HTTPClientOptions(h) + options.Tag = "" + return options +} + +func (o HTTPClientOptions) IsEmpty() bool { + if o.Tag != "" { + return false + } + o.DefaultOutbound = false + o.ResolveOnDetour = false + o.DirectResolver = false + return reflect.ValueOf(_HTTPClientOptions(o)).IsZero() +} + +func (o HTTPClientOptions) MarshalJSON() ([]byte, error) { + if o.Tag != "" { + return json.Marshal(o.Tag) + } + return badjson.MarshallObjects(_HTTPClientOptions(o), httpClientVariant(_HTTPClientOptions(o))) +} + +func (o *HTTPClientOptions) UnmarshalJSON(content []byte) error { + if len(content) > 0 && content[0] == '"' { + *o = HTTPClientOptions{} + return json.Unmarshal(content, &o.Tag) + } + var options _HTTPClientOptions + err := json.Unmarshal(content, &options) + if err != nil { + return err + } + err = unmarshalHTTPClientVersionOptions(content, &options, &options) + if err != nil { + return err + } + options.Tag = "" + *o = HTTPClientOptions(options) + return nil +} + +func (h HTTPClient) MarshalJSON() ([]byte, error) { + return badjson.MarshallObjects(_HTTPClientOptions(h), httpClientVariant(_HTTPClientOptions(h))) +} + +func (h *HTTPClient) UnmarshalJSON(content []byte) error { + err := json.Unmarshal(content, (*_HTTPClientOptions)(h)) + if err != nil { + return err + } + return unmarshalHTTPClientVersionOptions(content, (*_HTTPClientOptions)(h), (*_HTTPClientOptions)(h)) +} + +func unmarshalHTTPClientVersionOptions(content []byte, baseStruct any, options *_HTTPClientOptions) error { + switch options.Version { + case 1: + return json.UnmarshalDisallowUnknownFields(content, baseStruct) + case 0, 2: + options.Version = 2 + return badjson.UnmarshallExcluded(content, baseStruct, &options.HTTP2Options) + case 3: + return badjson.UnmarshallExcluded(content, baseStruct, &options.HTTP3Options) + default: + return E.New("unknown HTTP version: ", options.Version) + } +} + +func httpClientVariant(options _HTTPClientOptions) any { + switch options.Version { + case 1: + return nil + case 0, 2: + return options.HTTP2Options + case 3: + return options.HTTP3Options + default: + return nil + } +} diff --git a/option/hysteria.go b/option/hysteria.go index beb3685eac..f5ab87cec1 100644 --- a/option/hysteria.go +++ b/option/hysteria.go @@ -1,18 +1,28 @@ package option +import ( + "github.com/sagernet/sing/common/byteformats" + "github.com/sagernet/sing/common/json/badoption" +) + type HysteriaInboundOptions struct { ListenOptions - Up string `json:"up,omitempty"` - UpMbps int `json:"up_mbps,omitempty"` - Down string `json:"down,omitempty"` - DownMbps int `json:"down_mbps,omitempty"` - Obfs string `json:"obfs,omitempty"` - Users []HysteriaUser `json:"users,omitempty"` - ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"` - ReceiveWindowClient uint64 `json:"recv_window_client,omitempty"` - MaxConnClient int `json:"max_conn_client,omitempty"` - DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"` + Up *byteformats.NetworkBytesCompat `json:"up,omitempty"` + UpMbps int `json:"up_mbps,omitempty"` + Down *byteformats.NetworkBytesCompat `json:"down,omitempty"` + DownMbps int `json:"down_mbps,omitempty"` + Obfs string `json:"obfs,omitempty"` + Users []HysteriaUser `json:"users,omitempty"` + // Deprecated: use QUIC fields instead + ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"` + // Deprecated: use QUIC fields instead + ReceiveWindowClient uint64 `json:"recv_window_client,omitempty"` + // Deprecated: use QUIC fields instead + MaxConnClient int `json:"max_conn_client,omitempty"` + // Deprecated: use QUIC fields instead + DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"` InboundTLSOptionsContainer + QUICOptions } type HysteriaUser struct { @@ -24,16 +34,22 @@ type HysteriaUser struct { type HysteriaOutboundOptions struct { DialerOptions ServerOptions - Up string `json:"up,omitempty"` - UpMbps int `json:"up_mbps,omitempty"` - Down string `json:"down,omitempty"` - DownMbps int `json:"down_mbps,omitempty"` - Obfs string `json:"obfs,omitempty"` - Auth []byte `json:"auth,omitempty"` - AuthString string `json:"auth_str,omitempty"` - ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"` - ReceiveWindow uint64 `json:"recv_window,omitempty"` + ServerPorts badoption.Listable[string] `json:"server_ports,omitempty"` + HopInterval badoption.Duration `json:"hop_interval,omitempty"` + Up *byteformats.NetworkBytesCompat `json:"up,omitempty"` + UpMbps int `json:"up_mbps,omitempty"` + Down *byteformats.NetworkBytesCompat `json:"down,omitempty"` + DownMbps int `json:"down_mbps,omitempty"` + Obfs string `json:"obfs,omitempty"` + Auth []byte `json:"auth,omitempty"` + AuthString string `json:"auth_str,omitempty"` + // Deprecated: use QUIC fields instead + ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"` + // Deprecated: use QUIC fields instead + ReceiveWindow uint64 `json:"recv_window,omitempty"` + // Deprecated: use QUIC fields instead DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"` Network NetworkList `json:"network,omitempty"` OutboundTLSOptionsContainer + QUICOptions } diff --git a/option/hysteria2.go b/option/hysteria2.go index 5032c734a3..d20c0bd37b 100644 --- a/option/hysteria2.go +++ b/option/hysteria2.go @@ -1,5 +1,15 @@ package option +import ( + "net/url" + + C "github.com/sagernet/sing-box/constant" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" +) + type Hysteria2InboundOptions struct { ListenOptions UpMbps int `json:"up_mbps,omitempty"` @@ -8,13 +18,71 @@ type Hysteria2InboundOptions struct { Users []Hysteria2User `json:"users,omitempty"` IgnoreClientBandwidth bool `json:"ignore_client_bandwidth,omitempty"` InboundTLSOptionsContainer - Masquerade string `json:"masquerade,omitempty"` - BrutalDebug bool `json:"brutal_debug,omitempty"` + QUICOptions + Masquerade *Hysteria2Masquerade `json:"masquerade,omitempty"` + BBRProfile string `json:"bbr_profile,omitempty"` + BrutalDebug bool `json:"brutal_debug,omitempty"` + Realm *Hysteria2InboundRealm `json:"realm,omitempty"` } -type Hysteria2Obfs struct { - Type string `json:"type,omitempty"` - Password string `json:"password,omitempty"` +type Hysteria2Realm struct { + ServerURL string `json:"server_url"` + Token string `json:"token,omitempty"` + RealmID string `json:"realm_id"` + STUNServers badoption.Listable[string] `json:"stun_servers"` + HTTPClient *HTTPClientOptions `json:"http_client,omitempty"` +} + +type Hysteria2InboundRealm struct { + Hysteria2Realm + STUNDomainResolver *DomainResolveOptions `json:"stun_domain_resolver,omitempty"` +} + +type Hysteria2ObfsGecko struct { + MinPacketSize int `json:"min_packet_size,omitempty"` + MaxPacketSize int `json:"max_packet_size,omitempty"` +} + +type _Hysteria2Obfs struct { + Type string `json:"type,omitempty"` + Password string `json:"password,omitempty"` + GeckoOptions Hysteria2ObfsGecko `json:"-"` +} + +type Hysteria2Obfs _Hysteria2Obfs + +func (o Hysteria2Obfs) MarshalJSON() ([]byte, error) { + var v any + switch o.Type { + case C.Hysteria2ObfsTypeSalamander: + case C.Hysteria2ObfsTypeGecko: + v = o.GeckoOptions + default: + return nil, E.New("unknown obfs type: ", o.Type) + } + if v == nil { + return json.Marshal((_Hysteria2Obfs)(o)) + } + return badjson.MarshallObjects((_Hysteria2Obfs)(o), v) +} + +func (o *Hysteria2Obfs) UnmarshalJSON(bytes []byte) error { + err := json.Unmarshal(bytes, (*_Hysteria2Obfs)(o)) + if err != nil { + return err + } + var v any + switch o.Type { + case C.Hysteria2ObfsTypeSalamander: + case C.Hysteria2ObfsTypeGecko: + v = &o.GeckoOptions + default: + return E.New("unknown obfs type: ", o.Type) + } + if v == nil { + return nil + } + return badjson.UnmarshallExcluded(bytes, (*_Hysteria2Obfs)(o), v) } type Hysteria2User struct { @@ -22,14 +90,110 @@ type Hysteria2User struct { Password string `json:"password,omitempty"` } +type _Hysteria2Masquerade struct { + Type string `json:"type,omitempty"` + FileOptions Hysteria2MasqueradeFile `json:"-"` + ProxyOptions Hysteria2MasqueradeProxy `json:"-"` + StringOptions Hysteria2MasqueradeString `json:"-"` +} + +type Hysteria2Masquerade _Hysteria2Masquerade + +func (m Hysteria2Masquerade) MarshalJSON() ([]byte, error) { + var v any + switch m.Type { + case C.Hysterai2MasqueradeTypeFile: + v = m.FileOptions + case C.Hysterai2MasqueradeTypeProxy: + v = m.ProxyOptions + case C.Hysterai2MasqueradeTypeString: + v = m.StringOptions + default: + return nil, E.New("unknown masquerade type: ", m.Type) + } + return badjson.MarshallObjects((_Hysteria2Masquerade)(m), v) +} + +func (m *Hysteria2Masquerade) UnmarshalJSON(bytes []byte) error { + var urlString string + err := json.Unmarshal(bytes, &urlString) + if err == nil { + masqueradeURL, err := url.Parse(urlString) + if err != nil { + return E.Cause(err, "invalid masquerade URL") + } + switch masqueradeURL.Scheme { + case "file": + m.Type = C.Hysterai2MasqueradeTypeFile + m.FileOptions.Directory = masqueradeURL.Path + case "http", "https": + m.Type = C.Hysterai2MasqueradeTypeProxy + m.ProxyOptions.URL = urlString + default: + return E.New("unknown masquerade URL scheme: ", masqueradeURL.Scheme) + } + return nil + } + err = json.Unmarshal(bytes, (*_Hysteria2Masquerade)(m)) + if err != nil { + return err + } + var v any + switch m.Type { + case C.Hysterai2MasqueradeTypeFile: + v = &m.FileOptions + case C.Hysterai2MasqueradeTypeProxy: + v = &m.ProxyOptions + case C.Hysterai2MasqueradeTypeString: + v = &m.StringOptions + default: + return E.New("unknown masquerade type: ", m.Type) + } + return badjson.UnmarshallExcluded(bytes, (*_Hysteria2Masquerade)(m), v) +} + +type Hysteria2MasqueradeFile struct { + Directory string `json:"directory"` +} + +type Hysteria2MasqueradeProxy struct { + URL string `json:"url"` + RewriteHost bool `json:"rewrite_host,omitempty"` +} + +type Hysteria2MasqueradeString struct { + StatusCode int `json:"status_code,omitempty"` + Headers badoption.HTTPHeader `json:"headers,omitempty"` + Content string `json:"content"` +} + type Hysteria2OutboundOptions struct { DialerOptions ServerOptions - UpMbps int `json:"up_mbps,omitempty"` - DownMbps int `json:"down_mbps,omitempty"` - Obfs *Hysteria2Obfs `json:"obfs,omitempty"` - Password string `json:"password,omitempty"` - Network NetworkList `json:"network,omitempty"` + ServerPorts badoption.Listable[string] `json:"server_ports,omitempty"` + HopInterval badoption.Duration `json:"hop_interval,omitempty"` + HopIntervalMax badoption.Duration `json:"hop_interval_max,omitempty"` + UpMbps int `json:"up_mbps,omitempty"` + DownMbps int `json:"down_mbps,omitempty"` + Obfs *Hysteria2Obfs `json:"obfs,omitempty"` + Password string `json:"password,omitempty"` + Network NetworkList `json:"network,omitempty"` OutboundTLSOptionsContainer - BrutalDebug bool `json:"brutal_debug,omitempty"` + QUICOptions + BBRProfile string `json:"bbr_profile,omitempty"` + BrutalDebug bool `json:"brutal_debug,omitempty"` + Realm *Hysteria2Realm `json:"realm,omitempty"` +} + +type HysteriaRealmUser struct { + Name string `json:"name"` + Token string `json:"token"` + MaxRealms int `json:"max_realms,omitempty"` +} + +type HysteriaRealmServiceOptions struct { + ListenOptions + InboundTLSOptionsContainer + HTTP2Options + Users []HysteriaRealmUser `json:"users"` } diff --git a/option/inbound.go b/option/inbound.go index 54e8bab84a..21497a3f4e 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -1,126 +1,93 @@ package option import ( + "context" "time" - C "github.com/sagernet/sing-box/constant" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" + "github.com/sagernet/sing/service" ) +type InboundOptionsRegistry interface { + CreateOptions(outboundType string) (any, bool) +} + type _Inbound struct { - Type string `json:"type"` - Tag string `json:"tag,omitempty"` - TunOptions TunInboundOptions `json:"-"` - RedirectOptions RedirectInboundOptions `json:"-"` - TProxyOptions TProxyInboundOptions `json:"-"` - DirectOptions DirectInboundOptions `json:"-"` - SocksOptions SocksInboundOptions `json:"-"` - HTTPOptions HTTPMixedInboundOptions `json:"-"` - MixedOptions HTTPMixedInboundOptions `json:"-"` - ShadowsocksOptions ShadowsocksInboundOptions `json:"-"` - VMessOptions VMessInboundOptions `json:"-"` - TrojanOptions TrojanInboundOptions `json:"-"` - NaiveOptions NaiveInboundOptions `json:"-"` - HysteriaOptions HysteriaInboundOptions `json:"-"` - ShadowTLSOptions ShadowTLSInboundOptions `json:"-"` - VLESSOptions VLESSInboundOptions `json:"-"` - TUICOptions TUICInboundOptions `json:"-"` - Hysteria2Options Hysteria2InboundOptions `json:"-"` + Type string `json:"type"` + Tag string `json:"tag,omitempty"` + Options any `json:"-"` } type Inbound _Inbound -func (h *Inbound) RawOptions() (any, error) { - var rawOptionsPtr any - switch h.Type { - case C.TypeTun: - rawOptionsPtr = &h.TunOptions - case C.TypeRedirect: - rawOptionsPtr = &h.RedirectOptions - case C.TypeTProxy: - rawOptionsPtr = &h.TProxyOptions - case C.TypeDirect: - rawOptionsPtr = &h.DirectOptions - case C.TypeSOCKS: - rawOptionsPtr = &h.SocksOptions - case C.TypeHTTP: - rawOptionsPtr = &h.HTTPOptions - case C.TypeMixed: - rawOptionsPtr = &h.MixedOptions - case C.TypeShadowsocks: - rawOptionsPtr = &h.ShadowsocksOptions - case C.TypeVMess: - rawOptionsPtr = &h.VMessOptions - case C.TypeTrojan: - rawOptionsPtr = &h.TrojanOptions - case C.TypeNaive: - rawOptionsPtr = &h.NaiveOptions - case C.TypeHysteria: - rawOptionsPtr = &h.HysteriaOptions - case C.TypeShadowTLS: - rawOptionsPtr = &h.ShadowTLSOptions - case C.TypeVLESS: - rawOptionsPtr = &h.VLESSOptions - case C.TypeTUIC: - rawOptionsPtr = &h.TUICOptions - case C.TypeHysteria2: - rawOptionsPtr = &h.Hysteria2Options - case "": - return nil, E.New("missing inbound type") - default: - return nil, E.New("unknown inbound type: ", h.Type) - } - return rawOptionsPtr, nil -} - -func (h Inbound) MarshalJSON() ([]byte, error) { - rawOptions, err := h.RawOptions() - if err != nil { - return nil, err - } - return MarshallObjects((_Inbound)(h), rawOptions) +func (h *Inbound) MarshalJSONContext(ctx context.Context) ([]byte, error) { + return badjson.MarshallObjectsContext(ctx, (*_Inbound)(h), h.Options) } -func (h *Inbound) UnmarshalJSON(bytes []byte) error { - err := json.Unmarshal(bytes, (*_Inbound)(h)) +func (h *Inbound) UnmarshalJSONContext(ctx context.Context, content []byte) error { + err := json.UnmarshalContext(ctx, content, (*_Inbound)(h)) if err != nil { return err } - rawOptions, err := h.RawOptions() - if err != nil { - return err + registry := service.FromContext[InboundOptionsRegistry](ctx) + if registry == nil { + return E.New("missing inbound fields registry in context") } - err = UnmarshallExcluded(bytes, (*_Inbound)(h), rawOptions) + options, loaded := registry.CreateOptions(h.Type) + if !loaded { + return E.New("unknown inbound type: ", h.Type) + } + err = badjson.UnmarshallExcludedContext(ctx, content, (*_Inbound)(h), options) if err != nil { return err } + if listenWrapper, isListen := options.(ListenOptionsWrapper); isListen { + //nolint:staticcheck + if listenWrapper.TakeListenOptions().InboundOptions != (InboundOptions{}) { + return E.New("legacy inbound fields are deprecated in sing-box 1.11.0 and removed in sing-box 1.13.0, checkout migration: https://sing-box.sagernet.org/migration/#migrate-legacy-inbound-fields-to-rule-actions") + } + } + h.Options = options return nil } +// Deprecated: Use rule action instead type InboundOptions struct { - SniffEnabled bool `json:"sniff,omitempty"` - SniffOverrideDestination bool `json:"sniff_override_destination,omitempty"` - SniffTimeout Duration `json:"sniff_timeout,omitempty"` - DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` - UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` + SniffEnabled bool `json:"sniff,omitempty"` + SniffOverrideDestination bool `json:"sniff_override_destination,omitempty"` + SniffTimeout badoption.Duration `json:"sniff_timeout,omitempty"` + DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` + UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` } type ListenOptions struct { - Listen *ListenAddress `json:"listen,omitempty"` - ListenPort uint16 `json:"listen_port,omitempty"` - TCPFastOpen bool `json:"tcp_fast_open,omitempty"` - TCPMultiPath bool `json:"tcp_multi_path,omitempty"` - UDPFragment *bool `json:"udp_fragment,omitempty"` - UDPFragmentDefault bool `json:"-"` - UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` - ProxyProtocol bool `json:"proxy_protocol,omitempty"` - ProxyProtocolAcceptNoHeader bool `json:"proxy_protocol_accept_no_header,omitempty"` - Detour string `json:"detour,omitempty"` + Listen *badoption.Addr `json:"listen,omitempty"` + ListenPort uint16 `json:"listen_port,omitempty"` + BindInterface string `json:"bind_interface,omitempty"` + RoutingMark FwMark `json:"routing_mark,omitempty"` + ReuseAddr bool `json:"reuse_addr,omitempty"` + NetNs string `json:"netns,omitempty"` + DisableTCPKeepAlive bool `json:"disable_tcp_keep_alive,omitempty"` + TCPKeepAlive badoption.Duration `json:"tcp_keep_alive,omitempty"` + TCPKeepAliveInterval badoption.Duration `json:"tcp_keep_alive_interval,omitempty"` + TCPFastOpen bool `json:"tcp_fast_open,omitempty"` + TCPMultiPath bool `json:"tcp_multi_path,omitempty"` + UDPFragment *bool `json:"udp_fragment,omitempty"` + UDPFragmentDefault bool `json:"-"` + UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` + Detour string `json:"detour,omitempty"` + + // Deprecated: removed + ProxyProtocol bool `json:"proxy_protocol,omitempty"` + // Deprecated: removed + ProxyProtocolAcceptNoHeader bool `json:"proxy_protocol_accept_no_header,omitempty"` InboundOptions } -type UDPTimeoutCompat Duration +type UDPTimeoutCompat badoption.Duration func (c UDPTimeoutCompat) MarshalJSON() ([]byte, error) { return json.Marshal((time.Duration)(c).String()) @@ -133,7 +100,7 @@ func (c *UDPTimeoutCompat) UnmarshalJSON(data []byte) error { *c = UDPTimeoutCompat(time.Second * time.Duration(valueNumber)) return nil } - return json.Unmarshal(data, (*Duration)(c)) + return json.Unmarshal(data, (*badoption.Duration)(c)) } type ListenOptionsWrapper interface { diff --git a/option/json.go b/option/json.go deleted file mode 100644 index 07580e9f7b..0000000000 --- a/option/json.go +++ /dev/null @@ -1,75 +0,0 @@ -package option - -import ( - "bytes" - - "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/json" - "github.com/sagernet/sing/common/json/badjson" -) - -func ToMap(v any) (*badjson.JSONObject, error) { - inputContent, err := json.Marshal(v) - if err != nil { - return nil, err - } - var content badjson.JSONObject - err = content.UnmarshalJSON(inputContent) - if err != nil { - return nil, err - } - return &content, nil -} - -func MergeObjects(objects ...any) (*badjson.JSONObject, error) { - var content badjson.JSONObject - for _, object := range objects { - objectMap, err := ToMap(object) - if err != nil { - return nil, err - } - content.PutAll(objectMap) - } - return &content, nil -} - -func MarshallObjects(objects ...any) ([]byte, error) { - objects = common.FilterNotNil(objects) - if len(objects) == 1 { - return json.Marshal(objects[0]) - } - content, err := MergeObjects(objects...) - if err != nil { - return nil, err - } - return content.MarshalJSON() -} - -func UnmarshallExcluded(inputContent []byte, parentObject any, object any) error { - parentContent, err := ToMap(parentObject) - if err != nil { - return err - } - var content badjson.JSONObject - err = content.UnmarshalJSON(inputContent) - if err != nil { - return err - } - for _, key := range parentContent.Keys() { - content.Remove(key) - } - if object == nil { - if content.IsEmpty() { - return nil - } - return E.New("unexpected key: ", content.Keys()[0]) - } - inputContent, err = content.MarshalJSON() - if err != nil { - return err - } - decoder := json.NewDecoder(bytes.NewReader(inputContent)) - decoder.DisallowUnknownFields() - return decoder.Decode(object) -} diff --git a/option/masque.go b/option/masque.go new file mode 100644 index 0000000000..83fa849a6b --- /dev/null +++ b/option/masque.go @@ -0,0 +1,32 @@ +package option + +import ( + "github.com/sagernet/sing/common/json/badoption" +) + +type MASQUEOutboundOptions struct { + UseHTTP2 bool `json:"use_http2,omitempty"` + UseIPv6 bool `json:"use_ipv6,omitempty"` + Profile CloudflareProfile `json:"profile,omitempty"` + UDPTimeout badoption.Duration `json:"udp_timeout,omitempty"` + UDPKeepalivePeriod badoption.Duration `json:"udp_keepalive_period,omitempty"` + UDPInitialPacketSize uint16 `json:"udp_initial_packet_size,omitempty"` + ReconnectDelay badoption.Duration `json:"reconnect_delay,omitempty"` + MASQUEOutboundTLSOptions + DialerOptions +} + +type MASQUEOutboundTLSOptions struct { + Insecure bool `json:"insecure,omitempty"` + CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"` + CurvePreferences badoption.Listable[CurvePreference] `json:"curve_preferences,omitempty"` + Fragment bool `json:"fragment,omitempty"` + FragmentFallbackDelay badoption.Duration `json:"fragment_fallback_delay,omitempty"` + RecordFragment bool `json:"record_fragment,omitempty"` + KernelTx bool `json:"kernel_tx,omitempty"` + KernelRx bool `json:"kernel_rx,omitempty"` +} + +type MASQUEOutboundTLSOptionsContainer struct { + TLS *OutboundTLSOptions `json:"tls,omitempty"` +} diff --git a/option/mieru.go b/option/mieru.go new file mode 100644 index 0000000000..5a7fdcfd27 --- /dev/null +++ b/option/mieru.go @@ -0,0 +1,30 @@ +package option + +type MieruOutboundOptions struct { + DialerOptions + ServerOptions + Network NetworkList `json:"network,omitempty"` + PortBindings []MieruPortBinding `json:"portBindings,omitempty"` + UserName string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Multiplexing string `json:"multiplexing,omitempty"` + HandshakeMode string `json:"handshake_mode,omitempty"` +} + +type MieruInboundOptions struct { + ListenOptions + Users []MieruUser `json:"users,omitempty"` + PortBindings []MieruPortBinding `json:"portBindings,omitempty"` + Network NetworkList `json:"network,omitempty"` +} + +type MieruPortBinding struct { + Protocol string `json:"protocol,omitempty"` + PortRange string `json:"portRange,omitempty"` + Port uint16 `json:"port,omitempty"` +} + +type MieruUser struct { + Name string `json:"username,omitempty"` + Password string `json:"password,omitempty"` +} diff --git a/option/monitoring.go b/option/monitoring.go new file mode 100644 index 0000000000..4ff5cd0bf7 --- /dev/null +++ b/option/monitoring.go @@ -0,0 +1,12 @@ +package option + +import "github.com/sagernet/sing/common/json/badoption" + +type MonitoringOptions struct { + Interval badoption.Duration `json:"interval,omitempty"` + URLs []string `json:"urls,omitempty"` //H + Workers int `json:"workers,omitempty"` + DebounceWindow badoption.Duration `json:"debounce_window,omitempty"` + URLTestTimeout badoption.Duration `json:"url_test_timeout,omitempty"` + IdleTimeout badoption.Duration `json:"idle_timeout,omitempty"` +} diff --git a/option/naive.go b/option/naive.go index 0b19f26473..da3a88db98 100644 --- a/option/naive.go +++ b/option/naive.go @@ -1,10 +1,40 @@ package option -import "github.com/sagernet/sing/common/auth" +import ( + "github.com/sagernet/sing/common/auth" + "github.com/sagernet/sing/common/byteformats" + "github.com/sagernet/sing/common/json/badoption" +) + +type QuicheCongestionControl string + +const ( + QuicheCongestionControlDefault QuicheCongestionControl = "" + QuicheCongestionControlBBR QuicheCongestionControl = "TBBR" + QuicheCongestionControlBBRv2 QuicheCongestionControl = "B2ON" + QuicheCongestionControlCubic QuicheCongestionControl = "QBIC" + QuicheCongestionControlReno QuicheCongestionControl = "RENO" +) type NaiveInboundOptions struct { ListenOptions - Users []auth.User `json:"users,omitempty"` - Network NetworkList `json:"network,omitempty"` + Users []auth.User `json:"users,omitempty"` + Network NetworkList `json:"network,omitempty"` + QUICCongestionControl string `json:"quic_congestion_control,omitempty"` InboundTLSOptionsContainer } + +type NaiveOutboundOptions struct { + DialerOptions + ServerOptions + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + InsecureConcurrency int `json:"insecure_concurrency,omitempty"` + ExtraHeaders badoption.HTTPHeader `json:"extra_headers,omitempty"` + ReceiveWindow *byteformats.MemoryBytes `json:"stream_receive_window,omitempty"` + UDPOverTCP *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"` + QUIC bool `json:"quic,omitempty"` + QUICCongestionControl string `json:"quic_congestion_control,omitempty"` + QUICSessionReceiveWindow *byteformats.MemoryBytes `json:"quic_session_receive_window,omitempty"` + OutboundTLSOptionsContainer +} diff --git a/option/ntp.go b/option/ntp.go index 0bd2489ac2..d441d95e86 100644 --- a/option/ntp.go +++ b/option/ntp.go @@ -1,9 +1,11 @@ package option +import "github.com/sagernet/sing/common/json/badoption" + type NTPOptions struct { - Enabled bool `json:"enabled,omitempty"` - Interval Duration `json:"interval,omitempty"` - WriteToSystem bool `json:"write_to_system,omitempty"` + Enabled bool `json:"enabled,omitempty"` + Interval badoption.Duration `json:"interval,omitempty"` + WriteToSystem bool `json:"write_to_system,omitempty"` ServerOptions DialerOptions } diff --git a/option/ocm.go b/option/ocm.go new file mode 100644 index 0000000000..c13a1c1f53 --- /dev/null +++ b/option/ocm.go @@ -0,0 +1,20 @@ +package option + +import ( + "github.com/sagernet/sing/common/json/badoption" +) + +type OCMServiceOptions struct { + ListenOptions + InboundTLSOptionsContainer + CredentialPath string `json:"credential_path,omitempty"` + Users []OCMUser `json:"users,omitempty"` + Headers badoption.HTTPHeader `json:"headers,omitempty"` + Detour string `json:"detour,omitempty"` + UsagesPath string `json:"usages_path,omitempty"` +} + +type OCMUser struct { + Name string `json:"name,omitempty"` + Token string `json:"token,omitempty"` +} diff --git a/option/oom_killer.go b/option/oom_killer.go new file mode 100644 index 0000000000..1183b502b7 --- /dev/null +++ b/option/oom_killer.go @@ -0,0 +1,15 @@ +package option + +import ( + "github.com/sagernet/sing/common/byteformats" + "github.com/sagernet/sing/common/json/badoption" +) + +type OOMKillerServiceOptions struct { + MemoryLimit *byteformats.MemoryBytes `json:"memory_limit,omitempty"` + SafetyMargin *byteformats.MemoryBytes `json:"safety_margin,omitempty"` + MinInterval badoption.Duration `json:"min_interval,omitempty"` + MaxInterval badoption.Duration `json:"max_interval,omitempty"` + KillerDisabled bool `json:"-"` + MemoryLimitOverride uint64 `json:"-"` +} diff --git a/option/options.go b/option/options.go new file mode 100644 index 0000000000..4e87852ac3 --- /dev/null +++ b/option/options.go @@ -0,0 +1,152 @@ +package option + +import ( + "bytes" + "context" + + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/json" +) + +type _Options struct { + RawMessage json.RawMessage `json:"-"` + CommentsSet *json.CommentSet `json:"-"` + Schema string `json:"$schema,omitempty"` + Log *LogOptions `json:"log,omitempty"` + DNS *DNSOptions `json:"dns,omitempty"` + NTP *NTPOptions `json:"ntp,omitempty"` + Certificate *CertificateOptions `json:"certificate,omitempty"` + CertificateProviders []CertificateProvider `json:"certificate_providers,omitempty"` + HTTPClients []HTTPClient `json:"http_clients,omitempty"` + Endpoints []Endpoint `json:"endpoints,omitempty"` + Inbounds []Inbound `json:"inbounds,omitempty"` + Outbounds []Outbound `json:"outbounds,omitempty"` + Route *RouteOptions `json:"route,omitempty"` + Services []Service `json:"services,omitempty"` + Experimental *ExperimentalOptions `json:"experimental,omitempty"` +} + +type Options _Options + +func (o Options) MarshalJSONContext(ctx context.Context) ([]byte, error) { + return json.MarshalContext(ctx, (_Options)(o)) +} + +func (o *Options) UnmarshalJSONContext(ctx context.Context, content []byte) error { + decoder := json.NewDecoderContext(ctx, bytes.NewReader(content)) + decoder.DisallowUnknownFields() + err := decoder.Decode((*_Options)(o)) + if err != nil { + return err + } + o.RawMessage = content + return checkOptions(o) +} + +func (o Options) Comments() *json.CommentSet { + return o.CommentsSet +} + +func (o *Options) SetComments(comments *json.CommentSet) { + o.CommentsSet = comments +} + +type LogOptions struct { + Disabled bool `json:"disabled,omitempty"` + Level string `json:"level,omitempty"` + Output string `json:"output,omitempty"` + Timestamp bool `json:"timestamp,omitempty"` + DisableColor bool `json:"-"` +} + +type StubOptions struct{} + +func checkOptions(options *Options) error { + err := checkInbounds(options.Inbounds) + if err != nil { + return err + } + err = checkOutbounds(options.Outbounds, options.Endpoints) + if err != nil { + return err + } + err = checkCertificateProviders(options.CertificateProviders) + if err != nil { + return err + } + err = checkHTTPClients(options.HTTPClients) + if err != nil { + return err + } + return nil +} + +func checkCertificateProviders(providers []CertificateProvider) error { + seen := make(map[string]bool) + for i, provider := range providers { + tag := provider.Tag + if tag == "" { + tag = F.ToString(i) + } + if seen[tag] { + return E.New("duplicate certificate provider tag: ", tag) + } + seen[tag] = true + } + return nil +} + +func checkHTTPClients(clients []HTTPClient) error { + seen := make(map[string]bool) + for _, client := range clients { + if client.Tag == "" { + return E.New("missing http client tag") + } + if seen[client.Tag] { + return E.New("duplicate http client tag: ", client.Tag) + } + seen[client.Tag] = true + } + return nil +} + +func checkInbounds(inbounds []Inbound) error { + seen := make(map[string]bool) + for i, inbound := range inbounds { + tag := inbound.Tag + if tag == "" { + tag = F.ToString(i) + } + if seen[tag] { + return E.New("duplicate inbound tag: ", tag) + } + seen[tag] = true + } + return nil +} + +func checkOutbounds(outbounds []Outbound, endpoints []Endpoint) error { + seen := make(map[string]bool) + for i, outbound := range outbounds { + tag := outbound.Tag + if tag == "" { + tag = F.ToString(i) + } + if seen[tag] { + return E.New("duplicate outbound/endpoint tag: ", tag) + } + seen[tag] = true + } + for i, endpoint := range endpoints { + tag := endpoint.Tag + if tag == "" { + tag = F.ToString(i) + } + if seen[tag] { + return E.New("duplicate outbound/endpoint tag: ", tag) + } + seen[tag] = true + } + return nil +} diff --git a/option/origin_ca.go b/option/origin_ca.go new file mode 100644 index 0000000000..5a9f956af7 --- /dev/null +++ b/option/origin_ca.go @@ -0,0 +1,76 @@ +package option + +import ( + "strings" + + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badoption" +) + +type CloudflareOriginCACertificateProviderOptions struct { + Domain badoption.Listable[string] `json:"domain,omitempty"` + DataDirectory string `json:"data_directory,omitempty"` + APIToken string `json:"api_token,omitempty"` + OriginCAKey string `json:"origin_ca_key,omitempty"` + RequestType CloudflareOriginCARequestType `json:"request_type,omitempty"` + RequestedValidity CloudflareOriginCARequestValidity `json:"requested_validity,omitempty"` + HTTPClient *HTTPClientOptions `json:"http_client,omitempty"` +} + +type CloudflareOriginCARequestType string + +const ( + CloudflareOriginCARequestTypeOriginRSA = CloudflareOriginCARequestType("origin-rsa") + CloudflareOriginCARequestTypeOriginECC = CloudflareOriginCARequestType("origin-ecc") +) + +func (t *CloudflareOriginCARequestType) UnmarshalJSON(data []byte) error { + var value string + err := json.Unmarshal(data, &value) + if err != nil { + return err + } + value = strings.ToLower(value) + switch CloudflareOriginCARequestType(value) { + case "", CloudflareOriginCARequestTypeOriginRSA, CloudflareOriginCARequestTypeOriginECC: + *t = CloudflareOriginCARequestType(value) + default: + return E.New("unsupported Cloudflare Origin CA request type: ", value) + } + return nil +} + +type CloudflareOriginCARequestValidity uint16 + +const ( + CloudflareOriginCARequestValidity7 = CloudflareOriginCARequestValidity(7) + CloudflareOriginCARequestValidity30 = CloudflareOriginCARequestValidity(30) + CloudflareOriginCARequestValidity90 = CloudflareOriginCARequestValidity(90) + CloudflareOriginCARequestValidity365 = CloudflareOriginCARequestValidity(365) + CloudflareOriginCARequestValidity730 = CloudflareOriginCARequestValidity(730) + CloudflareOriginCARequestValidity1095 = CloudflareOriginCARequestValidity(1095) + CloudflareOriginCARequestValidity5475 = CloudflareOriginCARequestValidity(5475) +) + +func (v *CloudflareOriginCARequestValidity) UnmarshalJSON(data []byte) error { + var value uint16 + err := json.Unmarshal(data, &value) + if err != nil { + return err + } + switch CloudflareOriginCARequestValidity(value) { + case 0, + CloudflareOriginCARequestValidity7, + CloudflareOriginCARequestValidity30, + CloudflareOriginCARequestValidity90, + CloudflareOriginCARequestValidity365, + CloudflareOriginCARequestValidity730, + CloudflareOriginCARequestValidity1095, + CloudflareOriginCARequestValidity5475: + *v = CloudflareOriginCARequestValidity(value) + default: + return E.New("unsupported Cloudflare Origin CA requested validity: ", value) + } + return nil +} diff --git a/option/outbound.go b/option/outbound.go index 59ee85ab69..aa32db8148 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -1,104 +1,61 @@ package option import ( + "context" + C "github.com/sagernet/sing-box/constant" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/service" ) +type OutboundOptionsRegistry interface { + CreateOptions(outboundType string) (any, bool) +} + type _Outbound struct { - Type string `json:"type"` - Tag string `json:"tag,omitempty"` - DirectOptions DirectOutboundOptions `json:"-"` - SocksOptions SocksOutboundOptions `json:"-"` - HTTPOptions HTTPOutboundOptions `json:"-"` - ShadowsocksOptions ShadowsocksOutboundOptions `json:"-"` - VMessOptions VMessOutboundOptions `json:"-"` - TrojanOptions TrojanOutboundOptions `json:"-"` - WireGuardOptions WireGuardOutboundOptions `json:"-"` - HysteriaOptions HysteriaOutboundOptions `json:"-"` - TorOptions TorOutboundOptions `json:"-"` - SSHOptions SSHOutboundOptions `json:"-"` - ShadowTLSOptions ShadowTLSOutboundOptions `json:"-"` - ShadowsocksROptions ShadowsocksROutboundOptions `json:"-"` - VLESSOptions VLESSOutboundOptions `json:"-"` - TUICOptions TUICOutboundOptions `json:"-"` - Hysteria2Options Hysteria2OutboundOptions `json:"-"` - SelectorOptions SelectorOutboundOptions `json:"-"` - URLTestOptions URLTestOutboundOptions `json:"-"` + Type string `json:"type"` + Tag string `json:"tag,omitempty"` + Options any `json:"-"` } type Outbound _Outbound -func (h *Outbound) RawOptions() (any, error) { - var rawOptionsPtr any - switch h.Type { - case C.TypeDirect: - rawOptionsPtr = &h.DirectOptions - case C.TypeBlock, C.TypeDNS: - rawOptionsPtr = nil - case C.TypeSOCKS: - rawOptionsPtr = &h.SocksOptions - case C.TypeHTTP: - rawOptionsPtr = &h.HTTPOptions - case C.TypeShadowsocks: - rawOptionsPtr = &h.ShadowsocksOptions - case C.TypeVMess: - rawOptionsPtr = &h.VMessOptions - case C.TypeTrojan: - rawOptionsPtr = &h.TrojanOptions - case C.TypeWireGuard: - rawOptionsPtr = &h.WireGuardOptions - case C.TypeHysteria: - rawOptionsPtr = &h.HysteriaOptions - case C.TypeTor: - rawOptionsPtr = &h.TorOptions - case C.TypeSSH: - rawOptionsPtr = &h.SSHOptions - case C.TypeShadowTLS: - rawOptionsPtr = &h.ShadowTLSOptions - case C.TypeShadowsocksR: - rawOptionsPtr = &h.ShadowsocksROptions - case C.TypeVLESS: - rawOptionsPtr = &h.VLESSOptions - case C.TypeTUIC: - rawOptionsPtr = &h.TUICOptions - case C.TypeHysteria2: - rawOptionsPtr = &h.Hysteria2Options - case C.TypeSelector: - rawOptionsPtr = &h.SelectorOptions - case C.TypeURLTest: - rawOptionsPtr = &h.URLTestOptions - case "": - return nil, E.New("missing outbound type") - default: - return nil, E.New("unknown outbound type: ", h.Type) - } - return rawOptionsPtr, nil -} - -func (h *Outbound) MarshalJSON() ([]byte, error) { - rawOptions, err := h.RawOptions() - if err != nil { - return nil, err - } - return MarshallObjects((*_Outbound)(h), rawOptions) +func (h *Outbound) MarshalJSONContext(ctx context.Context) ([]byte, error) { + return badjson.MarshallObjectsContext(ctx, (*_Outbound)(h), h.Options) } -func (h *Outbound) UnmarshalJSON(bytes []byte) error { - err := json.Unmarshal(bytes, (*_Outbound)(h)) +func (h *Outbound) UnmarshalJSONContext(ctx context.Context, content []byte) error { + err := json.UnmarshalContext(ctx, content, (*_Outbound)(h)) if err != nil { return err } - rawOptions, err := h.RawOptions() - if err != nil { - return err + registry := service.FromContext[OutboundOptionsRegistry](ctx) + if registry == nil { + return E.New("missing outbound options registry in context") + } + switch h.Type { + case C.TypeDNS: + return E.New("dns outbound is deprecated in sing-box 1.11.0 and removed in sing-box 1.13.0, use rule actions instead") + } + options, loaded := registry.CreateOptions(h.Type) + if !loaded { + return E.New("unknown outbound type: ", h.Type) } - err = UnmarshallExcluded(bytes, (*_Outbound)(h), rawOptions) + err = badjson.UnmarshallExcludedContext(ctx, content, (*_Outbound)(h), options) if err != nil { return err } + if listenWrapper, isListen := options.(ListenOptionsWrapper); isListen { + //nolint:staticcheck + if listenWrapper.TakeListenOptions().InboundOptions != (InboundOptions{}) { + return E.New("legacy inbound fields are deprecated in sing-box 1.11.0 and removed in sing-box 1.13.0, use rule actions instead") + } + } + h.Options = options return nil } @@ -108,21 +65,75 @@ type DialerOptionsWrapper interface { } type DialerOptions struct { - Detour string `json:"detour,omitempty"` - BindInterface string `json:"bind_interface,omitempty"` - Inet4BindAddress *ListenAddress `json:"inet4_bind_address,omitempty"` - Inet6BindAddress *ListenAddress `json:"inet6_bind_address,omitempty"` - ProtectPath string `json:"protect_path,omitempty"` - RoutingMark int `json:"routing_mark,omitempty"` - ReuseAddr bool `json:"reuse_addr,omitempty"` - ConnectTimeout Duration `json:"connect_timeout,omitempty"` - TCPFastOpen bool `json:"tcp_fast_open,omitempty"` - TCPMultiPath bool `json:"tcp_multi_path,omitempty"` - UDPFragment *bool `json:"udp_fragment,omitempty"` - UDPFragmentDefault bool `json:"-"` - DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` - FallbackDelay Duration `json:"fallback_delay,omitempty"` - IsWireGuardListener bool `json:"-"` + Detour string `json:"detour,omitempty"` + BindInterface string `json:"bind_interface,omitempty"` + Inet4BindAddress *badoption.Addr `json:"inet4_bind_address,omitempty"` + Inet6BindAddress *badoption.Addr `json:"inet6_bind_address,omitempty"` + BindAddressNoPort bool `json:"bind_address_no_port,omitempty"` + ProtectPath string `json:"protect_path,omitempty"` + RoutingMark FwMark `json:"routing_mark,omitempty"` + ReuseAddr bool `json:"reuse_addr,omitempty"` + NetNs string `json:"netns,omitempty"` + ConnectTimeout badoption.Duration `json:"connect_timeout,omitempty"` + TCPFastOpen bool `json:"tcp_fast_open,omitempty"` + TCPMultiPath bool `json:"tcp_multi_path,omitempty"` + DisableTCPKeepAlive bool `json:"disable_tcp_keep_alive,omitempty"` + TCPKeepAlive badoption.Duration `json:"tcp_keep_alive,omitempty"` + TCPKeepAliveInterval badoption.Duration `json:"tcp_keep_alive_interval,omitempty"` + UDPFragment *bool `json:"udp_fragment,omitempty"` + UDPFragmentDefault bool `json:"-"` + DomainResolver *DomainResolveOptions `json:"domain_resolver,omitempty"` + NetworkStrategy *NetworkStrategy `json:"network_strategy,omitempty"` + NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` + FallbackNetworkType badoption.Listable[InterfaceType] `json:"fallback_network_type,omitempty"` + FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"` + + // Deprecated: migrated to domain resolver + DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` +} + +type _DomainResolveOptions struct { + Server string `json:"server"` + Timeout badoption.Duration `json:"timeout,omitempty"` + Strategy DomainStrategy `json:"strategy,omitempty"` + DisableCache bool `json:"disable_cache,omitempty"` + DisableOptimisticCache bool `json:"disable_optimistic_cache,omitempty"` + RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` + ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` +} + +type DomainResolveOptions _DomainResolveOptions + +func (o DomainResolveOptions) MarshalJSON() ([]byte, error) { + if o.Server == "" { + return []byte("{}"), nil + } else if o.Strategy == DomainStrategy(C.DomainStrategyAsIS) && + o.Timeout == 0 && + !o.DisableCache && + !o.DisableOptimisticCache && + o.RewriteTTL == nil && + o.ClientSubnet == nil { + return json.Marshal(o.Server) + } else { + return json.Marshal((_DomainResolveOptions)(o)) + } +} + +func (o *DomainResolveOptions) UnmarshalJSON(bytes []byte) error { + var stringValue string + err := json.Unmarshal(bytes, &stringValue) + if err == nil { + o.Server = stringValue + return nil + } + err = json.Unmarshal(bytes, (*_DomainResolveOptions)(o)) + if err != nil { + return err + } + if o.Server == "" { + return E.New("empty domain_resolver.server") + } + return nil } func (o *DialerOptions) TakeDialerOptions() DialerOptions { @@ -147,6 +158,10 @@ func (o ServerOptions) Build() M.Socksaddr { return M.ParseSocksaddrHostPort(o.Server, o.ServerPort) } +func (o ServerOptions) ServerIsDomain() bool { + return o.Build().IsDomain() +} + func (o *ServerOptions) TakeServerOptions() ServerOptions { return *o } diff --git a/option/platform.go b/option/platform.go index a43cbf230f..e4ecd6fa1e 100644 --- a/option/platform.go +++ b/option/platform.go @@ -3,6 +3,7 @@ package option import ( E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badoption" ) type OnDemandOptions struct { @@ -12,10 +13,10 @@ type OnDemandOptions struct { type OnDemandRule struct { Action *OnDemandRuleAction `json:"action,omitempty"` - DNSSearchDomainMatch Listable[string] `json:"dns_search_domain_match,omitempty"` - DNSServerAddressMatch Listable[string] `json:"dns_server_address_match,omitempty"` + DNSSearchDomainMatch badoption.Listable[string] `json:"dns_search_domain_match,omitempty"` + DNSServerAddressMatch badoption.Listable[string] `json:"dns_server_address_match,omitempty"` InterfaceTypeMatch *OnDemandRuleInterfaceType `json:"interface_type_match,omitempty"` - SSIDMatch Listable[string] `json:"ssid_match,omitempty"` + SSIDMatch badoption.Listable[string] `json:"ssid_match,omitempty"` ProbeURL string `json:"probe_url,omitempty"` } diff --git a/option/psiphon.go b/option/psiphon.go new file mode 100644 index 0000000000..16a1aa368e --- /dev/null +++ b/option/psiphon.go @@ -0,0 +1,22 @@ +package option + +import "github.com/sagernet/sing/common/json/badoption" + +type PsiphonOutboundOptions struct { + Network NetworkList `json:"network,omitempty"` + DialerOptions + + DataDirectory string `json:"data_directory,omitempty"` + EgressRegion string `json:"egress_region,omitempty"` + PropagationChannelID string `json:"propagation_channel_id,omitempty"` + SponsorID string `json:"sponsor_id,omitempty"` + NetworkID string `json:"network_id,omitempty"` + ClientPlatform string `json:"client_platform,omitempty"` + ClientVersion string `json:"client_version,omitempty"` + RemoteServerListURL string `json:"remote_server_list_url,omitempty"` + RemoteServerListDownloadFilename string `json:"remote_server_list_download_filename,omitempty"` + RemoteServerListSignaturePublicKey string `json:"remote_server_list_signature_public_key,omitempty"` + UpstreamProxyURL string `json:"upstream_proxy_url,omitempty"` + AllowDefaultDNSResolverWithBindToDevice *bool `json:"allow_default_dns_resolver_with_bind_to_device,omitempty"` + EstablishTunnelTimeout badoption.Duration `json:"establish_tunnel_timeout,omitempty"` +} diff --git a/option/resolved.go b/option/resolved.go new file mode 100644 index 0000000000..cb9f579d1f --- /dev/null +++ b/option/resolved.go @@ -0,0 +1,49 @@ +package option + +import ( + "context" + "net/netip" + + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badoption" +) + +type _ResolvedServiceOptions struct { + ListenOptions +} + +type ResolvedServiceOptions _ResolvedServiceOptions + +func (r ResolvedServiceOptions) MarshalJSONContext(ctx context.Context) ([]byte, error) { + if r.Listen != nil && netip.Addr(*r.Listen) == (netip.AddrFrom4([4]byte{127, 0, 0, 53})) { + r.Listen = nil + } + if r.ListenPort == 53 { + r.ListenPort = 0 + } + return json.MarshalContext(ctx, (*_ResolvedServiceOptions)(&r)) +} + +func (r *ResolvedServiceOptions) UnmarshalJSONContext(ctx context.Context, bytes []byte) error { + err := json.UnmarshalContextDisallowUnknownFields(ctx, bytes, (*_ResolvedServiceOptions)(r)) + if err != nil { + return err + } + if r.Listen == nil { + r.Listen = (*badoption.Addr)(common.Ptr(netip.AddrFrom4([4]byte{127, 0, 0, 53}))) + } + if r.ListenPort == 0 { + r.ListenPort = 53 + } + return nil +} + +type ResolvedDNSServerOptions struct { + Service string `json:"service"` + AcceptDefaultResolvers bool `json:"accept_default_resolvers,omitempty"` + // NDots int `json:"ndots,omitempty"` + // Timeout badoption.Duration `json:"timeout,omitempty"` + // Attempts int `json:"attempts,omitempty"` + // Rotate bool `json:"rotate,omitempty"` +} diff --git a/option/route.go b/option/route.go index e313fcf242..893be8ece2 100644 --- a/option/route.go +++ b/option/route.go @@ -1,16 +1,26 @@ package option +import "github.com/sagernet/sing/common/json/badoption" + type RouteOptions struct { - GeoIP *GeoIPOptions `json:"geoip,omitempty"` - Geosite *GeositeOptions `json:"geosite,omitempty"` - Rules []Rule `json:"rules,omitempty"` - RuleSet []RuleSet `json:"rule_set,omitempty"` - Final string `json:"final,omitempty"` - FindProcess bool `json:"find_process,omitempty"` - AutoDetectInterface bool `json:"auto_detect_interface,omitempty"` - OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"` - DefaultInterface string `json:"default_interface,omitempty"` - DefaultMark int `json:"default_mark,omitempty"` + GeoIP *GeoIPOptions `json:"geoip,omitempty"` + Geosite *GeositeOptions `json:"geosite,omitempty"` + Rules []Rule `json:"rules,omitempty"` + RuleSet []RuleSet `json:"rule_set,omitempty"` + Final string `json:"final,omitempty"` + FindProcess bool `json:"find_process,omitempty"` + FindNeighbor bool `json:"find_neighbor,omitempty"` + DHCPLeaseFiles badoption.Listable[string] `json:"dhcp_lease_files,omitempty"` + AutoDetectInterface bool `json:"auto_detect_interface,omitempty"` + OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"` + DefaultInterface string `json:"default_interface,omitempty"` + DefaultMark FwMark `json:"default_mark,omitempty"` + DefaultDomainResolver *DomainResolveOptions `json:"default_domain_resolver,omitempty"` + DefaultNetworkStrategy *NetworkStrategy `json:"default_network_strategy,omitempty"` + DefaultNetworkType badoption.Listable[InterfaceType] `json:"default_network_type,omitempty"` + DefaultFallbackNetworkType badoption.Listable[InterfaceType] `json:"default_fallback_network_type,omitempty"` + DefaultFallbackDelay badoption.Duration `json:"default_fallback_delay,omitempty"` + DefaultHTTPClient string `json:"default_http_client,omitempty"` } type GeoIPOptions struct { diff --git a/option/rule.go b/option/rule.go index 0ea133c75f..b83d2335df 100644 --- a/option/rule.go +++ b/option/rule.go @@ -1,12 +1,15 @@ package option import ( + "context" "reflect" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" ) type _Rule struct { @@ -28,29 +31,27 @@ func (r Rule) MarshalJSON() ([]byte, error) { default: return nil, E.New("unknown rule type: " + r.Type) } - return MarshallObjects((_Rule)(r), v) + return badjson.MarshallObjects((_Rule)(r), v) } -func (r *Rule) UnmarshalJSON(bytes []byte) error { - err := json.Unmarshal(bytes, (*_Rule)(r)) +func (r *Rule) UnmarshalJSONContext(ctx context.Context, bytes []byte) error { + err := json.UnmarshalContext(ctx, bytes, (*_Rule)(r)) + if err != nil { + return err + } + payload, err := rulePayloadWithoutType(ctx, bytes) if err != nil { return err } - var v any switch r.Type { case "", C.RuleTypeDefault: r.Type = C.RuleTypeDefault - v = &r.DefaultOptions + return unmarshalDefaultRuleContext(ctx, payload, &r.DefaultOptions) case C.RuleTypeLogical: - v = &r.LogicalOptions + return unmarshalLogicalRuleContext(ctx, payload, &r.LogicalOptions) default: return E.New("unknown rule type: " + r.Type) } - err = UnmarshallExcluded(bytes, (*_Rule)(r), v) - if err != nil { - return err - } - return nil } func (r Rule) IsValid() bool { @@ -64,55 +65,163 @@ func (r Rule) IsValid() bool { } } +type RawDefaultRule struct { + Inbound badoption.Listable[string] `json:"inbound,omitempty"` + IPVersion int `json:"ip_version,omitempty"` + Network badoption.Listable[string] `json:"network,omitempty"` + AuthUser badoption.Listable[string] `json:"auth_user,omitempty"` + Protocol badoption.Listable[string] `json:"protocol,omitempty"` + Client badoption.Listable[string] `json:"client,omitempty"` + Domain badoption.Listable[string] `json:"domain,omitempty"` + DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` + Geosite badoption.Listable[string] `json:"geosite,omitempty"` + SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"` + GeoIP badoption.Listable[string] `json:"geoip,omitempty"` + SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` + SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` + IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` + IPIsPrivate bool `json:"ip_is_private,omitempty"` + SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` + Port badoption.Listable[uint16] `json:"port,omitempty"` + PortRange badoption.Listable[string] `json:"port_range,omitempty"` + ProcessName badoption.Listable[string] `json:"process_name,omitempty"` + ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` + ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` + PackageName badoption.Listable[string] `json:"package_name,omitempty"` + PackageNameRegex badoption.Listable[string] `json:"package_name_regex,omitempty"` + User badoption.Listable[string] `json:"user,omitempty"` + UserID badoption.Listable[int32] `json:"user_id,omitempty"` + ClashMode string `json:"clash_mode,omitempty"` + NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` + NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` + NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` + WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` + InterfaceAddress *badjson.TypedMap[string, badoption.Listable[*badoption.Prefixable]] `json:"interface_address,omitempty"` + NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[*badoption.Prefixable]] `json:"network_interface_address,omitempty"` + DefaultInterfaceAddress badoption.Listable[*badoption.Prefixable] `json:"default_interface_address,omitempty"` + SourceMACAddress badoption.Listable[string] `json:"source_mac_address,omitempty"` + SourceHostname badoption.Listable[string] `json:"source_hostname,omitempty"` + PreferredBy badoption.Listable[string] `json:"preferred_by,omitempty"` + RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` + RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` + Invert bool `json:"invert,omitempty"` + + // Deprecated: renamed to rule_set_ip_cidr_match_source + Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` + + TunnelSource badoption.Listable[string] `json:"tunnel_source,omitempty"` //E + TunnelDestination badoption.Listable[string] `json:"tunnel_destination,omitempty"` //E + +} + type DefaultRule struct { - Inbound Listable[string] `json:"inbound,omitempty"` - IPVersion int `json:"ip_version,omitempty"` - Network Listable[string] `json:"network,omitempty"` - AuthUser Listable[string] `json:"auth_user,omitempty"` - Protocol Listable[string] `json:"protocol,omitempty"` - Domain Listable[string] `json:"domain,omitempty"` - DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex Listable[string] `json:"domain_regex,omitempty"` - Geosite Listable[string] `json:"geosite,omitempty"` - SourceGeoIP Listable[string] `json:"source_geoip,omitempty"` - GeoIP Listable[string] `json:"geoip,omitempty"` - SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` - SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` - IPCIDR Listable[string] `json:"ip_cidr,omitempty"` - IPIsPrivate bool `json:"ip_is_private,omitempty"` - SourcePort Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange Listable[string] `json:"source_port_range,omitempty"` - Port Listable[uint16] `json:"port,omitempty"` - PortRange Listable[string] `json:"port_range,omitempty"` - ProcessName Listable[string] `json:"process_name,omitempty"` - ProcessPath Listable[string] `json:"process_path,omitempty"` - PackageName Listable[string] `json:"package_name,omitempty"` - User Listable[string] `json:"user,omitempty"` - UserID Listable[int32] `json:"user_id,omitempty"` - ClashMode string `json:"clash_mode,omitempty"` - WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"` - RuleSet Listable[string] `json:"rule_set,omitempty"` - RuleSetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` - Invert bool `json:"invert,omitempty"` - Outbound string `json:"outbound,omitempty"` + RawDefaultRule + RuleAction +} + +func (r DefaultRule) MarshalJSON() ([]byte, error) { + return badjson.MarshallObjects(r.RawDefaultRule, r.RuleAction) +} + +func (r *DefaultRule) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, &r.RawDefaultRule) + if err != nil { + return err + } + return badjson.UnmarshallExcluded(data, &r.RawDefaultRule, &r.RuleAction) } func (r DefaultRule) IsValid() bool { var defaultValue DefaultRule defaultValue.Invert = r.Invert - defaultValue.Outbound = r.Outbound return !reflect.DeepEqual(r, defaultValue) } +type RawLogicalRule struct { + Mode string `json:"mode"` + Rules []Rule `json:"rules,omitempty"` + Invert bool `json:"invert,omitempty"` +} + type LogicalRule struct { - Mode string `json:"mode"` - Rules []Rule `json:"rules,omitempty"` - Invert bool `json:"invert,omitempty"` - Outbound string `json:"outbound,omitempty"` + RawLogicalRule + RuleAction +} + +func (r LogicalRule) MarshalJSON() ([]byte, error) { + return badjson.MarshallObjects(r.RawLogicalRule, r.RuleAction) +} + +func (r *LogicalRule) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, &r.RawLogicalRule) + if err != nil { + return err + } + return badjson.UnmarshallExcluded(data, &r.RawLogicalRule, &r.RuleAction) +} + +func rulePayloadWithoutType(ctx context.Context, data []byte) ([]byte, error) { + var content badjson.JSONObject + err := content.UnmarshalJSONContext(ctx, data) + if err != nil { + return nil, err + } + content.Remove("type") + return content.MarshalJSONContext(ctx) +} + +func unmarshalDefaultRuleContext(ctx context.Context, data []byte, rule *DefaultRule) error { + rawAction, routeOptions, err := inspectRouteRuleAction(ctx, data) + if err != nil { + return err + } + err = rejectNestedRouteRuleAction(ctx, data) + if err != nil { + return err + } + depth := nestedRuleDepth(ctx) + err = json.UnmarshalContext(ctx, data, &rule.RawDefaultRule) + if err != nil { + return err + } + err = badjson.UnmarshallExcludedContext(ctx, data, &rule.RawDefaultRule, &rule.RuleAction) + if err != nil { + return err + } + if depth > 0 && rawAction == "" && routeOptions == (RouteActionOptions{}) { + rule.RuleAction = RuleAction{} + } + return nil +} + +func unmarshalLogicalRuleContext(ctx context.Context, data []byte, rule *LogicalRule) error { + rawAction, routeOptions, err := inspectRouteRuleAction(ctx, data) + if err != nil { + return err + } + err = rejectNestedRouteRuleAction(ctx, data) + if err != nil { + return err + } + depth := nestedRuleDepth(ctx) + err = json.UnmarshalContext(nestedRuleChildContext(ctx), data, &rule.RawLogicalRule) + if err != nil { + return err + } + err = badjson.UnmarshallExcludedContext(ctx, data, &rule.RawLogicalRule, &rule.RuleAction) + if err != nil { + return err + } + if depth > 0 && rawAction == "" && routeOptions == (RouteActionOptions{}) { + rule.RuleAction = RuleAction{} + } + return nil } -func (r LogicalRule) IsValid() bool { +func (r *LogicalRule) IsValid() bool { return len(r.Rules) > 0 && common.All(r.Rules, Rule.IsValid) } diff --git a/option/rule_action.go b/option/rule_action.go new file mode 100644 index 0000000000..a8ec576be7 --- /dev/null +++ b/option/rule_action.go @@ -0,0 +1,342 @@ +package option + +import ( + "context" + "fmt" + "net/netip" + "time" + + C "github.com/sagernet/sing-box/constant" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" +) + +type _RuleAction struct { + Action string `json:"action,omitempty"` + RouteOptions RouteActionOptions `json:"-"` + RouteOptionsOptions RouteOptionsActionOptions `json:"-"` + DirectOptions DirectActionOptions `json:"-"` + BypassOptions RouteActionOptions `json:"-"` + RejectOptions RejectActionOptions `json:"-"` + SniffOptions RouteActionSniff `json:"-"` + ResolveOptions RouteActionResolve `json:"-"` +} + +type RuleAction _RuleAction + +func (r RuleAction) MarshalJSON() ([]byte, error) { + if r.Action == "" { + return json.Marshal(struct{}{}) + } + var v any + switch r.Action { + case C.RuleActionTypeRoute: + r.Action = "" + v = r.RouteOptions + case C.RuleActionTypeRouteOptions: + v = r.RouteOptionsOptions + case C.RuleActionTypeDirect: + v = r.DirectOptions + case C.RuleActionTypeBypass: + v = r.BypassOptions + case C.RuleActionTypeReject: + v = r.RejectOptions + case C.RuleActionTypeHijackDNS: + v = nil + case C.RuleActionTypeSniff: + v = r.SniffOptions + case C.RuleActionTypeResolve: + v = r.ResolveOptions + default: + return nil, E.New("unknown rule action: " + r.Action) + } + if v == nil { + return badjson.MarshallObjects((_RuleAction)(r)) + } + return badjson.MarshallObjects((_RuleAction)(r), v) +} + +func (r *RuleAction) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, (*_RuleAction)(r)) + if err != nil { + return err + } + var v any + switch r.Action { + case "", C.RuleActionTypeRoute: + r.Action = C.RuleActionTypeRoute + v = &r.RouteOptions + case C.RuleActionTypeRouteOptions: + v = &r.RouteOptionsOptions + case C.RuleActionTypeDirect: + v = &r.DirectOptions + case C.RuleActionTypeBypass: + v = &r.BypassOptions + case C.RuleActionTypeReject: + v = &r.RejectOptions + case C.RuleActionTypeHijackDNS: + v = nil + case C.RuleActionTypeSniff: + v = &r.SniffOptions + case C.RuleActionTypeResolve: + v = &r.ResolveOptions + default: + return E.New("unknown rule action: " + r.Action) + } + if v == nil { + // check unknown fields + return json.UnmarshalDisallowUnknownFields(data, &_RuleAction{}) + } + err = badjson.UnmarshallExcluded(data, (*_RuleAction)(r), v) + if err != nil { + return err + } + return nil +} + +type _DNSRuleAction struct { + Action string `json:"action,omitempty"` + RouteOptions DNSRouteActionOptions `json:"-"` + RouteOptionsOptions DNSRouteOptionsActionOptions `json:"-"` + RejectOptions RejectActionOptions `json:"-"` + PredefinedOptions DNSRouteActionPredefined `json:"-"` +} + +type DNSRuleAction _DNSRuleAction + +func (r DNSRuleAction) MarshalJSON() ([]byte, error) { + if r.Action == "" { + return json.Marshal(struct{}{}) + } + var v any + switch r.Action { + case C.RuleActionTypeRoute: + r.Action = "" + v = r.RouteOptions + case C.RuleActionTypeEvaluate: + v = r.RouteOptions + case C.RuleActionTypeRespond: + v = nil + case C.RuleActionTypeRouteOptions: + v = r.RouteOptionsOptions + case C.RuleActionTypeReject: + v = r.RejectOptions + case C.RuleActionTypePredefined: + v = r.PredefinedOptions + default: + return nil, E.New("unknown DNS rule action: " + r.Action) + } + if v == nil { + return badjson.MarshallObjects((_DNSRuleAction)(r)) + } + return badjson.MarshallObjects((_DNSRuleAction)(r), v) +} + +func (r *DNSRuleAction) UnmarshalJSONContext(ctx context.Context, data []byte) error { + err := json.Unmarshal(data, (*_DNSRuleAction)(r)) + if err != nil { + return err + } + var v any + switch r.Action { + case "", C.RuleActionTypeRoute: + r.Action = C.RuleActionTypeRoute + v = &r.RouteOptions + case C.RuleActionTypeEvaluate: + v = &r.RouteOptions + case C.RuleActionTypeRespond: + v = nil + case C.RuleActionTypeRouteOptions: + v = &r.RouteOptionsOptions + case C.RuleActionTypeReject: + v = &r.RejectOptions + case C.RuleActionTypePredefined: + v = &r.PredefinedOptions + default: + return E.New("unknown DNS rule action: " + r.Action) + } + if v == nil { + return json.UnmarshalDisallowUnknownFields(data, &_DNSRuleAction{}) + } + return badjson.UnmarshallExcludedContext(ctx, data, (*_DNSRuleAction)(r), v) +} + +type RouteActionOptions struct { + Outbound string `json:"outbound,omitempty"` + RawRouteOptionsActionOptions +} + +type RawRouteOptionsActionOptions struct { + OverrideAddress string `json:"override_address,omitempty"` + OverridePort uint16 `json:"override_port,omitempty"` + OverrideTunnelDestination string `json:"override_tunnel_destination,omitempty"` + + NetworkStrategy *NetworkStrategy `json:"network_strategy,omitempty"` + FallbackDelay uint32 `json:"fallback_delay,omitempty"` + + UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` + UDPConnect bool `json:"udp_connect,omitempty"` + UDPTimeout badoption.Duration `json:"udp_timeout,omitempty"` + + TLSFragment bool `json:"tls_fragment,omitempty"` + TLSFragmentFallbackDelay badoption.Duration `json:"tls_fragment_fallback_delay,omitempty"` + TLSRecordFragment bool `json:"tls_record_fragment,omitempty"` +} + +type RouteOptionsActionOptions RawRouteOptionsActionOptions + +func (r *RouteOptionsActionOptions) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, (*RawRouteOptionsActionOptions)(r)) + if err != nil { + return err + } + if *r == (RouteOptionsActionOptions{}) { + return E.New("empty route option action") + } + if r.TLSFragment && r.TLSRecordFragment { + return E.New("`tls_fragment` and `tls_record_fragment` are mutually exclusive") + } + return nil +} + +type DNSRouteActionOptions struct { + Server string `json:"server,omitempty"` + Strategy DomainStrategy `json:"strategy,omitempty"` + DisableCache bool `json:"disable_cache,omitempty"` + DisableOptimisticCache bool `json:"disable_optimistic_cache,omitempty"` + RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` + ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` + + BypassIfFailed bool `json:"bypass_if_failed,omitempty"` //H +} + +type _DNSRouteOptionsActionOptions struct { + Strategy DomainStrategy `json:"strategy,omitempty"` + DisableCache bool `json:"disable_cache,omitempty"` + DisableOptimisticCache bool `json:"disable_optimistic_cache,omitempty"` + RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` + ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` +} + +type DNSRouteOptionsActionOptions _DNSRouteOptionsActionOptions + +func (r *DNSRouteOptionsActionOptions) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, (*_DNSRouteOptionsActionOptions)(r)) + if err != nil { + return err + } + if *r == (DNSRouteOptionsActionOptions{}) { + return E.New("empty DNS route option action") + } + return nil +} + +type _DirectActionOptions DialerOptions + +type DirectActionOptions _DirectActionOptions + +func (d DirectActionOptions) Descriptions() []string { + var descriptions []string + if d.BindInterface != "" { + descriptions = append(descriptions, "bind_interface="+d.BindInterface) + } + if d.Inet4BindAddress != nil { + descriptions = append(descriptions, "inet4_bind_address="+d.Inet4BindAddress.Build(netip.IPv4Unspecified()).String()) + } + if d.Inet6BindAddress != nil { + descriptions = append(descriptions, "inet6_bind_address="+d.Inet6BindAddress.Build(netip.IPv6Unspecified()).String()) + } + if d.RoutingMark != 0 { + descriptions = append(descriptions, "routing_mark="+fmt.Sprintf("0x%x", d.RoutingMark)) + } + if d.ReuseAddr { + descriptions = append(descriptions, "reuse_addr") + } + if d.ConnectTimeout != 0 { + descriptions = append(descriptions, "connect_timeout="+time.Duration(d.ConnectTimeout).String()) + } + if d.TCPFastOpen { + descriptions = append(descriptions, "tcp_fast_open") + } + if d.TCPMultiPath { + descriptions = append(descriptions, "tcp_multi_path") + } + if d.UDPFragment != nil { + descriptions = append(descriptions, "udp_fragment="+fmt.Sprint(*d.UDPFragment)) + } + if d.DomainStrategy != DomainStrategy(C.DomainStrategyAsIS) { + descriptions = append(descriptions, "domain_strategy="+d.DomainStrategy.String()) + } + if d.FallbackDelay != 0 { + descriptions = append(descriptions, "fallback_delay="+time.Duration(d.FallbackDelay).String()) + } + return descriptions +} + +func (d *DirectActionOptions) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, (*_DirectActionOptions)(d)) + if err != nil { + return err + } + if d.Detour != "" { + return E.New("detour is not available in the current context") + } + return nil +} + +type _RejectActionOptions struct { + Method string `json:"method,omitempty"` + NoDrop bool `json:"no_drop,omitempty"` +} + +type RejectActionOptions _RejectActionOptions + +func (r RejectActionOptions) MarshalJSON() ([]byte, error) { + switch r.Method { + case C.RuleActionRejectMethodDefault: + r.Method = "" + } + return json.Marshal((_RejectActionOptions)(r)) +} + +func (r *RejectActionOptions) UnmarshalJSON(bytes []byte) error { + err := json.Unmarshal(bytes, (*_RejectActionOptions)(r)) + if err != nil { + return err + } + switch r.Method { + case "", C.RuleActionRejectMethodDefault: + r.Method = C.RuleActionRejectMethodDefault + case C.RuleActionRejectMethodDrop: + case C.RuleActionRejectMethodReply: + default: + return E.New("unknown reject method: " + r.Method) + } + if r.Method == C.RuleActionRejectMethodDrop && r.NoDrop { + return E.New("no_drop is not available in current context") + } + return nil +} + +type RouteActionSniff struct { + Sniffer badoption.Listable[string] `json:"sniffer,omitempty"` + Timeout badoption.Duration `json:"timeout,omitempty"` +} + +type RouteActionResolve struct { + Server string `json:"server,omitempty"` + Strategy DomainStrategy `json:"strategy,omitempty"` + DisableCache bool `json:"disable_cache,omitempty"` + DisableOptimisticCache bool `json:"disable_optimistic_cache,omitempty"` + RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` + ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"` +} + +type DNSRouteActionPredefined struct { + Rcode *DNSRCode `json:"rcode,omitempty"` + Answer badoption.Listable[DNSRecordOptions] `json:"answer,omitempty"` + Ns badoption.Listable[DNSRecordOptions] `json:"ns,omitempty"` + Extra badoption.Listable[DNSRecordOptions] `json:"extra,omitempty"` +} diff --git a/option/rule_action_test.go b/option/rule_action_test.go new file mode 100644 index 0000000000..0007cd36ed --- /dev/null +++ b/option/rule_action_test.go @@ -0,0 +1,29 @@ +package option + +import ( + "context" + "testing" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common/json" + + "github.com/stretchr/testify/require" +) + +func TestDNSRuleActionRespondUnmarshalJSON(t *testing.T) { + t.Parallel() + + var action DNSRuleAction + err := json.UnmarshalContext(context.Background(), []byte(`{"action":"respond"}`), &action) + require.NoError(t, err) + require.Equal(t, C.RuleActionTypeRespond, action.Action) + require.Equal(t, DNSRouteActionOptions{}, action.RouteOptions) +} + +func TestDNSRuleActionRespondRejectsUnknownFields(t *testing.T) { + t.Parallel() + + var action DNSRuleAction + err := json.UnmarshalContext(context.Background(), []byte(`{"action":"respond","disable_cache":true}`), &action) + require.ErrorContains(t, err, "unknown field") +} diff --git a/option/rule_dns.go b/option/rule_dns.go index c5994e1cec..4834035315 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -1,12 +1,15 @@ package option import ( + "context" "reflect" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" ) type _DNSRule struct { @@ -28,11 +31,11 @@ func (r DNSRule) MarshalJSON() ([]byte, error) { default: return nil, E.New("unknown rule type: " + r.Type) } - return MarshallObjects((_DNSRule)(r), v) + return badjson.MarshallObjects((_DNSRule)(r), v) } -func (r *DNSRule) UnmarshalJSON(bytes []byte) error { - err := json.Unmarshal(bytes, (*_DNSRule)(r)) +func (r *DNSRule) UnmarshalJSONContext(ctx context.Context, bytes []byte) error { + err := json.UnmarshalContext(ctx, bytes, (*_DNSRule)(r)) if err != nil { return err } @@ -46,7 +49,7 @@ func (r *DNSRule) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown rule type: " + r.Type) } - err = UnmarshallExcluded(bytes, (*_DNSRule)(r), v) + err = badjson.UnmarshallExcludedContext(ctx, bytes, (*_DNSRule)(r), v) if err != nil { return err } @@ -64,66 +67,145 @@ func (r DNSRule) IsValid() bool { } } +type RawDefaultDNSRule struct { + Inbound badoption.Listable[string] `json:"inbound,omitempty"` + IPVersion int `json:"ip_version,omitempty"` + QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` + Network badoption.Listable[string] `json:"network,omitempty"` + AuthUser badoption.Listable[string] `json:"auth_user,omitempty"` + Protocol badoption.Listable[string] `json:"protocol,omitempty"` + Domain badoption.Listable[string] `json:"domain,omitempty"` + DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` + SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` + SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` + SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` + Port badoption.Listable[uint16] `json:"port,omitempty"` + PortRange badoption.Listable[string] `json:"port_range,omitempty"` + ProcessName badoption.Listable[string] `json:"process_name,omitempty"` + ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` + ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` + PackageName badoption.Listable[string] `json:"package_name,omitempty"` + PackageNameRegex badoption.Listable[string] `json:"package_name_regex,omitempty"` + User badoption.Listable[string] `json:"user,omitempty"` + UserID badoption.Listable[int32] `json:"user_id,omitempty"` + Outbound badoption.Listable[string] `json:"outbound,omitempty"` + ClashMode string `json:"clash_mode,omitempty"` + NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` + NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` + NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` + WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` + InterfaceAddress *badjson.TypedMap[string, badoption.Listable[*badoption.Prefixable]] `json:"interface_address,omitempty"` + NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[*badoption.Prefixable]] `json:"network_interface_address,omitempty"` + DefaultInterfaceAddress badoption.Listable[*badoption.Prefixable] `json:"default_interface_address,omitempty"` + SourceMACAddress badoption.Listable[string] `json:"source_mac_address,omitempty"` + SourceHostname badoption.Listable[string] `json:"source_hostname,omitempty"` + RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` + RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` + MatchResponse bool `json:"match_response,omitempty"` + IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` + IPIsPrivate bool `json:"ip_is_private,omitempty"` + IPAcceptAny bool `json:"ip_accept_any,omitempty"` + ResponseRcode *DNSRCode `json:"response_rcode,omitempty"` + ResponseAnswer badoption.Listable[DNSRecordOptions] `json:"response_answer,omitempty"` + ResponseNs badoption.Listable[DNSRecordOptions] `json:"response_ns,omitempty"` + ResponseExtra badoption.Listable[DNSRecordOptions] `json:"response_extra,omitempty"` + Invert bool `json:"invert,omitempty"` + + TunnelSource badoption.Listable[string] `json:"tunnel_source,omitempty"` //E + TunnelDestination badoption.Listable[string] `json:"tunnel_destination,omitempty"` //E + + // Deprecated: removed in sing-box 1.12.0 + Geosite badoption.Listable[string] `json:"geosite,omitempty"` + SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"` + GeoIP badoption.Listable[string] `json:"geoip,omitempty"` + // Deprecated: removed in sing-box 1.11.0 + RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"` + // Deprecated: renamed to rule_set_ip_cidr_match_source + Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` +} + type DefaultDNSRule struct { - Inbound Listable[string] `json:"inbound,omitempty"` - IPVersion int `json:"ip_version,omitempty"` - QueryType Listable[DNSQueryType] `json:"query_type,omitempty"` - Network Listable[string] `json:"network,omitempty"` - AuthUser Listable[string] `json:"auth_user,omitempty"` - Protocol Listable[string] `json:"protocol,omitempty"` - Domain Listable[string] `json:"domain,omitempty"` - DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex Listable[string] `json:"domain_regex,omitempty"` - Geosite Listable[string] `json:"geosite,omitempty"` - SourceGeoIP Listable[string] `json:"source_geoip,omitempty"` - GeoIP Listable[string] `json:"geoip,omitempty"` - IPCIDR Listable[string] `json:"ip_cidr,omitempty"` - IPIsPrivate bool `json:"ip_is_private,omitempty"` - SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` - SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` - SourcePort Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange Listable[string] `json:"source_port_range,omitempty"` - Port Listable[uint16] `json:"port,omitempty"` - PortRange Listable[string] `json:"port_range,omitempty"` - ProcessName Listable[string] `json:"process_name,omitempty"` - ProcessPath Listable[string] `json:"process_path,omitempty"` - PackageName Listable[string] `json:"package_name,omitempty"` - User Listable[string] `json:"user,omitempty"` - UserID Listable[int32] `json:"user_id,omitempty"` - Outbound Listable[string] `json:"outbound,omitempty"` - ClashMode string `json:"clash_mode,omitempty"` - WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"` - RuleSet Listable[string] `json:"rule_set,omitempty"` - RuleSetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` - Invert bool `json:"invert,omitempty"` - Server string `json:"server,omitempty"` - DisableCache bool `json:"disable_cache,omitempty"` - RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` - ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` + RawDefaultDNSRule + DNSRuleAction +} + +func (r DefaultDNSRule) MarshalJSON() ([]byte, error) { + return badjson.MarshallObjects(r.RawDefaultDNSRule, r.DNSRuleAction) +} + +func (r *DefaultDNSRule) UnmarshalJSONContext(ctx context.Context, data []byte) error { + rawAction, routeOptions, err := inspectDNSRuleAction(ctx, data) + if err != nil { + return err + } + err = rejectNestedDNSRuleAction(ctx, data) + if err != nil { + return err + } + depth := nestedRuleDepth(ctx) + err = json.UnmarshalContext(ctx, data, &r.RawDefaultDNSRule) + if err != nil { + return err + } + err = badjson.UnmarshallExcludedContext(ctx, data, &r.RawDefaultDNSRule, &r.DNSRuleAction) + if err != nil { + return err + } + if depth > 0 && rawAction == "" && routeOptions == (DNSRouteActionOptions{}) { + r.DNSRuleAction = DNSRuleAction{} + } + return nil } func (r DefaultDNSRule) IsValid() bool { var defaultValue DefaultDNSRule defaultValue.Invert = r.Invert - defaultValue.Server = r.Server - defaultValue.DisableCache = r.DisableCache - defaultValue.RewriteTTL = r.RewriteTTL - defaultValue.ClientSubnet = r.ClientSubnet return !reflect.DeepEqual(r, defaultValue) } +type RawLogicalDNSRule struct { + Mode string `json:"mode"` + Rules []DNSRule `json:"rules,omitempty"` + Invert bool `json:"invert,omitempty"` +} + type LogicalDNSRule struct { - Mode string `json:"mode"` - Rules []DNSRule `json:"rules,omitempty"` - Invert bool `json:"invert,omitempty"` - Server string `json:"server,omitempty"` - DisableCache bool `json:"disable_cache,omitempty"` - RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` - ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` + RawLogicalDNSRule + DNSRuleAction +} + +func (r LogicalDNSRule) MarshalJSON() ([]byte, error) { + return badjson.MarshallObjects(r.RawLogicalDNSRule, r.DNSRuleAction) +} + +func (r *LogicalDNSRule) UnmarshalJSONContext(ctx context.Context, data []byte) error { + rawAction, routeOptions, err := inspectDNSRuleAction(ctx, data) + if err != nil { + return err + } + err = rejectNestedDNSRuleAction(ctx, data) + if err != nil { + return err + } + depth := nestedRuleDepth(ctx) + err = json.UnmarshalContext(nestedRuleChildContext(ctx), data, &r.RawLogicalDNSRule) + if err != nil { + return err + } + err = badjson.UnmarshallExcludedContext(ctx, data, &r.RawLogicalDNSRule, &r.DNSRuleAction) + if err != nil { + return err + } + if depth > 0 && rawAction == "" && routeOptions == (DNSRouteActionOptions{}) { + r.DNSRuleAction = DNSRuleAction{} + } + return nil } -func (r LogicalDNSRule) IsValid() bool { +func (r *LogicalDNSRule) IsValid() bool { return len(r.Rules) > 0 && common.All(r.Rules, DNSRule.IsValid) } diff --git a/option/rule_nested.go b/option/rule_nested.go new file mode 100644 index 0000000000..a48c17ea39 --- /dev/null +++ b/option/rule_nested.go @@ -0,0 +1,129 @@ +package option + +import ( + "context" + "reflect" + "slices" + "strings" + + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" +) + +type nestedRuleDepthContextKey struct{} + +const ( + RouteRuleActionNestedUnsupportedMessage = "rule action is not supported in nested rules" + DNSRuleActionNestedUnsupportedMessage = "DNS rule action is not supported in nested rules" +) + +var ( + routeRuleActionKeys = jsonFieldNames(reflect.TypeFor[_RuleAction](), reflect.TypeFor[RouteActionOptions]()) + dnsRuleActionKeys = jsonFieldNames(reflect.TypeFor[_DNSRuleAction](), reflect.TypeFor[DNSRouteActionOptions]()) +) + +func nestedRuleChildContext(ctx context.Context) context.Context { + return context.WithValue(ctx, nestedRuleDepthContextKey{}, nestedRuleDepth(ctx)+1) +} + +func rejectNestedRouteRuleAction(ctx context.Context, content []byte) error { + return rejectNestedRuleAction(ctx, content, routeRuleActionKeys, RouteRuleActionNestedUnsupportedMessage) +} + +func rejectNestedDNSRuleAction(ctx context.Context, content []byte) error { + return rejectNestedRuleAction(ctx, content, dnsRuleActionKeys, DNSRuleActionNestedUnsupportedMessage) +} + +func nestedRuleDepth(ctx context.Context) int { + depth, _ := ctx.Value(nestedRuleDepthContextKey{}).(int) + return depth +} + +func rejectNestedRuleAction(ctx context.Context, content []byte, keys []string, message string) error { + if nestedRuleDepth(ctx) == 0 { + return nil + } + hasActionKey, err := hasAnyJSONKey(ctx, content, keys...) + if err != nil { + return err + } + if hasActionKey { + return E.New(message) + } + return nil +} + +func hasAnyJSONKey(ctx context.Context, content []byte, keys ...string) (bool, error) { + var object badjson.JSONObject + err := object.UnmarshalJSONContext(ctx, content) + if err != nil { + return false, err + } + return slices.ContainsFunc(keys, object.ContainsKey), nil +} + +func inspectRouteRuleAction(ctx context.Context, content []byte) (string, RouteActionOptions, error) { + var rawAction _RuleAction + err := json.UnmarshalContext(ctx, content, &rawAction) + if err != nil { + return "", RouteActionOptions{}, err + } + var routeOptions RouteActionOptions + err = json.UnmarshalContext(ctx, content, &routeOptions) + if err != nil { + return "", RouteActionOptions{}, err + } + return rawAction.Action, routeOptions, nil +} + +func inspectDNSRuleAction(ctx context.Context, content []byte) (string, DNSRouteActionOptions, error) { + var rawAction _DNSRuleAction + err := json.UnmarshalContext(ctx, content, &rawAction) + if err != nil { + return "", DNSRouteActionOptions{}, err + } + var routeOptions DNSRouteActionOptions + err = json.UnmarshalContext(ctx, content, &routeOptions) + if err != nil { + return "", DNSRouteActionOptions{}, err + } + return rawAction.Action, routeOptions, nil +} + +func jsonFieldNames(types ...reflect.Type) []string { + fieldMap := make(map[string]struct{}) + for _, fieldType := range types { + appendJSONFieldNames(fieldMap, fieldType) + } + fieldNames := make([]string, 0, len(fieldMap)) + for fieldName := range fieldMap { + fieldNames = append(fieldNames, fieldName) + } + return fieldNames +} + +func appendJSONFieldNames(fieldMap map[string]struct{}, fieldType reflect.Type) { + for fieldType.Kind() == reflect.Pointer { + fieldType = fieldType.Elem() + } + if fieldType.Kind() != reflect.Struct { + return + } + for i := range fieldType.NumField() { + field := fieldType.Field(i) + tagValue := field.Tag.Get("json") + tagName, _, _ := strings.Cut(tagValue, ",") + if tagName == "-" { + continue + } + if field.Anonymous && tagName == "" { + appendJSONFieldNames(fieldMap, field.Type) + continue + } + if tagName == "" { + tagName = field.Name + } + fieldMap[tagName] = struct{}{} + } +} diff --git a/option/rule_nested_test.go b/option/rule_nested_test.go new file mode 100644 index 0000000000..3b2ef2e5f0 --- /dev/null +++ b/option/rule_nested_test.go @@ -0,0 +1,68 @@ +package option + +import ( + "context" + "testing" + + "github.com/sagernet/sing/common/json" + + "github.com/stretchr/testify/require" +) + +func TestRuleRejectsNestedDefaultRuleAction(t *testing.T) { + t.Parallel() + + var rule Rule + err := json.UnmarshalContext(context.Background(), []byte(`{ + "type": "logical", + "mode": "and", + "rules": [ + {"domain": "example.com", "outbound": "direct"} + ] + }`), &rule) + require.ErrorContains(t, err, RouteRuleActionNestedUnsupportedMessage) +} + +func TestRuleLeavesUnknownNestedKeysToNormalValidation(t *testing.T) { + t.Parallel() + + var rule Rule + err := json.UnmarshalContext(context.Background(), []byte(`{ + "type": "logical", + "mode": "and", + "rules": [ + {"domain": "example.com", "foo": "bar"} + ] + }`), &rule) + require.ErrorContains(t, err, "unknown field") + require.NotContains(t, err.Error(), RouteRuleActionNestedUnsupportedMessage) +} + +func TestDNSRuleRejectsNestedDefaultRuleAction(t *testing.T) { + t.Parallel() + + var rule DNSRule + err := json.UnmarshalContext(context.Background(), []byte(`{ + "type": "logical", + "mode": "and", + "rules": [ + {"domain": "example.com", "server": "default"} + ] + }`), &rule) + require.ErrorContains(t, err, DNSRuleActionNestedUnsupportedMessage) +} + +func TestDNSRuleLeavesUnknownNestedKeysToNormalValidation(t *testing.T) { + t.Parallel() + + var rule DNSRule + err := json.UnmarshalContext(context.Background(), []byte(`{ + "type": "logical", + "mode": "and", + "rules": [ + {"domain": "example.com", "foo": "bar"} + ] + }`), &rule) + require.ErrorContains(t, err, "unknown field") + require.NotContains(t, err.Error(), DNSRuleActionNestedUnsupportedMessage) +} diff --git a/option/rule_set.go b/option/rule_set.go index 8e367e69a5..18d8490339 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -1,6 +1,8 @@ package option import ( + "net/url" + "path/filepath" "reflect" C "github.com/sagernet/sing-box/constant" @@ -9,14 +11,17 @@ import ( E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" "go4.org/netipx" ) type _RuleSet struct { - Type string `json:"type"` + Type string `json:"type,omitempty"` Tag string `json:"tag"` - Format string `json:"format"` + Format string `json:"format,omitempty"` + InlineOptions PlainRuleSet `json:"-"` LocalOptions LocalRuleSet `json:"-"` RemoteOptions RemoteRuleSet `json:"-"` } @@ -24,16 +29,31 @@ type _RuleSet struct { type RuleSet _RuleSet func (r RuleSet) MarshalJSON() ([]byte, error) { + if r.Type != C.RuleSetTypeInline { + var defaultFormat string + switch r.Type { + case C.RuleSetTypeLocal: + defaultFormat = ruleSetDefaultFormat(r.LocalOptions.Path) + case C.RuleSetTypeRemote: + defaultFormat = ruleSetDefaultFormat(r.RemoteOptions.URL) + } + if r.Format == defaultFormat { + r.Format = "" + } + } var v any switch r.Type { + case "", C.RuleSetTypeInline: + r.Type = "" + v = r.InlineOptions case C.RuleSetTypeLocal: v = r.LocalOptions case C.RuleSetTypeRemote: v = r.RemoteOptions default: - return nil, E.New("unknown rule set type: " + r.Type) + return nil, E.New("unknown rule-set type: " + r.Type) } - return MarshallObjects((_RuleSet)(r), v) + return badjson.MarshallObjects((_RuleSet)(r), v) } func (r *RuleSet) UnmarshalJSON(bytes []byte) error { @@ -44,39 +64,68 @@ func (r *RuleSet) UnmarshalJSON(bytes []byte) error { if r.Tag == "" { return E.New("missing tag") } - switch r.Format { - case "": - return E.New("missing format") - case C.RuleSetFormatSource, C.RuleSetFormatBinary: - default: - return E.New("unknown rule set format: " + r.Format) - } var v any switch r.Type { + case "", C.RuleSetTypeInline: + r.Type = C.RuleSetTypeInline + v = &r.InlineOptions case C.RuleSetTypeLocal: v = &r.LocalOptions case C.RuleSetTypeRemote: v = &r.RemoteOptions - case "": - return E.New("missing type") default: - return E.New("unknown rule set type: " + r.Type) + return E.New("unknown rule-set type: " + r.Type) } - err = UnmarshallExcluded(bytes, (*_RuleSet)(r), v) + err = badjson.UnmarshallExcluded(bytes, (*_RuleSet)(r), v) if err != nil { return err } + if r.Type != C.RuleSetTypeInline { + if r.Format == "" { + switch r.Type { + case C.RuleSetTypeLocal: + r.Format = ruleSetDefaultFormat(r.LocalOptions.Path) + case C.RuleSetTypeRemote: + r.Format = ruleSetDefaultFormat(r.RemoteOptions.URL) + } + } + switch r.Format { + case "": + return E.New("missing format") + case C.RuleSetFormatSource, C.RuleSetFormatBinary: + default: + return E.New("unknown rule-set format: " + r.Format) + } + } else { + r.Format = "" + } return nil } +func ruleSetDefaultFormat(path string) string { + if pathURL, err := url.Parse(path); err == nil { + path = pathURL.Path + } + switch filepath.Ext(path) { + case ".json": + return C.RuleSetFormatSource + case ".srs": + return C.RuleSetFormatBinary + default: + return "" + } +} + type LocalRuleSet struct { Path string `json:"path,omitempty"` } type RemoteRuleSet struct { - URL string `json:"url"` - DownloadDetour string `json:"download_detour,omitempty"` - UpdateInterval Duration `json:"update_interval,omitempty"` + URL string `json:"url"` + HTTPClient *HTTPClientOptions `json:"http_client,omitempty"` + UpdateInterval badoption.Duration `json:"update_interval,omitempty"` + // Deprecated: use http_client instead + DownloadDetour string `json:"download_detour,omitempty"` } type _HeadlessRule struct { @@ -98,7 +147,7 @@ func (r HeadlessRule) MarshalJSON() ([]byte, error) { default: return nil, E.New("unknown rule type: " + r.Type) } - return MarshallObjects((_HeadlessRule)(r), v) + return badjson.MarshallObjects((_HeadlessRule)(r), v) } func (r *HeadlessRule) UnmarshalJSON(bytes []byte) error { @@ -116,7 +165,7 @@ func (r *HeadlessRule) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown rule type: " + r.Type) } - err = UnmarshallExcluded(bytes, (*_HeadlessRule)(r), v) + err = badjson.UnmarshallExcluded(bytes, (*_HeadlessRule)(r), v) if err != nil { return err } @@ -135,28 +184,43 @@ func (r HeadlessRule) IsValid() bool { } type DefaultHeadlessRule struct { - QueryType Listable[DNSQueryType] `json:"query_type,omitempty"` - Network Listable[string] `json:"network,omitempty"` - Domain Listable[string] `json:"domain,omitempty"` - DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex Listable[string] `json:"domain_regex,omitempty"` - SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` - IPCIDR Listable[string] `json:"ip_cidr,omitempty"` - SourcePort Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange Listable[string] `json:"source_port_range,omitempty"` - Port Listable[uint16] `json:"port,omitempty"` - PortRange Listable[string] `json:"port_range,omitempty"` - ProcessName Listable[string] `json:"process_name,omitempty"` - ProcessPath Listable[string] `json:"process_path,omitempty"` - PackageName Listable[string] `json:"package_name,omitempty"` - WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"` - Invert bool `json:"invert,omitempty"` + QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` + Network badoption.Listable[string] `json:"network,omitempty"` + Domain badoption.Listable[string] `json:"domain,omitempty"` + DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` + SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` + IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` + SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` + Port badoption.Listable[uint16] `json:"port,omitempty"` + PortRange badoption.Listable[string] `json:"port_range,omitempty"` + ProcessName badoption.Listable[string] `json:"process_name,omitempty"` + ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` + ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` + PackageName badoption.Listable[string] `json:"package_name,omitempty"` + PackageNameRegex badoption.Listable[string] `json:"package_name_regex,omitempty"` + NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` + NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` + NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` + WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` + NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[*badoption.Prefixable]] `json:"network_interface_address,omitempty"` + DefaultInterfaceAddress badoption.Listable[*badoption.Prefixable] `json:"default_interface_address,omitempty"` + + Invert bool `json:"invert,omitempty"` DomainMatcher *domain.Matcher `json:"-"` SourceIPSet *netipx.IPSet `json:"-"` IPSet *netipx.IPSet `json:"-"` + + AdGuardDomain badoption.Listable[string] `json:"-"` + AdGuardDomainMatcher *domain.AdGuardMatcher `json:"-"` + + TunnelSource badoption.Listable[string] `json:"tunnel_source,omitempty"` //E + TunnelDestination badoption.Listable[string] `json:"tunnel_destination,omitempty"` //E + } func (r DefaultHeadlessRule) IsValid() bool { @@ -176,8 +240,9 @@ func (r LogicalHeadlessRule) IsValid() bool { } type _PlainRuleSetCompat struct { - Version int `json:"version"` - Options PlainRuleSet `json:"-"` + Version uint8 `json:"version"` + Options PlainRuleSet `json:"-"` + RawMessage json.RawMessage `json:"-"` } type PlainRuleSetCompat _PlainRuleSetCompat @@ -185,12 +250,12 @@ type PlainRuleSetCompat _PlainRuleSetCompat func (r PlainRuleSetCompat) MarshalJSON() ([]byte, error) { var v any switch r.Version { - case C.RuleSetVersion1: + case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4, C.RuleSetVersion5: v = r.Options default: - return nil, E.New("unknown rule set version: ", r.Version) + return nil, E.New("unknown rule-set version: ", r.Version) } - return MarshallObjects((_PlainRuleSetCompat)(r), v) + return badjson.MarshallObjects((_PlainRuleSetCompat)(r), v) } func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error { @@ -200,29 +265,28 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error { } var v any switch r.Version { - case C.RuleSetVersion1: + case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4, C.RuleSetVersion5: v = &r.Options case 0: - return E.New("missing rule set version") + return E.New("missing rule-set version") default: - return E.New("unknown rule set version: ", r.Version) + return E.New("unknown rule-set version: ", r.Version) } - err = UnmarshallExcluded(bytes, (*_PlainRuleSetCompat)(r), v) + err = badjson.UnmarshallExcluded(bytes, (*_PlainRuleSetCompat)(r), v) if err != nil { return err } + r.RawMessage = bytes return nil } -func (r PlainRuleSetCompat) Upgrade() PlainRuleSet { - var result PlainRuleSet +func (r PlainRuleSetCompat) Upgrade() (PlainRuleSet, error) { switch r.Version { - case C.RuleSetVersion1: - result = r.Options + case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4, C.RuleSetVersion5: default: - panic("unknown rule set version: " + F.ToString(r.Version)) + return PlainRuleSet{}, E.New("unknown rule-set version: " + F.ToString(r.Version)) } - return result + return r.Options, nil } type PlainRuleSet struct { diff --git a/option/service.go b/option/service.go new file mode 100644 index 0000000000..7d45bc1433 --- /dev/null +++ b/option/service.go @@ -0,0 +1,47 @@ +package option + +import ( + "context" + + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/service" +) + +type ServiceOptionsRegistry interface { + CreateOptions(serviceType string) (any, bool) +} + +type _Service struct { + Type string `json:"type"` + Tag string `json:"tag,omitempty"` + Options any `json:"-"` +} + +type Service _Service + +func (h *Service) MarshalJSONContext(ctx context.Context) ([]byte, error) { + return badjson.MarshallObjectsContext(ctx, (*_Service)(h), h.Options) +} + +func (h *Service) UnmarshalJSONContext(ctx context.Context, content []byte) error { + err := json.UnmarshalContext(ctx, content, (*_Service)(h)) + if err != nil { + return err + } + registry := service.FromContext[ServiceOptionsRegistry](ctx) + if registry == nil { + return E.New("missing service fields registry in context") + } + options, loaded := registry.CreateOptions(h.Type) + if !loaded { + return E.New("unknown inbound type: ", h.Type) + } + err = badjson.UnmarshallExcludedContext(ctx, content, (*_Service)(h), options) + if err != nil { + return err + } + h.Options = options + return nil +} diff --git a/option/shadowsocks.go b/option/shadowsocks.go index 187b9b633d..cbb7cdf8c7 100644 --- a/option/shadowsocks.go +++ b/option/shadowsocks.go @@ -8,6 +8,9 @@ type ShadowsocksInboundOptions struct { Users []ShadowsocksUser `json:"users,omitempty"` Destinations []ShadowsocksDestination `json:"destinations,omitempty"` Multiplex *InboundMultiplexOptions `json:"multiplex,omitempty"` + Managed bool `json:"managed,omitempty"` + ObfsMode string `json:"obfs_mode,omitempty"` + ObfsHost string `json:"obfs_host,omitempty"` } type ShadowsocksUser struct { diff --git a/option/shadowtls.go b/option/shadowtls.go index a8be77406c..81ef9a4370 100644 --- a/option/shadowtls.go +++ b/option/shadowtls.go @@ -1,13 +1,65 @@ package option +import ( + "encoding/json" + + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json/badjson" +) + type ShadowTLSInboundOptions struct { ListenOptions - Version int `json:"version,omitempty"` - Password string `json:"password,omitempty"` - Users []ShadowTLSUser `json:"users,omitempty"` - Handshake ShadowTLSHandshakeOptions `json:"handshake,omitempty"` - HandshakeForServerName map[string]ShadowTLSHandshakeOptions `json:"handshake_for_server_name,omitempty"` - StrictMode bool `json:"strict_mode,omitempty"` + Version int `json:"version,omitempty"` + Password string `json:"password,omitempty"` + Users []ShadowTLSUser `json:"users,omitempty"` + Handshake ShadowTLSHandshakeOptions `json:"handshake,omitempty"` + HandshakeForServerName *badjson.TypedMap[string, ShadowTLSHandshakeOptions] `json:"handshake_for_server_name,omitempty"` + StrictMode bool `json:"strict_mode,omitempty"` + WildcardSNI WildcardSNI `json:"wildcard_sni,omitempty"` +} + +type WildcardSNI int + +const ( + ShadowTLSWildcardSNIOff WildcardSNI = iota + ShadowTLSWildcardSNIAuthed + ShadowTLSWildcardSNIAll +) + +func (w WildcardSNI) MarshalJSON() ([]byte, error) { + return json.Marshal(w.String()) +} + +func (w WildcardSNI) String() string { + switch w { + case ShadowTLSWildcardSNIOff: + return "off" + case ShadowTLSWildcardSNIAuthed: + return "authed" + case ShadowTLSWildcardSNIAll: + return "all" + default: + panic("unknown wildcard SNI value") + } +} + +func (w *WildcardSNI) UnmarshalJSON(bytes []byte) error { + var valueString string + err := json.Unmarshal(bytes, &valueString) + if err != nil { + return err + } + switch valueString { + case "off", "": + *w = ShadowTLSWildcardSNIOff + case "authed": + *w = ShadowTLSWildcardSNIAuthed + case "all": + *w = ShadowTLSWildcardSNIAll + default: + return E.New("unknown wildcard SNI value: ", valueString) + } + return nil } type ShadowTLSUser struct { diff --git a/option/simple.go b/option/simple.go index ba9d6bf190..f244ba18b8 100644 --- a/option/simple.go +++ b/option/simple.go @@ -1,20 +1,25 @@ package option -import "github.com/sagernet/sing/common/auth" +import ( + "github.com/sagernet/sing/common/auth" + "github.com/sagernet/sing/common/json/badoption" +) type SocksInboundOptions struct { ListenOptions - Users []auth.User `json:"users,omitempty"` + Users []auth.User `json:"users,omitempty"` + DomainResolver *DomainResolveOptions `json:"domain_resolver,omitempty"` } type HTTPMixedInboundOptions struct { ListenOptions - Users []auth.User `json:"users,omitempty"` - SetSystemProxy bool `json:"set_system_proxy,omitempty"` + Users []auth.User `json:"users,omitempty"` + DomainResolver *DomainResolveOptions `json:"domain_resolver,omitempty"` + SetSystemProxy bool `json:"set_system_proxy,omitempty"` InboundTLSOptionsContainer } -type SocksOutboundOptions struct { +type SOCKSOutboundOptions struct { DialerOptions ServerOptions Version string `json:"version,omitempty"` @@ -30,6 +35,6 @@ type HTTPOutboundOptions struct { Username string `json:"username,omitempty"` Password string `json:"password,omitempty"` OutboundTLSOptionsContainer - Path string `json:"path,omitempty"` - Headers HTTPHeader `json:"headers,omitempty"` + Path string `json:"path,omitempty"` + Headers badoption.HTTPHeader `json:"headers,omitempty"` } diff --git a/option/smart_dns_pool.go b/option/smart_dns_pool.go new file mode 100644 index 0000000000..400805d62b --- /dev/null +++ b/option/smart_dns_pool.go @@ -0,0 +1,43 @@ +package option + +import ( + "github.com/sagernet/sing/common/json/badoption" +) + +// SmartDNSPoolUpstreamOptions describes one recursive resolver inside a +// smart_dns_pool service. The smart pool carries adaptive AIMD throttling, +// failover, and recovery probing across all upstreams configured here. +type SmartDNSPoolUpstreamOptions struct { + // Type is one of "udp" | "tcp" | "tls" (DoT) | "https" (DoH). + Type string `json:"type"` + // Address is "host:port" for udp/tcp/tls, or a full https URL for DoH + // (e.g. https://cloudflare-dns.com/dns-query). + Address string `json:"address"` + // Weight is consulted by the "weighted" load_balance strategy. 0 means + // the upstream is a pure fallback (only reached when every weighted + // candidate is unavailable). Ignored by other strategies. + Weight int `json:"weight,omitempty"` + // Name is an optional friendly id surfaced in stats. Defaults to + // "://
". + Name string `json:"name,omitempty"` +} + +// SmartDNSPoolServiceOptions configures a `smart_dns_pool` service: a +// local DNS server (UDP+TCP) that fans queries out to many recursive +// upstream resolvers using github.com/hiddify/hmrd_multi_resolver_dns. +// +// Typical use: configure this with the recursive resolvers you want to +// distribute load across, then point dnstt's `resolvers` at the local +// listen address (e.g. udp://127.0.0.1:19876) instead of a fixed +// `8.8.8.8` etc. dnstt sees a normal local resolver; the pool transparently +// handles failover, AIMD rate-limit throttling, and recovery probing for +// the real upstreams. +type SmartDNSPoolServiceOptions struct { + ListenOptions + Upstreams []SmartDNSPoolUpstreamOptions `json:"upstreams"` + LoadBalance string `json:"load_balance,omitempty"` // "roundrobin" (default) | "weighted" | "lowest_latency" + Deadline badoption.Duration `json:"deadline,omitempty"` // overall query deadline; default 5s + PerAttempt badoption.Duration `json:"per_attempt,omitempty"` // per-resolver attempt cap; default 2s + ProbeInterval badoption.Duration `json:"probe_interval,omitempty"` // recovery probe cadence; default 5s + DownAfter int `json:"down_after,omitempty"` // consecutive failures before "down"; default 8 +} diff --git a/option/snell.go b/option/snell.go new file mode 100644 index 0000000000..82582fa916 --- /dev/null +++ b/option/snell.go @@ -0,0 +1,30 @@ +package option + +// SnellUser is one entry in a multi-user Snell inbound configuration. +type SnellUser struct { + Name string `json:"name"` + PSK string `json:"psk"` +} + +type SnellInboundOptions struct { + ListenOptions + // PSK is the pre-shared key for single-user mode. + // Mutually exclusive with Users. + PSK string `json:"psk,omitempty"` + Version int `json:"version,omitempty"` + // Users enables multi-user mode. When set, PSK must be empty. + Users []SnellUser `json:"users,omitempty"` + ObfsMode string `json:"obfs_mode,omitempty"` + ObfsHost string `json:"obfs_host,omitempty"` +} + +type SnellOutboundOptions struct { + DialerOptions + ServerOptions + PSK string `json:"psk"` + Version int `json:"version,omitempty"` + Reuse bool `json:"reuse,omitempty"` + Network NetworkList `json:"network,omitempty"` + ObfsMode string `json:"obfs_mode,omitempty"` + ObfsHost string `json:"obfs_host,omitempty"` +} diff --git a/option/ssh.go b/option/ssh.go index d0bfbf7425..0ded3b36eb 100644 --- a/option/ssh.go +++ b/option/ssh.go @@ -1,14 +1,33 @@ package option +import "github.com/sagernet/sing/common/json/badoption" + type SSHOutboundOptions struct { DialerOptions ServerOptions - User string `json:"user,omitempty"` - Password string `json:"password,omitempty"` - PrivateKey Listable[string] `json:"private_key,omitempty"` - PrivateKeyPath string `json:"private_key_path,omitempty"` - PrivateKeyPassphrase string `json:"private_key_passphrase,omitempty"` - HostKey Listable[string] `json:"host_key,omitempty"` - HostKeyAlgorithms Listable[string] `json:"host_key_algorithms,omitempty"` - ClientVersion string `json:"client_version,omitempty"` + User string `json:"user,omitempty"` + Password string `json:"password,omitempty"` + PrivateKey badoption.Listable[string] `json:"private_key,omitempty"` + PrivateKeyPath string `json:"private_key_path,omitempty"` + PrivateKeyPassphrase string `json:"private_key_passphrase,omitempty"` + HostKey badoption.Listable[string] `json:"host_key,omitempty"` + HostKeyAlgorithms badoption.Listable[string] `json:"host_key_algorithms,omitempty"` + ClientVersion string `json:"client_version,omitempty"` + UDPOverTCP *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"` + Network NetworkList `json:"network,omitempty"` +} + +type SSHInboundOptions struct { + ListenOptions + Users []SSHUser `json:"users,omitempty"` + Network NetworkList `json:"network,omitempty"` + HostKey badoption.Listable[string] `json:"host_key,omitempty"` + HostKeyAlgorithms badoption.Listable[string] `json:"host_key_algorithms,omitempty"` + ServerVersion string `json:"server_version,omitempty"` +} + +type SSHUser struct { + User string `json:"user,omitempty"` + Password string `json:"password,omitempty"` + PublicKey string `json:"public_key,omitempty"` } diff --git a/option/ssmapi.go b/option/ssmapi.go new file mode 100644 index 0000000000..8d25f4002f --- /dev/null +++ b/option/ssmapi.go @@ -0,0 +1,12 @@ +package option + +import ( + "github.com/sagernet/sing/common/json/badjson" +) + +type SSMAPIServiceOptions struct { + ListenOptions + Servers *badjson.TypedMap[string, string] `json:"servers"` + CachePath string `json:"cache_path,omitempty"` + InboundTLSOptionsContainer +} diff --git a/option/tailscale.go b/option/tailscale.go new file mode 100644 index 0000000000..a078e9aa88 --- /dev/null +++ b/option/tailscale.go @@ -0,0 +1,144 @@ +package option + +import ( + "net/netip" + "net/url" + + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" + M "github.com/sagernet/sing/common/metadata" +) + +type TailscaleEndpointOptions struct { + // Deprecated: use control_http_client instead + DialerOptions + StateDirectory string `json:"state_directory,omitempty"` + AuthKey string `json:"auth_key,omitempty"` + ControlURL string `json:"control_url,omitempty"` + ControlHTTPClient *HTTPClientOptions `json:"control_http_client,omitempty"` + Ephemeral bool `json:"ephemeral,omitempty"` + Hostname string `json:"hostname,omitempty"` + AcceptRoutes bool `json:"accept_routes,omitempty"` + ExitNode string `json:"exit_node,omitempty"` + ExitNodeAllowLANAccess bool `json:"exit_node_allow_lan_access,omitempty"` + AdvertiseRoutes []netip.Prefix `json:"advertise_routes,omitempty"` + AdvertiseExitNode bool `json:"advertise_exit_node,omitempty"` + AdvertiseTags badoption.Listable[string] `json:"advertise_tags,omitempty"` + RelayServerPort *uint16 `json:"relay_server_port,omitempty"` + RelayServerStaticEndpoints []netip.AddrPort `json:"relay_server_static_endpoints,omitempty"` + SystemInterface bool `json:"system_interface,omitempty"` + SystemInterfaceName string `json:"system_interface_name,omitempty"` + SystemInterfaceMTU uint32 `json:"system_interface_mtu,omitempty"` + UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` +} + +type TailscaleDNSServerOptions struct { + Endpoint string `json:"endpoint,omitempty"` + AcceptDefaultResolvers bool `json:"accept_default_resolvers,omitempty"` + AcceptSearchDomain bool `json:"accept_search_domain,omitempty"` +} + +type TailscaleCertificateProviderOptions struct { + Endpoint string `json:"endpoint,omitempty"` +} + +type DERPServiceOptions struct { + ListenOptions + InboundTLSOptionsContainer + ConfigPath string `json:"config_path,omitempty"` + VerifyClientEndpoint badoption.Listable[string] `json:"verify_client_endpoint,omitempty"` + VerifyClientURL badoption.Listable[*DERPVerifyClientURLOptions] `json:"verify_client_url,omitempty"` + Home string `json:"home,omitempty"` + MeshWith badoption.Listable[*DERPMeshOptions] `json:"mesh_with,omitempty"` + MeshPSK string `json:"mesh_psk,omitempty"` + MeshPSKFile string `json:"mesh_psk_file,omitempty"` + STUN *DERPSTUNListenOptions `json:"stun,omitempty"` +} + +type _DERPVerifyClientURLBase struct { + URL string `json:"url,omitempty"` +} + +type _DERPVerifyClientURLOptions struct { + _DERPVerifyClientURLBase + HTTPClientOptions +} + +type DERPVerifyClientURLOptions _DERPVerifyClientURLOptions + +func (d DERPVerifyClientURLOptions) ServerIsDomain() bool { + verifyURL, err := url.Parse(d.URL) + if err != nil { + return false + } + return M.ParseSocksaddr(verifyURL.Hostname()).IsDomain() +} + +func (d DERPVerifyClientURLOptions) MarshalJSON() ([]byte, error) { + if d.URL != "" && d.HTTPClientOptions.IsEmpty() { + return json.Marshal(d.URL) + } + return badjson.MarshallObjects(d._DERPVerifyClientURLBase, HTTPClient(d.HTTPClientOptions)) +} + +func (d *DERPVerifyClientURLOptions) UnmarshalJSON(bytes []byte) error { + var stringValue string + err := json.Unmarshal(bytes, &stringValue) + if err == nil { + *d = DERPVerifyClientURLOptions{ + _DERPVerifyClientURLBase: _DERPVerifyClientURLBase{URL: stringValue}, + } + return nil + } + err = json.Unmarshal(bytes, &d._DERPVerifyClientURLBase) + if err != nil { + return err + } + var client HTTPClient + err = badjson.UnmarshallExcluded(bytes, &d._DERPVerifyClientURLBase, &client) + if err != nil { + return err + } + d.HTTPClientOptions = HTTPClientOptions(client) + return nil +} + +type DERPMeshOptions struct { + ServerOptions + Host string `json:"host,omitempty"` + OutboundTLSOptionsContainer + DialerOptions +} + +type _DERPSTUNListenOptions struct { + Enabled bool + ListenOptions +} + +type DERPSTUNListenOptions _DERPSTUNListenOptions + +func (d DERPSTUNListenOptions) MarshalJSON() ([]byte, error) { + portOptions := _DERPSTUNListenOptions{ + Enabled: d.Enabled, + ListenOptions: ListenOptions{ + ListenPort: d.ListenPort, + }, + } + if _DERPSTUNListenOptions(d) == portOptions { + return json.Marshal(d.Enabled) + } else { + return json.Marshal(_DERPSTUNListenOptions(d)) + } +} + +func (d *DERPSTUNListenOptions) UnmarshalJSON(bytes []byte) error { + var portValue uint16 + err := json.Unmarshal(bytes, &portValue) + if err == nil { + d.Enabled = true + d.ListenPort = portValue + return nil + } + return json.Unmarshal(bytes, (*_DERPSTUNListenOptions)(d)) +} diff --git a/option/time_unit.go b/option/time_unit.go deleted file mode 100644 index 5e531dadf1..0000000000 --- a/option/time_unit.go +++ /dev/null @@ -1,226 +0,0 @@ -package option - -import ( - "errors" - "time" -) - -// Copyright 2010 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. - -const durationDay = 24 * time.Hour - -var unitMap = map[string]uint64{ - "ns": uint64(time.Nanosecond), - "us": uint64(time.Microsecond), - "µs": uint64(time.Microsecond), // U+00B5 = micro symbol - "μs": uint64(time.Microsecond), // U+03BC = Greek letter mu - "ms": uint64(time.Millisecond), - "s": uint64(time.Second), - "m": uint64(time.Minute), - "h": uint64(time.Hour), - "d": uint64(durationDay), -} - -// ParseDuration parses a duration string. -// A duration string is a possibly signed sequence of -// decimal numbers, each with optional fraction and a unit suffix, -// such as "300ms", "-1.5h" or "2h45m". -// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". -func ParseDuration(s string) (Duration, error) { - // [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+ - orig := s - var d uint64 - neg := false - - // Consume [-+]? - if s != "" { - c := s[0] - if c == '-' || c == '+' { - neg = c == '-' - s = s[1:] - } - } - // Special case: if all that is left is "0", this is zero. - if s == "0" { - return 0, nil - } - if s == "" { - return 0, errors.New("time: invalid duration " + quote(orig)) - } - for s != "" { - var ( - v, f uint64 // integers before, after decimal point - scale float64 = 1 // value = v + f/scale - ) - - var err error - - // The next character must be [0-9.] - if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') { - return 0, errors.New("time: invalid duration " + quote(orig)) - } - // Consume [0-9]* - pl := len(s) - v, s, err = leadingInt(s) - if err != nil { - return 0, errors.New("time: invalid duration " + quote(orig)) - } - pre := pl != len(s) // whether we consumed anything before a period - - // Consume (\.[0-9]*)? - post := false - if s != "" && s[0] == '.' { - s = s[1:] - pl := len(s) - f, scale, s = leadingFraction(s) - post = pl != len(s) - } - if !pre && !post { - // no digits (e.g. ".s" or "-.s") - return 0, errors.New("time: invalid duration " + quote(orig)) - } - - // Consume unit. - i := 0 - for ; i < len(s); i++ { - c := s[i] - if c == '.' || '0' <= c && c <= '9' { - break - } - } - if i == 0 { - return 0, errors.New("time: missing unit in duration " + quote(orig)) - } - u := s[:i] - s = s[i:] - unit, ok := unitMap[u] - if !ok { - return 0, errors.New("time: unknown unit " + quote(u) + " in duration " + quote(orig)) - } - if v > 1<<63/unit { - // overflow - return 0, errors.New("time: invalid duration " + quote(orig)) - } - v *= unit - if f > 0 { - // float64 is needed to be nanosecond accurate for fractions of hours. - // v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit) - v += uint64(float64(f) * (float64(unit) / scale)) - if v > 1<<63 { - // overflow - return 0, errors.New("time: invalid duration " + quote(orig)) - } - } - d += v - if d > 1<<63 { - return 0, errors.New("time: invalid duration " + quote(orig)) - } - } - if neg { - return -Duration(d), nil - } - if d > 1<<63-1 { - return 0, errors.New("time: invalid duration " + quote(orig)) - } - return Duration(d), nil -} - -var errLeadingInt = errors.New("time: bad [0-9]*") // never printed - -// leadingInt consumes the leading [0-9]* from s. -func leadingInt[bytes []byte | string](s bytes) (x uint64, rem bytes, err error) { - i := 0 - for ; i < len(s); i++ { - c := s[i] - if c < '0' || c > '9' { - break - } - if x > 1<<63/10 { - // overflow - return 0, rem, errLeadingInt - } - x = x*10 + uint64(c) - '0' - if x > 1<<63 { - // overflow - return 0, rem, errLeadingInt - } - } - return x, s[i:], nil -} - -// leadingFraction consumes the leading [0-9]* from s. -// It is used only for fractions, so does not return an error on overflow, -// it just stops accumulating precision. -func leadingFraction(s string) (x uint64, scale float64, rem string) { - i := 0 - scale = 1 - overflow := false - for ; i < len(s); i++ { - c := s[i] - if c < '0' || c > '9' { - break - } - if overflow { - continue - } - if x > (1<<63-1)/10 { - // It's possible for overflow to give a positive number, so take care. - overflow = true - continue - } - y := x*10 + uint64(c) - '0' - if y > 1<<63 { - overflow = true - continue - } - x = y - scale *= 10 - } - return x, scale, s[i:] -} - -// These are borrowed from unicode/utf8 and strconv and replicate behavior in -// that package, since we can't take a dependency on either. -const ( - lowerhex = "0123456789abcdef" - runeSelf = 0x80 - runeError = '\uFFFD' -) - -func quote(s string) string { - buf := make([]byte, 1, len(s)+2) // slice will be at least len(s) + quotes - buf[0] = '"' - for i, c := range s { - if c >= runeSelf || c < ' ' { - // This means you are asking us to parse a time.Duration or - // time.Location with unprintable or non-ASCII characters in it. - // We don't expect to hit this case very often. We could try to - // reproduce strconv.Quote's behavior with full fidelity but - // given how rarely we expect to hit these edge cases, speed and - // conciseness are better. - var width int - if c == runeError { - width = 1 - if i+2 < len(s) && s[i:i+3] == string(runeError) { - width = 3 - } - } else { - width = len(string(c)) - } - for j := 0; j < width; j++ { - buf = append(buf, `\x`...) - buf = append(buf, lowerhex[s[i+j]>>4]) - buf = append(buf, lowerhex[s[i+j]&0xF]) - } - } else { - if c == '"' || c == '\\' { - buf = append(buf, '\\') - } - buf = append(buf, string(c)...) - } - } - buf = append(buf, '"') - return string(buf) -} diff --git a/option/tls.go b/option/tls.go index 3680afe052..177dd27f07 100644 --- a/option/tls.go +++ b/option/tls.go @@ -1,20 +1,85 @@ package option +import ( + "crypto/tls" + "encoding/json" + "strings" + + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json/badoption" +) + type InboundTLSOptions struct { - Enabled bool `json:"enabled,omitempty"` - ServerName string `json:"server_name,omitempty"` - Insecure bool `json:"insecure,omitempty"` - ALPN Listable[string] `json:"alpn,omitempty"` - MinVersion string `json:"min_version,omitempty"` - MaxVersion string `json:"max_version,omitempty"` - CipherSuites Listable[string] `json:"cipher_suites,omitempty"` - Certificate Listable[string] `json:"certificate,omitempty"` - CertificatePath string `json:"certificate_path,omitempty"` - Key Listable[string] `json:"key,omitempty"` - KeyPath string `json:"key_path,omitempty"` - ACME *InboundACMEOptions `json:"acme,omitempty"` - ECH *InboundECHOptions `json:"ech,omitempty"` - Reality *InboundRealityOptions `json:"reality,omitempty"` + Enabled bool `json:"enabled,omitempty"` + ServerName string `json:"server_name,omitempty"` + Insecure bool `json:"insecure,omitempty"` + ALPN badoption.Listable[string] `json:"alpn,omitempty"` + MinVersion string `json:"min_version,omitempty"` + MaxVersion string `json:"max_version,omitempty"` + CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"` + CurvePreferences badoption.Listable[CurvePreference] `json:"curve_preferences,omitempty"` + Certificate badoption.Listable[string] `json:"certificate,omitempty"` + CertificatePath string `json:"certificate_path,omitempty"` + ClientAuthentication ClientAuthType `json:"client_authentication,omitempty"` + ClientCertificate badoption.Listable[string] `json:"client_certificate,omitempty"` + ClientCertificatePath badoption.Listable[string] `json:"client_certificate_path,omitempty"` + ClientCertificatePublicKeySHA256 badoption.Listable[[]byte] `json:"client_certificate_public_key_sha256,omitempty"` + Key badoption.Listable[string] `json:"key,omitempty"` + KeyPath string `json:"key_path,omitempty"` + KernelTx bool `json:"kernel_tx,omitempty"` + KernelRx bool `json:"kernel_rx,omitempty"` + HandshakeTimeout badoption.Duration `json:"handshake_timeout,omitempty"` + CertificateProvider *CertificateProviderOptions `json:"certificate_provider,omitempty"` + + // Deprecated: use certificate_provider + ACME *InboundACMEOptions `json:"acme,omitempty"` + + ECH *InboundECHOptions `json:"ech,omitempty"` + Reality *InboundRealityOptions `json:"reality,omitempty"` +} + +type ClientAuthType tls.ClientAuthType + +func (t ClientAuthType) MarshalJSON() ([]byte, error) { + var stringValue string + switch t { + case ClientAuthType(tls.NoClientCert): + stringValue = "no" + case ClientAuthType(tls.RequestClientCert): + stringValue = "request" + case ClientAuthType(tls.RequireAnyClientCert): + stringValue = "require-any" + case ClientAuthType(tls.VerifyClientCertIfGiven): + stringValue = "verify-if-given" + case ClientAuthType(tls.RequireAndVerifyClientCert): + stringValue = "require-and-verify" + default: + return nil, E.New("unknown client authentication type: ", int(t)) + } + return json.Marshal(stringValue) +} + +func (t *ClientAuthType) UnmarshalJSON(data []byte) error { + var stringValue string + err := json.Unmarshal(data, &stringValue) + if err != nil { + return err + } + switch stringValue { + case "no": + *t = ClientAuthType(tls.NoClientCert) + case "request": + *t = ClientAuthType(tls.RequestClientCert) + case "require-any": + *t = ClientAuthType(tls.RequireAnyClientCert) + case "verify-if-given": + *t = ClientAuthType(tls.VerifyClientCertIfGiven) + case "require-and-verify": + *t = ClientAuthType(tls.RequireAndVerifyClientCert) + default: + return E.New("unknown client authentication type: ", stringValue) + } + return nil } type InboundTLSOptionsContainer struct { @@ -35,19 +100,36 @@ func (o *InboundTLSOptionsContainer) ReplaceInboundTLSOptions(options *InboundTL } type OutboundTLSOptions struct { - Enabled bool `json:"enabled,omitempty"` - DisableSNI bool `json:"disable_sni,omitempty"` - ServerName string `json:"server_name,omitempty"` - Insecure bool `json:"insecure,omitempty"` - ALPN Listable[string] `json:"alpn,omitempty"` - MinVersion string `json:"min_version,omitempty"` - MaxVersion string `json:"max_version,omitempty"` - CipherSuites Listable[string] `json:"cipher_suites,omitempty"` - Certificate Listable[string] `json:"certificate,omitempty"` - CertificatePath string `json:"certificate_path,omitempty"` - ECH *OutboundECHOptions `json:"ech,omitempty"` - UTLS *OutboundUTLSOptions `json:"utls,omitempty"` - Reality *OutboundRealityOptions `json:"reality,omitempty"` + Enabled bool `json:"enabled,omitempty"` + Engine string `json:"engine,omitempty"` + DisableSNI bool `json:"disable_sni,omitempty"` + ServerName string `json:"server_name,omitempty"` + Insecure bool `json:"insecure,omitempty"` + ALPN badoption.Listable[string] `json:"alpn,omitempty"` + MinVersion string `json:"min_version,omitempty"` + MaxVersion string `json:"max_version,omitempty"` + CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"` + CurvePreferences badoption.Listable[CurvePreference] `json:"curve_preferences,omitempty"` + Certificate badoption.Listable[string] `json:"certificate,omitempty"` + CertificatePath string `json:"certificate_path,omitempty"` + CertificatePublicKeySHA256 badoption.Listable[[]byte] `json:"certificate_public_key_sha256,omitempty"` + ClientCertificate badoption.Listable[string] `json:"client_certificate,omitempty"` + ClientCertificatePath string `json:"client_certificate_path,omitempty"` + ClientKey badoption.Listable[string] `json:"client_key,omitempty"` + ClientKeyPath string `json:"client_key_path,omitempty"` + Fragment bool `json:"fragment,omitempty"` + FragmentFallbackDelay badoption.Duration `json:"fragment_fallback_delay,omitempty"` + RecordFragment bool `json:"record_fragment,omitempty"` + Spoof string `json:"spoof,omitempty"` + SpoofMethod string `json:"spoof_method,omitempty"` + KernelTx bool `json:"kernel_tx,omitempty"` + KernelRx bool `json:"kernel_rx,omitempty"` + HandshakeTimeout badoption.Duration `json:"handshake_timeout,omitempty"` + ECH *OutboundECHOptions `json:"ech,omitempty"` + UTLS *OutboundUTLSOptions `json:"utls,omitempty"` + Reality *OutboundRealityOptions `json:"reality,omitempty"` + + TLSTricks *TLSTricksOptions `json:"tls_tricks,omitempty"` //H } type OutboundTLSOptionsContainer struct { @@ -67,12 +149,64 @@ func (o *OutboundTLSOptionsContainer) ReplaceOutboundTLSOptions(options *Outboun o.TLS = options } +type CurvePreference tls.CurveID + +const ( + CurveP256 = 23 + CurveP384 = 24 + CurveP521 = 25 + X25519 = 29 + X25519MLKEM768 = 4588 +) + +func (c CurvePreference) MarshalJSON() ([]byte, error) { + var stringValue string + switch c { + case CurvePreference(CurveP256): + stringValue = "P256" + case CurvePreference(CurveP384): + stringValue = "P384" + case CurvePreference(CurveP521): + stringValue = "P521" + case CurvePreference(X25519): + stringValue = "X25519" + case CurvePreference(X25519MLKEM768): + stringValue = "X25519MLKEM768" + default: + return nil, E.New("unknown curve id: ", int(c)) + } + return json.Marshal(stringValue) +} + +func (c *CurvePreference) UnmarshalJSON(data []byte) error { + var stringValue string + err := json.Unmarshal(data, &stringValue) + if err != nil { + return err + } + switch strings.ToUpper(stringValue) { + case "P256": + *c = CurvePreference(CurveP256) + case "P384": + *c = CurvePreference(CurveP384) + case "P521": + *c = CurvePreference(CurveP521) + case "X25519": + *c = CurvePreference(X25519) + case "X25519MLKEM768": + *c = CurvePreference(X25519MLKEM768) + default: + return E.New("unknown curve name: ", stringValue) + } + return nil +} + type InboundRealityOptions struct { Enabled bool `json:"enabled,omitempty"` Handshake InboundRealityHandshakeOptions `json:"handshake,omitempty"` PrivateKey string `json:"private_key,omitempty"` - ShortID Listable[string] `json:"short_id,omitempty"` - MaxTimeDifference Duration `json:"max_time_difference,omitempty"` + ShortID badoption.Listable[string] `json:"short_id,omitempty"` + MaxTimeDifference badoption.Duration `json:"max_time_difference,omitempty"` } type InboundRealityHandshakeOptions struct { @@ -81,19 +215,26 @@ type InboundRealityHandshakeOptions struct { } type InboundECHOptions struct { - Enabled bool `json:"enabled,omitempty"` - PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"` - DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"` - Key Listable[string] `json:"key,omitempty"` - KeyPath string `json:"key_path,omitempty"` + Enabled bool `json:"enabled,omitempty"` + Key badoption.Listable[string] `json:"key,omitempty"` + KeyPath string `json:"key_path,omitempty"` + + // Deprecated: not supported by stdlib + PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"` + // Deprecated: added by fault + DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"` } type OutboundECHOptions struct { - Enabled bool `json:"enabled,omitempty"` - PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"` - DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"` - Config Listable[string] `json:"config,omitempty"` - ConfigPath string `json:"config_path,omitempty"` + Enabled bool `json:"enabled,omitempty"` + Config badoption.Listable[string] `json:"config,omitempty"` + ConfigPath string `json:"config_path,omitempty"` + QueryServerName string `json:"query_server_name,omitempty"` + + // Deprecated: not supported by stdlib + PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"` + // Deprecated: added by fault + DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"` } type OutboundUTLSOptions struct { diff --git a/option/tls_acme.go b/option/tls_acme.go index 17d515e20b..636abc6ece 100644 --- a/option/tls_acme.go +++ b/option/tls_acme.go @@ -4,10 +4,12 @@ import ( C "github.com/sagernet/sing-box/constant" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" ) type InboundACMEOptions struct { - Domain Listable[string] `json:"domain,omitempty"` + Domain badoption.Listable[string] `json:"domain,omitempty"` DataDirectory string `json:"data_directory,omitempty"` DefaultServerName string `json:"default_server_name,omitempty"` Email string `json:"email,omitempty"` @@ -18,6 +20,7 @@ type InboundACMEOptions struct { AlternativeTLSPort uint16 `json:"alternative_tls_port,omitempty"` ExternalAccount *ACMEExternalAccountOptions `json:"external_account,omitempty"` DNS01Challenge *ACMEDNS01ChallengeOptions `json:"dns01_challenge,omitempty"` + Profile string `json:"profile,omitempty"` } type ACMEExternalAccountOptions struct { @@ -29,6 +32,7 @@ type _ACMEDNS01ChallengeOptions struct { Provider string `json:"provider,omitempty"` AliDNSOptions ACMEDNS01AliDNSOptions `json:"-"` CloudflareOptions ACMEDNS01CloudflareOptions `json:"-"` + ACMEDNSOptions ACMEDNS01ACMEDNSOptions `json:"-"` } type ACMEDNS01ChallengeOptions _ACMEDNS01ChallengeOptions @@ -40,12 +44,14 @@ func (o ACMEDNS01ChallengeOptions) MarshalJSON() ([]byte, error) { v = o.AliDNSOptions case C.DNSProviderCloudflare: v = o.CloudflareOptions + case C.DNSProviderACMEDNS: + v = o.ACMEDNSOptions case "": return nil, E.New("missing provider type") default: return nil, E.New("unknown provider type: " + o.Provider) } - return MarshallObjects((_ACMEDNS01ChallengeOptions)(o), v) + return badjson.MarshallObjects((_ACMEDNS01ChallengeOptions)(o), v) } func (o *ACMEDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error { @@ -59,10 +65,12 @@ func (o *ACMEDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error { v = &o.AliDNSOptions case C.DNSProviderCloudflare: v = &o.CloudflareOptions + case C.DNSProviderACMEDNS: + v = &o.ACMEDNSOptions default: return E.New("unknown provider type: " + o.Provider) } - err = UnmarshallExcluded(bytes, (*_ACMEDNS01ChallengeOptions)(o), v) + err = badjson.UnmarshallExcluded(bytes, (*_ACMEDNS01ChallengeOptions)(o), v) if err != nil { return err } @@ -73,8 +81,17 @@ type ACMEDNS01AliDNSOptions struct { AccessKeyID string `json:"access_key_id,omitempty"` AccessKeySecret string `json:"access_key_secret,omitempty"` RegionID string `json:"region_id,omitempty"` + SecurityToken string `json:"security_token,omitempty"` } type ACMEDNS01CloudflareOptions struct { - APIToken string `json:"api_token,omitempty"` + APIToken string `json:"api_token,omitempty"` + ZoneToken string `json:"zone_token,omitempty"` +} + +type ACMEDNS01ACMEDNSOptions struct { + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Subdomain string `json:"subdomain,omitempty"` + ServerURL string `json:"server_url,omitempty"` } diff --git a/option/tls_tricks.go b/option/tls_tricks.go new file mode 100644 index 0000000000..e1c88664a2 --- /dev/null +++ b/option/tls_tricks.go @@ -0,0 +1,8 @@ +package option + +type TLSTricksOptions struct { + MixedCaseSNI bool `json:"mixedcase_sni,omitempty"` + PaddingMode string `json:"padding_mode,omitempty"` + PaddingSize string `json:"padding_size,omitempty"` + PaddingSNI string `json:"padding_sni,omitempty"` +} diff --git a/option/tuic.go b/option/tuic.go index 736d5a6606..51cc0a5b96 100644 --- a/option/tuic.go +++ b/option/tuic.go @@ -1,13 +1,16 @@ package option +import "github.com/sagernet/sing/common/json/badoption" + type TUICInboundOptions struct { ListenOptions - Users []TUICUser `json:"users,omitempty"` - CongestionControl string `json:"congestion_control,omitempty"` - AuthTimeout Duration `json:"auth_timeout,omitempty"` - ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"` - Heartbeat Duration `json:"heartbeat,omitempty"` + Users []TUICUser `json:"users,omitempty"` + CongestionControl string `json:"congestion_control,omitempty"` + AuthTimeout badoption.Duration `json:"auth_timeout,omitempty"` + ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"` + Heartbeat badoption.Duration `json:"heartbeat,omitempty"` InboundTLSOptionsContainer + QUICOptions } type TUICUser struct { @@ -19,13 +22,14 @@ type TUICUser struct { type TUICOutboundOptions struct { DialerOptions ServerOptions - UUID string `json:"uuid,omitempty"` - Password string `json:"password,omitempty"` - CongestionControl string `json:"congestion_control,omitempty"` - UDPRelayMode string `json:"udp_relay_mode,omitempty"` - UDPOverStream bool `json:"udp_over_stream,omitempty"` - ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"` - Heartbeat Duration `json:"heartbeat,omitempty"` - Network NetworkList `json:"network,omitempty"` + UUID string `json:"uuid,omitempty"` + Password string `json:"password,omitempty"` + CongestionControl string `json:"congestion_control,omitempty"` + UDPRelayMode string `json:"udp_relay_mode,omitempty"` + UDPOverStream bool `json:"udp_over_stream,omitempty"` + ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"` + Heartbeat badoption.Duration `json:"heartbeat,omitempty"` + Network NetworkList `json:"network,omitempty"` OutboundTLSOptionsContainer + QUICOptions } diff --git a/option/tun.go b/option/tun.go index ac66a8061c..379aa7c330 100644 --- a/option/tun.go +++ b/option/tun.go @@ -1,31 +1,90 @@ package option -import "net/netip" +import ( + "net/netip" + "strconv" + + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badoption" +) type TunInboundOptions struct { - InterfaceName string `json:"interface_name,omitempty"` - MTU uint32 `json:"mtu,omitempty"` - GSO bool `json:"gso,omitempty"` - Inet4Address Listable[netip.Prefix] `json:"inet4_address,omitempty"` - Inet6Address Listable[netip.Prefix] `json:"inet6_address,omitempty"` - AutoRoute bool `json:"auto_route,omitempty"` - StrictRoute bool `json:"strict_route,omitempty"` - Inet4RouteAddress Listable[netip.Prefix] `json:"inet4_route_address,omitempty"` - Inet6RouteAddress Listable[netip.Prefix] `json:"inet6_route_address,omitempty"` - Inet4RouteExcludeAddress Listable[netip.Prefix] `json:"inet4_route_exclude_address,omitempty"` - Inet6RouteExcludeAddress Listable[netip.Prefix] `json:"inet6_route_exclude_address,omitempty"` - IncludeInterface Listable[string] `json:"include_interface,omitempty"` - ExcludeInterface Listable[string] `json:"exclude_interface,omitempty"` - IncludeUID Listable[uint32] `json:"include_uid,omitempty"` - IncludeUIDRange Listable[string] `json:"include_uid_range,omitempty"` - ExcludeUID Listable[uint32] `json:"exclude_uid,omitempty"` - ExcludeUIDRange Listable[string] `json:"exclude_uid_range,omitempty"` - IncludeAndroidUser Listable[int] `json:"include_android_user,omitempty"` - IncludePackage Listable[string] `json:"include_package,omitempty"` - ExcludePackage Listable[string] `json:"exclude_package,omitempty"` - EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty"` - UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` - Stack string `json:"stack,omitempty"` - Platform *TunPlatformOptions `json:"platform,omitempty"` + InterfaceName string `json:"interface_name,omitempty"` + MTU uint32 `json:"mtu,omitempty"` + Address badoption.Listable[netip.Prefix] `json:"address,omitempty"` + DNSMode string `json:"dns_mode,omitempty"` + DNSAddress badoption.Listable[netip.Addr] `json:"dns_address,omitempty"` + AutoRoute bool `json:"auto_route,omitempty"` + IPRoute2TableIndex int `json:"iproute2_table_index,omitempty"` + IPRoute2RuleIndex int `json:"iproute2_rule_index,omitempty"` + AutoRedirect bool `json:"auto_redirect,omitempty"` + AutoRedirectInputMark FwMark `json:"auto_redirect_input_mark,omitempty"` + AutoRedirectOutputMark FwMark `json:"auto_redirect_output_mark,omitempty"` + AutoRedirectResetMark FwMark `json:"auto_redirect_reset_mark,omitempty"` + AutoRedirectNFQueue uint16 `json:"auto_redirect_nfqueue,omitempty"` + AutoRedirectFallbackRuleIndex int `json:"auto_redirect_iproute2_fallback_rule_index,omitempty"` + ExcludeMPTCP bool `json:"exclude_mptcp,omitempty"` + LoopbackAddress badoption.Listable[netip.Addr] `json:"loopback_address,omitempty"` + StrictRoute bool `json:"strict_route,omitempty"` + RouteAddress badoption.Listable[netip.Prefix] `json:"route_address,omitempty"` + RouteAddressSet badoption.Listable[string] `json:"route_address_set,omitempty"` + RouteExcludeAddress badoption.Listable[netip.Prefix] `json:"route_exclude_address,omitempty"` + RouteExcludeAddressSet badoption.Listable[string] `json:"route_exclude_address_set,omitempty"` + IncludeInterface badoption.Listable[string] `json:"include_interface,omitempty"` + ExcludeInterface badoption.Listable[string] `json:"exclude_interface,omitempty"` + IncludeUID badoption.Listable[uint32] `json:"include_uid,omitempty"` + IncludeUIDRange badoption.Listable[string] `json:"include_uid_range,omitempty"` + ExcludeUID badoption.Listable[uint32] `json:"exclude_uid,omitempty"` + ExcludeUIDRange badoption.Listable[string] `json:"exclude_uid_range,omitempty"` + IncludeAndroidUser badoption.Listable[int] `json:"include_android_user,omitempty"` + IncludePackage badoption.Listable[string] `json:"include_package,omitempty"` + ExcludePackage badoption.Listable[string] `json:"exclude_package,omitempty"` + IncludeMACAddress badoption.Listable[string] `json:"include_mac_address,omitempty"` + ExcludeMACAddress badoption.Listable[string] `json:"exclude_mac_address,omitempty"` + UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` + Stack string `json:"stack,omitempty"` + Platform *TunPlatformOptions `json:"platform,omitempty"` InboundOptions + + // Deprecated: removed + GSO bool `json:"gso,omitempty"` + // Deprecated: merged to Address + Inet4Address badoption.Listable[netip.Prefix] `json:"inet4_address,omitempty"` + // Deprecated: merged to Address + Inet6Address badoption.Listable[netip.Prefix] `json:"inet6_address,omitempty"` + // Deprecated: merged to RouteAddress + Inet4RouteAddress badoption.Listable[netip.Prefix] `json:"inet4_route_address,omitempty"` + // Deprecated: merged to RouteAddress + Inet6RouteAddress badoption.Listable[netip.Prefix] `json:"inet6_route_address,omitempty"` + // Deprecated: merged to RouteExcludeAddress + Inet4RouteExcludeAddress badoption.Listable[netip.Prefix] `json:"inet4_route_exclude_address,omitempty"` + // Deprecated: merged to RouteExcludeAddress + Inet6RouteExcludeAddress badoption.Listable[netip.Prefix] `json:"inet6_route_exclude_address,omitempty"` + // Deprecated: removed + EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty"` +} + +type FwMark uint32 + +func (f FwMark) MarshalJSON() ([]byte, error) { + return json.Marshal(F.ToString("0x", strconv.FormatUint(uint64(f), 16))) +} + +func (f *FwMark) UnmarshalJSON(bytes []byte) error { + var stringValue string + err := json.Unmarshal(bytes, &stringValue) + if err != nil { + if rawErr := json.Unmarshal(bytes, (*uint32)(f)); rawErr == nil { + return nil + } + return E.Cause(err, "invalid number or string mark") + } + intValue, err := strconv.ParseUint(stringValue, 0, 32) + if err != nil { + return err + } + *f = FwMark(intValue) + return nil } diff --git a/option/tun_platform.go b/option/tun_platform.go index a0a54eed05..b42f6894f4 100644 --- a/option/tun_platform.go +++ b/option/tun_platform.go @@ -1,5 +1,7 @@ package option +import "github.com/sagernet/sing/common/json/badoption" + type TunPlatformOptions struct { HTTPProxy *HTTPProxyOptions `json:"http_proxy,omitempty"` } @@ -7,6 +9,6 @@ type TunPlatformOptions struct { type HTTPProxyOptions struct { Enabled bool `json:"enabled,omitempty"` ServerOptions - BypassDomain Listable[string] `json:"bypass_domain,omitempty"` - MatchDomain Listable[string] `json:"match_domain,omitempty"` + BypassDomain badoption.Listable[string] `json:"bypass_domain,omitempty"` + MatchDomain badoption.Listable[string] `json:"match_domain,omitempty"` } diff --git a/option/tunnel.go b/option/tunnel.go new file mode 100644 index 0000000000..cc1df36b66 --- /dev/null +++ b/option/tunnel.go @@ -0,0 +1,21 @@ +package option + +import "github.com/sagernet/sing/common/json/badoption" + +type TunnelClientEndpointOptions struct { + UUID string `json:"uuid"` + Key string `json:"key"` + Outbound Outbound `json:"outbound"` +} + +type TunnelServerEndpointOptions struct { + UUID string `json:"uuid"` + Users []TunnelUser `json:"users"` + Inbound Inbound `json:"inbound"` + ConnectTimeout badoption.Duration `json:"connect_timeout,omitempty"` +} + +type TunnelUser struct { + UUID string `json:"uuid"` + Key string `json:"key"` +} diff --git a/option/types.go b/option/types.go index 83fee8f05a..87cf382c65 100644 --- a/option/types.go +++ b/option/types.go @@ -1,12 +1,9 @@ package option import ( - "net/http" - "net/netip" "strings" - "time" - "github.com/sagernet/sing-dns" + C "github.com/sagernet/sing-box/constant" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" @@ -15,76 +12,6 @@ import ( mDNS "github.com/miekg/dns" ) -type ListenAddress netip.Addr - -func NewListenAddress(addr netip.Addr) *ListenAddress { - address := ListenAddress(addr) - return &address -} - -func (a ListenAddress) MarshalJSON() ([]byte, error) { - addr := netip.Addr(a) - if !addr.IsValid() { - return nil, nil - } - return json.Marshal(addr.String()) -} - -func (a *ListenAddress) UnmarshalJSON(content []byte) error { - var value string - err := json.Unmarshal(content, &value) - if err != nil { - return err - } - addr, err := netip.ParseAddr(value) - if err != nil { - return err - } - *a = ListenAddress(addr) - return nil -} - -func (a *ListenAddress) Build() netip.Addr { - if a == nil { - return netip.AddrFrom4([4]byte{127, 0, 0, 1}) - } - return (netip.Addr)(*a) -} - -type AddrPrefix netip.Prefix - -func (a AddrPrefix) MarshalJSON() ([]byte, error) { - prefix := netip.Prefix(a) - if prefix.Bits() == prefix.Addr().BitLen() { - return json.Marshal(prefix.Addr().String()) - } else { - return json.Marshal(prefix.String()) - } -} - -func (a *AddrPrefix) UnmarshalJSON(content []byte) error { - var value string - err := json.Unmarshal(content, &value) - if err != nil { - return err - } - prefix, prefixErr := netip.ParsePrefix(value) - if prefixErr == nil { - *a = AddrPrefix(prefix) - return nil - } - addr, addrErr := netip.ParseAddr(value) - if addrErr == nil { - *a = AddrPrefix(netip.PrefixFrom(addr, addr.BitLen())) - return nil - } - return prefixErr -} - -func (a AddrPrefix) Build() netip.Prefix { - return netip.Prefix(a) -} - type NetworkList string func (v *NetworkList) UnmarshalJSON(content []byte) error { @@ -101,7 +28,6 @@ func (v *NetworkList) UnmarshalJSON(content []byte) error { for _, networkName := range networkList { switch networkName { case N.NetworkTCP, N.NetworkUDP: - break default: return E.New("unknown network: " + networkName) } @@ -117,45 +43,38 @@ func (v NetworkList) Build() []string { return strings.Split(string(v), "\n") } -type Listable[T any] []T - -func (l Listable[T]) MarshalJSON() ([]byte, error) { - arrayList := []T(l) - if len(arrayList) == 1 { - return json.Marshal(arrayList[0]) - } - return json.Marshal(arrayList) -} - -func (l *Listable[T]) UnmarshalJSON(content []byte) error { - err := json.Unmarshal(content, (*[]T)(l)) - if err == nil { - return nil - } - var singleItem T - newError := json.Unmarshal(content, &singleItem) - if newError != nil { - return E.Errors(err, newError) +type DomainStrategy C.DomainStrategy + +func (s DomainStrategy) String() string { + switch C.DomainStrategy(s) { + case C.DomainStrategyAsIS: + return "" + case C.DomainStrategyPreferIPv4: + return "prefer_ipv4" + case C.DomainStrategyPreferIPv6: + return "prefer_ipv6" + case C.DomainStrategyIPv4Only: + return "ipv4_only" + case C.DomainStrategyIPv6Only: + return "ipv6_only" + default: + panic(E.New("unknown domain strategy: ", s)) } - *l = []T{singleItem} - return nil } -type DomainStrategy dns.DomainStrategy - func (s DomainStrategy) MarshalJSON() ([]byte, error) { var value string - switch dns.DomainStrategy(s) { - case dns.DomainStrategyAsIS: + switch C.DomainStrategy(s) { + case C.DomainStrategyAsIS: value = "" - // value = "AsIS" - case dns.DomainStrategyPreferIPv4: + // value = "as_is" + case C.DomainStrategyPreferIPv4: value = "prefer_ipv4" - case dns.DomainStrategyPreferIPv6: + case C.DomainStrategyPreferIPv6: value = "prefer_ipv6" - case dns.DomainStrategyUseIPv4: + case C.DomainStrategyIPv4Only: value = "ipv4_only" - case dns.DomainStrategyUseIPv6: + case C.DomainStrategyIPv6Only: value = "ipv6_only" default: return nil, E.New("unknown domain strategy: ", s) @@ -171,41 +90,21 @@ func (s *DomainStrategy) UnmarshalJSON(bytes []byte) error { } switch value { case "", "as_is": - *s = DomainStrategy(dns.DomainStrategyAsIS) + *s = DomainStrategy(C.DomainStrategyAsIS) case "prefer_ipv4": - *s = DomainStrategy(dns.DomainStrategyPreferIPv4) + *s = DomainStrategy(C.DomainStrategyPreferIPv4) case "prefer_ipv6": - *s = DomainStrategy(dns.DomainStrategyPreferIPv6) + *s = DomainStrategy(C.DomainStrategyPreferIPv6) case "ipv4_only": - *s = DomainStrategy(dns.DomainStrategyUseIPv4) + *s = DomainStrategy(C.DomainStrategyIPv4Only) case "ipv6_only": - *s = DomainStrategy(dns.DomainStrategyUseIPv6) + *s = DomainStrategy(C.DomainStrategyIPv6Only) default: return E.New("unknown domain strategy: ", value) } return nil } -type Duration time.Duration - -func (d Duration) MarshalJSON() ([]byte, error) { - return json.Marshal((time.Duration)(d).String()) -} - -func (d *Duration) UnmarshalJSON(bytes []byte) error { - var value string - err := json.Unmarshal(bytes, &value) - if err != nil { - return err - } - duration, err := ParseDuration(value) - if err != nil { - return err - } - *d = Duration(duration) - return nil -} - type DNSQueryType uint16 func (t DNSQueryType) String() string { @@ -251,14 +150,46 @@ func DNSQueryTypeToString(queryType uint16) string { return F.ToString(queryType) } -type HTTPHeader map[string]Listable[string] +type NetworkStrategy C.NetworkStrategy -func (h HTTPHeader) Build() http.Header { - header := make(http.Header) - for name, values := range h { - for _, value := range values { - header.Add(name, value) - } +func (n NetworkStrategy) MarshalJSON() ([]byte, error) { + return json.Marshal(C.NetworkStrategy(n).String()) +} + +func (n *NetworkStrategy) UnmarshalJSON(content []byte) error { + var value string + err := json.Unmarshal(content, &value) + if err != nil { + return err + } + strategy, loaded := C.StringToNetworkStrategy[value] + if !loaded { + return E.New("unknown network strategy: ", value) } - return header + *n = NetworkStrategy(strategy) + return nil +} + +type InterfaceType C.InterfaceType + +func (t InterfaceType) Build() C.InterfaceType { + return C.InterfaceType(t) +} + +func (t InterfaceType) MarshalJSON() ([]byte, error) { + return json.Marshal(C.InterfaceType(t).String()) +} + +func (t *InterfaceType) UnmarshalJSON(content []byte) error { + var value string + err := json.Unmarshal(content, &value) + if err != nil { + return err + } + interfaceType, loaded := C.StringToInterfaceType[value] + if !loaded { + return E.New("unknown interface type: ", value) + } + *t = InterfaceType(interfaceType) + return nil } diff --git a/option/udp_over_tcp.go b/option/udp_over_tcp.go index e8a7a9726e..b496017a41 100644 --- a/option/udp_over_tcp.go +++ b/option/udp_over_tcp.go @@ -26,5 +26,5 @@ func (o *UDPOverTCPOptions) UnmarshalJSON(bytes []byte) error { if err == nil { return nil } - return json.Unmarshal(bytes, (*_UDPOverTCPOptions)(o)) + return json.UnmarshalDisallowUnknownFields(bytes, (*_UDPOverTCPOptions)(o)) } diff --git a/option/v2ray_transport.go b/option/v2ray_transport.go index fcd81f9462..3e2f3c2e86 100644 --- a/option/v2ray_transport.go +++ b/option/v2ray_transport.go @@ -1,9 +1,16 @@ package option import ( + "net/http" + "strings" + + Xbadoption "github.com/sagernet/sing-box/common/xray/json/badoption" + "github.com/sagernet/sing-box/common/xray/utils" C "github.com/sagernet/sing-box/constant" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" ) type _V2RayTransportOptions struct { @@ -13,6 +20,8 @@ type _V2RayTransportOptions struct { QUICOptions V2RayQUICOptions `json:"-"` GRPCOptions V2RayGRPCOptions `json:"-"` HTTPUpgradeOptions V2RayHTTPUpgradeOptions `json:"-"` + XHTTPOptions V2RayXHTTPOptions `json:"-"` + DNSTTOptions DnsttOptions `json:"-"` } type V2RayTransportOptions _V2RayTransportOptions @@ -30,12 +39,14 @@ func (o V2RayTransportOptions) MarshalJSON() ([]byte, error) { v = o.GRPCOptions case C.V2RayTransportTypeHTTPUpgrade: v = o.HTTPUpgradeOptions + case C.V2RayTransportTypeXHTTP: + v = o.XHTTPOptions case "": return nil, E.New("missing transport type") default: return nil, E.New("unknown transport type: " + o.Type) } - return MarshallObjects((_V2RayTransportOptions)(o), v) + return badjson.MarshallObjects((_V2RayTransportOptions)(o), v) } func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error { @@ -55,10 +66,12 @@ func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error { v = &o.GRPCOptions case C.V2RayTransportTypeHTTPUpgrade: v = &o.HTTPUpgradeOptions + case C.V2RayTransportTypeXHTTP: + v = &o.XHTTPOptions default: return E.New("unknown transport type: " + o.Type) } - err = UnmarshallExcluded(bytes, (*_V2RayTransportOptions)(o), v) + err = badjson.UnmarshallExcluded(bytes, (*_V2RayTransportOptions)(o), v) if err != nil { return err } @@ -66,33 +79,393 @@ func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error { } type V2RayHTTPOptions struct { - Host Listable[string] `json:"host,omitempty"` - Path string `json:"path,omitempty"` - Method string `json:"method,omitempty"` - Headers HTTPHeader `json:"headers,omitempty"` - IdleTimeout Duration `json:"idle_timeout,omitempty"` - PingTimeout Duration `json:"ping_timeout,omitempty"` + Host badoption.Listable[string] `json:"host,omitempty"` + Path string `json:"path,omitempty"` + Method string `json:"method,omitempty"` + Headers badoption.HTTPHeader `json:"headers,omitempty"` + IdleTimeout badoption.Duration `json:"idle_timeout,omitempty"` + PingTimeout badoption.Duration `json:"ping_timeout,omitempty"` } type V2RayWebsocketOptions struct { - Path string `json:"path,omitempty"` - Headers HTTPHeader `json:"headers,omitempty"` - MaxEarlyData uint32 `json:"max_early_data,omitempty"` - EarlyDataHeaderName string `json:"early_data_header_name,omitempty"` + Path string `json:"path,omitempty"` + Headers badoption.HTTPHeader `json:"headers,omitempty"` + MaxEarlyData uint32 `json:"max_early_data,omitempty"` + EarlyDataHeaderName string `json:"early_data_header_name,omitempty"` } type V2RayQUICOptions struct{} type V2RayGRPCOptions struct { - ServiceName string `json:"service_name,omitempty"` - IdleTimeout Duration `json:"idle_timeout,omitempty"` - PingTimeout Duration `json:"ping_timeout,omitempty"` - PermitWithoutStream bool `json:"permit_without_stream,omitempty"` - ForceLite bool `json:"-"` // for test + ServiceName string `json:"service_name,omitempty"` + IdleTimeout badoption.Duration `json:"idle_timeout,omitempty"` + PingTimeout badoption.Duration `json:"ping_timeout,omitempty"` + PermitWithoutStream bool `json:"permit_without_stream,omitempty"` + ForceLite bool `json:"-"` // for test } type V2RayHTTPUpgradeOptions struct { - Host string `json:"host,omitempty"` - Path string `json:"path,omitempty"` - Headers HTTPHeader `json:"headers,omitempty"` + Host string `json:"host,omitempty"` + Path string `json:"path,omitempty"` + Headers badoption.HTTPHeader `json:"headers,omitempty"` +} + +type V2RayXHTTPBaseOptions struct { + Host string `json:"host,omitempty"` + Path string `json:"path,omitempty"` + Headers map[string]string `json:"headers,omitempty"` + DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` + XPaddingBytes Xbadoption.Range `json:"x_padding_bytes"` + NoGRPCHeader bool `json:"no_grpc_header,omitempty"` + NoSSEHeader bool `json:"no_sse_header,omitempty"` + ScMaxEachPostBytes Xbadoption.Range `json:"sc_max_each_post_bytes"` + ScMinPostsIntervalMs Xbadoption.Range `json:"sc_min_posts_interval_ms"` + ScMaxBufferedPosts int64 `json:"sc_max_buffered_posts,omitempty"` + ScStreamUpServerSecs Xbadoption.Range `json:"sc_stream_up_server_secs"` + Xmux *V2RayXHTTPXmuxOptions `json:"xmux"` + XPaddingObfsMode bool `json:"x_padding_obfs_mode,omitempty"` + XPaddingKey string `json:"x_padding_key,omitempty"` + XPaddingHeader string `json:"x_padding_header,omitempty"` + XPaddingPlacement string `json:"x_padding_placement,omitempty"` + XPaddingMethod string `json:"x_padding_method,omitempty"` + UplinkHTTPMethod string `json:"uplink_http_method,omitempty"` + SessionPlacement string `json:"session_placement,omitempty"` + SessionKey string `json:"session_key,omitempty"` + SeqPlacement string `json:"seq_placement,omitempty"` + SeqKey string `json:"seq_key,omitempty"` + UplinkDataPlacement string `json:"uplink_data_placement,omitempty"` + UplinkDataKey string `json:"uplink_data_key,omitempty"` + UplinkChunkSize uint32 `json:"uplink_chunk_size,omitempty"` +} + +type _V2RayXHTTPOptions struct { + Mode string `json:"mode"` + V2RayXHTTPBaseOptions + Download *V2RayXHTTPDownloadOptions `json:"download"` +} + +type V2RayXHTTPOptions _V2RayXHTTPOptions + +type V2RayXHTTPDownloadOptions struct { + V2RayXHTTPBaseOptions + ServerOptions + OutboundTLSOptionsContainer + Detour string `json:"detour,omitempty"` +} + +const ( + PlacementQueryInHeader = "queryInHeader" + PlacementCookie = "cookie" + PlacementHeader = "header" + PlacementQuery = "query" + PlacementPath = "path" + PlacementBody = "body" +) + +func (c V2RayXHTTPOptions) MarshalJSON() ([]byte, error) { + return json.Marshal((*_V2RayXHTTPOptions)(&c)) +} + +func (c *V2RayXHTTPOptions) UnmarshalJSON(bytes []byte) error { + err := json.Unmarshal(bytes, (*_V2RayXHTTPOptions)(c)) + if err != nil { + return err + } + switch c.Mode { + case "": + c.Mode = "auto" + case "auto", "packet-up", "stream-up", "stream-one": + default: + return E.New("unsupported mode: " + c.Mode) + } + err = checkV2RayXHTTPBaseOptions(c.Mode, &c.V2RayXHTTPBaseOptions) + if err != nil { + return err + } + if c.Download != nil { + err = checkV2RayXHTTPBaseOptions(c.Mode, &c.Download.V2RayXHTTPBaseOptions) + if err != nil { + return err + } + } + return nil +} + +func checkV2RayXHTTPBaseOptions(mode string, options *V2RayXHTTPBaseOptions) error { + // Priority (client): host > serverName > address + for k := range options.Headers { + if strings.ToLower(k) == "host" { + return E.New(`"headers" can't contain "host"`) + } + } + if options.XPaddingBytes.From <= 0 || options.XPaddingBytes.To <= 0 { + return E.New("xPaddingBytes cannot be disabled") + } + if options.XPaddingKey == "" { + options.XPaddingKey = "x_padding" + } + if options.XPaddingHeader == "" { + options.XPaddingHeader = "X-Padding" + } + switch options.XPaddingPlacement { + case "": + options.XPaddingPlacement = "queryInHeader" + case "cookie", "header", "query", "queryInHeader": + default: + return E.New("unsupported padding placement: " + options.XPaddingPlacement) + } + switch options.XPaddingMethod { + case "": + options.XPaddingMethod = "repeat-x" + case "repeat-x", "tokenish": + default: + return E.New("unsupported padding method: " + options.XPaddingMethod) + } + switch options.UplinkDataPlacement { + case "": + options.UplinkDataPlacement = "body" + case "body": + case "cookie", "header": + if mode != "packet-up" { + return E.New("UplinkDataPlacement can be " + options.UplinkDataPlacement + " only in packet-up mode") + } + default: + return E.New("unsupported uplink data placement: " + options.UplinkDataPlacement) + } + if options.UplinkHTTPMethod == "" { + options.UplinkHTTPMethod = "POST" + } + options.UplinkHTTPMethod = strings.ToUpper(options.UplinkHTTPMethod) + if options.UplinkHTTPMethod == "GET" && mode != "packet-up" { + return E.New("uplinkHTTPMethod can be GET only in packet-up mode") + } + switch options.SessionPlacement { + case "": + options.SessionPlacement = "path" + case "path", "cookie", "header", "query": + default: + return E.New("unsupported session placement: " + options.SessionPlacement) + } + switch options.SeqPlacement { + case "": + options.SeqPlacement = "path" + case "path": + case "cookie", "header", "query": + if options.SessionPlacement == "path" { + return E.New("SeqPlacement must be path when SessionPlacement is path") + } + default: + return E.New("unsupported seq placement: " + options.SeqPlacement) + } + if options.SessionPlacement != "path" && options.SessionKey == "" { + switch options.SessionPlacement { + case "cookie", "query": + options.SessionKey = "x_session" + case "header": + options.SessionKey = "X-Session" + } + } + if options.SeqPlacement != "path" && options.SeqKey == "" { + switch options.SeqPlacement { + case "cookie", "query": + options.SeqKey = "x_seq" + case "header": + options.SeqKey = "X-Seq" + } + } + if options.UplinkDataPlacement != "body" && options.UplinkDataKey == "" { + switch options.UplinkDataPlacement { + case "cookie": + options.UplinkDataKey = "x_data" + case "header": + options.UplinkDataKey = "X-Data" + } + } + if options.UplinkChunkSize == 0 { + switch options.UplinkDataPlacement { + case "cookie": + options.UplinkChunkSize = 3 * 1024 // 3KB + case "header": + options.UplinkChunkSize = 4 * 1024 // 4KB + } + } else if options.UplinkChunkSize < 64 { + options.UplinkChunkSize = 64 + } + if options.Xmux == nil { + options.Xmux = &V2RayXHTTPXmuxOptions{} + options.Xmux.MaxConcurrency.From = 1 + options.Xmux.MaxConcurrency.To = 1 + options.Xmux.HMaxRequestTimes.From = 600 + options.Xmux.HMaxRequestTimes.To = 900 + options.Xmux.HMaxReusableSecs.From = 1800 + options.Xmux.HMaxReusableSecs.To = 3000 + } else if options.Xmux.MaxConnections.To > 0 && options.Xmux.MaxConcurrency.To > 0 { + return E.New("maxConnections cannot be specified together with maxConcurrency") + } + return nil +} + +func (c *V2RayXHTTPBaseOptions) GetNormalizedPath() string { + pathAndQuery := strings.SplitN(c.Path, "?", 2) + path := pathAndQuery[0] + if path == "" || path[0] != '/' { + path = "/" + path + } + if path[len(path)-1] != '/' { + path = path + "/" + } + return path +} + +func (c *V2RayXHTTPBaseOptions) GetNormalizedQuery() string { + pathAndQuery := strings.SplitN(c.Path, "?", 2) + query := "" + if len(pathAndQuery) > 1 { + query = pathAndQuery[1] + } + return query +} + +func (c *V2RayXHTTPBaseOptions) GetRequestHeader() http.Header { + header := http.Header{} + for k, v := range c.Headers { + header.Add(k, v) + } + if header.Get("User-Agent") == "" { + header.Set("User-Agent", utils.ChromeUA) + } + return header +} + +func (c *V2RayXHTTPBaseOptions) GetNormalizedXPaddingBytes() Xbadoption.Range { + if c.XPaddingBytes.To == 0 { + return Xbadoption.Range{ + From: 100, + To: 1000, + } + } + return c.XPaddingBytes +} + +func (c *V2RayXHTTPBaseOptions) GetNormalizedUplinkHTTPMethod() string { + if c.UplinkHTTPMethod == "" { + return "POST" + } + return c.UplinkHTTPMethod +} + +func (c *V2RayXHTTPBaseOptions) GetNormalizedScMaxEachPostBytes() Xbadoption.Range { + if c.ScMaxEachPostBytes.To == 0 { + return Xbadoption.Range{ + From: 1000000, + To: 1000000, + } + } + return c.ScMaxEachPostBytes +} + +func (c *V2RayXHTTPBaseOptions) GetNormalizedScMinPostsIntervalMs() Xbadoption.Range { + if c.ScMinPostsIntervalMs.To == 0 { + return Xbadoption.Range{ + From: 30, + To: 30, + } + } + return c.ScMinPostsIntervalMs +} + +func (c *V2RayXHTTPBaseOptions) GetNormalizedScMaxBufferedPosts() int { + if c.ScMaxBufferedPosts == 0 { + return 30 + } + + return int(c.ScMaxBufferedPosts) +} + +func (c *V2RayXHTTPBaseOptions) GetNormalizedScStreamUpServerSecs() Xbadoption.Range { + if c.ScStreamUpServerSecs.To == 0 { + return Xbadoption.Range{ + From: 20, + To: 80, + } + } + return c.ScStreamUpServerSecs +} + +func (c *V2RayXHTTPBaseOptions) GetNormalizedSessionPlacement() string { + if c.SessionPlacement == "" { + return PlacementPath + } + return c.SessionPlacement +} + +func (c *V2RayXHTTPBaseOptions) GetNormalizedSeqPlacement() string { + if c.SeqPlacement == "" { + return PlacementPath + } + return c.SeqPlacement +} + +func (c *V2RayXHTTPBaseOptions) GetNormalizedUplinkDataPlacement() string { + if c.UplinkDataPlacement == "" { + return PlacementBody + } + return c.UplinkDataPlacement +} + +func (c *V2RayXHTTPBaseOptions) GetNormalizedSessionKey() string { + if c.SessionKey != "" { + return c.SessionKey + } + switch c.GetNormalizedSessionPlacement() { + case PlacementHeader: + return "X-Session" + case PlacementCookie, PlacementQuery: + return "x_session" + default: + return "" + } +} + +func (c *V2RayXHTTPBaseOptions) GetNormalizedSeqKey() string { + if c.SeqKey != "" { + return c.SeqKey + } + switch c.GetNormalizedSeqPlacement() { + case PlacementHeader: + return "X-Seq" + case PlacementCookie, PlacementQuery: + return "x_seq" + default: + return "" + } +} + +type V2RayXHTTPXmuxOptions struct { + MaxConcurrency Xbadoption.Range `json:"max_concurrency"` + MaxConnections Xbadoption.Range `json:"max_connections"` + CMaxReuseTimes Xbadoption.Range `json:"c_max_reuse_times"` + HMaxRequestTimes Xbadoption.Range `json:"h_max_request_times"` + HMaxReusableSecs Xbadoption.Range `json:"h_max_reusable_secs"` + HKeepAlivePeriod int64 `json:"h_keep_alive_period"` +} + +func (m *V2RayXHTTPXmuxOptions) GetNormalizedMaxConcurrency() Xbadoption.Range { + return m.MaxConcurrency +} + +func (m *V2RayXHTTPXmuxOptions) GetNormalizedMaxConnections() Xbadoption.Range { + return m.MaxConnections +} + +func (m *V2RayXHTTPXmuxOptions) GetNormalizedCMaxReuseTimes() Xbadoption.Range { + return m.CMaxReuseTimes +} + +func (m *V2RayXHTTPXmuxOptions) GetNormalizedHMaxRequestTimes() Xbadoption.Range { + return m.HMaxRequestTimes +} + +func (m *V2RayXHTTPXmuxOptions) GetNormalizedHMaxReusableSecs() Xbadoption.Range { + return m.HMaxReusableSecs } diff --git a/option/vless.go b/option/vless.go index 5acf2aeed0..989bcc6502 100644 --- a/option/vless.go +++ b/option/vless.go @@ -2,7 +2,8 @@ package option type VLESSInboundOptions struct { ListenOptions - Users []VLESSUser `json:"users,omitempty"` + Users []VLESSUser `json:"users,omitempty"` + Decryption string `json:"decryption,omitempty"` InboundTLSOptionsContainer Multiplex *InboundMultiplexOptions `json:"multiplex,omitempty"` Transport *V2RayTransportOptions `json:"transport,omitempty"` @@ -17,9 +18,10 @@ type VLESSUser struct { type VLESSOutboundOptions struct { DialerOptions ServerOptions - UUID string `json:"uuid"` - Flow string `json:"flow,omitempty"` - Network NetworkList `json:"network,omitempty"` + UUID string `json:"uuid"` + Flow string `json:"flow,omitempty"` + Encryption string `json:"encryption,omitempty"` + Network NetworkList `json:"network,omitempty"` OutboundTLSOptionsContainer Multiplex *OutboundMultiplexOptions `json:"multiplex,omitempty"` Transport *V2RayTransportOptions `json:"transport,omitempty"` diff --git a/option/wireguard.go b/option/wireguard.go index 65bfad2061..7dcee2bd9c 100644 --- a/option/wireguard.go +++ b/option/wireguard.go @@ -1,28 +1,94 @@ package option -import "net/netip" +import ( + "net/netip" -type WireGuardOutboundOptions struct { + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common/json/badoption" + hiddify "github.com/sagernet/wireguard-go/hiddify" +) + +type WireGuardEndpointOptions struct { + System bool `json:"system,omitempty"` + Name string `json:"name,omitempty"` + MTU uint32 `json:"mtu,omitempty"` + Address badoption.Listable[netip.Prefix] `json:"address"` + PrivateKey string `json:"private_key"` + ListenPort uint16 `json:"listen_port,omitempty"` + Peers []WireGuardPeer `json:"peers,omitempty"` + UDPTimeout badoption.Duration `json:"udp_timeout,omitempty"` + Workers int `json:"workers,omitempty"` + PreallocatedBuffersPerPool uint32 `json:"preallocated_buffers_per_pool,omitempty"` + DisablePauses bool `json:"disable_pauses,omitempty"` DialerOptions - SystemInterface bool `json:"system_interface,omitempty"` - GSO bool `json:"gso,omitempty"` - InterfaceName string `json:"interface_name,omitempty"` - LocalAddress Listable[netip.Prefix] `json:"local_address"` - PrivateKey string `json:"private_key"` - Peers []WireGuardPeer `json:"peers,omitempty"` - ServerOptions - PeerPublicKey string `json:"peer_public_key"` - PreSharedKey string `json:"pre_shared_key,omitempty"` - Reserved []uint8 `json:"reserved,omitempty"` - Workers int `json:"workers,omitempty"` - MTU uint32 `json:"mtu,omitempty"` - Network NetworkList `json:"network,omitempty"` + + Noise hiddify.NoiseOptions `json:"noise,omitempty"` + AWG *AwgOptions `json:"awg,omitempty"` } type WireGuardPeer struct { + Address string `json:"address,omitempty"` + Port uint16 `json:"port,omitempty"` + PublicKey string `json:"public_key,omitempty"` + PreSharedKey string `json:"pre_shared_key,omitempty"` + AllowedIPs badoption.Listable[netip.Prefix] `json:"allowed_ips,omitempty"` + PersistentKeepaliveInterval uint16 `json:"persistent_keepalive_interval,omitempty"` + Reserved []uint8 `json:"reserved,omitempty"` +} + +type WARPEndpointOptions struct { + System bool `json:"system,omitempty"` + Name string `json:"name,omitempty"` + ListenPort uint16 `json:"listen_port,omitempty"` + UDPTimeout badoption.Duration `json:"udp_timeout,omitempty"` + Workers int `json:"workers,omitempty"` + PreallocatedBuffersPerPool uint32 `json:"preallocated_buffers_per_pool,omitempty"` + DisablePauses bool `json:"disable_pauses,omitempty"` + Profile WARPProfile `json:"profile,omitempty"` + DialerOptions + + UniqueIdentifier string `json:"unique_identifier,omitempty"` //h + ServerOptions //H + Noise hiddify.NoiseOptions `json:"noise,omitempty"` //H + AWG *AwgOptions `json:"awg,omitempty"` + *C.WARPConfig //H + MTU uint32 `json:"mtu,omitempty"` +} + +type WARPProfile struct { + ID string `json:"id,omitempty"` + PrivateKey string `json:"private_key,omitempty"` + AuthToken string `json:"auth_token,omitempty"` + Recreate bool `json:"recreate,omitempty"` + Detour string `json:"detour,omitempty"` + License string `json:"license,omitempty"` +} + +type LegacyWireGuardOutboundOptions struct { + DialerOptions + SystemInterface bool `json:"system_interface,omitempty"` + GSO bool `json:"gso,omitempty"` + InterfaceName string `json:"interface_name,omitempty"` + LocalAddress badoption.Listable[netip.Prefix] `json:"local_address"` + PrivateKey string `json:"private_key"` + Peers []LegacyWireGuardPeer `json:"peers,omitempty"` + ServerOptions + PeerPublicKey string `json:"peer_public_key"` + PreSharedKey string `json:"pre_shared_key,omitempty"` + Reserved []uint8 `json:"reserved,omitempty"` + Workers int `json:"workers,omitempty"` + PreallocatedBuffersPerPool uint32 `json:"preallocated_buffers_per_pool,omitempty"` + DisablePauses bool `json:"disable_pauses,omitempty"` + MTU uint32 `json:"mtu,omitempty"` + Network NetworkList `json:"network,omitempty"` + + Noise hiddify.NoiseOptions `json:"noise,omitempty"` +} + +type LegacyWireGuardPeer struct { ServerOptions - PublicKey string `json:"public_key,omitempty"` - PreSharedKey string `json:"pre_shared_key,omitempty"` - AllowedIPs Listable[string] `json:"allowed_ips,omitempty"` - Reserved []uint8 `json:"reserved,omitempty"` + PublicKey string `json:"public_key,omitempty"` + PreSharedKey string `json:"pre_shared_key,omitempty"` + AllowedIPs badoption.Listable[netip.Prefix] `json:"allowed_ips,omitempty"` + Reserved []uint8 `json:"reserved,omitempty"` } diff --git a/option/xray.go b/option/xray.go new file mode 100644 index 0000000000..07fe023da3 --- /dev/null +++ b/option/xray.go @@ -0,0 +1,12 @@ +package option + +type XrayOutboundOptions struct { + DialerOptions + Network NetworkList `json:"network,omitempty"` + UDPOverTCP *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"` + XConfig *map[string]any `json:"xconfig,omitempty"` + XDebug bool `json:"xdebug,omitempty"` + DeprecatedXrayOutboundJson *map[string]any `json:"xray_outbound_raw,omitempty"` + DeprecatedFragment any `json:"xray_fragment,omitempty"` + DeprecatedLogLevel *string `json:"xray_loglevel,omitempty"` +} diff --git a/outbound/block.go b/outbound/block.go deleted file mode 100644 index e73c44228b..0000000000 --- a/outbound/block.go +++ /dev/null @@ -1,52 +0,0 @@ -package outbound - -import ( - "context" - "io" - "net" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -var _ adapter.Outbound = (*Block)(nil) - -type Block struct { - myOutboundAdapter -} - -func NewBlock(logger log.ContextLogger, tag string) *Block { - return &Block{ - myOutboundAdapter{ - protocol: C.TypeBlock, - network: []string{N.NetworkTCP, N.NetworkUDP}, - logger: logger, - tag: tag, - }, - } -} - -func (h *Block) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - h.logger.InfoContext(ctx, "blocked connection to ", destination) - return nil, io.EOF -} - -func (h *Block) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - h.logger.InfoContext(ctx, "blocked packet connection to ", destination) - return nil, io.EOF -} - -func (h *Block) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - conn.Close() - h.logger.InfoContext(ctx, "blocked connection to ", metadata.Destination) - return nil -} - -func (h *Block) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - conn.Close() - h.logger.InfoContext(ctx, "blocked packet connection to ", metadata.Destination) - return nil -} diff --git a/outbound/builder.go b/outbound/builder.go deleted file mode 100644 index e4d6a80e06..0000000000 --- a/outbound/builder.go +++ /dev/null @@ -1,65 +0,0 @@ -package outbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" -) - -func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Outbound) (adapter.Outbound, error) { - var metadata *adapter.InboundContext - if tag != "" { - ctx, metadata = adapter.AppendContext(ctx) - metadata.Outbound = tag - } - if options.Type == "" { - return nil, E.New("missing outbound type") - } - ctx = ContextWithTag(ctx, tag) - switch options.Type { - case C.TypeDirect: - return NewDirect(router, logger, tag, options.DirectOptions) - case C.TypeBlock: - return NewBlock(logger, tag), nil - case C.TypeDNS: - return NewDNS(router, tag), nil - case C.TypeSOCKS: - return NewSocks(router, logger, tag, options.SocksOptions) - case C.TypeHTTP: - return NewHTTP(ctx, router, logger, tag, options.HTTPOptions) - case C.TypeShadowsocks: - return NewShadowsocks(ctx, router, logger, tag, options.ShadowsocksOptions) - case C.TypeVMess: - return NewVMess(ctx, router, logger, tag, options.VMessOptions) - case C.TypeTrojan: - return NewTrojan(ctx, router, logger, tag, options.TrojanOptions) - case C.TypeWireGuard: - return NewWireGuard(ctx, router, logger, tag, options.WireGuardOptions) - case C.TypeHysteria: - return NewHysteria(ctx, router, logger, tag, options.HysteriaOptions) - case C.TypeTor: - return NewTor(ctx, router, logger, tag, options.TorOptions) - case C.TypeSSH: - return NewSSH(ctx, router, logger, tag, options.SSHOptions) - case C.TypeShadowTLS: - return NewShadowTLS(ctx, router, logger, tag, options.ShadowTLSOptions) - case C.TypeShadowsocksR: - return NewShadowsocksR(ctx, router, logger, tag, options.ShadowsocksROptions) - case C.TypeVLESS: - return NewVLESS(ctx, router, logger, tag, options.VLESSOptions) - case C.TypeTUIC: - return NewTUIC(ctx, router, logger, tag, options.TUICOptions) - case C.TypeHysteria2: - return NewHysteria2(ctx, router, logger, tag, options.Hysteria2Options) - case C.TypeSelector: - return NewSelector(ctx, router, logger, tag, options.SelectorOptions) - case C.TypeURLTest: - return NewURLTest(ctx, router, logger, tag, options.URLTestOptions) - default: - return nil, E.New("unknown outbound type: ", options.Type) - } -} diff --git a/outbound/default.go b/outbound/default.go deleted file mode 100644 index 972aca9479..0000000000 --- a/outbound/default.go +++ /dev/null @@ -1,244 +0,0 @@ -package outbound - -import ( - "context" - "net" - "net/netip" - "os" - "time" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-dns" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/bufio" - "github.com/sagernet/sing/common/canceler" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -type myOutboundAdapter struct { - protocol string - network []string - router adapter.Router - logger log.ContextLogger - tag string - dependencies []string -} - -func (a *myOutboundAdapter) Type() string { - return a.protocol -} - -func (a *myOutboundAdapter) Tag() string { - return a.tag -} - -func (a *myOutboundAdapter) Network() []string { - return a.network -} - -func (a *myOutboundAdapter) Dependencies() []string { - return a.dependencies -} - -func (a *myOutboundAdapter) NewError(ctx context.Context, err error) { - NewError(a.logger, ctx, err) -} - -func withDialerDependency(options option.DialerOptions) []string { - if options.Detour != "" { - return []string{options.Detour} - } - return nil -} - -func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error { - ctx = adapter.WithContext(ctx, &metadata) - var outConn net.Conn - var err error - if len(metadata.DestinationAddresses) > 0 { - outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses) - } else { - outConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination) - } - if err != nil { - return N.ReportHandshakeFailure(conn, err) - } - err = N.ReportHandshakeSuccess(conn) - if err != nil { - outConn.Close() - return err - } - return CopyEarlyConn(ctx, conn, outConn) -} - -func NewDirectConnection(ctx context.Context, router adapter.Router, this N.Dialer, conn net.Conn, metadata adapter.InboundContext, domainStrategy dns.DomainStrategy) error { - ctx = adapter.WithContext(ctx, &metadata) - var outConn net.Conn - var err error - if len(metadata.DestinationAddresses) > 0 { - outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses) - } else if metadata.Destination.IsFqdn() { - var destinationAddresses []netip.Addr - destinationAddresses, err = router.Lookup(ctx, metadata.Destination.Fqdn, domainStrategy) - if err != nil { - return N.ReportHandshakeFailure(conn, err) - } - outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, destinationAddresses) - } else { - outConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination) - } - if err != nil { - return N.ReportHandshakeFailure(conn, err) - } - err = N.ReportHandshakeSuccess(conn) - if err != nil { - outConn.Close() - return err - } - return CopyEarlyConn(ctx, conn, outConn) -} - -func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext) error { - ctx = adapter.WithContext(ctx, &metadata) - var outConn net.PacketConn - var destinationAddress netip.Addr - var err error - if len(metadata.DestinationAddresses) > 0 { - outConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses) - } else { - outConn, err = this.ListenPacket(ctx, metadata.Destination) - } - if err != nil { - return N.ReportHandshakeFailure(conn, err) - } - err = N.ReportHandshakeSuccess(conn) - if err != nil { - outConn.Close() - return err - } - if destinationAddress.IsValid() { - if metadata.Destination.IsFqdn() { - if metadata.InboundOptions.UDPDisableDomainUnmapping { - outConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) - } else { - outConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) - } - } - if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded { - natConn.UpdateDestination(destinationAddress) - } - } - switch metadata.Protocol { - case C.ProtocolSTUN: - ctx, conn = canceler.NewPacketConn(ctx, conn, C.STUNTimeout) - case C.ProtocolQUIC: - ctx, conn = canceler.NewPacketConn(ctx, conn, C.QUICTimeout) - case C.ProtocolDNS: - ctx, conn = canceler.NewPacketConn(ctx, conn, C.DNSTimeout) - } - return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outConn)) -} - -func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext, domainStrategy dns.DomainStrategy) error { - ctx = adapter.WithContext(ctx, &metadata) - var outConn net.PacketConn - var destinationAddress netip.Addr - var err error - if len(metadata.DestinationAddresses) > 0 { - outConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses) - } else if metadata.Destination.IsFqdn() { - var destinationAddresses []netip.Addr - destinationAddresses, err = router.Lookup(ctx, metadata.Destination.Fqdn, domainStrategy) - if err != nil { - return N.ReportHandshakeFailure(conn, err) - } - outConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, destinationAddresses) - } else { - outConn, err = this.ListenPacket(ctx, metadata.Destination) - } - if err != nil { - return N.ReportHandshakeFailure(conn, err) - } - err = N.ReportHandshakeSuccess(conn) - if err != nil { - outConn.Close() - return err - } - if destinationAddress.IsValid() { - if metadata.Destination.IsFqdn() { - outConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) - } - if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded { - natConn.UpdateDestination(destinationAddress) - } - } - switch metadata.Protocol { - case C.ProtocolSTUN: - ctx, conn = canceler.NewPacketConn(ctx, conn, C.STUNTimeout) - case C.ProtocolQUIC: - ctx, conn = canceler.NewPacketConn(ctx, conn, C.QUICTimeout) - case C.ProtocolDNS: - ctx, conn = canceler.NewPacketConn(ctx, conn, C.DNSTimeout) - } - return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outConn)) -} - -func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) error { - if cachedReader, isCached := conn.(N.CachedReader); isCached { - payload := cachedReader.ReadCached() - if payload != nil && !payload.IsEmpty() { - _, err := serverConn.Write(payload.Bytes()) - payload.Release() - if err != nil { - serverConn.Close() - return err - } - return bufio.CopyConn(ctx, conn, serverConn) - } - } - if earlyConn, isEarlyConn := common.Cast[N.EarlyConn](serverConn); isEarlyConn && earlyConn.NeedHandshake() { - payload := buf.NewPacket() - err := conn.SetReadDeadline(time.Now().Add(C.ReadPayloadTimeout)) - if err != os.ErrInvalid { - if err != nil { - payload.Release() - serverConn.Close() - return err - } - _, err = payload.ReadOnceFrom(conn) - if err != nil && !E.IsTimeout(err) { - payload.Release() - serverConn.Close() - return E.Cause(err, "read payload") - } - err = conn.SetReadDeadline(time.Time{}) - if err != nil { - payload.Release() - serverConn.Close() - return err - } - } - _, err = serverConn.Write(payload.Bytes()) - payload.Release() - if err != nil { - serverConn.Close() - return N.ReportHandshakeFailure(conn, err) - } - } - return bufio.CopyConn(ctx, conn, serverConn) -} - -func NewError(logger log.ContextLogger, ctx context.Context, err error) { - common.Close(err) - if E.IsClosedOrCanceled(err) { - logger.DebugContext(ctx, "connection closed: ", err) - return - } - logger.ErrorContext(ctx, err) -} diff --git a/outbound/direct.go b/outbound/direct.go deleted file mode 100644 index 11f650e4d9..0000000000 --- a/outbound/direct.go +++ /dev/null @@ -1,170 +0,0 @@ -package outbound - -import ( - "context" - "net" - "net/netip" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/dialer" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-dns" - "github.com/sagernet/sing/common/bufio" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -var ( - _ adapter.Outbound = (*Direct)(nil) - _ N.ParallelDialer = (*Direct)(nil) -) - -type Direct struct { - myOutboundAdapter - dialer N.Dialer - domainStrategy dns.DomainStrategy - fallbackDelay time.Duration - overrideOption int - overrideDestination M.Socksaddr - loopBack *loopBackDetector -} - -func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (*Direct, error) { - options.UDPFragmentDefault = true - outboundDialer, err := dialer.New(router, options.DialerOptions) - if err != nil { - return nil, err - } - outbound := &Direct{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeDirect, - network: []string{N.NetworkTCP, N.NetworkUDP}, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, - domainStrategy: dns.DomainStrategy(options.DomainStrategy), - fallbackDelay: time.Duration(options.FallbackDelay), - dialer: outboundDialer, - loopBack: newLoopBackDetector(router), - } - if options.ProxyProtocol != 0 { - return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0") - } - if options.OverrideAddress != "" && options.OverridePort != 0 { - outbound.overrideOption = 1 - outbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort) - } else if options.OverrideAddress != "" { - outbound.overrideOption = 2 - outbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort) - } else if options.OverridePort != 0 { - outbound.overrideOption = 3 - outbound.overrideDestination = M.Socksaddr{Port: options.OverridePort} - } - return outbound, nil -} - -func (h *Direct) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - ctx, metadata := adapter.AppendContext(ctx) - metadata.Outbound = h.tag - metadata.Destination = destination - switch h.overrideOption { - case 1: - destination = h.overrideDestination - case 2: - newDestination := h.overrideDestination - newDestination.Port = destination.Port - destination = newDestination - case 3: - destination.Port = h.overrideDestination.Port - } - network = N.NetworkName(network) - switch network { - case N.NetworkTCP: - h.logger.InfoContext(ctx, "outbound connection to ", destination) - case N.NetworkUDP: - h.logger.InfoContext(ctx, "outbound packet connection to ", destination) - } - conn, err := h.dialer.DialContext(ctx, network, destination) - if err != nil { - return nil, err - } - return h.loopBack.NewConn(conn), nil -} - -func (h *Direct) DialParallel(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) { - ctx, metadata := adapter.AppendContext(ctx) - metadata.Outbound = h.tag - metadata.Destination = destination - switch h.overrideOption { - case 1, 2: - // override address - return h.DialContext(ctx, network, destination) - case 3: - destination.Port = h.overrideDestination.Port - } - network = N.NetworkName(network) - switch network { - case N.NetworkTCP: - h.logger.InfoContext(ctx, "outbound connection to ", destination) - case N.NetworkUDP: - h.logger.InfoContext(ctx, "outbound packet connection to ", destination) - } - var domainStrategy dns.DomainStrategy - if h.domainStrategy != dns.DomainStrategyAsIS { - domainStrategy = h.domainStrategy - } else { - domainStrategy = dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) - } - return N.DialParallel(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, h.fallbackDelay) -} - -func (h *Direct) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag - metadata.Destination = destination - originDestination := destination - switch h.overrideOption { - case 1: - destination = h.overrideDestination - case 2: - newDestination := h.overrideDestination - newDestination.Port = destination.Port - destination = newDestination - case 3: - destination.Port = h.overrideDestination.Port - } - if h.overrideOption == 0 { - h.logger.InfoContext(ctx, "outbound packet connection") - } else { - h.logger.InfoContext(ctx, "outbound packet connection to ", destination) - } - conn, err := h.dialer.ListenPacket(ctx, destination) - if err != nil { - return nil, err - } - conn = h.loopBack.NewPacketConn(bufio.NewPacketConn(conn), destination) - if originDestination != destination { - conn = bufio.NewNATPacketConn(bufio.NewPacketConn(conn), destination, originDestination) - } - return conn, nil -} - -func (h *Direct) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - if h.loopBack.CheckConn(metadata.Source.AddrPort(), M.AddrPortFromNet(conn.LocalAddr())) { - return E.New("reject loopback connection to ", metadata.Destination) - } - return NewConnection(ctx, h, conn, metadata) -} - -func (h *Direct) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - if h.loopBack.CheckPacketConn(metadata.Source.AddrPort(), M.AddrPortFromNet(conn.LocalAddr())) { - return E.New("reject loopback packet connection to ", metadata.Destination) - } - return NewPacketConnection(ctx, h, conn, metadata) -} diff --git a/outbound/direct_loopback_detect.go b/outbound/direct_loopback_detect.go deleted file mode 100644 index 1469b9d017..0000000000 --- a/outbound/direct_loopback_detect.go +++ /dev/null @@ -1,186 +0,0 @@ -package outbound - -import ( - "net" - "net/netip" - "sync" - - "github.com/sagernet/sing-box/adapter" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -type loopBackDetector struct { - router adapter.Router - connAccess sync.RWMutex - packetConnAccess sync.RWMutex - connMap map[netip.AddrPort]netip.AddrPort - packetConnMap map[uint16]uint16 -} - -func newLoopBackDetector(router adapter.Router) *loopBackDetector { - return &loopBackDetector{ - router: router, - connMap: make(map[netip.AddrPort]netip.AddrPort), - packetConnMap: make(map[uint16]uint16), - } -} - -func (l *loopBackDetector) NewConn(conn net.Conn) net.Conn { - source := M.AddrPortFromNet(conn.LocalAddr()) - if !source.IsValid() { - return conn - } - if udpConn, isUDPConn := conn.(abstractUDPConn); isUDPConn { - if !source.Addr().IsLoopback() { - _, err := l.router.InterfaceFinder().InterfaceByAddr(source.Addr()) - if err != nil { - return conn - } - } - if !N.IsPublicAddr(source.Addr()) { - return conn - } - l.packetConnAccess.Lock() - l.packetConnMap[source.Port()] = M.AddrPortFromNet(conn.RemoteAddr()).Port() - l.packetConnAccess.Unlock() - return &loopBackDetectUDPWrapper{abstractUDPConn: udpConn, detector: l, connPort: source.Port()} - } else { - l.connAccess.Lock() - l.connMap[source] = M.AddrPortFromNet(conn.RemoteAddr()) - l.connAccess.Unlock() - return &loopBackDetectWrapper{Conn: conn, detector: l, connAddr: source} - } -} - -func (l *loopBackDetector) NewPacketConn(conn N.NetPacketConn, destination M.Socksaddr) N.NetPacketConn { - source := M.AddrPortFromNet(conn.LocalAddr()) - if !source.IsValid() { - return conn - } - if !source.Addr().IsLoopback() { - _, err := l.router.InterfaceFinder().InterfaceByAddr(source.Addr()) - if err != nil { - return conn - } - } - l.packetConnAccess.Lock() - l.packetConnMap[source.Port()] = destination.AddrPort().Port() - l.packetConnAccess.Unlock() - return &loopBackDetectPacketWrapper{NetPacketConn: conn, detector: l, connPort: source.Port()} -} - -func (l *loopBackDetector) CheckConn(source netip.AddrPort, local netip.AddrPort) bool { - l.connAccess.RLock() - defer l.connAccess.RUnlock() - destination, loaded := l.connMap[source] - return loaded && destination != local -} - -func (l *loopBackDetector) CheckPacketConn(source netip.AddrPort, local netip.AddrPort) bool { - if !source.IsValid() { - return false - } - if !source.Addr().IsLoopback() { - _, err := l.router.InterfaceFinder().InterfaceByAddr(source.Addr()) - if err != nil { - return false - } - } - if N.IsPublicAddr(source.Addr()) { - return false - } - l.packetConnAccess.RLock() - defer l.packetConnAccess.RUnlock() - destinationPort, loaded := l.packetConnMap[source.Port()] - return loaded && destinationPort != local.Port() -} - -type loopBackDetectWrapper struct { - net.Conn - detector *loopBackDetector - connAddr netip.AddrPort - closeOnce sync.Once -} - -func (w *loopBackDetectWrapper) Close() error { - w.closeOnce.Do(func() { - w.detector.connAccess.Lock() - delete(w.detector.connMap, w.connAddr) - w.detector.connAccess.Unlock() - }) - return w.Conn.Close() -} - -func (w *loopBackDetectWrapper) ReaderReplaceable() bool { - return true -} - -func (w *loopBackDetectWrapper) WriterReplaceable() bool { - return true -} - -func (w *loopBackDetectWrapper) Upstream() any { - return w.Conn -} - -type loopBackDetectPacketWrapper struct { - N.NetPacketConn - detector *loopBackDetector - connPort uint16 - closeOnce sync.Once -} - -func (w *loopBackDetectPacketWrapper) Close() error { - w.closeOnce.Do(func() { - w.detector.packetConnAccess.Lock() - delete(w.detector.packetConnMap, w.connPort) - w.detector.packetConnAccess.Unlock() - }) - return w.NetPacketConn.Close() -} - -func (w *loopBackDetectPacketWrapper) ReaderReplaceable() bool { - return true -} - -func (w *loopBackDetectPacketWrapper) WriterReplaceable() bool { - return true -} - -func (w *loopBackDetectPacketWrapper) Upstream() any { - return w.NetPacketConn -} - -type abstractUDPConn interface { - net.Conn - net.PacketConn -} - -type loopBackDetectUDPWrapper struct { - abstractUDPConn - detector *loopBackDetector - connPort uint16 - closeOnce sync.Once -} - -func (w *loopBackDetectUDPWrapper) Close() error { - w.closeOnce.Do(func() { - w.detector.packetConnAccess.Lock() - delete(w.detector.packetConnMap, w.connPort) - w.detector.packetConnAccess.Unlock() - }) - return w.abstractUDPConn.Close() -} - -func (w *loopBackDetectUDPWrapper) ReaderReplaceable() bool { - return true -} - -func (w *loopBackDetectUDPWrapper) WriterReplaceable() bool { - return true -} - -func (w *loopBackDetectUDPWrapper) Upstream() any { - return w.abstractUDPConn -} diff --git a/outbound/http.go b/outbound/http.go deleted file mode 100644 index cfc03216fa..0000000000 --- a/outbound/http.go +++ /dev/null @@ -1,74 +0,0 @@ -package outbound - -import ( - "context" - "net" - "os" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/dialer" - "github.com/sagernet/sing-box/common/tls" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" - sHTTP "github.com/sagernet/sing/protocol/http" -) - -var _ adapter.Outbound = (*HTTP)(nil) - -type HTTP struct { - myOutboundAdapter - client *sHTTP.Client -} - -func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPOutboundOptions) (*HTTP, error) { - outboundDialer, err := dialer.New(router, options.DialerOptions) - if err != nil { - return nil, err - } - detour, err := tls.NewDialerFromOptions(ctx, router, outboundDialer, options.Server, common.PtrValueOrDefault(options.TLS)) - if err != nil { - return nil, err - } - return &HTTP{ - myOutboundAdapter{ - protocol: C.TypeHTTP, - network: []string{N.NetworkTCP}, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, - sHTTP.NewClient(sHTTP.Options{ - Dialer: detour, - Server: options.ServerOptions.Build(), - Username: options.Username, - Password: options.Password, - Path: options.Path, - Headers: options.Headers.Build(), - }), - }, nil -} - -func (h *HTTP) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - ctx, metadata := adapter.AppendContext(ctx) - metadata.Outbound = h.tag - metadata.Destination = destination - h.logger.InfoContext(ctx, "outbound connection to ", destination) - return h.client.DialContext(ctx, network, destination) -} - -func (h *HTTP) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - return nil, os.ErrInvalid -} - -func (h *HTTP) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *HTTP) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid -} diff --git a/outbound/hysteria2.go b/outbound/hysteria2.go deleted file mode 100644 index 9079e40308..0000000000 --- a/outbound/hysteria2.go +++ /dev/null @@ -1,125 +0,0 @@ -//go:build with_quic - -package outbound - -import ( - "context" - "net" - "os" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/dialer" - "github.com/sagernet/sing-box/common/tls" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-quic/hysteria" - "github.com/sagernet/sing-quic/hysteria2" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/bufio" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -var ( - _ adapter.Outbound = (*TUIC)(nil) - _ adapter.InterfaceUpdateListener = (*TUIC)(nil) -) - -type Hysteria2 struct { - myOutboundAdapter - client *hysteria2.Client -} - -func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2OutboundOptions) (*Hysteria2, error) { - options.UDPFragmentDefault = true - if options.TLS == nil || !options.TLS.Enabled { - return nil, C.ErrTLSRequired - } - tlsConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS)) - if err != nil { - return nil, err - } - var salamanderPassword string - if options.Obfs != nil { - if options.Obfs.Password == "" { - return nil, E.New("missing obfs password") - } - switch options.Obfs.Type { - case hysteria2.ObfsTypeSalamander: - salamanderPassword = options.Obfs.Password - default: - return nil, E.New("unknown obfs type: ", options.Obfs.Type) - } - } - outboundDialer, err := dialer.New(router, options.DialerOptions) - if err != nil { - return nil, err - } - networkList := options.Network.Build() - client, err := hysteria2.NewClient(hysteria2.ClientOptions{ - Context: ctx, - Dialer: outboundDialer, - Logger: logger, - BrutalDebug: options.BrutalDebug, - ServerAddress: options.ServerOptions.Build(), - SendBPS: uint64(options.UpMbps * hysteria.MbpsToBps), - ReceiveBPS: uint64(options.DownMbps * hysteria.MbpsToBps), - SalamanderPassword: salamanderPassword, - Password: options.Password, - TLSConfig: tlsConfig, - UDPDisabled: !common.Contains(networkList, N.NetworkUDP), - }) - if err != nil { - return nil, err - } - return &Hysteria2{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeHysteria2, - network: networkList, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, - client: client, - }, nil -} - -func (h *Hysteria2) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - switch N.NetworkName(network) { - case N.NetworkTCP: - h.logger.InfoContext(ctx, "outbound connection to ", destination) - return h.client.DialConn(ctx, destination) - case N.NetworkUDP: - conn, err := h.ListenPacket(ctx, destination) - if err != nil { - return nil, err - } - return bufio.NewBindPacketConn(conn, destination), nil - default: - return nil, E.New("unsupported network: ", network) - } -} - -func (h *Hysteria2) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - h.logger.InfoContext(ctx, "outbound packet connection to ", destination) - return h.client.ListenPacket(ctx) -} - -func (h *Hysteria2) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *Hysteria2) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewPacketConnection(ctx, h, conn, metadata) -} - -func (h *Hysteria2) InterfaceUpdated() { - h.client.CloseWithError(E.New("network changed")) -} - -func (h *Hysteria2) Close() error { - return h.client.CloseWithError(os.ErrClosed) -} diff --git a/outbound/hysteria_stub.go b/outbound/hysteria_stub.go deleted file mode 100644 index 84db5305bc..0000000000 --- a/outbound/hysteria_stub.go +++ /dev/null @@ -1,20 +0,0 @@ -//go:build !with_quic - -package outbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" -) - -func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (adapter.Outbound, error) { - return nil, C.ErrQUICNotIncluded -} - -func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2OutboundOptions) (adapter.Outbound, error) { - return nil, C.ErrQUICNotIncluded -} diff --git a/outbound/lookback.go b/outbound/lookback.go deleted file mode 100644 index aeb7451dba..0000000000 --- a/outbound/lookback.go +++ /dev/null @@ -1,14 +0,0 @@ -package outbound - -import "context" - -type outboundTagKey struct{} - -func ContextWithTag(ctx context.Context, outboundTag string) context.Context { - return context.WithValue(ctx, outboundTagKey{}, outboundTag) -} - -func TagFromContext(ctx context.Context) (string, bool) { - value, loaded := ctx.Value(outboundTagKey{}).(string) - return value, loaded -} diff --git a/outbound/proxy.go b/outbound/proxy.go deleted file mode 100644 index 6127f0f215..0000000000 --- a/outbound/proxy.go +++ /dev/null @@ -1,131 +0,0 @@ -package outbound - -import ( - std_bufio "bufio" - "context" - "crypto/rand" - "encoding/hex" - "net" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/auth" - "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/bufio" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/rw" - "github.com/sagernet/sing/protocol/http" - "github.com/sagernet/sing/protocol/socks" - "github.com/sagernet/sing/protocol/socks/socks4" - "github.com/sagernet/sing/protocol/socks/socks5" -) - -type ProxyListener struct { - ctx context.Context - logger log.ContextLogger - dialer N.Dialer - tcpListener *net.TCPListener - username string - password string - authenticator *auth.Authenticator -} - -func NewProxyListener(ctx context.Context, logger log.ContextLogger, dialer N.Dialer) *ProxyListener { - var usernameB [64]byte - var passwordB [64]byte - rand.Read(usernameB[:]) - rand.Read(passwordB[:]) - username := hex.EncodeToString(usernameB[:]) - password := hex.EncodeToString(passwordB[:]) - return &ProxyListener{ - ctx: ctx, - logger: logger, - dialer: dialer, - authenticator: auth.NewAuthenticator([]auth.User{{Username: username, Password: password}}), - username: username, - password: password, - } -} - -func (l *ProxyListener) Start() error { - tcpListener, err := net.ListenTCP("tcp", &net.TCPAddr{ - IP: net.IPv4(127, 0, 0, 1), - }) - if err != nil { - return err - } - l.tcpListener = tcpListener - go l.acceptLoop() - return nil -} - -func (l *ProxyListener) Port() uint16 { - if l.tcpListener == nil { - panic("start listener first") - } - return M.SocksaddrFromNet(l.tcpListener.Addr()).Port -} - -func (l *ProxyListener) Username() string { - return l.username -} - -func (l *ProxyListener) Password() string { - return l.password -} - -func (l *ProxyListener) Close() error { - return common.Close(l.tcpListener) -} - -func (l *ProxyListener) acceptLoop() { - for { - tcpConn, err := l.tcpListener.AcceptTCP() - if err != nil { - return - } - ctx := log.ContextWithNewID(l.ctx) - go func() { - hErr := l.accept(ctx, tcpConn) - if hErr != nil { - if E.IsClosedOrCanceled(hErr) { - l.logger.DebugContext(ctx, E.Cause(hErr, "proxy connection closed")) - return - } - l.logger.ErrorContext(ctx, E.Cause(hErr, "proxy")) - } - }() - } -} - -func (l *ProxyListener) accept(ctx context.Context, conn *net.TCPConn) error { - headerType, err := rw.ReadByte(conn) - if err != nil { - return err - } - switch headerType { - case socks4.Version, socks5.Version: - return socks.HandleConnection0(ctx, conn, headerType, l.authenticator, l, M.Metadata{}) - } - reader := std_bufio.NewReader(bufio.NewCachedReader(conn, buf.As([]byte{headerType}))) - return http.HandleConnection(ctx, conn, reader, l.authenticator, l, M.Metadata{}) -} - -func (l *ProxyListener) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata M.Metadata) error { - var metadata adapter.InboundContext - metadata.Network = N.NetworkTCP - metadata.Destination = upstreamMetadata.Destination - l.logger.InfoContext(ctx, "proxy connection to ", metadata.Destination) - return NewConnection(ctx, l.dialer, conn, metadata) -} - -func (l *ProxyListener) NewPacketConnection(ctx context.Context, conn N.PacketConn, upstreamMetadata M.Metadata) error { - var metadata adapter.InboundContext - metadata.Network = N.NetworkUDP - metadata.Destination = upstreamMetadata.Destination - l.logger.InfoContext(ctx, "proxy packet connection to ", metadata.Destination) - return NewPacketConnection(ctx, l.dialer, conn, metadata) -} diff --git a/outbound/shadowsocksr.go b/outbound/shadowsocksr.go deleted file mode 100644 index 615a71e4e4..0000000000 --- a/outbound/shadowsocksr.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build with_shadowsocksr - -package outbound - -import ( - "context" - "os" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" -) - -var _ int = "ShadowsocksR is deprecated and removed in sing-box 1.6.0" - -func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (adapter.Outbound, error) { - return nil, os.ErrInvalid -} diff --git a/outbound/shadowsocksr_stub.go b/outbound/shadowsocksr_stub.go deleted file mode 100644 index 94971da0bc..0000000000 --- a/outbound/shadowsocksr_stub.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build !with_shadowsocksr - -package outbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" -) - -func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (adapter.Outbound, error) { - return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0") -} diff --git a/outbound/tor_embed.go b/outbound/tor_embed.go deleted file mode 100644 index d80b49ae66..0000000000 --- a/outbound/tor_embed.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build with_embedded_tor && !(android || ios) - -package outbound - -import ( - "berty.tech/go-libtor" - "github.com/cretz/bine/tor" -) - -func newConfig() tor.StartConf { - return tor.StartConf{ - ProcessCreator: libtor.Creator, - UseEmbeddedControlConn: true, - } -} diff --git a/outbound/tor_embed_mobile.go b/outbound/tor_embed_mobile.go deleted file mode 100644 index 0900d8c938..0000000000 --- a/outbound/tor_embed_mobile.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build with_embedded_tor && (android || ios) - -package outbound - -import ( - "github.com/cretz/bine/tor" - "github.com/ooni/go-libtor" -) - -func newConfig() tor.StartConf { - return tor.StartConf{ - ProcessCreator: libtor.Creator, - UseEmbeddedControlConn: true, - } -} diff --git a/outbound/tor_external.go b/outbound/tor_external.go deleted file mode 100644 index 6bce95d1d9..0000000000 --- a/outbound/tor_external.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !with_embedded_tor - -package outbound - -import "github.com/cretz/bine/tor" - -func newConfig() tor.StartConf { - return tor.StartConf{} -} diff --git a/outbound/tuic_stub.go b/outbound/tuic_stub.go deleted file mode 100644 index a6372c9ee3..0000000000 --- a/outbound/tuic_stub.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build !with_quic - -package outbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" -) - -func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (adapter.Outbound, error) { - return nil, C.ErrQUICNotIncluded -} diff --git a/outbound/vless.go b/outbound/vless.go deleted file mode 100644 index 66080eafca..0000000000 --- a/outbound/vless.go +++ /dev/null @@ -1,220 +0,0 @@ -package outbound - -import ( - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/dialer" - "github.com/sagernet/sing-box/common/mux" - "github.com/sagernet/sing-box/common/tls" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-box/transport/v2ray" - "github.com/sagernet/sing-vmess/packetaddr" - "github.com/sagernet/sing-vmess/vless" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/bufio" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -var _ adapter.Outbound = (*VLESS)(nil) - -type VLESS struct { - myOutboundAdapter - dialer N.Dialer - client *vless.Client - serverAddr M.Socksaddr - multiplexDialer *mux.Client - tlsConfig tls.Config - transport adapter.V2RayClientTransport - packetAddr bool - xudp bool -} - -func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSOutboundOptions) (*VLESS, error) { - outboundDialer, err := dialer.New(router, options.DialerOptions) - if err != nil { - return nil, err - } - outbound := &VLESS{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeVLESS, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, - dialer: outboundDialer, - serverAddr: options.ServerOptions.Build(), - } - if options.TLS != nil { - outbound.tlsConfig, err = tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS)) - if err != nil { - return nil, err - } - } - if options.Transport != nil { - outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig) - if err != nil { - return nil, E.Cause(err, "create client transport: ", options.Transport.Type) - } - } - if options.PacketEncoding == nil { - outbound.xudp = true - } else { - switch *options.PacketEncoding { - case "": - case "packetaddr": - outbound.packetAddr = true - case "xudp": - outbound.xudp = true - default: - return nil, E.New("unknown packet encoding: ", options.PacketEncoding) - } - } - outbound.client, err = vless.NewClient(options.UUID, options.Flow, logger) - if err != nil { - return nil, err - } - outbound.multiplexDialer, err = mux.NewClientWithOptions((*vlessDialer)(outbound), logger, common.PtrValueOrDefault(options.Multiplex)) - if err != nil { - return nil, err - } - return outbound, nil -} - -func (h *VLESS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - if h.multiplexDialer == nil { - switch N.NetworkName(network) { - case N.NetworkTCP: - h.logger.InfoContext(ctx, "outbound connection to ", destination) - case N.NetworkUDP: - h.logger.InfoContext(ctx, "outbound packet connection to ", destination) - } - return (*vlessDialer)(h).DialContext(ctx, network, destination) - } else { - switch N.NetworkName(network) { - case N.NetworkTCP: - h.logger.InfoContext(ctx, "outbound multiplex connection to ", destination) - case N.NetworkUDP: - h.logger.InfoContext(ctx, "outbound multiplex packet connection to ", destination) - } - return h.multiplexDialer.DialContext(ctx, network, destination) - } -} - -func (h *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - if h.multiplexDialer == nil { - h.logger.InfoContext(ctx, "outbound packet connection to ", destination) - return (*vlessDialer)(h).ListenPacket(ctx, destination) - } else { - h.logger.InfoContext(ctx, "outbound multiplex packet connection to ", destination) - return h.multiplexDialer.ListenPacket(ctx, destination) - } -} - -func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *VLESS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewPacketConnection(ctx, h, conn, metadata) -} - -func (h *VLESS) InterfaceUpdated() { - if h.transport != nil { - h.transport.Close() - } - if h.multiplexDialer != nil { - h.multiplexDialer.Reset() - } - return -} - -func (h *VLESS) Close() error { - return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport) -} - -type vlessDialer VLESS - -func (h *vlessDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - ctx, metadata := adapter.AppendContext(ctx) - metadata.Outbound = h.tag - metadata.Destination = destination - var conn net.Conn - var err error - if h.transport != nil { - conn, err = h.transport.DialContext(ctx) - } else { - conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr) - if err == nil && h.tlsConfig != nil { - conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig) - } - } - if err != nil { - return nil, err - } - switch N.NetworkName(network) { - case N.NetworkTCP: - h.logger.InfoContext(ctx, "outbound connection to ", destination) - return h.client.DialEarlyConn(conn, destination) - case N.NetworkUDP: - h.logger.InfoContext(ctx, "outbound packet connection to ", destination) - if h.xudp { - return h.client.DialEarlyXUDPPacketConn(conn, destination) - } else if h.packetAddr { - if destination.IsFqdn() { - return nil, E.New("packetaddr: domain destination is not supported") - } - packetConn, err := h.client.DialEarlyPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}) - if err != nil { - return nil, err - } - return bufio.NewBindPacketConn(packetaddr.NewConn(packetConn, destination), destination), nil - } else { - return h.client.DialEarlyPacketConn(conn, destination) - } - default: - return nil, E.Extend(N.ErrUnknownNetwork, network) - } -} - -func (h *vlessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - h.logger.InfoContext(ctx, "outbound packet connection to ", destination) - ctx, metadata := adapter.AppendContext(ctx) - metadata.Outbound = h.tag - metadata.Destination = destination - var conn net.Conn - var err error - if h.transport != nil { - conn, err = h.transport.DialContext(ctx) - } else { - conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr) - if err == nil && h.tlsConfig != nil { - conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig) - } - } - if err != nil { - common.Close(conn) - return nil, err - } - if h.xudp { - return h.client.DialEarlyXUDPPacketConn(conn, destination) - } else if h.packetAddr { - if destination.IsFqdn() { - return nil, E.New("packetaddr: domain destination is not supported") - } - conn, err := h.client.DialEarlyPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}) - if err != nil { - return nil, err - } - return packetaddr.NewConn(conn, destination), nil - } else { - return h.client.DialEarlyPacketConn(conn, destination) - } -} diff --git a/outbound/wireguard.go b/outbound/wireguard.go deleted file mode 100644 index 7805e165a1..0000000000 --- a/outbound/wireguard.go +++ /dev/null @@ -1,240 +0,0 @@ -//go:build with_wireguard - -package outbound - -import ( - "context" - "encoding/base64" - "encoding/hex" - "fmt" - "net" - "net/netip" - "strings" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/dialer" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-box/transport/wireguard" - "github.com/sagernet/sing-dns" - "github.com/sagernet/sing-tun" - "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/x/list" - "github.com/sagernet/sing/service" - "github.com/sagernet/sing/service/pause" - "github.com/sagernet/wireguard-go/conn" - "github.com/sagernet/wireguard-go/device" -) - -var ( - _ adapter.Outbound = (*WireGuard)(nil) - _ adapter.InterfaceUpdateListener = (*WireGuard)(nil) -) - -type WireGuard struct { - myOutboundAdapter - ctx context.Context - workers int - peers []wireguard.PeerConfig - useStdNetBind bool - listener N.Dialer - ipcConf string - - pauseManager pause.Manager - pauseCallback *list.Element[pause.Callback] - bind conn.Bind - device *device.Device - tunDevice wireguard.Device -} - -func NewWireGuard(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (*WireGuard, error) { - outbound := &WireGuard{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeWireGuard, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, - ctx: ctx, - workers: options.Workers, - pauseManager: service.FromContext[pause.Manager](ctx), - } - peers, err := wireguard.ParsePeers(options) - if err != nil { - return nil, err - } - outbound.peers = peers - if len(options.LocalAddress) == 0 { - return nil, E.New("missing local address") - } - if options.GSO { - if options.GSO && options.Detour != "" { - return nil, E.New("gso is conflict with detour") - } - options.IsWireGuardListener = true - outbound.useStdNetBind = true - } - listener, err := dialer.New(router, options.DialerOptions) - if err != nil { - return nil, err - } - outbound.listener = listener - var privateKey string - { - bytes, err := base64.StdEncoding.DecodeString(options.PrivateKey) - if err != nil { - return nil, E.Cause(err, "decode private key") - } - privateKey = hex.EncodeToString(bytes) - } - outbound.ipcConf = "private_key=" + privateKey - mtu := options.MTU - if mtu == 0 { - mtu = 1408 - } - var wireTunDevice wireguard.Device - if !options.SystemInterface && tun.WithGVisor { - wireTunDevice, err = wireguard.NewStackDevice(options.LocalAddress, mtu) - } else { - wireTunDevice, err = wireguard.NewSystemDevice(router, options.InterfaceName, options.LocalAddress, mtu, options.GSO) - } - if err != nil { - return nil, E.Cause(err, "create WireGuard device") - } - outbound.tunDevice = wireTunDevice - return outbound, nil -} - -func (w *WireGuard) Start() error { - if common.Any(w.peers, func(peer wireguard.PeerConfig) bool { - return !peer.Endpoint.IsValid() - }) { - // wait for all outbounds to be started and continue in PortStart - return nil - } - return w.start() -} - -func (w *WireGuard) PostStart() error { - if common.All(w.peers, func(peer wireguard.PeerConfig) bool { - return peer.Endpoint.IsValid() - }) { - return nil - } - return w.start() -} - -func (w *WireGuard) start() error { - err := wireguard.ResolvePeers(w.ctx, w.router, w.peers) - if err != nil { - return err - } - var bind conn.Bind - if w.useStdNetBind { - bind = conn.NewStdNetBind(w.listener.(dialer.WireGuardListener)) - } else { - var ( - isConnect bool - connectAddr netip.AddrPort - reserved [3]uint8 - ) - peerLen := len(w.peers) - if peerLen == 1 { - isConnect = true - connectAddr = w.peers[0].Endpoint - reserved = w.peers[0].Reserved - } - bind = wireguard.NewClientBind(w.ctx, w, w.listener, isConnect, connectAddr, reserved) - } - wgDevice := device.NewDevice(w.tunDevice, bind, &device.Logger{ - Verbosef: func(format string, args ...interface{}) { - w.logger.Debug(fmt.Sprintf(strings.ToLower(format), args...)) - }, - Errorf: func(format string, args ...interface{}) { - w.logger.Error(fmt.Sprintf(strings.ToLower(format), args...)) - }, - }, w.workers) - ipcConf := w.ipcConf - for _, peer := range w.peers { - ipcConf += peer.GenerateIpcLines() - } - err = wgDevice.IpcSet(ipcConf) - if err != nil { - return E.Cause(err, "setup wireguard: \n", ipcConf) - } - w.device = wgDevice - w.pauseCallback = w.pauseManager.RegisterCallback(w.onPauseUpdated) - return w.tunDevice.Start() -} - -func (w *WireGuard) Close() error { - if w.device != nil { - w.device.Close() - } - if w.pauseCallback != nil { - w.pauseManager.UnregisterCallback(w.pauseCallback) - } - w.tunDevice.Close() - return nil -} - -func (w *WireGuard) InterfaceUpdated() { - w.device.BindUpdate() - return -} - -func (w *WireGuard) onPauseUpdated(event int) { - switch event { - case pause.EventDevicePaused: - w.device.Down() - case pause.EventDeviceWake: - w.device.Up() - } -} - -func (w *WireGuard) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - switch network { - case N.NetworkTCP: - w.logger.InfoContext(ctx, "outbound connection to ", destination) - case N.NetworkUDP: - w.logger.InfoContext(ctx, "outbound packet connection to ", destination) - } - if destination.IsFqdn() { - destinationAddresses, err := w.router.LookupDefault(ctx, destination.Fqdn) - if err != nil { - return nil, err - } - return N.DialSerial(ctx, w.tunDevice, network, destination, destinationAddresses) - } - return w.tunDevice.DialContext(ctx, network, destination) -} - -func (w *WireGuard) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - w.logger.InfoContext(ctx, "outbound packet connection to ", destination) - if destination.IsFqdn() { - destinationAddresses, err := w.router.LookupDefault(ctx, destination.Fqdn) - if err != nil { - return nil, err - } - packetConn, _, err := N.ListenSerial(ctx, w.tunDevice, destination, destinationAddresses) - if err != nil { - return nil, err - } - return packetConn, err - } - return w.tunDevice.ListenPacket(ctx, destination) -} - -func (w *WireGuard) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewDirectConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS) -} - -func (w *WireGuard) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewDirectPacketConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS) -} diff --git a/outbound/wireguard_stub.go b/outbound/wireguard_stub.go deleted file mode 100644 index 3a8b0e87a5..0000000000 --- a/outbound/wireguard_stub.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build !with_wireguard - -package outbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" -) - -func NewWireGuard(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (adapter.Outbound, error) { - return nil, E.New(`WireGuard is not included in this build, rebuild with -tags with_wireguard`) -} diff --git a/protocol/anytls/inbound.go b/protocol/anytls/inbound.go new file mode 100644 index 0000000000..e61e837a2a --- /dev/null +++ b/protocol/anytls/inbound.go @@ -0,0 +1,134 @@ +package anytls + +import ( + "context" + "net" + "strings" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/common/uot" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/auth" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + anytls "github.com/anytls/sing-anytls" + "github.com/anytls/sing-anytls/padding" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.AnyTLSInboundOptions](registry, C.TypeAnyTLS, NewInbound) +} + +type Inbound struct { + inbound.Adapter + tlsConfig tls.ServerConfig + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener + service *anytls.Service +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.AnyTLSInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeAnyTLS, tag), + router: uot.NewRouter(router, logger), + logger: logger, + } + + if options.TLS != nil && options.TLS.Enabled { + tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + inbound.tlsConfig = tlsConfig + } + + paddingScheme := padding.DefaultPaddingScheme + if len(options.PaddingScheme) > 0 { + paddingScheme = []byte(strings.Join(options.PaddingScheme, "\n")) + } + + service, err := anytls.NewService(anytls.ServiceConfig{ + Users: common.Map(options.Users, func(it option.AnyTLSUser) anytls.User { + return (anytls.User)(it) + }), + PaddingScheme: paddingScheme, + Handler: (*inboundHandler)(inbound), + Logger: logger, + }) + if err != nil { + return nil, err + } + inbound.service = service + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + }) + return inbound, nil +} + +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + if h.tlsConfig != nil { + err := h.tlsConfig.Start() + if err != nil { + return err + } + } + return h.listener.Start() +} + +func (h *Inbound) Close() error { + return common.Close(h.listener, h.tlsConfig) +} + +func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + if h.tlsConfig != nil { + tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig) + if err != nil { + N.CloseOnHandshakeFailure(conn, onClose, err) + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source, ": TLS handshake")) + return + } + conn = tlsConn + } + err := h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose) + if err != nil { + N.CloseOnHandshakeFailure(conn, onClose, err) + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} + +type inboundHandler Inbound + +func (h *inboundHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + //nolint:staticcheck + metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck + metadata.Source = source + metadata.Destination = destination.Unwrap() + if userName, _ := auth.UserFromContext[string](ctx); userName != "" { + metadata.User = userName + h.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", metadata.Destination) + } else { + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + } + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/protocol/anytls/outbound.go b/protocol/anytls/outbound.go new file mode 100644 index 0000000000..2f24c2ef8f --- /dev/null +++ b/protocol/anytls/outbound.go @@ -0,0 +1,131 @@ +package anytls + +import ( + "context" + "net" + "os" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/uot" + + anytls "github.com/anytls/sing-anytls" +) + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.AnyTLSOutboundOptions](registry, C.TypeAnyTLS, NewOutbound) +} + +type Outbound struct { + outbound.Adapter + dialer tls.Dialer + server M.Socksaddr + tlsConfig tls.Config + client *anytls.Client + uotClient *uot.Client + logger log.ContextLogger +} + +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.AnyTLSOutboundOptions) (adapter.Outbound, error) { + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeAnyTLS, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), + server: options.ServerOptions.Build(), + logger: logger, + } + if options.TLS == nil || !options.TLS.Enabled { + return nil, C.ErrTLSRequired + } + // TCP Fast Open is incompatible with anytls because TFO creates a lazy connection + // that only establishes on first write. The lazy connection returns an empty address + // before establishment, but anytls SOCKS wrapper tries to access the remote address + // during handshake, causing a null pointer dereference crash. + if options.DialerOptions.TCPFastOpen { + return nil, E.New("tcp_fast_open is not supported with anytls outbound") + } + + tlsConfig, err := tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + outbound.tlsConfig = tlsConfig + + outboundDialer, err := dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: options.DialerOptions, + RemoteIsDomain: options.ServerIsDomain(), + }) + if err != nil { + return nil, err + } + + outbound.dialer = tls.NewDialer(outboundDialer, tlsConfig) + + client, err := anytls.NewClient(ctx, anytls.ClientConfig{ + Password: options.Password, + IdleSessionCheckInterval: options.IdleSessionCheckInterval.Build(), + IdleSessionTimeout: options.IdleSessionTimeout.Build(), + MinIdleSession: options.MinIdleSession, + DialOut: outbound.dialOut, + Logger: logger, + }) + if err != nil { + return nil, err + } + outbound.client = client + + outbound.uotClient = &uot.Client{ + Dialer: (anytlsDialer)(client.CreateProxy), + Version: uot.Version, + } + return outbound, nil +} + +type anytlsDialer func(ctx context.Context, destination M.Socksaddr) (net.Conn, error) + +func (d anytlsDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + return d(ctx, destination) +} + +func (d anytlsDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return nil, os.ErrInvalid +} + +func (h *Outbound) dialOut(ctx context.Context) (net.Conn, error) { + return h.dialer.DialTLSContext(ctx, h.server) +} + +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() + metadata.Destination = destination + switch N.NetworkName(network) { + case N.NetworkTCP: + h.logger.InfoContext(ctx, "outbound connection to ", destination) + return h.client.CreateProxy(ctx, destination) + case N.NetworkUDP: + h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) + return h.uotClient.DialContext(ctx, network, destination) + } + return nil, os.ErrInvalid +} + +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() + metadata.Destination = destination + h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) + return h.uotClient.ListenPacket(ctx, destination) +} + +func (h *Outbound) Close() error { + return common.Close(h.client) +} diff --git a/protocol/awg/endpoint.go b/protocol/awg/endpoint.go new file mode 100644 index 0000000000..14a0d02acf --- /dev/null +++ b/protocol/awg/endpoint.go @@ -0,0 +1,348 @@ +package awg + +import ( + "context" + "encoding/base64" + "encoding/hex" + "net" + "net/netip" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/endpoint" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/monitoring" + "github.com/sagernet/sing-box/constant" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/transport/awg" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/format" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" + "go4.org/netipx" +) + +func RegisterEndpoint(registry *endpoint.Registry) { + endpoint.Register(registry, constant.TypeAwg, NewEndpoint) +} + +type Endpoint struct { + *awg.Device + endpoint.Adapter + address []netip.Prefix + router adapter.Router + logger log.ContextLogger + dnsRouter adapter.DNSRouter + started bool + ctx context.Context +} + +func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.AwgEndpointOptions) (adapter.Endpoint, error) { + if options.MTU == 0 { + options.MTU = 1408 + } + + options.UDPFragmentDefault = true + // Check if any peer has a domain address + remoteIsDomain := common.Any(options.Peers, func(peer option.AwgPeerOptions) bool { + return !M.ParseAddr(peer.Address).IsValid() + }) + dial, err := dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: options.DialerOptions, + RemoteIsDomain: remoteIsDomain, + ResolverOnDetour: true, + DirectOutbound: true, + }) + if err != nil { + return nil, err + } + + var allowedPrefixBuilder netipx.IPSetBuilder + var excludedPrefixBuilder netipx.IPSetBuilder + for _, peer := range options.Peers { + for _, prefix := range peer.AllowedIPs { + allowedPrefixBuilder.AddPrefix(prefix) + } + + if addr, err := netip.ParseAddr(peer.Address); err == nil { + excludedPrefixBuilder.Add(addr) + } + } + allowedIps, err := allowedPrefixBuilder.IPSet() + if err != nil { + return nil, err + } + excludedIps, err := excludedPrefixBuilder.IPSet() + if err != nil { + return nil, err + } + + // Create peer resolver function for domain endpoints + // Always use system resolver for peer endpoints because: + // 1. VPN server must be resolved before VPN tunnel is established + // 2. dnsRouter may not be fully initialized at this stage + var resolvePeer func(domain string) (netip.Addr, error) + if remoteIsDomain { + resolvePeer = func(domain string) (netip.Addr, error) { + addrs, lookupErr := net.DefaultResolver.LookupNetIP(ctx, "ip", domain) + if lookupErr != nil { + return netip.Addr{}, lookupErr + } + return addrs[0], nil + } + } + + ipc, err := genIpcConfig(options, resolvePeer) + if err != nil { + return nil, err + } + + logger.Debug("AWG IPC config:\n", ipc) + + dev, err := awg.NewDevice(ctx, logger, dial, ipc, awg.DeviceOpts{ + UseIntegratedTun: options.UseIntegratedTun, + Address: options.Address, + AllowedIps: allowedIps.Prefixes(), + ExcludedIps: excludedIps.Prefixes(), + MTU: options.MTU, + }) + if err != nil { + return nil, err + } + + return &Endpoint{ + Device: dev, + Adapter: endpoint.NewAdapterWithDialerOptions("awg", tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), + address: options.Address, + router: router, + logger: logger, + dnsRouter: service.FromContext[adapter.DNSRouter](ctx), + ctx: ctx, + }, nil +} + +func genIpcConfig(opts option.AwgEndpointOptions, resolvePeer func(domain string) (netip.Addr, error)) (string, error) { + privateKeyBytes, err := base64.StdEncoding.DecodeString(opts.PrivateKey) + if err != nil { + return "", err + } + s := "private_key=" + hex.EncodeToString(privateKeyBytes) + if opts.ListenPort != 0 { + s += "\nlisten_port=" + format.ToString(opts.ListenPort) + } + awg := opts.Awg + if awg.Jc != 0 { + s += "\njc=" + format.ToString(awg.Jc) + } + if awg.Jmin != 0 { + s += "\njmin=" + format.ToString(awg.Jmin) + } + if awg.Jmax != 0 { + s += "\njmax=" + format.ToString(awg.Jmax) + } + if awg.S1 != 0 { + s += "\ns1=" + format.ToString(awg.S1) + } + if awg.S2 != 0 { + s += "\ns2=" + format.ToString(awg.S2) + } + if awg.S3 != 0 { + s += "\ns3=" + format.ToString(awg.S3) + } + if awg.S4 != 0 { + s += "\ns4=" + format.ToString(awg.S4) + } + if awg.H1 != "" { + s += "\nh1=" + awg.H1 + } + if awg.H2 != "" { + s += "\nh2=" + awg.H2 + } + if awg.H3 != "" { + s += "\nh3=" + awg.H3 + } + if awg.H4 != "" { + s += "\nh4=" + awg.H4 + } + if awg.I1 != "" { + s += "\ni1=" + awg.I1 + } + if awg.I2 != "" { + s += "\ni2=" + awg.I2 + } + if awg.I3 != "" { + s += "\ni3=" + awg.I3 + } + if awg.I4 != "" { + s += "\ni4=" + awg.I4 + } + if awg.I5 != "" { + s += "\ni5=" + awg.I5 + } + + for _, peer := range opts.Peers { + publicKeyBytes, err := base64.StdEncoding.DecodeString(peer.PublicKey) + if err != nil { + return "", err + } + s += "\npublic_key=" + hex.EncodeToString(publicKeyBytes) + if peer.PresharedKey != "" { + presharedKeyBytes, err := base64.StdEncoding.DecodeString(peer.PresharedKey) + if err != nil { + return "", err + } + s += "\npreshared_key=" + hex.EncodeToString(presharedKeyBytes) + } + if peer.Address != "" && peer.Port != 0 { + // Resolve domain to IP if necessary + endpointAddr := peer.Address + if addr := M.ParseAddr(peer.Address); !addr.IsValid() { + // It's a domain, resolve it + if resolvePeer == nil { + return "", E.New("peer address is a domain but no resolver provided: ", peer.Address) + } + resolvedAddr, resolveErr := resolvePeer(peer.Address) + if resolveErr != nil { + return "", E.Cause(resolveErr, "resolve peer endpoint ", peer.Address) + } + endpointAddr = resolvedAddr.String() + } + s += "\nendpoint=" + endpointAddr + ":" + format.ToString(peer.Port) + } + if peer.PersistentKeepaliveInterval != 0 { + s += "\npersistent_keepalive_interval=" + format.ToString(peer.PersistentKeepaliveInterval) + } + for _, allowedIp := range peer.AllowedIPs { + s += "\nallowed_ip=" + allowedIp.String() + } + } + return s, nil +} + +func (e *Endpoint) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = e.Tag() + metadata.InboundType = e.Type() + metadata.Source = source + metadata.Destination = destination + for _, addr := range e.address { + if addr.Contains(destination.Addr) { + metadata.OriginDestination = destination + if destination.Addr.Is4() { + metadata.Destination.Addr = netip.AddrFrom4([4]uint8{127, 0, 0, 1}) + } else { + metadata.Destination.Addr = netip.IPv6Loopback() + } + conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination) + } + } + e.logger.InfoContext(ctx, "inbound packet connection from ", source) + e.logger.InfoContext(ctx, "inbound packet connection to ", destination) + e.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} + +func (e *Endpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + switch network { + case N.NetworkTCP: + e.logger.InfoContext(ctx, "outbound connection to ", destination) + case N.NetworkUDP: + e.logger.InfoContext(ctx, "outbound packet connection to ", destination) + } + if destination.IsFqdn() { + destinationAddresses, err := e.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) + if err != nil { + return nil, err + } + return N.DialSerial(ctx, e.Device, network, destination, destinationAddresses) + } else if !destination.Addr.IsValid() { + return nil, E.New("invalid destination: ", destination) + } + return e.Device.DialContext(ctx, network, destination) +} + +func (e *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + e.logger.InfoContext(ctx, "outbound packet connection to ", destination) + if destination.IsFqdn() { + destinationAddresses, err := e.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) + if err != nil { + return nil, err + } + packetConn, _, err := N.ListenSerial(ctx, e.Device, destination, destinationAddresses) + if err != nil { + return nil, err + } + return packetConn, nil + } + return e.Device.ListenPacket(ctx, destination) +} + +func (w *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = w.Tag() + metadata.InboundType = w.Type() + metadata.Source = source + for _, addr := range w.address { + if addr.Contains(destination.Addr) { + metadata.OriginDestination = destination + if destination.Addr.Is4() { + destination.Addr = netip.AddrFrom4([4]uint8{127, 0, 0, 1}) + } else { + destination.Addr = netip.IPv6Loopback() + } + break + } + } + metadata.Destination = destination + w.logger.InfoContext(ctx, "inbound connection from ", source) + w.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + w.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (o *Endpoint) Start(stage adapter.StartStage) error { + switch stage { + case adapter.StartStateStart: + return o.Device.Start(stage) + case adapter.StartStatePostStart: + go o.readyChecker() + } + return nil +} + +func (w *Endpoint) readyChecker() { + for i := 0; i < 10; i++ { + if w.deviceReady() { + break + } + select { + case <-w.ctx.Done(): + return + case <-time.After(time.Second): + } + } + w.started = true + if m := monitoring.Get(w.ctx); m != nil { + m.TestNow(w.Tag()) + } +} + +func (w *Endpoint) deviceReady() bool { + return w.Device != nil && w.Device.IsUnderLoad() +} + +func (w *Endpoint) IsReady() bool { + return w.started || w.deviceReady() +} + +func (w *Endpoint) DisplayType() string { + str := C.ProxyDisplayName(w.Type()) + if !w.IsReady() { + str += " ⚠️ Connecting..." + } + return str +} diff --git a/protocol/block/outbound.go b/protocol/block/outbound.go new file mode 100644 index 0000000000..fe1ccda760 --- /dev/null +++ b/protocol/block/outbound.go @@ -0,0 +1,42 @@ +package block + +import ( + "context" + "net" + "syscall" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.StubOptions](registry, C.TypeBlock, New) +} + +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger +} + +func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, _ option.StubOptions) (adapter.Outbound, error) { + return &Outbound{ + Adapter: outbound.NewAdapter(C.TypeBlock, tag, []string{N.NetworkTCP, N.NetworkUDP}, nil), + logger: logger, + }, nil +} + +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + h.logger.InfoContext(ctx, "blocked connection to ", destination) + return nil, syscall.EPERM +} + +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + h.logger.InfoContext(ctx, "blocked packet connection to ", destination) + return nil, syscall.EPERM +} diff --git a/protocol/cloudflare/inbound.go b/protocol/cloudflare/inbound.go new file mode 100644 index 0000000000..f445ab956b --- /dev/null +++ b/protocol/cloudflare/inbound.go @@ -0,0 +1,160 @@ +//go:build with_cloudflared + +package cloudflare + +import ( + "context" + "net" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + boxDialer "github.com/sagernet/sing-box/common/dialer" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/route/rule" + cloudflared "github.com/sagernet/sing-cloudflared" + tun "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/pipe" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.CloudflaredInboundOptions](registry, C.TypeCloudflared, NewInbound) +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.CloudflaredInboundOptions) (adapter.Inbound, error) { + controlDialer, err := boxDialer.NewWithOptions(boxDialer.Options{ + Context: ctx, + Options: options.ControlDialer, + RemoteIsDomain: true, + }) + if err != nil { + return nil, E.Cause(err, "build cloudflared control dialer") + } + tunnelDialer, err := boxDialer.NewWithOptions(boxDialer.Options{ + Context: ctx, + Options: options.TunnelDialer, + RemoteIsDomain: true, + }) + if err != nil { + return nil, E.Cause(err, "build cloudflared tunnel dialer") + } + + service, err := cloudflared.NewService(cloudflared.ServiceOptions{ + Logger: logger, + ConnectionDialer: &routerDialer{router: router, tag: tag}, + ControlDialer: controlDialer, + TunnelDialer: tunnelDialer, + ICMPHandler: &icmpRouterHandler{router: router, logger: logger, tag: tag}, + ConnContext: func(connCtx context.Context) context.Context { + return adapter.WithContext(connCtx, &adapter.InboundContext{ + Inbound: tag, + InboundType: C.TypeCloudflared, + }) + }, + Token: options.Token, + HAConnections: options.HighAvailabilityConnections, + Protocol: options.Protocol, + PostQuantum: options.PostQuantum, + EdgeIPVersion: options.EdgeIPVersion, + DatagramVersion: options.DatagramVersion, + GracePeriod: time.Duration(options.GracePeriod), + Region: options.Region, + }) + if err != nil { + return nil, err + } + + return &Inbound{ + Adapter: inbound.NewAdapter(C.TypeCloudflared, tag), + service: service, + }, nil +} + +type Inbound struct { + inbound.Adapter + service *cloudflared.Service +} + +func (i *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + return i.service.Start() +} + +func (i *Inbound) Close() error { + return i.service.Close() +} + +type routerDialer struct { + router adapter.Router + tag string +} + +func (d *routerDialer) newMetadata(network string, destination M.Socksaddr) adapter.InboundContext { + return adapter.InboundContext{ + Inbound: d.tag, + InboundType: C.TypeCloudflared, + Network: network, + Destination: destination, + } +} + +func (d *routerDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + input, output := pipe.Pipe() + go d.router.RouteConnectionEx(ctx, output, d.newMetadata(N.NetworkTCP, destination), N.OnceClose(func(it error) { + input.Close() + })) + return input, nil +} + +func (d *routerDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + input, output := pipe.Pipe() + routerConn := bufio.NewUnbindPacketConn(output) + go d.router.RoutePacketConnectionEx(ctx, routerConn, d.newMetadata(N.NetworkUDP, destination), N.OnceClose(func(it error) { + input.Close() + })) + return bufio.NewUnbindPacketConn(input), nil +} + +type icmpRouterHandler struct { + router adapter.Router + logger log.ContextLogger + tag string +} + +func (h *icmpRouterHandler) RouteICMPConnection(ctx context.Context, session tun.DirectRouteSession, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + var ipVersion uint8 + if session.Destination.Is4() { + ipVersion = 4 + } else { + ipVersion = 6 + } + destination := M.SocksaddrFrom(session.Destination, 0) + routeDestination, err := h.router.PreMatch(adapter.InboundContext{ + Inbound: h.tag, + InboundType: C.TypeCloudflared, + IPVersion: ipVersion, + Network: N.NetworkICMP, + Source: M.SocksaddrFrom(session.Source, 0), + Destination: destination, + OriginDestination: destination, + }, routeContext, timeout, false) + if err != nil { + switch { + case rule.IsBypassed(err): + err = nil + case rule.IsRejected(err): + h.logger.Trace("reject ICMP connection from ", session.Source, " to ", session.Destination) + default: + h.logger.Warn(E.Cause(err, "link ICMP connection from ", session.Source, " to ", session.Destination)) + } + } + return routeDestination, err +} diff --git a/protocol/direct/inbound.go b/protocol/direct/inbound.go new file mode 100644 index 0000000000..fcb4671d89 --- /dev/null +++ b/protocol/direct/inbound.go @@ -0,0 +1,177 @@ +package direct + +import ( + "context" + "net" + "os" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/udpnat2" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.DirectInboundOptions](registry, C.TypeDirect, NewInbound) +} + +type Inbound struct { + inbound.Adapter + ctx context.Context + router adapter.ConnectionRouterEx + logger log.ContextLogger + listener *listener.Listener + udpNat *udpnat.Service + overrideOption int + overrideDestination M.Socksaddr +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectInboundOptions) (adapter.Inbound, error) { + options.UDPFragmentDefault = true + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeDirect, tag), + ctx: ctx, + router: router, + logger: logger, + } + if options.OverrideAddress != "" && options.OverridePort != 0 { + inbound.overrideOption = 1 + inbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort) + } else if options.OverrideAddress != "" { + inbound.overrideOption = 2 + inbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort) + } else if options.OverridePort != 0 { + inbound.overrideOption = 3 + inbound.overrideDestination = M.Socksaddr{Port: options.OverridePort} + } + var udpTimeout time.Duration + if options.UDPTimeout != 0 { + udpTimeout = time.Duration(options.UDPTimeout) + } else { + udpTimeout = C.UDPTimeout + } + inbound.udpNat = udpnat.New(inbound, inbound.preparePacketConnection, udpTimeout, false) + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: options.Network.Build(), + Listen: options.ListenOptions, + ConnectionHandler: inbound, + PacketHandler: inbound, + }) + return inbound, nil +} + +func (i *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + return i.listener.Start() +} + +func (i *Inbound) Close() error { + return i.listener.Close() +} + +func (i *Inbound) NewPacket(buffer *buf.Buffer, source M.Socksaddr) { + i.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, i.listener.UDPAddr(), nil) +} + +func (i *Inbound) NewPacketBatch(buffers []*buf.Buffer, sources []M.Socksaddr) { + i.udpNat.NewPacketBatch(buffers, sources, i.listener.UDPAddr(), nil) +} + +func (i *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Inbound = i.Tag() + metadata.InboundType = i.Type() + destination := metadata.OriginDestination + switch i.overrideOption { + case 1: + destination = i.overrideDestination + case 2: + destination.Addr = i.overrideDestination.Addr + case 3: + destination.Port = i.overrideDestination.Port + } + metadata.Destination = destination + if i.overrideOption != 0 { + i.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + } + i.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (i *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + i.logger.InfoContext(ctx, "inbound packet connection from ", source) + var metadata adapter.InboundContext + metadata.Inbound = i.Tag() + metadata.InboundType = i.Type() + //nolint:staticcheck + metadata.InboundDetour = i.listener.ListenOptions().Detour + //nolint:staticcheck + metadata.Source = source + destination = i.listener.UDPAddr() + switch i.overrideOption { + case 1: + destination = i.overrideDestination + case 2: + destination.Addr = i.overrideDestination.Addr + case 3: + destination.Port = i.overrideDestination.Port + default: + } + i.logger.InfoContext(ctx, "inbound packet connection to ", destination) + metadata.Destination = destination + if i.overrideOption != 0 { + conn = bufio.NewDestinationNATPacketConn(bufio.NewNetPacketConn(conn), i.listener.UDPAddr(), destination) + } + i.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} + +func (i *Inbound) preparePacketConnection(source M.Socksaddr, destination M.Socksaddr, userData any) (bool, context.Context, N.PacketWriter, N.CloseHandlerFunc) { + return true, log.ContextWithNewID(i.ctx), &directPacketWriter{i.listener.PacketWriter(), source}, nil +} + +type directPacketWriter struct { + writer N.PacketWriter + source M.Socksaddr +} + +func (w *directPacketWriter) WritePacket(buffer *buf.Buffer, addr M.Socksaddr) error { + return w.writer.WritePacket(buffer, w.source) +} + +func (w *directPacketWriter) CreatePacketBatchWriter() (N.PacketBatchWriter, bool) { + writer, created := bufio.CreatePacketBatchWriter(w.writer) + if !created { + return nil, false + } + return &directPacketBatchWriter{ + writer: writer, + source: w.source, + }, true +} + +type directPacketBatchWriter struct { + writer N.PacketBatchWriter + source M.Socksaddr +} + +func (w *directPacketBatchWriter) WritePacketBatch(buffers []*buf.Buffer, destinations []M.Socksaddr) error { + if len(buffers) == 0 || len(buffers) != len(destinations) { + buf.ReleaseMulti(buffers) + return os.ErrInvalid + } + sources := make([]M.Socksaddr, len(destinations)) + for index := range sources { + sources[index] = w.source + } + return w.writer.WritePacketBatch(buffers, sources) +} diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go new file mode 100644 index 0000000000..630a67552f --- /dev/null +++ b/protocol/direct/outbound.go @@ -0,0 +1,155 @@ +package direct + +import ( + "context" + "net" + "net/netip" + "reflect" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/common/dialer" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing-tun/ping" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.DirectOutboundOptions](registry, C.TypeDirect, NewOutbound) +} + +var ( + _ N.ParallelDialer = (*Outbound)(nil) + _ dialer.ParallelNetworkDialer = (*Outbound)(nil) + _ dialer.DirectDialer = (*Outbound)(nil) + _ adapter.DirectRouteOutbound = (*Outbound)(nil) +) + +type Outbound struct { + outbound.Adapter + ctx context.Context + logger logger.ContextLogger + dialer dialer.ParallelInterfaceDialer + domainStrategy C.DomainStrategy + fallbackDelay time.Duration + isEmpty bool +} + +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (adapter.Outbound, error) { + options.UDPFragmentDefault = true + if options.Detour != "" { + return nil, E.New("`detour` is not supported in direct context") + } + outboundDialer, err := dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: options.DialerOptions, + RemoteIsDomain: true, + DirectOutbound: true, + }) + if err != nil { + return nil, err + } + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions), + ctx: ctx, + logger: logger, + //nolint:staticcheck + domainStrategy: C.DomainStrategy(options.DomainStrategy), + fallbackDelay: time.Duration(options.FallbackDelay), + dialer: outboundDialer.(dialer.ParallelInterfaceDialer), + isEmpty: reflect.DeepEqual(options.DialerOptions, option.DialerOptions{UDPFragmentDefault: true}), + } + //nolint:staticcheck + if options.ProxyProtocol != 0 { + return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0") + } + return outbound, nil +} + +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() + metadata.Destination = destination + network = N.NetworkName(network) + switch network { + case N.NetworkTCP: + h.logger.InfoContext(ctx, "outbound connection to ", destination) + case N.NetworkUDP: + h.logger.InfoContext(ctx, "outbound packet connection to ", destination) + } + return h.dialer.DialContext(ctx, network, destination) +} + +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() + metadata.Destination = destination + h.logger.InfoContext(ctx, "outbound packet connection") + conn, err := h.dialer.ListenPacket(ctx, destination) + if err != nil { + return nil, err + } + return conn, nil +} + +func (h *Outbound) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + ctx := log.ContextWithNewID(h.ctx) + destination, err := ping.ConnectDestination(ctx, h.logger, common.MustCast[*dialer.DefaultDialer](h.dialer).DialerForICMPDestination(metadata.Destination.Addr).Control, metadata.Destination.Addr, routeContext, timeout) + if err != nil { + return nil, err + } + h.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString()) + return destination, nil +} + +func (h *Outbound) DialParallel(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() + metadata.Destination = destination + network = N.NetworkName(network) + switch network { + case N.NetworkTCP: + h.logger.InfoContext(ctx, "outbound connection to ", destination) + case N.NetworkUDP: + h.logger.InfoContext(ctx, "outbound packet connection to ", destination) + } + return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, len(destinationAddresses) > 0 && destinationAddresses[0].Is6(), nil, nil, nil, h.fallbackDelay) +} + +func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() + metadata.Destination = destination + network = N.NetworkName(network) + switch network { + case N.NetworkTCP: + h.logger.InfoContext(ctx, "outbound connection to ", destination) + case N.NetworkUDP: + h.logger.InfoContext(ctx, "outbound packet connection to ", destination) + } + return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, len(destinationAddresses) > 0 && destinationAddresses[0].Is6(), networkStrategy, networkType, fallbackNetworkType, fallbackDelay) +} + +func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() + metadata.Destination = destination + h.logger.InfoContext(ctx, "outbound packet connection") + conn, newDestination, err := dialer.ListenSerialNetworkPacket(ctx, h.dialer, destination, destinationAddresses, networkStrategy, networkType, fallbackNetworkType, fallbackDelay) + if err != nil { + return nil, netip.Addr{}, err + } + return conn, newDestination, nil +} + +func (h *Outbound) IsEmpty() bool { + return h.isEmpty +} diff --git a/outbound/dns.go b/protocol/dns/handle.go similarity index 69% rename from outbound/dns.go rename to protocol/dns/handle.go index b18b901e7a..d7d89ca8a9 100644 --- a/outbound/dns.go +++ b/protocol/dns/handle.go @@ -1,14 +1,13 @@ -package outbound +package dns import ( "context" "encoding/binary" "net" - "os" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-dns" + "github.com/sagernet/sing-box/dns" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" @@ -20,50 +19,14 @@ import ( mDNS "github.com/miekg/dns" ) -var _ adapter.Outbound = (*DNS)(nil) - -type DNS struct { - myOutboundAdapter -} - -func NewDNS(router adapter.Router, tag string) *DNS { - return &DNS{ - myOutboundAdapter{ - protocol: C.TypeDNS, - network: []string{N.NetworkTCP, N.NetworkUDP}, - router: router, - tag: tag, - }, - } -} - -func (d *DNS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - return nil, os.ErrInvalid -} - -func (d *DNS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - return nil, os.ErrInvalid -} - -func (d *DNS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - metadata.Destination = M.Socksaddr{} - defer conn.Close() - for { - err := d.handleConnection(ctx, conn, metadata) - if err != nil { - return err - } - } -} - -func (d *DNS) handleConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func HandleStreamDNSRequest(ctx context.Context, router adapter.DNSRouter, conn net.Conn, metadata adapter.InboundContext) error { var queryLength uint16 err := binary.Read(conn, binary.BigEndian, &queryLength) if err != nil { return err } if queryLength == 0 { - return dns.RCodeFormatError + return dns.RcodeFormatError } buffer := buf.NewSize(int(queryLength)) defer buffer.Release() @@ -78,11 +41,13 @@ func (d *DNS) handleConnection(ctx context.Context, conn net.Conn, metadata adap } metadataInQuery := metadata go func() error { - response, err := d.router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message) + response, err := router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message, adapter.DNSQueryOptions{}) if err != nil { + conn.Close() return err } - responseBuffer := buf.NewPacket() + responseLength := response.Len() + responseBuffer := buf.NewSize(3 + responseLength) defer responseBuffer.Release() responseBuffer.Resize(2, 0) n, err := response.PackBuffer(responseBuffer.FreeBytes()) @@ -97,11 +62,11 @@ func (d *DNS) handleConnection(ctx context.Context, conn net.Conn, metadata adap return nil } -func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func NewDNSPacketConnection(ctx context.Context, router adapter.DNSRouter, conn N.PacketConn, cachedPackets []*N.PacketBuffer, metadata adapter.InboundContext) error { metadata.Destination = M.Socksaddr{} var reader N.PacketReader = conn var counters []N.CountFunc - var cachedPackets []*N.PacketBuffer + cachedPackets = common.Reverse(cachedPackets) for { reader, counters = N.UnwrapCountPacketReader(reader, counters) if cachedReader, isCached := reader.(N.CachedPacketReader); isCached { @@ -113,14 +78,14 @@ func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metada } if readWaiter, created := bufio.CreatePacketReadWaiter(reader); created { readWaiter.InitializeReadWaiter(N.ReadWaitOptions{}) - return d.newPacketConnection(ctx, conn, readWaiter, counters, cachedPackets, metadata) + return newDNSPacketConnection(ctx, router, conn, readWaiter, counters, cachedPackets, metadata) } break } - fastClose, cancel := common.ContextWithCancelCause(ctx) + fastClose, cancel := context.WithCancelCause(ctx) timeout := canceler.New(fastClose, cancel, C.DNSTimeout) var group task.Group - group.Append0(func(ctx context.Context) error { + group.Append0(func(_ context.Context) error { for { var message mDNS.Msg var destination M.Socksaddr @@ -159,7 +124,7 @@ func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metada } metadataInQuery := metadata go func() error { - response, err := d.router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message) + response, err := router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message, adapter.DNSQueryOptions{}) if err != nil { cancel(err) return err @@ -184,12 +149,11 @@ func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metada return group.Run(fastClose) } -func (d *DNS) newPacketConnection(ctx context.Context, conn N.PacketConn, readWaiter N.PacketReadWaiter, readCounters []N.CountFunc, cached []*N.PacketBuffer, metadata adapter.InboundContext) error { - ctx = adapter.WithContext(ctx, &metadata) - fastClose, cancel := common.ContextWithCancelCause(ctx) +func newDNSPacketConnection(ctx context.Context, router adapter.DNSRouter, conn N.PacketConn, readWaiter N.PacketReadWaiter, readCounters []N.CountFunc, cached []*N.PacketBuffer, metadata adapter.InboundContext) error { + fastClose, cancel := context.WithCancelCause(ctx) timeout := canceler.New(fastClose, cancel, C.DNSTimeout) var group task.Group - group.Append0(func(ctx context.Context) error { + group.Append0(func(_ context.Context) error { for { var ( message mDNS.Msg @@ -205,11 +169,12 @@ func (d *DNS) newPacketConnection(ctx context.Context, conn N.PacketConn, readWa } err = message.Unpack(packet.Buffer.Bytes()) packet.Buffer.Release() + destination = packet.Destination + N.PutPacketBuffer(packet) if err != nil { cancel(err) return err } - destination = packet.Destination } else { buffer, destination, err = readWaiter.WaitReadPacket() if err != nil { @@ -229,7 +194,7 @@ func (d *DNS) newPacketConnection(ctx context.Context, conn N.PacketConn, readWa } metadataInQuery := metadata go func() error { - response, err := d.router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message) + response, err := router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message, adapter.DNSQueryOptions{}) if err != nil { cancel(err) return err diff --git a/protocol/dns/outbound.go b/protocol/dns/outbound.go new file mode 100644 index 0000000000..747d578ea7 --- /dev/null +++ b/protocol/dns/outbound.go @@ -0,0 +1,63 @@ +package dns + +import ( + "context" + "net" + "os" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" +) + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.StubOptions](registry, C.TypeDNS, NewOutbound) +} + +type Outbound struct { + outbound.Adapter + router adapter.DNSRouter + logger logger.ContextLogger +} + +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.StubOptions) (adapter.Outbound, error) { + return &Outbound{ + Adapter: outbound.NewAdapter(C.TypeDNS, tag, []string{N.NetworkTCP, N.NetworkUDP}, nil), + router: service.FromContext[adapter.DNSRouter](ctx), + logger: logger, + }, nil +} + +func (d *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + return nil, os.ErrInvalid +} + +func (d *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return nil, os.ErrInvalid +} + +func (d *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Destination = M.Socksaddr{} + for { + conn.SetReadDeadline(time.Now().Add(C.DNSTimeout)) + err := HandleStreamDNSRequest(ctx, d.router, conn, metadata) + if err != nil { + conn.Close() + if onClose != nil { + onClose(err) + } + return + } + } +} + +func (d *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + NewDNSPacketConnection(ctx, d.router, conn, nil, metadata) +} diff --git a/protocol/firebasetunnel/compress.go b/protocol/firebasetunnel/compress.go new file mode 100644 index 0000000000..d8eb8a1d8b --- /dev/null +++ b/protocol/firebasetunnel/compress.go @@ -0,0 +1,41 @@ +package firebasetunnel + +// Adapted from github.com/Hiddify2/Firebase-Tunnel (no LICENSE upstream; +// logic rewritten into this package rather than vendored). + +import ( + "fmt" + + "github.com/klauspost/compress/zstd" +) + +const compressionLevel = 3 + +var ( + zstdEncoder *zstd.Encoder + zstdDecoder *zstd.Decoder +) + +func init() { + var err error + zstdEncoder, err = zstd.NewWriter(nil, zstd.WithEncoderLevel(zstd.EncoderLevel(compressionLevel))) + if err != nil { + panic(fmt.Sprintf("firebasetunnel: zstd encoder init: %v", err)) + } + zstdDecoder, err = zstd.NewReader(nil) + if err != nil { + panic(fmt.Sprintf("firebasetunnel: zstd decoder init: %v", err)) + } +} + +func compressBytes(data []byte) []byte { + return zstdEncoder.EncodeAll(data, make([]byte, 0, len(data))) +} + +func decompressBytes(data []byte) ([]byte, error) { + out, err := zstdDecoder.DecodeAll(data, make([]byte, 0, len(data)*3)) + if err != nil { + return nil, fmt.Errorf("firebasetunnel: zstd decompress: %w", err) + } + return out, nil +} diff --git a/protocol/firebasetunnel/compress_test.go b/protocol/firebasetunnel/compress_test.go new file mode 100644 index 0000000000..8123674e51 --- /dev/null +++ b/protocol/firebasetunnel/compress_test.go @@ -0,0 +1,32 @@ +package firebasetunnel + +import ( + "bytes" + "testing" +) + +func TestCompressRoundTrip(t *testing.T) { + cases := [][]byte{ + []byte(""), + []byte("hello world"), + bytes.Repeat([]byte("a"), 100000), + []byte{0x00, 0xff, 0x10, 0x20}, + } + for _, data := range cases { + compressed := compressBytes(data) + out, err := decompressBytes(compressed) + if err != nil { + t.Fatalf("decompress: %v", err) + } + if !bytes.Equal(out, data) { + t.Fatalf("round trip mismatch: got %d bytes, want %d", len(out), len(data)) + } + } +} + +func TestDecompressInvalid(t *testing.T) { + _, err := decompressBytes([]byte("not zstd data")) + if err == nil { + t.Fatal("expected error decompressing invalid data") + } +} diff --git a/protocol/firebasetunnel/crypto.go b/protocol/firebasetunnel/crypto.go new file mode 100644 index 0000000000..a44e5c2973 --- /dev/null +++ b/protocol/firebasetunnel/crypto.go @@ -0,0 +1,122 @@ +package firebasetunnel + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "encoding/binary" + "fmt" + "io" +) + +const hmacTagLen = 16 + +// deriveKey turns an arbitrary-length PSK string into a 32-byte AES-256 key. +// This is a simple SHA-256 KDF, sufficient for a pre-shared-secret use case +// (not a password — operators are expected to provide a high-entropy PSK). +func deriveKey(psk string) [32]byte { + return sha256.Sum256([]byte(psk)) +} + +// deriveHMACKey derives a 32-byte HMAC key from the raw Firebase secret. +// Used for integrity verification on the unencrypted (no-PSK) path. +func deriveHMACKey(secret string) []byte { + h := sha256.Sum256(append([]byte("hmac:"), []byte(secret)...)) + return h[:] +} + +// hmacTag returns a hmacTagLen-byte HMAC-SHA256 tag over payload, keyed by secretKey. +func hmacTag(secretKey []byte, payload []byte) []byte { + mac := hmac.New(sha256.New, secretKey) + mac.Write(payload) + return mac.Sum(nil)[:hmacTagLen] +} + +// appendHMACTag appends a hmacTagLen-byte HMAC tag to payload and returns +// the combined slice (no allocation if capacity permits). +func appendHMACTag(secretKey []byte, payload []byte) []byte { + tag := hmacTag(secretKey, payload) + return append(payload, tag...) +} + +// verifyAndStripHMACTag verifies the hmacTagLen-byte tag appended to data +// and returns the payload without the tag. Returns an error on mismatch — +// treat as integrity failure / potential abuse signal. +func verifyAndStripHMACTag(secretKey []byte, data []byte) ([]byte, error) { + if len(data) < hmacTagLen { + return nil, fmt.Errorf("firebasetunnel: HMAC tag missing (data too short)") + } + payload := data[:len(data)-hmacTagLen] + got := data[len(data)-hmacTagLen:] + expected := hmacTag(secretKey, payload) + if !hmac.Equal(got, expected) { + return nil, fmt.Errorf("firebasetunnel: HMAC verification failed — data integrity compromised") + } + return payload, nil +} + +// encryptPayloadAAD encrypts data with AES-256-GCM under key, binding aad as +// additional authenticated data. Nonce is prepended to the ciphertext. +// If aad is nil, no additional data is bound (same as old encryptPayload). +func encryptPayloadAAD(key [32]byte, data []byte, aad []byte) ([]byte, error) { + block, err := aes.NewCipher(key[:]) + if err != nil { + return nil, err + } + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + nonce := make([]byte, gcm.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return nil, err + } + return gcm.Seal(nonce, nonce, data, aad), nil +} + +// decryptPayloadAAD reverses encryptPayloadAAD. aad must match what was +// passed to encryptPayloadAAD exactly; mismatch causes an auth error. +func decryptPayloadAAD(key [32]byte, data []byte, aad []byte) ([]byte, error) { + block, err := aes.NewCipher(key[:]) + if err != nil { + return nil, err + } + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + if len(data) < gcm.NonceSize() { + return nil, fmt.Errorf("firebasetunnel: ciphertext too short") + } + nonce, ciphertext := data[:gcm.NonceSize()], data[gcm.NonceSize():] + return gcm.Open(nil, nonce, ciphertext, aad) +} + +// encryptPayload encrypts with no additional authenticated data. +// Kept for backward compatibility with existing tests. +func encryptPayload(key [32]byte, data []byte) ([]byte, error) { + return encryptPayloadAAD(key, data, nil) +} + +// decryptPayload decrypts with no additional authenticated data. +// Kept for backward compatibility with existing tests. +func decryptPayload(key [32]byte, data []byte) ([]byte, error) { + return decryptPayloadAAD(key, data, nil) +} + +// chunkAAD builds the AEAD additional data for a chunk: encodes sessionID, +// direction ("c2s"/"s2c"), and seq into a compact binary blob so that a chunk +// from one session/direction/position cannot be replayed into another. +func chunkAAD(sessionID, direction string, seq uint64) []byte { + seqBuf := make([]byte, 8) + binary.LittleEndian.PutUint64(seqBuf, seq) + aad := make([]byte, 0, len(sessionID)+1+len(direction)+1+8) + aad = append(aad, []byte(sessionID)...) + aad = append(aad, '/') + aad = append(aad, []byte(direction)...) + aad = append(aad, '/') + aad = append(aad, seqBuf...) + return aad +} diff --git a/protocol/firebasetunnel/crypto_test.go b/protocol/firebasetunnel/crypto_test.go new file mode 100644 index 0000000000..547e77e2b1 --- /dev/null +++ b/protocol/firebasetunnel/crypto_test.go @@ -0,0 +1,58 @@ +package firebasetunnel + +import ( + "bytes" + "testing" +) + +func TestEncryptDecryptRoundTrip(t *testing.T) { + key := deriveKey("test-psk") + data := []byte("secret payload bytes") + + ct, err := encryptPayload(key, data) + if err != nil { + t.Fatalf("encrypt: %v", err) + } + if bytes.Equal(ct, data) { + t.Fatal("ciphertext must not equal plaintext") + } + + pt, err := decryptPayload(key, ct) + if err != nil { + t.Fatalf("decrypt: %v", err) + } + if !bytes.Equal(pt, data) { + t.Fatalf("decrypted mismatch: got %q want %q", pt, data) + } +} + +func TestDecryptWrongKeyFails(t *testing.T) { + key := deriveKey("psk-a") + wrongKey := deriveKey("psk-b") + ct, err := encryptPayload(key, []byte("data")) + if err != nil { + t.Fatalf("encrypt: %v", err) + } + if _, err := decryptPayload(wrongKey, ct); err == nil { + t.Fatal("expected decrypt failure with wrong key") + } +} + +func TestDecryptTruncatedFails(t *testing.T) { + key := deriveKey("psk") + if _, err := decryptPayload(key, []byte("x")); err == nil { + t.Fatal("expected error for truncated ciphertext") + } +} + +func TestDeriveKeyDeterministic(t *testing.T) { + a := deriveKey("same-psk") + b := deriveKey("same-psk") + if a != b { + t.Fatal("deriveKey must be deterministic for the same input") + } + c := deriveKey("different-psk") + if a == c { + t.Fatal("deriveKey must differ for different input") + } +} diff --git a/protocol/firebasetunnel/docs/README.md b/protocol/firebasetunnel/docs/README.md new file mode 100644 index 0000000000..70769ba244 --- /dev/null +++ b/protocol/firebasetunnel/docs/README.md @@ -0,0 +1,19 @@ +# Firebase Tunnel + +A sing-box endpoint pair (`firebasetunnel_client` / `firebasetunnel_server`) that relays TCP byte streams through Firebase Realtime Database, used when direct connectivity between client and server is blocked but Firebase's REST API is reachable. + +Adapted from [github.com/Hiddify2/Firebase-Tunnel](https://github.com/Hiddify2/Firebase-Tunnel) (a single-star proof-of-concept with no LICENSE — the logic below was rewritten for sing-box rather than vendored; see [provenance.md](./provenance.md)). + +## Documents + +- [architecture.md](./architecture.md) — how the relay works, wire format, Firebase database layout +- [configuration.md](./configuration.md) — client/server config reference +- [security.md](./security.md) — threat model, auth options, encryption +- [operations.md](./operations.md) — rate limits, session caps, GC, rollout/rollback +- [provenance.md](./provenance.md) — relationship to the upstream PoC, what was reused vs. rewritten + +## Known limitations + +- Added latency: Firebase RTDB polling/SSE imposes a realistic floor of ~200ms+ round-trip on top of normal network latency. +- Firebase rate limits apply per project; high-throughput use needs read/write quota headroom (see [operations.md](./operations.md)). +- Default auth is a single shared Firebase Database Secret — coarse-grained (see [security.md](./security.md) for the recommended token/PSK alternatives). diff --git a/protocol/firebasetunnel/docs/architecture.md b/protocol/firebasetunnel/docs/architecture.md new file mode 100644 index 0000000000..3141b6ab31 --- /dev/null +++ b/protocol/firebasetunnel/docs/architecture.md @@ -0,0 +1,71 @@ +# Architecture + +## Endpoint, not outbound+inbound + +`firebasetunnel_client` and `firebasetunnel_server` are both registered as sing-box **Endpoints** (`adapter.Endpoint`), the same shape used by the existing `tunnel_client`/`tunnel_server` pair in `protocol/tunnel/`. An Endpoint can both dial out (`DialContext`) and run its own background listener (`Start`) — exactly the dual nature needed here: the client dials *through* Firebase, the server listens for sessions arriving *via* Firebase and dials the real target. + +``` +sing-box (client box) sing-box (server box) +┌─────────────────────────┐ ┌─────────────────────────┐ +│ inbound (mixed/socks/…) │ │ firebasetunnel_server │ +│ │ │ │ │ │ +│ ▼ │ │ ▼ │ +│ firebasetunnel_client │◄──── Firebase Realtime Database ──►│ router.RouteConnectionEx│ +│ .DialContext() │ (sessions/{id}/...) │ │ │ +└─────────────────────────┘ │ ▼ │ + │ direct/other outbound │ + └─────────────────────────┘ +``` + +## Session lifecycle + +1. **Client** generates a UUIDv4 `session_id`, writes `sessions/{id}/metadata` with `state=pending`, `target_host`/`target_port` (the dial destination), and its configured `user` label. +2. **Server** discovers the new pending session (via a Firebase SSE `Listen` stream on `sessions/`, with a coarse poll fallback), validates `user` against its configured user list, enforces session caps/rate limits, then flips `state=active`. +3. **Client** observes `state=active` and begins relaying. +4. Both sides exchange `net.Pipe()`-backed connections internally: the client hands the caller of `DialContext` one end of a pipe and relays the other end to Firebase; the server does the same and routes its end through `router.RouteConnectionEx` so normal sing-box routing/DNS/sniffing rules apply to the final egress. +5. Bytes are batched (default: every 50ms or 32KiB, whichever first), optionally zstd-compressed, optionally AES-256-GCM encrypted (see [security.md](./security.md)), base64-encoded, and written as sequential `chunk` records. +6. The reading side reassembles chunks in order (buffering any that arrive out of order), writes reassembled bytes to its local pipe end, then records an ack pointer and deletes acknowledged chunks to bound database growth. +7. Either side closing triggers `state=closing` then `state=closed`; the session's Firebase node is deleted on completion. A background GC sweep also removes abandoned sessions (see [operations.md](./operations.md)). + +## Firebase database layout + +``` +sessions/ + {session_id}/ + metadata - sessionMetadata: session_id, version, target_host, target_port, + created_at, state (pending|active|closing|closed), user + c2s/ + {seq}/ - chunk: client→server data, keyed by monotonically increasing seq + s2c/ + {seq}/ - chunk: server→client data + acks/ + c2s_ack - uint64, highest consecutive c2s seq the server has processed + s2c_ack - uint64, highest consecutive s2c seq the client has processed +``` + +A `chunk` record: + +```json +{ + "seq": 42, + "timestamp": 1719500000000, + "compressed": true, + "encrypted": false, + "data": "" +} +``` + +`encrypted` is only set when the session's user has a PSK configured (see [security.md](./security.md)); `data` is then AES-256-GCM ciphertext (nonce-prefixed) rather than the raw/compressed payload. + +## Code layout (`protocol/firebasetunnel/`) + +| File | Purpose | +|---|---| +| `protocol.go` | Wire types (`sessionMetadata`, `chunk`), Firebase path helpers | +| `firebase.go` | REST client: Get/Put/Delete with retry, SSE `Listen` with jittered reconnect | +| `session.go` | `chunkSender` (batch/compress/encrypt + backpressure cap), `chunkReceiver` (reorder/decrypt/decompress), ack/cleanup | +| `crypto.go` | Optional AES-256-GCM payload encryption keyed by a PSK | +| `compress.go` | zstd wrappers | +| `util.go` | Jittered backoff, token bucket rate limiter | +| `client.go` | `ClientEndpoint`: session creation, `net.Pipe` dial adapter, c2s/s2c relay | +| `server.go` | `ServerEndpoint`: session discovery, user/limit enforcement, relay, GC sweep, SSM tracker hookup | diff --git a/protocol/firebasetunnel/docs/configuration.md b/protocol/firebasetunnel/docs/configuration.md new file mode 100644 index 0000000000..8c4a575b43 --- /dev/null +++ b/protocol/firebasetunnel/docs/configuration.md @@ -0,0 +1,72 @@ +# Configuration reference + +## `firebasetunnel_client` + +```json +{ + "type": "firebasetunnel_client", + "tag": "fb-client", + "firebase_urls": ["https://your-project-default-rtdb.firebaseio.com"], + "firebase_secret": "YOUR_DATABASE_SECRET", + "user": "alice", + "psk": "optional-shared-secret-for-alice", + "batch_interval": "50ms", + "batch_max_bytes": 32768, + "retry_limit": 5, + "activation_timeout": "30s" +} +``` + +| Field | Required | Default | Notes | +|---|---|---|---| +| `firebase_urls` | yes | — | First URL is used; list exists for future multi-backend failover. | +| `firebase_secret` | one of secret/token | — | Legacy Firebase Database Secret. Coarse: grants full read/write on the whole project. | +| `firebase_auth_token` | one of secret/token | — | Short-lived Firebase Auth/service-account token; preferred over `firebase_secret`. | +| `user` | yes | — | Self-reported accounting label sent in session metadata. | +| `psk` | no | — | If set, encrypts this client's relayed bytes and lets the server verify the `user` label cryptographically. Must match the server's per-user PSK for this `user`. | +| `batch_interval` | no | `50ms` | How long to accumulate bytes before an early flush timer fires. | +| `batch_max_bytes` | no | `32768` | Flush early once buffered bytes reach this size. | +| `retry_limit` | no | `5` | Firebase REST retry attempts before failing a request. | +| `activation_timeout` | no | `30s` | How long `DialContext` waits for the server to mark a session active before failing (lets outbound groups like `urltest`/`selector` fail over). | + +Use it as the target of another outbound's chain, or directly as a `detour`/`outbound` reference, the same way `tunnel_client` is used. + +## `firebasetunnel_server` + +```json +{ + "type": "firebasetunnel_server", + "tag": "fb-server", + "firebase_urls": ["https://your-project-default-rtdb.firebaseio.com"], + "firebase_secret": "YOUR_DATABASE_SECRET", + "users": [ + { "name": "alice", "psk": "optional-shared-secret-for-alice" }, + { "name": "bob" } + ], + "poll_interval": "200ms", + "session_timeout": "300s", + "retry_limit": 5, + "max_sessions": 1000, + "max_sessions_per_user": 50, + "max_sessions_per_second_per_user": 5 +} +``` + +| Field | Required | Default | Notes | +|---|---|---|---| +| `firebase_urls` | yes | — | Must match the client(s)' project. | +| `firebase_secret` / `firebase_auth_token` | one required | — | Same semantics as client. | +| `users` | yes, non-empty | — | Sessions declaring an unlisted `user` are rejected. | +| `users[].psk` | no | — | If set, sessions from this user must encrypt/decrypt correctly under this key or are rejected — turns the `user` label into a verified credential. | +| `poll_interval` | no | `200ms` | Fallback poll cadence alongside the SSE listen stream; also the c2s/s2c chunk poll interval during an active relay. | +| `session_timeout` | no | `300s` | Inactivity timeout for an active session; also drives the GC sweep interval (half this value) and the grace period for abandoned-session cleanup. | +| `retry_limit` | no | `5` | Firebase REST retry attempts. | +| `max_sessions` | no | `1000` | Global concurrent session cap. | +| `max_sessions_per_user` | no | `50` | Per-user concurrent session cap. | +| `max_sessions_per_second_per_user` | no | `5` | Token-bucket rate limit on new session creation per user. | + +The server endpoint dials real targets via the router (`router.RouteConnectionEx`), so normal `route` rules, DNS, and sniffing in the server's sing-box config apply to traffic exiting through this tunnel — it is not a raw unrestricted relay. + +## Traffic accounting + +Per-user upload/download bytes are tracked through the same SSM (`service/ssmapi`) traffic manager used by the Shadowsocks multi-user inbound, so Hiddify Manager can read `firebasetunnel_server` usage the same way it already reads Shadowsocks usage — wire a tracker via `ServerEndpoint.SetTracker(...)` at box-construction time. diff --git a/protocol/firebasetunnel/docs/operations.md b/protocol/firebasetunnel/docs/operations.md new file mode 100644 index 0000000000..64c481cdfa --- /dev/null +++ b/protocol/firebasetunnel/docs/operations.md @@ -0,0 +1,46 @@ +# Operations + +## Expected latency and throughput + +Polling/SSE against Firebase Realtime Database imposes a realistic floor of **~200ms+ added round-trip latency** beyond normal network latency — this is inherent to the relay mechanism (inherited from the upstream PoC's own measurements), not something this implementation eliminates. Treat this protocol as a fallback transport for blocked-direct-connectivity scenarios, not a low-latency primary path. + +Firebase Realtime Database enforces project-level read/write quotas; high session counts or throughput can hit these limits. See caps below for how this implementation bounds its own usage; operators should also monitor Firebase's own quota dashboards. + +## Resource limits (server-side) + +| Limit | Config field | Default | Effect when exceeded | +|---|---|---|---| +| Global concurrent sessions | `max_sessions` | 1000 | New session rejected (`state=closed`), logged at Warn. | +| Per-user concurrent sessions | `max_sessions_per_user` | 50 | Same — bounds one user's worst-case resource use. | +| Per-user session creation rate | `max_sessions_per_second_per_user` | 5/s (token bucket) | New session rejected if the user's bucket is empty — protects against a compromised/buggy client spamming session creation. | +| Sender pending-bytes (client and server) | — (internal, `maxPendingBytes`) | 8 MiB | `feed()` returns an error rather than buffering unbounded memory; caller should treat this as backpressure. | + +Rejections are logged with the session ID at `Warn` (never with secrets/PSKs) so operators can spot misconfigured clients or abuse attempts. + +## Session garbage collection + +Two timeout-driven cleanups exist: + +1. **In-relay timeout**: an *active* session with no chunk activity for `session_timeout` (default 300s) is torn down by the relay loop itself. +2. **GC sweep**: a background loop (interval = `session_timeout`/2) scans `sessions/` and deletes: + - sessions stuck in `pending` (never reached `active`) older than `session_timeout` — covers a client that crashed after writing metadata but before the server ever responded, + - sessions stuck in `closing`/`closed` for more than `session_timeout` + 10s grace — covers a peer that died mid-shutdown handshake. + +This prevents unbounded Firebase node growth from abandoned sessions without relying on either side cleanly exiting. + +## Failure isolation + +- The server's poll loop and GC loop each run under panic recovery with a 5s restart backoff (`runWithRecovery`) — a bug in either does not crash the rest of the sing-box process or other endpoints/inbounds sharing it. +- A sustained Firebase outage degrades the server to "no new sessions accepted, existing sessions time out via `session_timeout`" rather than a stuck goroutine. +- On the client side, `DialContext` fails within `activation_timeout` (default 30s) if the server never marks a session active — bounded rather than indefinite, so outbound groups (`urltest`/`selector`) can fail over to an alternative outbound in reasonable time. +- SSE reconnects use jittered exponential backoff (`jitteredBackoff`, capped at 30s) to avoid thundering-herd reconnects when many sessions/instances share one Firebase project. + +## Rollout / rollback + +This protocol ships as ordinary registered endpoint types (`firebasetunnel_client`/`firebasetunnel_server`) — there is currently no build-tag or experimental-flag gate. Adopting it for a fleet should be done config-by-config (enable on a small subset first, monitor logs and SSM stats, then widen) rather than relying on a kill switch built into the binary. Disabling it for a given deployment means removing the endpoint from that config; the server endpoint's `Close()` cancels its poll/GC loops but does not forcibly kill in-flight sessions — they drain naturally via the timeout/GC mechanisms above. + +## What to monitor + +- Server logs at `Warn`/`Error` for: rejected sessions (unknown user, limit/rate exceeded), ack/ingest failures (potential decrypt/auth failures if PSKs are in use), GC deletions (volume spikes may indicate misbehaving clients). +- SSM traffic stats (if a tracker is wired via `SetTracker`) for per-user byte counts, the same surface used for Shadowsocks usage today. +- Firebase project's own console/quota metrics for read/write volume against plan limits. diff --git a/protocol/firebasetunnel/docs/provenance.md b/protocol/firebasetunnel/docs/provenance.md new file mode 100644 index 0000000000..2b69129eda --- /dev/null +++ b/protocol/firebasetunnel/docs/provenance.md @@ -0,0 +1,29 @@ +# Provenance + +This protocol is inspired by [github.com/Hiddify2/Firebase-Tunnel](https://github.com/Hiddify2/Firebase-Tunnel), a small (single-star, no LICENSE file) proof-of-concept that relays a SOCKS5 connection through Firebase Realtime Database REST calls. Because the upstream repository carries no license, **no code was copied or vendored** — the mechanism (batched/compressed/base64 chunks written to sequential database keys, ack-and-delete cleanup, SSE-based session discovery) was studied and reimplemented from scratch for sing-box's architecture. + +## What's conceptually the same as the PoC + +- Firebase REST `GET`/`PUT`/`DELETE` for session metadata and chunk queues, plus SSE `Listen` for push-based session discovery. +- Chunk batching (time-or-size triggered flush), zstd compression, base64 encoding, sequence-numbered ordering with out-of-order buffering. +- Ack-pointer-then-delete cleanup pattern to bound database growth. +- Session state machine: `pending` → `active` → `closing` → `closed`. + +## What's different + +| Aspect | Upstream PoC | This implementation | +|---|---|---| +| Client transport entry point | Local SOCKS5 listener | sing-box's own inbound chain (mixed/socks/etc.) feeds connections in; this protocol is purely the outbound leg, exposed as a `net.Conn` via `net.Pipe()` from `DialContext`. | +| Server egress | Raw `net.Dial` to the declared target | `router.RouteConnectionEx`, so the server's own sing-box `route` rules, DNS, and sniffing apply — this cannot be used to bypass routing restrictions configured elsewhere in the same sing-box instance. | +| Multi-tenancy | Single-tenant (no user concept) | `user` label per session, validated against a configured user list; optional per-user PSK turns the label into a verified credential. | +| Traffic accounting | None | Wired into the existing SSM (`service/ssmapi`) traffic manager via `ServerEndpoint.SetTracker`, the same mechanism used for Shadowsocks per-user usage. | +| Auth | Legacy Database Secret only | Legacy secret retained as the simple default, plus an optional Firebase Auth/service-account token path. | +| Payload confidentiality | None (cleartext to anyone who can read the Firebase project) | Optional AES-256-GCM encryption keyed by a per-user PSK. | +| Resource limits | None | Global/per-user session caps, per-user session-creation rate limiting, sender backpressure byte cap. | +| Abandoned-session cleanup | In-session inactivity timeout only | Same, plus a background GC sweep for sessions that never reached `active` or that stalled mid-shutdown. | +| Failure isolation | N/A (single-purpose binary) | Poll/GC loops run under panic recovery with restart backoff so a bug can't take down the rest of the sing-box process. | +| Reconnect behavior | Fixed 2s SSE reconnect delay | Jittered exponential backoff, to avoid thundering-herd reconnects across many sessions/instances sharing a project. | + +## Known shared limitation + +Both the upstream PoC and this implementation share the same fundamental latency floor (~200ms+ added round-trip) and Firebase rate-limit exposure, since both rely on the same underlying Firebase RTDB polling/SSE mechanism — this is an inherent property of the transport choice, not an implementation gap. See [operations.md](./operations.md). diff --git a/protocol/firebasetunnel/docs/security.md b/protocol/firebasetunnel/docs/security.md new file mode 100644 index 0000000000..bda93aeac9 --- /dev/null +++ b/protocol/firebasetunnel/docs/security.md @@ -0,0 +1,41 @@ +# Security / threat model + +## Who can see what + +By default (no `psk` configured), relayed application bytes are **cleartext to anyone who can read the Firebase project** — the project owner, anyone holding the `firebase_secret` or a valid auth token, and anyone with read access to the Realtime Database console. Transport security (HTTPS to Firebase) protects bytes in transit between this process and Firebase, but not from Firebase-side observers. + +Even with the optional PSK encryption enabled (below), **metadata is never hidden**: `target_host`/`target_port` (the dial destination), session timing, and chunk sizes are visible to anyone with project read access. This protocol does not protect against traffic analysis — an observer who can read the Firebase project can infer what's being accessed and roughly how much data flows, even if the payload itself is encrypted. + +## Auth options + +| Option | Field | Strength | Notes | +|---|---|---|---| +| Legacy Database Secret | `firebase_secret` | Weak | Single shared secret grants full read/write to the *entire* Firebase project, not just this tunnel's data. Matches the upstream PoC's default; simplest to set up. | +| Firebase Auth / service-account token | `firebase_auth_token` | Better | Short-lived, can be scoped via Firebase security rules. Recommended for anything beyond personal/test use. | + +Neither option authenticates *which client* is connecting — that's what the `user` label and optional per-user PSK are for. + +## User-label verification (optional, per-user PSK) + +Without a PSK, `user` is a **self-reported claim** — the server uses it only as an accounting label, not a credential. Any client holding the project's `firebase_secret`/token could claim to be any user. + +Setting `psk` on a `FirebaseTunnelUser` (server) and the matching `psk` on the client turns the label into a verified credential: the server only accepts a session's declared `user` if its chunks decrypt successfully under that user's derived key (AES-256-GCM, key derived from the PSK via SHA-256). A mismatched or missing PSK causes chunk ingestion to fail, and the session is treated as an abuse signal (logged, counted against that user's failure rate) rather than silently relayed. + +## Payload encryption + +When a PSK is configured for a user, every chunk's payload is encrypted with AES-256-GCM before base64 encoding (`chunk.encrypted = true`). This is **opt-in** — off by default, matching the upstream PoC's simplest-path behavior — because: +- it requires coordinating a PSK between client and server config (extra operational step), +- the metadata-visibility limitation above means it's a partial mitigation, not full traffic confidentiality. + +Strongly recommended for anything beyond personal/throwaway test use. + +## What this protocol does *not* protect against + +- A malicious or compromised Firebase project administrator (full project access bypasses everything above). +- Traffic analysis via session/chunk timing and sizes, even with payload encryption enabled. +- Firebase-side logging/retention outside this protocol's control. +- Denial of service from a party who obtains the shared secret/token (mitigated, not eliminated, by the per-user rate limits and session caps described in [operations.md](./operations.md)). + +## Rotation + +Secrets and PSKs are currently single-value per config (`firebase_secret`, `users[].psk`) — rotating either requires a coordinated config update on both client and server with a brief overlap window where old sessions using the previous value may fail. Multi-value (list-based) rotation support is a known follow-up, not yet implemented. diff --git a/protocol/firebasetunnel/endpoint.go b/protocol/firebasetunnel/endpoint.go new file mode 100644 index 0000000000..f2cf8eb2e4 --- /dev/null +++ b/protocol/firebasetunnel/endpoint.go @@ -0,0 +1,754 @@ +package firebasetunnel + +import ( + "context" + "fmt" + "net" + "os" + "sync" + "sync/atomic" + "time" + + "github.com/google/uuid" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/endpoint" + "github.com/sagernet/sing-box/adapter/outbound" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +// Timing / capacity defaults. Callers may override via options. +const ( + defaultBatchInterval = 50 * time.Millisecond + defaultBatchMaxBytes = 32 * 1024 + defaultRetryLimit = 5 + defaultActivationTimeout = 30 * time.Second + defaultS2CPollInterval = 150 * time.Millisecond + defaultReadBufSize = 32 * 1024 + warnActivationAfter = 3 * time.Second + + defaultPollInterval = 200 * time.Millisecond + defaultSessionTimeout = 300 * time.Second + defaultMaxSessions = 1000 + defaultMaxSessionsPerUser = 50 + defaultMaxSessionsPerSecondPerUser = 5 + drainTimeout = 5 * time.Second +) + +// userConfig holds per-user server-side settings derived from options at +// construction time. +type userConfig struct { + name string + key *[32]byte // nil → no PSK, HMAC-only integrity +} + +// serverState holds all fields that are only needed in server (inbound) mode. +// When Endpoint.srv is nil the endpoint is in client (outbound) mode. +type serverState struct { + router adapter.Router + users map[string]userConfig + pollInterval time.Duration + sessionTimeout time.Duration + tracker adapter.SSMTracker + + maxSessions int + maxSessionsPerUser int + sessionRate int + + mu sync.Mutex + activeSessions int + perUserActive map[string]int + perUserBucket map[string]*tokenBucket + + closing atomic.Bool + stop context.CancelFunc +} + +// Endpoint implements adapter.Endpoint for the Firebase Tunnel protocol in +// either client (outbound) or server (inbound) mode. +// +// Role is fixed at construction time: +// - options.Client != nil → client mode: dials via DialContext +// - options.Server != nil → server mode: discovers sessions in pollLoop +// +// Exactly one must be set; NewEndpoint validates this. +type Endpoint struct { + outbound.Adapter + ctx context.Context + logger logger.ContextLogger + fb *firebaseClient + key *[32]byte // encryption key (PSK-derived); nil → HMAC-only + hmacKey []byte // integrity key; nil when key != nil or no secret + + // client-mode only + user string + batchInterval time.Duration + batchMaxBytes int + activationTimeout time.Duration + + // non-nil only in server mode + srv *serverState +} + +func RegisterEndpoint(registry *endpoint.Registry) { + endpoint.Register[option.FirebaseTunnelOptions](registry, C.TypeFirebaseTunnel, NewEndpoint) +} + +func NewEndpoint(ctx context.Context, router adapter.Router, lg log.ContextLogger, tag string, options option.FirebaseTunnelOptions) (adapter.Endpoint, error) { + switch { + case options.Server != nil && options.Client != nil: + return nil, E.New("firebasetunnel: set exactly one of server or client, not both") + case options.Server == nil && options.Client == nil: + return nil, E.New("firebasetunnel: one of server or client must be set") + case len(options.FirebaseURLs) == 0: + return nil, E.New("firebase_urls is required") + case options.FirebaseSecret == "" && options.FirebaseAuthToken == "": + return nil, E.New("one of firebase_secret or firebase_auth_token is required") + } + + retryLimit := options.RetryLimit + if retryLimit == 0 { + retryLimit = defaultRetryLimit + } + + ep := &Endpoint{ + Adapter: outbound.NewAdapter(C.TypeFirebaseTunnel, tag, []string{N.NetworkTCP}, nil), + ctx: ctx, + logger: lg, + fb: newFirebaseClient(options.FirebaseURLs[0], options.FirebaseSecret, options.FirebaseAuthToken, retryLimit, lg), + } + + if options.Client != nil { + return ep, ep.applyClientOptions(options) + } + return ep, ep.applyServerOptions(ctx, router, options) +} + +func (e *Endpoint) applyClientOptions(options option.FirebaseTunnelOptions) error { + c := options.Client + if c.User == "" { + return E.New("client.user is required") + } + + if c.PSK != "" { + k := deriveKey(c.PSK) + e.key = &k + } else if options.FirebaseSecret != "" { + e.hmacKey = deriveHMACKey(options.FirebaseSecret) + } + + e.user = c.User + e.batchInterval = durationOr(time.Duration(c.BatchInterval), defaultBatchInterval) + e.batchMaxBytes = intOr(c.BatchMaxBytes, defaultBatchMaxBytes) + e.activationTimeout = durationOr(time.Duration(c.ActivationTimeout), defaultActivationTimeout) + return nil +} + +func (e *Endpoint) applyServerOptions(ctx context.Context, router adapter.Router, options option.FirebaseTunnelOptions) error { + s := options.Server + if len(s.Users) == 0 { + return E.New("server.users must have at least one entry") + } + + users := make(map[string]userConfig, len(s.Users)) + for _, u := range s.Users { + if u.Name == "" { + return E.New("user name must not be empty") + } + uc := userConfig{name: u.Name} + if u.PSK != "" { + k := deriveKey(u.PSK) + uc.key = &k + } + users[u.Name] = uc + } + + if options.FirebaseSecret != "" { + e.hmacKey = deriveHMACKey(options.FirebaseSecret) + } + + e.srv = &serverState{ + router: router, + users: users, + pollInterval: durationOr(time.Duration(s.PollInterval), defaultPollInterval), + sessionTimeout: durationOr(time.Duration(s.SessionTimeout), defaultSessionTimeout), + maxSessions: intOr(s.MaxSessions, defaultMaxSessions), + maxSessionsPerUser: intOr(s.MaxSessionsPerUser, defaultMaxSessionsPerUser), + sessionRate: intOr(s.MaxSessionsPerSecondPerUser, defaultMaxSessionsPerSecondPerUser), + perUserActive: make(map[string]int), + perUserBucket: make(map[string]*tokenBucket), + } + return nil +} + +// SetTracker wires per-user traffic accounting into the SSM stats subsystem. +// No-op in client mode. +func (e *Endpoint) SetTracker(tracker adapter.SSMTracker) { + if e.srv == nil { + return + } + e.srv.mu.Lock() + e.srv.tracker = tracker + e.srv.mu.Unlock() +} + +func (e *Endpoint) Start(stage adapter.StartStage) error { + if e.srv == nil || stage != adapter.StartStatePostStart { + return nil + } + ctx, cancel := context.WithCancel(e.ctx) + e.srv.stop = cancel + go e.runWithRecovery(ctx, e.pollLoop) + go e.runWithRecovery(ctx, e.gcLoop) + return nil +} + +func (e *Endpoint) Close() error { + if e.srv == nil { + return nil + } + srv := e.srv + srv.closing.Store(true) + deadline := time.Now().Add(drainTimeout) + for time.Now().Before(deadline) { + srv.mu.Lock() + active := srv.activeSessions + srv.mu.Unlock() + if active == 0 { + break + } + time.Sleep(100 * time.Millisecond) + } + if srv.stop != nil { + srv.stop() + } + return nil +} + +// DialContext is only valid in client mode; returns os.ErrInvalid in server mode. +func (e *Endpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + if e.srv != nil || network != N.NetworkTCP { + return nil, os.ErrInvalid + } + + sessionID := uuid.New().String() + meta := sessionMetadata{ + SessionID: sessionID, + Version: protocolVersion, + TargetHost: destination.AddrString(), + TargetPort: destination.Port, + CreatedAt: nowMillis(), + State: sessionStatePending, + User: e.user, + } + if err := e.fb.Put(ctx, pathMetadata(sessionID), &meta); err != nil { + return nil, fmt.Errorf("firebasetunnel: creating session: %w", err) + } + + activateCtx, cancel := context.WithTimeout(ctx, e.activationTimeout) + defer cancel() + if err := e.waitForActive(activateCtx, sessionID); err != nil { + return nil, fmt.Errorf("firebasetunnel: session activation: %w", err) + } + + local, remote := net.Pipe() + go e.runSession(ctx, sessionID, remote) + return local, nil +} + +func (e *Endpoint) ListenPacket(_ context.Context, _ M.Socksaddr) (net.PacketConn, error) { + return nil, os.ErrInvalid +} + +// ── client internals ────────────────────────────────────────────────────────── + +func (e *Endpoint) waitForActive(ctx context.Context, sessionID string) error { + path := pathMetadata(sessionID) + start := time.Now() + warned := false + consecutiveErrors := 0 + + for { + var meta sessionMetadata + found, err := e.fb.Get(ctx, path, &meta) + if err != nil { + consecutiveErrors++ + e.logger.WarnContext(ctx, "firebasetunnel: poll error waiting for session ", sessionID, " to activate: ", err) + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(jitteredBackoff(consecutiveErrors)): + continue + } + } + consecutiveErrors = 0 + + if found { + switch meta.State { + case sessionStateActive: + return nil + case sessionStateClosed: + return E.New("server rejected session") + } + } + + if !warned && time.Since(start) > warnActivationAfter { + warned = true + e.logger.WarnContext(ctx, "firebasetunnel: session ", sessionID, " still pending after ", + time.Since(start).Round(time.Second), " — server may be absent or overloaded") + } + + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(250 * time.Millisecond): + } + } +} + +func (e *Endpoint) runSession(ctx context.Context, sessionID string, conn net.Conn) { + defer conn.Close() + + sender := newChunkSender(ctx, pathC2S(sessionID), sessionID, "c2s", + e.fb, e.batchInterval, e.batchMaxBytes, e.key, e.hmacKey, e.logger) + + relayCtx, cancel := context.WithCancel(ctx) + defer cancel() + + c2sDone := make(chan struct{}) + go func() { + defer close(c2sDone) + copyToSender(relayCtx, conn, sender, e.logger) + _ = e.setSessionState(context.Background(), sessionID, sessionStateClosing) + }() + + s2cDone := make(chan struct{}) + go func() { + defer close(s2cDone) + e.runS2C(relayCtx, sessionID, conn) + }() + + select { + case <-c2sDone: + case <-s2cDone: + } + cancel() + + _ = e.setSessionState(context.Background(), sessionID, sessionStateClosed) + if err := e.fb.Delete(context.Background(), "sessions/"+sessionID); err != nil { + e.logger.WarnContext(ctx, "firebasetunnel: session cleanup failed: ", err) + } +} + +func (e *Endpoint) runS2C(ctx context.Context, sessionID string, conn net.Conn) { + receiver, byteRx := newChunkReceiver(e.key, e.hmacKey, sessionID, "s2c") + var deliveredUpTo *uint64 + s2cPath := pathS2C(sessionID) + ackPath := pathAcks(sessionID) + "/s2c_ack" + + for { + var meta sessionMetadata + if found, err := e.fb.Get(ctx, pathMetadata(sessionID), &meta); err == nil && found && isTerminal(meta.State) { + return + } + + chunks, err := fetchNewChunks(ctx, e.fb, s2cPath, deliveredUpTo) + if err != nil { + select { + case <-ctx.Done(): + return + case <-time.After(defaultS2CPollInterval): + continue + } + } + + for _, ck := range chunks { + newAck, err := receiver.ingest(ctx, ck) + if err != nil { + e.logger.WarnContext(ctx, "firebasetunnel: s2c ingest error: ", err) + return + } + if newAck == nil { + continue + } + deliveredUpTo = newAck + if err := drainInto(byteRx, conn); err != nil { + return + } + if err := updateAckAndCleanup(ctx, e.fb, ackPath, s2cPath, *newAck, e.logger); err != nil { + e.logger.WarnContext(ctx, "firebasetunnel: ack update failed: ", err) + } + } + + select { + case <-ctx.Done(): + return + case <-time.After(defaultS2CPollInterval): + } + } +} + +func (e *Endpoint) setSessionState(ctx context.Context, sessionID string, state sessionState) error { + path := pathMetadata(sessionID) + var meta sessionMetadata + found, err := e.fb.Get(ctx, path, &meta) + if err != nil || !found { + return err + } + meta.State = state + return e.fb.Put(ctx, path, &meta) +} + +// ── server internals ────────────────────────────────────────────────────────── + +func (e *Endpoint) runWithRecovery(ctx context.Context, fn func(context.Context)) { + for { + if ctx.Err() != nil { + return + } + func() { + defer func() { + if r := recover(); r != nil { + e.logger.ErrorContext(ctx, "firebasetunnel: server loop panic, restarting: ", r) + } + }() + fn(ctx) + }() + if ctx.Err() != nil { + return + } + select { + case <-ctx.Done(): + return + case <-time.After(5 * time.Second): + } + } +} + +func (e *Endpoint) pollLoop(ctx context.Context) { + srv := e.srv + seen := make(map[string]struct{}) + events := e.fb.listen(ctx, pathSessionsRoot()) + ticker := time.NewTicker(srv.pollInterval) + defer ticker.Stop() + + dispatch := func() { + var raw map[string]sessionMetadata + if found, err := e.fb.Get(ctx, pathSessionsRoot(), &raw); err != nil || !found { + return + } + for id, meta := range raw { + if meta.State != sessionStatePending { + continue + } + if _, ok := seen[id]; ok { + continue + } + seen[id] = struct{}{} + meta.SessionID = id + go e.handleSession(ctx, meta) + } + } + + for { + select { + case <-ctx.Done(): + return + case _, ok := <-events: + if !ok { + events = e.fb.listen(ctx, pathSessionsRoot()) + continue + } + dispatch() + case <-ticker.C: + dispatch() + } + } +} + +func (e *Endpoint) handleSession(ctx context.Context, meta sessionMetadata) { + sessionID := meta.SessionID + srv := e.srv + + userCfg, ok := srv.users[meta.User] + if !ok { + e.logger.WarnContext(ctx, "firebasetunnel: rejecting session ", sessionID, ": unknown user") + e.closeSession(ctx, sessionID) + return + } + if !e.acquireSessionSlot(meta.User) { + e.logger.WarnContext(ctx, "firebasetunnel: rejecting session ", sessionID, ": limit/rate exceeded for user ", meta.User) + e.closeSession(ctx, sessionID) + return + } + defer e.releaseSessionSlot(meta.User) + + // Collision defense: re-read to verify CreatedAt hasn't changed under us. + var current sessionMetadata + if found, _ := e.fb.Get(ctx, pathMetadata(sessionID), ¤t); !found || current.CreatedAt != meta.CreatedAt { + e.logger.WarnContext(ctx, "firebasetunnel: session collision for ", sessionID, ", rejecting") + e.closeSession(ctx, sessionID) + return + } + + destination := M.ParseSocksaddrHostPort(meta.TargetHost, meta.TargetPort) + inboundMeta := adapter.InboundContext{ + Inbound: e.Tag(), + InboundType: C.TypeFirebaseTunnel, + Destination: destination, + User: meta.User, + } + + local, remote := net.Pipe() + var conn net.Conn = local + srv.mu.Lock() + tracker := srv.tracker + srv.mu.Unlock() + if tracker != nil { + conn = tracker.TrackConnection(conn, inboundMeta) + } + + updated := meta + updated.State = sessionStateActive + if err := e.fb.Put(ctx, pathMetadata(sessionID), &updated); err != nil { + remote.Close() + local.Close() + e.logger.WarnContext(ctx, "firebasetunnel: marking session active failed: ", err) + return + } + + sessionKey := userCfg.key + sessionHMACKey := e.hmacKey + if sessionKey != nil { + sessionHMACKey = nil // AEAD handles integrity; HMAC not needed + } + + relayDone := make(chan struct{}) + go func() { + defer close(relayDone) + e.runRelay(ctx, sessionID, remote, sessionKey, sessionHMACKey) + }() + + srv.router.RouteConnectionEx(ctx, conn, inboundMeta, func(error) {}) + <-relayDone + + updated.State = sessionStateClosed + _ = e.fb.Put(context.Background(), pathMetadata(sessionID), &updated) + if err := e.fb.Delete(context.Background(), "sessions/"+sessionID); err != nil { + e.logger.WarnContext(ctx, "firebasetunnel: session cleanup failed: ", err) + } +} + +func (e *Endpoint) closeSession(ctx context.Context, sessionID string) { + _ = e.fb.Put(ctx, pathMetadata(sessionID), &sessionMetadata{SessionID: sessionID, State: sessionStateClosed}) +} + +func (e *Endpoint) runRelay(ctx context.Context, sessionID string, conn net.Conn, key *[32]byte, hmacKey []byte) { + defer conn.Close() + srv := e.srv + + relayCtx, cancel := context.WithCancel(ctx) + defer cancel() + + c2sDone := make(chan struct{}) + go func() { + defer close(c2sDone) + e.runC2S(relayCtx, sessionID, conn, key, hmacKey) + }() + + s2cDone := make(chan struct{}) + go func() { + defer close(s2cDone) + sender := newChunkSender(relayCtx, pathS2C(sessionID), sessionID, "s2c", + e.fb, srv.pollInterval, defaultReadBufSize, key, hmacKey, e.logger) + copyToSender(relayCtx, conn, sender, e.logger) + }() + + select { + case <-c2sDone: + case <-s2cDone: + } + cancel() +} + +func (e *Endpoint) runC2S(ctx context.Context, sessionID string, conn net.Conn, key *[32]byte, hmacKey []byte) { + srv := e.srv + receiver, byteRx := newChunkReceiver(key, hmacKey, sessionID, "c2s") + var deliveredUpTo *uint64 + c2sPath := pathC2S(sessionID) + ackPath := pathAcks(sessionID) + "/c2s_ack" + lastActivity := time.Now() + + for { + if time.Since(lastActivity) > srv.sessionTimeout { + e.logger.WarnContext(ctx, "firebasetunnel: session ", sessionID, " timed out") + return + } + + var meta sessionMetadata + if found, err := e.fb.Get(ctx, pathMetadata(sessionID), &meta); err == nil && found && isTerminal(meta.State) { + return + } + + chunks, err := fetchNewChunks(ctx, e.fb, c2sPath, deliveredUpTo) + if err != nil { + select { + case <-ctx.Done(): + return + case <-time.After(srv.pollInterval): + continue + } + } + if len(chunks) > 0 { + lastActivity = time.Now() + } + + for _, ck := range chunks { + newAck, err := receiver.ingest(ctx, ck) + if err != nil { + e.logger.WarnContext(ctx, "firebasetunnel: c2s ingest error (session ", sessionID, "): ", err) + return + } + if newAck == nil { + continue + } + deliveredUpTo = newAck + if err := drainInto(byteRx, conn); err != nil { + return + } + if err := updateAckAndCleanup(ctx, e.fb, ackPath, c2sPath, *newAck, e.logger); err != nil { + e.logger.WarnContext(ctx, "firebasetunnel: ack update failed: ", err) + } + } + + select { + case <-ctx.Done(): + return + case <-time.After(srv.pollInterval): + } + } +} + +func (e *Endpoint) gcLoop(ctx context.Context) { + srv := e.srv + ticker := time.NewTicker(srv.sessionTimeout / 2) + defer ticker.Stop() + const closedGrace = 10 * time.Second + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + var raw map[string]sessionMetadata + if found, err := e.fb.Get(ctx, pathSessionsRoot(), &raw); err != nil || !found { + continue + } + now := nowMillis() + for id, meta := range raw { + age := time.Duration(now-meta.CreatedAt) * time.Millisecond + stale := (meta.State == sessionStatePending && age > srv.sessionTimeout) || + (isTerminal(meta.State) && age > srv.sessionTimeout+closedGrace) + if stale { + if err := e.fb.Delete(ctx, "sessions/"+id); err != nil { + e.logger.WarnContext(ctx, "firebasetunnel: gc delete failed for ", id, ": ", err) + } + } + } + } + } +} + +func (e *Endpoint) acquireSessionSlot(user string) bool { + srv := e.srv + if srv.closing.Load() { + return false + } + srv.mu.Lock() + defer srv.mu.Unlock() + + bucket, ok := srv.perUserBucket[user] + if !ok { + bucket = newTokenBucket(srv.sessionRate, srv.sessionRate) + srv.perUserBucket[user] = bucket + } + if !bucket.take() || srv.activeSessions >= srv.maxSessions || srv.perUserActive[user] >= srv.maxSessionsPerUser { + return false + } + srv.activeSessions++ + srv.perUserActive[user]++ + return true +} + +func (e *Endpoint) releaseSessionSlot(user string) { + srv := e.srv + srv.mu.Lock() + defer srv.mu.Unlock() + srv.activeSessions-- + if srv.perUserActive[user]--; srv.perUserActive[user] == 0 { + delete(srv.perUserActive, user) + } +} + +// ── helpers ─────────────────────────────────────────────────────────────────── + +// isTerminal reports whether s is a state that signals the session is ending. +func isTerminal(s sessionState) bool { + return s == sessionStateClosing || s == sessionStateClosed +} + +// drainInto reads all buffered data from ch and writes it to conn. +// Returns the first write error encountered, or nil. +func drainInto(ch <-chan []byte, conn net.Conn) error { + for { + select { + case data := <-ch: + if _, err := conn.Write(data); err != nil { + return err + } + default: + return nil + } + } +} + +// copyToSender reads from conn in a loop and feeds each chunk to sender. +// Stops (without error) when conn returns an error or ctx is cancelled. +func copyToSender(ctx context.Context, conn net.Conn, sender *chunkSender, lg logger.ContextLogger) { + buf := make([]byte, defaultReadBufSize) + for { + n, err := conn.Read(buf) + if n > 0 { + if feedErr := sender.feed(ctx, buf[:n]); feedErr != nil { + if lg != nil { + lg.WarnContext(ctx, "firebasetunnel: sender feed error: ", feedErr) + } + return + } + } + if err != nil { + return + } + } +} + +// durationOr returns d if d > 0, otherwise fallback. +func durationOr(d, fallback time.Duration) time.Duration { + if d > 0 { + return d + } + return fallback +} + +// intOr returns v if v > 0, otherwise fallback. +func intOr(v, fallback int) int { + if v > 0 { + return v + } + return fallback +} diff --git a/protocol/firebasetunnel/endpoint_test.go b/protocol/firebasetunnel/endpoint_test.go new file mode 100644 index 0000000000..4823dd08e3 --- /dev/null +++ b/protocol/firebasetunnel/endpoint_test.go @@ -0,0 +1,143 @@ +package firebasetunnel + +import ( + "context" + "io" + "net" + "testing" + "time" +) + +// TestRelayRoundTrip exercises the full chunk relay pipeline end-to-end using +// the fakeFirebaseServer (with SSE). It simulates what handleSession does: +// one side runs runRelay (server), the other side runs runSession (client), +// and bytes must flow both directions correctly. +func TestRelayRoundTrip(t *testing.T) { + srv := newFakeFirebaseServer() + defer srv.Close() + + fb := newFirebaseClient(srv.URL, "relay-secret", "", 0, nil) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + sessionID := "e2e-relay-test" + hmacKey := deriveHMACKey("relay-secret") + + // server side: local↔remote pipe; runRelay on remote end + serverLocal, serverRemote := net.Pipe() + + serverDone := make(chan struct{}) + go func() { + defer close(serverDone) + s := &Endpoint{ + fb: fb, + srv: &serverState{pollInterval: 20 * time.Millisecond, sessionTimeout: 10 * time.Second}, + } + s.runRelay(ctx, sessionID, serverRemote, nil, hmacKey) + }() + + // client side: runSession writes c2s to Firebase, reads s2c from Firebase. + clientLocal, clientRemote := net.Pipe() + + clientDone := make(chan struct{}) + go func() { + defer close(clientDone) + c := &Endpoint{ + fb: fb, + clientKey: nil, + hmacKey: hmacKey, + batchInterval: 20 * time.Millisecond, + batchMaxBytes: defaultBatchMaxBytes, + } + c.runSession(ctx, sessionID, clientRemote) + }() + + // Echo goroutine on server's local end: read what server delivers, write it back. + echoDone := make(chan struct{}) + go func() { + defer close(echoDone) + io.Copy(serverLocal, serverLocal) //nolint:errcheck + }() + + payload := []byte("hello end-to-end relay") + + if _, err := clientLocal.Write(payload); err != nil { + t.Fatalf("client write: %v", err) + } + + buf := make([]byte, len(payload)) + clientLocal.SetReadDeadline(time.Now().Add(8 * time.Second)) + n, err := io.ReadFull(clientLocal, buf) + if err != nil { + t.Fatalf("client read: %v (got %d bytes)", err, n) + } + if string(buf[:n]) != string(payload) { + t.Fatalf("round-trip mismatch: got %q want %q", buf[:n], payload) + } + + clientLocal.Close() + serverLocal.Close() + <-clientDone + <-serverDone +} + +// TestRelayEncryptedRoundTrip is the same as TestRelayRoundTrip but uses PSK encryption. +func TestRelayEncryptedRoundTrip(t *testing.T) { + srv := newFakeFirebaseServer() + defer srv.Close() + + fb := newFirebaseClient(srv.URL, "relay-secret", "", 0, nil) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + sessionID := "e2e-encrypted-relay-test" + psk := "e2e-psk" + key := deriveKey(psk) + + serverLocal, serverRemote := net.Pipe() + serverDone := make(chan struct{}) + go func() { + defer close(serverDone) + s := &Endpoint{ + fb: fb, + srv: &serverState{pollInterval: 20 * time.Millisecond, sessionTimeout: 10 * time.Second}, + } + s.runRelay(ctx, sessionID, serverRemote, &key, nil) + }() + + clientLocal, clientRemote := net.Pipe() + clientDone := make(chan struct{}) + go func() { + defer close(clientDone) + c := &Endpoint{ + fb: fb, + clientKey: &key, + hmacKey: nil, + batchInterval: 20 * time.Millisecond, + batchMaxBytes: defaultBatchMaxBytes, + } + c.runSession(ctx, sessionID, clientRemote) + }() + + go io.Copy(serverLocal, serverLocal) //nolint:errcheck + + payload := []byte("encrypted end-to-end relay") + if _, err := clientLocal.Write(payload); err != nil { + t.Fatalf("client write: %v", err) + } + + buf := make([]byte, len(payload)) + clientLocal.SetReadDeadline(time.Now().Add(8 * time.Second)) + n, err := io.ReadFull(clientLocal, buf) + if err != nil { + t.Fatalf("client read: %v (got %d bytes)", err, n) + } + if string(buf[:n]) != string(payload) { + t.Fatalf("round-trip mismatch: got %q want %q", buf[:n], payload) + } + + clientLocal.Close() + serverLocal.Close() + <-clientDone + <-serverDone +} diff --git a/protocol/firebasetunnel/firebase.go b/protocol/firebasetunnel/firebase.go new file mode 100644 index 0000000000..5599633186 --- /dev/null +++ b/protocol/firebasetunnel/firebase.go @@ -0,0 +1,250 @@ +package firebasetunnel + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "time" + + "github.com/sagernet/sing-box/log" +) + +// sseEvent is a parsed Server-Sent Event from Firebase's streaming endpoint. +type sseEvent struct { + Event string + Data string +} + +// firebaseClient is an async Firebase Realtime Database REST client. +// Safe for concurrent use. +// +// Auth: if authToken is set, requests use ?access_token= (short-lived +// Firebase Auth / service-account token, recommended). Otherwise secret is +// used as ?auth= (legacy Database Secret: anyone holding it has +// full read/write access to the entire Firebase project, not just this +// tunnel's data). +type firebaseClient struct { + baseURL string + secret string + authToken string + http *http.Client + retryLimit uint32 + logger log.ContextLogger +} + +func newFirebaseClient(baseURL, secret, authToken string, retryLimit uint32, logger log.ContextLogger) *firebaseClient { + transport := &http.Transport{ + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + } + return &firebaseClient{ + baseURL: strings.TrimRight(baseURL, "/"), + secret: secret, + authToken: authToken, + http: &http.Client{Timeout: 30 * time.Second, Transport: transport}, + retryLimit: retryLimit, + logger: logger, + } +} + +func (c *firebaseClient) authParam() string { + if c.authToken != "" { + return "access_token=" + c.authToken + } + return "auth=" + c.secret +} + +func (c *firebaseClient) url(path string) string { + p := strings.TrimPrefix(path, "/") + return fmt.Sprintf("%s/%s.json?%s", c.baseURL, p, c.authParam()) +} + +func (c *firebaseClient) streamURL(path string) string { + p := strings.TrimPrefix(path, "/") + return fmt.Sprintf(`%s/%s.json?%s&orderBy="$key"`, c.baseURL, p, c.authParam()) +} + +func (c *firebaseClient) Get(ctx context.Context, path string, v interface{}) (bool, error) { + resp, err := c.doWithRetry(ctx, http.MethodGet, c.url(path), nil) + if err != nil { + return false, err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return false, fmt.Errorf("firebasetunnel: reading GET response from %s: %w", path, err) + } + if strings.TrimSpace(string(body)) == "null" { + return false, nil + } + if err := json.Unmarshal(body, v); err != nil { + return false, fmt.Errorf("firebasetunnel: decoding GET response from %s: %w", path, err) + } + return true, nil +} + +func (c *firebaseClient) Put(ctx context.Context, path string, v interface{}) error { + body, err := json.Marshal(v) + if err != nil { + return fmt.Errorf("firebasetunnel: encoding PUT body for %s: %w", path, err) + } + resp, err := c.doWithRetry(ctx, http.MethodPut, c.url(path), body) + if err != nil { + return err + } + return resp.Body.Close() +} + +func (c *firebaseClient) Delete(ctx context.Context, path string) error { + resp, err := c.doWithRetry(ctx, http.MethodDelete, c.url(path), nil) + if err != nil { + return err + } + return resp.Body.Close() +} + +// doWithRetry issues an HTTP request with exponential backoff retry on +// transient errors/status codes. Caller must close the returned response body. +func (c *firebaseClient) doWithRetry(ctx context.Context, method, url string, body []byte) (*http.Response, error) { + delay := 200 * time.Millisecond + var lastErr error + for attempt := uint32(0); attempt <= c.retryLimit; attempt++ { + var reqBody io.Reader + if body != nil { + reqBody = bytes.NewReader(body) + } + req, err := http.NewRequestWithContext(ctx, method, url, reqBody) + if err != nil { + return nil, err + } + if body != nil { + req.Header.Set("Content-Type", "application/json") + } + resp, err := c.http.Do(req) + if err != nil { + lastErr = err + } else if resp.StatusCode >= 200 && resp.StatusCode < 300 { + return resp, nil + } else if resp.StatusCode == http.StatusNotFound && method == http.MethodDelete { + return resp, nil + } else { + resp.Body.Close() + if !shouldRetryStatus(resp.StatusCode) { + return nil, fmt.Errorf("firebasetunnel: %s %s failed with status %d", method, url, resp.StatusCode) + } + lastErr = fmt.Errorf("firebasetunnel: %s status %d", method, resp.StatusCode) + } + if c.logger != nil { + c.logger.WarnContext(ctx, "firebasetunnel: ", method, " attempt ", attempt, " failed: ", lastErr) + } + if attempt < c.retryLimit { + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-time.After(delay): + } + if delay < 10*time.Second { + delay *= 2 + } + } + } + return nil, fmt.Errorf("firebasetunnel: %s %s failed after %d attempts: %w", method, url, c.retryLimit+1, lastErr) +} + +func shouldRetryStatus(status int) bool { + switch status { + case http.StatusTooManyRequests, http.StatusInternalServerError, http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout: + return true + } + return false +} + +// listen opens a Server-Sent Events stream for path, reconnecting +// automatically (with jittered backoff) on transient errors. Cancel ctx to +// stop. Closes the returned channel only when ctx is done. +func (c *firebaseClient) listen(ctx context.Context, path string) <-chan sseEvent { + ch := make(chan sseEvent, 256) + go c.listenLoop(ctx, path, ch) + return ch +} + +func (c *firebaseClient) listenLoop(ctx context.Context, path string, ch chan<- sseEvent) { + defer close(ch) + u := c.streamURL(path) + consecutiveFailures := 0 + for { + err := c.runSSELoop(ctx, u, ch) + if ctx.Err() != nil { + return + } + if err == nil { + consecutiveFailures = 0 + continue + } + consecutiveFailures++ + backoff := jitteredBackoff(consecutiveFailures) + if c.logger != nil { + c.logger.ErrorContext(ctx, "firebasetunnel: SSE stream error, reconnecting in ", backoff, ": ", err) + } + select { + case <-ctx.Done(): + return + case <-time.After(backoff): + } + } +} + +func (c *firebaseClient) runSSELoop(ctx context.Context, url string, ch chan<- sseEvent) error { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return err + } + req.Header.Set("Accept", "text/event-stream") + req.Header.Set("Cache-Control", "no-cache") + + resp, err := c.http.Do(req) + if err != nil { + return fmt.Errorf("firebasetunnel: SSE connect: %w", err) + } + defer resp.Body.Close() + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return fmt.Errorf("firebasetunnel: SSE connect failed with status %d", resp.StatusCode) + } + + scanner := bufio.NewScanner(resp.Body) + var currentEvent, currentData string + for scanner.Scan() { + line := scanner.Text() + switch { + case line == "": + if currentEvent != "" && currentData != "" { + select { + case ch <- sseEvent{Event: currentEvent, Data: currentData}: + case <-ctx.Done(): + return nil + } + currentEvent, currentData = "", "" + } + case strings.HasPrefix(line, "event:"): + currentEvent = strings.TrimSpace(strings.TrimPrefix(line, "event:")) + case strings.HasPrefix(line, "data:"): + currentData = strings.TrimSpace(strings.TrimPrefix(line, "data:")) + } + } + if err := scanner.Err(); err != nil { + if ctx.Err() != nil { + return nil + } + return fmt.Errorf("firebasetunnel: SSE read: %w", err) + } + if ctx.Err() != nil { + return nil + } + return fmt.Errorf("firebasetunnel: SSE stream ended unexpectedly") +} diff --git a/protocol/firebasetunnel/fuzz_test.go b/protocol/firebasetunnel/fuzz_test.go new file mode 100644 index 0000000000..d730f8201a --- /dev/null +++ b/protocol/firebasetunnel/fuzz_test.go @@ -0,0 +1,55 @@ +package firebasetunnel + +import ( + "context" + "encoding/json" + "testing" +) + +func FuzzIngestChunk(f *testing.F) { + f.Add([]byte(`{"seq":0,"timestamp":1000,"compressed":false,"data":"aGVsbG8="}`)) + f.Add([]byte(`{"seq":0,"timestamp":1000,"compressed":true,"encrypted":false,"data":"aGVsbG8="}`)) + f.Add([]byte(`{"seq":0,"has_hmac":true,"data":"aGVsbG8="}`)) + f.Add([]byte(`{}`)) + f.Fuzz(func(t *testing.T, data []byte) { + var c chunk + if err := json.Unmarshal(data, &c); err != nil { + return // invalid JSON — not a bug + } + receiver, _ := newChunkReceiver(nil, nil, "fuzz-session", "c2s") + // Must not panic regardless of input. + _, _ = receiver.ingest(context.Background(), c) + }) +} + +func FuzzParseSessionMetadata(f *testing.F) { + f.Add([]byte(`{"session_id":"abc","version":1,"target_host":"1.2.3.4","target_port":80,"created_at":1000,"state":"pending","user":"alice"}`)) + f.Add([]byte(`{"state":"active"}`)) + f.Add([]byte(`{}`)) + f.Fuzz(func(t *testing.T, data []byte) { + var m sessionMetadata + _ = json.Unmarshal(data, &m) + // Verify field access doesn't panic. + _ = m.SessionID + m.User + string(m.State) + _ = m.TargetPort + _ = m.CreatedAt + }) +} + +func FuzzDecodeChunkPayload(f *testing.F) { + // Seed: valid base64, empty, random-looking. + f.Add([]byte("aGVsbG8="), false, false, false) + f.Add([]byte(""), false, false, false) + f.Add([]byte("AAAAAAAAAAAAAAAA"), true, false, false) // looks encrypted + f.Fuzz(func(t *testing.T, rawData []byte, encrypted, compressed, hasHMAC bool) { + c := chunk{ + Seq: 0, + Encrypted: encrypted, + Compressed: compressed, + HasHMAC: hasHMAC, + Data: string(rawData), + } + // Must not panic; errors are expected for malformed inputs. + _, _ = decodeChunkPayload(c, nil, nil, "fuzz-session", "c2s") + }) +} diff --git a/protocol/firebasetunnel/protocol.go b/protocol/firebasetunnel/protocol.go new file mode 100644 index 0000000000..7a71ce5174 --- /dev/null +++ b/protocol/firebasetunnel/protocol.go @@ -0,0 +1,97 @@ +// Package firebasetunnel implements a sing-box endpoint pair that relays +// TCP byte streams through Firebase Realtime Database, adapted from +// github.com/Hiddify2/Firebase-Tunnel (a single-star proof-of-concept with +// no LICENSE; logic below is rewritten for sing-box rather than vendored). +// +// Database layout, keyed by an opaque per-session UUID: +// +// sessions/ +// {session_id}/ +// metadata/ - sessionMetadata (written by client on creation) +// c2s/{seq}/ - chunk, client-to-server queue +// s2c/{seq}/ - chunk, server-to-client queue +// acks/ +// c2s_ack - uint64, highest consecutive seq the server processed +// s2c_ack - uint64, highest consecutive seq the client processed +// +// Sequence numbers start at 0 and increase monotonically per direction per +// session. Out-of-order chunks are buffered and only delivered once all +// predecessors have arrived. Acknowledged chunks are deleted to bound +// database growth. +package firebasetunnel + +import ( + "fmt" + "time" +) + +const protocolVersion = 1 + +type sessionState string + +const ( + sessionStatePending sessionState = "pending" + sessionStateActive sessionState = "active" + sessionStateClosing sessionState = "closing" + sessionStateClosed sessionState = "closed" +) + +// sessionMetadata is written by the client when a session is created. The +// server reads it to know where to connect and which configured user the +// session should be attributed to. +type sessionMetadata struct { + SessionID string `json:"session_id"` + Version int `json:"version"` + TargetHost string `json:"target_host"` + TargetPort uint16 `json:"target_port"` + CreatedAt uint64 `json:"created_at"` + State sessionState `json:"state"` + // User is a self-reported accounting label, verified against the + // session's PSK (if configured) rather than trusted outright. + User string `json:"user,omitempty"` +} + +// chunk is a single batched, optionally compressed, base64-encoded segment +// of relayed bytes stored at sessions/{id}/c2s/{seq} or sessions/{id}/s2c/{seq}. +type chunk struct { + Seq uint64 `json:"seq"` + Timestamp uint64 `json:"timestamp"` + Compressed bool `json:"compressed"` + // Encrypted indicates Data is AES-256-GCM ciphertext (nonce-prefixed, with + // session-ID+direction+seq as AEAD additional data) rather than a plain + // zstd/raw payload. Set only when the session's user has a PSK configured. + Encrypted bool `json:"encrypted,omitempty"` + // HasHMAC indicates that the last hmacTagLen bytes of the decoded Data are + // an HMAC-SHA256 tag over the payload, present on unencrypted chunks when + // the firebase_secret is set. Provides integrity without confidentiality. + HasHMAC bool `json:"has_hmac,omitempty"` + Data string `json:"data"` +} + +func pathMetadata(sessionID string) string { + return "sessions/" + sessionID + "/metadata" +} + +func pathC2S(sessionID string) string { + return "sessions/" + sessionID + "/c2s" +} + +func pathS2C(sessionID string) string { + return "sessions/" + sessionID + "/s2c" +} + +func pathAcks(sessionID string) string { + return "sessions/" + sessionID + "/acks" +} + +func pathSessionsRoot() string { + return "sessions" +} + +func pathChunk(queuePath string, seq uint64) string { + return fmt.Sprintf("%s/%d", queuePath, seq) +} + +func nowMillis() uint64 { + return uint64(time.Now().UnixMilli()) +} diff --git a/protocol/firebasetunnel/session.go b/protocol/firebasetunnel/session.go new file mode 100644 index 0000000000..acf3649898 --- /dev/null +++ b/protocol/firebasetunnel/session.go @@ -0,0 +1,378 @@ +package firebasetunnel + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "sync" + "time" + + "github.com/sagernet/sing-box/log" +) + +const compressMinBytes = 64 + +// maxPendingBytes bounds in-memory buffering in chunkSender beyond the +// channel's slot count, so a burst of large writes can't balloon memory +// before backpressure kicks in. +const maxPendingBytes = 8 * 1024 * 1024 + +// maxChunkBytes is the maximum decoded payload size for a single chunk. +// Rejects oversized chunks from malicious/buggy clients before allocating. +const maxChunkBytes = 1 * 1024 * 1024 // 1 MiB + +// chunkSender accumulates outgoing bytes, batches/compresses/optionally +// encrypts them, and writes them to Firebase as sequential chunk nodes. +type chunkSender struct { + rawCh chan []byte + pending chanCounter +} + +type chanCounter struct { + mu sync.Mutex + bytes int +} + +func (c *chanCounter) add(n int) bool { + c.mu.Lock() + defer c.mu.Unlock() + if c.bytes+n > maxPendingBytes { + return false + } + c.bytes += n + return true +} + +func (c *chanCounter) sub(n int) { + c.mu.Lock() + defer c.mu.Unlock() + c.bytes -= n +} + +// newChunkSender creates a sender that flushes to queuePath on fb. +// sessionID and direction ("c2s"/"s2c") are bound into AEAD additional data +// when encrypting, so chunks cannot be replayed across sessions or directions. +// hmacKey (non-nil) enables HMAC integrity tags on unencrypted chunks. +func newChunkSender(ctx context.Context, queuePath, sessionID, direction string, fb *firebaseClient, batchInterval time.Duration, batchMaxBytes int, key *[32]byte, hmacKey []byte, logger log.ContextLogger) *chunkSender { + rawCh := make(chan []byte, 1024) + s := &chunkSender{rawCh: rawCh} + go s.flusherTask(ctx, queuePath, sessionID, direction, fb, batchInterval, batchMaxBytes, key, hmacKey, logger) + return s +} + +// feed enqueues data for the next batch. Returns an error if the pending +// byte budget is exceeded (caller should treat this as backpressure, not +// retry indefinitely) or if ctx is done. +func (s *chunkSender) feed(ctx context.Context, data []byte) error { + if !s.pending.add(len(data)) { + return fmt.Errorf("firebasetunnel: sender backpressure limit exceeded") + } + cp := make([]byte, len(data)) + copy(cp, data) + select { + case s.rawCh <- cp: + return nil + case <-ctx.Done(): + s.pending.sub(len(data)) + return ctx.Err() + } +} + +func (s *chunkSender) flusherTask(ctx context.Context, queuePath, sessionID, direction string, fb *firebaseClient, batchInterval time.Duration, batchMaxBytes int, key *[32]byte, hmacKey []byte, logger log.ContextLogger) { + buffer := make([]byte, 0, batchMaxBytes) + var seq uint64 + ticker := time.NewTicker(batchInterval) + defer ticker.Stop() + + flush := func(flushCtx context.Context) { + if len(buffer) == 0 { + return + } + n := len(buffer) + if err := flushBuffer(flushCtx, queuePath, sessionID, direction, fb, &buffer, &seq, key, hmacKey); err != nil && logger != nil { + logger.WarnContext(flushCtx, "firebasetunnel: flush error: ", err) + } + s.pending.sub(n) + } + + for { + select { + case <-ticker.C: + flush(ctx) + case data, ok := <-s.rawCh: + if !ok { + flush(ctx) + return + } + buffer = append(buffer, data...) + if len(buffer) >= batchMaxBytes { + flush(ctx) + } + case <-ctx.Done(): + flushCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + flush(flushCtx) + cancel() + return + } + } +} + +func flushBuffer(ctx context.Context, queuePath, sessionID, direction string, fb *firebaseClient, buffer *[]byte, seq *uint64, key *[32]byte, hmacKey []byte) error { + raw := make([]byte, len(*buffer)) + copy(raw, *buffer) + *buffer = (*buffer)[:0] + + var payload []byte + compressed := false + if len(raw) >= compressMinBytes { + payload = compressBytes(raw) + compressed = true + } else { + payload = raw + } + + encrypted := false + hasHMAC := false + if key != nil { + aad := chunkAAD(sessionID, direction, *seq) + enc, err := encryptPayloadAAD(*key, payload, aad) + if err != nil { + return fmt.Errorf("firebasetunnel: encrypting chunk seq=%d: %w", *seq, err) + } + payload = enc + encrypted = true + } else if len(hmacKey) > 0 { + payload = appendHMACTag(hmacKey, payload) + hasHMAC = true + } + + c := chunk{ + Seq: *seq, + Timestamp: nowMillis(), + Compressed: compressed, + Encrypted: encrypted, + HasHMAC: hasHMAC, + Data: base64.StdEncoding.EncodeToString(payload), + } + if err := fb.Put(ctx, pathChunk(queuePath, *seq), &c); err != nil { + return fmt.Errorf("firebasetunnel: writing chunk seq=%d: %w", *seq, err) + } + *seq++ + return nil +} + +// chunkReceiver reassembles an in-order byte stream from chunks that may +// arrive out of order, buffering until contiguous, then delivering on the +// channel returned by newChunkReceiver. +type chunkReceiver struct { + mu sync.Mutex + pending map[uint64][]byte + nextSeq uint64 + outCh chan []byte + ackPtr *uint64 + key *[32]byte + hmacKey []byte + sessionID string + direction string +} + +// newChunkReceiver creates a receiver for chunks arriving on sessionID/direction. +// hmacKey (non-nil) enables HMAC verification on unencrypted chunks. +func newChunkReceiver(key *[32]byte, hmacKey []byte, sessionID, direction string) (*chunkReceiver, <-chan []byte) { + outCh := make(chan []byte, 1024) + r := &chunkReceiver{ + pending: make(map[uint64][]byte), + outCh: outCh, + key: key, + hmacKey: hmacKey, + sessionID: sessionID, + direction: direction, + } + return r, outCh +} + +// ingest processes one chunk, returning the new ack pointer if it advanced. +// Duplicate (already-delivered) chunks are silently ignored. Decrypt/HMAC +// failures are returned as errors — callers should treat them as an +// auth/abuse signal, not a transient fault. +func (r *chunkReceiver) ingest(ctx context.Context, c chunk) (*uint64, error) { + r.mu.Lock() + defer r.mu.Unlock() + + if c.Seq < r.nextSeq { + return nil, nil + } + + payload, err := decodeChunkPayload(c, r.key, r.hmacKey, r.sessionID, r.direction) + if err != nil { + return nil, err + } + r.pending[c.Seq] = payload + + oldAck := r.ackPtr + for { + data, ok := r.pending[r.nextSeq] + if !ok { + break + } + delete(r.pending, r.nextSeq) + select { + case r.outCh <- data: + case <-ctx.Done(): + return nil, ctx.Err() + } + ack := r.nextSeq + r.ackPtr = &ack + r.nextSeq++ + } + + if !ackEqual(r.ackPtr, oldAck) { + return r.ackPtr, nil + } + return nil, nil +} + +func ackEqual(a, b *uint64) bool { + if a == nil && b == nil { + return true + } + if a == nil || b == nil { + return false + } + return *a == *b +} + +func decodeChunkPayload(c chunk, key *[32]byte, hmacKey []byte, sessionID, direction string) ([]byte, error) { + // Reject oversized chunks before any allocation. + if len(c.Data) > base64.StdEncoding.EncodedLen(maxChunkBytes) { + return nil, fmt.Errorf("firebasetunnel: chunk seq=%d exceeds max size cap", c.Seq) + } + + decoded, err := base64.StdEncoding.DecodeString(c.Data) + if err != nil { + return nil, fmt.Errorf("firebasetunnel: base64-decoding chunk seq=%d: %w", c.Seq, err) + } + if c.Encrypted { + if key == nil { + return nil, fmt.Errorf("firebasetunnel: chunk seq=%d is encrypted but no key configured", c.Seq) + } + aad := chunkAAD(sessionID, direction, c.Seq) + decoded, err = decryptPayloadAAD(*key, decoded, aad) + if err != nil { + return nil, fmt.Errorf("firebasetunnel: decrypting chunk seq=%d: %w", c.Seq, err) + } + } else if c.HasHMAC { + if len(hmacKey) == 0 { + return nil, fmt.Errorf("firebasetunnel: chunk seq=%d has HMAC tag but no HMAC key configured", c.Seq) + } + decoded, err = verifyAndStripHMACTag(hmacKey, decoded) + if err != nil { + return nil, fmt.Errorf("firebasetunnel: chunk seq=%d: %w", c.Seq, err) + } + } + if c.Compressed { + decoded, err = decompressBytes(decoded) + if err != nil { + return nil, fmt.Errorf("firebasetunnel: decompressing chunk seq=%d: %w", c.Seq, err) + } + } + return decoded, nil +} + +// fetchNewChunks reads all chunks from queuePath with seq > after, sorted +// by seq. Firebase collapses integer-keyed objects into JSON arrays, so +// both shapes are handled. +func fetchNewChunks(ctx context.Context, fb *firebaseClient, queuePath string, after *uint64) ([]chunk, error) { + var raw interface{} + found, err := fb.Get(ctx, queuePath, &raw) + if err != nil { + return nil, err + } + if !found { + return nil, nil + } + + var chunks []chunk + switch v := raw.(type) { + case map[string]interface{}: + for _, val := range v { + ck, err := interfaceToChunk(val) + if err != nil { + return nil, err + } + chunks = append(chunks, ck) + } + case []interface{}: + for _, val := range v { + if val == nil { + continue + } + ck, err := interfaceToChunk(val) + if err != nil { + return nil, err + } + chunks = append(chunks, ck) + } + default: + return nil, nil + } + + if after != nil { + filtered := chunks[:0] + for _, ck := range chunks { + if ck.Seq > *after { + filtered = append(filtered, ck) + } + } + chunks = filtered + } + + sortChunksBySeq(chunks) + return chunks, nil +} + +func interfaceToChunk(v interface{}) (chunk, error) { + data, err := json.Marshal(v) + if err != nil { + return chunk{}, err + } + var ck chunk + if err := json.Unmarshal(data, &ck); err != nil { + return chunk{}, err + } + return ck, nil +} + +func sortChunksBySeq(chunks []chunk) { + n := len(chunks) + for i := 1; i < n; i++ { + key := chunks[i] + j := i - 1 + for j >= 0 && chunks[j].Seq > key.Seq { + chunks[j+1] = chunks[j] + j-- + } + chunks[j+1] = key + } +} + +// updateAckAndCleanup persists the new ack pointer and deletes all chunks +// with seq <= ack from queuePath (best-effort, parallel deletes). +func updateAckAndCleanup(ctx context.Context, fb *firebaseClient, ackFieldPath, queuePath string, ack uint64, logger log.ContextLogger) error { + if err := fb.Put(ctx, ackFieldPath, ack); err != nil { + return fmt.Errorf("firebasetunnel: updating ack: %w", err) + } + var wg sync.WaitGroup + for seq := uint64(0); seq <= ack; seq++ { + wg.Add(1) + go func(seq uint64) { + defer wg.Done() + if err := fb.Delete(ctx, pathChunk(queuePath, seq)); err != nil && logger != nil { + logger.WarnContext(ctx, "firebasetunnel: cleanup chunk seq=", seq, " failed: ", err) + } + }(seq) + } + wg.Wait() + return nil +} diff --git a/protocol/firebasetunnel/session_test.go b/protocol/firebasetunnel/session_test.go new file mode 100644 index 0000000000..b5f31c8cbb --- /dev/null +++ b/protocol/firebasetunnel/session_test.go @@ -0,0 +1,382 @@ +package firebasetunnel + +import ( + "context" + "encoding/base64" + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "sync" + "testing" + "time" +) + +// fakeFirebaseServer is a minimal in-memory stand-in for the Firebase RTDB +// REST API, sufficient for exercising chunkSender/chunkReceiver against a +// real *firebaseClient without network access. +type fakeFirebaseServer struct { + mu sync.Mutex + data map[string]json.RawMessage + listeners []chan struct{} +} + +func newFakeFirebaseServer() *httptest.Server { + f := &fakeFirebaseServer{data: make(map[string]json.RawMessage)} + return httptest.NewServer(http.HandlerFunc(f.handle)) +} + +func (f *fakeFirebaseServer) notifyListeners() { + for _, ch := range f.listeners { + select { + case ch <- struct{}{}: + default: + } + } +} + +func (f *fakeFirebaseServer) handle(w http.ResponseWriter, r *http.Request) { + path := strings.TrimSuffix(strings.TrimPrefix(r.URL.Path, "/"), ".json") + + // SSE streaming endpoint. + if r.Method == http.MethodGet && r.Header.Get("Accept") == "text/event-stream" { + notify := make(chan struct{}, 4) + f.mu.Lock() + f.listeners = append(f.listeners, notify) + // Send initial state. + snapshot, _ := json.Marshal(f.buildSnapshot()) + f.mu.Unlock() + + w.Header().Set("Content-Type", "text/event-stream") + w.Header().Set("Cache-Control", "no-cache") + flusher, ok := w.(http.Flusher) + if !ok { + http.Error(w, "streaming not supported", http.StatusInternalServerError) + return + } + // Firebase SSE format. + _, _ = w.Write([]byte("event: put\ndata: {\"path\":\"/\",\"data\":" + string(snapshot) + "}\n\n")) + flusher.Flush() + + for { + select { + case <-notify: + f.mu.Lock() + snap, _ := json.Marshal(f.buildSnapshot()) + f.mu.Unlock() + _, _ = w.Write([]byte("event: put\ndata: {\"path\":\"/\",\"data\":" + string(snap) + "}\n\n")) + flusher.Flush() + case <-r.Context().Done(): + f.mu.Lock() + listeners := f.listeners[:0] + for _, ch := range f.listeners { + if ch != notify { + listeners = append(listeners, ch) + } + } + f.listeners = listeners + f.mu.Unlock() + return + } + } + } + + f.mu.Lock() + defer f.mu.Unlock() + + switch r.Method { + case http.MethodGet: + if v, ok := f.data[path]; ok { + w.Write(v) + return + } + w.Write([]byte("null")) + case http.MethodPut: + body := make([]byte, r.ContentLength) + r.Body.Read(body) + f.data[path] = json.RawMessage(body) + f.notifyListeners() + w.Write([]byte("{}")) + case http.MethodDelete: + delete(f.data, path) + w.Write([]byte("null")) + default: + w.WriteHeader(http.StatusMethodNotAllowed) + } +} + +// buildSnapshot returns the top-level "sessions" map for SSE payloads. +// Must be called with f.mu held. +func (f *fakeFirebaseServer) buildSnapshot() map[string]json.RawMessage { + out := make(map[string]json.RawMessage) + for k, v := range f.data { + out[k] = v + } + return out +} + +func TestChunkSenderReceiverRoundTrip(t *testing.T) { + srv := newFakeFirebaseServer() + defer srv.Close() + + fb := newFirebaseClient(srv.URL, "test-secret", "", 0, nil) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + sessionID := "test-session" + queuePath := "sessions/" + sessionID + "/c2s" + sender := newChunkSender(ctx, queuePath, sessionID, "c2s", fb, 20*time.Millisecond, 1024, nil, nil, nil) + + payload := []byte("hello firebase tunnel") + if err := sender.feed(ctx, payload); err != nil { + t.Fatalf("feed: %v", err) + } + + // Wait for the flusher to write the chunk. + time.Sleep(100 * time.Millisecond) + + chunks, err := fetchNewChunks(ctx, fb, queuePath, nil) + if err != nil { + t.Fatalf("fetchNewChunks: %v", err) + } + if len(chunks) != 1 { + t.Fatalf("expected 1 chunk, got %d", len(chunks)) + } + + receiver, byteRx := newChunkReceiver(nil, nil, sessionID, "c2s") + if _, err := receiver.ingest(ctx, chunks[0]); err != nil { + t.Fatalf("ingest: %v", err) + } + + select { + case data := <-byteRx: + if string(data) != string(payload) { + t.Fatalf("got %q want %q", data, payload) + } + case <-time.After(time.Second): + t.Fatal("timed out waiting for reassembled bytes") + } +} + +func TestChunkSenderEncrypted(t *testing.T) { + srv := newFakeFirebaseServer() + defer srv.Close() + + fb := newFirebaseClient(srv.URL, "test-secret", "", 0, nil) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + sessionID := "test-session-2" + key := deriveKey("shared-psk") + queuePath := "sessions/" + sessionID + "/c2s" + sender := newChunkSender(ctx, queuePath, sessionID, "c2s", fb, 20*time.Millisecond, 1024, &key, nil, nil) + + payload := []byte("encrypted payload bytes") + if err := sender.feed(ctx, payload); err != nil { + t.Fatalf("feed: %v", err) + } + time.Sleep(100 * time.Millisecond) + + chunks, err := fetchNewChunks(ctx, fb, queuePath, nil) + if err != nil { + t.Fatalf("fetchNewChunks: %v", err) + } + if len(chunks) != 1 || !chunks[0].Encrypted { + t.Fatalf("expected 1 encrypted chunk, got %+v", chunks) + } + + // Wrong key must fail to decrypt. + wrongKey := deriveKey("wrong-psk") + receiverWrong, _ := newChunkReceiver(&wrongKey, nil, sessionID, "c2s") + if _, err := receiverWrong.ingest(ctx, chunks[0]); err == nil { + t.Fatal("expected ingest failure with wrong key") + } + + // Correct key must succeed. + receiver, byteRx := newChunkReceiver(&key, nil, sessionID, "c2s") + if _, err := receiver.ingest(ctx, chunks[0]); err != nil { + t.Fatalf("ingest with correct key: %v", err) + } + select { + case data := <-byteRx: + if string(data) != string(payload) { + t.Fatalf("got %q want %q", data, payload) + } + case <-time.After(time.Second): + t.Fatal("timed out waiting for reassembled bytes") + } +} + +func TestChunkSenderHMAC(t *testing.T) { + srv := newFakeFirebaseServer() + defer srv.Close() + + fb := newFirebaseClient(srv.URL, "test-secret", "", 0, nil) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + sessionID := "test-hmac" + hmacKey := deriveHMACKey("test-secret") + queuePath := "sessions/" + sessionID + "/c2s" + sender := newChunkSender(ctx, queuePath, sessionID, "c2s", fb, 20*time.Millisecond, 1024, nil, hmacKey, nil) + + payload := []byte("hmac protected payload") + if err := sender.feed(ctx, payload); err != nil { + t.Fatalf("feed: %v", err) + } + time.Sleep(100 * time.Millisecond) + + chunks, err := fetchNewChunks(ctx, fb, queuePath, nil) + if err != nil { + t.Fatalf("fetchNewChunks: %v", err) + } + if len(chunks) != 1 || !chunks[0].HasHMAC { + t.Fatalf("expected 1 HMAC chunk, got %+v", chunks) + } + + // Wrong HMAC key must fail. + wrongHMACKey := deriveHMACKey("wrong-secret") + receiverWrong, _ := newChunkReceiver(nil, wrongHMACKey, sessionID, "c2s") + if _, err := receiverWrong.ingest(ctx, chunks[0]); err == nil { + t.Fatal("expected ingest failure with wrong HMAC key") + } + + // Correct HMAC key must succeed. + receiver, byteRx := newChunkReceiver(nil, hmacKey, sessionID, "c2s") + if _, err := receiver.ingest(ctx, chunks[0]); err != nil { + t.Fatalf("ingest with correct hmac key: %v", err) + } + select { + case data := <-byteRx: + if string(data) != string(payload) { + t.Fatalf("got %q want %q", data, payload) + } + case <-time.After(time.Second): + t.Fatal("timed out waiting for reassembled bytes") + } +} + +func TestChunkAADBinding(t *testing.T) { + // A chunk encrypted for session A / direction c2s / seq 0 must not decrypt + // if the receiver uses a different sessionID, direction, or seq. + key := deriveKey("binding-psk") + sessionID := "session-a" + payload := []byte("aad binding test") + + aad := chunkAAD(sessionID, "c2s", 0) + ct, err := encryptPayloadAAD(key, payload, aad) + if err != nil { + t.Fatalf("encrypt: %v", err) + } + + // Wrong session ID. + wrongAAD := chunkAAD("session-b", "c2s", 0) + if _, err := decryptPayloadAAD(key, ct, wrongAAD); err == nil { + t.Fatal("expected failure with wrong sessionID in AAD") + } + + // Wrong direction. + wrongDir := chunkAAD(sessionID, "s2c", 0) + if _, err := decryptPayloadAAD(key, ct, wrongDir); err == nil { + t.Fatal("expected failure with wrong direction in AAD") + } + + // Wrong seq. + wrongSeq := chunkAAD(sessionID, "c2s", 1) + if _, err := decryptPayloadAAD(key, ct, wrongSeq); err == nil { + t.Fatal("expected failure with wrong seq in AAD") + } + + // Correct AAD must succeed. + pt, err := decryptPayloadAAD(key, ct, aad) + if err != nil { + t.Fatalf("correct AAD failed: %v", err) + } + if string(pt) != string(payload) { + t.Fatalf("got %q want %q", pt, payload) + } +} + +func TestChunkReceiverOutOfOrder(t *testing.T) { + ctx := context.Background() + receiver, byteRx := newChunkReceiver(nil, nil, "sid", "c2s") + + c1 := chunk{Seq: 1, Data: encodeRaw([]byte("b"))} + c0 := chunk{Seq: 0, Data: encodeRaw([]byte("a"))} + + // Deliver seq=1 first; nothing should drain yet. + if _, err := receiver.ingest(ctx, c1); err != nil { + t.Fatalf("ingest c1: %v", err) + } + select { + case data := <-byteRx: + t.Fatalf("unexpected early delivery: %q", data) + default: + } + + // Now deliver seq=0; both should drain in order. + if _, err := receiver.ingest(ctx, c0); err != nil { + t.Fatalf("ingest c0: %v", err) + } + first := <-byteRx + second := <-byteRx + if string(first) != "a" || string(second) != "b" { + t.Fatalf("got %q, %q; want a, b", first, second) + } +} + +func TestChunkReceiverDuplicateIgnored(t *testing.T) { + ctx := context.Background() + receiver, byteRx := newChunkReceiver(nil, nil, "sid", "c2s") + + c0 := chunk{Seq: 0, Data: encodeRaw([]byte("a"))} + if _, err := receiver.ingest(ctx, c0); err != nil { + t.Fatalf("ingest: %v", err) + } + <-byteRx + + // Re-ingesting the same seq must not deliver again. + if _, err := receiver.ingest(ctx, c0); err != nil { + t.Fatalf("ingest duplicate: %v", err) + } + select { + case data := <-byteRx: + t.Fatalf("unexpected duplicate delivery: %q", data) + case <-time.After(50 * time.Millisecond): + } +} + +func TestSenderBackpressure(t *testing.T) { + srv := newFakeFirebaseServer() + defer srv.Close() + fb := newFirebaseClient(srv.URL, "secret", "", 0, nil) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Long batch interval so nothing flushes during the test. + sender := newChunkSender(ctx, "sessions/x/c2s", "x", "c2s", fb, time.Hour, 1<<30, nil, nil, nil) + + big := make([]byte, maxPendingBytes) + if err := sender.feed(ctx, big); err != nil { + t.Fatalf("first feed should succeed: %v", err) + } + if err := sender.feed(ctx, []byte("more")); err == nil { + t.Fatal("expected backpressure error once pending budget exceeded") + } +} + +func TestChunkSizeCapRejected(t *testing.T) { + ctx := context.Background() + receiver, _ := newChunkReceiver(nil, nil, "sid", "c2s") + + // Craft a chunk whose Data field exceeds the encoded size cap. + oversized := make([]byte, maxChunkBytes+1) + c := chunk{Seq: 0, Data: base64.StdEncoding.EncodeToString(oversized)} + if _, err := receiver.ingest(ctx, c); err == nil { + t.Fatal("expected error for oversized chunk") + } +} + +func encodeRaw(data []byte) string { + return base64.StdEncoding.EncodeToString(data) +} diff --git a/protocol/firebasetunnel/util.go b/protocol/firebasetunnel/util.go new file mode 100644 index 0000000000..9642655152 --- /dev/null +++ b/protocol/firebasetunnel/util.go @@ -0,0 +1,63 @@ +package firebasetunnel + +import ( + "math/rand" + "sync" + "time" +) + +// jitteredBackoff returns an exponential backoff duration (capped at 30s) +// with +/-20% jitter, to avoid thundering-herd reconnects when many +// sessions/instances share one Firebase project. +func jitteredBackoff(attempt int) time.Duration { + base := 2 * time.Second + max := 30 * time.Second + d := base << uint(min(attempt-1, 4)) + if d > max || d <= 0 { + d = max + } + jitter := time.Duration(rand.Int63n(int64(d) / 5)) // up to 20% + return d - jitter/2 + time.Duration(rand.Int63n(int64(jitter)+1)) +} + +// tokenBucket is a simple per-second refill rate limiter used to bound +// session-creation rate per user. +type tokenBucket struct { + mu sync.Mutex + tokens float64 + maxTokens float64 + refillRate float64 // tokens per second + last time.Time +} + +func newTokenBucket(ratePerSecond, burst int) *tokenBucket { + if ratePerSecond <= 0 { + ratePerSecond = 1 + } + if burst <= 0 { + burst = ratePerSecond + } + return &tokenBucket{ + tokens: float64(burst), + maxTokens: float64(burst), + refillRate: float64(ratePerSecond), + last: time.Now(), + } +} + +func (b *tokenBucket) take() bool { + b.mu.Lock() + defer b.mu.Unlock() + now := time.Now() + elapsed := now.Sub(b.last).Seconds() + b.last = now + b.tokens += elapsed * b.refillRate + if b.tokens > b.maxTokens { + b.tokens = b.maxTokens + } + if b.tokens < 1 { + return false + } + b.tokens-- + return true +} diff --git a/protocol/group/balancer/balancer.go b/protocol/group/balancer/balancer.go new file mode 100644 index 0000000000..e1a83553c9 --- /dev/null +++ b/protocol/group/balancer/balancer.go @@ -0,0 +1,239 @@ +package balancer + +import ( + "context" + "net" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/common/interrupt" + "github.com/sagernet/sing-box/common/monitoring" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + tun "github.com/sagernet/sing-tun" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" +) + +func RegisterLoadBalance(registry *outbound.Registry) { + outbound.Register[option.BalancerOutboundOptions](registry, C.TypeBalancer, NewLoadBalance) +} + +var _ adapter.OutboundGroup = (*Balancer)(nil) + +const ( + StrategyRoundRobin = "round-robin" + StrategyConsistentHashing = "consistent-hashing" + StrategyStickySessions = "sticky-sessions" + StrategyLowestDelay = "lowest-delay" +) + +type Balancer struct { + outbound.Adapter + ctx context.Context + router adapter.Router + outbound adapter.OutboundManager + connection adapter.ConnectionManager + logger log.ContextLogger + tags []string + link string + interval time.Duration + tolerance uint16 + idleTimeout time.Duration + strategyFn Strategy + options option.BalancerOutboundOptions + interruptExternalConnections bool + + monitor *monitoring.OutboundMonitoring + + availbleOutbounds []adapter.Outbound + close chan struct{} + interruptGroup *interrupt.Group +} + +func NewLoadBalance(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.BalancerOutboundOptions) (adapter.Outbound, error) { + outbound := &Balancer{ + Adapter: outbound.NewAdapter(C.TypeBalancer, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.Outbounds), + ctx: ctx, + router: router, + outbound: service.FromContext[adapter.OutboundManager](ctx), + connection: service.FromContext[adapter.ConnectionManager](ctx), + logger: logger, + tags: options.Outbounds, + tolerance: options.Tolerance, + interruptExternalConnections: options.InterruptExistConnections, + options: options, + interruptGroup: interrupt.NewGroup(), + } + if len(outbound.tags) == 0 { + return nil, E.New("missing tags") + } + + return outbound, nil +} + +func (s *Balancer) Strategy() string { + return s.options.Strategy +} + +func (s *Balancer) Start() error { + s.monitor = monitoring.Get(s.ctx) + s.logger.Info("starting load balance, monitoring enabled: ", s.monitor != nil) + outbounds := make([]adapter.Outbound, 0, len(s.tags)) + for i, tag := range s.tags { + detour, loaded := s.outbound.Outbound(tag) + if !loaded { + return E.New("outbound ", i, " not found: ", tag) + } + outbounds = append(outbounds, detour) + } + switch s.options.Strategy { + case StrategyRoundRobin: + s.strategyFn = NewRoundRobin(outbounds, s.options) + case StrategyConsistentHashing: + s.strategyFn = NewConsistentHashing(outbounds, s.options) + case StrategyStickySessions: + s.strategyFn = NewStickySession(outbounds, s.options) + case StrategyLowestDelay: + s.strategyFn = NewLowestDelay(outbounds, s.options) + default: + return E.New("unknown load balance strategy: ", s.options.Strategy) + } + + return nil +} + +func (s *Balancer) PostStart() error { + go s.worker() + + return nil +} + +func (s *Balancer) worker() { + observer, err := s.monitor.SubscribeGroup(s.Tag()) + if err != nil { + s.logger.Error("failed to observe monitoring group: ", err) + return + } + defer s.monitor.UnsubscribeGroup(s.Tag(), observer) + + for { + select { + case <-s.close: + return + + case <-s.ctx.Done(): + return + case _, ok := <-observer: + if !ok { + return + } + outbounds := s.monitor.OutboundsHistory(s.Tag()) + if s.strategyFn.UpdateOutboundsInfo(outbounds) { + s.interruptGroup.Interrupt(s.interruptExternalConnections) + } + + } + } +} +func (s *Balancer) Close() error { + if s.close != nil { + close(s.close) + } + return nil +} + +func (s *Balancer) Now() string { + if s.strategyFn == nil { + return "" + } + return s.strategyFn.Now() +} + +func (s *Balancer) All() []string { + return s.tags +} + +func (s *Balancer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + metadata := adapter.ContextFrom(ctx) + if metadata == nil { + metadata = &adapter.InboundContext{} + } + outbound := s.strategyFn.Select(*metadata, network, true) + if outbound == nil { + return nil, E.New("missing supported outbound") + } + if metadata != nil { + metadata.SetRealOutbound(outbound.Tag()) + } + + conn, err := outbound.DialContext(ctx, network, destination) + if err == nil { + return s.interruptGroup.NewConn(conn, interrupt.IsExternalConnectionFromContext(ctx)), nil + } + s.logger.ErrorContext(ctx, err) + s.monitor.InvalidateTest(outbound.Tag()) + + return nil, err +} + +func (s *Balancer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + metadata := adapter.ContextFrom(ctx) + if metadata == nil { + metadata = &adapter.InboundContext{} + } + outbound := s.strategyFn.Select(*metadata, N.NetworkUDP, true) + if outbound == nil { + return nil, E.New("missing supported outbound") + } + if metadata != nil { + metadata.SetRealOutbound(outbound.Tag()) + } + + conn, err := outbound.ListenPacket(ctx, destination) + if err == nil { + return s.interruptGroup.NewPacketConn(conn, interrupt.IsExternalConnectionFromContext(ctx)), nil + } + s.logger.ErrorContext(ctx, err) + s.monitor.InvalidateTest(outbound.Tag()) + return nil, err +} + +func (s *Balancer) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + ctx = interrupt.ContextWithIsExternalConnection(ctx) + selected := s.strategyFn.Select(metadata, metadata.Network, true) + conn = s.interruptGroup.NewConn(conn, interrupt.IsExternalConnectionFromContext(ctx)) + if outboundHandler, isHandler := selected.(adapter.ConnectionHandler); isHandler { + outboundHandler.NewConnection(ctx, conn, metadata, onClose) + } else { + s.connection.NewConnection(ctx, selected, conn, metadata, onClose) + } +} + +func (s *Balancer) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + ctx = interrupt.ContextWithIsExternalConnection(ctx) + selected := s.strategyFn.Select(metadata, metadata.Network, true) + if selected == nil { + return + } + metadata.SetRealOutbound(selected.Tag()) + conn = s.interruptGroup.NewSingPacketConn(conn, interrupt.IsExternalConnectionFromContext(ctx)) + if outboundHandler, isHandler := selected.(adapter.PacketConnectionHandler); isHandler { + outboundHandler.NewPacketConnection(ctx, conn, metadata, onClose) + } else { + s.connection.NewPacketConnection(ctx, selected, conn, metadata, onClose) + } +} + +func (s *Balancer) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + selected := s.strategyFn.Select(metadata, metadata.Network, true) + if selected == nil { + return nil, E.New(metadata.Network, " is not supported by outbound: ") + } + metadata.SetRealOutbound(selected.Tag()) + return selected.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout) +} diff --git a/protocol/group/balancer/common.go b/protocol/group/balancer/common.go new file mode 100644 index 0000000000..91439df205 --- /dev/null +++ b/protocol/group/balancer/common.go @@ -0,0 +1,183 @@ +package balancer + +import ( + "fmt" + "math" + "net" + "net/netip" + "sort" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/monitoring" + "github.com/sagernet/sing/common" + N "github.com/sagernet/sing/common/network" + "golang.org/x/net/publicsuffix" +) + +func getKey(metadata *adapter.InboundContext) string { + if metadata == nil { + return "" + } + + var metadataHost string + if metadata.Destination.IsFqdn() { + metadataHost = metadata.Destination.Fqdn + } else { + metadataHost = metadata.Domain + } + + if metadataHost != "" { + // ip host + if ip := net.ParseIP(metadataHost); ip != nil { + return metadataHost + } + + if etld, err := publicsuffix.EffectiveTLDPlusOne(metadataHost); err == nil { + return etld + } + } + + var destinationAddr netip.Addr + if len(metadata.DestinationAddresses) > 0 { + destinationAddr = metadata.DestinationAddresses[0] + } else { + destinationAddr = metadata.Destination.Addr + } + + if !destinationAddr.IsValid() { + return "" + } + + return destinationAddr.String() +} + +func getKeyWithSrcAndDst(metadata *adapter.InboundContext) string { + dst := getKey(metadata) + src := "" + if metadata != nil { + src = metadata.Source.Addr.String() + } + + return fmt.Sprintf("%s%s", src, dst) +} + +func jumpHash(key uint64, buckets int32) int32 { + var b, j int64 + + for j < int64(buckets) { + b = j + key = key*2862933555777941757 + 1 + j = int64(float64(b+1) * (float64(int64(1)<<31) / float64((key>>33)+1))) + } + + return int32(b) +} + +func getModifiedDelay(his *adapter.URLTestHistory) uint16 { + if his == nil { + return monitoring.TimeoutDelay + } + delay := his.Delay + if delay == 0 { + delay = monitoring.TimeoutDelay + } + if his.IsFromCache && delay < 20000 { + // if the history is from cache, we can not trust the delay + delay += 20000 + } + return delay +} +func getTagDelay(tag string, history map[string]*adapter.URLTestHistory) uint16 { + if his, ok := history[tag]; ok && his != nil { + return getModifiedDelay(his) + } + return monitoring.TimeoutDelay +} + +func filterOutbounds(outbounds []adapter.Outbound, network string) []adapter.Outbound { + res := []adapter.Outbound{} + for _, out := range outbounds { + if !common.Contains(out.Network(), network) { + continue + } + res = append(res, out) + } + if len(res) == 0 { + return outbounds + } + return res +} +func convertOutbounds(outbounds []adapter.Outbound) map[string][]adapter.Outbound { + return map[string][]adapter.Outbound{ + N.NetworkTCP: filterOutbounds(outbounds, N.NetworkTCP), + N.NetworkUDP: filterOutbounds(outbounds, N.NetworkUDP), + } +} + +func sortOutboundsByDelay(outbounds map[string][]adapter.Outbound, history map[string]*adapter.URLTestHistory) map[string][]adapter.Outbound { + res := map[string][]adapter.Outbound{} + for net, outs := range outbounds { + res[net] = append([]adapter.Outbound{}, outs...) + sort.SliceStable(res[net], func(i, j int) bool { + var delayi uint16 = getTagDelay(res[net][i].Tag(), history) + var delayj uint16 = getTagDelay(res[net][j].Tag(), history) + return delayi < delayj + }) + } + + return res +} +func getAcceptableIndex(sortedOutbounds map[string][]adapter.Outbound, history map[string]*adapter.URLTestHistory, delayAcceptableRatio float64) map[string]int { + res := map[string]int{} + for net, outs := range sortedOutbounds { + minDelay := getTagDelay(outs[0].Tag(), history) + + maxAcceptableDelay := float64(math.Max(100, float64(minDelay))) * delayAcceptableRatio + + maxAvailableIndex := 0 + for i, outbound := range outs { + delay := getTagDelay(outbound.Tag(), history) + if delay <= uint16(maxAcceptableDelay) { + maxAvailableIndex = i + } + } + res[net] = maxAvailableIndex + } + return res + +} + +func getMinDelay(outbounds map[string][]adapter.Outbound, history map[string]*adapter.URLTestHistory) (map[string]adapter.Outbound, map[string]uint16) { + delays := map[string]uint16{} + bestOuts := map[string]adapter.Outbound{} + for net, outs := range outbounds { + minDelay := monitoring.TimeoutDelay + var minOut adapter.Outbound + for _, out := range outs { + + d := getTagDelay(out.Tag(), history) + if d <= minDelay { + minDelay = d + minOut = out + } + } + delays[net] = minDelay + bestOuts[net] = minOut + } + return bestOuts, delays + +} + +func getDelayMap(history map[string]*adapter.URLTestHistory) map[string]uint16 { + delayMap := make(map[string]uint16) + for tag, his := range history { + if his != nil { + delayMap[tag] = his.Delay + } else { + delayMap[tag] = monitoring.TimeoutDelay + } + } + + return delayMap + +} diff --git a/protocol/group/balancer/consistent_hashing.go b/protocol/group/balancer/consistent_hashing.go new file mode 100644 index 0000000000..11df8f1c07 --- /dev/null +++ b/protocol/group/balancer/consistent_hashing.go @@ -0,0 +1,86 @@ +package balancer + +import ( + "math" + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/option" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/contrab/maphash" +) + +type ConsistentHashing struct { + outbounds map[string][]adapter.Outbound + delays map[string]uint16 + hash maphash.Hasher[string] + maxRetry int + maxAcceptableDelay map[string]uint16 + mu sync.Mutex + delayAcceptableRatio float64 +} + +func NewConsistentHashing(outbounds []adapter.Outbound, options option.BalancerOutboundOptions) *ConsistentHashing { + cOutbounds := convertOutbounds(outbounds) + + return &ConsistentHashing{ + outbounds: cOutbounds, + hash: maphash.NewHasher[string](), + maxRetry: options.MaxRetry, + delayAcceptableRatio: options.DelayAcceptableRatio, + } +} + +var _ Strategy = (*ConsistentHashing)(nil) + +func (s *ConsistentHashing) Now() string { + return "" +} +func (s *ConsistentHashing) UpdateOutboundsInfo(history map[string]*adapter.URLTestHistory) bool { + _, minDelay := getMinDelay(s.outbounds, history) + delayMap := getDelayMap(history) + res := map[string]uint16{} + for net, d := range minDelay { + acceptableDelay := uint16(math.Max(100, float64(d)) * s.delayAcceptableRatio) + res[net] = acceptableDelay + } + s.mu.Lock() + s.delays = delayMap + s.maxAcceptableDelay = res + s.mu.Unlock() + return true +} +func (g *ConsistentHashing) Select(metadata adapter.InboundContext, net string, touch bool) adapter.Outbound { + g.mu.Lock() + defer g.mu.Unlock() + if net != N.NetworkTCP && net != N.NetworkUDP { + net = N.NetworkTCP + } + key := g.hash.Hash(getKey(&metadata)) + buckets := int32(len(g.outbounds[net])) + for i := 0; i < g.maxRetry; i, key = i+1, key+1 { + idx := jumpHash(key, buckets) + proxy := g.outbounds[net][idx] + if g.Alive(proxy, net) { + return proxy + } + } + + // when availability is poor, traverse the entire list to get the available nodes + for _, proxy := range g.outbounds[net] { + if g.Alive(proxy, net) { + return proxy + } + } + idx := jumpHash(key, buckets) + return g.outbounds[net][idx] + +} + +func (s *ConsistentHashing) Alive(proxy adapter.Outbound, net string) bool { + if delay, ok := s.delays[proxy.Tag()]; ok { + return delay <= s.maxAcceptableDelay[net] + } + return false + +} diff --git a/protocol/group/balancer/lowest_delay.go b/protocol/group/balancer/lowest_delay.go new file mode 100644 index 0000000000..1ed6f61334 --- /dev/null +++ b/protocol/group/balancer/lowest_delay.go @@ -0,0 +1,58 @@ +package balancer + +import ( + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/option" + N "github.com/sagernet/sing/common/network" +) + +type LowestDelay struct { + outbounds map[string][]adapter.Outbound + udpOutbounds []adapter.Outbound + selectedOutbound map[string]adapter.Outbound + + mu sync.Mutex +} + +func NewLowestDelay(outbounds []adapter.Outbound, options option.BalancerOutboundOptions) *LowestDelay { + couts := convertOutbounds(outbounds) + return &LowestDelay{ + outbounds: couts, + selectedOutbound: map[string]adapter.Outbound{ + N.NetworkUDP: couts[N.NetworkUDP][0], + N.NetworkTCP: couts[N.NetworkTCP][0], + }, + } +} + +var _ Strategy = (*LowestDelay)(nil) + +func (s *LowestDelay) Now() string { + s.mu.Lock() + defer s.mu.Unlock() + + return s.selectedOutbound[N.NetworkTCP].Tag() +} +func (s *LowestDelay) UpdateOutboundsInfo(history map[string]*adapter.URLTestHistory) bool { + min, _ := getMinDelay(s.outbounds, history) + + s.mu.Lock() + changed := false + for net, out := range min { + changed = changed || out.Tag() != s.selectedOutbound[net].Tag() + } + s.selectedOutbound = min + s.mu.Unlock() + return changed +} +func (s *LowestDelay) Select(metadata adapter.InboundContext, net string, touch bool) adapter.Outbound { + s.mu.Lock() + defer s.mu.Unlock() + if net != N.NetworkTCP && net != N.NetworkUDP { + net = N.NetworkTCP + } + return s.selectedOutbound[net] + +} diff --git a/protocol/group/balancer/roundrobin.go b/protocol/group/balancer/roundrobin.go new file mode 100644 index 0000000000..8a31080549 --- /dev/null +++ b/protocol/group/balancer/roundrobin.go @@ -0,0 +1,82 @@ +package balancer + +import ( + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/option" + N "github.com/sagernet/sing/common/network" +) + +var _ Strategy = (*RoundRobin)(nil) + +type RoundRobin struct { + outbounds map[string][]adapter.Outbound + + sortedOutbounds map[string][]adapter.Outbound + + maxAcceptableIndex map[string]int + idx map[string]int + mu sync.Mutex + delayAcceptableRatio float64 +} + +func NewRoundRobin(outbounds []adapter.Outbound, options option.BalancerOutboundOptions) *RoundRobin { + cOutbounds := convertOutbounds(outbounds) + acceptable := map[string]int{} + idx := map[string]int{} + for net, outs := range cOutbounds { + acceptable[net] = len(outs) - 1 + idx[net] = 0 + } + return &RoundRobin{ + outbounds: cOutbounds, + + sortedOutbounds: cOutbounds, + maxAcceptableIndex: acceptable, + delayAcceptableRatio: options.DelayAcceptableRatio, + idx: idx, + } +} + +func (s *RoundRobin) Now() string { + // s.idxMutex.Lock() + // defer s.idxMutex.Unlock() + return "" +} + +func (s *RoundRobin) UpdateOutboundsInfo(history map[string]*adapter.URLTestHistory) bool { + sortedOutbounds := sortOutboundsByDelay(s.outbounds, history) + acceptableIndex := getAcceptableIndex(sortedOutbounds, history, s.delayAcceptableRatio) + + s.mu.Lock() + changed := false + for net, ix := range acceptableIndex { + changed = changed || ix != s.maxAcceptableIndex[net] + } + + s.sortedOutbounds = sortedOutbounds + s.maxAcceptableIndex = acceptableIndex + s.mu.Unlock() + return changed +} + +func (s *RoundRobin) Select(metadata adapter.InboundContext, net string, touch bool) adapter.Outbound { + s.mu.Lock() + defer s.mu.Unlock() + if net != N.NetworkTCP && net != N.NetworkUDP { + net = N.NetworkTCP + } + i := 1 + length := s.maxAcceptableIndex[net] + 1 + if length == 0 { + return nil + } + id := (s.idx[net] + i) % length + proxy := s.sortedOutbounds[net][id] + if touch { + s.idx[net] = id + } + return proxy + +} diff --git a/protocol/group/balancer/sticky_session.go b/protocol/group/balancer/sticky_session.go new file mode 100644 index 0000000000..bf6f18459f --- /dev/null +++ b/protocol/group/balancer/sticky_session.go @@ -0,0 +1,100 @@ +package balancer + +import ( + "math" + "sync" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/contrab/freelru" + "github.com/sagernet/sing/contrab/maphash" +) + +type StickySession struct { + outbounds map[string][]adapter.Outbound + hash maphash.Hasher[string] + maxRetry int + delays map[string]uint16 + maxAcceptableDelay map[string]uint16 + + mu sync.Mutex + delayAcceptableRatio float64 + lruCache *freelru.ShardedLRU[uint64, int] +} + +func NewStickySession(outbounds []adapter.Outbound, options option.BalancerOutboundOptions) *StickySession { + lruCache := common.Must1(freelru.NewSharded[uint64, int](1000, maphash.NewHasher[uint64]().Hash32)) + lruCache.SetLifetime(options.TTL.Build()) + cOutbounds := convertOutbounds(outbounds) + return &StickySession{ + outbounds: cOutbounds, + lruCache: lruCache, + hash: maphash.NewHasher[string](), + maxRetry: options.MaxRetry, + delayAcceptableRatio: options.DelayAcceptableRatio, + } +} + +var _ Strategy = (*StickySession)(nil) + +func (s *StickySession) Select(metadata adapter.InboundContext, net string, touch bool) adapter.Outbound { + s.mu.Lock() + defer s.mu.Unlock() + + if net != N.NetworkTCP && net != N.NetworkUDP { + net = N.NetworkTCP + } + key := s.hash.Hash(getKeyWithSrcAndDst(&metadata)) + length := len(s.outbounds[net]) + idx, has := s.lruCache.Get(key) + if !has || idx >= length { + idx = int(jumpHash(key+uint64(time.Now().UnixNano()), int32(length))) + } + + nowIdx := idx + for i := 1; i < s.maxRetry; i++ { + proxy := s.outbounds[net][nowIdx] + if s.Alive(proxy, net) { + if !has || nowIdx != idx { + s.lruCache.Add(key, nowIdx) + } + + return proxy + } else { + nowIdx = int(jumpHash(key+uint64(time.Now().UnixNano()), int32(length))) + } + } + + s.lruCache.Add(key, nowIdx) + return s.outbounds[net][nowIdx] + +} + +func (s *StickySession) Now() string { + return "" +} +func (s *StickySession) UpdateOutboundsInfo(history map[string]*adapter.URLTestHistory) bool { + _, minDelay := getMinDelay(s.outbounds, history) + delayMap := getDelayMap(history) + res := map[string]uint16{} + for net, d := range minDelay { + acceptableDelay := uint16(math.Max(100, float64(d)) * s.delayAcceptableRatio) + res[net] = acceptableDelay + } + s.mu.Lock() + s.delays = delayMap + s.maxAcceptableDelay = res + s.mu.Unlock() + return true +} + +func (s *StickySession) Alive(proxy adapter.Outbound, net string) bool { + if delay, ok := s.delays[proxy.Tag()]; ok { + return delay <= s.maxAcceptableDelay[net] + } + return false + +} diff --git a/protocol/group/balancer/strategy.go b/protocol/group/balancer/strategy.go new file mode 100644 index 0000000000..b95d5ae45f --- /dev/null +++ b/protocol/group/balancer/strategy.go @@ -0,0 +1,9 @@ +package balancer + +import "github.com/sagernet/sing-box/adapter" + +type Strategy interface { + UpdateOutboundsInfo(outbounds map[string]*adapter.URLTestHistory) (changed bool) + Select(metadata adapter.InboundContext, network string, touch bool) adapter.Outbound + Now() string +} diff --git a/outbound/selector.go b/protocol/group/selector.go similarity index 53% rename from outbound/selector.go rename to protocol/group/selector.go index e801daeadc..85bea2b964 100644 --- a/outbound/selector.go +++ b/protocol/group/selector.go @@ -1,46 +1,56 @@ -package outbound +package group import ( "context" "net" + "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/interrupt" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + tun "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/service" ) +func RegisterSelector(registry *outbound.Registry) { + outbound.Register[option.SelectorOutboundOptions](registry, C.TypeSelector, NewSelector) +} + var ( - _ adapter.Outbound = (*Selector)(nil) - _ adapter.OutboundGroup = (*Selector)(nil) + _ adapter.OutboundGroup = (*Selector)(nil) + _ adapter.ConnectionHandler = (*Selector)(nil) + _ adapter.PacketConnectionHandler = (*Selector)(nil) ) type Selector struct { - myOutboundAdapter + outbound.Adapter ctx context.Context + outbound adapter.OutboundManager + connection adapter.ConnectionManager + logger logger.ContextLogger tags []string defaultTag string outbounds map[string]adapter.Outbound - selected adapter.Outbound + selected common.TypedValue[adapter.Outbound] interruptGroup *interrupt.Group interruptExternalConnections bool } -func NewSelector(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SelectorOutboundOptions) (*Selector, error) { +func NewSelector(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SelectorOutboundOptions) (adapter.Outbound, error) { outbound := &Selector{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeSelector, - router: router, - logger: logger, - tag: tag, - dependencies: options.Outbounds, - }, + Adapter: outbound.NewAdapter(C.TypeSelector, tag, nil, options.Outbounds), ctx: ctx, + outbound: service.FromContext[adapter.OutboundManager](ctx), + connection: service.FromContext[adapter.ConnectionManager](ctx), + logger: logger, tags: options.Outbounds, defaultTag: options.Default, outbounds: make(map[string]adapter.Outbound), @@ -54,29 +64,30 @@ func NewSelector(ctx context.Context, router adapter.Router, logger log.ContextL } func (s *Selector) Network() []string { - if s.selected == nil { + selected := s.selected.Load() + if selected == nil { return []string{N.NetworkTCP, N.NetworkUDP} } - return s.selected.Network() + return selected.Network() } func (s *Selector) Start() error { for i, tag := range s.tags { - detour, loaded := s.router.Outbound(tag) + detour, loaded := s.outbound.Outbound(tag) if !loaded { return E.New("outbound ", i, " not found: ", tag) } s.outbounds[tag] = detour } - if s.tag != "" { + if s.Tag() != "" { cacheFile := service.FromContext[adapter.CacheFile](s.ctx) if cacheFile != nil { - selected := cacheFile.LoadSelected(s.tag) + selected := cacheFile.LoadSelected(s.Tag()) if selected != "" { detour, loaded := s.outbounds[selected] if loaded { - s.selected = detour + s.selected.Store(detour) return nil } } @@ -88,16 +99,20 @@ func (s *Selector) Start() error { if !loaded { return E.New("default outbound not found: ", s.defaultTag) } - s.selected = detour + s.selected.Store(detour) return nil } - s.selected = s.outbounds[s.tags[0]] + s.selected.Store(s.outbounds[s.tags[0]]) return nil } func (s *Selector) Now() string { - return s.selected.Tag() + selected := s.selected.Load() + if selected == nil { + return s.tags[0] + } + return selected.Tag() } func (s *Selector) All() []string { @@ -109,14 +124,13 @@ func (s *Selector) SelectOutbound(tag string) bool { if !loaded { return false } - if s.selected == detour { + if s.selected.Swap(detour) == detour { return true } - s.selected = detour - if s.tag != "" { + if s.Tag() != "" { cacheFile := service.FromContext[adapter.CacheFile](s.ctx) if cacheFile != nil { - err := cacheFile.StoreSelected(s.tag, tag) + err := cacheFile.StoreSelected(s.Tag(), tag) if err != nil { s.logger.Error("store selected: ", err) } @@ -127,7 +141,7 @@ func (s *Selector) SelectOutbound(tag string) bool { } func (s *Selector) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - conn, err := s.selected.DialContext(ctx, network, destination) + conn, err := s.selected.Load().DialContext(ctx, network, destination) if err != nil { return nil, err } @@ -135,21 +149,39 @@ func (s *Selector) DialContext(ctx context.Context, network string, destination } func (s *Selector) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - conn, err := s.selected.ListenPacket(ctx, destination) + conn, err := s.selected.Load().ListenPacket(ctx, destination) if err != nil { return nil, err } return s.interruptGroup.NewPacketConn(conn, interrupt.IsExternalConnectionFromContext(ctx)), nil } -func (s *Selector) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (s *Selector) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { ctx = interrupt.ContextWithIsExternalConnection(ctx) - return s.selected.NewConnection(ctx, conn, metadata) + selected := s.selected.Load() + if outboundHandler, isHandler := selected.(adapter.ConnectionHandler); isHandler { + outboundHandler.NewConnection(ctx, conn, metadata, onClose) + } else { + s.connection.NewConnection(ctx, selected, conn, metadata, onClose) + } } -func (s *Selector) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (s *Selector) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { ctx = interrupt.ContextWithIsExternalConnection(ctx) - return s.selected.NewPacketConnection(ctx, conn, metadata) + selected := s.selected.Load() + if outboundHandler, isHandler := selected.(adapter.PacketConnectionHandler); isHandler { + outboundHandler.NewPacketConnection(ctx, conn, metadata, onClose) + } else { + s.connection.NewPacketConnection(ctx, selected, conn, metadata, onClose) + } +} + +func (s *Selector) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + selected := s.selected.Load() + if !common.Contains(selected.Network(), metadata.Network) { + return nil, E.New(metadata.Network, " is not supported by outbound: ", selected.Tag()) + } + return selected.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout) } func RealTag(detour adapter.Outbound) string { diff --git a/outbound/urltest.go b/protocol/group/urltest.go similarity index 69% rename from outbound/urltest.go rename to protocol/group/urltest.go index c6e38ec5a8..51fe3d0a47 100644 --- a/outbound/urltest.go +++ b/protocol/group/urltest.go @@ -1,36 +1,42 @@ -package outbound +package group import ( "context" "net" "sync" + "sync/atomic" "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/interrupt" "github.com/sagernet/sing-box/common/urltest" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/batch" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/pause" ) -var ( - _ adapter.Outbound = (*URLTest)(nil) - _ adapter.OutboundGroup = (*URLTest)(nil) - _ adapter.InterfaceUpdateListener = (*URLTest)(nil) -) +func RegisterURLTest(registry *outbound.Registry) { + outbound.Register[option.URLTestOutboundOptions](registry, C.TypeURLTest, NewURLTest) +} + +var _ adapter.OutboundGroup = (*URLTest)(nil) type URLTest struct { - myOutboundAdapter + outbound.Adapter ctx context.Context + outbound adapter.OutboundManager + connection adapter.ConnectionManager + logger log.ContextLogger tags []string link string interval time.Duration @@ -40,17 +46,13 @@ type URLTest struct { interruptExternalConnections bool } -func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (*URLTest, error) { +func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (adapter.Outbound, error) { outbound := &URLTest{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeURLTest, - network: []string{N.NetworkTCP, N.NetworkUDP}, - router: router, - logger: logger, - tag: tag, - dependencies: options.Outbounds, - }, + Adapter: outbound.NewAdapter(C.TypeURLTest, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.Outbounds), ctx: ctx, + outbound: service.FromContext[adapter.OutboundManager](ctx), + connection: service.FromContext[adapter.ConnectionManager](ctx), + logger: logger, tags: options.Outbounds, link: options.URL, interval: time.Duration(options.Interval), @@ -67,23 +69,13 @@ func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLo func (s *URLTest) Start() error { outbounds := make([]adapter.Outbound, 0, len(s.tags)) for i, tag := range s.tags { - detour, loaded := s.router.Outbound(tag) + detour, loaded := s.outbound.Outbound(tag) if !loaded { return E.New("outbound ", i, " not found: ", tag) } outbounds = append(outbounds, detour) } - group, err := NewURLTestGroup( - s.ctx, - s.router, - s.logger, - outbounds, - s.link, - s.interval, - s.tolerance, - s.idleTimeout, - s.interruptExternalConnections, - ) + group, err := NewURLTestGroup(s.ctx, s.outbound, s.logger, outbounds, s.link, s.interval, s.tolerance, s.idleTimeout, s.interruptExternalConnections) if err != nil { return err } @@ -167,56 +159,56 @@ func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (ne return nil, err } -func (s *URLTest) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (s *URLTest) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { ctx = interrupt.ContextWithIsExternalConnection(ctx) - return NewConnection(ctx, s, conn, metadata) + s.connection.NewConnection(ctx, s, conn, metadata, onClose) } -func (s *URLTest) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (s *URLTest) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { ctx = interrupt.ContextWithIsExternalConnection(ctx) - return NewPacketConnection(ctx, s, conn, metadata) + s.connection.NewPacketConnection(ctx, s, conn, metadata, onClose) } -func (s *URLTest) InterfaceUpdated() { - go s.group.CheckOutbounds(true) - return +func (s *URLTest) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + s.group.Touch() + selected := s.group.selectedOutboundTCP + if selected == nil { + selected, _ = s.group.Select(N.NetworkTCP) + } + if selected == nil { + return nil, E.New("missing supported outbound") + } + if !common.Contains(selected.Network(), metadata.Network) { + return nil, E.New(metadata.Network, " is not supported by outbound: ", selected.Tag()) + } + return selected.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout) } type URLTestGroup struct { ctx context.Context - router adapter.Router + outbound adapter.OutboundManager + pause pause.Manager + pauseCallback *list.Element[pause.Callback] logger log.Logger outbounds []adapter.Outbound link string interval time.Duration tolerance uint16 idleTimeout time.Duration - history *urltest.HistoryStorage + history adapter.URLTestHistoryStorage checking atomic.Bool - pauseManager pause.Manager selectedOutboundTCP adapter.Outbound selectedOutboundUDP adapter.Outbound interruptGroup *interrupt.Group interruptExternalConnections bool - - access sync.Mutex - ticker *time.Ticker - close chan struct{} - started bool - lastActive atomic.TypedValue[time.Time] + access sync.Mutex + ticker *time.Ticker + close chan struct{} + started bool + lastActive common.TypedValue[time.Time] } -func NewURLTestGroup( - ctx context.Context, - router adapter.Router, - logger log.Logger, - outbounds []adapter.Outbound, - link string, - interval time.Duration, - tolerance uint16, - idleTimeout time.Duration, - interruptExternalConnections bool, -) (*URLTestGroup, error) { +func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManager, logger log.Logger, outbounds []adapter.Outbound, link string, interval time.Duration, tolerance uint16, idleTimeout time.Duration, interruptExternalConnections bool) (*URLTestGroup, error) { if interval == 0 { interval = C.DefaultURLTestInterval } @@ -229,16 +221,17 @@ func NewURLTestGroup( if interval > idleTimeout { return nil, E.New("interval must be less or equal than idle_timeout") } - var history *urltest.HistoryStorage - if history = service.PtrFromContext[urltest.HistoryStorage](ctx); history != nil { - } else if clashServer := router.ClashServer(); clashServer != nil { + var history adapter.URLTestHistoryStorage + if historyFromCtx := service.PtrFromContext[urltest.HistoryStorage](ctx); historyFromCtx != nil { + history = historyFromCtx + } else if clashServer := service.FromContext[adapter.ClashServer](ctx); clashServer != nil { history = clashServer.HistoryStorage() } else { history = urltest.NewHistoryStorage() } return &URLTestGroup{ ctx: ctx, - router: router, + outbound: outboundManager, logger: logger, outbounds: outbounds, link: link, @@ -247,13 +240,15 @@ func NewURLTestGroup( idleTimeout: idleTimeout, history: history, close: make(chan struct{}), - pauseManager: service.FromContext[pause.Manager](ctx), + pause: service.FromContext[pause.Manager](ctx), interruptGroup: interrupt.NewGroup(), interruptExternalConnections: interruptExternalConnections, }, nil } func (g *URLTestGroup) PostStart() { + g.access.Lock() + defer g.access.Unlock() g.started = true g.lastActive.Store(time.Now()) go g.CheckOutbounds(false) @@ -263,24 +258,28 @@ func (g *URLTestGroup) Touch() { if !g.started { return } - if g.ticker != nil { - g.lastActive.Store(time.Now()) - return - } g.access.Lock() defer g.access.Unlock() if g.ticker != nil { + g.lastActive.Store(time.Now()) return } - g.ticker = time.NewTicker(g.interval) - go g.loopCheck() + ticker := time.NewTicker(g.interval) + g.ticker = ticker + g.pauseCallback = pause.RegisterTicker(g.pause, ticker, g.interval, nil) + go g.loopCheck(ticker, g.close) } func (g *URLTestGroup) Close() error { + g.access.Lock() + defer g.access.Unlock() if g.ticker == nil { return nil } g.ticker.Stop() + g.ticker = nil + g.pause.UnregisterCallback(g.pauseCallback) + g.pauseCallback = nil close(g.close) return nil } @@ -329,25 +328,28 @@ func (g *URLTestGroup) Select(network string) (adapter.Outbound, bool) { return minOutbound, true } -func (g *URLTestGroup) loopCheck() { - if time.Now().Sub(g.lastActive.Load()) > g.interval { +func (g *URLTestGroup) loopCheck(ticker *time.Ticker, closeChan <-chan struct{}) { + if time.Since(g.lastActive.Load()) > g.interval { g.lastActive.Store(time.Now()) g.CheckOutbounds(false) } for { select { - case <-g.close: + case <-closeChan: return - case <-g.ticker.C: + case <-ticker.C: } - if time.Now().Sub(g.lastActive.Load()) > g.idleTimeout { + if time.Since(g.lastActive.Load()) > g.idleTimeout { g.access.Lock() - g.ticker.Stop() - g.ticker = nil + if g.ticker == ticker { + g.ticker.Stop() + g.ticker = nil + g.pause.UnregisterCallback(g.pauseCallback) + g.pauseCallback = nil + } g.access.Unlock() return } - g.pauseManager.WaitActive() g.CheckOutbounds(false) } } @@ -376,11 +378,11 @@ func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint continue } history := g.history.LoadURLTestHistory(realTag) - if !force && history != nil && time.Now().Sub(history.Time) < g.interval { + if !force && history != nil && time.Since(history.Time) < g.interval { continue } checked[realTag] = true - p, loaded := g.router.Outbound(realTag) + p, loaded := g.outbound.Outbound(realTag) if !loaded { continue } @@ -393,7 +395,7 @@ func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint g.history.DeleteURLTestHistory(realTag) } else { g.logger.Debug("outbound ", tag, " available: ", t, "ms") - g.history.StoreURLTestHistory(realTag, &urltest.History{ + g.history.StoreURLTestHistory(realTag, &adapter.URLTestHistory{ Time: time.Now(), Delay: t, }) @@ -412,12 +414,16 @@ func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint func (g *URLTestGroup) performUpdateCheck() { var updated bool if outbound, exists := g.Select(N.NetworkTCP); outbound != nil && (g.selectedOutboundTCP == nil || (exists && outbound != g.selectedOutboundTCP)) { + if g.selectedOutboundTCP != nil { + updated = true + } g.selectedOutboundTCP = outbound - updated = true } if outbound, exists := g.Select(N.NetworkUDP); outbound != nil && (g.selectedOutboundUDP == nil || (exists && outbound != g.selectedOutboundUDP)) { + if g.selectedOutboundUDP != nil { + updated = true + } g.selectedOutboundUDP = outbound - updated = true } if updated { g.interruptGroup.Interrupt(g.interruptExternalConnections) diff --git a/protocol/group/urltest_helper.go b/protocol/group/urltest_helper.go new file mode 100644 index 0000000000..5cfaca1df2 --- /dev/null +++ b/protocol/group/urltest_helper.go @@ -0,0 +1,317 @@ +package group + +// import ( +// "context" +// "sync" +// "time" + +// "github.com/sagernet/sing-box/log" + +// "github.com/sagernet/sing-box/adapter" +// "github.com/sagernet/sing-box/common/urltest" +// C "github.com/sagernet/sing-box/constant" +// "github.com/sagernet/sing-box/hiddify/ipinfo" +// "github.com/sagernet/sing/common" +// "github.com/sagernet/sing/common/batch" +// N "github.com/sagernet/sing/common/network" +// ) + +// const TimeoutDelay = 65535 + +// func urltestTimeout(ctx context.Context, logger log.Logger, realTag string, outbound adapter.Outbound, url string, history adapter.URLTestHistoryStorage, timeout time.Duration) *adapter.URLTestHistory { +// testCtx, cancel := context.WithTimeout(ctx, timeout) +// defer cancel() +// t, err := urltest.URLTest(testCtx, url, outbound) +// if err != nil || t == 0 { +// t = TimeoutDelay +// } + +// his := &adapter.URLTestHistory{ +// Time: time.Now(), +// Delay: t, +// } +// logger.Debug("outbound new ping ", realTag, " = ", his.Delay) +// return his +// } + +// func CheckOutbound(logger log.Logger, ctx context.Context, history adapter.URLTestHistoryStorage, router adapter.OutboundManager, url string, outbound adapter.Outbound, ipbatch *batch.Batch[any]) uint16 { +// realTag := RealTag(outbound) +// hisbefore := history.LoadURLTestHistory(realTag) +// timeout := C.TCPTimeout +// isTimeoutBefore := isTimeout(hisbefore) + +// if !isTimeoutBefore { +// timeout = time.Duration(max(400, hisbefore.Delay)) * time.Millisecond * 5 +// logger.Debug("outbound is already connected ", realTag, " = ", hisbefore.Delay, " set timeout for new urltest to ", timeout) +// } +// his := urltestTimeout(ctx, logger, realTag, outbound, url, history, timeout) + +// // if outbound.Type() == C.TypeWireGuard && his.Delay > 1000 { // double check for wireguard +// // his = urltestTimeout(ctx, logger, realTag, outbound, url, history, timeout) +// // } +// if isTimeout(his) && !isTimeoutBefore { +// his = urltestTimeout(ctx, logger, realTag, outbound, url, history, C.TCPTimeout) +// } +// his = history.StoreURLTestHistory(realTag, his) +// if !isTimeout(his) && his.IpInfo == nil { +// if ipbatch == nil { +// go CheckIP(logger, ctx, history, router, outbound) +// } else { +// ipbatch.Go(realTag+"ip", func() (any, error) { +// select { +// case <-ctx.Done(): +// return nil, ctx.Err() +// default: +// } +// CheckIP(logger, ctx, history, router, outbound) +// return "", nil +// }) +// } +// } + +// return his.Delay +// } + +// func CheckIP(logger log.Logger, ctx context.Context, history adapter.URLTestHistoryStorage, router adapter.OutboundManager, outbound adapter.Outbound) { +// if outbound == nil { +// return +// } +// if history == nil { +// return +// } +// realTag := RealTag(outbound) +// detour, loaded := router.Outbound(realTag) +// if !loaded { +// return +// } +// his := history.LoadURLTestHistory(realTag) +// if isTimeout(his) { +// return +// } +// if his.IpInfo != nil { +// // logger.Debug("ip already calculated ", fmt.Sprint(his.IpInfo)) +// return +// } +// newip, t, err := ipinfo.GetIpInfo(logger, ctx, detour) +// if err != nil { +// // g.logger.Debug("outbound ", realTag, " IP unavailable (", t, "ms): ", err) +// // g.history.AddOnlyIpToHistory(realTag, &urltest.History{ +// // Time: time.Now(), +// // Delay: TimeoutDelay, +// // IpInfo: &ipinfo.IpInfo{}, +// // }) +// return +// } +// // g.logger.Trace("outbound ", realTag, " IP ", fmt.Sprint(newip), " (", t, "ms): ", err) +// history.AddOnlyIpToHistory(realTag, &adapter.URLTestHistory{ +// Time: time.Now(), +// Delay: t, +// IpInfo: newip, +// }) +// } + +// func isTimeout(history *adapter.URLTestHistory) bool { +// return history == nil || history.Delay >= TimeoutDelay || history.Delay == 0 +// } + +// func (g *URLTestGroup) urlTestEx(ctx context.Context, force bool, force_check_even_previous_not_completed bool) (map[string]uint16, error) { +// if t := g.selectedOutboundTCP; t != nil { +// go g.urltestImp(t, nil) +// } +// if t := g.selectedOutboundUDP; t != nil && t != g.selectedOutboundTCP { +// go g.urltestImp(t, nil) +// } + +// if force_check_even_previous_not_completed && time.Since(g.lastForceRecheck) < 15*time.Second { +// return make(map[string]uint16), nil +// } + +// if g.checking.Swap(true) { +// if !force_check_even_previous_not_completed { +// return make(map[string]uint16), nil +// } +// if g.checkingEx.Swap(true) { +// g.performUpdateCheck() +// return make(map[string]uint16), nil +// } +// defer g.checkingEx.Store(false) +// g.lastForceRecheck = time.Now() +// } +// defer g.checking.Store(false) + +// result, err := g.urlTestExImp(ctx, force, force_check_even_previous_not_completed) +// if err != nil { +// return nil, err +// } +// if !force_check_even_previous_not_completed && g.currentLinkIndex == 0 { +// for i := 1; i < len(g.links); i++ { +// result, err := g.urlTestExImp(ctx, force, force_check_even_previous_not_completed) +// if err != nil { +// return nil, err +// } +// if g.hasOneAvailableOutbound() { +// g.currentLinkIndex = i +// return result, nil +// } + +// } +// } +// return result, nil +// } +// func (g *URLTestGroup) urlTestExImp(ctx context.Context, force bool, force_check_even_previous_not_completed bool) (map[string]uint16, error) { +// result := make(map[string]uint16) +// ipbatch, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10)) +// b, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10)) +// checked := make(map[string]bool) +// var resultAccess sync.Mutex +// for _, detour := range g.outbounds { +// tag := detour.Tag() +// realTag := RealTag(detour) +// if checked[realTag] { +// continue +// } +// history := g.history.LoadURLTestHistory(realTag) +// if !force && !isTimeout(history) && time.Since(history.Time) < g.interval { +// continue +// } +// checked[realTag] = true +// p, loaded := g.outbound.Outbound(realTag) +// if !loaded { +// continue +// } +// b.Go(realTag, func() (any, error) { +// select { +// case <-ctx.Done(): +// return nil, ctx.Err() +// default: +// } +// if !force_check_even_previous_not_completed && g.checkingEx.Load() { +// return nil, nil +// } +// t := g.urltestImp(p, ipbatch) +// resultAccess.Lock() +// result[tag] = t +// g.performOutboundUpdateCheck(detour) +// resultAccess.Unlock() +// return nil, nil +// }) +// } +// if err := WaitBatchesWithContext(ctx, b, ipbatch); err != nil { +// return nil, err +// } + +// g.performUpdateCheck() + +// return result, nil +// } + +// func WaitBatchesWithContext(ctx context.Context, batches ...*batch.Batch[any]) error { +// done := make(chan error, len(batches)) + +// for _, b := range batches { +// go func(b *batch.Batch[any]) { +// done <- b.Wait() +// }(b) +// } + +// for i := 0; i < len(batches); i++ { +// select { +// case err := <-done: +// if err != nil { +// return err +// } +// case <-ctx.Done(): +// return ctx.Err() +// } +// } +// return nil +// } +// func (g *URLTestGroup) urltestImp(outbound adapter.Outbound, ipbatch *batch.Batch[any]) uint16 { +// return CheckOutbound(g.logger, g.ctx, g.history, g.outbound, g.links[g.currentLinkIndex], outbound, ipbatch) +// } + +// func (g *URLTestGroup) hasOneAvailableOutbound() bool { +// for _, detour := range g.outbounds { +// if !common.Contains(detour.Network(), "tcp") { +// continue +// } +// realTag := RealTag(detour) +// history := g.history.LoadURLTestHistory(realTag) +// if isTimeout(history) { +// continue +// } +// g.logger.Debug("has one outbound ", realTag, " available: ", history.Delay, "ms") +// return true +// } +// g.logger.Debug("no available outbound ") +// return false +// } + +// func (g *URLTestGroup) forceUpdateOutbound(tcp adapter.Outbound, udp adapter.Outbound) bool { +// update := false +// if tcp != nil && g.selectedOutboundTCP != tcp { +// g.selectedOutboundTCP = tcp +// // g.tcpConnectionFailureCount.Reset() +// // go g.checkHistoryIp(g.selectedOutboundTCP) +// update = true + +// } +// if udp != nil && g.selectedOutboundUDP != udp { +// g.selectedOutboundUDP = udp +// // g.udpConnectionFailureCount.Reset() +// update = true +// } +// if update { +// g.interruptGroup.Interrupt(g.interruptExternalConnections) +// } +// return update +// } +// func (g *URLTestGroup) performOutboundUpdateCheck(outbound adapter.Outbound) { +// tcpOutbound, shouldReselectTCP := g.getPreferredOutbound(outbound, g.selectedOutboundTCP, N.NetworkTCP) +// udpOutbound, shouldReselectUDP := g.getPreferredOutbound(outbound, g.selectedOutboundUDP, N.NetworkUDP) +// if shouldReselectTCP { +// tcpOutbound, _ = g.Select(N.NetworkTCP) +// } +// if shouldReselectUDP { +// udpOutbound, _ = g.Select(N.NetworkUDP) +// } + +// g.forceUpdateOutbound(tcpOutbound, udpOutbound) +// } + +// func (g *URLTestGroup) getPreferredOutbound(newOutbound, selectedOutbound adapter.Outbound, networkType string) (outbound adapter.Outbound, shouldReselect bool) { +// if newOutbound == nil { +// return nil, false +// } + +// if !common.Contains(newOutbound.Network(), networkType) { +// return nil, false +// } + +// newHistory := g.history.LoadURLTestHistory(RealTag(newOutbound)) +// if isTimeout(newHistory) { +// if newOutbound == selectedOutbound { +// return nil, true +// } +// return nil, false +// } + +// if selectedOutbound == nil { +// return newOutbound, false +// } + +// selectedHistory := g.history.LoadURLTestHistory(RealTag(selectedOutbound)) +// if isTimeout(selectedHistory) { +// return newOutbound, false +// } + +// if newHistory == nil || selectedHistory == nil { +// return nil, false +// } + +// if newHistory.Delay+g.tolerance < selectedHistory.Delay { +// return newOutbound, false +// } + +// return selectedOutbound, false +// } diff --git a/protocol/hiddify/dnstt/conn.go b/protocol/hiddify/dnstt/conn.go new file mode 100644 index 0000000000..8dfe976662 --- /dev/null +++ b/protocol/hiddify/dnstt/conn.go @@ -0,0 +1,42 @@ +package dnstt + +import ( + "bytes" + "net" +) + +type LoggingConn struct { + net.Conn + rx bytes.Buffer + tx bytes.Buffer + outbound *Outbound + tunnel_index int +} + +func (c *LoggingConn) Read(b []byte) (int, error) { + n, err := c.Conn.Read(b) + if n > 0 { + c.rx.Write(b[:n]) + } + return n, err +} + +func (c *LoggingConn) Write(b []byte) (int, error) { + if len(b) > 0 { + c.tx.Write(b) + } + return c.Conn.Write(b) +} + +func (c *LoggingConn) Close() error { + c.outbound.logger.Info(c.outbound.Tag(), " Tunnel ", c.tunnel_index, " closing connection. TX bytes: ", c.tx.Len(), ", RX bytes: ", c.rx.Len()) + // bs := c.rx.Bytes() + + // fmt.Printf("TX bytes: \n%s\n", c.tx.String()) + // if len(bs) > 0 { + // fmt.Printf("RX bytes: \n%s\n", c.tx.String()) + // } else { + // fmt.Printf("RX bytes: %d \n", len(bs)) + // } + return c.Conn.Close() +} diff --git a/protocol/hiddify/dnstt/dnstt_func.go b/protocol/hiddify/dnstt/dnstt_func.go new file mode 100644 index 0000000000..c010cca716 --- /dev/null +++ b/protocol/hiddify/dnstt/dnstt_func.go @@ -0,0 +1,192 @@ +package dnstt + +import ( + "context" + "math/rand" + "net" + + "fmt" + + dnstt "github.com/net2share/vaydns/client" + "github.com/sagernet/sing-box/common/monitoring" +) + +func (c *Outbound) addResolver(resolver dnstt.Resolver) { + c.mu.Lock() + if c.mdMgr != nil { + // Smart pool mode: register the resolver as a multidns upstream. + // c.resolvers stays at the single virtual resolver set up in + // NewOutbound, so the existing tunnel keeps running — the pool + // rotates upstreams behind it. No need to invalidate + // c.mutlitunnel here (unlike the legacy path, which has to + // rebuild the tunnel when its resolver list changes). + _, _ = c.mdMgr.AddResolverURL(resolverURL(resolver)) + } else { + // for i := 0; i < c.options.TunnelPerResolver; i++ { + c.resolvers = append(c.resolvers, resolver) + c.tunnels = append(c.tunnels, nil) + // } + c.mutlitunnel = nil + } + c.mu.Unlock() + if !c.IsReady() { + c.started = 1 + c.logger.InfoContext(c.ctx, "initial resolver ", resolver.ResolverAddr) + monitoring.Get(c.ctx).TestNow(c.Tag()) + } +} + +// resolverURL renders a dnstt.Resolver as the URL form multidns parses. +// UDP resolvers are bare host:port (multidns defaults to UDP/53); DoT gets +// the "dot://" prefix; DoH is already a full https:// URL. +func resolverURL(r dnstt.Resolver) string { + switch r.ResolverType { + case dnstt.ResolverTypeDOT: + return "dot://" + r.ResolverAddr + default: + return r.ResolverAddr + } +} + +func (c *Outbound) openStreamImp(ctx context.Context) (net.Conn, error) { + c.mu.Lock() + defer c.mu.Unlock() + if t := c.mutlitunnel; t != nil { + return t.OpenStream() + } + + if newtunnel, err := c.createDnsttTunnel(ctx, c.resolvers); err != nil { + return nil, err + } else { + c.mutlitunnel = newtunnel + return newtunnel.OpenStream() + } +} +func (c *Outbound) OpenStream(ctx context.Context) (net.Conn, error) { + var err error + var conn net.Conn + for range 3 { + conn, err = c.openStreamImp(ctx) + if err == nil { + return conn, nil + } + } + return nil, err +} +func (c *Outbound) OpenStreamSingleResolver(ctx context.Context) (net.Conn, error) { + // dnsttConfig := streamSettings.ProtocolSettings.(*Config) + c.mu.Lock() + defer c.mu.Unlock() + // c.tunnel_index = (c.tunnel_index + 1) % len(c.tunnels) + var lasterr error + for i := 0; i < max(5, len(c.tunnels)); i++ { + tunnel_index := rand.Intn(len(c.resolvers)) // 0 <= x < n + tunnel := c.tunnels[tunnel_index] + if tunnel == nil { + if newtunnel, err := c.createDnsttTunnel(ctx, []dnstt.Resolver{c.resolvers[tunnel_index]}); err != nil { + lasterr = err + c.logger.DebugContext(ctx, "tunnel [", tunnel_index, "] failed resolver ", c.resolvers[tunnel_index].ResolverAddr) + } else { + c.tunnels[tunnel_index] = newtunnel + tunnel = newtunnel + c.logger.InfoContext(ctx, "tunnel [", tunnel_index, "] resolver ", c.resolvers[tunnel_index].ResolverAddr) + + // return tunnel, nil + } + } + if tunnel != nil { + if conn, err := tunnel.OpenStream(); err == nil { + // return &LoggingConn{Conn: stream, outbound: h, tunnel_index: h.tunnel_index}, nil + return conn, nil + } else { + lasterr = err + } + tunnel.Close() + c.tunnels[tunnel_index] = nil + } + } + return nil, lasterr +} +func (c *Outbound) createDnsttTunnel(ctx context.Context, resolver []dnstt.Resolver) (*dnstt.Tunnel, error) { + tServer, err := dnstt.NewTunnelServer(c.options.Domain, c.options.PublicKey) + if err != nil { + return nil, fmt.Errorf("invalid tunnel server: %w", err) + } + + if c.options.MTU != nil { + tServer.MTU = *c.options.MTU + } + + tServer.DnsttCompat = c.options.DnsttCompat + + if c.options.ClientIDSize != nil { + tServer.ClientIDSize = *c.options.ClientIDSize + } + + if c.options.MaxNumLabels != nil { + tServer.MaxNumLabels = *c.options.MaxNumLabels + } + if c.options.RPS != nil { + tServer.RPS = *c.options.RPS + } + if c.options.MaxQnameLen != nil { + tServer.MaxQnameLen = *c.options.MaxQnameLen + } + + tunnel, err := dnstt.NewTunnel(resolver, tServer) + if err != nil { + return nil, fmt.Errorf("failed to create tunnel: %w", err) + } + if c.options.OpenStreamTimeout != nil { + tunnel.OpenStreamTimeout = c.options.OpenStreamTimeout.Build() + } + if c.options.MaxStreams != nil { + tunnel.MaxStreams = *c.options.MaxStreams + } + if c.options.ReconnectMinDelay != nil { + tunnel.ReconnectMinDelay = c.options.ReconnectMinDelay.Build() + } + if c.options.ReconnectMaxDelay != nil { + tunnel.ReconnectMaxDelay = c.options.ReconnectMaxDelay.Build() + } + if c.options.SessionCheckInterval != nil { + tunnel.SessionCheckInterval = c.options.SessionCheckInterval.Build() + } + if c.options.IdleTimeout != nil { + tunnel.IdleTimeout = c.options.IdleTimeout.Build() + } + if c.options.KeepAlive != nil { + tunnel.KeepAlive = c.options.KeepAlive.Build() + } + if c.options.HandshakeTimeout != nil { + tunnel.HandshakeTimeout = c.options.HandshakeTimeout.Build() + } + + if err := tunnel.InitiateResolverConnection(); err != nil { + return nil, fmt.Errorf("failed to initiate connection to resolver: %w", err) + } + + if err := tunnel.InitiateDNSPacketConn(tServer.Addr); err != nil { + return nil, fmt.Errorf("failed to initiate DNS packet connection: %w", err) + } + + // c.logger.DebugContext(c.ctx, "effective MTU %d", tServer.MTU) + + if err := tunnel.InitiateKCPConn(tServer.MTU); err != nil { + return nil, fmt.Errorf("failed to initiate KCP connection: %w", err) + } + + // c.logger.DebugContext(c.ctx, "established KCP conn") + if err := tunnel.InitiateNoiseChannel(); err != nil { + // c.logger.WarnContext(c.ctx, "failed to establish Noise channel: %v", err) + return nil, fmt.Errorf("failed to initiate noise channel: %w", err) + } + + // c.logger.DebugContext(c.ctx, "established Noise channel") + + if err := tunnel.InitiateSmuxSession(); err != nil { + return nil, fmt.Errorf("failed to initiate smux session: %w", err) + } + // c.logger.DebugContext(c.ctx, "established smux session") + return tunnel, nil +} diff --git a/protocol/hiddify/dnstt/outbound.go b/protocol/hiddify/dnstt/outbound.go new file mode 100644 index 0000000000..1715554936 --- /dev/null +++ b/protocol/hiddify/dnstt/outbound.go @@ -0,0 +1,179 @@ +package dnstt + +import ( + "context" + "fmt" + "net" + "sync" + "time" + + multidns "github.com/hiddify/hmrd_multi_resolver_dns" + dnstt "github.com/net2share/vaydns/client" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json/badoption" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/uot" + "github.com/sagernet/sing/service" +) + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.DnsttOptions](registry, C.TypeDNSTT, NewOutbound) + loadResolvers() +} + +var _ adapter.Outbound = (*Outbound)(nil) + +type Outbound struct { + outbound.Adapter + dnsRouter adapter.DNSRouter + logger logger.ContextLogger + ctx context.Context + + candidateResolvers []ResolverS + resolvers []dnstt.Resolver + tunnels []*dnstt.Tunnel + mutlitunnel *dnstt.Tunnel + mu sync.Mutex + cache adapter.CacheFile + uotClient *uot.Client + started int + resolve bool + tunnel_index int + + mdMgr *multidns.Manager // smart pool, set when options.SmartPool + + options option.DnsttOptions +} + +func (c *Outbound) PreStart() error { + c.cache = service.FromContext[adapter.CacheFile](c.ctx) + return nil +} + +func (h *Outbound) PostStart() error { + go h.startTestResolver() + return nil +} + +func initDuration(d *badoption.Duration, defultDuration time.Duration) time.Duration { + b := d.Build() + if b <= 0 { + return defultDuration + } + return b +} + +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DnsttOptions) (adapter.Outbound, error) { + + // if options.TunnelPerResolver <= 0 { + // options.TunnelPerResolver = 4 + // } + + resolvers, err := getConfigResolvers(options) + if err != nil { + return nil, err + } + + if len(resolvers) == 0 { + return nil, E.New("at least one resolver is required") + } + if options.RecordType == "" { + options.RecordType = "txt" + } + if options.PublicKey == "" { + return nil, E.New("public key is required") + } + + if options.Domain == "" { + return nil, E.New("domain is required") + } + if options.PreTestRecordType == "" { + options.PreTestRecordType = "a" + } + if options.PreTestDomain == "" { + options.PreTestDomain = "www.google.com" + } + + out := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDNSTT, tag, []string{N.NetworkTCP}, options.DialerOptions), + ctx: ctx, + logger: logger, + + candidateResolvers: resolvers, + resolvers: make([]dnstt.Resolver, 0), + tunnels: make([]*dnstt.Tunnel, 0), + + options: options, + } + + if options.SmartPool { + mgr, addr, err := multidns.StartLocal(multidns.Options{}) + if err != nil { + return nil, err + } + out.mdMgr = mgr + out.resolvers = []dnstt.Resolver{{ResolverType: dnstt.ResolverTypeUDP, ResolverAddr: addr}} + out.tunnels = []*dnstt.Tunnel{nil} + logger.InfoContext(ctx, "dnstt smart_pool listening on ", addr) + } + + return out, nil + +} + +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + if !h.IsReady() { + return nil, E.New("outbound is not started") + } + if h.options.SingleResolver { + return h.OpenStreamSingleResolver(ctx) + } + return h.OpenStream(ctx) +} +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + if !h.IsReady() { + return nil, E.New("outbound is not started") + } + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() + metadata.Destination = destination + if h.uotClient != nil { + h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) + return h.uotClient.ListenPacket(ctx, destination) + } + return nil, E.New("UoT is not enabled for this outbound") +} + +func (c *Outbound) IsReady() bool { + return c.started > 0 +} +func (w *Outbound) DisplayType() string { + str := C.ProxyDisplayName(w.Type()) + if w.started == 0 { + str += " ⚠️ Connecting..." + } else if w.started < 0 { + str += " ❌ Failed!" + } else { + str += fmt.Sprint(" ✔️ ", len(w.resolvers), " resolvers") + } + return str +} + +func (c *Outbound) Close() error { + for _, t := range c.tunnels { + if t != nil { + t.Close() + } + } + if c.mdMgr != nil { + _ = c.mdMgr.Close() + } + return nil +} diff --git a/protocol/hiddify/dnstt/resolvers_by_country.json b/protocol/hiddify/dnstt/resolvers_by_country.json new file mode 100644 index 0000000000..6c051612d3 --- /dev/null +++ b/protocol/hiddify/dnstt/resolvers_by_country.json @@ -0,0 +1,58696 @@ +{ + "": [ + "1.1.1.1", + "1.0.0.1", + "208.67.222.222", + "9.9.9.9", + "1.1.1.2", + "1.0.0.2", + "1.1.1.3", + "1.0.0.3", + "76.76.20.0", + "192.168.1.1", + "127.0.0.1" + ], + "AE": [ + "94.183.150.73", + "94.183.150.75", + "94.183.150.115", + "94.183.150.229", + "94.183.162.195", + "94.183.164.37", + "94.183.164.49", + "94.183.172.174", + "94.183.172.182", + "94.183.173.6", + "94.183.175.157", + "185.215.234.253", + "194.146.239.49", + "194.146.239.72", + "194.146.239.73", + "217.60.255.67", + "217.60.255.231", + "217.60.255.233", + "217.60.255.234", + "217.60.255.235", + "217.60.255.236", + "217.60.255.237", + "217.60.255.238", + "94.183.150.71", + "94.183.150.80", + "94.183.150.85", + "94.183.150.86", + "94.183.150.122", + "94.183.162.8", + "94.183.172.3", + "94.183.172.103", + "94.183.172.253", + "94.183.175.24", + "94.183.175.25", + "138.128.224.53", + "138.128.225.53", + "185.215.235.253", + "194.146.239.28", + "194.146.239.32", + "194.146.239.35", + "194.146.239.39", + "194.146.239.40", + "194.146.239.41", + "194.146.239.44", + "194.146.239.57", + "194.146.239.58", + "194.146.239.60", + "194.146.239.65", + "194.146.239.68", + "194.146.239.70", + "194.146.239.77", + "194.146.239.78", + "194.146.239.79", + "194.146.239.111", + "194.146.239.124", + "194.146.239.154", + "194.146.239.155", + "194.146.239.165", + "194.146.239.171", + "194.146.239.173", + "194.146.239.178", + "194.146.239.194", + "194.146.239.197", + "194.146.239.200", + "217.60.15.187", + "217.60.236.60", + "80.244.11.2", + "94.183.172.67" + ], + "AF": [ + "45.65.59.201", + "58.147.146.146", + "103.46.211.146", + "103.53.18.115", + "103.53.19.201", + "103.53.25.216", + "103.53.27.90", + "103.53.27.208", + "103.213.107.241", + "116.204.242.61", + "116.204.242.63", + "149.54.11.98", + "149.54.64.118", + "160.187.18.5", + "165.101.26.38", + "180.94.70.74", + "180.94.91.170", + "45.65.59.252" + ], + "AU": [ + "94.103.125.157", + "94.103.125.158", + "45.86.87.148", + "45.86.87.186", + "45.86.87.227", + "87.236.166.3", + "188.253.2.67", + "188.253.2.115", + "188.253.2.147", + "188.253.2.210", + "188.253.2.241", + "188.253.2.250", + "188.253.3.136", + "188.253.3.185", + "45.86.87.11", + "45.86.87.150", + "45.86.87.242", + "87.236.166.137", + "185.129.111.248", + "188.253.2.6", + "188.253.2.16", + "188.253.2.19", + "188.253.2.22", + "188.253.2.24", + "188.253.2.25", + "188.253.2.30", + "188.253.2.31", + "188.253.2.33", + "188.253.2.37", + "188.253.2.38", + "188.253.2.42", + "188.253.2.50", + "188.253.2.52", + "188.253.2.53", + "188.253.2.56", + "188.253.2.57", + "188.253.2.58", + "188.253.2.63", + "188.253.2.64", + "188.253.2.65", + "188.253.2.68", + "188.253.2.70", + "188.253.2.76", + "188.253.2.79", + "188.253.2.85", + "188.253.2.88", + "188.253.2.92", + "188.253.2.95", + "188.253.2.98", + "188.253.2.99", + "188.253.2.112", + "188.253.2.120", + "188.253.2.122", + "188.253.2.123", + "188.253.2.124", + "188.253.2.125", + "188.253.2.136", + "188.253.2.138", + "188.253.2.146", + "188.253.2.150", + "188.253.2.156", + "188.253.2.165", + "188.253.2.166", + "188.253.2.171", + "188.253.2.174", + "188.253.2.180", + "188.253.2.184", + "188.253.2.186", + "188.253.2.188", + "188.253.2.189", + "188.253.2.191", + "188.253.2.198", + "188.253.2.200", + "188.253.2.206", + "188.253.2.207", + "188.253.2.211", + "188.253.2.214", + "188.253.2.220", + "188.253.2.245", + "188.253.2.248", + "188.253.2.249", + "188.253.2.251", + "188.253.3.7", + "188.253.3.17", + "188.253.3.18", + "188.253.3.34", + "188.253.3.35", + "188.253.3.37", + "188.253.3.38", + "188.253.3.39", + "188.253.3.43", + "188.253.3.50", + "188.253.3.54", + "188.253.3.59", + "188.253.3.60", + "188.253.3.61", + "188.253.3.69", + "188.253.3.74", + "188.253.3.77", + "188.253.3.78", + "188.253.3.79", + "188.253.3.89", + "188.253.3.104", + "188.253.3.124", + "188.253.3.125", + "188.253.3.127", + "188.253.3.130", + "188.253.3.137", + "188.253.3.148", + "188.253.3.149", + "188.253.3.188", + "188.253.3.194", + "188.253.3.195", + "188.253.3.196", + "188.253.3.197", + "188.253.3.198", + "188.253.3.226", + "188.253.3.250" + ], + "BF": [ + "102.23.5.166", + "102.23.7.83" + ], + "BG": [ + "185.82.216.236" + ], + "BR": [ + "167.234.255.2" + ], + "CA": [ + "76.76.2.0", + "76.76.10.0" + ], + "CH": [ + "89.32.250.1", + "89.32.250.6", + "89.32.250.33", + "89.32.250.39", + "89.32.250.65", + "89.32.250.77", + "89.32.250.129", + "89.32.250.161", + "89.32.250.177", + "89.32.250.193", + "89.32.250.223", + "89.32.250.7", + "89.32.250.14", + "89.32.250.18", + "89.32.250.20", + "89.32.250.21", + "89.32.250.22", + "89.32.250.26", + "89.32.250.28", + "89.32.250.45", + "89.32.250.51", + "89.32.250.52", + "89.32.250.58", + "89.32.250.60", + "89.32.250.66", + "89.32.250.68", + "89.32.250.69", + "89.32.250.71", + "89.32.250.74", + "89.32.250.75", + "89.32.250.79", + "89.32.250.82", + "89.32.250.104", + "89.32.250.109", + "89.32.250.117", + "89.32.250.118", + "89.32.250.156", + "89.32.250.162", + "89.32.250.163", + "89.32.250.164", + "89.32.250.165", + "89.32.250.166", + "89.32.250.167", + "89.32.250.168", + "89.32.250.169", + "89.32.250.170", + "89.32.250.171", + "89.32.250.172", + "89.32.250.173", + "89.32.250.174", + "89.32.250.178", + "89.32.250.179", + "89.32.250.180", + "89.32.250.182", + "89.32.250.184", + "89.32.250.185", + "89.32.250.190", + "89.32.250.202", + "89.32.250.204", + "89.32.250.207", + "89.32.250.209", + "89.32.250.211", + "89.32.250.214", + "89.32.250.215", + "89.32.250.219", + "89.32.250.222", + "89.32.250.225", + "89.32.250.226", + "89.32.250.227", + "89.32.250.228", + "89.32.250.232", + "89.32.250.234", + "89.32.250.236", + "89.32.250.239", + "89.32.250.240", + "89.32.250.246", + "89.32.250.248", + "89.32.250.253", + "89.32.250.254" + ], + "CN": [ + "223.5.5.5", + "223.6.6.6", + "180.76.76.76", + "119.29.29.29" + ], + "CY": [ + "94.140.14.14", + "94.140.15.15", + "94.140.14.15", + "94.140.15.16", + "94.140.14.140", + "94.140.14.141" + ], + "DE": [ + "84.200.69.80", + "84.200.70.40", + "168.119.88.234" + ], + "DK": [ + "194.255.152.10", + "194.255.62.80", + "91.247.174.1" + ], + "ES": [ + "109.69.8.51" + ], + "FI": [ + "95.216.224.204", + "95.216.224.214", + "95.216.224.227", + "95.216.224.232", + "95.216.224.245" + ], + "FR": [ + "163.172.141.219" + ], + "GB": [ + "82.102.14.39", + "82.102.14.248", + "89.34.96.30", + "89.34.96.77", + "89.34.96.86", + "89.34.96.87", + "89.34.96.88", + "89.34.96.89", + "89.34.96.90", + "89.34.96.91", + "89.34.96.92", + "89.34.96.93", + "89.34.96.94", + "89.34.96.95", + "89.34.96.100", + "89.34.96.101", + "89.34.96.102", + "89.34.96.103", + "89.34.96.104", + "89.34.96.105", + "89.34.96.106", + "89.34.96.117", + "89.34.96.146", + "89.34.97.70", + "193.107.44.16", + "193.107.44.19", + "193.107.44.25", + "193.107.44.61", + "89.34.96.78", + "82.102.14.4", + "82.102.14.35", + "82.102.14.50", + "82.102.15.90", + "82.102.15.91", + "82.102.15.92", + "82.102.15.93", + "82.102.15.94", + "82.102.15.210", + "89.34.96.13", + "89.34.96.110", + "89.34.96.111", + "89.34.96.112", + "89.34.96.131", + "89.34.96.134", + "89.34.96.142", + "89.34.96.145", + "89.34.96.148", + "89.34.97.21" + ], + "GH": [ + "102.22.27.125", + "102.22.31.74" + ], + "IR": [ + "185.181.182.209", + "178.22.122.100", + "185.51.200.2", + "78.157.42.100", + "78.157.42.101", + "217.218.26.77", + "217.218.26.78", + "185.55.226.26", + "185.55.225.25", + "2.144.4.108", + "2.144.6.246", + "2.144.6.65", + "2.144.4.118", + "2.144.7.149", + "2.144.20.74", + "2.144.20.109", + "2.144.20.202", + "2.184.239.88", + "2.184.239.156", + "5.160.12.250", + "46.245.90.90", + "82.99.213.58", + "82.99.213.59", + "82.99.213.60", + "82.99.213.61", + "82.99.214.108", + "91.92.208.152", + "94.183.52.42", + "185.49.84.2", + "185.164.74.183", + "2.144.4.190", + "2.144.5.46", + "2.144.5.118", + "2.144.20.86", + "2.144.22.69", + "2.144.23.51", + "2.144.23.148", + "2.144.235.30", + "176.65.231.61", + "185.129.118.121", + "193.228.136.129", + "5.202.255.21", + "5.198.178.125", + "91.106.67.82", + "37.202.225.169", + "45.156.185.200", + "45.159.150.51", + "37.202.225.166", + "37.235.17.243", + "45.90.74.47", + "45.92.95.140", + "45.138.132.115", + "46.34.165.142", + "37.235.17.247", + "37.255.138.19", + "45.9.255.21", + "45.9.255.22", + "45.92.93.203", + "45.149.77.242", + "45.159.150.210", + "37.202.225.236", + "45.149.79.172", + "37.202.225.168", + "37.202.237.245", + "37.235.17.245", + "37.255.219.35", + "45.89.139.14", + "45.129.38.78", + "45.139.11.210", + "45.149.77.44", + "45.149.79.226", + "45.156.185.106", + "45.159.150.50", + "46.34.162.68", + "46.34.163.38", + "46.34.165.238", + "37.202.186.74", + "45.142.191.135", + "45.142.191.136", + "46.167.142.120", + "37.202.155.171", + "45.129.39.7", + "45.129.39.4", + "45.129.39.3", + "45.9.145.10", + "45.129.39.2", + "46.38.138.173", + "46.100.12.186", + "37.255.231.201", + "45.94.214.12", + "46.100.13.158", + "46.100.5.135", + "46.148.41.105", + "46.100.94.168", + "46.100.95.195", + "46.100.11.49", + "37.202.156.90", + "45.142.191.5", + "46.100.62.206", + "37.202.237.244", + "46.100.52.106", + "37.202.180.163", + "31.214.251.4", + "31.214.251.31", + "37.152.181.98", + "37.191.85.6", + "31.7.133.3", + "31.7.133.2", + "31.14.117.209", + "31.214.174.104", + "31.214.174.105", + "31.214.251.33", + "31.214.251.36", + "31.214.251.34", + "31.214.251.47", + "31.214.251.50", + "31.14.117.201", + "31.14.117.208", + "31.25.91.67", + "31.214.168.234", + "31.214.170.66", + "31.214.170.254", + "31.214.174.102", + "31.214.174.103", + "31.214.174.113", + "31.214.251.29", + "31.214.251.39", + "31.214.251.44", + "31.214.251.45", + "31.214.251.53", + "31.214.251.55", + "31.214.251.59", + "31.214.251.58", + "37.32.13.229", + "37.114.204.82", + "37.156.144.92", + "31.47.33.58", + "31.214.168.82", + "37.191.95.34", + "31.214.174.106", + "37.191.95.45", + "31.47.46.77", + "31.47.46.78", + "37.156.31.94", + "31.7.70.68", + "37.148.82.26", + "37.63.247.9", + "31.130.181.233", + "37.148.85.9", + "37.148.57.40", + "37.156.144.69", + "37.148.51.30", + "193.228.90.34", + "185.140.58.2", + "89.45.89.250", + "185.165.30.119", + "5.233.205.183", + "2.144.3.168", + "2.144.3.169", + "2.144.3.170", + "2.144.3.171", + "2.144.3.172", + "2.144.3.173", + "2.144.4.8", + "2.144.4.62", + "2.144.4.217", + "2.144.5.81", + "2.144.5.181", + "2.144.5.198", + "2.144.6.46", + "2.144.7.26", + "2.144.7.218", + "2.144.7.238", + "2.144.20.5", + "2.144.20.45", + "2.144.20.224", + "2.144.21.68", + "2.144.21.92", + "2.144.21.255", + "2.144.22.135", + "2.144.198.225", + "2.144.231.18", + "2.144.233.119", + "2.144.245.252", + "2.144.247.168", + "2.176.71.248", + "2.176.144.65", + "2.176.160.155", + "2.176.161.158", + "2.176.163.232", + "2.176.195.20", + "2.176.195.71", + "2.176.196.108", + "2.176.203.68", + "2.176.205.191", + "2.176.207.244", + "2.176.209.116", + "2.176.211.93", + "2.176.219.184", + "2.176.219.209", + "2.176.220.2", + "2.176.231.32", + "2.176.232.109", + "2.176.235.135", + "2.176.240.121", + "2.176.246.138", + "2.176.247.23", + "2.176.250.23", + "2.176.254.199", + "2.176.255.78", + "2.177.7.74", + "2.177.11.130", + "2.177.14.161", + "2.177.24.181", + "2.177.40.63", + "2.177.42.167", + "2.177.42.212", + "2.177.43.58", + "2.177.48.90", + "2.177.50.76", + "2.177.54.179", + "2.177.58.132", + "2.177.58.155", + "2.177.69.242", + "2.177.73.160", + "2.177.73.244", + "2.177.78.104", + "2.177.80.237", + "2.177.85.200", + "2.177.96.175", + "2.177.99.77", + "2.177.109.173", + "2.177.113.28", + "2.177.117.230", + "2.177.118.189", + "2.177.120.14", + "2.177.121.187", + "2.177.126.254", + "2.177.129.176", + "2.177.133.217", + "2.177.135.111", + "2.177.139.223", + "2.177.140.35", + "2.177.152.158", + "2.177.153.109", + "2.177.155.185", + "2.177.155.224", + "2.177.156.192", + "2.177.158.73", + "2.177.158.225", + "2.177.160.55", + "2.177.164.229", + "2.177.167.83", + "2.177.167.170", + "2.177.173.66", + "2.177.173.87", + "2.177.173.176", + "2.177.175.244", + "2.177.178.196", + "2.177.183.65", + "2.177.184.11", + "2.177.187.16", + "2.177.188.230", + "2.177.189.84", + "2.177.190.2", + "2.177.192.190", + "2.177.192.231", + "2.177.195.151", + "2.177.196.211", + "2.177.197.243", + "2.177.198.2", + "2.177.198.159", + "2.177.204.158", + "2.177.204.186", + "2.177.207.115", + "2.177.213.224", + "2.177.218.47", + "2.177.220.95", + "2.177.224.244", + "2.177.228.67", + "2.177.230.80", + "2.177.237.182", + "2.177.238.79", + "2.177.239.150", + "2.177.240.88", + "2.177.242.177", + "2.177.244.70", + "2.177.245.77", + "2.177.253.11", + "2.177.254.249", + "2.177.255.131", + "2.178.0.24", + "2.178.3.70", + "2.178.4.229", + "2.178.11.34", + "2.178.11.72", + "2.178.11.159", + "2.178.11.241", + "2.178.15.78", + "2.178.15.130", + "2.178.22.53", + "2.178.22.146", + "2.178.26.116", + "2.178.26.206", + "2.178.33.222", + "2.178.36.242", + "2.178.46.42", + "2.178.49.12", + "2.178.53.205", + "2.178.61.247", + "2.178.63.103", + "2.178.66.121", + "2.178.66.138", + "2.178.68.58", + "2.178.85.219", + "2.178.98.210", + "2.178.99.108", + "2.178.104.149", + "2.178.105.198", + "2.178.106.212", + "2.178.107.246", + "2.178.111.223", + "2.178.111.228", + "2.178.114.218", + "2.178.126.27", + "2.178.139.88", + "2.178.160.42", + "2.178.173.175", + "2.178.206.242", + "2.178.212.180", + "2.178.218.239", + "2.178.225.41", + "2.178.225.46", + "2.178.240.181", + "2.178.244.156", + "2.178.245.219", + "2.179.17.46", + "2.179.73.249", + "2.179.73.252", + "2.179.164.1", + "2.179.164.17", + "2.179.164.92", + "2.179.164.245", + "2.179.165.2", + "2.179.165.108", + "2.179.166.51", + "2.179.166.119", + "2.179.186.165", + "2.179.186.244", + "2.179.194.7", + "2.179.223.63", + "2.179.245.7", + "2.180.0.27", + "2.180.1.139", + "2.180.1.157", + "2.180.2.5", + "2.180.2.12", + "2.180.2.71", + "2.180.2.175", + "2.180.2.188", + "2.180.3.42", + "2.180.7.125", + "2.180.7.222", + "2.180.7.223", + "2.180.8.148", + "2.180.10.251", + "2.180.17.150", + "2.180.17.221", + "2.180.18.173", + "2.180.18.229", + "2.180.19.225", + "2.180.25.216", + "2.180.26.219", + "2.180.29.30", + "2.180.30.57", + "2.180.30.220", + "2.180.34.26", + "2.180.34.44", + "2.180.35.82", + "2.180.35.201", + "2.180.36.215", + "2.180.39.20", + "2.180.39.22", + "2.180.39.66", + "2.180.39.68", + "2.180.40.1", + "2.180.40.107", + "2.180.42.106", + "2.180.47.240", + "2.180.140.252", + "2.180.142.22", + "2.180.145.231", + "2.180.189.202", + "2.180.201.34", + "2.180.215.30", + "2.181.30.33", + "2.181.30.174", + "2.181.30.231", + "2.181.34.93", + "2.181.80.30", + "2.181.87.86", + "2.181.105.236", + "2.181.129.197", + "2.181.195.225", + "2.181.222.226", + "2.181.234.50", + "2.181.235.121", + "2.181.247.240", + "2.182.99.67", + "2.182.109.129", + "2.182.113.73", + "2.182.128.170", + "2.182.152.99", + "2.182.154.64", + "2.182.177.35", + "2.182.189.113", + "2.182.190.52", + "2.182.194.1", + "2.182.195.57", + "2.182.213.73", + "2.182.214.11", + "2.182.214.126", + "2.182.240.134", + "2.182.242.139", + "2.182.250.30", + "2.182.250.161", + "2.182.253.245", + "2.182.253.246", + "2.182.253.247", + "2.183.8.180", + "2.183.9.108", + "2.183.9.110", + "2.183.32.15", + "2.183.32.109", + "2.183.32.232", + "2.183.95.185", + "2.183.230.24", + "2.184.30.252", + "2.184.48.1", + "2.184.49.134", + "2.184.50.11", + "2.184.70.68", + "2.184.90.186", + "2.184.112.150", + "2.184.155.75", + "2.184.236.106", + "2.184.236.142", + "2.144.2.8", + "2.144.2.44", + "2.144.2.154", + "2.144.2.247", + "2.144.4.30", + "2.144.4.154", + "2.144.4.254", + "2.144.5.135", + "2.144.5.186", + "2.144.5.213", + "2.144.5.245", + "2.144.6.10", + "2.144.6.48", + "2.144.6.59", + "2.144.6.112", + "2.144.6.117", + "2.144.6.207", + "2.144.7.9", + "2.144.7.68", + "2.144.7.173", + "2.144.13.3", + "2.144.13.6", + "2.144.14.3", + "2.144.20.143", + "2.144.20.147", + "2.144.20.183", + "2.144.20.212", + "2.144.21.62", + "2.144.21.115", + "2.144.21.158", + "2.144.21.219", + "2.144.22.71", + "2.144.22.101", + "2.144.22.157", + "2.144.22.188", + "2.144.22.255", + "2.144.23.53", + "2.144.23.191", + "2.144.23.215", + "2.144.23.228", + "2.144.24.5", + "2.144.129.104", + "2.144.129.133", + "2.144.129.158", + "2.144.129.253", + "2.144.198.195", + "2.144.198.207", + "2.144.198.234", + "2.144.198.236", + "2.144.198.241", + "2.144.198.242", + "2.144.198.247", + "2.144.230.61", + "2.144.230.211", + "2.144.231.46", + "2.144.231.53", + "2.144.231.66", + "2.144.231.70", + "2.144.231.71", + "2.144.231.102", + "2.144.231.135", + "2.144.231.136", + "2.144.231.137", + "2.144.231.142", + "2.144.231.144", + "2.144.231.145", + "2.144.231.155", + "2.144.231.160", + "2.144.231.161", + "2.144.231.162", + "2.144.231.169", + "2.144.231.170", + "2.144.231.171", + "2.144.231.173", + "2.144.231.175", + "2.144.231.180", + "2.144.231.182", + "2.144.231.189", + "2.144.231.192", + "2.144.231.193", + "2.144.231.195", + "2.144.231.201", + "2.144.231.202", + "2.144.231.209", + "2.144.231.214", + "2.144.231.215", + "2.144.231.218", + "2.144.231.223", + "2.144.231.225", + "2.144.231.228", + "2.144.231.233", + "2.144.231.235", + "2.144.231.239", + "2.144.231.243", + "2.144.231.245", + "2.144.231.247", + "2.144.231.248", + "2.144.231.252", + "2.144.232.8", + "2.144.232.15", + "2.144.232.24", + "2.144.232.29", + "2.144.232.33", + "2.144.232.35", + "2.144.232.36", + "2.144.232.38", + "2.144.232.49", + "2.144.232.50", + "2.144.232.51", + "2.144.232.56", + "2.144.232.59", + "2.144.232.66", + "2.144.232.68", + "2.144.232.69", + "2.144.232.78", + "2.144.232.80", + "2.144.232.83", + "2.144.232.89", + "2.144.232.90", + "2.144.232.99", + "2.144.232.100", + "2.144.232.104", + "2.144.232.108", + "2.144.232.111", + "2.144.232.112", + "2.144.232.115", + "2.144.232.120", + "2.144.232.123", + "2.144.232.125", + "2.144.232.126", + "2.144.232.127", + "2.144.232.129", + "2.144.232.130", + "2.144.232.131", + "2.144.232.134", + "2.144.232.136", + "2.144.232.141", + "2.144.232.147", + "2.144.232.148", + "2.144.232.154", + "2.144.232.156", + "2.144.232.161", + "2.144.232.162", + "2.144.232.163", + "2.144.232.164", + "2.144.232.165", + "2.144.232.169", + "2.144.232.171", + "2.144.232.173", + "2.144.232.177", + "2.144.232.180", + "2.144.232.181", + "2.144.232.184", + "2.144.232.189", + "2.144.232.191", + "2.144.232.192", + "2.144.232.196", + "2.144.232.203", + "2.144.232.205", + "2.144.232.207", + "2.144.232.210", + "2.144.232.211", + "2.144.232.214", + "2.144.232.217", + "2.144.232.219", + "2.144.232.224", + "2.144.233.26", + "2.144.233.30", + "2.144.233.40", + "2.144.233.86", + "2.144.233.148", + "2.144.233.171", + "2.144.233.235", + "2.144.234.42", + "2.144.234.53", + "2.144.234.66", + "2.144.234.75", + "2.144.234.92", + "2.144.234.97", + "2.144.234.100", + "2.144.234.101", + "2.144.234.102", + "2.144.234.104", + "2.144.234.117", + "2.144.234.123", + "2.144.234.129", + "2.144.234.130", + "2.144.234.133", + "2.144.234.136", + "2.144.234.144", + "2.144.234.145", + "2.144.234.146", + "2.144.234.151", + "2.144.234.152", + "2.144.234.155", + "2.144.234.157", + "2.144.234.209", + "2.144.234.211", + "2.144.234.213", + "2.144.234.214", + "2.144.234.219", + "2.144.234.222", + "2.144.234.225", + "2.144.234.226", + "2.144.234.228", + "2.144.234.231", + "2.144.234.235", + "2.144.234.237", + "2.144.234.241", + "2.144.234.245", + "2.144.234.247", + "2.144.234.248", + "2.144.234.252", + "2.144.235.16", + "2.144.235.25", + "2.144.235.35", + "2.144.235.39", + "2.144.235.161", + "2.144.235.197", + "2.144.235.217", + "2.144.235.219", + "2.144.236.205", + "2.144.236.245", + "2.144.242.34", + "2.144.242.62", + "2.144.242.77", + "2.144.242.84", + "2.144.242.95", + "2.144.242.123", + "2.144.242.130", + "2.144.242.148", + "2.144.242.199", + "2.144.242.225", + "2.144.243.33", + "2.144.243.35", + "2.144.243.66", + "2.144.243.76", + "2.144.243.86", + "2.144.243.92", + "2.144.243.108", + "2.144.243.109", + "2.144.243.120", + "2.144.243.160", + "2.144.243.161", + "2.144.243.162", + "2.144.243.167", + "2.144.243.191", + "2.144.243.192", + "2.144.243.200", + "2.144.244.18", + "2.144.244.27", + "2.144.244.29", + "2.144.244.30", + "2.144.244.31", + "2.144.244.32", + "2.144.244.34", + "2.144.244.35", + "2.144.244.56", + "2.144.244.60", + "2.144.244.66", + "2.144.244.68", + "2.144.244.70", + "2.144.244.72", + "2.144.244.73", + "2.144.244.78", + "2.144.244.80", + "2.144.244.81", + "2.144.244.99", + "2.144.244.121", + "2.144.244.144", + "2.144.244.148", + "2.144.244.150", + "2.144.244.151", + "2.144.244.152", + "2.144.244.153", + "2.144.244.159", + "2.144.244.223", + "2.144.244.233", + "2.144.245.0", + "2.144.245.3", + "2.144.245.15", + "2.144.245.38", + "2.144.245.42", + "2.144.245.60", + "2.144.245.61", + "2.144.245.62", + "2.144.245.66", + "2.144.245.115", + "2.144.245.119", + "2.144.245.120", + "2.144.245.123", + "2.144.245.127", + "2.144.245.130", + "2.144.245.132", + "2.144.245.148", + "2.144.245.149", + "2.144.245.154", + "2.144.245.166", + "2.144.245.167", + "2.144.245.198", + "2.144.245.213", + "2.144.245.222", + "2.144.245.230", + "2.144.246.1", + "2.144.246.22", + "2.144.246.50", + "2.144.246.86", + "2.144.246.103", + "2.144.246.106", + "2.144.246.119", + "2.144.246.147", + "2.144.246.185", + "2.144.246.187", + "2.144.246.191", + "2.144.246.202", + "2.144.246.215", + "2.144.246.226", + "2.144.246.243", + "2.144.247.14", + "2.144.247.21", + "2.144.247.29", + "2.144.247.30", + "2.144.247.34", + "2.144.247.35", + "2.144.247.42", + "2.144.247.45", + "2.144.247.47", + "2.144.247.63", + "2.144.247.66", + "2.144.247.87", + "2.144.247.93", + "2.144.247.97", + "2.144.247.118", + "2.144.247.119", + "2.144.247.121", + "2.144.247.123", + "2.144.247.137", + "2.144.247.139", + "2.144.247.140", + "2.144.247.150", + "2.144.247.155", + "2.144.247.159", + "2.144.247.161", + "2.144.247.165", + "2.144.247.169", + "2.144.247.172", + "2.144.247.176", + "2.144.247.182", + "2.144.247.185", + "2.144.247.186", + "2.144.247.190", + "2.144.247.203", + "2.144.247.211", + "2.144.247.212", + "2.144.247.216", + "2.144.247.220", + "2.144.247.224", + "2.144.247.230", + "2.144.247.232", + "2.144.247.249", + "2.144.248.188", + "2.144.248.199", + "2.144.248.200", + "2.144.248.203", + "2.144.248.206", + "2.144.248.213", + "2.144.248.214", + "2.144.248.219", + "2.144.248.220", + "2.144.248.225", + "2.144.248.231", + "2.144.248.240", + "2.144.248.245", + "2.144.248.247", + "2.144.248.249", + "2.144.248.250", + "2.144.248.252", + "2.144.251.3", + "2.144.251.13", + "2.144.251.16", + "2.144.251.24", + "2.144.251.27", + "2.144.251.28", + "2.176.21.37", + "2.176.42.197", + "2.176.116.115", + "2.176.128.221", + "2.176.164.158", + "2.176.165.86", + "2.176.179.96", + "2.176.186.208", + "2.176.187.204", + "2.176.193.184", + "2.176.194.24", + "2.176.195.168", + "2.176.199.217", + "2.176.199.235", + "2.176.201.99", + "2.176.201.140", + "2.176.201.253", + "2.176.203.2", + "2.176.205.132", + "2.176.207.56", + "2.176.208.46", + "2.176.208.115", + "2.176.209.124", + "2.176.210.22", + "2.176.210.236", + "2.176.210.237", + "2.176.215.134", + "2.176.216.158", + "2.176.216.171", + "2.176.217.52", + "2.176.217.132", + "2.176.218.194", + "2.176.218.207", + "2.176.219.211", + "2.176.223.64", + "2.176.227.61", + "2.176.227.118", + "2.176.229.49", + "2.176.231.99", + "2.176.233.44", + "2.176.234.103", + "2.176.236.16", + "2.176.237.172", + "2.176.238.81", + "2.176.238.83", + "2.176.240.89", + "2.176.240.209", + "2.176.240.211", + "2.176.244.253", + "2.176.245.66", + "2.176.245.244", + "2.176.247.16", + "2.176.247.47", + "2.176.247.227", + "2.176.247.255", + "2.176.248.53", + "2.176.248.234", + "2.176.249.176", + "2.176.251.27", + "2.176.253.225", + "2.176.254.139", + "2.177.2.85", + "2.177.7.84", + "2.177.7.167", + "2.177.8.203", + "2.177.10.196", + "2.177.18.29", + "2.177.18.92", + "2.177.19.1", + "2.177.19.79", + "2.177.21.69", + "2.177.22.188", + "2.177.22.195", + "2.177.23.80", + "2.177.24.139", + "2.177.25.36", + "2.177.25.129", + "2.177.26.95", + "2.177.26.178", + "2.177.27.105", + "2.177.30.145", + "2.177.31.65", + "2.177.31.242", + "2.177.33.173", + "2.177.33.254", + "2.177.36.221", + "2.177.38.76", + "2.177.38.200", + "2.177.39.71", + "2.177.40.221", + "2.177.42.135", + "2.177.45.39", + "2.177.46.233", + "2.177.47.128", + "2.177.47.205", + "2.177.48.53", + "2.177.48.70", + "2.177.48.192", + "2.177.51.59", + "2.177.51.245", + "2.177.52.43", + "2.177.55.195", + "2.177.55.198", + "2.177.56.85", + "2.177.58.211", + "2.177.62.61", + "2.177.62.221", + "2.177.64.71", + "2.177.64.82", + "2.177.65.14", + "2.177.65.66", + "2.177.65.254", + "2.177.67.231", + "2.177.68.185", + "2.177.70.244", + "2.177.71.230", + "2.177.72.52", + "2.177.72.131", + "2.177.73.76", + "2.177.75.187", + "2.177.75.194", + "2.177.76.190", + "2.177.77.190", + "2.177.78.6", + "2.177.78.148", + "2.177.81.249", + "2.177.82.206", + "2.177.84.66", + "2.177.87.45", + "2.177.88.165", + "2.177.90.31", + "2.177.92.10", + "2.177.95.83", + "2.177.97.83", + "2.177.98.42", + "2.177.98.52", + "2.177.98.214", + "2.177.99.87", + "2.177.100.151", + "2.177.101.8", + "2.177.102.12", + "2.177.102.121", + "2.177.102.148", + "2.177.103.111", + "2.177.107.148", + "2.177.108.101", + "2.177.108.169", + "2.177.109.68", + "2.177.109.178", + "2.177.112.192", + "2.177.113.92", + "2.177.113.104", + "2.177.113.173", + "2.177.114.42", + "2.177.114.204", + "2.177.120.54", + "2.177.121.153", + "2.177.122.60", + "2.177.123.165", + "2.177.123.175", + "2.177.123.252", + "2.177.124.28", + "2.177.130.168", + "2.177.130.178", + "2.177.134.5", + "2.177.134.191", + "2.177.135.165", + "2.177.136.126", + "2.177.137.84", + "2.177.137.129", + "2.177.140.135", + "2.177.142.153", + "2.177.143.48", + "2.177.147.178", + "2.177.150.223", + "2.177.151.90", + "2.177.154.37", + "2.177.154.43", + "2.177.154.63", + "2.177.155.21", + "2.177.155.108", + "2.177.156.85", + "2.177.156.95", + "2.177.158.139", + "2.177.160.149", + "2.177.161.136", + "2.177.161.194", + "2.177.162.138", + "2.177.163.42", + "2.177.165.215", + "2.177.167.30", + "2.177.167.187", + "2.177.170.148", + "2.177.174.202", + "2.177.179.197", + "2.177.179.238", + "2.177.181.250", + "2.177.183.104", + "2.177.184.244", + "2.177.186.48", + "2.177.188.213", + "2.177.189.81", + "2.177.193.94", + "2.177.193.107", + "2.177.193.205", + "2.177.194.248", + "2.177.195.2", + "2.177.195.160", + "2.177.196.150", + "2.177.200.2", + "2.177.200.150", + "2.177.201.168", + "2.177.202.103", + "2.177.203.198", + "2.177.205.238", + "2.177.205.254", + "2.177.209.120", + "2.177.210.254", + "2.177.212.105", + "2.177.213.171", + "2.177.214.20", + "2.177.217.23", + "2.177.218.172", + "2.177.219.102", + "2.177.220.44", + "2.177.221.239", + "2.177.222.246", + "2.177.223.37", + "2.177.223.74", + "2.177.223.126", + "2.177.225.121", + "2.177.230.85", + "2.177.230.124", + "2.177.236.183", + "2.177.238.11", + "2.177.240.0", + "2.177.240.113", + "2.177.241.158", + "2.177.242.81", + "2.177.245.73", + "2.177.250.182", + "2.177.250.254", + "2.177.253.111", + "2.177.254.11", + "2.178.1.137", + "2.178.1.221", + "2.178.2.232", + "2.178.6.39", + "2.178.8.123", + "2.178.10.182", + "2.178.11.80", + "2.178.19.80", + "2.178.20.196", + "2.178.23.207", + "2.178.27.216", + "2.178.29.125", + "2.178.36.176", + "2.178.39.162", + "2.178.40.0", + "2.178.42.135", + "2.178.43.167", + "2.178.43.177", + "2.178.50.89", + "2.178.52.212", + "2.178.54.116", + "2.178.56.133", + "2.178.56.180", + "2.178.57.65", + "2.178.57.197", + "2.178.58.161", + "2.178.60.178", + "2.178.61.124", + "2.178.64.134", + "2.178.67.184", + "2.178.71.229", + "2.178.74.58", + "2.178.75.120", + "2.178.77.245", + "2.178.78.82", + "2.178.79.27", + "2.178.83.179", + "2.178.83.239", + "2.178.84.153", + "2.178.85.165", + "2.178.85.191", + "2.178.86.146", + "2.178.88.122", + "2.178.88.151", + "2.178.88.219", + "2.178.91.16", + "2.178.93.55", + "2.178.94.143", + "2.178.97.123", + "2.178.98.188", + "2.178.98.249", + "2.178.99.41", + "2.178.100.217", + "2.178.102.112", + "2.178.105.194", + "2.178.107.38", + "2.178.114.76", + "2.178.115.196", + "2.178.118.67", + "2.178.118.154", + "2.178.120.208", + "2.178.123.127", + "2.178.124.114", + "2.178.124.191", + "2.178.125.58", + "2.178.127.45", + "2.178.128.156", + "2.178.128.208", + "2.178.129.67", + "2.178.129.134", + "2.178.129.248", + "2.178.133.61", + "2.178.133.89", + "2.178.133.230", + "2.178.134.72", + "2.178.134.128", + "2.178.135.43", + "2.178.135.46", + "2.178.135.80", + "2.178.135.231", + "2.178.137.200", + "2.178.138.95", + "2.178.138.140", + "2.178.139.21", + "2.178.140.86", + "2.178.141.40", + "2.178.141.97", + "2.178.142.75", + "2.178.142.175", + "2.178.142.191", + "2.178.142.222", + "2.178.143.8", + "2.178.143.50", + "2.178.143.150", + "2.178.143.156", + "2.178.144.235", + "2.178.145.153", + "2.178.146.150", + "2.178.146.217", + "2.178.147.40", + "2.178.148.180", + "2.178.149.81", + "2.178.149.116", + "2.178.149.214", + "2.178.150.61", + "2.178.150.148", + "2.178.150.213", + "2.178.151.104", + "2.178.153.98", + "2.178.153.123", + "2.178.154.100", + "2.178.155.46", + "2.178.155.61", + "2.178.155.244", + "2.178.156.98", + "2.178.156.131", + "2.178.157.73", + "2.178.157.199", + "2.178.157.252", + "2.178.158.3", + "2.178.158.22", + "2.178.159.106", + "2.178.160.20", + "2.178.160.151", + "2.178.161.18", + "2.178.162.119", + "2.178.163.197", + "2.178.165.65", + "2.178.165.89", + "2.178.165.104", + "2.178.165.248", + "2.178.166.36", + "2.178.167.34", + "2.178.167.128", + "2.178.168.229", + "2.178.169.84", + "2.178.169.108", + "2.178.170.11", + "2.178.170.64", + "2.178.170.156", + "2.178.171.32", + "2.178.171.118", + "2.178.172.168", + "2.178.176.141", + "2.178.177.7", + "2.178.178.85", + "2.178.179.51", + "2.178.179.82", + "2.178.179.238", + "2.178.180.124", + "2.178.181.78", + "2.178.183.12", + "2.178.183.91", + "2.178.185.82", + "2.178.186.143", + "2.178.186.167", + "2.178.188.86", + "2.178.189.245", + "2.178.190.217", + "2.178.192.138", + "2.178.193.233", + "2.178.194.16", + "2.178.196.53", + "2.178.198.161", + "2.178.198.179", + "2.178.199.33", + "2.178.199.252", + "2.178.200.98", + "2.178.200.141", + "2.178.200.181", + "2.178.202.80", + "2.178.202.167", + "2.178.203.61", + "2.178.203.80", + "2.178.205.233", + "2.178.206.148", + "2.178.207.46", + "2.178.207.157", + "2.178.208.131", + "2.178.209.140", + "2.178.212.135", + "2.178.212.192", + "2.178.212.233", + "2.178.213.105", + "2.178.213.159", + "2.178.214.118", + "2.178.215.152", + "2.178.216.16", + "2.178.216.38", + "2.178.217.48", + "2.178.217.189", + "2.178.217.239", + "2.178.218.242", + "2.178.219.107", + "2.178.219.155", + "2.178.221.5", + "2.178.222.124", + "2.178.223.189", + "2.178.224.90", + "2.178.225.229", + "2.178.227.158", + "2.178.228.40", + "2.178.228.151", + "2.178.228.180", + "2.178.229.11", + "2.178.229.163", + "2.178.230.231", + "2.178.230.236", + "2.178.231.100", + "2.178.231.105", + "2.178.233.18", + "2.178.233.48", + "2.178.235.155", + "2.178.235.186", + "2.178.236.33", + "2.178.237.75", + "2.178.237.225", + "2.178.238.36", + "2.178.240.189", + "2.178.241.66", + "2.178.241.67", + "2.178.241.112", + "2.178.242.140", + "2.178.243.42", + "2.178.243.90", + "2.178.244.9", + "2.178.244.60", + "2.178.245.80", + "2.178.246.23", + "2.178.247.0", + "2.178.247.52", + "2.178.248.71", + "2.178.250.16", + "2.178.250.220", + "2.178.251.234", + "2.178.253.22", + "2.178.253.98", + "2.178.253.156", + "2.178.255.244", + "2.179.12.162", + "2.179.14.81", + "2.179.15.62", + "2.179.15.121", + "2.179.25.28", + "2.179.25.190", + "2.179.26.186", + "2.179.26.196", + "2.179.27.169", + "2.179.27.182", + "2.179.27.201", + "2.179.28.221", + "2.179.40.201", + "2.179.41.31", + "2.179.41.78", + "2.179.41.207", + "2.179.42.76", + "2.179.42.172", + "2.179.42.237", + "2.179.43.57", + "2.179.44.136", + "2.179.45.126", + "2.179.46.67", + "2.179.46.124", + "2.179.46.191", + "2.179.47.35", + "2.179.47.160", + "2.179.60.25", + "2.179.60.59", + "2.179.60.71", + "2.179.60.164", + "2.179.60.168", + "2.179.60.198", + "2.179.60.221", + "2.179.64.18", + "2.179.64.30", + "2.179.64.63", + "2.179.64.96", + "2.179.64.108", + "2.179.66.33", + "2.179.67.52", + "2.179.67.104", + "2.179.70.18", + "2.179.70.48", + "2.179.72.74", + "2.179.72.78", + "2.179.72.80", + "2.179.73.171", + "2.179.76.79", + "2.179.78.50", + "2.179.80.40", + "2.179.86.249", + "2.179.87.38", + "2.179.87.48", + "2.179.87.243", + "2.179.91.5", + "2.179.93.32", + "2.179.93.72", + "2.179.93.76", + "2.179.93.79", + "2.179.93.104", + "2.179.95.129", + "2.179.95.226", + "2.179.100.135", + "2.179.108.1", + "2.179.109.5", + "2.179.109.135", + "2.179.111.167", + "2.179.113.188", + "2.179.114.132", + "2.179.114.231", + "2.179.117.246", + "2.179.118.31", + "2.179.118.97", + "2.179.118.204", + "2.179.125.52", + "2.179.125.209", + "2.179.127.48", + "2.179.162.88", + "2.179.162.194", + "2.179.163.131", + "2.179.164.6", + "2.179.164.32", + "2.179.164.69", + "2.179.164.79", + "2.179.164.90", + "2.179.164.130", + "2.179.164.157", + "2.179.164.243", + "2.179.165.56", + "2.179.165.80", + "2.179.165.87", + "2.179.165.152", + "2.179.165.203", + "2.179.165.229", + "2.179.166.17", + "2.179.166.57", + "2.179.166.58", + "2.179.166.121", + "2.179.166.159", + "2.179.166.211", + "2.179.167.14", + "2.179.167.53", + "2.179.167.60", + "2.179.167.73", + "2.179.167.75", + "2.179.167.92", + "2.179.167.110", + "2.179.167.133", + "2.179.167.134", + "2.179.167.168", + "2.179.167.221", + "2.179.167.249", + "2.179.176.209", + "2.179.176.248", + "2.179.176.249", + "2.179.176.250", + "2.179.176.251", + "2.179.176.253", + "2.179.178.33", + "2.179.178.113", + "2.179.179.118", + "2.179.179.158", + "2.179.179.234", + "2.179.179.242", + "2.179.179.250", + "2.179.184.38", + "2.179.186.44", + "2.179.186.101", + "2.179.186.171", + "2.179.186.184", + "2.179.186.202", + "2.179.186.213", + "2.179.187.23", + "2.179.187.34", + "2.179.189.112", + "2.179.189.124", + "2.179.189.141", + "2.179.193.59", + "2.179.194.89", + "2.179.194.186", + "2.179.194.203", + "2.179.194.208", + "2.179.194.244", + "2.179.194.251", + "2.179.209.128", + "2.179.209.207", + "2.179.210.133", + "2.179.210.171", + "2.179.210.254", + "2.179.211.199", + "2.179.212.157", + "2.179.213.183", + "2.179.215.93", + "2.179.216.109", + "2.179.216.197", + "2.179.219.18", + "2.179.220.45", + "2.179.223.163", + "2.179.223.218", + "2.179.235.134", + "2.180.0.49", + "2.180.0.52", + "2.180.0.55", + "2.180.0.64", + "2.180.0.65", + "2.180.0.83", + "2.180.0.116", + "2.180.0.150", + "2.180.0.173", + "2.180.0.210", + "2.180.1.43", + "2.180.1.73", + "2.180.1.88", + "2.180.1.94", + "2.180.1.159", + "2.180.1.168", + "2.180.1.243", + "2.180.2.36", + "2.180.2.72", + "2.180.2.107", + "2.180.2.116", + "2.180.2.154", + "2.180.3.38", + "2.180.3.197", + "2.180.3.252", + "2.180.4.19", + "2.180.4.44", + "2.180.4.54", + "2.180.4.236", + "2.180.4.250", + "2.180.5.28", + "2.180.5.88", + "2.180.5.140", + "2.180.5.161", + "2.180.5.243", + "2.180.6.34", + "2.180.6.105", + "2.180.6.140", + "2.180.6.144", + "2.180.6.146", + "2.180.7.39", + "2.180.7.67", + "2.180.7.153", + "2.180.7.157", + "2.180.7.199", + "2.180.7.200", + "2.180.7.227", + "2.180.8.37", + "2.180.8.78", + "2.180.8.149", + "2.180.8.174", + "2.180.8.186", + "2.180.8.193", + "2.180.8.207", + "2.180.9.92", + "2.180.9.135", + "2.180.9.152", + "2.180.10.158", + "2.180.10.204", + "2.180.10.205", + "2.180.10.218", + "2.180.10.225", + "2.180.11.45", + "2.180.11.59", + "2.180.11.94", + "2.180.11.100", + "2.180.11.102", + "2.180.11.130", + "2.180.11.158", + "2.180.11.162", + "2.180.12.11", + "2.180.12.28", + "2.180.12.41", + "2.180.12.45", + "2.180.12.68", + "2.180.12.106", + "2.180.12.114", + "2.180.12.146", + "2.180.12.178", + "2.180.12.210", + "2.180.13.32", + "2.180.13.98", + "2.180.13.108", + "2.180.13.110", + "2.180.13.127", + "2.180.15.18", + "2.180.15.31", + "2.180.15.108", + "2.180.15.134", + "2.180.15.175", + "2.180.16.1", + "2.180.16.19", + "2.180.16.42", + "2.180.16.135", + "2.180.16.196", + "2.180.16.219", + "2.180.17.32", + "2.180.17.123", + "2.180.17.136", + "2.180.17.198", + "2.180.17.219", + "2.180.18.2", + "2.180.18.84", + "2.180.18.89", + "2.180.18.91", + "2.180.18.116", + "2.180.18.126", + "2.180.18.131", + "2.180.18.226", + "2.180.18.231", + "2.180.18.241", + "2.180.19.18", + "2.180.19.47", + "2.180.19.60", + "2.180.19.172", + "2.180.19.214", + "2.180.19.221", + "2.180.19.228", + "2.180.19.231", + "2.180.20.27", + "2.180.20.63", + "2.180.21.17", + "2.180.21.69", + "2.180.21.90", + "2.180.21.144", + "2.180.21.185", + "2.180.21.234", + "2.180.21.241", + "2.180.22.26", + "2.180.22.70", + "2.180.22.81", + "2.180.22.119", + "2.180.23.91", + "2.180.23.128", + "2.180.23.143", + "2.180.23.188", + "2.180.23.221", + "2.180.24.58", + "2.180.24.69", + "2.180.24.254", + "2.180.25.17", + "2.180.25.47", + "2.180.25.52", + "2.180.25.73", + "2.180.25.195", + "2.180.25.242", + "2.180.26.40", + "2.180.26.157", + "2.180.27.80", + "2.180.27.143", + "2.180.28.104", + "2.180.28.125", + "2.180.28.153", + "2.180.28.163", + "2.180.28.173", + "2.180.28.192", + "2.180.28.193", + "2.180.29.48", + "2.180.29.70", + "2.180.29.133", + "2.180.29.185", + "2.180.29.204", + "2.180.29.213", + "2.180.29.238", + "2.180.30.56", + "2.180.30.209", + "2.180.30.218", + "2.180.31.61", + "2.180.31.62", + "2.180.31.110", + "2.180.31.224", + "2.180.32.26", + "2.180.32.61", + "2.180.32.72", + "2.180.32.166", + "2.180.32.198", + "2.180.33.61", + "2.180.33.119", + "2.180.33.127", + "2.180.33.128", + "2.180.33.132", + "2.180.33.168", + "2.180.35.13", + "2.180.35.16", + "2.180.35.35", + "2.180.35.116", + "2.180.35.169", + "2.180.35.250", + "2.180.36.105", + "2.180.36.147", + "2.180.36.173", + "2.180.36.174", + "2.180.36.191", + "2.180.36.228", + "2.180.37.29", + "2.180.37.54", + "2.180.37.67", + "2.180.37.143", + "2.180.37.151", + "2.180.37.161", + "2.180.37.248", + "2.180.39.32", + "2.180.39.33", + "2.180.39.34", + "2.180.39.35", + "2.180.39.36", + "2.180.39.37", + "2.180.39.38", + "2.180.39.82", + "2.180.39.83", + "2.180.40.78", + "2.180.40.85", + "2.180.40.96", + "2.180.41.6", + "2.180.41.18", + "2.180.41.249", + "2.180.42.1", + "2.180.42.50", + "2.180.42.54", + "2.180.42.78", + "2.180.42.95", + "2.180.42.109", + "2.180.42.137", + "2.180.42.170", + "2.180.42.218", + "2.180.42.228", + "2.180.43.13", + "2.180.43.84", + "2.180.43.147", + "2.180.43.186", + "2.180.43.189", + "2.180.43.198", + "2.180.44.1", + "2.180.44.2", + "2.180.44.39", + "2.180.44.86", + "2.180.44.137", + "2.180.44.151", + "2.180.44.173", + "2.180.44.180", + "2.180.44.200", + "2.180.44.203", + "2.180.47.142", + "2.180.47.164", + "2.180.47.254", + "2.180.130.61", + "2.180.131.14", + "2.180.132.126", + "2.180.133.193", + "2.180.135.8", + "2.180.135.143", + "2.180.138.56", + "2.180.138.237", + "2.180.139.63", + "2.180.141.92", + "2.180.146.226", + "2.180.147.108", + "2.180.148.113", + "2.180.150.154", + "2.180.151.218", + "2.180.151.249", + "2.180.154.235", + "2.180.155.106", + "2.180.155.252", + "2.180.156.36", + "2.180.159.21", + "2.180.161.42", + "2.180.162.97", + "2.180.163.157", + "2.180.166.27", + "2.180.167.58", + "2.180.167.63", + "2.180.167.137", + "2.180.169.124", + "2.180.170.132", + "2.180.171.158", + "2.180.172.39", + "2.180.172.198", + "2.180.174.226", + "2.180.175.1", + "2.180.175.255", + "2.180.177.79", + "2.180.178.149", + "2.180.178.154", + "2.180.180.221", + "2.180.186.98", + "2.180.188.123", + "2.180.197.167", + "2.180.198.23", + "2.180.198.235", + "2.180.199.134", + "2.180.199.177", + "2.180.208.78", + "2.180.210.6", + "2.180.211.96", + "2.180.215.214", + "2.180.216.77", + "2.180.216.116", + "2.180.219.96", + "2.180.223.7", + "2.180.228.75", + "2.180.230.225", + "2.180.236.114", + "2.180.238.232", + "2.180.242.52", + "2.180.243.51", + "2.180.243.243", + "2.180.248.212", + "2.180.249.74", + "2.180.251.146", + "2.180.252.125", + "2.181.0.37", + "2.181.0.51", + "2.181.0.54", + "2.181.0.73", + "2.181.0.234", + "2.181.1.100", + "2.181.1.245", + "2.181.30.24", + "2.181.30.85", + "2.181.30.88", + "2.181.30.198", + "2.181.30.234", + "2.181.31.25", + "2.181.31.55", + "2.181.31.69", + "2.181.31.104", + "2.181.31.150", + "2.181.31.221", + "2.181.34.28", + "2.181.34.34", + "2.181.34.55", + "2.181.34.74", + "2.181.34.255", + "2.181.35.31", + "2.181.35.51", + "2.181.35.65", + "2.181.35.210", + "2.181.56.31", + "2.181.58.89", + "2.181.61.201", + "2.181.61.208", + "2.181.62.159", + "2.181.78.26", + "2.181.78.32", + "2.181.78.219", + "2.181.80.96", + "2.181.81.4", + "2.181.81.34", + "2.181.81.155", + "2.181.82.22", + "2.181.82.53", + "2.181.82.105", + "2.181.82.191", + "2.181.83.13", + "2.181.84.235", + "2.181.85.80", + "2.181.85.206", + "2.181.86.11", + "2.181.86.241", + "2.181.87.182", + "2.181.87.219", + "2.181.88.14", + "2.181.89.144", + "2.181.91.16", + "2.181.92.32", + "2.181.92.82", + "2.181.92.254", + "2.181.93.125", + "2.181.93.146", + "2.181.93.233", + "2.181.94.124", + "2.181.96.98", + "2.181.96.159", + "2.181.98.32", + "2.181.98.176", + "2.181.99.194", + "2.181.100.35", + "2.181.100.163", + "2.181.101.98", + "2.181.101.172", + "2.181.102.30", + "2.181.102.75", + "2.181.102.98", + "2.181.102.196", + "2.181.102.208", + "2.181.103.72", + "2.181.104.177", + "2.181.104.254", + "2.181.105.60", + "2.181.106.197", + "2.181.107.74", + "2.181.108.16", + "2.181.108.190", + "2.181.109.58", + "2.181.109.106", + "2.181.110.156", + "2.181.110.241", + "2.181.129.23", + "2.181.195.99", + "2.181.195.113", + "2.181.198.66", + "2.181.201.158", + "2.181.202.229", + "2.181.204.140", + "2.181.206.33", + "2.181.206.133", + "2.181.208.45", + "2.181.210.150", + "2.181.211.7", + "2.181.213.41", + "2.181.213.83", + "2.181.215.172", + "2.181.216.44", + "2.181.216.54", + "2.181.216.225", + "2.181.217.76", + "2.181.217.170", + "2.181.221.50", + "2.181.222.216", + "2.181.222.253", + "2.181.224.62", + "2.181.225.138", + "2.181.225.203", + "2.181.225.237", + "2.181.226.194", + "2.181.227.140", + "2.181.227.172", + "2.181.227.213", + "2.181.228.24", + "2.181.228.113", + "2.181.228.194", + "2.181.229.49", + "2.181.229.236", + "2.181.230.167", + "2.181.231.150", + "2.181.232.216", + "2.181.234.74", + "2.181.234.129", + "2.181.234.135", + "2.181.234.140", + "2.181.234.194", + "2.181.234.213", + "2.181.234.222", + "2.181.235.126", + "2.181.236.18", + "2.181.236.228", + "2.181.237.88", + "2.181.237.120", + "2.181.238.72", + "2.181.239.75", + "2.181.239.206", + "2.181.239.207", + "2.181.240.16", + "2.181.240.143", + "2.181.240.226", + "2.181.241.99", + "2.181.241.197", + "2.181.242.59", + "2.181.243.143", + "2.181.244.40", + "2.181.245.241", + "2.181.246.95", + "2.181.249.188", + "2.181.251.145", + "2.181.254.53", + "2.181.254.87", + "2.181.254.127", + "2.181.255.171", + "2.181.255.222", + "2.182.40.14", + "2.182.40.59", + "2.182.40.76", + "2.182.43.6", + "2.182.43.187", + "2.182.44.240", + "2.182.46.202", + "2.182.47.140", + "2.182.48.163", + "2.182.48.247", + "2.182.51.1", + "2.182.51.29", + "2.182.51.63", + "2.182.51.99", + "2.182.52.72", + "2.182.53.247", + "2.182.54.149", + "2.182.66.165", + "2.182.79.37", + "2.182.89.108", + "2.182.90.205", + "2.182.112.11", + "2.182.112.125", + "2.182.114.75", + "2.182.115.46", + "2.182.115.83", + "2.182.115.91", + "2.182.116.22", + "2.182.116.70", + "2.182.116.103", + "2.182.117.118", + "2.182.124.100", + "2.182.125.223", + "2.182.152.15", + "2.182.152.37", + "2.182.152.103", + "2.182.152.115", + "2.182.152.136", + "2.182.152.138", + "2.182.152.155", + "2.182.152.206", + "2.182.152.240", + "2.182.152.254", + "2.182.153.95", + "2.182.153.106", + "2.182.153.125", + "2.182.153.131", + "2.182.153.145", + "2.182.153.163", + "2.182.153.196", + "2.182.154.27", + "2.182.154.57", + "2.182.154.65", + "2.182.154.77", + "2.182.154.85", + "2.182.154.110", + "2.182.154.130", + "2.182.154.158", + "2.182.154.160", + "2.182.154.185", + "2.182.154.207", + "2.182.155.160", + "2.182.163.78", + "2.182.176.193", + "2.182.179.32", + "2.182.179.130", + "2.182.179.236", + "2.182.180.119", + "2.182.180.194", + "2.182.181.130", + "2.182.184.83", + "2.182.186.225", + "2.182.187.13", + "2.182.188.73", + "2.182.188.156", + "2.182.189.30", + "2.182.189.33", + "2.182.189.147", + "2.182.189.169", + "2.182.190.37", + "2.182.191.173", + "2.182.192.46", + "2.182.196.9", + "2.182.196.65", + "2.182.196.133", + "2.182.196.234", + "2.182.198.213", + "2.182.201.3", + "2.182.201.5", + "2.182.201.71", + "2.182.201.220", + "2.182.201.221", + "2.182.201.222", + "2.182.204.29", + "2.182.204.78", + "2.182.204.250", + "2.182.205.252", + "2.182.206.42", + "2.182.206.58", + "2.182.206.121", + "2.182.207.14", + "2.182.207.191", + "2.182.209.24", + "2.182.210.235", + "2.182.212.252", + "2.182.214.246", + "2.182.215.122", + "2.182.216.144", + "2.182.223.204", + "2.182.228.247", + "2.182.231.42", + "2.182.232.85", + "2.182.233.138", + "2.182.236.135", + "2.182.236.141", + "2.182.236.154", + "2.182.236.237", + "2.182.237.186", + "2.182.237.251", + "2.182.239.170", + "2.182.241.197", + "2.182.243.110", + "2.182.243.151", + "2.182.243.209", + "2.182.243.244", + "2.182.244.151", + "2.182.245.101", + "2.182.245.200", + "2.182.245.231", + "2.182.246.102", + "2.182.246.134", + "2.182.249.84", + "2.182.249.130", + "2.182.250.85", + "2.182.250.160", + "2.182.253.241", + "2.183.8.13", + "2.183.8.19", + "2.183.8.90", + "2.183.8.118", + "2.183.8.173", + "2.183.8.186", + "2.183.9.86", + "2.183.9.165", + "2.183.9.236", + "2.183.32.12", + "2.183.32.52", + "2.183.32.62", + "2.183.32.84", + "2.183.32.112", + "2.183.80.7", + "2.183.80.8", + "2.183.82.5", + "2.183.82.51", + "2.183.83.147", + "2.183.83.235", + "2.183.84.4", + "2.183.86.138", + "2.183.86.218", + "2.183.87.231", + "2.183.87.239", + "2.183.97.178", + "2.183.98.176", + "2.183.100.95", + "2.183.100.145", + "2.183.102.201", + "2.183.105.124", + "2.183.105.142", + "2.183.106.13", + "2.183.107.214", + "2.183.110.163", + "2.183.112.69", + "2.183.112.159", + "2.183.112.219", + "2.183.112.252", + "2.183.113.112", + "2.183.114.9", + "2.183.114.107", + "2.183.114.243", + "2.183.115.91", + "2.183.117.133", + "2.183.117.167", + "2.183.119.25", + "2.183.120.146", + "2.183.121.29", + "2.183.123.172", + "2.183.135.24", + "2.183.135.52", + "2.183.203.74", + "2.183.203.131", + "2.183.203.135", + "2.183.203.142", + "2.183.203.146", + "2.183.231.18", + "2.183.237.8", + "2.184.38.118", + "2.184.41.171", + "2.184.48.4", + "2.184.48.42", + "2.184.48.46", + "2.184.49.21", + "2.184.49.22", + "2.184.49.145", + "2.184.50.27", + "2.184.50.34", + "2.184.50.144", + "2.184.50.145", + "2.184.50.165", + "2.184.50.172", + "2.184.51.69", + "2.184.51.140", + "2.184.51.143", + "2.184.52.22", + "2.184.52.194", + "2.184.52.249", + "2.184.52.250", + "2.184.53.168", + "2.184.53.237", + "2.184.54.17", + "2.184.54.38", + "2.184.54.105", + "2.184.54.113", + "2.184.54.128", + "2.184.54.157", + "2.184.54.208", + "2.184.55.14", + "2.184.55.22", + "2.184.55.37", + "2.184.55.83", + "2.184.55.105", + "2.184.55.128", + "2.184.55.156", + "2.184.55.183", + "2.184.57.117", + "2.184.57.166", + "2.184.58.19", + "2.184.58.25", + "2.184.58.188", + "2.184.59.29", + "2.184.59.52", + "2.184.59.82", + "2.184.59.137", + "2.184.60.6", + "2.184.61.36", + "2.184.61.61", + "2.184.61.204", + "2.184.70.91", + "2.184.70.104", + "2.184.71.2", + "2.184.71.3", + "2.184.71.4", + "2.184.71.6", + "2.184.74.2", + "2.184.74.62", + "2.184.89.24", + "2.184.90.99", + "2.184.90.104", + "2.184.90.129", + "2.184.90.198", + "2.184.92.31", + "2.184.118.38", + "2.184.155.46", + "2.184.155.72", + "2.184.155.83", + "2.184.155.92", + "2.184.155.117", + "2.184.156.9", + "2.184.156.38", + "2.184.156.79", + "2.184.156.155", + "2.184.156.188", + "2.184.156.189", + "2.184.156.233", + "2.184.157.5", + "2.184.157.15", + "2.184.157.18", + "2.184.157.129", + "2.184.157.134", + "2.184.157.146", + "2.184.157.150", + "2.184.157.226", + "2.184.157.237", + "2.184.158.14", + "2.184.158.38", + "2.184.158.117", + "2.184.158.221", + "2.184.158.253", + "2.184.159.66", + "2.184.159.68", + "2.184.159.111", + "2.184.159.119", + "2.184.159.126", + "2.184.159.167", + "2.184.159.227", + "2.184.169.181", + "2.184.171.87", + "2.184.182.68", + "2.184.182.76", + "2.184.182.87", + "2.184.182.136", + "2.184.182.152", + "2.184.217.25", + "2.184.217.127", + "2.184.217.167", + "2.184.217.176", + "2.184.217.193", + "2.184.217.197", + "2.184.217.224", + "2.184.236.9", + "2.184.236.34", + "2.184.236.119", + "2.184.236.159", + "2.184.236.164", + "2.184.236.166", + "2.184.236.197", + "2.184.236.217", + "2.184.236.233", + "2.184.236.240", + "2.184.237.70", + "2.184.237.208", + "2.184.238.17", + "2.184.238.19", + "2.184.238.113", + "2.184.238.123", + "2.184.238.214", + "2.184.239.89", + "2.184.239.236", + "2.185.17.15", + "2.185.17.144", + "2.185.17.206", + "2.185.17.242", + "2.185.18.223", + "2.185.96.234", + "2.185.97.7", + "2.185.97.61", + "2.185.97.73", + "2.185.98.143", + "2.185.98.165", + "2.185.100.127", + "2.185.100.154", + "2.185.100.238", + "2.185.105.18", + "2.185.109.245", + "2.185.112.36", + "2.185.112.226", + "2.185.114.134", + "2.185.116.58", + "2.185.116.139", + "2.185.122.55", + "2.185.123.194", + "2.185.126.25", + "2.185.126.221", + "2.185.126.230", + "2.185.127.96", + "2.185.127.128", + "2.185.128.41", + "2.185.128.69", + "2.185.128.113", + "2.185.128.121", + "2.185.128.161", + "2.185.128.211", + "2.185.128.218", + "2.185.128.221", + "2.185.128.225", + "2.185.128.230", + "2.185.141.25", + "2.185.141.55", + "2.185.143.33", + "2.185.143.103", + "2.185.144.22", + "2.185.147.55", + "2.185.147.166", + "2.185.147.174", + "2.185.147.198", + "2.185.147.202", + "2.185.148.21", + "2.185.148.118", + "2.185.148.128", + "2.185.148.154", + "2.185.148.217", + "2.185.149.163", + "2.185.150.166", + "2.185.151.209", + "2.185.152.1", + "2.185.152.21", + "2.185.153.51", + "2.185.153.188", + "2.185.175.55", + "2.185.191.203", + "2.185.192.9", + "2.185.203.219", + "2.185.214.7", + "2.185.214.202", + "2.185.224.33", + "2.185.224.152", + "2.185.224.167", + "2.185.224.187", + "2.185.225.195", + "2.185.226.211", + "2.185.227.51", + "2.185.227.56", + "2.185.227.124", + "2.185.227.138", + "2.185.229.38", + "2.185.229.115", + "2.185.231.20", + "2.185.232.50", + "2.185.232.170", + "2.185.232.210", + "2.185.232.242", + "2.185.233.83", + "2.185.233.166", + "2.185.234.55", + "2.185.235.73", + "2.185.237.4", + "2.185.238.45", + "2.185.238.145", + "2.185.239.130", + "2.185.239.131", + "2.185.239.132", + "2.185.239.133", + "2.185.239.134", + "2.185.239.136", + "2.185.239.137", + "2.185.239.138", + "2.185.239.139", + "2.185.239.140", + "2.185.239.230", + "2.185.241.0", + "2.185.241.150", + "2.185.242.112", + "2.185.242.141", + "2.185.242.180", + "2.185.244.68", + "2.185.244.78", + "2.185.247.168", + "2.185.247.176", + "2.185.247.197", + "2.185.248.2", + "2.185.248.141", + "2.185.249.54", + "2.185.250.155", + "2.185.250.165", + "2.185.250.215", + "2.185.251.95", + "2.185.252.124", + "2.185.252.253", + "2.185.253.215", + "2.186.12.75", + "2.186.12.113", + "2.186.12.137", + "2.186.12.138", + "2.186.12.156", + "2.186.12.180", + "2.186.13.21", + "2.186.13.26", + "2.186.13.94", + "2.186.14.27", + "2.186.14.60", + "2.186.14.92", + "2.186.14.94", + "2.186.14.102", + "2.186.14.167", + "2.186.14.234", + "2.186.14.239", + "2.186.15.17", + "2.186.15.39", + "2.186.15.74", + "2.186.15.79", + "2.186.15.170", + "2.186.15.194", + "2.186.15.196", + "2.186.15.199", + "2.186.112.22", + "2.186.112.24", + "2.186.112.85", + "2.186.112.132", + "2.186.112.178", + "2.186.112.186", + "2.186.114.125", + "2.186.114.179", + "2.186.114.196", + "2.186.114.236", + "2.186.115.35", + "2.186.115.86", + "2.186.115.162", + "2.186.115.184", + "2.186.115.252", + "2.186.116.38", + "2.186.116.121", + "2.186.116.210", + "2.186.117.2", + "2.186.117.79", + "2.186.117.93", + "2.186.117.107", + "2.186.117.140", + "2.186.117.164", + "2.186.118.17", + "2.186.118.44", + "2.186.118.49", + "2.186.118.81", + "2.186.118.165", + "2.186.118.211", + "2.186.118.255", + "2.186.119.27", + "2.186.119.32", + "2.186.119.48", + "2.186.119.65", + "2.186.119.169", + "2.186.119.217", + "2.186.119.253", + "2.186.120.41", + "2.186.120.64", + "2.186.120.70", + "2.186.120.133", + "2.186.120.175", + "2.186.120.181", + "2.186.120.202", + "2.186.120.215", + "2.186.121.39", + "2.186.121.100", + "2.186.121.170", + "2.186.122.15", + "2.186.122.50", + "2.186.122.51", + "2.186.122.60", + "2.186.122.125", + "2.186.122.184", + "2.186.122.199", + "2.186.122.214", + "2.186.123.35", + "2.186.123.64", + "2.186.123.108", + "2.186.123.152", + "2.186.123.183", + "2.186.123.213", + "2.186.127.1", + "2.186.127.68", + "2.186.127.90", + "2.186.127.158", + "2.186.127.164", + "2.186.127.212", + "2.186.127.236", + "2.186.127.247", + "2.186.229.46", + "2.186.229.60", + "2.186.229.110", + "2.186.229.192", + "2.186.229.230", + "2.186.231.68", + "2.186.231.129", + "2.186.231.130", + "2.186.231.155", + "2.186.231.241", + "2.186.231.242", + "2.186.231.243", + "2.186.231.244", + "2.186.231.245", + "2.186.231.246", + "2.187.1.167", + "2.187.2.23", + "2.187.2.30", + "2.187.2.96", + "2.187.2.101", + "2.187.2.123", + "2.187.2.184", + "2.187.2.185", + "2.187.4.172", + "2.187.5.66", + "2.187.6.17", + "2.187.6.112", + "2.187.6.182", + "2.187.6.243", + "2.187.7.232", + "2.187.8.176", + "2.187.9.2", + "2.187.10.227", + "2.187.11.1", + "2.187.16.10", + "2.187.16.17", + "2.187.16.25", + "2.187.16.63", + "2.187.16.90", + "2.187.16.147", + "2.187.16.179", + "2.187.16.223", + "2.187.16.243", + "2.187.16.252", + "2.187.18.17", + "2.187.18.248", + "2.187.19.18", + "2.187.19.151", + "2.187.19.241", + "2.187.19.242", + "2.187.24.39", + "2.187.24.235", + "2.187.26.17", + "2.187.26.75", + "2.187.26.145", + "2.187.27.146", + "2.187.28.228", + "2.187.30.84", + "2.187.32.13", + "2.187.32.72", + "2.187.32.210", + "2.187.32.228", + "2.187.33.42", + "2.187.33.50", + "2.187.33.107", + "2.187.33.174", + "2.187.33.187", + "2.187.34.87", + "2.187.34.126", + "2.187.34.161", + "2.187.35.15", + "2.187.35.19", + "2.187.35.21", + "2.187.35.61", + "2.187.35.84", + "2.187.35.140", + "2.187.36.11", + "2.187.37.180", + "2.187.37.242", + "2.187.38.26", + "2.187.38.201", + "2.187.39.91", + "2.187.39.157", + "2.187.39.172", + "2.187.39.190", + "2.187.39.234", + "2.187.56.225", + "2.187.57.8", + "2.187.57.17", + "2.187.57.23", + "2.187.62.102", + "2.187.96.30", + "2.187.96.85", + "2.187.96.116", + "2.187.96.252", + "2.187.97.4", + "2.187.97.111", + "2.187.97.121", + "2.187.97.141", + "2.187.98.41", + "2.187.98.118", + "2.187.98.181", + "2.187.99.27", + "2.187.99.71", + "2.187.99.150", + "2.187.100.13", + "2.187.100.51", + "2.187.100.137", + "2.187.101.33", + "2.187.101.46", + "2.187.117.47", + "2.187.117.140", + "2.187.118.1", + "2.187.119.3", + "2.187.119.6", + "2.187.119.113", + "2.187.128.43", + "2.187.128.159", + "2.187.128.166", + "2.187.128.170", + "2.187.140.131", + "2.187.142.95", + "2.187.143.145", + "2.187.150.9", + "2.187.150.48", + "2.187.150.108", + "2.187.186.17", + "2.187.186.65", + "2.187.186.66", + "2.187.186.67", + "2.187.186.68", + "2.187.186.69", + "2.187.188.98", + "2.187.188.123", + "2.187.188.130", + "2.187.188.137", + "2.187.188.143", + "2.187.188.174", + "2.187.188.190", + "2.187.188.239", + "2.187.188.253", + "2.187.189.60", + "2.187.189.121", + "2.187.189.193", + "2.187.189.208", + "2.187.189.226", + "2.187.189.227", + "2.187.212.11", + "2.187.212.125", + "2.187.213.78", + "2.187.213.158", + "2.187.221.33", + "2.187.249.121", + "2.187.249.148", + "2.187.249.177", + "2.187.249.178", + "2.187.250.11", + "2.187.250.50", + "2.187.250.80", + "2.187.250.110", + "2.187.250.169", + "2.187.250.218", + "2.187.250.223", + "2.187.251.88", + "2.187.251.245", + "2.188.43.76", + "2.188.72.11", + "2.188.72.48", + "2.188.72.54", + "2.188.72.56", + "2.188.72.150", + "2.188.72.174", + "2.188.72.209", + "2.188.72.220", + "2.188.72.230", + "2.188.72.238", + "2.188.73.129", + "2.188.73.137", + "2.188.73.145", + "2.188.73.153", + "2.188.73.157", + "2.188.73.161", + "2.188.73.165", + "2.188.75.14", + "2.188.75.178", + "2.188.160.236", + "2.188.161.170", + "2.188.162.74", + "2.188.162.75", + "2.188.162.78", + "2.188.164.54", + "2.188.164.91", + "2.188.164.117", + "2.188.164.140", + "2.188.165.114", + "2.188.165.170", + "2.188.166.50", + "2.188.166.74", + "2.188.166.75", + "2.188.166.78", + "2.188.167.14", + "2.188.167.34", + "2.188.167.54", + "2.188.167.114", + "2.188.167.156", + "2.188.168.41", + "2.188.168.87", + "2.188.168.88", + "2.188.168.135", + "2.188.168.222", + "2.188.174.151", + "2.188.174.173", + "2.188.174.194", + "2.188.174.230", + "2.188.175.4", + "2.188.175.21", + "2.188.175.94", + "2.188.175.105", + "2.188.175.215", + "2.188.175.228", + "2.188.208.110", + "2.188.208.142", + "2.188.208.242", + "2.188.208.254", + "2.188.210.2", + "2.188.210.3", + "2.188.210.5", + "2.188.210.6", + "2.188.210.8", + "2.188.210.10", + "2.188.210.12", + "2.188.210.13", + "2.188.210.14", + "2.188.210.16", + "2.188.210.20", + "2.188.210.21", + "2.188.210.50", + "2.188.210.111", + "2.188.210.112", + "2.188.210.125", + "2.188.211.7", + "2.188.211.234", + "2.188.214.250", + "2.188.214.252", + "2.188.214.253", + "2.188.214.254", + "2.188.215.34", + "2.188.215.67", + "2.188.215.103", + "2.188.217.54", + "2.188.220.132", + "2.188.221.14", + "2.188.222.1", + "2.188.222.2", + "2.188.222.11", + "2.188.222.14", + "2.188.222.65", + "2.188.222.81", + "2.188.225.66", + "2.188.225.67", + "2.188.225.136", + "2.188.225.147", + "2.188.226.187", + "2.188.226.188", + "2.188.226.189", + "2.188.226.190", + "2.188.226.243", + "2.188.226.245", + "2.188.226.246", + "2.188.226.247", + "2.188.226.248", + "2.188.226.250", + "2.188.226.251", + "2.188.226.252", + "2.188.228.36", + "2.188.228.37", + "2.188.228.38", + "2.188.228.98", + "2.188.228.130", + "2.188.230.210", + "2.188.230.212", + "2.188.231.2", + "2.188.231.106", + "2.188.231.110", + "2.188.231.114", + "2.188.231.115", + "2.188.231.116", + "2.188.231.117", + "2.188.231.118", + "2.188.231.120", + "2.188.231.122", + "2.188.231.123", + "2.188.231.125", + "2.188.231.194", + "2.188.231.195", + "2.188.231.196", + "2.188.231.197", + "2.188.231.198", + "2.188.231.199", + "2.188.235.128", + "2.188.236.50", + "2.188.236.70", + "2.188.237.22", + "2.188.237.146", + "2.189.1.1", + "2.189.1.2", + "2.189.1.11", + "2.189.1.12", + "2.189.1.14", + "2.189.1.40", + "2.189.1.104", + "2.189.1.251", + "2.189.8.168", + "2.189.9.77", + "2.189.9.163", + "2.189.10.247", + "2.189.11.76", + "2.189.11.239", + "2.189.16.113", + "2.189.20.156", + "2.189.20.158", + "2.189.21.139", + "2.189.23.66", + "2.189.28.38", + "2.189.28.134", + "2.189.28.166", + "2.189.29.111", + "2.189.29.229", + "2.189.29.231", + "2.189.30.37", + "2.189.31.217", + "2.189.32.52", + "2.189.32.199", + "2.189.33.61", + "2.189.33.90", + "2.189.33.114", + "2.189.34.164", + "2.189.34.221", + "2.189.34.244", + "2.189.59.81", + "2.189.67.42", + "2.189.86.10", + "2.189.86.11", + "2.189.86.12", + "2.189.86.13", + "2.189.86.14", + "2.189.86.15", + "2.189.86.16", + "2.189.86.19", + "2.189.86.30", + "2.189.86.31", + "2.189.86.32", + "2.189.86.34", + "2.189.86.35", + "2.189.86.36", + "2.189.86.41", + "2.189.86.42", + "2.189.86.43", + "2.189.86.44", + "2.189.86.45", + "2.189.86.46", + "2.189.86.84", + "2.189.86.85", + "2.189.86.86", + "2.189.86.87", + "2.189.86.88", + "2.189.86.89", + "2.189.86.90", + "2.189.86.91", + "2.189.86.92", + "2.189.86.93", + "2.189.86.94", + "2.189.86.98", + "2.189.86.103", + "2.189.86.104", + "2.189.86.105", + "2.189.86.107", + "2.189.86.108", + "2.189.86.109", + "2.189.86.110", + "2.189.86.111", + "2.189.86.112", + "2.189.86.113", + "2.189.87.172", + "2.189.87.228", + "2.189.89.18", + "2.189.89.110", + "2.189.89.166", + "2.189.89.214", + "2.189.90.84", + "2.189.90.147", + "2.189.90.206", + "2.189.91.37", + "2.189.91.141", + "2.189.91.226", + "2.189.91.229", + "2.189.91.230", + "2.189.91.231", + "2.189.91.232", + "2.189.92.31", + "2.189.92.217", + "2.189.93.162", + "2.189.93.224", + "2.189.96.121", + "2.189.96.136", + "2.189.98.67", + "2.189.98.113", + "2.189.98.135", + "2.189.98.137", + "2.189.101.0", + "2.189.101.6", + "2.189.101.12", + "2.189.101.13", + "2.189.101.14", + "2.189.101.15", + "2.189.101.85", + "2.189.101.100", + "2.189.101.101", + "2.189.101.139", + "2.189.101.146", + "2.189.101.165", + "2.189.101.224", + "2.189.101.231", + "2.189.102.15", + "2.189.104.1", + "2.189.104.3", + "2.189.114.61", + "2.189.115.49", + "2.189.115.76", + "2.189.115.150", + "2.189.115.162", + "2.189.115.174", + "2.189.115.183", + "2.189.120.2", + "2.189.120.4", + "2.189.120.14", + "2.189.120.16", + "2.189.120.23", + "2.189.120.35", + "2.189.120.39", + "2.189.120.40", + "2.189.120.43", + "2.189.120.69", + "2.189.120.74", + "2.189.120.77", + "2.189.120.78", + "2.189.120.99", + "2.189.120.109", + "2.189.120.114", + "2.189.120.116", + "2.189.120.117", + "2.189.120.144", + "2.189.120.149", + "2.189.120.164", + "2.189.120.180", + "2.189.120.183", + "2.189.120.201", + "2.189.120.213", + "2.189.120.214", + "2.189.120.220", + "2.189.120.226", + "2.189.120.231", + "2.189.120.232", + "2.189.120.233", + "2.189.121.4", + "2.189.121.10", + "2.189.121.15", + "2.189.121.16", + "2.189.121.21", + "2.189.121.47", + "2.189.121.49", + "2.189.121.53", + "2.189.121.54", + "2.189.121.56", + "2.189.121.58", + "2.189.121.62", + "2.189.121.66", + "2.189.121.78", + "2.189.121.79", + "2.189.121.88", + "2.189.121.90", + "2.189.121.91", + "2.189.121.99", + "2.189.121.104", + "2.189.121.121", + "2.189.121.131", + "2.189.121.136", + "2.189.121.146", + "2.189.121.152", + "2.189.121.155", + "2.189.121.163", + "2.189.121.176", + "2.189.121.178", + "2.189.121.191", + "2.189.121.202", + "2.189.121.210", + "2.189.121.218", + "2.189.121.231", + "2.189.121.232", + "2.189.121.240", + "2.189.121.244", + "2.189.122.20", + "2.189.122.22", + "2.189.122.103", + "2.189.122.119", + "2.189.122.240", + "2.189.128.11", + "2.189.128.12", + "2.189.128.13", + "2.189.128.180", + "2.189.128.181", + "2.189.128.182", + "2.189.130.3", + "2.189.130.4", + "2.189.130.8", + "2.189.130.10", + "2.189.130.18", + "2.189.130.33", + "2.189.136.3", + "2.189.136.6", + "2.189.136.25", + "2.189.136.67", + "2.189.136.69", + "2.189.136.78", + "2.189.136.95", + "2.189.136.112", + "2.189.136.132", + "2.189.136.153", + "2.189.136.178", + "2.189.137.0", + "2.189.137.20", + "2.189.137.27", + "2.189.137.29", + "2.189.137.43", + "2.189.137.61", + "2.189.137.98", + "2.189.137.99", + "2.189.137.221", + "2.189.137.222", + "2.189.137.223", + "2.189.137.224", + "2.189.137.240", + "2.189.139.0", + "2.189.139.5", + "2.189.139.7", + "2.189.139.10", + "2.189.139.14", + "2.189.139.16", + "2.189.139.18", + "2.189.139.21", + "2.189.139.25", + "2.189.139.35", + "2.189.139.41", + "2.189.139.55", + "2.189.139.56", + "2.189.139.63", + "2.189.139.65", + "2.189.139.76", + "2.189.139.85", + "2.189.139.89", + "2.189.139.95", + "2.189.139.101", + "2.189.139.108", + "2.189.139.109", + "2.189.139.114", + "2.189.139.120", + "2.189.139.125", + "2.189.139.128", + "2.189.139.129", + "2.189.139.130", + "2.189.139.131", + "2.189.139.135", + "2.189.139.145", + "2.189.139.150", + "2.189.139.157", + "2.189.139.161", + "2.189.139.171", + "2.189.139.180", + "2.189.139.185", + "2.189.139.193", + "2.189.139.206", + "2.189.139.214", + "2.189.139.218", + "2.189.139.221", + "2.189.139.222", + "2.189.139.230", + "2.189.139.233", + "2.189.139.240", + "2.189.139.241", + "2.189.139.242", + "2.189.139.246", + "2.189.139.247", + "2.189.139.250", + "2.189.140.5", + "2.189.140.6", + "2.189.140.7", + "2.189.141.32", + "2.189.142.20", + "2.189.142.33", + "2.189.142.38", + "2.189.142.48", + "2.189.142.55", + "2.189.142.57", + "2.189.142.63", + "2.189.142.98", + "2.189.142.112", + "2.189.143.4", + "2.189.143.5", + "2.189.143.8", + "2.189.143.11", + "2.189.143.24", + "2.189.143.49", + "2.189.143.58", + "2.189.143.61", + "2.189.143.78", + "2.189.143.145", + "2.189.143.181", + "2.189.143.191", + "2.189.143.222", + "2.189.143.253", + "2.189.144.9", + "2.189.144.11", + "2.189.144.19", + "2.189.144.28", + "2.189.144.40", + "2.189.144.55", + "2.189.144.65", + "2.189.144.73", + "2.189.144.83", + "2.189.144.86", + "2.189.144.104", + "2.189.144.109", + "2.189.144.121", + "2.189.144.126", + "2.189.144.129", + "2.189.144.140", + "2.189.144.143", + "2.189.144.147", + "2.189.144.151", + "2.189.144.159", + "2.189.144.163", + "2.189.144.173", + "2.189.144.181", + "2.189.144.184", + "2.189.144.186", + "2.189.144.207", + "2.189.144.215", + "2.189.144.218", + "2.189.144.221", + "2.189.144.222", + "2.189.144.224", + "2.189.144.227", + "2.189.144.229", + "2.189.144.233", + "2.189.144.235", + "2.189.144.247", + "2.189.145.4", + "2.189.145.11", + "2.189.145.34", + "2.189.145.46", + "2.189.145.48", + "2.189.145.70", + "2.189.145.71", + "2.189.145.75", + "2.189.145.104", + "2.189.145.108", + "2.189.145.109", + "2.189.145.122", + "2.189.145.149", + "2.189.145.154", + "2.189.145.160", + "2.189.145.164", + "2.189.145.167", + "2.189.145.168", + "2.189.145.174", + "2.189.145.177", + "2.189.145.183", + "2.189.145.193", + "2.189.145.195", + "2.189.145.197", + "2.189.145.211", + "2.189.145.213", + "2.189.145.224", + "2.189.145.244", + "2.189.145.246", + "2.189.145.249", + "2.189.145.252", + "2.189.146.113", + "2.189.147.44", + "2.189.147.188", + "2.189.147.241", + "2.189.148.2", + "2.189.148.8", + "2.189.148.12", + "2.189.148.16", + "2.189.148.17", + "2.189.148.23", + "2.189.148.28", + "2.189.148.29", + "2.189.148.32", + "2.189.148.33", + "2.189.148.34", + "2.189.148.36", + "2.189.148.38", + "2.189.148.39", + "2.189.148.43", + "2.189.148.44", + "2.189.148.49", + "2.189.148.54", + "2.189.148.63", + "2.189.148.64", + "2.189.148.67", + "2.189.148.69", + "2.189.148.71", + "2.189.148.82", + "2.189.148.87", + "2.189.148.88", + "2.189.148.94", + "2.189.148.98", + "2.189.148.104", + "2.189.148.109", + "2.189.148.126", + "2.189.148.131", + "2.189.148.133", + "2.189.148.136", + "2.189.148.137", + "2.189.148.139", + "2.189.148.146", + "2.189.148.156", + "2.189.148.166", + "2.189.148.167", + "2.189.148.169", + "2.189.148.171", + "2.189.148.176", + "2.189.148.181", + "2.189.148.182", + "2.189.148.183", + "2.189.148.192", + "2.189.148.204", + "2.189.148.205", + "2.189.148.214", + "2.189.148.215", + "2.189.148.231", + "2.189.148.235", + "2.189.148.248", + "2.189.148.251", + "2.189.148.253", + "2.189.151.5", + "2.189.151.68", + "2.189.151.109", + "2.189.151.116", + "2.189.151.126", + "2.189.151.130", + "2.189.151.146", + "2.189.151.165", + "2.189.151.171", + "2.189.151.174", + "2.189.151.180", + "2.189.153.96", + "2.189.153.162", + "2.189.156.30", + "2.189.156.42", + "2.189.158.176", + "2.189.162.18", + "2.189.162.250", + "2.189.163.4", + "2.189.163.63", + "2.189.163.112", + "2.189.164.26", + "2.189.164.27", + "2.189.164.28", + "2.189.164.29", + "2.189.167.131", + "2.189.167.203", + "2.189.167.210", + "2.189.167.211", + "2.189.167.212", + "2.189.167.254", + "2.189.172.30", + "2.189.172.49", + "2.189.218.246", + "2.189.220.93", + "2.189.236.154", + "2.189.236.246", + "2.189.237.61", + "2.189.237.180", + "2.189.237.190", + "2.189.237.236", + "2.189.237.254", + "2.189.238.236", + "2.189.239.123", + "2.189.243.1", + "2.189.243.22", + "2.189.254.109", + "2.190.1.39", + "2.190.2.98", + "2.190.6.96", + "2.190.6.165", + "2.190.8.47", + "2.190.11.209", + "2.190.17.151", + "2.190.19.236", + "2.190.20.16", + "2.190.20.231", + "2.190.23.71", + "2.190.25.71", + "2.190.30.229", + "2.190.30.232", + "2.190.31.210", + "2.190.32.109", + "2.190.34.61", + "2.190.35.228", + "2.190.36.45", + "2.190.36.76", + "2.190.39.192", + "2.190.41.13", + "2.190.41.22", + "2.190.43.66", + "2.190.44.173", + "2.190.48.7", + "2.190.48.125", + "2.190.48.237", + "2.190.49.82", + "2.190.50.185", + "2.190.50.196", + "2.190.52.208", + "2.190.52.213", + "2.190.55.11", + "2.190.55.244", + "2.190.61.97", + "2.190.62.119", + "2.190.62.191", + "2.190.65.167", + "2.190.67.113", + "2.190.69.243", + "2.190.70.196", + "2.190.71.71", + "2.190.73.192", + "2.190.74.207", + "2.190.76.5", + "2.190.76.85", + "2.190.76.193", + "2.190.77.23", + "2.190.78.61", + "2.190.79.242", + "2.190.83.111", + "2.190.83.208", + "2.190.84.71", + "2.190.86.236", + "2.190.89.139", + "2.190.92.35", + "2.190.94.144", + "2.190.99.230", + "2.190.107.69", + "2.190.109.210", + "2.190.118.22", + "2.190.120.59", + "2.190.122.198", + "2.190.123.73", + "2.190.124.254", + "2.190.125.199", + "2.190.126.81", + "2.190.127.182", + "2.190.128.34", + "2.190.128.144", + "2.190.129.63", + "2.190.131.45", + "2.190.131.59", + "2.190.131.77", + "2.190.132.76", + "2.190.132.127", + "2.190.134.82", + "2.190.134.83", + "2.190.135.61", + "2.190.136.197", + "2.190.138.238", + "2.190.139.113", + "2.190.139.227", + "2.190.140.0", + "2.190.141.236", + "2.190.144.221", + "2.190.145.33", + "2.190.145.162", + "2.190.146.22", + "2.190.149.231", + "2.190.150.187", + "2.190.152.194", + "2.190.154.18", + "2.190.154.112", + "2.190.155.29", + "2.190.156.203", + "2.190.159.247", + "2.190.159.252", + "2.190.161.41", + "2.190.161.135", + "2.190.161.184", + "2.190.163.101", + "2.190.165.30", + "2.190.165.60", + "2.190.165.211", + "2.190.166.161", + "2.190.167.23", + "2.190.167.212", + "2.190.168.142", + "2.190.168.170", + "2.190.168.222", + "2.190.169.17", + "2.190.169.52", + "2.190.169.66", + "2.190.170.46", + "2.190.170.99", + "2.190.170.190", + "2.190.170.226", + "2.190.171.67", + "2.190.171.109", + "2.190.171.169", + "2.190.172.25", + "2.190.174.227", + "2.190.175.249", + "2.190.175.253", + "2.190.178.124", + "2.190.179.0", + "2.190.179.147", + "2.190.180.43", + "2.190.180.189", + "2.190.184.132", + "2.190.185.87", + "2.190.186.3", + "2.190.186.158", + "2.190.186.203", + "2.190.187.159", + "2.190.187.241", + "2.190.188.66", + "2.190.188.249", + "2.190.191.189", + "2.190.191.200", + "2.190.192.226", + "2.190.192.228", + "2.190.193.203", + "2.190.194.46", + "2.190.195.166", + "2.190.195.204", + "2.190.195.218", + "2.190.199.157", + "2.190.200.150", + "2.190.200.250", + "2.190.201.12", + "2.190.201.171", + "2.190.202.65", + "2.190.202.158", + "2.190.202.198", + "2.190.202.216", + "2.190.203.54", + "2.190.203.131", + "2.190.203.251", + "2.190.204.64", + "2.190.205.188", + "2.190.206.99", + "2.190.206.170", + "2.190.206.210", + "2.190.206.216", + "2.190.207.248", + "2.190.209.183", + "2.190.209.211", + "2.190.209.250", + "2.190.210.10", + "2.190.210.75", + "2.190.212.167", + "2.190.212.231", + "2.190.213.22", + "2.190.216.62", + "2.190.216.174", + "2.190.217.24", + "2.190.217.92", + "2.190.217.165", + "2.190.219.214", + "2.190.220.78", + "2.190.220.211", + "2.190.220.241", + "2.190.221.12", + "2.190.221.194", + "2.190.222.89", + "2.190.223.87", + "2.190.224.7", + "2.190.224.132", + "2.190.224.239", + "2.190.230.119", + "2.190.231.82", + "2.190.231.157", + "2.190.232.123", + "2.190.232.132", + "2.190.234.135", + "2.190.235.146", + "2.190.238.243", + "2.190.239.223", + "2.190.240.72", + "2.190.240.176", + "2.190.243.87", + "2.190.246.193", + "2.190.247.151", + "2.190.249.82", + "2.190.249.112", + "2.190.250.112", + "2.190.250.122", + "2.190.251.161", + "2.190.252.84", + "2.190.252.208", + "2.190.252.246", + "2.190.253.55", + "2.190.254.174", + "2.190.254.245", + "2.190.255.24", + "2.190.255.185", + "2.191.0.118", + "2.191.1.47", + "2.191.2.50", + "2.191.2.235", + "2.191.6.10", + "2.191.6.87", + "2.191.6.132", + "2.191.7.0", + "2.191.8.125", + "2.191.8.147", + "2.191.9.10", + "2.191.9.194", + "2.191.12.12", + "2.191.12.134", + "2.191.13.14", + "2.191.14.38", + "2.191.14.110", + "2.191.14.148", + "2.191.15.52", + "2.191.15.163", + "2.191.17.129", + "2.191.18.196", + "2.191.19.66", + "2.191.19.120", + "2.191.19.124", + "2.191.19.208", + "2.191.22.243", + "2.191.23.201", + "2.191.24.52", + "2.191.24.62", + "2.191.28.157", + "2.191.28.241", + "2.191.29.45", + "2.191.29.48", + "2.191.31.85", + "2.191.32.24", + "2.191.34.80", + "2.191.35.207", + "2.191.36.246", + "2.191.37.33", + "2.191.37.183", + "2.191.38.68", + "2.191.38.70", + "2.191.38.186", + "2.191.38.227", + "2.191.40.77", + "2.191.40.149", + "2.191.40.224", + "2.191.41.211", + "2.191.42.150", + "2.191.43.136", + "2.191.43.178", + "2.191.44.34", + "2.191.44.39", + "2.191.44.211", + "2.191.45.40", + "2.191.45.171", + "2.191.45.181", + "2.191.46.25", + "2.191.46.73", + "2.191.46.113", + "2.191.46.114", + "2.191.47.158", + "2.191.47.201", + "2.191.48.18", + "2.191.48.179", + "2.191.50.99", + "2.191.51.173", + "2.191.52.46", + "2.191.53.61", + "2.191.53.242", + "2.191.55.165", + "2.191.56.28", + "2.191.57.66", + "2.191.57.179", + "2.191.58.176", + "2.191.58.178", + "2.191.59.170", + "2.191.60.102", + "2.191.60.186", + "2.191.60.205", + "2.191.61.246", + "2.191.62.3", + "2.191.63.31", + "2.191.63.189", + "2.191.64.56", + "2.191.64.61", + "2.191.64.186", + "2.191.64.242", + "2.191.65.4", + "2.191.67.29", + "2.191.68.135", + "2.191.69.94", + "2.191.69.185", + "2.191.70.110", + "2.191.70.132", + "2.191.71.57", + "2.191.71.77", + "2.191.71.219", + "2.191.73.219", + "2.191.74.137", + "2.191.75.50", + "2.191.75.144", + "2.191.75.157", + "2.191.76.121", + "2.191.80.4", + "2.191.80.253", + "2.191.81.64", + "2.191.82.127", + "2.191.83.137", + "2.191.84.65", + "2.191.85.54", + "2.191.85.82", + "2.191.86.107", + "2.191.86.180", + "2.191.86.219", + "2.191.86.255", + "2.191.87.132", + "2.191.87.206", + "2.191.89.32", + "2.191.90.27", + "2.191.90.185", + "2.191.90.186", + "2.191.91.32", + "2.191.91.101", + "2.191.92.107", + "2.191.92.185", + "2.191.93.14", + "2.191.94.26", + "2.191.94.236", + "2.191.95.249", + "2.191.96.234", + "2.191.97.65", + "2.191.97.146", + "2.191.98.243", + "2.191.99.83", + "2.191.99.158", + "2.191.101.155", + "2.191.103.93", + "2.191.103.121", + "2.191.103.168", + "2.191.104.41", + "2.191.104.51", + "2.191.105.150", + "2.191.108.244", + "2.191.109.116", + "2.191.110.5", + "2.191.110.185", + "2.191.111.42", + "2.191.111.204", + "2.191.112.254", + "2.191.113.74", + "2.191.113.106", + "2.191.113.197", + "2.191.113.215", + "2.191.115.150", + "2.191.115.245", + "2.191.116.96", + "2.191.116.114", + "2.191.117.57", + "2.191.117.147", + "2.191.118.61", + "2.191.120.25", + "2.191.120.243", + "2.191.124.210", + "2.191.126.20", + "2.191.127.29", + "2.191.127.41", + "2.191.131.119", + "2.191.142.150", + "2.191.197.194", + "2.191.202.171", + "2.191.208.248", + "5.10.248.43", + "5.10.248.53", + "5.10.248.150", + "5.10.248.181", + "5.10.249.153", + "5.10.249.156", + "5.10.249.164", + "5.10.249.179", + "5.10.249.217", + "5.10.249.245", + "5.22.193.5", + "5.22.193.6", + "5.22.196.29", + "5.22.196.102", + "5.22.196.154", + "5.22.197.172", + "5.22.198.138", + "5.22.198.190", + "5.22.199.193", + "5.22.200.232", + "5.22.200.233", + "5.22.200.234", + "5.22.200.235", + "5.22.201.17", + "5.22.201.162", + "5.22.203.98", + "5.22.203.100", + "5.22.203.101", + "5.22.203.102", + "5.42.217.140", + "5.42.217.182", + "5.42.223.221", + "5.56.132.51", + "5.56.132.142", + "5.56.135.7", + "5.57.32.131", + "5.57.32.166", + "5.57.32.180", + "5.57.32.235", + "5.57.33.97", + "5.57.33.251", + "5.57.34.199", + "5.57.35.225", + "5.57.37.76", + "5.57.37.228", + "5.57.39.58", + "5.61.24.42", + "5.61.28.101", + "5.61.28.235", + "5.61.30.35", + "5.61.30.226", + "5.61.31.99", + "5.62.164.171", + "5.63.8.227", + "5.63.9.37", + "5.63.9.218", + "5.63.13.51", + "5.63.13.52", + "5.63.13.54", + "5.63.13.130", + "5.74.1.217", + "5.74.5.10", + "5.74.10.236", + "5.74.13.6", + "5.74.22.215", + "5.74.25.251", + "5.74.28.146", + "5.74.35.80", + "5.74.38.17", + "5.74.38.158", + "5.74.40.120", + "5.74.40.141", + "5.74.42.203", + "5.74.44.199", + "5.74.44.204", + "5.74.56.99", + "5.74.56.162", + "5.74.58.120", + "5.74.66.14", + "5.74.66.233", + "5.74.68.20", + "5.74.71.139", + "5.74.75.152", + "5.74.80.36", + "5.74.80.101", + "5.74.83.157", + "5.74.87.197", + "5.74.89.177", + "5.74.94.17", + "5.74.97.165", + "5.74.98.28", + "5.74.100.96", + "5.74.101.87", + "5.74.105.229", + "5.74.106.250", + "5.74.111.74", + "5.74.112.154", + "5.74.113.149", + "5.74.121.103", + "5.74.126.100", + "5.74.128.161", + "5.74.128.210", + "5.74.128.236", + "5.74.129.74", + "5.74.129.92", + "5.74.130.60", + "5.74.130.226", + "5.74.131.34", + "5.74.131.194", + "5.74.131.198", + "5.74.132.140", + "5.74.132.164", + "5.74.133.175", + "5.74.134.201", + "5.74.137.11", + "5.74.139.47", + "5.74.139.130", + "5.74.145.22", + "5.74.145.67", + "5.74.146.141", + "5.74.146.179", + "5.74.147.228", + "5.74.151.195", + "5.74.154.129", + "5.74.155.111", + "5.74.156.49", + "5.74.156.251", + "5.74.157.139", + "5.74.157.195", + "5.74.157.228", + "5.74.159.121", + "5.74.159.123", + "5.74.159.129", + "5.74.160.71", + "5.74.160.147", + "5.74.160.212", + "5.74.161.128", + "5.74.163.73", + "5.74.163.130", + "5.74.164.17", + "5.74.165.51", + "5.74.165.234", + "5.74.168.18", + "5.74.168.54", + "5.74.168.95", + "5.74.169.91", + "5.74.170.174", + "5.74.170.233", + "5.74.173.188", + "5.74.174.30", + "5.74.175.141", + "5.74.176.18", + "5.74.177.22", + "5.74.178.191", + "5.74.179.16", + "5.74.180.250", + "5.74.181.153", + "5.74.183.117", + "5.74.183.146", + "5.74.184.240", + "5.74.188.93", + "5.74.188.151", + "5.74.189.1", + "5.74.193.164", + "5.74.195.235", + "5.74.197.49", + "5.74.199.35", + "5.74.199.81", + "5.74.199.252", + "5.74.205.78", + "5.74.205.212", + "5.74.206.36", + "5.74.207.79", + "5.74.207.114", + "5.74.208.64", + "5.74.211.96", + "5.74.211.172", + "5.74.212.1", + "5.74.212.5", + "5.74.212.11", + "5.74.212.234", + "5.74.215.107", + "5.74.218.2", + "5.74.219.171", + "5.74.220.57", + "5.74.221.24", + "5.74.221.80", + "5.74.221.102", + "5.74.221.140", + "5.74.222.115", + "5.74.224.18", + "5.74.225.117", + "5.74.225.239", + "5.74.227.193", + "5.74.229.131", + "5.74.231.56", + "5.74.233.92", + "5.74.233.254", + "5.74.234.101", + "5.74.234.181", + "5.74.234.223", + "5.74.236.142", + "5.74.236.167", + "5.74.237.240", + "5.74.237.247", + "5.74.239.195", + "5.74.240.11", + "5.74.242.17", + "5.74.242.107", + "5.74.245.153", + "5.74.246.116", + "5.74.246.192", + "5.74.249.45", + "5.74.249.231", + "5.74.252.166", + "5.74.255.153", + "5.75.41.226", + "5.106.4.130", + "5.106.5.129", + "5.106.5.130", + "5.106.16.59", + "5.106.16.163", + "5.106.16.248", + "5.106.17.59", + "5.106.17.60", + "5.106.17.61", + "5.106.17.62", + "5.106.17.64", + "5.106.17.65", + "5.106.17.66", + "5.106.17.67", + "5.106.17.77", + "5.106.17.128", + "5.106.17.129", + "5.106.17.134", + "5.106.17.135", + "5.106.17.147", + "5.106.17.161", + "5.106.17.163", + "5.106.17.172", + "5.106.18.59", + "5.106.18.62", + "5.106.18.63", + "5.106.18.90", + "5.106.18.170", + "5.106.18.176", + "5.106.18.192", + "5.106.18.218", + "5.106.18.221", + "5.106.18.235", + "5.106.18.241", + "5.106.18.252", + "5.106.19.13", + "5.106.19.45", + "5.106.19.47", + "5.106.19.48", + "5.106.19.49", + "5.106.19.50", + "5.106.19.51", + "5.106.19.53", + "5.106.19.54", + "5.106.19.56", + "5.106.19.58", + "5.106.19.60", + "5.106.19.79", + "5.106.19.89", + "5.106.19.93", + "5.106.19.95", + "5.106.19.116", + "5.106.19.126", + "5.106.19.179", + "5.106.19.188", + "5.106.19.193", + "5.106.19.195", + "5.106.19.205", + "5.106.21.62", + "5.106.21.84", + "5.106.21.103", + "5.106.21.126", + "5.106.21.154", + "5.106.28.32", + "5.106.28.115", + "5.106.28.172", + "5.106.28.198", + "5.106.28.203", + "5.106.29.212", + "5.134.195.80", + "5.134.195.162", + "5.134.195.197", + "5.134.196.195", + "5.144.129.83", + "5.144.129.177", + "5.144.129.236", + "5.144.130.94", + "5.144.132.7", + "5.144.132.98", + "5.145.112.53", + "5.145.112.57", + "5.145.112.225", + "5.145.113.1", + "5.145.113.58", + "5.145.113.60", + "5.145.114.73", + "5.145.114.83", + "5.145.114.85", + "5.145.114.98", + "5.145.114.99", + "5.145.114.112", + "5.145.114.113", + "5.145.114.115", + "5.145.114.211", + "5.145.114.222", + "5.145.114.225", + "5.145.114.234", + "5.145.114.236", + "5.145.114.239", + "5.145.114.243", + "5.145.114.249", + "5.145.115.17", + "5.145.115.170", + "5.145.115.213", + "5.145.115.217", + "5.145.115.225", + "5.145.115.232", + "5.159.49.107", + "5.159.49.207", + "5.159.50.49", + "5.159.50.50", + "5.159.50.53", + "5.159.50.55", + "5.159.50.57", + "5.159.50.58", + "5.159.50.61", + "5.159.50.194", + "5.159.50.197", + "5.159.50.198", + "5.159.50.200", + "5.159.50.201", + "5.159.51.5", + "5.159.52.25", + "5.159.52.26", + "5.159.52.27", + "5.159.52.28", + "5.159.52.29", + "5.159.52.73", + "5.159.52.76", + "5.159.53.56", + "5.159.53.81", + "5.159.55.50", + "5.159.55.53", + "5.159.55.62", + "5.159.55.105", + "5.159.55.113", + "5.159.55.121", + "5.159.55.129", + "5.159.55.150", + "5.159.55.153", + "5.159.55.181", + "5.159.55.182", + "5.159.55.249", + "5.160.0.125", + "5.160.0.127", + "5.160.0.195", + "5.160.0.222", + "5.160.1.26", + "5.160.1.42", + "5.160.1.43", + "5.160.1.44", + "5.160.1.90", + "5.160.2.38", + "5.160.2.226", + "5.160.2.227", + "5.160.2.228", + "5.160.2.229", + "5.160.3.34", + "5.160.3.104", + "5.160.3.116", + "5.160.3.174", + "5.160.3.226", + "5.160.4.147", + "5.160.5.99", + "5.160.5.100", + "5.160.5.101", + "5.160.5.102", + "5.160.5.162", + "5.160.7.214", + "5.160.12.244", + "5.160.12.247", + "5.160.12.251", + "5.160.14.203", + "5.160.14.204", + "5.160.14.205", + "5.160.14.206", + "5.160.15.10", + "5.160.20.130", + "5.160.23.98", + "5.160.23.102", + "5.160.25.180", + "5.160.25.206", + "5.160.26.30", + "5.160.26.74", + "5.160.26.75", + "5.160.26.77", + "5.160.27.150", + "5.160.28.236", + "5.160.28.238", + "5.160.29.4", + "5.160.29.44", + "5.160.32.190", + "5.160.33.2", + "5.160.33.254", + "5.160.36.48", + "5.160.36.60", + "5.160.37.8", + "5.160.37.46", + "5.160.37.147", + "5.160.37.192", + "5.160.37.201", + "5.160.38.46", + "5.160.38.78", + "5.160.38.189", + "5.160.39.98", + "5.160.39.250", + "5.160.40.4", + "5.160.40.39", + "5.160.40.44", + "5.160.40.56", + "5.160.40.62", + "5.160.40.71", + "5.160.40.154", + "5.160.40.173", + "5.160.40.250", + "5.160.42.34", + "5.160.42.46", + "5.160.42.202", + "5.160.43.18", + "5.160.43.62", + "5.160.43.82", + "5.160.43.110", + "5.160.45.138", + "5.160.45.220", + "5.160.47.67", + "5.160.47.82", + "5.160.47.119", + "5.160.48.53", + "5.160.49.14", + "5.160.49.26", + "5.160.49.74", + "5.160.49.75", + "5.160.49.76", + "5.160.49.77", + "5.160.49.78", + "5.160.49.98", + "5.160.49.100", + "5.160.49.101", + "5.160.49.104", + "5.160.49.108", + "5.160.49.109", + "5.160.49.226", + "5.160.50.6", + "5.160.50.130", + "5.160.51.241", + "5.160.52.90", + "5.160.52.91", + "5.160.52.92", + "5.160.52.93", + "5.160.52.94", + "5.160.52.234", + "5.160.53.94", + "5.160.54.26", + "5.160.54.27", + "5.160.54.28", + "5.160.54.29", + "5.160.54.30", + "5.160.54.118", + "5.160.56.2", + "5.160.56.30", + "5.160.56.50", + "5.160.56.118", + "5.160.56.174", + "5.160.56.228", + "5.160.56.249", + "5.160.57.34", + "5.160.57.35", + "5.160.58.129", + "5.160.59.18", + "5.160.59.19", + "5.160.59.20", + "5.160.59.29", + "5.160.60.6", + "5.160.60.43", + "5.160.60.166", + "5.160.61.2", + "5.160.61.161", + "5.160.61.162", + "5.160.61.163", + "5.160.61.164", + "5.160.61.166", + "5.160.61.169", + "5.160.61.170", + "5.160.61.171", + "5.160.61.172", + "5.160.61.173", + "5.160.61.174", + "5.160.61.251", + "5.160.61.254", + "5.160.62.88", + "5.160.62.89", + "5.160.62.91", + "5.160.62.160", + "5.160.62.164", + "5.160.63.34", + "5.160.63.38", + "5.160.63.126", + "5.160.65.18", + "5.160.65.19", + "5.160.65.20", + "5.160.65.21", + "5.160.65.22", + "5.160.65.25", + "5.160.65.26", + "5.160.65.27", + "5.160.65.28", + "5.160.65.29", + "5.160.65.30", + "5.160.65.190", + "5.160.67.98", + "5.160.67.102", + "5.160.68.1", + "5.160.68.50", + "5.160.68.226", + "5.160.68.229", + "5.160.68.230", + "5.160.68.231", + "5.160.68.232", + "5.160.68.233", + "5.160.68.234", + "5.160.68.235", + "5.160.68.236", + "5.160.68.238", + "5.160.72.22", + "5.160.72.53", + "5.160.72.134", + "5.160.72.222", + "5.160.73.88", + "5.160.74.147", + "5.160.74.226", + "5.160.74.227", + "5.160.74.228", + "5.160.74.229", + "5.160.74.230", + "5.160.74.232", + "5.160.74.233", + "5.160.74.254", + "5.160.75.10", + "5.160.75.90", + "5.160.75.207", + "5.160.76.106", + "5.160.76.170", + "5.160.76.171", + "5.160.76.198", + "5.160.76.226", + "5.160.78.18", + "5.160.78.86", + "5.160.78.104", + "5.160.78.134", + "5.160.78.154", + "5.160.78.238", + "5.160.80.33", + "5.160.80.98", + "5.160.80.193", + "5.160.80.194", + "5.160.80.210", + "5.160.80.211", + "5.160.80.214", + "5.160.81.177", + "5.160.81.187", + "5.160.81.251", + "5.160.82.110", + "5.160.83.18", + "5.160.83.66", + "5.160.83.117", + "5.160.83.122", + "5.160.84.34", + "5.160.85.195", + "5.160.85.234", + "5.160.86.34", + "5.160.87.51", + "5.160.87.106", + "5.160.88.34", + "5.160.88.35", + "5.160.88.36", + "5.160.88.37", + "5.160.88.38", + "5.160.88.39", + "5.160.88.40", + "5.160.88.41", + "5.160.88.42", + "5.160.88.43", + "5.160.88.44", + "5.160.88.45", + "5.160.88.46", + "5.160.89.99", + "5.160.89.194", + "5.160.90.61", + "5.160.90.86", + "5.160.90.218", + "5.160.91.180", + "5.160.91.190", + "5.160.93.210", + "5.160.93.246", + "5.160.95.194", + "5.160.98.131", + "5.160.99.4", + "5.160.99.5", + "5.160.99.6", + "5.160.99.8", + "5.160.99.9", + "5.160.99.10", + "5.160.99.11", + "5.160.99.12", + "5.160.99.13", + "5.160.99.14", + "5.160.99.15", + "5.160.99.19", + "5.160.99.20", + "5.160.99.227", + "5.160.99.229", + "5.160.99.230", + "5.160.99.231", + "5.160.99.232", + "5.160.99.235", + "5.160.99.237", + "5.160.100.8", + "5.160.100.114", + "5.160.100.118", + "5.160.101.2", + "5.160.101.244", + "5.160.101.245", + "5.160.101.246", + "5.160.101.247", + "5.160.101.249", + "5.160.101.250", + "5.160.101.251", + "5.160.101.252", + "5.160.101.253", + "5.160.102.201", + "5.160.103.42", + "5.160.103.43", + "5.160.103.46", + "5.160.103.67", + "5.160.103.114", + "5.160.103.162", + "5.160.103.185", + "5.160.108.74", + "5.160.108.210", + "5.160.108.222", + "5.160.109.186", + "5.160.112.46", + "5.160.112.145", + "5.160.114.126", + "5.160.114.132", + "5.160.114.134", + "5.160.115.42", + "5.160.115.50", + "5.160.115.51", + "5.160.115.52", + "5.160.115.53", + "5.160.115.54", + "5.160.115.130", + "5.160.115.134", + "5.160.115.138", + "5.160.115.190", + "5.160.115.210", + "5.160.115.238", + "5.160.118.86", + "5.160.118.93", + "5.160.118.110", + "5.160.118.178", + "5.160.118.186", + "5.160.119.130", + "5.160.119.210", + "5.160.119.225", + "5.160.119.228", + "5.160.119.242", + "5.160.119.250", + "5.160.120.14", + "5.160.121.6", + "5.160.122.150", + "5.160.122.226", + "5.160.123.138", + "5.160.124.155", + "5.160.125.38", + "5.160.125.46", + "5.160.125.138", + "5.160.125.186", + "5.160.125.234", + "5.160.126.139", + "5.160.127.67", + "5.160.127.68", + "5.160.127.69", + "5.160.127.70", + "5.160.127.90", + "5.160.129.118", + "5.160.134.82", + "5.160.134.83", + "5.160.134.84", + "5.160.134.85", + "5.160.134.86", + "5.160.135.46", + "5.160.136.122", + "5.160.136.162", + "5.160.137.54", + "5.160.138.227", + "5.160.143.225", + "5.160.144.9", + "5.160.144.15", + "5.160.144.19", + "5.160.146.199", + "5.160.146.233", + "5.160.147.18", + "5.160.147.226", + "5.160.148.49", + "5.160.150.138", + "5.160.150.139", + "5.160.150.140", + "5.160.151.204", + "5.160.151.206", + "5.160.152.166", + "5.160.157.75", + "5.160.157.100", + "5.160.159.84", + "5.160.159.178", + "5.160.159.181", + "5.160.159.189", + "5.160.160.42", + "5.160.161.5", + "5.160.161.18", + "5.160.161.19", + "5.160.161.33", + "5.160.161.41", + "5.160.161.45", + "5.160.161.62", + "5.160.161.66", + "5.160.161.73", + "5.160.161.84", + "5.160.161.98", + "5.160.161.99", + "5.160.161.101", + "5.160.161.117", + "5.160.161.121", + "5.160.161.122", + "5.160.161.123", + "5.160.161.127", + "5.160.161.129", + "5.160.161.142", + "5.160.161.148", + "5.160.161.152", + "5.160.161.168", + "5.160.161.176", + "5.160.161.179", + "5.160.161.180", + "5.160.161.187", + "5.160.161.197", + "5.160.161.200", + "5.160.161.219", + "5.160.162.43", + "5.160.162.44", + "5.160.164.86", + "5.160.164.93", + "5.160.164.211", + "5.160.166.20", + "5.160.167.14", + "5.160.167.60", + "5.160.168.10", + "5.160.168.85", + "5.160.168.130", + "5.160.170.2", + "5.160.171.36", + "5.160.171.47", + "5.160.173.151", + "5.160.173.190", + "5.160.175.250", + "5.160.176.27", + "5.160.176.165", + "5.160.176.208", + "5.160.176.222", + "5.160.176.242", + "5.160.176.243", + "5.160.176.245", + "5.160.177.6", + "5.160.178.50", + "5.160.178.155", + "5.160.178.157", + "5.160.182.45", + "5.160.182.65", + "5.160.182.73", + "5.160.182.77", + "5.160.182.85", + "5.160.182.88", + "5.160.182.90", + "5.160.182.101", + "5.160.182.111", + "5.160.182.119", + "5.160.182.127", + "5.160.182.130", + "5.160.182.146", + "5.160.182.155", + "5.160.182.168", + "5.160.182.180", + "5.160.182.184", + "5.160.182.197", + "5.160.182.201", + "5.160.182.208", + "5.160.182.211", + "5.160.182.219", + "5.160.182.228", + "5.160.182.229", + "5.160.182.236", + "5.160.182.245", + "5.160.183.150", + "5.160.184.90", + "5.160.184.202", + "5.160.185.86", + "5.160.185.122", + "5.160.185.222", + "5.160.186.14", + "5.160.186.42", + "5.160.186.43", + "5.160.186.44", + "5.160.186.45", + "5.160.187.44", + "5.160.187.78", + "5.160.187.98", + "5.160.187.122", + "5.160.187.126", + "5.160.187.247", + "5.160.187.254", + "5.160.190.34", + "5.160.190.38", + "5.160.191.197", + "5.160.191.242", + "5.160.191.243", + "5.160.196.126", + "5.160.196.210", + "5.160.197.207", + "5.160.200.182", + "5.160.211.170", + "5.160.211.218", + "5.160.211.219", + "5.160.211.220", + "5.160.214.110", + "5.160.215.230", + "5.160.217.254", + "5.160.218.167", + "5.160.219.72", + "5.160.223.254", + "5.160.224.225", + "5.160.224.226", + "5.160.224.227", + "5.160.224.228", + "5.160.224.229", + "5.160.225.66", + "5.160.225.116", + "5.160.225.163", + "5.160.225.174", + "5.160.227.238", + "5.160.228.190", + "5.160.229.187", + "5.160.229.234", + "5.160.230.130", + "5.160.230.250", + "5.160.231.10", + "5.160.231.130", + "5.160.231.214", + "5.160.233.16", + "5.160.233.150", + "5.160.233.226", + "5.160.234.189", + "5.160.235.130", + "5.160.236.155", + "5.160.236.158", + "5.160.236.216", + "5.160.237.118", + "5.160.237.208", + "5.160.239.98", + "5.160.239.226", + "5.160.239.250", + "5.160.239.251", + "5.160.242.48", + "5.160.243.107", + "5.160.243.238", + "5.160.243.254", + "5.160.246.29", + "5.160.247.50", + "5.160.247.230", + "5.182.44.18", + "5.182.44.124", + "5.182.44.132", + "5.182.44.134", + "5.182.44.161", + "5.182.44.184", + "5.182.44.190", + "5.182.46.185", + "5.190.0.68", + "5.190.0.178", + "5.190.1.71", + "5.190.1.79", + "5.190.1.145", + "5.190.1.161", + "5.190.3.109", + "5.190.3.110", + "5.190.3.113", + "5.190.4.17", + "5.190.4.249", + "5.190.6.41", + "5.190.6.73", + "5.190.6.241", + "5.190.7.113", + "5.190.20.1", + "5.190.29.161", + "5.190.45.70", + "5.190.52.225", + "5.190.53.6", + "5.190.60.161", + "5.190.69.68", + "5.190.69.70", + "5.190.69.73", + "5.190.69.74", + "5.190.69.75", + "5.190.69.80", + "5.190.69.89", + "5.190.72.113", + "5.190.83.70", + "5.190.83.77", + "5.190.84.112", + "5.190.89.101", + "5.190.93.133", + "5.190.103.130", + "5.190.106.210", + "5.190.131.1", + "5.190.131.230", + "5.190.135.173", + "5.190.138.125", + "5.190.143.81", + "5.190.147.1", + "5.190.147.3", + "5.190.154.164", + "5.190.156.3", + "5.190.156.4", + "5.190.212.241", + "5.190.212.242", + "5.190.212.243", + "5.190.212.245", + "5.190.212.246", + "5.190.212.247", + "5.190.212.251", + "5.190.212.252", + "5.190.224.15", + "5.190.224.23", + "5.190.224.27", + "5.190.224.37", + "5.190.224.39", + "5.190.235.10", + "5.190.235.35", + "5.190.235.74", + "5.190.235.81", + "5.190.235.117", + "5.190.235.131", + "5.190.235.160", + "5.190.235.163", + "5.190.235.168", + "5.190.235.171", + "5.190.235.183", + "5.190.235.196", + "5.190.235.209", + "5.190.235.214", + "5.190.235.247", + "5.190.235.249", + "5.190.235.254", + "5.198.161.12", + "5.198.178.64", + "5.198.178.80", + "5.198.178.160", + "5.198.178.180", + "5.198.178.237", + "5.198.179.20", + "5.198.179.50", + "5.198.179.130", + "5.198.179.190", + "5.200.64.193", + "5.200.67.24", + "5.200.69.30", + "5.200.73.82", + "5.200.73.83", + "5.200.73.84", + "5.200.73.162", + "5.200.74.66", + "5.200.74.90", + "5.200.74.91", + "5.200.74.92", + "5.200.79.14", + "5.200.84.129", + "5.200.94.45", + "5.200.151.10", + "5.200.160.21", + "5.200.161.223", + "5.200.165.21", + "5.200.177.233", + "5.200.194.25", + "5.200.200.26", + "5.200.217.213", + "5.200.240.10", + "5.200.246.248", + "5.201.128.76", + "5.201.129.44", + "5.201.130.70", + "5.201.130.106", + "5.201.131.60", + "5.201.132.27", + "5.201.132.139", + "5.201.132.143", + "5.201.132.201", + "5.201.132.246", + "5.201.133.78", + "5.201.133.162", + "5.201.134.23", + "5.201.134.222", + "5.201.135.104", + "5.201.135.127", + "5.201.135.162", + "5.201.136.54", + "5.201.137.187", + "5.201.138.9", + "5.201.139.194", + "5.201.141.202", + "5.201.141.246", + "5.201.142.74", + "5.201.142.205", + "5.201.142.220", + "5.201.143.155", + "5.201.144.80", + "5.201.148.92", + "5.201.150.103", + "5.201.151.139", + "5.201.152.225", + "5.201.154.29", + "5.201.154.149", + "5.201.156.81", + "5.201.159.173", + "5.201.161.182", + "5.201.161.210", + "5.201.161.211", + "5.201.161.212", + "5.201.161.213", + "5.201.162.150", + "5.201.164.167", + "5.201.166.105", + "5.201.166.132", + "5.201.166.143", + "5.201.166.182", + "5.201.167.42", + "5.201.167.223", + "5.201.168.123", + "5.201.169.48", + "5.201.169.124", + "5.201.169.191", + "5.201.170.24", + "5.201.171.57", + "5.201.175.251", + "5.201.176.244", + "5.201.177.48", + "5.201.177.68", + "5.201.177.93", + "5.201.178.6", + "5.201.178.10", + "5.201.180.250", + "5.201.181.247", + "5.201.183.202", + "5.201.184.237", + "5.201.185.40", + "5.201.185.163", + "5.201.187.150", + "5.201.187.226", + "5.201.190.57", + "5.201.190.239", + "5.201.191.11", + "5.201.191.114", + "5.201.191.255", + "5.202.5.114", + "5.202.6.77", + "5.202.6.129", + "5.202.6.194", + "5.202.6.196", + "5.202.6.197", + "5.202.6.204", + "5.202.6.206", + "5.202.7.19", + "5.202.7.226", + "5.202.7.230", + "5.202.7.232", + "5.202.7.234", + "5.202.7.238", + "5.202.8.43", + "5.202.8.186", + "5.202.8.187", + "5.202.10.40", + "5.202.15.213", + "5.202.15.218", + "5.202.15.219", + "5.202.15.222", + "5.202.18.113", + "5.202.18.134", + "5.202.18.169", + "5.202.18.208", + "5.202.18.210", + "5.202.18.211", + "5.202.18.212", + "5.202.18.213", + "5.202.18.216", + "5.202.18.217", + "5.202.18.218", + "5.202.18.219", + "5.202.18.220", + "5.202.18.221", + "5.202.18.222", + "5.202.18.223", + "5.202.20.11", + "5.202.20.17", + "5.202.20.44", + "5.202.20.53", + "5.202.20.54", + "5.202.20.60", + "5.202.20.79", + "5.202.20.81", + "5.202.20.149", + "5.202.20.157", + "5.202.20.161", + "5.202.20.173", + "5.202.20.178", + "5.202.20.184", + "5.202.20.196", + "5.202.20.197", + "5.202.20.207", + "5.202.20.242", + "5.202.21.11", + "5.202.21.45", + "5.202.21.57", + "5.202.21.91", + "5.202.21.136", + "5.202.21.137", + "5.202.21.245", + "5.202.27.14", + "5.202.27.142", + "5.202.29.35", + "5.202.29.196", + "5.202.31.64", + "5.202.31.65", + "5.202.33.2", + "5.202.33.9", + "5.202.33.11", + "5.202.33.32", + "5.202.33.50", + "5.202.33.68", + "5.202.33.86", + "5.202.33.123", + "5.202.33.126", + "5.202.33.151", + "5.202.33.165", + "5.202.33.179", + "5.202.33.216", + "5.202.33.237", + "5.202.39.129", + "5.202.44.20", + "5.202.44.103", + "5.202.44.106", + "5.202.44.112", + "5.202.44.162", + "5.202.44.181", + "5.202.44.189", + "5.202.44.192", + "5.202.44.196", + "5.202.44.208", + "5.202.44.222", + "5.202.44.229", + "5.202.45.20", + "5.202.45.41", + "5.202.45.53", + "5.202.45.67", + "5.202.45.92", + "5.202.45.110", + "5.202.45.130", + "5.202.45.185", + "5.202.45.201", + "5.202.45.233", + "5.202.46.0", + "5.202.46.13", + "5.202.46.38", + "5.202.46.69", + "5.202.46.74", + "5.202.46.98", + "5.202.46.109", + "5.202.46.124", + "5.202.46.162", + "5.202.46.182", + "5.202.46.191", + "5.202.46.210", + "5.202.46.221", + "5.202.47.0", + "5.202.47.5", + "5.202.47.29", + "5.202.47.64", + "5.202.47.71", + "5.202.47.107", + "5.202.47.108", + "5.202.47.141", + "5.202.47.215", + "5.202.47.253", + "5.202.47.255", + "5.202.50.73", + "5.202.50.113", + "5.202.50.114", + "5.202.50.115", + "5.202.50.116", + "5.202.50.118", + "5.202.51.81", + "5.202.51.82", + "5.202.51.83", + "5.202.51.84", + "5.202.52.6", + "5.202.52.81", + "5.202.52.138", + "5.202.52.155", + "5.202.53.100", + "5.202.53.158", + "5.202.53.161", + "5.202.53.174", + "5.202.53.186", + "5.202.60.14", + "5.202.60.202", + "5.202.60.242", + "5.202.61.160", + "5.202.61.192", + "5.202.63.40", + "5.202.63.58", + "5.202.63.60", + "5.202.63.62", + "5.202.63.125", + "5.202.63.210", + "5.202.66.2", + "5.202.66.9", + "5.202.67.1", + "5.202.67.134", + "5.202.68.10", + "5.202.68.13", + "5.202.68.14", + "5.202.68.18", + "5.202.68.21", + "5.202.68.23", + "5.202.68.34", + "5.202.68.38", + "5.202.68.65", + "5.202.68.90", + "5.202.68.116", + "5.202.68.150", + "5.202.73.50", + "5.202.73.134", + "5.202.77.46", + "5.202.77.53", + "5.202.78.8", + "5.202.78.18", + "5.202.78.23", + "5.202.78.35", + "5.202.79.163", + "5.202.79.164", + "5.202.79.194", + "5.202.79.203", + "5.202.79.210", + "5.202.79.213", + "5.202.79.214", + "5.202.79.215", + "5.202.79.217", + "5.202.79.220", + "5.202.79.222", + "5.202.79.225", + "5.202.79.227", + "5.202.79.228", + "5.202.79.229", + "5.202.82.102", + "5.202.82.118", + "5.202.82.146", + "5.202.82.190", + "5.202.83.5", + "5.202.83.59", + "5.202.83.102", + "5.202.83.118", + "5.202.84.9", + "5.202.84.21", + "5.202.84.47", + "5.202.84.63", + "5.202.84.68", + "5.202.84.85", + "5.202.84.101", + "5.202.84.150", + "5.202.84.152", + "5.202.84.161", + "5.202.84.178", + "5.202.84.207", + "5.202.84.219", + "5.202.84.247", + "5.202.85.43", + "5.202.85.66", + "5.202.85.154", + "5.202.85.186", + "5.202.85.255", + "5.202.86.26", + "5.202.89.41", + "5.202.89.89", + "5.202.89.90", + "5.202.90.40", + "5.202.90.51", + "5.202.90.56", + "5.202.90.79", + "5.202.90.93", + "5.202.90.99", + "5.202.90.103", + "5.202.90.157", + "5.202.90.170", + "5.202.90.195", + "5.202.90.196", + "5.202.90.204", + "5.202.90.214", + "5.202.90.221", + "5.202.93.144", + "5.202.93.207", + "5.202.96.3", + "5.202.96.9", + "5.202.96.10", + "5.202.96.11", + "5.202.96.31", + "5.202.96.32", + "5.202.96.37", + "5.202.96.38", + "5.202.96.49", + "5.202.96.50", + "5.202.96.51", + "5.202.96.57", + "5.202.96.66", + "5.202.96.78", + "5.202.96.83", + "5.202.96.87", + "5.202.96.92", + "5.202.96.101", + "5.202.96.109", + "5.202.96.111", + "5.202.96.118", + "5.202.96.121", + "5.202.96.126", + "5.202.96.131", + "5.202.96.134", + "5.202.96.140", + "5.202.96.146", + "5.202.96.147", + "5.202.96.160", + "5.202.96.164", + "5.202.96.171", + "5.202.96.175", + "5.202.96.177", + "5.202.97.19", + "5.202.97.20", + "5.202.97.23", + "5.202.97.24", + "5.202.97.46", + "5.202.97.49", + "5.202.97.51", + "5.202.97.59", + "5.202.97.74", + "5.202.97.79", + "5.202.97.81", + "5.202.97.93", + "5.202.97.94", + "5.202.97.100", + "5.202.97.103", + "5.202.97.107", + "5.202.97.124", + "5.202.97.126", + "5.202.97.128", + "5.202.101.89", + "5.202.104.12", + "5.202.104.14", + "5.202.104.34", + "5.202.104.62", + "5.202.104.158", + "5.202.104.166", + "5.202.104.220", + "5.202.105.9", + "5.202.105.24", + "5.202.105.31", + "5.202.105.104", + "5.202.105.111", + "5.202.105.119", + "5.202.105.120", + "5.202.105.154", + "5.202.105.208", + "5.202.105.229", + "5.202.105.240", + "5.202.105.253", + "5.202.112.10", + "5.202.112.118", + "5.202.112.154", + "5.202.112.191", + "5.202.112.197", + "5.202.114.92", + "5.202.120.35", + "5.202.120.43", + "5.202.120.82", + "5.202.120.96", + "5.202.120.97", + "5.202.120.114", + "5.202.120.123", + "5.202.120.126", + "5.202.120.163", + "5.202.120.164", + "5.202.120.169", + "5.202.121.5", + "5.202.121.6", + "5.202.121.26", + "5.202.121.154", + "5.202.121.164", + "5.202.121.222", + "5.202.128.225", + "5.202.130.2", + "5.202.130.7", + "5.202.130.41", + "5.202.130.89", + "5.202.130.110", + "5.202.130.112", + "5.202.130.179", + "5.202.130.188", + "5.202.130.195", + "5.202.130.208", + "5.202.130.213", + "5.202.130.223", + "5.202.130.224", + "5.202.130.237", + "5.202.130.239", + "5.202.130.244", + "5.202.130.249", + "5.202.131.12", + "5.202.131.21", + "5.202.131.37", + "5.202.131.38", + "5.202.131.59", + "5.202.131.68", + "5.202.131.95", + "5.202.131.151", + "5.202.131.152", + "5.202.131.160", + "5.202.131.161", + "5.202.131.162", + "5.202.131.170", + "5.202.131.190", + "5.202.131.199", + "5.202.131.203", + "5.202.131.209", + "5.202.131.212", + "5.202.131.224", + "5.202.132.5", + "5.202.132.9", + "5.202.132.36", + "5.202.132.43", + "5.202.132.55", + "5.202.132.77", + "5.202.132.83", + "5.202.132.97", + "5.202.132.119", + "5.202.132.123", + "5.202.132.124", + "5.202.132.128", + "5.202.132.133", + "5.202.132.150", + "5.202.132.189", + "5.202.132.193", + "5.202.132.197", + "5.202.132.200", + "5.202.132.204", + "5.202.132.217", + "5.202.132.228", + "5.202.132.231", + "5.202.132.232", + "5.202.132.233", + "5.202.132.252", + "5.202.132.254", + "5.202.133.0", + "5.202.133.3", + "5.202.133.40", + "5.202.133.50", + "5.202.133.56", + "5.202.133.60", + "5.202.133.69", + "5.202.133.84", + "5.202.133.110", + "5.202.133.135", + "5.202.133.139", + "5.202.133.144", + "5.202.133.146", + "5.202.133.160", + "5.202.133.163", + "5.202.133.166", + "5.202.133.168", + "5.202.133.231", + "5.202.133.244", + "5.202.134.9", + "5.202.134.38", + "5.202.134.74", + "5.202.134.99", + "5.202.134.117", + "5.202.134.131", + "5.202.134.133", + "5.202.134.166", + "5.202.134.180", + "5.202.134.196", + "5.202.134.252", + "5.202.135.8", + "5.202.135.14", + "5.202.135.17", + "5.202.135.23", + "5.202.135.65", + "5.202.135.93", + "5.202.135.96", + "5.202.135.119", + "5.202.135.132", + "5.202.135.134", + "5.202.135.140", + "5.202.135.155", + "5.202.135.162", + "5.202.135.165", + "5.202.135.201", + "5.202.135.221", + "5.202.135.226", + "5.202.135.236", + "5.202.135.243", + "5.202.135.254", + "5.202.136.0", + "5.202.136.3", + "5.202.140.48", + "5.202.140.49", + "5.202.140.50", + "5.202.140.51", + "5.202.140.52", + "5.202.140.53", + "5.202.140.54", + "5.202.140.55", + "5.202.140.65", + "5.202.140.66", + "5.202.140.67", + "5.202.140.68", + "5.202.140.69", + "5.202.140.70", + "5.202.140.80", + "5.202.140.82", + "5.202.140.83", + "5.202.140.84", + "5.202.140.85", + "5.202.140.86", + "5.202.140.87", + "5.202.140.88", + "5.202.140.89", + "5.202.140.90", + "5.202.140.91", + "5.202.140.93", + "5.202.140.94", + "5.202.162.67", + "5.202.162.75", + "5.202.162.76", + "5.202.163.9", + "5.202.163.13", + "5.202.163.80", + "5.202.163.158", + "5.202.163.183", + "5.202.164.118", + "5.202.165.112", + "5.202.166.250", + "5.202.166.254", + "5.202.167.1", + "5.202.167.30", + "5.202.167.108", + "5.202.167.222", + "5.202.167.252", + "5.202.168.0", + "5.202.168.18", + "5.202.168.53", + "5.202.168.71", + "5.202.168.93", + "5.202.168.117", + "5.202.168.133", + "5.202.168.140", + "5.202.168.149", + "5.202.168.166", + "5.202.168.173", + "5.202.168.178", + "5.202.168.180", + "5.202.168.196", + "5.202.168.207", + "5.202.168.211", + "5.202.169.2", + "5.202.169.29", + "5.202.169.33", + "5.202.169.38", + "5.202.169.80", + "5.202.169.119", + "5.202.169.142", + "5.202.169.145", + "5.202.169.155", + "5.202.169.161", + "5.202.169.162", + "5.202.169.191", + "5.202.169.192", + "5.202.169.198", + "5.202.170.33", + "5.202.170.63", + "5.202.170.100", + "5.202.170.110", + "5.202.170.111", + "5.202.170.145", + "5.202.170.153", + "5.202.170.163", + "5.202.170.178", + "5.202.170.181", + "5.202.170.187", + "5.202.170.214", + "5.202.170.219", + "5.202.170.242", + "5.202.171.35", + "5.202.171.45", + "5.202.171.62", + "5.202.171.68", + "5.202.171.82", + "5.202.171.102", + "5.202.171.105", + "5.202.171.108", + "5.202.171.120", + "5.202.171.124", + "5.202.171.128", + "5.202.171.229", + "5.202.172.3", + "5.202.172.14", + "5.202.172.27", + "5.202.172.36", + "5.202.172.56", + "5.202.172.65", + "5.202.172.67", + "5.202.172.70", + "5.202.172.134", + "5.202.172.157", + "5.202.172.176", + "5.202.172.189", + "5.202.172.200", + "5.202.172.225", + "5.202.172.233", + "5.202.172.237", + "5.202.172.239", + "5.202.172.254", + "5.202.174.27", + "5.202.174.28", + "5.202.174.33", + "5.202.174.56", + "5.202.174.73", + "5.202.174.90", + "5.202.174.170", + "5.202.174.183", + "5.202.174.225", + "5.202.174.229", + "5.202.174.233", + "5.202.174.245", + "5.202.175.1", + "5.202.175.46", + "5.202.175.52", + "5.202.175.121", + "5.202.175.143", + "5.202.175.148", + "5.202.175.165", + "5.202.175.169", + "5.202.175.179", + "5.202.175.193", + "5.202.175.207", + "5.202.175.227", + "5.202.175.242", + "5.202.176.154", + "5.202.176.166", + "5.202.176.170", + "5.202.177.45", + "5.202.177.50", + "5.202.177.51", + "5.202.177.52", + "5.202.177.53", + "5.202.177.54", + "5.202.177.114", + "5.202.177.115", + "5.202.177.116", + "5.202.177.118", + "5.202.177.122", + "5.202.177.166", + "5.202.177.194", + "5.202.177.195", + "5.202.177.196", + "5.202.177.197", + "5.202.177.198", + "5.202.177.254", + "5.202.178.67", + "5.202.178.86", + "5.202.179.2", + "5.202.179.3", + "5.202.179.4", + "5.202.179.5", + "5.202.179.6", + "5.202.179.7", + "5.202.179.8", + "5.202.179.9", + "5.202.179.10", + "5.202.179.12", + "5.202.179.13", + "5.202.179.14", + "5.202.179.50", + "5.202.179.138", + "5.202.179.170", + "5.202.179.171", + "5.202.179.172", + "5.202.179.173", + "5.202.179.174", + "5.202.180.46", + "5.202.182.2", + "5.202.182.102", + "5.202.182.210", + "5.202.182.222", + "5.202.184.90", + "5.202.184.242", + "5.202.185.38", + "5.202.185.206", + "5.202.186.70", + "5.202.186.229", + "5.202.188.2", + "5.202.188.211", + "5.202.188.212", + "5.202.188.213", + "5.202.189.18", + "5.202.189.242", + "5.202.190.14", + "5.202.190.18", + "5.202.190.19", + "5.202.190.20", + "5.202.190.217", + "5.202.191.42", + "5.202.191.78", + "5.202.191.126", + "5.202.191.186", + "5.202.191.225", + "5.202.191.245", + "5.202.193.14", + "5.202.193.29", + "5.202.193.61", + "5.202.193.62", + "5.202.193.77", + "5.202.193.85", + "5.202.193.102", + "5.202.193.115", + "5.202.193.134", + "5.202.193.147", + "5.202.193.152", + "5.202.193.210", + "5.202.193.225", + "5.202.193.229", + "5.202.194.249", + "5.202.195.4", + "5.202.195.14", + "5.202.195.16", + "5.202.195.83", + "5.202.195.97", + "5.202.195.133", + "5.202.195.143", + "5.202.195.147", + "5.202.195.162", + "5.202.195.165", + "5.202.195.205", + "5.202.195.217", + "5.202.196.7", + "5.202.196.14", + "5.202.196.16", + "5.202.196.35", + "5.202.196.54", + "5.202.196.62", + "5.202.196.75", + "5.202.196.76", + "5.202.196.81", + "5.202.196.103", + "5.202.196.105", + "5.202.196.184", + "5.202.196.198", + "5.202.196.206", + "5.202.196.246", + "5.202.196.253", + "5.202.197.2", + "5.202.197.3", + "5.202.197.9", + "5.202.197.27", + "5.202.197.60", + "5.202.197.61", + "5.202.197.102", + "5.202.197.103", + "5.202.197.111", + "5.202.197.201", + "5.202.197.217", + "5.202.197.224", + "5.202.197.227", + "5.202.197.241", + "5.202.198.11", + "5.202.198.23", + "5.202.198.27", + "5.202.198.69", + "5.202.198.79", + "5.202.198.85", + "5.202.198.102", + "5.202.198.107", + "5.202.198.116", + "5.202.198.124", + "5.202.198.136", + "5.202.206.115", + "5.202.236.68", + "5.202.239.59", + "5.202.240.32", + "5.202.240.34", + "5.202.240.35", + "5.202.240.38", + "5.202.240.39", + "5.202.240.42", + "5.202.240.45", + "5.202.241.54", + "5.202.243.0", + "5.202.243.4", + "5.202.243.5", + "5.202.243.106", + "5.202.243.134", + "5.202.243.142", + "5.202.243.147", + "5.202.243.157", + "5.202.243.165", + "5.202.243.191", + "5.202.243.226", + "5.202.244.4", + "5.202.244.38", + "5.202.244.44", + "5.202.244.98", + "5.202.244.127", + "5.202.244.129", + "5.202.244.134", + "5.202.244.181", + "5.202.244.184", + "5.202.244.211", + "5.202.245.11", + "5.202.245.38", + "5.202.245.42", + "5.202.248.20", + "5.202.248.36", + "5.202.248.47", + "5.202.248.48", + "5.202.248.58", + "5.202.248.68", + "5.202.248.70", + "5.202.248.74", + "5.202.248.78", + "5.202.248.93", + "5.202.248.106", + "5.202.248.107", + "5.202.248.111", + "5.202.248.148", + "5.202.248.190", + "5.202.248.194", + "5.202.248.196", + "5.202.248.200", + "5.202.248.219", + "5.202.249.21", + "5.202.249.169", + "5.202.249.170", + "5.202.249.178", + "5.202.250.10", + "5.202.250.81", + "5.202.250.153", + "5.202.250.155", + "5.202.250.156", + "5.202.250.157", + "5.202.251.32", + "5.202.251.46", + "5.202.251.57", + "5.202.251.80", + "5.202.251.94", + "5.202.251.116", + "5.202.251.152", + "5.202.251.162", + "5.202.251.212", + "5.202.251.235", + "5.202.251.237", + "5.202.251.243", + "5.202.251.254", + "5.202.252.27", + "5.202.252.28", + "5.202.252.29", + "5.202.252.126", + "5.202.252.250", + "5.202.252.252", + "5.202.252.253", + "5.202.253.3", + "5.202.253.6", + "5.202.253.10", + "5.202.253.11", + "5.202.253.13", + "5.202.253.14", + "5.202.253.46", + "5.202.253.52", + "5.202.253.53", + "5.202.253.54", + "5.202.253.122", + "5.202.253.146", + "5.202.254.10", + "5.202.254.18", + "5.202.254.29", + "5.202.254.154", + "5.202.254.174", + "5.202.254.242", + "5.202.255.107", + "5.202.255.108", + "5.219.64.1", + "5.219.128.1", + "5.232.97.110", + "5.232.103.33", + "5.232.104.21", + "5.232.116.189", + "5.232.119.40", + "5.232.119.66", + "5.232.124.227", + "5.232.126.173", + "5.232.128.72", + "5.232.128.198", + "5.232.132.201", + "5.232.140.84", + "5.232.140.119", + "5.232.141.89", + "5.232.144.160", + "5.232.149.174", + "5.232.155.22", + "5.232.157.90", + "5.232.176.234", + "5.232.177.130", + "5.232.177.148", + "5.232.178.50", + "5.232.178.196", + "5.232.179.44", + "5.232.179.185", + "5.232.181.38", + "5.232.181.208", + "5.232.182.146", + "5.232.183.4", + "5.232.184.206", + "5.232.185.3", + "5.232.185.61", + "5.232.185.215", + "5.232.186.151", + "5.232.186.218", + "5.232.186.219", + "5.232.187.186", + "5.232.188.152", + "5.232.188.188", + "5.232.189.68", + "5.232.189.145", + "5.232.190.238", + "5.232.191.43", + "5.232.191.97", + "5.232.192.52", + "5.232.192.99", + "5.232.192.180", + "5.232.193.62", + "5.232.193.180", + "5.232.193.202", + "5.232.193.222", + "5.232.194.139", + "5.232.194.165", + "5.232.195.238", + "5.232.196.102", + "5.232.197.1", + "5.232.197.13", + "5.232.197.158", + "5.232.197.168", + "5.232.197.200", + "5.232.198.17", + "5.232.199.18", + "5.232.199.107", + "5.232.199.148", + "5.232.200.85", + "5.232.201.113", + "5.232.201.169", + "5.232.201.232", + "5.232.201.250", + "5.232.202.205", + "5.232.202.216", + "5.232.204.169", + "5.232.205.170", + "5.232.207.56", + "5.232.208.249", + "5.232.209.119", + "5.232.211.28", + "5.232.213.42", + "5.232.213.59", + "5.232.213.136", + "5.232.214.164", + "5.232.215.34", + "5.232.215.220", + "5.232.215.226", + "5.232.238.245", + "5.233.44.59", + "5.233.44.61", + "5.233.94.81", + "5.233.94.140", + "5.233.94.174", + "5.233.205.157", + "5.233.205.182", + "5.233.248.224", + "5.233.249.52", + "5.233.254.88", + "5.233.254.232", + "5.233.255.221", + "5.234.0.57", + "5.234.0.96", + "5.234.0.203", + "5.234.2.29", + "5.234.2.132", + "5.234.3.51", + "5.234.3.194", + "5.234.5.251", + "5.234.10.9", + "5.234.11.43", + "5.234.12.47", + "5.234.13.112", + "5.234.14.122", + "5.234.14.219", + "5.234.15.194", + "5.234.19.84", + "5.234.20.156", + "5.234.20.211", + "5.234.21.169", + "5.234.21.250", + "5.234.22.63", + "5.234.24.218", + "5.234.25.103", + "5.234.26.54", + "5.234.26.138", + "5.234.26.148", + "5.234.28.73", + "5.234.28.126", + "5.234.28.147", + "5.234.29.197", + "5.234.32.10", + "5.234.32.62", + "5.234.32.75", + "5.234.32.129", + "5.234.33.230", + "5.234.34.89", + "5.234.35.195", + "5.234.44.68", + "5.234.44.226", + "5.234.46.178", + "5.234.68.222", + "5.234.91.74", + "5.234.166.182", + "5.234.179.43", + "5.234.215.102", + "5.235.152.156", + "5.235.152.182", + "5.235.153.120", + "5.235.155.61", + "5.235.155.221", + "5.235.156.42", + "5.235.156.147", + "5.235.156.149", + "5.235.157.238", + "5.235.158.235", + "5.235.159.31", + "5.235.160.173", + "5.235.161.106", + "5.235.162.106", + "5.235.163.30", + "5.235.163.180", + "5.235.164.199", + "5.235.164.236", + "5.235.166.121", + "5.235.170.173", + "5.235.171.180", + "5.235.171.231", + "5.235.173.118", + "5.235.174.61", + "5.235.175.27", + "5.235.176.46", + "5.235.176.192", + "5.235.176.234", + "5.235.177.148", + "5.235.179.135", + "5.235.180.142", + "5.235.181.200", + "5.235.182.66", + "5.235.182.193", + "5.235.182.229", + "5.235.183.189", + "5.235.185.142", + "5.235.185.176", + "5.235.186.75", + "5.235.186.161", + "5.235.186.211", + "5.235.186.226", + "5.235.186.227", + "5.235.187.111", + "5.235.187.127", + "5.235.189.58", + "5.235.195.238", + "5.235.196.83", + "5.235.196.136", + "5.235.197.225", + "5.235.198.83", + "5.235.199.92", + "5.235.201.252", + "5.235.202.22", + "5.235.204.30", + "5.235.204.191", + "5.235.205.157", + "5.235.219.169", + "5.235.220.184", + "5.235.220.187", + "5.235.222.138", + "5.235.223.201", + "5.235.224.154", + "5.235.225.248", + "5.235.226.101", + "5.235.226.173", + "5.235.226.211", + "5.235.227.158", + "5.235.227.195", + "5.235.228.165", + "5.235.229.169", + "5.235.230.173", + "5.235.230.195", + "5.235.231.205", + "5.235.232.161", + "5.235.233.39", + "5.235.234.58", + "5.235.234.154", + "5.235.234.176", + "5.235.235.10", + "5.235.238.60", + "5.235.239.32", + "5.235.241.41", + "5.235.241.121", + "5.235.243.12", + "5.235.245.242", + "5.235.247.164", + "5.235.250.43", + "5.235.252.190", + "5.235.253.14", + "5.235.253.137", + "5.235.254.8", + "5.235.255.101", + "5.236.25.31", + "5.236.26.247", + "5.236.28.53", + "5.236.29.178", + "5.236.30.168", + "5.236.31.208", + "5.236.36.45", + "5.236.36.47", + "5.236.36.66", + "5.236.36.85", + "5.236.36.100", + "5.236.36.113", + "5.236.36.145", + "5.236.67.229", + "5.236.68.165", + "5.236.69.213", + "5.236.71.98", + "5.236.87.208", + "5.236.93.14", + "5.236.93.48", + "5.236.93.174", + "5.236.93.234", + "5.236.96.19", + "5.236.96.23", + "5.236.96.40", + "5.236.96.60", + "5.236.108.235", + "5.236.109.224", + "5.236.113.178", + "5.236.114.159", + "5.236.114.235", + "5.236.115.128", + "5.236.115.251", + "5.236.117.60", + "5.236.119.154", + "5.236.124.194", + "5.236.192.40", + "5.236.193.100", + "5.236.194.126", + "5.236.195.79", + "5.236.195.96", + "5.236.195.201", + "5.236.209.244", + "5.236.212.244", + "5.236.213.53", + "5.236.213.78", + "5.236.213.179", + "5.236.219.109", + "5.236.220.70", + "5.236.225.244", + "5.236.226.78", + "5.236.226.188", + "5.236.229.139", + "5.236.235.2", + "5.236.242.203", + "5.236.245.102", + "5.236.246.11", + "5.236.246.159", + "5.236.251.75", + "5.237.55.14", + "5.237.142.130", + "5.237.176.90", + "5.237.192.208", + "5.237.193.127", + "5.237.194.226", + "5.237.195.188", + "5.237.196.7", + "5.237.197.52", + "5.237.200.253", + "5.237.201.159", + "5.237.202.87", + "5.237.204.168", + "5.237.208.237", + "5.237.209.121", + "5.237.211.73", + "5.237.211.98", + "5.237.211.231", + "5.237.217.54", + "5.237.217.64", + "5.237.223.84", + "5.237.223.146", + "5.237.226.189", + "5.237.229.54", + "5.237.229.226", + "5.237.230.76", + "5.237.231.170", + "5.237.232.125", + "5.237.232.243", + "5.237.233.144", + "5.237.233.191", + "5.237.234.34", + "5.237.236.43", + "5.237.238.207", + "5.237.240.53", + "5.237.240.103", + "5.237.241.179", + "5.237.241.183", + "5.237.242.60", + "5.237.243.14", + "5.237.243.198", + "5.237.247.15", + "5.237.247.195", + "5.237.248.197", + "5.237.249.142", + "5.237.249.242", + "5.237.253.187", + "5.237.255.14", + "5.237.255.166", + "5.238.3.90", + "5.238.17.31", + "5.238.52.159", + "5.238.52.186", + "5.238.52.195", + "5.238.52.234", + "5.238.52.236", + "5.238.52.241", + "5.238.74.210", + "5.238.94.243", + "5.238.110.7", + "5.238.110.8", + "5.238.110.29", + "5.238.110.64", + "5.238.110.210", + "5.238.111.39", + "5.238.111.134", + "5.238.128.92", + "5.238.131.6", + "5.238.131.21", + "5.238.131.179", + "5.238.131.223", + "5.238.132.182", + "5.238.132.208", + "5.238.132.228", + "5.238.134.53", + "5.238.134.155", + "5.238.134.212", + "5.238.135.45", + "5.238.135.73", + "5.238.136.160", + "5.238.137.1", + "5.238.137.175", + "5.238.139.45", + "5.238.139.62", + "5.238.139.231", + "5.238.140.227", + "5.238.140.248", + "5.238.143.149", + "5.238.143.243", + "5.238.144.25", + "5.238.146.116", + "5.238.147.99", + "5.238.153.65", + "5.238.153.70", + "5.238.153.102", + "5.238.153.107", + "5.238.153.138", + "5.238.156.6", + "5.238.157.206", + "5.238.158.183", + "5.238.159.102", + "5.238.159.184", + "5.238.160.134", + "5.238.161.24", + "5.238.162.17", + "5.238.162.31", + "5.238.162.90", + "5.238.163.60", + "5.238.163.142", + "5.238.164.40", + "5.238.164.44", + "5.238.165.168", + "5.238.166.7", + "5.238.166.55", + "5.238.167.171", + "5.238.168.83", + "5.238.169.99", + "5.238.169.101", + "5.238.169.107", + "5.238.171.30", + "5.238.171.107", + "5.238.171.143", + "5.238.172.214", + "5.238.174.56", + "5.238.174.129", + "5.238.174.245", + "5.238.176.1", + "5.238.176.59", + "5.238.176.95", + "5.238.177.5", + "5.238.177.236", + "5.238.177.239", + "5.238.181.65", + "5.238.184.197", + "5.238.185.39", + "5.238.185.123", + "5.238.186.41", + "5.238.187.168", + "5.238.189.167", + "5.238.190.228", + "5.238.191.5", + "5.238.195.78", + "5.238.195.138", + "5.238.196.51", + "5.238.196.107", + "5.238.196.129", + "5.238.196.169", + "5.238.197.118", + "5.238.199.24", + "5.238.202.108", + "5.238.203.16", + "5.238.203.28", + "5.238.203.154", + "5.238.204.82", + "5.238.204.224", + "5.238.205.112", + "5.238.206.175", + "5.238.207.60", + "5.238.208.233", + "5.238.210.11", + "5.238.210.66", + "5.238.210.236", + "5.238.211.85", + "5.238.211.121", + "5.238.211.148", + "5.238.212.191", + "5.238.213.86", + "5.238.214.195", + "5.238.217.57", + "5.238.217.219", + "5.238.218.144", + "5.238.218.227", + "5.238.223.38", + "5.238.227.11", + "5.238.227.85", + "5.238.227.104", + "5.238.227.171", + "5.238.227.173", + "5.238.228.116", + "5.238.229.131", + "5.238.229.246", + "5.238.230.79", + "5.238.232.178", + "5.238.232.190", + "5.238.234.48", + "5.238.235.95", + "5.238.237.166", + "5.238.239.165", + "5.238.240.235", + "5.238.241.45", + "5.238.241.116", + "5.238.242.137", + "5.238.242.204", + "5.238.245.225", + "5.238.246.114", + "5.238.246.145", + "5.238.247.72", + "5.238.247.89", + "5.238.248.90", + "5.238.249.80", + "5.238.250.153", + "5.238.251.77", + "5.238.251.170", + "5.238.252.216", + "5.238.254.51", + "5.238.255.98", + "5.239.0.243", + "5.239.1.191", + "5.239.2.25", + "5.239.2.223", + "5.239.3.128", + "5.239.3.164", + "5.239.7.14", + "5.239.7.49", + "5.239.7.104", + "5.239.8.209", + "5.239.9.11", + "5.239.9.56", + "5.239.9.183", + "5.239.9.228", + "5.239.10.48", + "5.239.11.58", + "5.239.11.197", + "5.239.12.67", + "5.239.13.160", + "5.239.14.202", + "5.239.15.7", + "5.239.15.157", + "5.239.17.149", + "5.239.17.177", + "5.239.18.248", + "5.239.21.24", + "5.239.21.85", + "5.239.22.32", + "5.239.22.107", + "5.239.22.173", + "5.239.23.71", + "5.239.23.138", + "5.239.24.181", + "5.239.25.176", + "5.239.26.131", + "5.239.26.170", + "5.239.26.174", + "5.239.28.44", + "5.239.30.38", + "5.239.30.171", + "5.239.31.129", + "5.239.31.205", + "5.239.32.77", + "5.239.33.6", + "5.239.34.56", + "5.239.35.31", + "5.239.35.252", + "5.239.36.41", + "5.239.37.171", + "5.239.38.117", + "5.239.42.232", + "5.239.43.35", + "5.239.44.129", + "5.239.44.132", + "5.239.45.109", + "5.239.45.164", + "5.239.45.178", + "5.239.45.211", + "5.239.46.185", + "5.239.46.239", + "5.239.49.138", + "5.239.50.156", + "5.239.51.83", + "5.239.53.5", + "5.239.53.127", + "5.239.55.249", + "5.239.56.81", + "5.239.56.218", + "5.239.58.175", + "5.239.58.192", + "5.239.59.106", + "5.239.59.250", + "5.239.60.38", + "5.239.60.45", + "5.239.62.32", + "5.239.63.47", + "5.239.63.100", + "5.239.63.159", + "5.239.63.216", + "5.239.64.246", + "5.239.66.72", + "5.239.66.141", + "5.239.66.143", + "5.239.66.212", + "5.239.69.80", + "5.239.70.40", + "5.239.70.212", + "5.239.71.72", + "5.239.71.251", + "5.239.73.218", + "5.239.73.230", + "5.239.75.6", + "5.239.75.79", + "5.239.75.145", + "5.239.76.59", + "5.239.76.241", + "5.239.78.125", + "5.239.79.2", + "5.239.80.135", + "5.239.81.222", + "5.239.82.151", + "5.239.83.12", + "5.239.84.66", + "5.239.84.170", + "5.239.85.99", + "5.239.86.243", + "5.239.86.253", + "5.239.88.48", + "5.239.88.163", + "5.239.89.252", + "5.239.89.254", + "5.239.91.70", + "5.239.91.80", + "5.239.91.161", + "5.239.92.104", + "5.239.96.85", + "5.239.99.99", + "5.239.102.203", + "5.239.103.229", + "5.239.104.116", + "5.239.104.243", + "5.239.107.132", + "5.239.108.58", + "5.239.108.81", + "5.239.108.221", + "5.239.109.52", + "5.239.110.66", + "5.239.110.108", + "5.239.112.30", + "5.239.112.157", + "5.239.114.208", + "5.239.115.216", + "5.239.117.1", + "5.239.117.174", + "5.239.117.179", + "5.239.118.56", + "5.239.118.198", + "5.239.118.217", + "5.239.119.100", + "5.239.119.178", + "5.239.119.183", + "5.239.119.207", + "5.239.121.137", + "5.239.121.216", + "5.239.124.95", + "5.239.125.35", + "5.239.127.128", + "5.239.127.172", + "5.239.149.170", + "5.239.157.217", + "5.239.158.188", + "5.239.160.15", + "5.239.160.75", + "5.239.160.105", + "5.239.160.125", + "5.239.160.143", + "5.239.161.33", + "5.239.161.49", + "5.239.161.87", + "5.239.161.109", + "5.239.161.147", + "5.239.161.178", + "5.239.161.194", + "5.239.176.2", + "5.239.177.48", + "5.239.177.69", + "5.239.177.163", + "5.239.177.237", + "5.239.197.37", + "5.239.199.8", + "5.239.202.231", + "5.239.230.12", + "5.239.231.88", + "5.239.237.119", + "5.239.239.25", + "5.239.240.88", + "5.239.240.91", + "5.239.240.193", + "5.239.240.197", + "5.239.240.200", + "5.239.240.238", + "5.239.241.12", + "5.239.241.28", + "5.239.241.71", + "5.239.241.195", + "5.239.245.137", + "5.239.245.192", + "5.239.245.240", + "5.239.245.245", + "5.239.245.249", + "5.239.245.250", + "5.252.216.3", + "5.252.216.4", + "31.7.64.4", + "31.7.64.6", + "31.7.64.81", + "31.7.65.18", + "31.7.65.22", + "31.7.65.55", + "31.7.65.57", + "31.7.65.82", + "31.7.65.83", + "31.7.66.75", + "31.7.66.146", + "31.7.66.147", + "31.7.68.249", + "31.7.69.220", + "31.7.71.9", + "31.7.72.60", + "31.7.72.146", + "31.7.73.38", + "31.7.73.67", + "31.7.73.77", + "31.7.73.85", + "31.7.73.132", + "31.7.74.49", + "31.7.78.16", + "31.7.78.26", + "31.7.78.46", + "31.7.78.47", + "31.7.78.109", + "31.7.91.159", + "31.7.91.166", + "31.7.110.218", + "31.14.112.210", + "31.14.113.237", + "31.14.114.83", + "31.14.114.84", + "31.14.114.85", + "31.14.114.86", + "31.14.114.87", + "31.14.114.88", + "31.14.114.89", + "31.14.114.90", + "31.14.114.91", + "31.14.114.92", + "31.14.114.94", + "31.14.114.165", + "31.14.117.116", + "31.14.117.178", + "31.14.117.180", + "31.14.117.226", + "31.14.117.227", + "31.14.118.123", + "31.14.118.125", + "31.14.118.126", + "31.14.119.162", + "31.14.119.166", + "31.14.120.54", + "31.14.124.45", + "31.14.124.195", + "31.14.126.31", + "31.14.126.40", + "31.14.126.85", + "31.14.126.88", + "31.14.126.120", + "31.14.127.139", + "31.14.127.183", + "31.24.201.108", + "31.25.90.14", + "31.25.90.43", + "31.25.91.7", + "31.25.92.13", + "31.25.92.15", + "31.25.92.24", + "31.25.92.31", + "31.25.92.44", + "31.25.92.63", + "31.25.92.64", + "31.25.92.71", + "31.25.92.73", + "31.25.92.74", + "31.25.92.97", + "31.25.92.138", + "31.25.92.153", + "31.25.92.173", + "31.25.92.194", + "31.25.92.198", + "31.25.92.205", + "31.25.92.214", + "31.25.92.243", + "31.25.93.8", + "31.25.93.18", + "31.25.93.45", + "31.25.93.163", + "31.25.94.1", + "31.25.94.106", + "31.25.95.2", + "31.25.95.32", + "31.25.95.184", + "31.25.95.216", + "31.25.128.105", + "31.25.128.132", + "31.25.129.39", + "31.25.129.163", + "31.25.129.247", + "31.25.130.8", + "31.25.130.228", + "31.25.130.239", + "31.25.131.164", + "31.25.132.30", + "31.25.132.49", + "31.25.132.85", + "31.25.132.253", + "31.25.134.99", + "31.25.134.148", + "31.25.134.150", + "31.25.134.233", + "31.25.134.240", + "31.25.135.50", + "31.25.135.80", + "31.25.135.81", + "31.25.135.93", + "31.25.135.173", + "31.25.135.236", + "31.47.32.34", + "31.47.33.10", + "31.47.33.11", + "31.47.33.12", + "31.47.33.194", + "31.47.34.42", + "31.47.34.74", + "31.47.35.34", + "31.47.39.194", + "31.47.45.227", + "31.47.48.3", + "31.47.48.4", + "31.47.48.5", + "31.47.48.6", + "31.47.48.7", + "31.47.48.8", + "31.47.48.10", + "31.47.48.11", + "31.47.48.13", + "31.47.48.15", + "31.47.48.19", + "31.47.48.22", + "31.47.51.55", + "31.47.52.27", + "31.47.52.110", + "31.47.54.90", + "31.47.58.98", + "31.47.61.190", + "31.47.61.234", + "31.130.181.241", + "31.130.181.254", + "31.130.182.12", + "31.130.182.13", + "31.170.49.245", + "31.170.50.59", + "31.170.52.61", + "31.170.52.106", + "31.170.52.132", + "31.170.52.191", + "31.170.52.197", + "31.170.52.248", + "31.170.53.0", + "31.170.53.86", + "31.170.57.21", + "31.170.57.71", + "31.170.58.30", + "31.170.59.26", + "31.170.59.98", + "31.170.59.133", + "31.170.59.225", + "31.170.60.16", + "31.170.60.20", + "31.170.60.90", + "31.170.60.99", + "31.170.60.111", + "31.170.60.146", + "31.170.60.182", + "31.170.60.210", + "31.170.60.230", + "31.170.61.161", + "31.170.61.165", + "31.170.61.173", + "31.170.61.237", + "31.184.129.77", + "31.184.129.106", + "31.184.144.34", + "31.184.147.78", + "31.184.147.88", + "31.184.147.149", + "31.184.147.161", + "31.184.147.170", + "31.184.166.2", + "31.184.167.2", + "31.184.170.80", + "31.184.170.98", + "31.184.170.100", + "31.184.170.150", + "31.184.170.169", + "31.184.170.178", + "31.184.170.234", + "31.184.171.110", + "31.184.171.216", + "31.184.175.2", + "31.184.176.176", + "31.184.177.136", + "31.184.178.11", + "31.184.178.224", + "31.184.178.228", + "31.184.179.218", + "31.184.179.219", + "31.184.188.1", + "31.184.188.8", + "31.184.188.10", + "31.184.188.14", + "31.184.188.18", + "31.184.188.26", + "31.184.188.27", + "31.184.188.28", + "31.184.188.42", + "31.184.188.45", + "31.184.188.62", + "31.184.188.64", + "31.184.188.70", + "31.184.188.74", + "31.184.188.81", + "31.184.188.91", + "31.184.188.99", + "31.184.188.105", + "31.184.188.119", + "31.184.188.120", + "31.184.188.131", + "31.184.188.135", + "31.184.188.136", + "31.184.188.137", + "31.184.188.143", + "31.184.188.161", + "31.184.188.163", + "31.184.188.166", + "31.184.188.170", + "31.184.188.174", + "31.184.188.179", + "31.184.188.184", + "31.184.188.191", + "31.184.188.201", + "31.184.188.205", + "31.184.188.216", + "31.184.188.221", + "31.184.188.224", + "31.184.188.226", + "31.184.188.227", + "31.184.188.230", + "31.184.188.231", + "31.184.188.232", + "31.184.188.239", + "31.184.189.10", + "31.184.189.22", + "31.184.189.27", + "31.184.189.29", + "31.184.189.35", + "31.184.189.41", + "31.184.189.46", + "31.184.189.48", + "31.184.189.50", + "31.184.189.57", + "31.184.189.67", + "31.184.189.68", + "31.184.189.88", + "31.184.189.97", + "31.184.189.98", + "31.184.189.109", + "31.184.189.120", + "31.184.189.127", + "31.184.189.146", + "31.184.189.151", + "31.184.189.152", + "31.184.189.153", + "31.184.189.157", + "31.184.189.179", + "31.184.189.184", + "31.184.189.187", + "31.184.189.199", + "31.184.189.208", + "31.184.189.213", + "31.184.189.233", + "31.184.189.234", + "31.184.189.236", + "31.184.189.237", + "31.184.189.246", + "31.184.190.2", + "31.184.190.10", + "31.184.190.51", + "31.184.190.56", + "31.184.190.64", + "31.184.190.77", + "31.184.190.84", + "31.184.190.100", + "31.184.190.101", + "31.184.190.104", + "31.184.190.111", + "31.184.190.114", + "31.184.190.136", + "31.184.190.145", + "31.184.190.158", + "31.184.190.180", + "31.184.190.182", + "31.184.190.185", + "31.184.190.194", + "31.184.190.202", + "31.184.190.208", + "31.184.190.219", + "31.184.190.228", + "31.184.190.231", + "31.184.190.241", + "31.184.190.245", + "31.184.190.254", + "31.184.191.2", + "31.184.191.6", + "31.184.191.7", + "31.184.191.12", + "31.184.191.20", + "31.184.191.22", + "31.184.191.23", + "31.184.191.28", + "31.184.191.34", + "31.184.191.47", + "31.184.191.48", + "31.184.191.54", + "31.184.191.62", + "31.184.191.74", + "31.184.191.94", + "31.184.191.100", + "31.184.191.112", + "31.184.191.116", + "31.184.191.125", + "31.184.191.126", + "31.184.191.127", + "31.184.191.132", + "31.184.191.138", + "31.184.191.146", + "31.184.191.159", + "31.184.191.160", + "31.184.191.166", + "31.184.191.184", + "31.184.191.199", + "31.184.191.201", + "31.184.191.213", + "31.184.191.215", + "31.184.191.220", + "31.184.191.226", + "31.184.191.228", + "31.184.191.235", + "31.184.191.242", + "31.184.191.253", + "31.214.147.255", + "31.214.168.66", + "31.214.168.104", + "31.214.168.178", + "31.214.171.136", + "31.214.171.226", + "31.214.172.4", + "31.214.172.58", + "31.214.173.75", + "31.214.173.77", + "31.214.174.130", + "31.214.175.241", + "31.214.231.120", + "31.214.248.242", + "31.214.250.18", + "31.214.250.115", + "31.214.251.237", + "31.214.255.52", + "31.214.255.164", + "31.214.255.173", + "31.216.62.242", + "37.10.67.11", + "37.10.109.148", + "37.32.4.222", + "37.32.5.5", + "37.32.5.25", + "37.32.5.59", + "37.32.5.119", + "37.32.5.215", + "37.32.6.67", + "37.32.6.194", + "37.32.9.83", + "37.32.10.126", + "37.32.11.191", + "37.32.11.204", + "37.32.12.30", + "37.32.12.84", + "37.32.12.231", + "37.32.13.25", + "37.32.13.30", + "37.32.14.62", + "37.32.14.81", + "37.32.14.200", + "37.32.15.39", + "37.32.21.0", + "37.32.21.21", + "37.32.21.148", + "37.32.24.21", + "37.32.24.49", + "37.32.25.86", + "37.32.25.134", + "37.32.25.227", + "37.32.26.37", + "37.32.27.56", + "37.32.27.68", + "37.32.40.38", + "37.32.43.74", + "37.32.43.75", + "37.32.43.77", + "37.32.43.78", + "37.32.43.80", + "37.32.43.81", + "37.32.43.82", + "37.32.43.83", + "37.32.43.84", + "37.32.43.85", + "37.32.43.86", + "37.32.43.87", + "37.32.43.88", + "37.32.43.89", + "37.32.43.90", + "37.32.43.91", + "37.32.43.92", + "37.32.43.93", + "37.32.43.197", + "37.32.45.96", + "37.32.46.140", + "37.32.47.181", + "37.32.47.199", + "37.32.112.2", + "37.32.117.5", + "37.32.120.100", + "37.32.120.137", + "37.32.121.48", + "37.32.121.53", + "37.32.121.76", + "37.32.124.16", + "37.32.125.131", + "37.32.125.192", + "37.32.125.193", + "37.32.125.195", + "37.32.127.132", + "37.32.127.163", + "37.75.240.1", + "37.75.240.2", + "37.75.240.20", + "37.75.240.21", + "37.75.240.30", + "37.75.240.65", + "37.75.242.89", + "37.75.242.179", + "37.75.242.188", + "37.75.243.61", + "37.75.243.62", + "37.75.243.82", + "37.75.243.146", + "37.75.243.154", + "37.75.243.195", + "37.75.243.254", + "37.75.244.22", + "37.75.244.26", + "37.75.244.50", + "37.75.244.52", + "37.75.244.53", + "37.75.244.122", + "37.75.245.77", + "37.75.245.80", + "37.75.245.131", + "37.75.246.54", + "37.75.246.138", + "37.75.246.241", + "37.75.246.242", + "37.75.246.243", + "37.75.246.244", + "37.75.246.246", + "37.75.247.176", + "37.75.247.177", + "37.75.247.178", + "37.75.247.182", + "37.98.112.30", + "37.98.112.210", + "37.98.118.100", + "37.114.204.42", + "37.114.204.114", + "37.114.204.230", + "37.114.205.66", + "37.114.205.67", + "37.114.205.68", + "37.114.205.69", + "37.114.205.70", + "37.114.205.74", + "37.114.225.82", + "37.114.225.84", + "37.114.225.85", + "37.114.225.138", + "37.114.246.46", + "37.114.246.196", + "37.114.247.50", + "37.114.247.202", + "37.130.200.3", + "37.130.200.29", + "37.130.202.36", + "37.130.205.2", + "37.130.207.2", + "37.143.144.8", + "37.143.146.240", + "37.143.147.34", + "37.143.147.58", + "37.143.147.78", + "37.143.147.123", + "37.143.147.133", + "37.143.147.147", + "37.143.147.150", + "37.143.148.3", + "37.143.148.9", + "37.143.148.20", + "37.143.148.31", + "37.143.148.36", + "37.143.148.72", + "37.143.148.75", + "37.143.148.83", + "37.143.148.99", + "37.143.148.100", + "37.143.148.114", + "37.143.148.120", + "37.143.148.153", + "37.143.148.164", + "37.143.148.184", + "37.143.148.188", + "37.143.148.192", + "37.143.148.208", + "37.143.148.252", + "37.143.148.253", + "37.143.149.0", + "37.143.149.1", + "37.143.149.2", + "37.143.149.3", + "37.143.149.4", + "37.143.149.5", + "37.143.149.7", + "37.143.149.8", + "37.143.149.9", + "37.143.149.10", + "37.143.149.11", + "37.143.149.12", + "37.143.149.13", + "37.143.149.14", + "37.143.149.15", + "37.143.149.16", + "37.143.149.18", + "37.143.149.20", + "37.143.149.21", + "37.143.149.22", + "37.143.149.23", + "37.143.149.24", + "37.143.149.25", + "37.143.149.26", + "37.143.149.27", + "37.143.149.28", + "37.143.149.29", + "37.143.149.30", + "37.143.149.31", + "37.143.149.32", + "37.143.149.33", + "37.143.149.34", + "37.143.149.35", + "37.143.149.36", + "37.143.149.37", + "37.143.149.38", + "37.143.149.39", + "37.143.149.40", + "37.143.149.41", + "37.143.149.42", + "37.143.149.43", + "37.143.149.44", + "37.143.149.45", + "37.143.149.46", + "37.143.149.47", + "37.143.149.48", + "37.143.149.49", + "37.143.149.50", + "37.143.149.51", + "37.143.149.52", + "37.143.149.53", + "37.143.149.54", + "37.143.149.55", + "37.143.149.57", + "37.143.149.58", + "37.143.149.59", + "37.143.149.64", + "37.143.149.65", + "37.143.149.67", + "37.143.149.68", + "37.143.149.69", + "37.143.149.70", + "37.143.149.71", + "37.143.149.72", + "37.143.149.73", + "37.143.149.74", + "37.143.149.75", + "37.143.149.76", + "37.143.149.77", + "37.143.149.78", + "37.143.149.79", + "37.143.149.80", + "37.143.149.82", + "37.143.149.83", + "37.143.149.84", + "37.143.149.85", + "37.143.149.86", + "37.143.149.87", + "37.143.149.88", + "37.143.149.89", + "37.143.149.90", + "37.143.149.92", + "37.143.149.93", + "37.143.149.94", + "37.143.149.95", + "37.143.149.96", + "37.143.149.97", + "37.143.149.98", + "37.143.149.99", + "37.143.149.100", + "37.143.149.101", + "37.143.149.102", + "37.143.149.103", + "37.143.149.104", + "37.143.149.105", + "37.143.149.106", + "37.143.149.107", + "37.143.149.108", + "37.143.149.109", + "37.143.149.110", + "37.143.149.111", + "37.143.149.112", + "37.143.149.113", + "37.143.149.114", + "37.143.149.116", + "37.143.149.117", + "37.143.149.118", + "37.143.149.119", + "37.143.149.120", + "37.143.149.121", + "37.143.149.122", + "37.143.149.123", + "37.143.149.124", + "37.143.149.125", + "37.143.149.126", + "37.143.149.127", + "37.143.151.40", + "37.143.151.78", + "37.143.151.82", + "37.143.151.89", + "37.143.151.108", + "37.143.151.157", + "37.143.151.162", + "37.143.151.192", + "37.143.151.210", + "37.143.151.219", + "37.148.0.4", + "37.148.0.8", + "37.148.0.22", + "37.148.0.32", + "37.148.0.55", + "37.148.0.60", + "37.148.0.73", + "37.148.0.77", + "37.148.0.80", + "37.148.0.82", + "37.148.0.88", + "37.148.0.99", + "37.148.0.103", + "37.148.0.200", + "37.148.0.215", + "37.148.0.220", + "37.148.0.225", + "37.148.0.238", + "37.148.0.246", + "37.148.1.27", + "37.148.1.28", + "37.148.1.30", + "37.148.1.33", + "37.148.1.56", + "37.148.1.60", + "37.148.1.66", + "37.148.1.87", + "37.148.1.115", + "37.148.1.116", + "37.148.1.128", + "37.148.1.134", + "37.148.1.150", + "37.148.1.154", + "37.148.1.161", + "37.148.1.173", + "37.148.1.186", + "37.148.1.223", + "37.148.1.226", + "37.148.1.228", + "37.148.1.239", + "37.148.2.13", + "37.148.2.14", + "37.148.2.15", + "37.148.2.52", + "37.148.2.54", + "37.148.2.63", + "37.148.2.96", + "37.148.2.98", + "37.148.2.107", + "37.148.2.108", + "37.148.2.114", + "37.148.2.116", + "37.148.2.125", + "37.148.2.127", + "37.148.2.147", + "37.148.2.185", + "37.148.2.189", + "37.148.2.198", + "37.148.2.200", + "37.148.2.205", + "37.148.2.218", + "37.148.2.228", + "37.148.2.232", + "37.148.2.233", + "37.148.2.240", + "37.148.2.243", + "37.148.3.3", + "37.148.3.7", + "37.148.3.16", + "37.148.3.36", + "37.148.3.42", + "37.148.3.60", + "37.148.3.64", + "37.148.3.83", + "37.148.3.88", + "37.148.3.91", + "37.148.3.94", + "37.148.3.100", + "37.148.3.103", + "37.148.3.108", + "37.148.3.120", + "37.148.3.125", + "37.148.3.127", + "37.148.3.133", + "37.148.3.134", + "37.148.3.136", + "37.148.3.141", + "37.148.3.148", + "37.148.3.150", + "37.148.3.157", + "37.148.3.160", + "37.148.3.179", + "37.148.3.237", + "37.148.3.246", + "37.148.4.53", + "37.148.4.74", + "37.148.4.75", + "37.148.4.96", + "37.148.4.106", + "37.148.4.132", + "37.148.4.168", + "37.148.4.179", + "37.148.4.192", + "37.148.5.43", + "37.148.5.79", + "37.148.5.88", + "37.148.5.94", + "37.148.5.97", + "37.148.5.109", + "37.148.5.115", + "37.148.5.132", + "37.148.5.136", + "37.148.5.143", + "37.148.5.147", + "37.148.5.165", + "37.148.5.199", + "37.148.5.222", + "37.148.5.223", + "37.148.5.228", + "37.148.5.248", + "37.148.6.1", + "37.148.6.17", + "37.148.6.32", + "37.148.6.48", + "37.148.6.65", + "37.148.6.76", + "37.148.6.82", + "37.148.6.84", + "37.148.6.89", + "37.148.6.92", + "37.148.6.97", + "37.148.6.101", + "37.148.6.109", + "37.148.6.111", + "37.148.6.119", + "37.148.6.134", + "37.148.6.136", + "37.148.6.138", + "37.148.6.145", + "37.148.6.156", + "37.148.6.161", + "37.148.6.162", + "37.148.6.166", + "37.148.6.179", + "37.148.6.188", + "37.148.6.198", + "37.148.6.216", + "37.148.6.225", + "37.148.6.228", + "37.148.6.231", + "37.148.6.255", + "37.148.7.0", + "37.148.7.4", + "37.148.7.22", + "37.148.7.23", + "37.148.7.50", + "37.148.7.65", + "37.148.7.74", + "37.148.7.78", + "37.148.7.85", + "37.148.7.105", + "37.148.7.127", + "37.148.7.130", + "37.148.7.132", + "37.148.7.181", + "37.148.7.185", + "37.148.7.196", + "37.148.7.234", + "37.148.8.4", + "37.148.8.8", + "37.148.8.10", + "37.148.8.12", + "37.148.8.16", + "37.148.8.40", + "37.148.8.51", + "37.148.8.53", + "37.148.8.63", + "37.148.8.68", + "37.148.8.73", + "37.148.8.87", + "37.148.8.95", + "37.148.8.99", + "37.148.8.111", + "37.148.8.118", + "37.148.8.158", + "37.148.8.188", + "37.148.8.196", + "37.148.8.201", + "37.148.8.210", + "37.148.8.217", + "37.148.8.218", + "37.148.8.231", + "37.148.8.240", + "37.148.8.253", + "37.148.9.32", + "37.148.9.36", + "37.148.9.56", + "37.148.9.96", + "37.148.9.102", + "37.148.9.146", + "37.148.9.149", + "37.148.9.155", + "37.148.9.174", + "37.148.9.188", + "37.148.9.228", + "37.148.9.246", + "37.148.10.65", + "37.148.10.79", + "37.148.10.83", + "37.148.10.113", + "37.148.10.121", + "37.148.10.141", + "37.148.10.155", + "37.148.10.166", + "37.148.10.192", + "37.148.10.195", + "37.148.10.217", + "37.148.10.230", + "37.148.11.9", + "37.148.11.23", + "37.148.11.39", + "37.148.11.50", + "37.148.11.64", + "37.148.11.66", + "37.148.11.75", + "37.148.11.85", + "37.148.11.108", + "37.148.11.141", + "37.148.11.168", + "37.148.11.171", + "37.148.11.191", + "37.148.11.212", + "37.148.11.229", + "37.148.11.231", + "37.148.11.252", + "37.148.12.25", + "37.148.12.27", + "37.148.12.28", + "37.148.12.35", + "37.148.12.46", + "37.148.12.68", + "37.148.12.76", + "37.148.12.89", + "37.148.12.129", + "37.148.12.145", + "37.148.12.152", + "37.148.12.158", + "37.148.12.160", + "37.148.12.162", + "37.148.12.169", + "37.148.12.200", + "37.148.12.203", + "37.148.12.212", + "37.148.12.218", + "37.148.12.239", + "37.148.12.240", + "37.148.12.241", + "37.148.13.0", + "37.148.13.3", + "37.148.13.55", + "37.148.13.78", + "37.148.13.80", + "37.148.13.93", + "37.148.13.111", + "37.148.13.119", + "37.148.13.123", + "37.148.13.128", + "37.148.13.130", + "37.148.13.131", + "37.148.13.142", + "37.148.13.156", + "37.148.13.160", + "37.148.13.166", + "37.148.13.174", + "37.148.13.202", + "37.148.13.206", + "37.148.13.213", + "37.148.13.224", + "37.148.13.228", + "37.148.13.232", + "37.148.13.239", + "37.148.13.246", + "37.148.14.19", + "37.148.14.38", + "37.148.14.41", + "37.148.14.57", + "37.148.14.60", + "37.148.14.69", + "37.148.14.94", + "37.148.14.116", + "37.148.14.151", + "37.148.14.173", + "37.148.14.179", + "37.148.14.193", + "37.148.14.197", + "37.148.14.204", + "37.148.14.246", + "37.148.15.3", + "37.148.15.7", + "37.148.15.11", + "37.148.15.26", + "37.148.15.65", + "37.148.15.115", + "37.148.15.117", + "37.148.15.142", + "37.148.15.149", + "37.148.15.153", + "37.148.15.155", + "37.148.15.177", + "37.148.15.195", + "37.148.15.198", + "37.148.15.222", + "37.148.15.234", + "37.148.15.237", + "37.148.16.6", + "37.148.16.15", + "37.148.16.17", + "37.148.16.26", + "37.148.16.31", + "37.148.16.32", + "37.148.16.45", + "37.148.16.47", + "37.148.16.54", + "37.148.16.61", + "37.148.16.66", + "37.148.16.67", + "37.148.16.76", + "37.148.16.79", + "37.148.16.100", + "37.148.16.130", + "37.148.16.176", + "37.148.16.196", + "37.148.16.214", + "37.148.16.215", + "37.148.16.231", + "37.148.16.254", + "37.148.16.255", + "37.148.17.22", + "37.148.17.29", + "37.148.17.38", + "37.148.17.43", + "37.148.17.61", + "37.148.17.63", + "37.148.17.67", + "37.148.17.83", + "37.148.17.87", + "37.148.17.100", + "37.148.17.105", + "37.148.17.111", + "37.148.17.135", + "37.148.17.144", + "37.148.17.161", + "37.148.17.170", + "37.148.17.188", + "37.148.17.215", + "37.148.17.229", + "37.148.17.255", + "37.148.18.40", + "37.148.18.45", + "37.148.18.46", + "37.148.18.71", + "37.148.18.79", + "37.148.18.84", + "37.148.18.92", + "37.148.18.124", + "37.148.18.128", + "37.148.18.132", + "37.148.18.135", + "37.148.18.137", + "37.148.18.143", + "37.148.18.144", + "37.148.18.146", + "37.148.18.155", + "37.148.18.166", + "37.148.18.173", + "37.148.18.185", + "37.148.18.199", + "37.148.18.215", + "37.148.18.218", + "37.148.18.226", + "37.148.18.237", + "37.148.18.246", + "37.148.18.247", + "37.148.19.13", + "37.148.19.15", + "37.148.19.28", + "37.148.19.43", + "37.148.19.61", + "37.148.19.72", + "37.148.19.77", + "37.148.19.78", + "37.148.19.92", + "37.148.19.93", + "37.148.19.107", + "37.148.19.145", + "37.148.19.147", + "37.148.19.195", + "37.148.19.200", + "37.148.19.210", + "37.148.19.218", + "37.148.20.12", + "37.148.20.62", + "37.148.20.69", + "37.148.20.72", + "37.148.20.94", + "37.148.20.119", + "37.148.20.120", + "37.148.20.148", + "37.148.20.159", + "37.148.20.172", + "37.148.20.175", + "37.148.20.188", + "37.148.20.190", + "37.148.20.198", + "37.148.20.200", + "37.148.20.232", + "37.148.20.235", + "37.148.20.236", + "37.148.20.252", + "37.148.21.55", + "37.148.21.70", + "37.148.21.130", + "37.148.21.133", + "37.148.21.150", + "37.148.21.157", + "37.148.21.165", + "37.148.21.167", + "37.148.21.199", + "37.148.21.210", + "37.148.21.213", + "37.148.21.253", + "37.148.22.12", + "37.148.22.32", + "37.148.22.35", + "37.148.22.56", + "37.148.22.83", + "37.148.22.114", + "37.148.22.138", + "37.148.22.170", + "37.148.22.173", + "37.148.22.176", + "37.148.22.185", + "37.148.22.196", + "37.148.22.204", + "37.148.22.218", + "37.148.22.241", + "37.148.22.244", + "37.148.23.1", + "37.148.23.4", + "37.148.23.6", + "37.148.23.18", + "37.148.23.26", + "37.148.23.32", + "37.148.23.33", + "37.148.23.47", + "37.148.23.53", + "37.148.23.76", + "37.148.23.93", + "37.148.23.104", + "37.148.23.109", + "37.148.23.115", + "37.148.23.128", + "37.148.23.138", + "37.148.23.141", + "37.148.23.142", + "37.148.23.144", + "37.148.23.147", + "37.148.23.169", + "37.148.23.178", + "37.148.23.182", + "37.148.23.184", + "37.148.23.225", + "37.148.24.7", + "37.148.24.8", + "37.148.24.17", + "37.148.24.54", + "37.148.24.61", + "37.148.24.64", + "37.148.24.68", + "37.148.24.70", + "37.148.24.80", + "37.148.24.95", + "37.148.24.108", + "37.148.24.111", + "37.148.24.116", + "37.148.24.138", + "37.148.24.139", + "37.148.24.142", + "37.148.24.147", + "37.148.24.175", + "37.148.24.179", + "37.148.24.188", + "37.148.24.205", + "37.148.24.210", + "37.148.24.211", + "37.148.24.216", + "37.148.24.240", + "37.148.24.243", + "37.148.24.245", + "37.148.25.3", + "37.148.25.13", + "37.148.25.14", + "37.148.25.21", + "37.148.25.23", + "37.148.25.30", + "37.148.25.32", + "37.148.25.56", + "37.148.25.105", + "37.148.25.134", + "37.148.25.138", + "37.148.25.146", + "37.148.25.151", + "37.148.25.165", + "37.148.25.168", + "37.148.25.185", + "37.148.25.210", + "37.148.25.214", + "37.148.25.253", + "37.148.26.23", + "37.148.26.36", + "37.148.26.67", + "37.148.26.79", + "37.148.26.81", + "37.148.26.103", + "37.148.26.107", + "37.148.26.161", + "37.148.26.171", + "37.148.26.186", + "37.148.26.195", + "37.148.26.210", + "37.148.26.213", + "37.148.26.214", + "37.148.26.233", + "37.148.26.235", + "37.148.26.250", + "37.148.27.13", + "37.148.27.24", + "37.148.27.30", + "37.148.27.46", + "37.148.27.56", + "37.148.27.57", + "37.148.27.58", + "37.148.27.74", + "37.148.27.100", + "37.148.27.107", + "37.148.27.110", + "37.148.27.123", + "37.148.27.124", + "37.148.27.138", + "37.148.27.147", + "37.148.27.156", + "37.148.27.157", + "37.148.27.172", + "37.148.27.176", + "37.148.27.182", + "37.148.27.189", + "37.148.27.194", + "37.148.27.208", + "37.148.27.244", + "37.148.27.250", + "37.148.27.254", + "37.148.28.39", + "37.148.28.44", + "37.148.28.46", + "37.148.28.66", + "37.148.28.81", + "37.148.28.91", + "37.148.28.119", + "37.148.28.131", + "37.148.28.142", + "37.148.28.166", + "37.148.28.170", + "37.148.28.202", + "37.148.28.204", + "37.148.28.214", + "37.148.28.215", + "37.148.28.247", + "37.148.28.248", + "37.148.29.3", + "37.148.29.17", + "37.148.29.25", + "37.148.29.27", + "37.148.29.34", + "37.148.29.39", + "37.148.29.46", + "37.148.29.71", + "37.148.29.77", + "37.148.29.81", + "37.148.29.82", + "37.148.29.83", + "37.148.29.92", + "37.148.29.97", + "37.148.29.98", + "37.148.29.99", + "37.148.29.107", + "37.148.29.123", + "37.148.29.135", + "37.148.29.166", + "37.148.29.183", + "37.148.29.184", + "37.148.29.189", + "37.148.29.193", + "37.148.29.196", + "37.148.29.224", + "37.148.29.235", + "37.148.29.248", + "37.148.30.1", + "37.148.30.4", + "37.148.30.8", + "37.148.30.13", + "37.148.30.20", + "37.148.30.47", + "37.148.30.52", + "37.148.30.54", + "37.148.30.66", + "37.148.30.69", + "37.148.30.71", + "37.148.30.79", + "37.148.30.89", + "37.148.30.92", + "37.148.30.99", + "37.148.30.101", + "37.148.30.104", + "37.148.30.111", + "37.148.30.119", + "37.148.30.130", + "37.148.30.139", + "37.148.30.143", + "37.148.30.146", + "37.148.30.147", + "37.148.30.176", + "37.148.30.182", + "37.148.30.185", + "37.148.30.195", + "37.148.30.202", + "37.148.30.204", + "37.148.30.209", + "37.148.30.219", + "37.148.30.235", + "37.148.30.248", + "37.148.30.252", + "37.148.31.1", + "37.148.31.9", + "37.148.31.15", + "37.148.31.16", + "37.148.31.21", + "37.148.31.49", + "37.148.31.50", + "37.148.31.53", + "37.148.31.56", + "37.148.31.71", + "37.148.31.87", + "37.148.31.97", + "37.148.31.103", + "37.148.31.104", + "37.148.31.117", + "37.148.31.131", + "37.148.31.141", + "37.148.31.157", + "37.148.31.159", + "37.148.31.163", + "37.148.31.173", + "37.148.31.180", + "37.148.31.181", + "37.148.31.185", + "37.148.31.192", + "37.148.31.201", + "37.148.31.225", + "37.148.31.240", + "37.148.31.241", + "37.148.31.242", + "37.148.31.243", + "37.148.31.249", + "37.148.31.250", + "37.148.40.3", + "37.148.40.9", + "37.148.40.11", + "37.148.40.20", + "37.148.40.44", + "37.148.40.60", + "37.148.40.69", + "37.148.40.70", + "37.148.40.73", + "37.148.40.80", + "37.148.40.83", + "37.148.40.85", + "37.148.40.95", + "37.148.40.103", + "37.148.40.120", + "37.148.40.136", + "37.148.40.153", + "37.148.40.179", + "37.148.40.180", + "37.148.40.184", + "37.148.40.189", + "37.148.40.192", + "37.148.40.197", + "37.148.40.205", + "37.148.40.209", + "37.148.40.211", + "37.148.40.213", + "37.148.40.219", + "37.148.40.236", + "37.148.40.237", + "37.148.40.240", + "37.148.40.252", + "37.148.41.31", + "37.148.41.36", + "37.148.41.40", + "37.148.41.56", + "37.148.41.57", + "37.148.41.66", + "37.148.41.71", + "37.148.41.78", + "37.148.41.89", + "37.148.41.90", + "37.148.41.111", + "37.148.41.127", + "37.148.41.141", + "37.148.41.151", + "37.148.41.164", + "37.148.41.181", + "37.148.41.182", + "37.148.41.183", + "37.148.41.186", + "37.148.41.188", + "37.148.41.195", + "37.148.41.205", + "37.148.41.207", + "37.148.41.208", + "37.148.41.213", + "37.148.41.215", + "37.148.41.219", + "37.148.41.224", + "37.148.42.15", + "37.148.42.19", + "37.148.42.22", + "37.148.42.26", + "37.148.42.30", + "37.148.42.31", + "37.148.42.40", + "37.148.42.53", + "37.148.42.62", + "37.148.42.68", + "37.148.42.74", + "37.148.42.76", + "37.148.42.86", + "37.148.42.92", + "37.148.42.101", + "37.148.42.108", + "37.148.42.133", + "37.148.42.150", + "37.148.42.151", + "37.148.42.156", + "37.148.42.162", + "37.148.42.164", + "37.148.42.172", + "37.148.42.174", + "37.148.42.180", + "37.148.42.182", + "37.148.42.185", + "37.148.42.189", + "37.148.42.194", + "37.148.42.196", + "37.148.42.198", + "37.148.42.205", + "37.148.42.211", + "37.148.42.212", + "37.148.42.235", + "37.148.42.251", + "37.148.43.2", + "37.148.43.4", + "37.148.43.6", + "37.148.43.7", + "37.148.43.8", + "37.148.43.11", + "37.148.43.35", + "37.148.43.42", + "37.148.43.62", + "37.148.43.65", + "37.148.43.67", + "37.148.43.80", + "37.148.43.85", + "37.148.43.98", + "37.148.43.107", + "37.148.43.119", + "37.148.43.122", + "37.148.43.125", + "37.148.43.128", + "37.148.43.130", + "37.148.43.164", + "37.148.43.166", + "37.148.43.169", + "37.148.43.171", + "37.148.43.189", + "37.148.43.190", + "37.148.43.195", + "37.148.43.196", + "37.148.43.202", + "37.148.43.205", + "37.148.43.207", + "37.148.43.219", + "37.148.43.222", + "37.148.43.229", + "37.148.43.233", + "37.148.43.250", + "37.148.44.0", + "37.148.44.14", + "37.148.44.24", + "37.148.44.30", + "37.148.44.39", + "37.148.44.43", + "37.148.44.60", + "37.148.44.65", + "37.148.44.70", + "37.148.44.72", + "37.148.44.73", + "37.148.44.80", + "37.148.44.84", + "37.148.44.85", + "37.148.44.92", + "37.148.44.95", + "37.148.44.98", + "37.148.44.106", + "37.148.44.113", + "37.148.44.118", + "37.148.44.129", + "37.148.44.133", + "37.148.44.134", + "37.148.44.135", + "37.148.44.142", + "37.148.44.143", + "37.148.44.152", + "37.148.44.159", + "37.148.44.161", + "37.148.44.169", + "37.148.44.181", + "37.148.44.185", + "37.148.44.188", + "37.148.44.189", + "37.148.44.191", + "37.148.44.198", + "37.148.44.201", + "37.148.44.213", + "37.148.44.215", + "37.148.44.216", + "37.148.44.238", + "37.148.44.244", + "37.148.44.254", + "37.148.45.21", + "37.148.45.30", + "37.148.45.31", + "37.148.45.35", + "37.148.45.37", + "37.148.45.43", + "37.148.45.52", + "37.148.45.53", + "37.148.45.54", + "37.148.45.64", + "37.148.45.65", + "37.148.45.72", + "37.148.45.77", + "37.148.45.89", + "37.148.45.96", + "37.148.45.97", + "37.148.45.100", + "37.148.45.108", + "37.148.45.116", + "37.148.45.120", + "37.148.45.125", + "37.148.45.129", + "37.148.45.134", + "37.148.45.142", + "37.148.45.151", + "37.148.45.162", + "37.148.45.164", + "37.148.45.183", + "37.148.45.194", + "37.148.45.202", + "37.148.45.206", + "37.148.45.217", + "37.148.46.31", + "37.148.46.35", + "37.148.46.47", + "37.148.46.53", + "37.148.46.59", + "37.148.46.67", + "37.148.46.113", + "37.148.46.142", + "37.148.46.174", + "37.148.46.191", + "37.148.46.237", + "37.148.47.7", + "37.148.47.19", + "37.148.47.25", + "37.148.47.33", + "37.148.47.45", + "37.148.47.70", + "37.148.47.75", + "37.148.47.92", + "37.148.47.99", + "37.148.47.100", + "37.148.47.131", + "37.148.47.132", + "37.148.47.139", + "37.148.47.146", + "37.148.47.165", + "37.148.48.15", + "37.148.48.17", + "37.148.48.19", + "37.148.48.25", + "37.148.48.31", + "37.148.48.43", + "37.148.48.52", + "37.148.48.53", + "37.148.48.56", + "37.148.48.68", + "37.148.48.70", + "37.148.48.82", + "37.148.48.86", + "37.148.48.87", + "37.148.48.99", + "37.148.48.102", + "37.148.48.103", + "37.148.48.104", + "37.148.48.116", + "37.148.48.123", + "37.148.48.124", + "37.148.48.180", + "37.148.48.202", + "37.148.48.203", + "37.148.48.207", + "37.148.48.210", + "37.148.48.217", + "37.148.48.234", + "37.148.48.239", + "37.148.48.241", + "37.148.48.242", + "37.148.48.244", + "37.148.48.252", + "37.148.49.17", + "37.148.49.19", + "37.148.49.26", + "37.148.49.40", + "37.148.49.41", + "37.148.49.43", + "37.148.49.44", + "37.148.49.45", + "37.148.49.70", + "37.148.49.71", + "37.148.49.74", + "37.148.49.80", + "37.148.49.93", + "37.148.49.120", + "37.148.49.130", + "37.148.49.133", + "37.148.49.134", + "37.148.49.137", + "37.148.49.141", + "37.148.49.142", + "37.148.49.143", + "37.148.49.153", + "37.148.49.155", + "37.148.49.162", + "37.148.49.163", + "37.148.49.165", + "37.148.49.169", + "37.148.49.171", + "37.148.49.173", + "37.148.49.175", + "37.148.49.177", + "37.148.49.178", + "37.148.49.182", + "37.148.49.183", + "37.148.49.184", + "37.148.49.193", + "37.148.49.236", + "37.148.50.9", + "37.148.50.11", + "37.148.50.18", + "37.148.50.29", + "37.148.50.30", + "37.148.50.39", + "37.148.50.41", + "37.148.50.58", + "37.148.50.67", + "37.148.50.71", + "37.148.50.82", + "37.148.50.91", + "37.148.50.108", + "37.148.50.116", + "37.148.50.124", + "37.148.50.128", + "37.148.50.134", + "37.148.50.152", + "37.148.50.157", + "37.148.50.158", + "37.148.50.160", + "37.148.50.163", + "37.148.50.170", + "37.148.50.174", + "37.148.50.175", + "37.148.50.220", + "37.148.50.223", + "37.148.50.230", + "37.148.50.241", + "37.148.50.248", + "37.148.51.6", + "37.148.51.15", + "37.148.51.28", + "37.148.51.41", + "37.148.51.51", + "37.148.51.58", + "37.148.51.59", + "37.148.51.61", + "37.148.51.79", + "37.148.51.114", + "37.148.51.118", + "37.148.51.131", + "37.148.51.134", + "37.148.51.135", + "37.148.51.137", + "37.148.51.142", + "37.148.51.144", + "37.148.51.147", + "37.148.51.167", + "37.148.51.168", + "37.148.51.200", + "37.148.51.201", + "37.148.51.222", + "37.148.51.226", + "37.148.56.4", + "37.148.56.10", + "37.148.56.26", + "37.148.56.29", + "37.148.56.48", + "37.148.56.81", + "37.148.56.84", + "37.148.56.110", + "37.148.56.125", + "37.148.56.155", + "37.148.56.157", + "37.148.56.160", + "37.148.56.163", + "37.148.56.165", + "37.148.56.172", + "37.148.56.173", + "37.148.56.179", + "37.148.56.191", + "37.148.56.206", + "37.148.56.211", + "37.148.56.232", + "37.148.57.4", + "37.148.57.5", + "37.148.57.8", + "37.148.57.15", + "37.148.57.18", + "37.148.57.21", + "37.148.57.27", + "37.148.57.35", + "37.148.57.36", + "37.148.57.37", + "37.148.57.39", + "37.148.57.67", + "37.148.57.71", + "37.148.57.74", + "37.148.57.82", + "37.148.57.88", + "37.148.57.98", + "37.148.57.102", + "37.148.57.108", + "37.148.57.117", + "37.148.57.119", + "37.148.57.122", + "37.148.57.137", + "37.148.57.144", + "37.148.57.171", + "37.148.57.186", + "37.148.57.187", + "37.148.57.194", + "37.148.57.198", + "37.148.57.208", + "37.148.57.216", + "37.148.57.222", + "37.148.57.236", + "37.148.57.250", + "37.148.58.53", + "37.148.58.63", + "37.148.58.89", + "37.148.58.102", + "37.148.58.107", + "37.148.58.121", + "37.148.58.138", + "37.148.58.157", + "37.148.58.159", + "37.148.58.177", + "37.148.58.190", + "37.148.58.191", + "37.148.58.231", + "37.148.58.233", + "37.148.58.235", + "37.148.58.237", + "37.148.59.1", + "37.148.59.5", + "37.148.59.10", + "37.148.59.29", + "37.148.59.31", + "37.148.59.32", + "37.148.59.53", + "37.148.59.57", + "37.148.59.101", + "37.148.59.109", + "37.148.59.169", + "37.148.59.174", + "37.148.59.180", + "37.148.59.188", + "37.148.59.189", + "37.148.59.195", + "37.148.59.229", + "37.148.59.231", + "37.148.59.235", + "37.148.59.252", + "37.148.60.12", + "37.148.60.22", + "37.148.60.55", + "37.148.60.64", + "37.148.60.69", + "37.148.60.78", + "37.148.60.81", + "37.148.60.89", + "37.148.60.98", + "37.148.60.100", + "37.148.60.101", + "37.148.60.109", + "37.148.60.114", + "37.148.60.126", + "37.148.60.132", + "37.148.60.140", + "37.148.60.143", + "37.148.60.155", + "37.148.60.178", + "37.148.60.190", + "37.148.60.193", + "37.148.60.197", + "37.148.60.198", + "37.148.60.249", + "37.148.61.29", + "37.148.61.30", + "37.148.61.37", + "37.148.61.60", + "37.148.61.85", + "37.148.61.89", + "37.148.61.97", + "37.148.61.109", + "37.148.61.149", + "37.148.61.151", + "37.148.61.168", + "37.148.61.173", + "37.148.61.201", + "37.148.61.204", + "37.148.61.213", + "37.148.61.215", + "37.148.61.217", + "37.148.61.218", + "37.148.61.234", + "37.148.61.251", + "37.148.62.23", + "37.148.62.37", + "37.148.62.100", + "37.148.62.105", + "37.148.62.118", + "37.148.62.119", + "37.148.62.141", + "37.148.62.144", + "37.148.62.153", + "37.148.62.188", + "37.148.62.195", + "37.148.62.216", + "37.148.62.240", + "37.148.62.252", + "37.148.63.45", + "37.148.63.55", + "37.148.63.60", + "37.148.63.66", + "37.148.63.79", + "37.148.63.93", + "37.148.63.95", + "37.148.63.96", + "37.148.63.97", + "37.148.63.109", + "37.148.63.117", + "37.148.63.129", + "37.148.63.137", + "37.148.63.140", + "37.148.63.161", + "37.148.63.171", + "37.148.63.178", + "37.148.63.186", + "37.148.63.193", + "37.148.63.195", + "37.148.63.197", + "37.148.63.199", + "37.148.63.217", + "37.148.63.218", + "37.148.63.250", + "37.148.80.18", + "37.148.80.35", + "37.148.80.56", + "37.148.80.78", + "37.148.80.82", + "37.148.80.88", + "37.148.80.90", + "37.148.80.95", + "37.148.80.109", + "37.148.80.116", + "37.148.80.123", + "37.148.80.146", + "37.148.80.152", + "37.148.80.157", + "37.148.80.160", + "37.148.80.161", + "37.148.80.166", + "37.148.80.172", + "37.148.80.183", + "37.148.80.188", + "37.148.80.213", + "37.148.80.221", + "37.148.80.244", + "37.148.81.0", + "37.148.81.15", + "37.148.81.47", + "37.148.81.70", + "37.148.81.80", + "37.148.81.90", + "37.148.81.92", + "37.148.81.102", + "37.148.81.118", + "37.148.81.151", + "37.148.81.153", + "37.148.81.170", + "37.148.81.173", + "37.148.81.180", + "37.148.81.194", + "37.148.81.220", + "37.148.81.224", + "37.148.81.233", + "37.148.81.234", + "37.148.81.245", + "37.148.82.9", + "37.148.82.13", + "37.148.82.28", + "37.148.82.36", + "37.148.82.48", + "37.148.82.49", + "37.148.82.56", + "37.148.82.63", + "37.148.82.69", + "37.148.82.75", + "37.148.82.88", + "37.148.82.112", + "37.148.82.113", + "37.148.82.124", + "37.148.82.134", + "37.148.82.139", + "37.148.82.152", + "37.148.82.159", + "37.148.82.161", + "37.148.82.166", + "37.148.82.170", + "37.148.82.174", + "37.148.82.184", + "37.148.82.193", + "37.148.82.212", + "37.148.82.213", + "37.148.82.223", + "37.148.82.226", + "37.148.82.245", + "37.148.83.2", + "37.148.83.5", + "37.148.83.9", + "37.148.83.25", + "37.148.83.28", + "37.148.83.36", + "37.148.83.37", + "37.148.83.46", + "37.148.83.62", + "37.148.83.68", + "37.148.83.76", + "37.148.83.141", + "37.148.83.169", + "37.148.83.179", + "37.148.83.194", + "37.148.83.221", + "37.148.83.223", + "37.148.83.230", + "37.148.83.252", + "37.148.83.255", + "37.148.84.4", + "37.148.84.10", + "37.148.84.19", + "37.148.84.44", + "37.148.84.46", + "37.148.84.48", + "37.148.84.60", + "37.148.84.62", + "37.148.84.64", + "37.148.84.66", + "37.148.84.80", + "37.148.84.115", + "37.148.84.155", + "37.148.84.161", + "37.148.84.162", + "37.148.84.163", + "37.148.84.165", + "37.148.84.188", + "37.148.84.195", + "37.148.84.227", + "37.148.84.238", + "37.148.84.240", + "37.148.84.242", + "37.148.84.243", + "37.148.84.247", + "37.148.85.19", + "37.148.85.21", + "37.148.85.24", + "37.148.85.57", + "37.148.85.62", + "37.148.85.78", + "37.148.85.83", + "37.148.85.90", + "37.148.85.94", + "37.148.85.95", + "37.148.85.102", + "37.148.85.105", + "37.148.85.115", + "37.148.85.125", + "37.148.85.131", + "37.148.85.132", + "37.148.85.141", + "37.148.85.159", + "37.148.85.175", + "37.148.85.209", + "37.148.85.211", + "37.148.85.212", + "37.148.85.218", + "37.148.85.223", + "37.148.85.243", + "37.148.86.26", + "37.148.86.42", + "37.148.86.48", + "37.148.86.55", + "37.148.86.57", + "37.148.86.85", + "37.148.86.88", + "37.148.86.94", + "37.148.86.102", + "37.148.86.110", + "37.148.86.129", + "37.148.86.139", + "37.148.86.144", + "37.148.86.149", + "37.148.86.170", + "37.148.86.209", + "37.148.86.213", + "37.148.86.243", + "37.148.86.249", + "37.148.87.2", + "37.148.87.19", + "37.148.87.25", + "37.148.87.26", + "37.148.87.29", + "37.148.87.42", + "37.148.87.44", + "37.148.87.49", + "37.148.87.65", + "37.148.87.74", + "37.148.87.76", + "37.148.87.78", + "37.148.87.83", + "37.148.87.88", + "37.148.87.90", + "37.148.87.91", + "37.148.87.92", + "37.148.87.100", + "37.148.87.110", + "37.148.87.117", + "37.148.87.118", + "37.148.87.140", + "37.148.87.144", + "37.148.87.150", + "37.148.87.161", + "37.148.87.169", + "37.148.87.173", + "37.148.87.181", + "37.148.87.182", + "37.148.87.184", + "37.148.87.192", + "37.148.87.198", + "37.148.87.214", + "37.148.87.219", + "37.148.87.223", + "37.148.87.229", + "37.148.87.230", + "37.148.87.236", + "37.148.87.248", + "37.148.87.250", + "37.148.87.251", + "37.148.87.253", + "37.148.90.12", + "37.148.90.13", + "37.148.90.18", + "37.148.90.22", + "37.148.90.34", + "37.148.90.47", + "37.148.90.51", + "37.148.90.56", + "37.148.90.66", + "37.148.90.84", + "37.148.90.93", + "37.148.90.102", + "37.148.90.118", + "37.148.90.119", + "37.148.90.124", + "37.148.90.132", + "37.148.90.176", + "37.148.90.223", + "37.148.90.240", + "37.152.160.42", + "37.152.161.45", + "37.152.163.14", + "37.152.163.69", + "37.152.163.132", + "37.152.163.145", + "37.152.163.148", + "37.152.163.160", + "37.152.163.211", + "37.152.163.216", + "37.152.163.226", + "37.152.163.227", + "37.152.163.253", + "37.152.164.17", + "37.152.165.37", + "37.152.165.136", + "37.152.165.139", + "37.152.165.151", + "37.152.165.156", + "37.152.165.197", + "37.152.165.200", + "37.152.166.10", + "37.152.166.18", + "37.152.166.36", + "37.152.166.58", + "37.152.166.68", + "37.152.166.84", + "37.152.166.88", + "37.152.166.98", + "37.152.166.102", + "37.152.166.121", + "37.152.166.139", + "37.152.166.142", + "37.152.166.144", + "37.152.166.148", + "37.152.166.151", + "37.152.166.156", + "37.152.166.158", + "37.152.167.92", + "37.152.167.142", + "37.152.171.115", + "37.152.172.104", + "37.152.173.126", + "37.152.177.179", + "37.152.177.200", + "37.152.178.112", + "37.152.178.130", + "37.152.179.40", + "37.152.179.135", + "37.152.181.83", + "37.152.182.77", + "37.152.188.155", + "37.152.189.124", + "37.152.190.29", + "37.152.190.69", + "37.152.190.238", + "37.156.8.43", + "37.156.8.147", + "37.156.8.165", + "37.156.8.197", + "37.156.8.240", + "37.156.9.26", + "37.156.9.42", + "37.156.9.67", + "37.156.9.99", + "37.156.9.152", + "37.156.9.184", + "37.156.9.192", + "37.156.10.32", + "37.156.10.49", + "37.156.10.60", + "37.156.10.71", + "37.156.11.6", + "37.156.11.10", + "37.156.11.24", + "37.156.11.31", + "37.156.11.74", + "37.156.11.80", + "37.156.11.138", + "37.156.11.142", + "37.156.11.167", + "37.156.11.175", + "37.156.11.179", + "37.156.11.196", + "37.156.11.229", + "37.156.11.248", + "37.156.12.3", + "37.156.12.4", + "37.156.12.65", + "37.156.12.94", + "37.156.12.147", + "37.156.12.166", + "37.156.12.169", + "37.156.12.209", + "37.156.12.231", + "37.156.12.233", + "37.156.12.241", + "37.156.12.251", + "37.156.13.11", + "37.156.13.23", + "37.156.13.151", + "37.156.13.152", + "37.156.13.163", + "37.156.13.237", + "37.156.14.64", + "37.156.14.97", + "37.156.14.206", + "37.156.14.209", + "37.156.14.215", + "37.156.14.226", + "37.156.14.248", + "37.156.15.20", + "37.156.15.131", + "37.156.15.149", + "37.156.15.174", + "37.156.15.198", + "37.156.15.254", + "37.156.16.202", + "37.156.16.241", + "37.156.17.102", + "37.156.17.179", + "37.156.17.246", + "37.156.18.149", + "37.156.19.32", + "37.156.19.54", + "37.156.19.84", + "37.156.20.60", + "37.156.20.92", + "37.156.20.185", + "37.156.20.235", + "37.156.20.241", + "37.156.21.15", + "37.156.21.216", + "37.156.21.225", + "37.156.22.51", + "37.156.23.23", + "37.156.23.35", + "37.156.23.39", + "37.156.23.80", + "37.156.23.105", + "37.156.24.204", + "37.156.25.28", + "37.156.26.223", + "37.156.27.77", + "37.156.115.84", + "37.156.144.66", + "37.156.144.172", + "37.156.144.174", + "37.156.145.85", + "37.156.145.195", + "37.156.145.234", + "37.156.146.2", + "37.156.146.3", + "37.156.146.4", + "37.156.146.5", + "37.156.146.46", + "37.156.146.115", + "37.156.146.180", + "37.156.147.34", + "37.156.147.36", + "37.156.147.38", + "37.156.147.213", + "37.156.165.69", + "37.156.165.198", + "37.156.248.43", + "37.156.248.193", + "37.156.249.213", + "37.156.249.237", + "37.191.76.48", + "37.191.76.49", + "37.191.76.50", + "37.191.76.51", + "37.191.76.52", + "37.191.76.53", + "37.191.76.54", + "37.191.76.64", + "37.191.76.177", + "37.191.77.102", + "37.191.77.106", + "37.191.77.202", + "37.191.78.210", + "37.191.78.211", + "37.191.78.212", + "37.191.78.215", + "37.191.79.18", + "37.191.79.55", + "37.191.79.252", + "37.191.84.46", + "37.191.84.170", + "37.191.84.222", + "37.191.85.58", + "37.191.92.225", + "37.191.93.67", + "37.191.93.126", + "37.191.93.151", + "37.191.94.66", + "37.191.95.88", + "37.191.95.181", + "37.202.148.10", + "37.202.148.28", + "37.202.148.41", + "37.202.148.69", + "37.202.148.78", + "37.202.148.89", + "37.202.148.96", + "37.202.148.101", + "37.202.148.124", + "37.202.148.126", + "37.202.148.141", + "37.202.148.165", + "37.202.148.185", + "37.202.148.190", + "37.202.148.219", + "37.202.148.231", + "37.202.148.234", + "37.202.148.235", + "37.202.149.215", + "37.202.149.224", + "37.202.149.227", + "37.202.149.234", + "37.202.149.244", + "37.202.149.252", + "37.202.152.4", + "37.202.152.33", + "37.202.152.43", + "37.202.152.46", + "37.202.152.50", + "37.202.152.84", + "37.202.152.109", + "37.202.152.112", + "37.202.152.126", + "37.202.152.133", + "37.202.152.139", + "37.202.152.149", + "37.202.152.160", + "37.202.152.165", + "37.202.152.178", + "37.202.152.182", + "37.202.152.187", + "37.202.152.189", + "37.202.152.194", + "37.202.152.203", + "37.202.152.207", + "37.202.152.213", + "37.202.152.224", + "37.202.152.226", + "37.202.152.231", + "37.202.152.250", + "37.202.153.21", + "37.202.153.22", + "37.202.153.30", + "37.202.153.61", + "37.202.153.80", + "37.202.153.83", + "37.202.153.89", + "37.202.153.94", + "37.202.153.97", + "37.202.153.103", + "37.202.153.105", + "37.202.153.123", + "37.202.153.127", + "37.202.153.138", + "37.202.153.140", + "37.202.153.145", + "37.202.153.148", + "37.202.153.197", + "37.202.153.202", + "37.202.153.204", + "37.202.153.212", + "37.202.153.219", + "37.202.153.221", + "37.202.153.232", + "37.202.153.235", + "37.202.153.242", + "37.202.153.255", + "37.202.154.5", + "37.202.154.8", + "37.202.154.26", + "37.202.154.34", + "37.202.154.51", + "37.202.154.76", + "37.202.154.83", + "37.202.154.90", + "37.202.154.132", + "37.202.154.134", + "37.202.154.158", + "37.202.154.163", + "37.202.154.181", + "37.202.154.184", + "37.202.154.195", + "37.202.154.218", + "37.202.154.219", + "37.202.154.228", + "37.202.154.230", + "37.202.154.234", + "37.202.154.246", + "37.202.154.252", + "37.202.154.253", + "37.202.155.6", + "37.202.155.19", + "37.202.155.36", + "37.202.155.40", + "37.202.155.45", + "37.202.155.50", + "37.202.155.51", + "37.202.155.69", + "37.202.155.75", + "37.202.155.82", + "37.202.155.86", + "37.202.155.88", + "37.202.155.97", + "37.202.155.108", + "37.202.155.131", + "37.202.155.132", + "37.202.155.174", + "37.202.155.188", + "37.202.155.192", + "37.202.155.218", + "37.202.155.225", + "37.202.155.231", + "37.202.155.232", + "37.202.155.242", + "37.202.155.246", + "37.202.156.0", + "37.202.156.5", + "37.202.156.10", + "37.202.156.20", + "37.202.156.26", + "37.202.156.34", + "37.202.156.53", + "37.202.156.61", + "37.202.156.72", + "37.202.156.79", + "37.202.156.82", + "37.202.156.87", + "37.202.156.88", + "37.202.156.115", + "37.202.156.132", + "37.202.156.134", + "37.202.156.135", + "37.202.156.143", + "37.202.156.204", + "37.202.156.218", + "37.202.156.240", + "37.202.156.243", + "37.202.156.246", + "37.202.156.251", + "37.202.157.3", + "37.202.157.12", + "37.202.157.40", + "37.202.157.42", + "37.202.157.86", + "37.202.157.91", + "37.202.157.96", + "37.202.157.110", + "37.202.157.119", + "37.202.157.121", + "37.202.157.126", + "37.202.157.145", + "37.202.157.175", + "37.202.157.189", + "37.202.157.197", + "37.202.157.207", + "37.202.157.209", + "37.202.157.234", + "37.202.157.237", + "37.202.158.15", + "37.202.158.31", + "37.202.158.42", + "37.202.158.59", + "37.202.158.71", + "37.202.158.79", + "37.202.158.110", + "37.202.158.154", + "37.202.158.186", + "37.202.158.198", + "37.202.158.205", + "37.202.158.238", + "37.202.158.240", + "37.202.158.249", + "37.202.158.254", + "37.202.159.1", + "37.202.159.20", + "37.202.159.58", + "37.202.159.72", + "37.202.159.78", + "37.202.159.90", + "37.202.159.101", + "37.202.159.126", + "37.202.159.135", + "37.202.159.136", + "37.202.159.147", + "37.202.159.171", + "37.202.159.184", + "37.202.159.206", + "37.202.159.210", + "37.202.159.224", + "37.202.159.243", + "37.202.159.246", + "37.202.159.253", + "37.202.168.10", + "37.202.168.28", + "37.202.168.55", + "37.202.168.61", + "37.202.168.64", + "37.202.168.80", + "37.202.168.96", + "37.202.168.101", + "37.202.168.112", + "37.202.168.119", + "37.202.168.120", + "37.202.168.121", + "37.202.168.122", + "37.202.168.135", + "37.202.168.137", + "37.202.168.176", + "37.202.168.177", + "37.202.168.228", + "37.202.169.3", + "37.202.169.8", + "37.202.169.59", + "37.202.169.65", + "37.202.169.67", + "37.202.169.89", + "37.202.169.104", + "37.202.169.145", + "37.202.169.146", + "37.202.169.168", + "37.202.169.179", + "37.202.169.189", + "37.202.169.197", + "37.202.169.222", + "37.202.169.224", + "37.202.169.242", + "37.202.170.3", + "37.202.170.7", + "37.202.170.49", + "37.202.170.60", + "37.202.170.80", + "37.202.170.88", + "37.202.170.96", + "37.202.170.115", + "37.202.170.145", + "37.202.170.209", + "37.202.170.252", + "37.202.171.4", + "37.202.171.21", + "37.202.171.22", + "37.202.171.37", + "37.202.171.49", + "37.202.171.82", + "37.202.171.100", + "37.202.171.106", + "37.202.171.131", + "37.202.171.145", + "37.202.171.146", + "37.202.171.152", + "37.202.171.157", + "37.202.171.158", + "37.202.171.171", + "37.202.171.180", + "37.202.171.188", + "37.202.171.198", + "37.202.171.203", + "37.202.171.208", + "37.202.171.230", + "37.202.171.251", + "37.202.171.253", + "37.202.171.254", + "37.202.171.255", + "37.202.172.2", + "37.202.172.6", + "37.202.172.10", + "37.202.172.11", + "37.202.172.15", + "37.202.172.16", + "37.202.172.18", + "37.202.172.36", + "37.202.172.55", + "37.202.172.70", + "37.202.172.76", + "37.202.172.81", + "37.202.172.103", + "37.202.172.115", + "37.202.172.132", + "37.202.172.138", + "37.202.172.171", + "37.202.172.187", + "37.202.172.202", + "37.202.172.222", + "37.202.172.246", + "37.202.173.4", + "37.202.173.14", + "37.202.173.23", + "37.202.173.44", + "37.202.173.57", + "37.202.173.64", + "37.202.173.72", + "37.202.173.88", + "37.202.173.91", + "37.202.173.93", + "37.202.173.112", + "37.202.173.148", + "37.202.173.155", + "37.202.173.179", + "37.202.173.183", + "37.202.173.188", + "37.202.173.205", + "37.202.173.228", + "37.202.173.240", + "37.202.173.243", + "37.202.173.244", + "37.202.174.3", + "37.202.174.10", + "37.202.174.17", + "37.202.174.23", + "37.202.174.33", + "37.202.174.50", + "37.202.174.53", + "37.202.174.62", + "37.202.174.71", + "37.202.174.91", + "37.202.174.101", + "37.202.174.104", + "37.202.174.105", + "37.202.174.115", + "37.202.174.121", + "37.202.174.128", + "37.202.174.161", + "37.202.174.181", + "37.202.174.184", + "37.202.174.212", + "37.202.174.231", + "37.202.174.232", + "37.202.175.34", + "37.202.175.35", + "37.202.175.37", + "37.202.175.39", + "37.202.175.52", + "37.202.175.59", + "37.202.175.68", + "37.202.175.69", + "37.202.175.87", + "37.202.175.88", + "37.202.175.93", + "37.202.175.94", + "37.202.175.98", + "37.202.175.115", + "37.202.175.116", + "37.202.175.120", + "37.202.175.129", + "37.202.175.173", + "37.202.175.175", + "37.202.175.185", + "37.202.175.205", + "37.202.175.207", + "37.202.175.208", + "37.202.175.224", + "37.202.175.232", + "37.202.175.242", + "37.202.176.4", + "37.202.176.10", + "37.202.176.13", + "37.202.176.26", + "37.202.176.34", + "37.202.176.49", + "37.202.176.51", + "37.202.176.59", + "37.202.176.64", + "37.202.176.66", + "37.202.176.70", + "37.202.176.76", + "37.202.176.87", + "37.202.176.95", + "37.202.176.97", + "37.202.176.104", + "37.202.176.111", + "37.202.176.119", + "37.202.176.132", + "37.202.176.143", + "37.202.176.154", + "37.202.176.168", + "37.202.176.171", + "37.202.176.179", + "37.202.176.186", + "37.202.176.189", + "37.202.176.199", + "37.202.176.200", + "37.202.176.201", + "37.202.176.205", + "37.202.176.226", + "37.202.176.230", + "37.202.177.6", + "37.202.177.10", + "37.202.177.11", + "37.202.177.26", + "37.202.177.29", + "37.202.177.30", + "37.202.177.44", + "37.202.177.46", + "37.202.177.51", + "37.202.177.58", + "37.202.177.96", + "37.202.177.107", + "37.202.177.112", + "37.202.177.117", + "37.202.177.119", + "37.202.177.137", + "37.202.177.152", + "37.202.177.174", + "37.202.177.192", + "37.202.177.193", + "37.202.177.211", + "37.202.177.217", + "37.202.177.219", + "37.202.177.224", + "37.202.177.242", + "37.202.178.4", + "37.202.178.8", + "37.202.178.12", + "37.202.178.13", + "37.202.178.24", + "37.202.178.60", + "37.202.178.76", + "37.202.178.82", + "37.202.178.106", + "37.202.178.109", + "37.202.178.123", + "37.202.178.128", + "37.202.178.136", + "37.202.178.143", + "37.202.178.178", + "37.202.178.182", + "37.202.178.198", + "37.202.178.240", + "37.202.178.242", + "37.202.178.244", + "37.202.178.245", + "37.202.179.2", + "37.202.179.19", + "37.202.179.20", + "37.202.179.21", + "37.202.179.28", + "37.202.179.32", + "37.202.179.47", + "37.202.179.53", + "37.202.179.64", + "37.202.179.67", + "37.202.179.71", + "37.202.179.80", + "37.202.179.92", + "37.202.179.96", + "37.202.179.110", + "37.202.179.188", + "37.202.179.191", + "37.202.179.193", + "37.202.179.197", + "37.202.179.203", + "37.202.179.206", + "37.202.179.224", + "37.202.179.229", + "37.202.179.230", + "37.202.179.238", + "37.202.179.239", + "37.202.180.2", + "37.202.180.43", + "37.202.180.51", + "37.202.180.72", + "37.202.180.79", + "37.202.180.83", + "37.202.180.94", + "37.202.180.112", + "37.202.180.121", + "37.202.180.133", + "37.202.180.135", + "37.202.180.136", + "37.202.180.149", + "37.202.180.176", + "37.202.180.189", + "37.202.180.196", + "37.202.180.210", + "37.202.180.219", + "37.202.180.225", + "37.202.180.228", + "37.202.180.238", + "37.202.181.5", + "37.202.181.7", + "37.202.181.20", + "37.202.181.22", + "37.202.181.26", + "37.202.181.37", + "37.202.181.42", + "37.202.181.54", + "37.202.181.62", + "37.202.181.67", + "37.202.181.77", + "37.202.181.111", + "37.202.181.112", + "37.202.181.120", + "37.202.181.125", + "37.202.181.165", + "37.202.181.192", + "37.202.181.212", + "37.202.181.219", + "37.202.181.223", + "37.202.181.231", + "37.202.181.233", + "37.202.181.243", + "37.202.181.253", + "37.202.182.41", + "37.202.182.46", + "37.202.182.48", + "37.202.182.59", + "37.202.182.66", + "37.202.182.72", + "37.202.182.83", + "37.202.182.87", + "37.202.182.90", + "37.202.182.130", + "37.202.182.153", + "37.202.182.167", + "37.202.182.189", + "37.202.182.203", + "37.202.184.36", + "37.202.184.39", + "37.202.184.42", + "37.202.184.50", + "37.202.184.82", + "37.202.184.98", + "37.202.184.130", + "37.202.184.131", + "37.202.184.146", + "37.202.184.147", + "37.202.184.154", + "37.202.184.162", + "37.202.184.189", + "37.202.184.218", + "37.202.184.251", + "37.202.184.252", + "37.202.184.253", + "37.202.184.254", + "37.202.185.1", + "37.202.185.22", + "37.202.185.26", + "37.202.185.31", + "37.202.185.35", + "37.202.185.36", + "37.202.185.51", + "37.202.185.80", + "37.202.185.91", + "37.202.185.125", + "37.202.185.142", + "37.202.185.156", + "37.202.185.170", + "37.202.185.196", + "37.202.185.241", + "37.202.185.253", + "37.202.186.0", + "37.202.186.2", + "37.202.186.5", + "37.202.186.17", + "37.202.186.29", + "37.202.186.33", + "37.202.186.40", + "37.202.186.51", + "37.202.186.52", + "37.202.186.53", + "37.202.186.58", + "37.202.186.64", + "37.202.186.67", + "37.202.186.78", + "37.202.186.114", + "37.202.186.117", + "37.202.186.120", + "37.202.186.140", + "37.202.186.151", + "37.202.186.155", + "37.202.186.181", + "37.202.186.190", + "37.202.186.201", + "37.202.186.209", + "37.202.186.211", + "37.202.186.223", + "37.202.186.247", + "37.202.186.252", + "37.202.187.43", + "37.202.187.48", + "37.202.187.67", + "37.202.187.69", + "37.202.187.72", + "37.202.187.79", + "37.202.187.80", + "37.202.187.82", + "37.202.187.120", + "37.202.187.132", + "37.202.187.135", + "37.202.187.137", + "37.202.187.140", + "37.202.187.160", + "37.202.187.181", + "37.202.187.182", + "37.202.187.185", + "37.202.187.204", + "37.202.187.210", + "37.202.187.211", + "37.202.187.220", + "37.202.187.227", + "37.202.188.10", + "37.202.188.13", + "37.202.188.15", + "37.202.188.27", + "37.202.188.40", + "37.202.188.43", + "37.202.188.50", + "37.202.188.54", + "37.202.188.59", + "37.202.188.90", + "37.202.188.91", + "37.202.188.110", + "37.202.188.144", + "37.202.188.149", + "37.202.188.158", + "37.202.188.165", + "37.202.188.177", + "37.202.188.179", + "37.202.188.185", + "37.202.188.195", + "37.202.188.198", + "37.202.188.235", + "37.202.188.238", + "37.202.188.240", + "37.202.188.255", + "37.202.189.9", + "37.202.189.25", + "37.202.189.39", + "37.202.189.44", + "37.202.189.47", + "37.202.189.76", + "37.202.189.94", + "37.202.189.99", + "37.202.189.100", + "37.202.189.103", + "37.202.189.112", + "37.202.189.134", + "37.202.189.138", + "37.202.189.147", + "37.202.189.149", + "37.202.189.158", + "37.202.189.159", + "37.202.189.189", + "37.202.189.200", + "37.202.189.214", + "37.202.189.235", + "37.202.189.239", + "37.202.190.6", + "37.202.190.8", + "37.202.190.16", + "37.202.190.61", + "37.202.190.80", + "37.202.190.82", + "37.202.190.85", + "37.202.190.88", + "37.202.190.92", + "37.202.190.103", + "37.202.190.118", + "37.202.190.137", + "37.202.190.147", + "37.202.190.153", + "37.202.190.162", + "37.202.190.166", + "37.202.190.172", + "37.202.190.182", + "37.202.190.190", + "37.202.190.194", + "37.202.190.200", + "37.202.190.217", + "37.202.190.219", + "37.202.190.222", + "37.202.190.226", + "37.202.190.240", + "37.202.190.241", + "37.202.190.242", + "37.202.190.244", + "37.202.191.2", + "37.202.191.4", + "37.202.191.12", + "37.202.191.17", + "37.202.191.21", + "37.202.191.58", + "37.202.191.63", + "37.202.191.78", + "37.202.191.90", + "37.202.191.106", + "37.202.191.128", + "37.202.191.155", + "37.202.191.175", + "37.202.191.185", + "37.202.191.212", + "37.202.191.228", + "37.202.191.230", + "37.202.191.232", + "37.202.191.238", + "37.202.191.240", + "37.202.225.185", + "37.202.228.171", + "37.202.228.254", + "37.202.229.103", + "37.202.231.16", + "37.202.231.59", + "37.202.231.242", + "37.202.237.194", + "37.202.239.132", + "37.228.136.214", + "37.228.137.85", + "37.228.137.88", + "37.228.137.107", + "37.228.137.108", + "37.228.137.156", + "37.228.137.157", + "37.228.137.158", + "37.228.137.254", + "37.228.138.66", + "37.228.139.27", + "37.228.139.51", + "37.228.139.61", + "37.228.139.226", + "37.254.0.1", + "37.254.96.1", + "37.255.64.1", + "37.255.128.41", + "37.255.133.26", + "37.255.133.27", + "37.255.133.28", + "37.255.133.29", + "37.255.134.66", + "37.255.134.160", + "37.255.134.194", + "37.255.135.34", + "37.255.135.35", + "37.255.135.50", + "37.255.135.66", + "37.255.135.203", + "37.255.149.136", + "37.255.149.222", + "37.255.173.83", + "37.255.173.134", + "37.255.177.65", + "37.255.177.243", + "37.255.186.150", + "37.255.187.147", + "37.255.192.12", + "37.255.192.24", + "37.255.192.96", + "37.255.192.191", + "37.255.192.227", + "37.255.193.110", + "37.255.193.115", + "37.255.193.151", + "37.255.193.159", + "37.255.193.229", + "37.255.193.241", + "37.255.194.14", + "37.255.194.19", + "37.255.194.51", + "37.255.194.85", + "37.255.194.100", + "37.255.194.108", + "37.255.194.121", + "37.255.194.139", + "37.255.194.171", + "37.255.194.198", + "37.255.194.199", + "37.255.194.213", + "37.255.194.231", + "37.255.195.7", + "37.255.195.10", + "37.255.195.96", + "37.255.195.170", + "37.255.195.194", + "37.255.196.26", + "37.255.196.90", + "37.255.196.95", + "37.255.196.125", + "37.255.196.150", + "37.255.196.199", + "37.255.197.63", + "37.255.197.190", + "37.255.198.41", + "37.255.198.110", + "37.255.198.220", + "37.255.198.231", + "37.255.199.204", + "37.255.199.248", + "37.255.200.103", + "37.255.200.127", + "37.255.200.161", + "37.255.200.204", + "37.255.200.254", + "37.255.201.105", + "37.255.201.171", + "37.255.201.250", + "37.255.202.94", + "37.255.202.123", + "37.255.202.124", + "37.255.202.160", + "37.255.202.219", + "37.255.202.244", + "37.255.202.251", + "37.255.203.94", + "37.255.203.130", + "37.255.203.155", + "37.255.203.235", + "37.255.203.250", + "37.255.204.1", + "37.255.204.238", + "37.255.204.239", + "37.255.205.22", + "37.255.205.232", + "37.255.206.150", + "37.255.206.159", + "37.255.206.180", + "37.255.207.114", + "37.255.207.164", + "37.255.207.236", + "37.255.208.10", + "37.255.208.12", + "37.255.208.43", + "37.255.208.53", + "37.255.209.0", + "37.255.209.44", + "37.255.209.123", + "37.255.209.136", + "37.255.209.149", + "37.255.209.161", + "37.255.209.212", + "37.255.210.51", + "37.255.210.55", + "37.255.210.70", + "37.255.210.92", + "37.255.210.168", + "37.255.211.26", + "37.255.211.56", + "37.255.211.106", + "37.255.211.221", + "37.255.211.245", + "37.255.211.252", + "37.255.212.30", + "37.255.212.46", + "37.255.213.36", + "37.255.213.38", + "37.255.213.51", + "37.255.213.88", + "37.255.213.150", + "37.255.213.235", + "37.255.214.119", + "37.255.215.21", + "37.255.215.185", + "37.255.216.19", + "37.255.216.22", + "37.255.216.113", + "37.255.216.160", + "37.255.217.22", + "37.255.217.41", + "37.255.217.115", + "37.255.217.164", + "37.255.217.196", + "37.255.218.116", + "37.255.218.143", + "37.255.218.170", + "37.255.218.191", + "37.255.219.17", + "37.255.219.190", + "37.255.220.48", + "37.255.220.51", + "37.255.220.59", + "37.255.220.121", + "37.255.220.162", + "37.255.220.172", + "37.255.220.208", + "37.255.221.6", + "37.255.221.16", + "37.255.221.40", + "37.255.221.62", + "37.255.221.147", + "37.255.221.163", + "37.255.222.36", + "37.255.222.39", + "37.255.222.42", + "37.255.222.124", + "37.255.222.223", + "37.255.222.232", + "37.255.223.34", + "37.255.223.38", + "37.255.223.40", + "37.255.223.51", + "37.255.223.201", + "37.255.223.235", + "37.255.223.239", + "37.255.224.3", + "37.255.224.143", + "37.255.225.38", + "37.255.225.108", + "37.255.228.106", + "37.255.228.122", + "37.255.228.165", + "37.255.228.219", + "37.255.229.35", + "37.255.229.88", + "37.255.229.99", + "37.255.229.163", + "37.255.229.168", + "37.255.229.238", + "37.255.230.64", + "37.255.230.169", + "37.255.230.246", + "37.255.231.32", + "37.255.231.128", + "37.255.231.132", + "37.255.231.172", + "37.255.231.184", + "37.255.231.207", + "37.255.231.221", + "37.255.232.10", + "37.255.232.130", + "37.255.232.194", + "37.255.232.233", + "37.255.232.255", + "37.255.233.12", + "37.255.233.128", + "37.255.233.143", + "37.255.233.226", + "37.255.233.253", + "37.255.234.15", + "37.255.234.75", + "37.255.234.132", + "37.255.234.134", + "37.255.234.180", + "37.255.234.188", + "37.255.235.111", + "37.255.235.138", + "37.255.236.5", + "37.255.236.64", + "37.255.236.97", + "37.255.236.134", + "37.255.236.149", + "37.255.236.193", + "37.255.236.209", + "37.255.237.68", + "37.255.237.69", + "37.255.237.70", + "37.255.237.116", + "37.255.237.117", + "37.255.237.118", + "37.255.237.119", + "37.255.237.132", + "37.255.238.4", + "37.255.238.12", + "37.255.238.23", + "37.255.238.142", + "37.255.238.188", + "37.255.238.253", + "37.255.239.81", + "37.255.239.99", + "37.255.239.104", + "37.255.239.171", + "37.255.240.0", + "37.255.240.23", + "37.255.240.124", + "37.255.240.217", + "37.255.241.27", + "37.255.241.102", + "37.255.241.122", + "37.255.241.129", + "37.255.241.137", + "37.255.241.140", + "37.255.241.213", + "37.255.242.91", + "37.255.242.182", + "37.255.242.186", + "37.255.242.212", + "37.255.243.44", + "37.255.243.69", + "37.255.243.169", + "37.255.243.205", + "37.255.243.218", + "37.255.244.53", + "37.255.244.254", + "37.255.245.67", + "37.255.246.156", + "37.255.248.156", + "37.255.248.202", + "37.255.248.241", + "37.255.254.18", + "37.255.254.20", + "37.255.254.254", + "37.255.255.18", + "45.8.160.13", + "45.8.160.79", + "45.8.160.82", + "45.8.160.84", + "45.8.160.94", + "45.8.160.211", + "45.8.160.232", + "45.8.160.237", + "45.8.160.253", + "45.9.252.86", + "45.9.253.10", + "45.9.253.46", + "45.9.253.53", + "45.9.253.122", + "45.9.253.169", + "45.9.254.90", + "45.11.185.253", + "45.11.187.165", + "45.11.187.173", + "45.15.200.30", + "45.81.19.10", + "45.81.19.11", + "45.81.19.13", + "45.81.19.14", + "45.81.19.15", + "45.81.19.16", + "45.81.19.19", + "45.81.19.59", + "45.81.19.132", + "45.81.19.150", + "45.81.19.235", + "45.82.137.234", + "45.82.138.48", + "45.84.157.43", + "45.84.157.200", + "45.84.157.202", + "45.84.157.203", + "45.84.157.204", + "45.84.157.209", + "45.84.157.210", + "45.84.157.211", + "45.84.157.212", + "45.84.157.213", + "45.84.157.217", + "45.84.157.218", + "45.84.157.220", + "45.86.4.11", + "45.86.4.66", + "45.86.4.67", + "45.86.4.82", + "45.86.4.100", + "45.86.5.211", + "45.86.6.25", + "45.86.6.113", + "45.87.4.116", + "45.87.4.151", + "45.87.5.66", + "45.87.5.148", + "45.87.5.178", + "45.87.6.9", + "45.87.6.38", + "45.87.6.45", + "45.89.236.37", + "45.89.236.181", + "45.89.237.66", + "45.89.239.214", + "45.89.239.227", + "45.90.72.166", + "45.90.73.207", + "45.90.74.109", + "45.90.74.225", + "45.92.93.236", + "45.92.94.4", + "45.92.94.20", + "45.92.94.122", + "45.92.95.59", + "45.94.214.3", + "45.94.214.163", + "45.94.214.219", + "45.94.214.240", + "45.94.215.32", + "45.94.255.186", + "45.129.36.45", + "45.129.36.181", + "45.129.36.220", + "45.129.36.221", + "45.129.36.222", + "45.129.37.123", + "45.129.37.124", + "45.129.37.145", + "45.129.37.148", + "45.129.37.153", + "45.129.37.156", + "45.129.37.157", + "45.129.37.158", + "45.129.37.185", + "45.129.37.198", + "45.129.37.199", + "45.129.38.35", + "45.129.38.51", + "45.129.38.53", + "45.129.38.56", + "45.129.38.60", + "45.129.39.5", + "45.129.39.6", + "45.129.39.9", + "45.129.39.12", + "45.129.39.13", + "45.129.39.168", + "45.132.172.9", + "45.132.172.56", + "45.132.172.84", + "45.132.172.93", + "45.132.172.100", + "45.132.172.144", + "45.132.172.161", + "45.132.173.200", + "45.132.174.138", + "45.132.174.191", + "45.132.175.4", + "45.134.99.4", + "45.135.241.79", + "45.135.243.61", + "45.135.243.70", + "45.138.132.27", + "45.138.133.205", + "45.138.135.32", + "45.138.135.39", + "45.138.135.57", + "45.138.135.224", + "45.139.10.61", + "45.139.10.81", + "45.139.10.200", + "45.139.11.250", + "45.140.225.4", + "45.140.225.6", + "45.140.225.60", + "45.140.225.62", + "45.140.225.218", + "45.140.226.9", + "45.140.226.10", + "45.140.226.11", + "45.140.226.12", + "45.140.226.25", + "45.140.226.73", + "45.140.226.226", + "45.142.190.13", + "45.142.190.24", + "45.142.190.46", + "45.142.190.139", + "45.142.190.142", + "45.142.191.3", + "45.142.191.9", + "45.142.191.11", + "45.142.191.17", + "45.142.191.19", + "45.142.191.24", + "45.142.191.33", + "45.142.191.35", + "45.142.191.38", + "45.142.191.39", + "45.144.18.7", + "45.144.18.52", + "45.144.19.70", + "45.144.19.75", + "45.144.19.76", + "45.144.19.77", + "45.144.19.78", + "45.144.19.85", + "45.147.76.252", + "45.147.77.59", + "45.147.77.88", + "45.147.77.169", + "45.147.77.170", + "45.147.77.171", + "45.147.77.195", + "45.147.79.36", + "45.147.79.37", + "45.147.79.38", + "45.147.79.39", + "45.148.248.8", + "45.148.249.62", + "45.148.250.68", + "45.149.76.12", + "45.149.76.138", + "45.149.76.183", + "45.149.76.195", + "45.149.77.114", + "45.149.77.135", + "45.149.77.186", + "45.149.78.156", + "45.149.78.226", + "45.149.79.53", + "45.156.184.16", + "45.156.184.26", + "45.156.184.32", + "45.156.184.36", + "45.156.184.125", + "45.156.185.30", + "45.156.185.59", + "45.156.185.80", + "45.156.185.121", + "45.156.185.129", + "45.156.185.130", + "45.156.185.164", + "45.156.185.187", + "45.156.185.192", + "45.156.185.201", + "45.156.185.240", + "45.156.185.254", + "45.156.186.108", + "45.156.186.230", + "45.156.186.240", + "45.156.187.106", + "45.158.120.7", + "45.159.112.10", + "45.159.112.43", + "45.159.112.87", + "45.159.112.107", + "45.159.112.143", + "45.159.112.144", + "45.159.112.196", + "45.159.113.144", + "45.159.113.161", + "45.159.113.165", + "45.159.113.172", + "45.159.113.242", + "45.159.113.244", + "45.159.114.200", + "45.159.114.201", + "45.159.114.202", + "45.159.114.236", + "45.159.115.117", + "45.159.115.119", + "45.159.115.127", + "45.159.115.129", + "45.159.115.151", + "45.159.149.40", + "45.159.149.108", + "45.159.149.158", + "45.159.150.94", + "45.159.150.117", + "45.159.150.157", + "45.159.150.165", + "45.159.151.16", + "45.159.151.32", + "45.159.151.39", + "45.159.151.128", + "45.159.151.169", + "45.159.197.86", + "45.159.197.114", + "45.159.198.246", + "46.28.72.18", + "46.28.72.19", + "46.28.72.20", + "46.28.72.21", + "46.28.72.133", + "46.28.74.130", + "46.28.75.66", + "46.28.75.67", + "46.28.75.106", + "46.28.75.107", + "46.28.75.108", + "46.28.75.109", + "46.28.75.130", + "46.28.76.101", + "46.28.77.26", + "46.28.77.38", + "46.28.77.54", + "46.28.77.194", + "46.32.1.13", + "46.32.1.75", + "46.32.1.118", + "46.32.1.145", + "46.32.1.165", + "46.32.1.171", + "46.32.1.202", + "46.32.1.250", + "46.32.2.13", + "46.32.4.82", + "46.32.4.83", + "46.32.4.85", + "46.32.4.86", + "46.32.4.90", + "46.32.4.164", + "46.32.21.14", + "46.32.25.75", + "46.32.25.76", + "46.32.25.77", + "46.32.25.78", + "46.32.25.178", + "46.32.28.17", + "46.32.28.29", + "46.32.28.40", + "46.32.28.52", + "46.32.28.68", + "46.32.28.128", + "46.32.28.141", + "46.32.28.144", + "46.32.28.209", + "46.32.28.220", + "46.32.28.238", + "46.32.31.50", + "46.34.96.20", + "46.34.161.9", + "46.34.161.41", + "46.34.161.67", + "46.34.161.68", + "46.34.161.104", + "46.34.161.110", + "46.34.161.183", + "46.34.161.186", + "46.34.161.226", + "46.34.161.227", + "46.34.162.18", + "46.34.162.19", + "46.34.162.20", + "46.34.162.22", + "46.34.163.161", + "46.34.164.10", + "46.34.164.18", + "46.34.164.94", + "46.34.164.105", + "46.34.164.110", + "46.34.164.218", + "46.34.164.238", + "46.34.165.50", + "46.34.165.134", + "46.34.165.138", + "46.34.165.186", + "46.34.166.18", + "46.34.166.91", + "46.34.166.106", + "46.34.166.114", + "46.34.166.115", + "46.34.166.186", + "46.34.166.187", + "46.34.166.235", + "46.34.166.237", + "46.34.167.14", + "46.34.167.52", + "46.34.167.162", + "46.34.178.130", + "46.36.96.30", + "46.36.103.93", + "46.36.107.56", + "46.38.138.50", + "46.38.138.72", + "46.38.138.139", + "46.38.138.151", + "46.38.138.165", + "46.38.143.163", + "46.38.143.189", + "46.38.147.88", + "46.38.147.219", + "46.38.147.220", + "46.38.147.222", + "46.38.149.4", + "46.38.149.200", + "46.38.149.201", + "46.38.151.110", + "46.51.37.37", + "46.51.37.38", + "46.51.37.70", + "46.51.37.159", + "46.51.37.181", + "46.51.37.233", + "46.51.37.246", + "46.100.5.15", + "46.100.5.59", + "46.100.5.122", + "46.100.5.134", + "46.100.5.139", + "46.100.5.143", + "46.100.5.186", + "46.100.5.221", + "46.100.5.225", + "46.100.6.13", + "46.100.6.82", + "46.100.6.132", + "46.100.6.184", + "46.100.6.198", + "46.100.7.0", + "46.100.7.18", + "46.100.7.32", + "46.100.7.60", + "46.100.7.143", + "46.100.7.190", + "46.100.7.198", + "46.100.7.234", + "46.100.8.3", + "46.100.8.84", + "46.100.8.134", + "46.100.8.180", + "46.100.9.8", + "46.100.9.100", + "46.100.9.191", + "46.100.9.199", + "46.100.10.34", + "46.100.10.77", + "46.100.11.42", + "46.100.11.70", + "46.100.11.131", + "46.100.11.138", + "46.100.11.139", + "46.100.11.171", + "46.100.11.212", + "46.100.11.242", + "46.100.11.250", + "46.100.12.23", + "46.100.12.81", + "46.100.12.99", + "46.100.12.102", + "46.100.12.112", + "46.100.12.130", + "46.100.12.141", + "46.100.12.145", + "46.100.12.175", + "46.100.12.176", + "46.100.12.208", + "46.100.12.214", + "46.100.13.12", + "46.100.13.14", + "46.100.13.16", + "46.100.13.93", + "46.100.13.145", + "46.100.13.147", + "46.100.13.148", + "46.100.14.1", + "46.100.14.69", + "46.100.34.124", + "46.100.40.59", + "46.100.40.72", + "46.100.40.93", + "46.100.40.132", + "46.100.40.175", + "46.100.40.185", + "46.100.40.187", + "46.100.40.210", + "46.100.40.230", + "46.100.41.100", + "46.100.41.120", + "46.100.41.217", + "46.100.41.254", + "46.100.42.40", + "46.100.42.49", + "46.100.42.51", + "46.100.42.103", + "46.100.42.132", + "46.100.42.160", + "46.100.42.163", + "46.100.42.188", + "46.100.42.245", + "46.100.43.11", + "46.100.43.41", + "46.100.43.154", + "46.100.45.39", + "46.100.45.52", + "46.100.45.72", + "46.100.45.195", + "46.100.45.252", + "46.100.46.8", + "46.100.46.56", + "46.100.46.107", + "46.100.46.111", + "46.100.46.113", + "46.100.46.122", + "46.100.46.126", + "46.100.46.135", + "46.100.46.167", + "46.100.47.51", + "46.100.47.95", + "46.100.47.112", + "46.100.47.122", + "46.100.47.137", + "46.100.47.202", + "46.100.47.249", + "46.100.47.250", + "46.100.48.37", + "46.100.48.63", + "46.100.48.66", + "46.100.48.120", + "46.100.48.178", + "46.100.48.181", + "46.100.48.199", + "46.100.48.235", + "46.100.50.8", + "46.100.50.33", + "46.100.50.63", + "46.100.50.106", + "46.100.50.115", + "46.100.50.118", + "46.100.50.122", + "46.100.50.155", + "46.100.52.16", + "46.100.52.40", + "46.100.52.52", + "46.100.52.112", + "46.100.52.170", + "46.100.52.192", + "46.100.52.239", + "46.100.52.247", + "46.100.52.251", + "46.100.53.35", + "46.100.53.36", + "46.100.53.85", + "46.100.53.106", + "46.100.53.115", + "46.100.53.176", + "46.100.53.185", + "46.100.53.187", + "46.100.53.212", + "46.100.53.232", + "46.100.54.1", + "46.100.54.4", + "46.100.54.23", + "46.100.54.48", + "46.100.54.53", + "46.100.54.167", + "46.100.54.191", + "46.100.55.16", + "46.100.55.18", + "46.100.55.37", + "46.100.55.54", + "46.100.55.67", + "46.100.55.72", + "46.100.55.158", + "46.100.55.172", + "46.100.55.190", + "46.100.55.193", + "46.100.55.207", + "46.100.55.232", + "46.100.56.237", + "46.100.57.62", + "46.100.57.115", + "46.100.57.124", + "46.100.57.237", + "46.100.58.16", + "46.100.58.87", + "46.100.58.141", + "46.100.58.142", + "46.100.59.119", + "46.100.59.146", + "46.100.59.148", + "46.100.59.168", + "46.100.60.24", + "46.100.60.26", + "46.100.60.28", + "46.100.60.67", + "46.100.60.73", + "46.100.60.80", + "46.100.60.122", + "46.100.60.144", + "46.100.60.177", + "46.100.61.16", + "46.100.61.17", + "46.100.61.32", + "46.100.61.33", + "46.100.61.196", + "46.100.62.21", + "46.100.62.44", + "46.100.62.56", + "46.100.62.80", + "46.100.62.110", + "46.100.62.129", + "46.100.62.169", + "46.100.62.235", + "46.100.63.28", + "46.100.63.43", + "46.100.63.112", + "46.100.63.124", + "46.100.63.220", + "46.100.63.221", + "46.100.63.244", + "46.100.74.110", + "46.100.84.9", + "46.100.84.20", + "46.100.84.47", + "46.100.84.171", + "46.100.84.197", + "46.100.84.205", + "46.100.84.227", + "46.100.85.171", + "46.100.85.218", + "46.100.85.234", + "46.100.90.82", + "46.100.90.105", + "46.100.90.116", + "46.100.90.135", + "46.100.90.138", + "46.100.90.168", + "46.100.91.17", + "46.100.91.22", + "46.100.91.27", + "46.100.91.109", + "46.100.91.134", + "46.100.91.218", + "46.100.92.15", + "46.100.92.50", + "46.100.92.84", + "46.100.92.92", + "46.100.92.113", + "46.100.92.128", + "46.100.92.130", + "46.100.92.227", + "46.100.93.214", + "46.100.94.2", + "46.100.94.7", + "46.100.94.132", + "46.100.94.147", + "46.100.94.190", + "46.100.95.29", + "46.100.95.119", + "46.100.95.162", + "46.100.95.193", + "46.100.97.51", + "46.100.97.188", + "46.100.99.31", + "46.100.101.194", + "46.100.101.226", + "46.100.101.227", + "46.100.104.22", + "46.100.104.48", + "46.100.104.162", + "46.100.104.182", + "46.100.104.183", + "46.100.107.90", + "46.100.107.213", + "46.100.116.145", + "46.100.132.34", + "46.100.134.39", + "46.100.164.15", + "46.100.164.22", + "46.100.164.27", + "46.100.164.39", + "46.100.164.48", + "46.100.164.88", + "46.100.164.112", + "46.100.164.157", + "46.100.164.165", + "46.100.164.195", + "46.100.164.205", + "46.100.164.212", + "46.100.164.216", + "46.100.164.242", + "46.100.165.4", + "46.100.165.56", + "46.100.165.140", + "46.100.165.150", + "46.100.165.151", + "46.100.165.196", + "46.100.165.249", + "46.100.166.10", + "46.100.166.18", + "46.100.166.32", + "46.100.166.43", + "46.100.166.55", + "46.100.166.153", + "46.100.166.194", + "46.100.166.204", + "46.100.167.86", + "46.100.167.99", + "46.100.167.117", + "46.100.167.185", + "46.100.167.189", + "46.100.168.101", + "46.100.171.4", + "46.100.177.18", + "46.100.177.41", + "46.100.177.184", + "46.100.178.45", + "46.100.179.39", + "46.100.180.71", + "46.100.181.18", + "46.100.182.46", + "46.100.182.95", + "46.100.183.82", + "46.100.183.91", + "46.100.248.135", + "46.100.248.199", + "46.100.249.82", + "46.100.249.89", + "46.100.249.117", + "46.100.249.190", + "46.100.249.207", + "46.100.250.5", + "46.100.251.5", + "46.100.251.156", + "46.102.132.2", + "46.102.132.3", + "46.102.132.4", + "46.102.132.31", + "46.102.137.20", + "46.102.137.22", + "46.102.137.32", + "46.102.139.18", + "46.102.139.21", + "46.102.139.85", + "46.102.140.35", + "46.102.143.141", + "46.102.143.143", + "46.102.184.135", + "46.102.184.175", + "46.102.187.30", + "46.102.187.139", + "46.148.32.5", + "46.148.32.16", + "46.148.32.17", + "46.148.32.26", + "46.148.32.70", + "46.148.32.98", + "46.148.32.112", + "46.148.32.121", + "46.148.32.125", + "46.148.32.135", + "46.148.32.158", + "46.148.32.167", + "46.148.32.176", + "46.148.32.209", + "46.148.32.235", + "46.148.32.238", + "46.148.32.239", + "46.148.34.74", + "46.148.34.226", + "46.148.35.43", + "46.148.35.57", + "46.148.35.59", + "46.148.35.210", + "46.148.35.217", + "46.148.36.10", + "46.148.36.40", + "46.148.36.55", + "46.148.36.93", + "46.148.36.100", + "46.148.36.105", + "46.148.36.106", + "46.148.36.147", + "46.148.36.150", + "46.148.36.219", + "46.148.36.241", + "46.148.36.244", + "46.148.38.154", + "46.148.38.167", + "46.148.39.25", + "46.148.39.75", + "46.148.39.104", + "46.148.40.0", + "46.148.40.2", + "46.148.40.32", + "46.148.40.40", + "46.148.40.47", + "46.148.41.5", + "46.148.41.9", + "46.148.42.210", + "46.148.43.33", + "46.148.43.206", + "46.148.43.212", + "46.148.44.48", + "46.148.44.54", + "46.148.44.106", + "46.148.44.167", + "46.148.44.221", + "46.148.44.227", + "46.148.45.5", + "46.148.45.26", + "46.148.45.28", + "46.148.45.33", + "46.148.45.42", + "46.148.45.45", + "46.148.45.80", + "46.148.45.89", + "46.148.45.114", + "46.148.45.133", + "46.148.45.145", + "46.148.45.154", + "46.148.45.155", + "46.148.45.202", + "46.148.45.208", + "46.148.45.223", + "46.148.45.233", + "46.148.45.235", + "46.148.45.242", + "46.167.128.168", + "46.167.131.114", + "46.167.132.199", + "46.167.133.40", + "46.167.133.248", + "46.167.134.160", + "46.167.135.178", + "46.167.136.14", + "46.167.136.26", + "46.167.136.112", + "46.167.138.65", + "46.167.138.205", + "46.167.139.228", + "46.167.140.98", + "46.167.141.0", + "46.167.141.21", + "46.167.141.22", + "46.167.141.138", + "46.167.143.104", + "46.167.146.138", + "46.167.148.146", + "46.167.149.72", + "46.167.149.76", + "46.167.150.29", + "46.167.150.227", + "46.167.152.192", + "46.167.152.245", + "46.167.154.99", + "46.167.154.209", + "46.167.155.0", + "46.167.155.69", + "46.167.156.137", + "46.167.156.199", + "46.167.157.162", + "46.167.157.169", + "46.167.157.205", + "46.167.158.135", + "46.167.159.0", + "46.182.32.129", + "46.209.5.114", + "46.209.12.25", + "46.209.15.187", + "46.209.20.85", + "46.209.20.207", + "46.209.22.226", + "46.209.22.227", + "46.209.22.228", + "46.209.22.229", + "46.209.27.3", + "46.209.28.27", + "46.209.30.11", + "46.209.30.12", + "46.209.30.107", + "46.209.30.108", + "46.209.45.246", + "46.209.48.5", + "46.209.48.82", + "46.209.48.114", + "46.209.48.115", + "46.209.48.116", + "46.209.48.118", + "46.209.48.194", + "46.209.48.213", + "46.209.49.254", + "46.209.58.194", + "46.209.58.195", + "46.209.61.98", + "46.209.61.99", + "46.209.63.218", + "46.209.70.226", + "46.209.73.54", + "46.209.74.198", + "46.209.79.58", + "46.209.79.59", + "46.209.79.60", + "46.209.79.61", + "46.209.79.62", + "46.209.92.162", + "46.209.92.163", + "46.209.92.164", + "46.209.93.85", + "46.209.94.14", + "46.209.95.102", + "46.209.100.114", + "46.209.103.118", + "46.209.115.30", + "46.209.115.74", + "46.209.118.178", + "46.209.118.180", + "46.209.118.181", + "46.209.118.182", + "46.209.122.194", + "46.209.122.196", + "46.209.122.226", + "46.209.140.170", + "46.209.140.172", + "46.209.142.2", + "46.209.142.4", + "46.209.142.51", + "46.209.142.53", + "46.209.142.82", + "46.209.142.83", + "46.209.142.84", + "46.209.142.85", + "46.209.148.202", + "46.209.148.203", + "46.209.148.204", + "46.209.148.205", + "46.209.153.225", + "46.209.153.226", + "46.209.153.228", + "46.209.153.229", + "46.209.153.230", + "46.209.153.233", + "46.209.153.234", + "46.209.153.236", + "46.209.153.238", + "46.209.153.240", + "46.209.153.243", + "46.209.153.246", + "46.209.153.247", + "46.209.153.248", + "46.209.153.249", + "46.209.153.250", + "46.209.156.131", + "46.209.157.18", + "46.209.157.22", + "46.209.181.202", + "46.209.195.18", + "46.209.195.19", + "46.209.195.20", + "46.209.195.21", + "46.209.199.86", + "46.209.201.58", + "46.209.203.141", + "46.209.203.143", + "46.209.204.142", + "46.209.206.62", + "46.209.206.178", + "46.209.207.97", + "46.209.207.98", + "46.209.207.146", + "46.209.207.147", + "46.209.207.148", + "46.209.207.149", + "46.209.207.150", + "46.209.207.151", + "46.209.207.152", + "46.209.207.155", + "46.209.207.156", + "46.209.207.158", + "46.209.211.50", + "46.209.221.66", + "46.209.221.68", + "46.209.221.69", + "46.209.221.70", + "46.209.221.71", + "46.209.221.72", + "46.209.221.73", + "46.209.221.75", + "46.209.221.76", + "46.209.221.77", + "46.209.221.78", + "46.209.221.82", + "46.209.221.84", + "46.209.221.85", + "46.209.221.86", + "46.209.221.87", + "46.209.221.88", + "46.209.221.89", + "46.209.221.90", + "46.209.221.91", + "46.209.221.93", + "46.209.222.123", + "46.209.232.146", + "46.209.239.30", + "46.209.240.90", + "46.209.240.226", + "46.209.240.227", + "46.209.240.228", + "46.209.241.67", + "46.209.241.81", + "46.209.241.82", + "46.209.241.155", + "46.209.241.226", + "46.209.241.227", + "46.209.241.229", + "46.209.241.250", + "46.209.247.155", + "46.209.251.94", + "46.235.76.81", + "46.235.76.225", + "46.235.77.15", + "46.235.77.80", + "46.235.77.110", + "46.235.77.118", + "46.235.77.131", + "46.235.77.153", + "46.235.77.185", + "46.235.77.251", + "46.245.0.109", + "46.245.0.128", + "46.245.0.138", + "46.245.0.167", + "46.245.1.65", + "46.245.1.104", + "46.245.2.47", + "46.245.2.161", + "46.245.2.253", + "46.245.3.30", + "46.245.4.85", + "46.245.5.63", + "46.245.5.75", + "46.245.5.202", + "46.245.5.236", + "46.245.6.215", + "46.245.7.102", + "46.245.7.223", + "46.245.8.66", + "46.245.10.13", + "46.245.12.183", + "46.245.12.187", + "46.245.12.222", + "46.245.12.241", + "46.245.17.101", + "46.245.19.79", + "46.245.25.72", + "46.245.30.51", + "46.245.32.26", + "46.245.32.29", + "46.245.32.40", + "46.245.37.66", + "46.245.38.42", + "46.245.38.99", + "46.245.38.107", + "46.245.38.119", + "46.245.38.206", + "46.245.38.234", + "46.245.39.63", + "46.245.39.101", + "46.245.39.102", + "46.245.39.184", + "46.245.39.185", + "46.245.39.186", + "46.245.39.210", + "46.245.41.104", + "46.245.42.44", + "46.245.43.249", + "46.245.44.1", + "46.245.44.9", + "46.245.46.128", + "46.245.47.207", + "46.245.65.2", + "46.245.65.3", + "46.245.66.4", + "46.245.66.201", + "46.245.66.202", + "46.245.66.203", + "46.245.66.204", + "46.245.66.205", + "46.245.66.251", + "46.245.66.253", + "46.245.67.142", + "46.245.67.143", + "46.245.67.144", + "46.245.67.146", + "46.245.67.147", + "46.245.67.148", + "46.245.67.151", + "46.245.67.161", + "46.245.69.214", + "46.245.72.218", + "46.245.72.219", + "46.245.72.220", + "46.245.76.203", + "46.245.77.98", + "46.245.77.99", + "46.245.77.100", + "46.245.77.101", + "46.245.77.102", + "46.245.77.103", + "46.245.77.106", + "46.245.77.107", + "46.245.77.108", + "46.245.77.109", + "46.245.77.110", + "46.245.78.154", + "46.245.80.172", + "46.245.81.34", + "46.245.81.115", + "46.245.81.116", + "46.245.81.117", + "46.245.81.118", + "46.245.83.142", + "46.245.84.81", + "46.245.84.82", + "46.245.84.83", + "46.245.84.84", + "46.245.84.86", + "46.245.86.185", + "46.245.87.218", + "46.245.87.219", + "46.245.87.221", + "46.245.89.99", + "46.245.89.101", + "46.245.90.73", + "46.245.90.103", + "46.245.91.68", + "46.245.91.71", + "46.245.91.76", + "46.245.91.82", + "46.245.91.83", + "46.245.91.84", + "46.245.91.85", + "46.245.91.86", + "46.245.91.87", + "46.245.91.88", + "46.245.91.89", + "46.245.91.90", + "46.245.91.91", + "46.245.91.92", + "46.245.91.93", + "46.245.91.94", + "46.245.92.159", + "46.245.94.106", + "46.245.94.149", + "46.245.94.150", + "46.245.94.151", + "46.245.94.177", + "46.245.94.178", + "46.245.95.30", + "46.245.95.65", + "46.245.95.188", + "46.245.98.77", + "46.245.98.133", + "46.245.107.197", + "46.245.112.16", + "46.245.112.17", + "46.245.112.18", + "46.245.114.226", + "46.245.114.227", + "46.245.115.36", + "46.245.115.39", + "46.245.115.40", + "46.245.115.43", + "46.245.115.202", + "46.245.117.51", + "46.245.118.147", + "46.245.122.19", + "46.245.122.29", + "46.245.123.9", + "46.245.123.10", + "46.245.123.82", + "46.245.123.84", + "46.245.123.86", + "46.245.124.224", + "46.249.120.25", + "46.249.120.43", + "46.249.120.104", + "46.249.121.88", + "46.249.121.89", + "46.249.121.91", + "46.249.121.97", + "46.249.121.190", + "46.249.122.3", + "46.249.122.10", + "46.249.122.12", + "46.249.122.13", + "46.249.122.15", + "46.249.122.18", + "46.249.122.21", + "46.249.122.26", + "46.249.122.29", + "46.249.122.32", + "46.249.122.34", + "46.249.122.35", + "46.249.122.39", + "46.249.122.41", + "46.249.122.48", + "46.249.122.57", + "46.249.123.7", + "46.249.123.23", + "46.249.124.14", + "46.249.124.34", + "46.249.124.244", + "46.249.124.245", + "46.249.125.9", + "46.249.125.129", + "46.249.125.200", + "46.249.125.205", + "46.249.125.215", + "46.249.125.225", + "46.249.125.244", + "46.249.126.93", + "46.249.127.121", + "46.249.127.199", + "62.3.14.8", + "62.3.14.136", + "62.3.41.30", + "62.3.41.88", + "62.3.41.112", + "62.3.41.131", + "62.3.41.165", + "62.3.42.2", + "62.3.42.3", + "62.3.42.50", + "62.3.42.133", + "62.3.42.136", + "62.3.42.221", + "62.3.42.241", + "62.60.128.143", + "62.60.128.166", + "62.60.141.98", + "62.60.141.99", + "62.60.141.100", + "62.60.141.102", + "62.60.141.106", + "62.60.141.107", + "62.60.141.108", + "62.60.141.109", + "62.60.141.254", + "62.60.143.146", + "62.60.143.151", + "62.60.143.163", + "62.60.143.240", + "62.60.143.243", + "62.60.144.150", + "62.60.144.151", + "62.60.144.154", + "62.60.144.167", + "62.60.144.177", + "62.60.144.184", + "62.60.144.187", + "62.60.144.195", + "62.60.144.196", + "62.60.144.197", + "62.60.144.198", + "62.60.144.199", + "62.60.144.200", + "62.60.160.29", + "62.60.160.81", + "62.60.160.123", + "62.60.161.203", + "62.60.161.217", + "62.60.162.97", + "62.60.162.148", + "62.60.162.213", + "62.60.163.202", + "62.60.163.227", + "62.60.163.233", + "62.60.163.251", + "62.60.164.128", + "62.60.164.146", + "62.60.167.26", + "62.60.167.29", + "62.60.190.190", + "62.60.190.200", + "62.60.200.18", + "62.60.200.19", + "62.60.200.20", + "62.60.200.170", + "62.60.200.243", + "62.60.200.244", + "62.60.200.245", + "62.60.200.246", + "62.60.204.141", + "62.60.205.24", + "62.60.206.6", + "62.60.206.17", + "62.60.207.160", + "62.60.207.177", + "62.60.210.60", + "62.60.210.179", + "62.60.212.164", + "62.60.214.47", + "62.60.214.71", + "62.60.214.128", + "62.60.214.134", + "62.60.214.189", + "62.60.218.224", + "62.106.95.53", + "62.106.95.132", + "62.204.61.3", + "62.204.61.16", + "62.204.61.20", + "62.204.61.40", + "62.204.61.165", + "62.204.61.188", + "62.220.100.170", + "62.220.100.172", + "62.220.100.183", + "62.220.112.63", + "62.220.113.122", + "62.220.113.160", + "62.220.114.193", + "62.220.114.197", + "62.220.114.241", + "62.220.115.99", + "62.220.117.202", + "62.220.124.52", + "62.220.124.54", + "62.220.126.3", + "62.220.126.4", + "62.220.126.5", + "62.220.126.6", + "62.220.126.8", + "62.220.126.9", + "62.220.126.15", + "62.220.126.16", + "62.220.126.23", + "62.220.126.26", + "62.220.126.28", + "62.220.126.29", + "62.220.126.30", + "62.220.126.31", + "62.220.126.33", + "62.220.126.34", + "62.220.126.35", + "62.220.126.36", + "62.220.126.37", + "62.220.126.42", + "62.220.126.48", + "62.220.126.50", + "62.220.126.52", + "62.220.126.57", + "62.220.126.73", + "62.220.126.74", + "62.220.126.75", + "62.220.126.78", + "62.220.126.79", + "62.220.126.109", + "62.220.126.111", + "62.220.126.119", + "62.220.126.153", + "62.220.126.154", + "62.220.126.155", + "62.220.126.157", + "62.220.126.159", + "62.220.126.171", + "62.220.126.172", + "62.220.126.173", + "62.220.126.180", + "62.220.126.209", + "62.220.126.210", + "62.220.126.211", + "62.220.126.213", + "62.220.126.214", + "62.220.126.215", + "62.220.126.221", + "62.220.126.222", + "62.220.126.226", + "62.220.126.236", + "62.220.126.237", + "62.220.126.238", + "62.220.126.239", + "62.220.126.244", + "62.220.126.245", + "62.220.126.246", + "62.220.127.82", + "62.220.127.116", + "62.220.127.117", + "62.220.127.118", + "62.220.127.186", + "66.79.96.123", + "66.79.97.38", + "66.79.98.201", + "66.79.98.210", + "66.79.99.182", + "66.79.100.21", + "66.79.100.86", + "66.79.100.90", + "66.79.101.14", + "66.79.101.45", + "66.79.102.175", + "66.79.103.222", + "66.79.105.22", + "66.79.105.60", + "77.36.237.37", + "77.36.243.129", + "77.81.34.250", + "77.81.76.9", + "77.81.76.16", + "77.81.76.73", + "77.81.78.16", + "77.81.78.31", + "77.81.78.39", + "77.81.78.61", + "77.81.78.63", + "77.81.78.82", + "77.81.78.93", + "77.81.78.98", + "77.81.78.108", + "77.81.78.111", + "77.81.78.117", + "77.81.78.123", + "77.81.78.125", + "77.81.78.128", + "77.81.78.141", + "77.81.78.157", + "77.81.78.186", + "77.81.82.1", + "77.81.82.21", + "77.81.82.194", + "77.81.83.3", + "77.81.83.10", + "77.81.144.14", + "77.81.146.44", + "77.81.147.224", + "77.81.147.254", + "77.81.148.63", + "77.81.148.122", + "77.81.148.155", + "77.81.148.182", + "77.81.149.7", + "77.81.149.100", + "77.81.149.196", + "77.81.149.231", + "77.81.151.101", + "77.81.151.131", + "77.81.152.62", + "77.81.152.97", + "77.81.152.229", + "77.81.154.30", + "77.81.156.28", + "77.81.157.124", + "77.81.158.53", + "77.81.159.164", + "77.95.219.74", + "77.95.219.126", + "77.95.219.155", + "77.95.219.182", + "77.104.66.74", + "77.104.66.134", + "77.104.67.146", + "77.104.67.147", + "77.104.67.148", + "77.104.67.149", + "77.104.67.150", + "77.104.67.151", + "77.104.67.152", + "77.104.67.153", + "77.104.67.154", + "77.104.68.129", + "77.104.68.153", + "77.104.68.212", + "77.104.72.22", + "77.104.72.146", + "77.104.73.66", + "77.104.73.67", + "77.104.73.68", + "77.104.73.69", + "77.104.73.70", + "77.104.73.71", + "77.104.73.72", + "77.104.73.74", + "77.104.73.75", + "77.104.73.76", + "77.104.73.77", + "77.104.73.78", + "77.104.73.130", + "77.104.73.131", + "77.104.73.132", + "77.104.73.133", + "77.104.73.135", + "77.104.73.136", + "77.104.73.137", + "77.104.73.138", + "77.104.73.139", + "77.104.75.114", + "77.104.75.227", + "77.104.76.6", + "77.104.76.44", + "77.104.76.50", + "77.104.76.230", + "77.104.77.114", + "77.104.77.197", + "77.104.79.243", + "77.104.79.244", + "77.104.81.67", + "77.104.82.66", + "77.104.82.67", + "77.104.82.68", + "77.104.82.69", + "77.104.82.70", + "77.104.82.110", + "77.104.82.196", + "77.104.84.130", + "77.104.84.136", + "77.104.84.146", + "77.104.84.150", + "77.104.85.14", + "77.104.85.50", + "77.104.85.51", + "77.104.85.74", + "77.104.85.90", + "77.104.92.3", + "77.104.93.85", + "77.104.99.150", + "77.104.100.186", + "77.104.102.162", + "77.104.103.130", + "77.104.103.250", + "77.104.110.6", + "77.104.110.10", + "77.104.110.11", + "77.104.110.13", + "77.104.110.18", + "77.104.110.137", + "77.104.110.166", + "77.104.114.229", + "77.104.114.233", + "77.104.114.237", + "77.104.114.241", + "77.104.114.245", + "77.104.114.249", + "77.104.115.183", + "77.104.115.190", + "77.104.115.217", + "77.104.115.239", + "77.104.126.28", + "77.104.126.43", + "77.104.126.152", + "77.104.126.156", + "77.104.126.181", + "77.104.126.183", + "77.104.126.189", + "77.104.126.211", + "77.104.127.6", + "77.237.66.72", + "77.237.67.19", + "77.237.69.136", + "77.237.69.192", + "77.237.69.193", + "77.237.70.222", + "77.237.76.74", + "77.237.76.223", + "77.237.77.42", + "77.237.77.106", + "77.237.77.109", + "77.237.78.1", + "77.237.78.7", + "77.237.78.8", + "77.237.78.186", + "77.237.82.49", + "77.237.82.56", + "77.237.82.112", + "77.237.82.169", + "77.237.82.221", + "77.237.82.247", + "77.237.83.186", + "77.237.85.193", + "77.237.85.197", + "77.237.85.201", + "77.237.85.205", + "77.237.85.209", + "77.237.85.213", + "77.237.85.217", + "77.237.85.229", + "77.237.85.230", + "77.237.85.233", + "77.237.85.241", + "77.237.85.245", + "77.237.85.249", + "77.237.87.190", + "77.237.88.146", + "77.237.88.178", + "77.237.89.18", + "77.237.89.194", + "77.237.89.196", + "77.237.89.197", + "77.237.89.198", + "77.237.91.146", + "77.237.92.1", + "77.237.92.82", + "77.237.92.90", + "77.237.92.106", + "77.237.92.118", + "77.237.92.220", + "77.237.93.34", + "77.237.93.35", + "77.237.93.36", + "77.237.93.37", + "77.237.94.10", + "77.237.94.202", + "77.237.94.203", + "77.237.95.186", + "77.238.104.0", + "77.238.104.47", + "77.238.104.102", + "77.238.104.107", + "77.238.104.135", + "77.238.104.150", + "77.238.104.208", + "77.238.104.238", + "77.238.105.125", + "77.238.105.147", + "77.238.105.242", + "77.238.106.19", + "77.238.106.41", + "77.238.106.58", + "77.238.106.96", + "77.238.106.103", + "77.238.106.119", + "77.238.106.138", + "77.238.106.198", + "77.238.106.225", + "77.238.107.24", + "77.238.107.32", + "77.238.107.174", + "77.238.107.198", + "77.238.108.92", + "77.238.109.91", + "77.238.110.40", + "77.238.110.44", + "77.238.110.142", + "77.238.110.163", + "77.238.110.177", + "77.238.110.231", + "77.238.111.235", + "77.238.114.64", + "77.238.115.44", + "77.238.117.255", + "77.238.118.166", + "77.238.119.32", + "77.238.119.96", + "77.238.120.23", + "77.238.121.92", + "77.238.121.128", + "77.238.121.138", + "77.238.121.200", + "77.238.121.206", + "77.238.122.146", + "77.238.122.163", + "77.238.122.242", + "77.238.123.238", + "78.31.235.1", + "78.31.235.2", + "78.38.0.73", + "78.38.0.82", + "78.38.0.92", + "78.38.0.93", + "78.38.16.210", + "78.38.17.48", + "78.38.17.67", + "78.38.17.118", + "78.38.17.155", + "78.38.17.159", + "78.38.18.137", + "78.38.19.151", + "78.38.20.2", + "78.38.20.33", + "78.38.20.98", + "78.38.20.99", + "78.38.20.105", + "78.38.24.122", + "78.38.24.199", + "78.38.26.79", + "78.38.26.103", + "78.38.26.107", + "78.38.26.124", + "78.38.26.129", + "78.38.26.130", + "78.38.26.131", + "78.38.26.133", + "78.38.26.136", + "78.38.26.137", + "78.38.26.138", + "78.38.26.140", + "78.38.26.141", + "78.38.26.142", + "78.38.26.145", + "78.38.26.146", + "78.38.26.147", + "78.38.26.148", + "78.38.26.156", + "78.38.26.157", + "78.38.26.158", + "78.38.26.160", + "78.38.26.161", + "78.38.26.162", + "78.38.26.163", + "78.38.26.165", + "78.38.26.167", + "78.38.26.168", + "78.38.26.169", + "78.38.26.170", + "78.38.26.171", + "78.38.26.173", + "78.38.26.174", + "78.38.26.175", + "78.38.26.176", + "78.38.26.177", + "78.38.26.178", + "78.38.26.179", + "78.38.26.180", + "78.38.26.182", + "78.38.26.184", + "78.38.26.185", + "78.38.26.186", + "78.38.26.187", + "78.38.26.227", + "78.38.29.9", + "78.38.29.147", + "78.38.30.35", + "78.38.30.69", + "78.38.30.179", + "78.38.30.200", + "78.38.31.45", + "78.38.31.107", + "78.38.31.117", + "78.38.31.161", + "78.38.32.56", + "78.38.32.159", + "78.38.36.61", + "78.38.38.48", + "78.38.41.17", + "78.38.41.37", + "78.38.46.154", + "78.38.46.200", + "78.38.46.209", + "78.38.46.220", + "78.38.48.107", + "78.38.48.178", + "78.38.48.210", + "78.38.49.109", + "78.38.49.110", + "78.38.49.240", + "78.38.49.255", + "78.38.50.39", + "78.38.50.44", + "78.38.50.61", + "78.38.50.125", + "78.38.50.154", + "78.38.50.207", + "78.38.52.74", + "78.38.52.82", + "78.38.60.218", + "78.38.64.65", + "78.38.64.73", + "78.38.64.129", + "78.38.65.65", + "78.38.65.108", + "78.38.65.109", + "78.38.67.24", + "78.38.67.26", + "78.38.67.93", + "78.38.67.95", + "78.38.69.87", + "78.38.69.89", + "78.38.71.13", + "78.38.71.164", + "78.38.77.47", + "78.38.78.49", + "78.38.80.241", + "78.38.80.242", + "78.38.84.12", + "78.38.90.251", + "78.38.92.106", + "78.38.92.218", + "78.38.93.34", + "78.38.93.58", + "78.38.93.86", + "78.38.93.90", + "78.38.94.74", + "78.38.94.122", + "78.38.94.123", + "78.38.94.130", + "78.38.94.131", + "78.38.94.132", + "78.38.94.133", + "78.38.94.134", + "78.38.95.57", + "78.38.99.46", + "78.38.99.50", + "78.38.99.82", + "78.38.99.118", + "78.38.99.186", + "78.38.99.194", + "78.38.99.195", + "78.38.99.196", + "78.38.99.238", + "78.38.100.54", + "78.38.100.71", + "78.38.100.121", + "78.38.101.66", + "78.38.105.9", + "78.38.105.74", + "78.38.106.232", + "78.38.106.243", + "78.38.107.53", + "78.38.107.72", + "78.38.107.88", + "78.38.107.172", + "78.38.108.194", + "78.38.108.196", + "78.38.108.198", + "78.38.108.199", + "78.38.108.202", + "78.38.108.203", + "78.38.108.204", + "78.38.108.205", + "78.38.109.100", + "78.38.110.110", + "78.38.110.138", + "78.38.110.171", + "78.38.110.174", + "78.38.112.130", + "78.38.114.86", + "78.38.114.210", + "78.38.114.212", + "78.38.114.213", + "78.38.114.221", + "78.38.120.80", + "78.38.120.120", + "78.38.121.218", + "78.38.123.210", + "78.38.124.28", + "78.38.124.35", + "78.38.124.163", + "78.38.126.2", + "78.38.126.101", + "78.38.126.141", + "78.38.126.247", + "78.38.127.149", + "78.38.127.238", + "78.38.140.50", + "78.38.144.243", + "78.38.145.228", + "78.38.149.32", + "78.38.152.102", + "78.38.153.37", + "78.38.153.81", + "78.38.153.177", + "78.38.153.181", + "78.38.156.22", + "78.38.156.129", + "78.38.156.205", + "78.38.156.229", + "78.38.156.246", + "78.38.157.65", + "78.38.157.66", + "78.38.157.112", + "78.38.157.124", + "78.38.157.167", + "78.38.158.22", + "78.38.161.80", + "78.38.166.121", + "78.38.166.122", + "78.38.166.123", + "78.38.166.145", + "78.38.166.249", + "78.38.174.78", + "78.38.179.236", + "78.38.182.61", + "78.38.189.221", + "78.38.209.57", + "78.38.221.170", + "78.38.221.172", + "78.38.221.173", + "78.38.221.174", + "78.38.224.98", + "78.38.239.220", + "78.38.239.221", + "78.38.239.222", + "78.38.239.223", + "78.38.239.224", + "78.38.243.6", + "78.38.243.234", + "78.38.243.237", + "78.38.246.174", + "78.38.246.183", + "78.38.248.46", + "78.38.248.117", + "78.38.248.154", + "78.38.248.242", + "78.38.248.250", + "78.38.250.6", + "78.38.250.82", + "78.38.250.84", + "78.38.250.85", + "78.38.250.86", + "78.38.250.150", + "78.38.251.250", + "78.38.251.251", + "78.38.251.252", + "78.38.251.253", + "78.38.254.14", + "78.38.254.66", + "78.39.10.54", + "78.39.10.234", + "78.39.11.2", + "78.39.11.14", + "78.39.11.62", + "78.39.11.122", + "78.39.37.170", + "78.39.40.114", + "78.39.40.115", + "78.39.40.142", + "78.39.40.147", + "78.39.40.155", + "78.39.43.242", + "78.39.46.121", + "78.39.46.122", + "78.39.46.123", + "78.39.46.124", + "78.39.46.125", + "78.39.46.126", + "78.39.46.127", + "78.39.48.28", + "78.39.48.140", + "78.39.48.150", + "78.39.48.202", + "78.39.49.90", + "78.39.50.6", + "78.39.50.42", + "78.39.50.76", + "78.39.50.134", + "78.39.50.170", + "78.39.50.234", + "78.39.53.120", + "78.39.53.162", + "78.39.53.194", + "78.39.53.254", + "78.39.56.183", + "78.39.57.178", + "78.39.57.179", + "78.39.59.14", + "78.39.59.38", + "78.39.59.142", + "78.39.62.37", + "78.39.62.147", + "78.39.62.148", + "78.39.67.138", + "78.39.80.59", + "78.39.80.169", + "78.39.81.17", + "78.39.81.26", + "78.39.81.57", + "78.39.81.166", + "78.39.82.26", + "78.39.83.13", + "78.39.83.20", + "78.39.83.27", + "78.39.83.30", + "78.39.83.55", + "78.39.83.103", + "78.39.83.111", + "78.39.83.118", + "78.39.83.128", + "78.39.88.175", + "78.39.99.17", + "78.39.99.25", + "78.39.99.98", + "78.39.102.99", + "78.39.102.103", + "78.39.102.108", + "78.39.103.3", + "78.39.103.4", + "78.39.103.9", + "78.39.103.10", + "78.39.103.15", + "78.39.103.16", + "78.39.103.24", + "78.39.108.18", + "78.39.108.24", + "78.39.108.121", + "78.39.108.196", + "78.39.109.10", + "78.39.109.54", + "78.39.117.48", + "78.39.124.225", + "78.39.125.69", + "78.39.125.70", + "78.39.125.71", + "78.39.125.72", + "78.39.125.73", + "78.39.125.74", + "78.39.125.75", + "78.39.125.77", + "78.39.125.78", + "78.39.125.80", + "78.39.125.81", + "78.39.136.50", + "78.39.138.94", + "78.39.138.115", + "78.39.140.23", + "78.39.141.84", + "78.39.141.204", + "78.39.144.157", + "78.39.145.101", + "78.39.145.216", + "78.39.147.202", + "78.39.150.26", + "78.39.159.153", + "78.39.175.161", + "78.39.176.89", + "78.39.180.157", + "78.39.180.174", + "78.39.181.87", + "78.39.181.97", + "78.39.192.15", + "78.39.193.34", + "78.39.193.74", + "78.39.193.75", + "78.39.193.78", + "78.39.194.9", + "78.39.195.234", + "78.39.195.238", + "78.39.198.4", + "78.39.204.9", + "78.39.204.17", + "78.39.204.33", + "78.39.209.115", + "78.39.209.202", + "78.39.217.45", + "78.39.217.53", + "78.39.217.161", + "78.39.217.236", + "78.39.218.2", + "78.39.218.3", + "78.39.218.4", + "78.39.218.6", + "78.39.218.7", + "78.39.218.8", + "78.39.218.10", + "78.39.218.12", + "78.39.220.114", + "78.39.220.122", + "78.39.220.123", + "78.39.220.124", + "78.39.220.125", + "78.39.220.126", + "78.39.220.163", + "78.39.225.143", + "78.39.226.16", + "78.39.226.113", + "78.39.227.26", + "78.39.228.234", + "78.39.229.24", + "78.39.230.239", + "78.39.233.108", + "78.39.233.143", + "78.39.233.156", + "78.39.233.159", + "78.39.234.54", + "78.39.234.221", + "78.39.234.231", + "78.39.235.108", + "78.39.235.148", + "78.39.244.140", + "78.39.245.159", + "78.39.249.51", + "78.39.249.136", + "78.39.250.143", + "78.39.251.53", + "78.39.251.221", + "78.39.252.62", + "78.39.253.225", + "78.39.255.246", + "78.109.194.122", + "78.109.195.58", + "78.109.196.139", + "78.109.198.2", + "78.109.199.57", + "78.109.201.52", + "78.109.201.55", + "78.110.120.195", + "78.110.120.200", + "78.110.120.227", + "78.110.122.96", + "78.110.123.67", + "78.110.124.19", + "78.110.124.90", + "78.110.124.162", + "78.110.124.203", + "78.110.124.206", + "78.110.125.58", + "78.110.125.83", + "78.110.125.85", + "78.110.125.250", + "78.110.126.170", + "78.110.127.91", + "78.111.0.30", + "78.111.0.68", + "78.111.1.22", + "78.111.3.17", + "78.111.3.94", + "78.111.3.177", + "78.111.5.2", + "78.111.5.246", + "78.111.7.1", + "78.111.7.2", + "78.111.7.3", + "78.111.7.4", + "78.111.7.5", + "78.111.7.6", + "78.111.7.7", + "78.111.7.8", + "78.111.7.9", + "78.111.7.10", + "78.111.7.12", + "78.111.7.14", + "78.111.7.15", + "78.111.7.16", + "78.111.7.18", + "78.111.7.19", + "78.111.7.20", + "78.111.7.22", + "78.111.7.23", + "78.111.7.24", + "78.111.7.25", + "78.111.7.26", + "78.111.7.27", + "78.111.7.28", + "78.111.7.29", + "78.111.7.30", + "78.111.7.31", + "78.111.7.32", + "78.111.7.33", + "78.111.7.34", + "78.111.7.35", + "78.111.7.36", + "78.111.7.37", + "78.111.7.38", + "78.111.7.39", + "78.111.7.40", + "78.111.7.42", + "78.111.7.43", + "78.111.7.44", + "78.111.7.45", + "78.111.7.46", + "78.111.7.47", + "78.111.7.48", + "78.111.7.49", + "78.111.7.50", + "78.111.7.52", + "78.111.7.53", + "78.111.7.54", + "78.111.7.55", + "78.111.7.56", + "78.111.7.57", + "78.111.7.58", + "78.111.7.59", + "78.111.7.60", + "78.111.7.61", + "78.111.7.62", + "78.111.7.63", + "78.111.7.64", + "78.111.7.66", + "78.111.7.67", + "78.111.7.68", + "78.111.7.69", + "78.111.7.70", + "78.111.7.71", + "78.111.7.72", + "78.111.7.74", + "78.111.7.75", + "78.111.7.76", + "78.111.7.77", + "78.111.7.78", + "78.111.7.79", + "78.111.7.80", + "78.111.7.81", + "78.111.7.82", + "78.111.7.84", + "78.111.7.85", + "78.111.7.86", + "78.111.7.87", + "78.111.7.88", + "78.111.7.89", + "78.111.7.90", + "78.111.7.91", + "78.111.7.92", + "78.111.7.93", + "78.111.7.94", + "78.111.7.95", + "78.111.7.96", + "78.111.7.97", + "78.111.7.98", + "78.111.7.99", + "78.111.7.100", + "78.111.7.101", + "78.111.7.102", + "78.111.7.103", + "78.111.7.104", + "78.111.7.105", + "78.111.7.106", + "78.111.7.108", + "78.111.7.109", + "78.111.7.110", + "78.111.7.112", + "78.111.7.113", + "78.111.7.114", + "78.111.7.115", + "78.111.7.116", + "78.111.7.117", + "78.111.7.118", + "78.111.7.119", + "78.111.7.121", + "78.111.7.122", + "78.111.7.123", + "78.111.7.124", + "78.111.7.125", + "78.111.7.126", + "78.111.7.127", + "78.111.7.128", + "78.111.7.129", + "78.111.7.130", + "78.111.7.131", + "78.111.7.132", + "78.111.7.133", + "78.111.7.134", + "78.111.7.135", + "78.111.7.136", + "78.111.7.138", + "78.111.7.139", + "78.111.7.140", + "78.111.7.141", + "78.111.7.142", + "78.111.7.143", + "78.111.7.144", + "78.111.7.145", + "78.111.7.146", + "78.111.7.147", + "78.111.7.149", + "78.111.7.150", + "78.111.7.151", + "78.111.7.152", + "78.111.7.153", + "78.111.7.154", + "78.111.7.156", + "78.111.7.157", + "78.111.7.158", + "78.111.7.159", + "78.111.7.161", + "78.111.7.162", + "78.111.7.163", + "78.111.7.164", + "78.111.7.165", + "78.111.7.166", + "78.111.7.167", + "78.111.7.168", + "78.111.7.169", + "78.111.7.170", + "78.111.7.171", + "78.111.7.173", + "78.111.7.174", + "78.111.7.175", + "78.111.7.176", + "78.111.7.177", + "78.111.7.178", + "78.111.7.179", + "78.111.7.180", + "78.111.7.182", + "78.111.7.183", + "78.111.7.185", + "78.111.7.186", + "78.111.7.187", + "78.111.7.188", + "78.111.7.189", + "78.111.7.190", + "78.111.7.191", + "78.111.7.192", + "78.111.7.194", + "78.111.7.195", + "78.111.7.196", + "78.111.7.197", + "78.111.7.198", + "78.111.7.200", + "78.111.7.201", + "78.111.7.202", + "78.111.7.203", + "78.111.7.204", + "78.111.7.205", + "78.111.7.206", + "78.111.7.207", + "78.111.7.208", + "78.111.7.209", + "78.111.7.210", + "78.111.7.211", + "78.111.7.212", + "78.111.7.213", + "78.111.7.214", + "78.111.7.215", + "78.111.7.216", + "78.111.7.217", + "78.111.7.218", + "78.111.7.219", + "78.111.7.220", + "78.111.7.221", + "78.111.7.222", + "78.111.7.223", + "78.111.7.224", + "78.111.7.225", + "78.111.7.228", + "78.111.7.229", + "78.111.7.230", + "78.111.7.231", + "78.111.7.232", + "78.111.7.234", + "78.111.7.235", + "78.111.7.236", + "78.111.7.237", + "78.111.7.238", + "78.111.7.239", + "78.111.7.240", + "78.111.7.241", + "78.111.7.243", + "78.111.7.244", + "78.111.7.245", + "78.111.7.246", + "78.111.7.247", + "78.111.7.248", + "78.111.7.249", + "78.111.7.251", + "78.111.7.252", + "78.111.7.253", + "78.111.7.254", + "78.157.34.172", + "78.157.34.173", + "78.157.35.53", + "78.157.35.71", + "78.157.35.225", + "78.157.36.94", + "78.157.36.116", + "78.157.36.117", + "78.157.36.118", + "78.157.36.119", + "78.157.36.120", + "78.157.36.121", + "78.157.36.122", + "78.157.36.123", + "78.157.36.124", + "78.157.36.125", + "78.157.36.126", + "78.157.38.110", + "78.157.38.115", + "78.157.38.210", + "78.157.39.71", + "78.157.39.167", + "78.157.39.180", + "78.157.40.67", + "78.157.40.75", + "78.157.40.144", + "78.157.41.50", + "78.157.41.242", + "78.157.41.243", + "78.157.42.199", + "78.157.42.207", + "78.157.44.181", + "78.157.44.221", + "78.157.45.197", + "78.157.48.187", + "78.157.48.189", + "78.157.48.190", + "78.157.48.230", + "78.157.49.38", + "78.157.49.44", + "78.157.50.35", + "78.157.50.36", + "78.157.50.206", + "78.157.50.208", + "78.157.50.218", + "78.157.50.226", + "78.157.51.212", + "78.157.51.249", + "78.157.51.253", + "78.157.53.183", + "78.157.56.22", + "78.157.56.98", + "78.157.56.99", + "78.157.56.101", + "78.157.57.62", + "78.157.57.69", + "78.157.57.71", + "78.157.57.85", + "78.157.57.204", + "78.157.57.222", + "78.157.57.251", + "78.157.60.1", + "78.157.60.2", + "78.157.60.3", + "78.157.60.215", + "78.157.61.24", + "78.157.61.109", + "78.157.62.33", + "78.157.62.148", + "78.157.63.106", + "78.158.165.138", + "78.158.165.139", + "78.158.166.131", + "78.158.166.132", + "78.158.168.143", + "78.158.168.189", + "78.158.168.190", + "78.158.179.8", + "78.158.179.10", + "78.158.179.43", + "78.158.179.87", + "78.158.179.112", + "78.158.179.120", + "78.158.179.124", + "78.158.179.146", + "78.158.179.155", + "78.158.179.203", + "78.158.182.126", + "78.158.191.77", + "78.158.191.115", + "78.158.191.130", + "78.158.191.156", + "78.158.191.158", + "78.158.191.224", + "78.158.191.225", + "78.158.191.231", + "78.158.191.236", + "78.158.191.237", + "78.158.191.241", + "78.158.191.252", + "79.127.0.18", + "79.127.0.63", + "79.127.0.85", + "79.127.0.140", + "79.127.0.184", + "79.127.0.200", + "79.127.0.224", + "79.127.1.32", + "79.127.1.77", + "79.127.1.85", + "79.127.1.104", + "79.127.1.187", + "79.127.1.205", + "79.127.1.211", + "79.127.1.248", + "79.127.2.25", + "79.127.2.120", + "79.127.2.128", + "79.127.2.141", + "79.127.2.142", + "79.127.2.149", + "79.127.2.164", + "79.127.2.175", + "79.127.2.236", + "79.127.3.10", + "79.127.3.28", + "79.127.3.42", + "79.127.3.50", + "79.127.3.53", + "79.127.3.164", + "79.127.3.173", + "79.127.3.182", + "79.127.3.196", + "79.127.3.208", + "79.127.3.210", + "79.127.4.49", + "79.127.4.59", + "79.127.4.81", + "79.127.4.134", + "79.127.4.135", + "79.127.4.197", + "79.127.4.200", + "79.127.4.245", + "79.127.5.11", + "79.127.5.13", + "79.127.5.17", + "79.127.5.53", + "79.127.5.79", + "79.127.5.93", + "79.127.5.94", + "79.127.5.109", + "79.127.5.127", + "79.127.5.161", + "79.127.5.164", + "79.127.6.2", + "79.127.6.65", + "79.127.6.91", + "79.127.6.105", + "79.127.6.141", + "79.127.6.142", + "79.127.6.191", + "79.127.6.218", + "79.127.6.227", + "79.127.7.18", + "79.127.7.34", + "79.127.7.50", + "79.127.7.59", + "79.127.7.83", + "79.127.7.114", + "79.127.7.127", + "79.127.7.226", + "79.127.11.80", + "79.127.11.106", + "79.127.11.154", + "79.127.11.226", + "79.127.12.1", + "79.127.12.112", + "79.127.12.147", + "79.127.12.148", + "79.127.12.162", + "79.127.13.0", + "79.127.13.30", + "79.127.13.38", + "79.127.13.134", + "79.127.13.167", + "79.127.13.171", + "79.127.13.200", + "79.127.13.236", + "79.127.14.49", + "79.127.14.70", + "79.127.14.81", + "79.127.14.108", + "79.127.14.116", + "79.127.14.140", + "79.127.14.145", + "79.127.15.17", + "79.127.15.28", + "79.127.15.41", + "79.127.15.91", + "79.127.15.117", + "79.127.15.135", + "79.127.15.148", + "79.127.15.156", + "79.127.15.159", + "79.127.15.165", + "79.127.15.206", + "79.127.15.223", + "79.127.15.239", + "79.127.16.37", + "79.127.24.251", + "79.127.29.99", + "79.127.29.164", + "79.127.30.178", + "79.127.30.179", + "79.127.30.180", + "79.127.30.186", + "79.127.31.209", + "79.127.32.2", + "79.127.32.18", + "79.127.32.26", + "79.127.32.43", + "79.127.32.122", + "79.127.32.123", + "79.127.32.125", + "79.127.32.126", + "79.127.32.250", + "79.127.33.10", + "79.127.33.81", + "79.127.33.106", + "79.127.33.186", + "79.127.33.188", + "79.127.33.198", + "79.127.34.58", + "79.127.34.62", + "79.127.34.130", + "79.127.34.226", + "79.127.34.229", + "79.127.35.50", + "79.127.35.242", + "79.127.35.243", + "79.127.36.98", + "79.127.37.4", + "79.127.37.5", + "79.127.37.6", + "79.127.37.90", + "79.127.37.226", + "79.127.37.234", + "79.127.37.235", + "79.127.40.66", + "79.127.40.135", + "79.127.40.202", + "79.127.41.42", + "79.127.42.226", + "79.127.45.49", + "79.127.47.18", + "79.127.47.19", + "79.127.47.26", + "79.127.47.27", + "79.127.47.28", + "79.127.47.29", + "79.127.47.30", + "79.127.47.68", + "79.127.47.82", + "79.127.47.83", + "79.127.47.146", + "79.127.47.186", + "79.127.47.251", + "79.127.49.27", + "79.127.49.130", + "79.127.49.178", + "79.127.49.179", + "79.127.50.178", + "79.127.50.180", + "79.127.50.250", + "79.127.51.90", + "79.127.51.170", + "79.127.51.226", + "79.127.52.162", + "79.127.52.163", + "79.127.52.164", + "79.127.52.165", + "79.127.52.178", + "79.127.52.194", + "79.127.52.234", + "79.127.53.26", + "79.127.53.122", + "79.127.53.131", + "79.127.53.170", + "79.127.54.163", + "79.127.54.186", + "79.127.54.205", + "79.127.54.242", + "79.127.54.250", + "79.127.55.178", + "79.127.55.234", + "79.127.56.58", + "79.127.56.61", + "79.127.56.139", + "79.127.56.140", + "79.127.56.178", + "79.127.59.101", + "79.127.60.18", + "79.127.63.90", + "79.127.63.210", + "79.127.65.34", + "79.127.65.155", + "79.127.66.90", + "79.127.66.101", + "79.127.66.194", + "79.127.66.195", + "79.127.66.250", + "79.127.68.34", + "79.127.68.35", + "79.127.68.37", + "79.127.68.38", + "79.127.69.116", + "79.127.69.117", + "79.127.69.118", + "79.127.69.122", + "79.127.69.123", + "79.127.69.124", + "79.127.69.126", + "79.127.69.163", + "79.127.70.130", + "79.127.70.131", + "79.127.70.132", + "79.127.70.133", + "79.127.70.134", + "79.127.70.135", + "79.127.70.136", + "79.127.70.137", + "79.127.70.139", + "79.127.70.141", + "79.127.70.142", + "79.127.70.146", + "79.127.70.162", + "79.127.71.10", + "79.127.71.11", + "79.127.71.12", + "79.127.71.14", + "79.127.71.34", + "79.127.71.114", + "79.127.71.219", + "79.127.71.243", + "79.127.72.26", + "79.127.72.187", + "79.127.72.189", + "79.127.72.191", + "79.127.72.218", + "79.127.73.201", + "79.127.74.34", + "79.127.74.66", + "79.127.74.114", + "79.127.74.115", + "79.127.74.116", + "79.127.74.117", + "79.127.74.234", + "79.127.74.243", + "79.127.75.10", + "79.127.76.18", + "79.127.76.122", + "79.127.76.138", + "79.127.76.147", + "79.127.76.186", + "79.127.76.210", + "79.127.76.211", + "79.127.76.214", + "79.127.76.226", + "79.127.77.106", + "79.127.77.142", + "79.127.77.161", + "79.127.78.26", + "79.127.78.66", + "79.127.79.10", + "79.127.79.162", + "79.127.79.165", + "79.127.80.114", + "79.127.80.115", + "79.127.81.2", + "79.127.81.10", + "79.127.82.26", + "79.127.83.26", + "79.127.83.94", + "79.127.83.114", + "79.127.83.202", + "79.127.84.122", + "79.127.84.124", + "79.127.84.125", + "79.127.84.126", + "79.127.85.170", + "79.127.85.171", + "79.127.85.178", + "79.127.86.11", + "79.127.86.252", + "79.127.87.179", + "79.127.87.226", + "79.127.92.1", + "79.127.92.8", + "79.127.92.9", + "79.127.92.16", + "79.127.92.17", + "79.127.92.20", + "79.127.92.79", + "79.127.92.83", + "79.127.92.106", + "79.127.92.166", + "79.127.92.180", + "79.127.92.186", + "79.127.92.215", + "79.127.92.217", + "79.127.92.221", + "79.127.92.238", + "79.127.94.21", + "79.127.94.22", + "79.127.94.30", + "79.127.94.36", + "79.127.94.55", + "79.127.94.57", + "79.127.94.59", + "79.127.94.83", + "79.127.94.91", + "79.127.94.108", + "79.127.94.116", + "79.127.94.135", + "79.127.94.141", + "79.127.94.142", + "79.127.94.157", + "79.127.94.163", + "79.127.94.170", + "79.127.94.184", + "79.127.94.187", + "79.127.94.213", + "79.127.94.228", + "79.127.94.255", + "79.127.95.6", + "79.127.95.14", + "79.127.95.63", + "79.127.95.74", + "79.127.95.96", + "79.127.95.102", + "79.127.95.107", + "79.127.95.110", + "79.127.95.117", + "79.127.95.119", + "79.127.95.130", + "79.127.95.133", + "79.127.95.139", + "79.127.95.143", + "79.127.95.164", + "79.127.95.184", + "79.127.95.193", + "79.127.95.195", + "79.127.95.205", + "79.127.95.230", + "79.127.95.234", + "79.127.95.236", + "79.127.95.241", + "79.127.95.248", + "79.127.96.6", + "79.127.96.40", + "79.127.101.21", + "79.127.101.194", + "79.127.102.143", + "79.127.103.225", + "79.127.107.233", + "79.127.108.80", + "79.127.110.91", + "79.127.113.91", + "79.127.116.1", + "79.127.116.9", + "79.127.116.17", + "79.127.116.56", + "79.127.116.186", + "79.127.117.60", + "79.127.117.66", + "79.127.117.199", + "79.127.119.146", + "79.127.120.51", + "79.127.120.82", + "79.127.120.218", + "79.127.120.222", + "79.127.121.10", + "79.127.121.184", + "79.127.121.185", + "79.127.121.187", + "79.127.121.188", + "79.127.121.189", + "79.127.121.226", + "79.127.122.10", + "79.127.122.98", + "79.127.123.34", + "79.127.124.106", + "79.127.125.74", + "79.127.125.75", + "79.127.125.76", + "79.127.125.77", + "79.127.125.78", + "79.127.125.212", + "79.127.126.67", + "79.127.126.93", + "79.127.126.114", + "79.127.126.243", + "79.127.127.71", + "79.127.127.73", + "79.127.127.122", + "79.127.127.214", + "79.127.127.216", + "79.132.193.34", + "79.132.193.58", + "79.132.193.73", + "79.132.193.196", + "79.132.193.198", + "79.132.193.212", + "79.132.193.235", + "79.132.205.29", + "79.132.205.33", + "79.132.206.198", + "79.132.206.209", + "79.132.206.217", + "79.132.208.49", + "79.132.208.50", + "79.132.208.53", + "79.132.214.254", + "79.143.84.70", + "79.143.86.3", + "79.143.86.11", + "79.143.86.12", + "79.143.86.40", + "79.143.86.50", + "79.143.86.66", + "79.143.86.129", + "79.143.86.158", + "79.143.86.161", + "79.143.86.193", + "79.143.86.201", + "79.143.86.209", + "79.174.160.210", + "79.174.160.222", + "79.175.131.190", + "79.175.131.196", + "79.175.131.253", + "79.175.133.54", + "79.175.134.37", + "79.175.139.185", + "79.175.149.66", + "79.175.149.68", + "79.175.149.69", + "79.175.149.70", + "79.175.149.217", + "79.175.151.43", + "79.175.151.142", + "79.175.152.194", + "79.175.152.195", + "79.175.153.210", + "79.175.153.211", + "79.175.153.212", + "79.175.153.213", + "79.175.153.214", + "79.175.153.215", + "79.175.153.217", + "79.175.153.218", + "79.175.153.219", + "79.175.153.221", + "79.175.153.222", + "79.175.162.226", + "79.175.163.215", + "79.175.165.171", + "79.175.167.96", + "79.175.168.78", + "79.175.168.220", + "79.175.168.222", + "79.175.169.50", + "79.175.169.61", + "79.175.169.213", + "79.175.169.221", + "79.175.170.133", + "79.175.173.154", + "79.175.173.155", + "79.175.173.156", + "79.175.173.157", + "79.175.173.158", + "79.175.176.2", + "79.175.176.90", + "79.175.177.8", + "79.175.177.10", + "79.175.177.125", + "79.175.187.189", + "79.175.189.3", + "79.175.189.4", + "79.175.189.231", + "80.66.177.23", + "80.71.112.51", + "80.71.112.98", + "80.71.112.115", + "80.71.114.16", + "80.71.114.19", + "80.71.114.23", + "80.71.114.38", + "80.71.114.148", + "80.71.114.153", + "80.71.114.164", + "80.71.114.175", + "80.71.114.197", + "80.71.114.203", + "80.71.118.2", + "80.71.149.32", + "80.71.149.62", + "80.75.0.201", + "80.75.1.66", + "80.75.1.230", + "80.75.4.24", + "80.75.4.50", + "80.75.4.66", + "80.75.4.67", + "80.75.4.68", + "80.75.4.69", + "80.75.4.76", + "80.75.4.77", + "80.75.7.22", + "80.75.7.58", + "80.75.7.66", + "80.75.7.94", + "80.75.7.170", + "80.75.7.171", + "80.75.7.172", + "80.75.7.173", + "80.75.7.174", + "80.75.8.194", + "80.75.9.182", + "80.75.9.197", + "80.75.9.222", + "80.75.11.58", + "80.75.12.100", + "80.75.13.30", + "80.75.13.34", + "80.75.13.107", + "80.75.13.178", + "80.75.14.34", + "80.75.14.110", + "80.75.14.111", + "80.75.14.130", + "80.75.14.219", + "80.75.14.250", + "80.75.213.163", + "80.75.213.247", + "80.91.208.26", + "80.91.208.27", + "80.91.208.28", + "80.91.208.29", + "80.91.208.30", + "80.91.208.75", + "80.91.208.76", + "80.191.2.7", + "80.191.2.224", + "80.191.11.45", + "80.191.11.217", + "80.191.16.15", + "80.191.16.68", + "80.191.16.126", + "80.191.16.194", + "80.191.17.100", + "80.191.18.190", + "80.191.18.211", + "80.191.23.134", + "80.191.27.33", + "80.191.27.34", + "80.191.40.130", + "80.191.40.131", + "80.191.40.132", + "80.191.40.133", + "80.191.40.134", + "80.191.40.135", + "80.191.40.138", + "80.191.40.139", + "80.191.40.140", + "80.191.40.141", + "80.191.40.142", + "80.191.40.143", + "80.191.40.144", + "80.191.40.145", + "80.191.40.146", + "80.191.40.147", + "80.191.40.148", + "80.191.40.149", + "80.191.40.150", + "80.191.40.151", + "80.191.40.153", + "80.191.40.154", + "80.191.40.155", + "80.191.40.156", + "80.191.40.157", + "80.191.40.158", + "80.191.42.10", + "80.191.44.80", + "80.191.44.91", + "80.191.46.150", + "80.191.50.28", + "80.191.50.253", + "80.191.51.53", + "80.191.52.37", + "80.191.52.216", + "80.191.52.254", + "80.191.60.35", + "80.191.60.36", + "80.191.75.159", + "80.191.77.139", + "80.191.84.18", + "80.191.85.46", + "80.191.89.66", + "80.191.91.57", + "80.191.91.84", + "80.191.91.109", + "80.191.92.74", + "80.191.100.9", + "80.191.100.67", + "80.191.100.106", + "80.191.100.124", + "80.191.100.182", + "80.191.100.199", + "80.191.102.39", + "80.191.102.46", + "80.191.102.93", + "80.191.102.154", + "80.191.102.167", + "80.191.102.225", + "80.191.102.241", + "80.191.106.141", + "80.191.106.144", + "80.191.106.237", + "80.191.107.16", + "80.191.107.90", + "80.191.108.51", + "80.191.108.70", + "80.191.108.107", + "80.191.108.164", + "80.191.108.192", + "80.191.108.228", + "80.191.110.90", + "80.191.110.161", + "80.191.110.178", + "80.191.115.120", + "80.191.115.122", + "80.191.116.30", + "80.191.128.1", + "80.191.131.64", + "80.191.143.18", + "80.191.144.1", + "80.191.152.66", + "80.191.153.1", + "80.191.153.4", + "80.191.153.107", + "80.191.155.242", + "80.191.160.149", + "80.191.164.98", + "80.191.164.105", + "80.191.168.212", + "80.191.169.162", + "80.191.171.224", + "80.191.172.9", + "80.191.172.23", + "80.191.172.33", + "80.191.172.35", + "80.191.172.50", + "80.191.172.81", + "80.191.185.180", + "80.191.186.118", + "80.191.189.125", + "80.191.190.179", + "80.191.191.11", + "80.191.191.12", + "80.191.191.14", + "80.191.191.18", + "80.191.192.209", + "80.191.197.194", + "80.191.198.122", + "80.191.198.226", + "80.191.200.3", + "80.191.200.5", + "80.191.206.50", + "80.191.206.66", + "80.191.206.98", + "80.191.206.135", + "80.191.207.2", + "80.191.207.3", + "80.191.207.4", + "80.191.207.5", + "80.191.207.7", + "80.191.207.9", + "80.191.207.10", + "80.191.207.11", + "80.191.207.21", + "80.191.207.33", + "80.191.207.34", + "80.191.207.35", + "80.191.207.37", + "80.191.207.38", + "80.191.207.40", + "80.191.209.94", + "80.191.209.105", + "80.191.212.141", + "80.191.221.26", + "80.191.221.27", + "80.191.221.33", + "80.191.227.147", + "80.191.227.148", + "80.191.235.90", + "80.191.235.91", + "80.191.235.92", + "80.191.235.93", + "80.191.235.94", + "80.191.235.145", + "80.191.235.194", + "80.191.236.19", + "80.191.238.82", + "80.191.238.83", + "80.191.240.66", + "80.191.240.128", + "80.191.240.138", + "80.191.240.185", + "80.191.240.191", + "80.191.241.194", + "80.191.241.195", + "80.191.241.196", + "80.191.241.197", + "80.191.241.198", + "80.191.241.199", + "80.191.241.200", + "80.191.241.201", + "80.191.241.202", + "80.191.241.203", + "80.191.241.205", + "80.191.241.208", + "80.191.241.209", + "80.191.241.210", + "80.191.241.212", + "80.191.241.214", + "80.191.241.217", + "80.191.241.218", + "80.191.241.220", + "80.191.241.221", + "80.191.242.2", + "80.191.242.110", + "80.191.243.226", + "80.191.243.234", + "80.191.245.235", + "80.191.252.152", + "80.191.253.205", + "80.191.255.18", + "80.191.255.22", + "80.210.17.128", + "80.210.17.137", + "80.210.17.163", + "80.210.17.197", + "80.210.17.229", + "80.210.17.240", + "80.210.17.249", + "80.210.18.45", + "80.210.18.93", + "80.210.18.121", + "80.210.18.147", + "80.210.18.219", + "80.210.18.220", + "80.210.18.235", + "80.210.18.243", + "80.210.19.31", + "80.210.19.69", + "80.210.19.124", + "80.210.19.172", + "80.210.19.179", + "80.210.19.186", + "80.210.19.215", + "80.210.19.220", + "80.210.20.31", + "80.210.20.88", + "80.210.21.16", + "80.210.21.70", + "80.210.21.84", + "80.210.21.91", + "80.210.21.179", + "80.210.21.211", + "80.210.21.224", + "80.210.22.14", + "80.210.22.47", + "80.210.22.77", + "80.210.22.79", + "80.210.22.113", + "80.210.22.140", + "80.210.22.142", + "80.210.22.153", + "80.210.22.217", + "80.210.22.229", + "80.210.22.243", + "80.210.24.7", + "80.210.24.89", + "80.210.24.110", + "80.210.24.158", + "80.210.24.185", + "80.210.24.246", + "80.210.25.33", + "80.210.25.237", + "80.210.26.72", + "80.210.26.90", + "80.210.26.101", + "80.210.26.104", + "80.210.26.110", + "80.210.26.120", + "80.210.26.126", + "80.210.26.179", + "80.210.26.219", + "80.210.26.228", + "80.210.26.240", + "80.210.26.251", + "80.210.27.111", + "80.210.27.140", + "80.210.27.148", + "80.210.27.153", + "80.210.27.187", + "80.210.27.213", + "80.210.27.221", + "80.210.28.38", + "80.210.28.52", + "80.210.28.88", + "80.210.28.161", + "80.210.29.27", + "80.210.29.50", + "80.210.29.57", + "80.210.29.105", + "80.210.29.132", + "80.210.29.155", + "80.210.29.168", + "80.210.29.175", + "80.210.29.180", + "80.210.29.184", + "80.210.29.224", + "80.210.30.2", + "80.210.30.57", + "80.210.30.129", + "80.210.30.152", + "80.210.30.155", + "80.210.30.170", + "80.210.30.202", + "80.210.30.251", + "80.210.31.23", + "80.210.31.60", + "80.210.31.74", + "80.210.31.112", + "80.210.31.133", + "80.210.31.151", + "80.210.31.184", + "80.210.31.218", + "80.210.31.223", + "80.210.31.229", + "80.210.32.40", + "80.210.32.63", + "80.210.32.103", + "80.210.32.104", + "80.210.32.116", + "80.210.32.131", + "80.210.32.143", + "80.210.32.146", + "80.210.32.176", + "80.210.32.201", + "80.210.32.235", + "80.210.33.52", + "80.210.33.166", + "80.210.33.227", + "80.210.33.238", + "80.210.34.40", + "80.210.34.71", + "80.210.34.89", + "80.210.34.137", + "80.210.34.150", + "80.210.34.172", + "80.210.34.230", + "80.210.35.144", + "80.210.35.159", + "80.210.35.171", + "80.210.35.175", + "80.210.36.17", + "80.210.36.37", + "80.210.36.68", + "80.210.36.111", + "80.210.36.121", + "80.210.36.169", + "80.210.36.196", + "80.210.36.201", + "80.210.36.203", + "80.210.37.14", + "80.210.37.100", + "80.210.37.115", + "80.210.37.209", + "80.210.38.53", + "80.210.38.67", + "80.210.38.101", + "80.210.38.127", + "80.210.38.130", + "80.210.38.178", + "80.210.38.187", + "80.210.38.205", + "80.210.38.222", + "80.210.38.251", + "80.210.39.57", + "80.210.39.81", + "80.210.39.98", + "80.210.39.153", + "80.210.39.167", + "80.210.39.205", + "80.210.39.221", + "80.210.39.230", + "80.210.40.34", + "80.210.40.72", + "80.210.40.98", + "80.210.40.109", + "80.210.40.147", + "80.210.40.192", + "80.210.40.195", + "80.210.40.213", + "80.210.40.233", + "80.210.41.13", + "80.210.41.22", + "80.210.41.34", + "80.210.41.113", + "80.210.41.116", + "80.210.41.236", + "80.210.42.3", + "80.210.42.11", + "80.210.42.18", + "80.210.42.49", + "80.210.42.69", + "80.210.42.149", + "80.210.42.161", + "80.210.42.169", + "80.210.43.101", + "80.210.43.108", + "80.210.43.131", + "80.210.43.141", + "80.210.43.169", + "80.210.43.184", + "80.210.43.217", + "80.210.44.21", + "80.210.44.59", + "80.210.44.71", + "80.210.44.105", + "80.210.44.147", + "80.210.44.184", + "80.210.44.187", + "80.210.44.245", + "80.210.45.59", + "80.210.45.107", + "80.210.45.234", + "80.210.46.105", + "80.210.46.171", + "80.210.46.202", + "80.210.46.246", + "80.210.47.68", + "80.210.47.93", + "80.210.47.94", + "80.210.47.149", + "80.210.48.24", + "80.210.48.26", + "80.210.48.48", + "80.210.48.57", + "80.210.48.84", + "80.210.48.98", + "80.210.48.147", + "80.210.48.177", + "80.210.49.41", + "80.210.49.70", + "80.210.49.79", + "80.210.49.83", + "80.210.49.173", + "80.210.49.196", + "80.210.49.232", + "80.210.50.30", + "80.210.50.35", + "80.210.50.40", + "80.210.50.173", + "80.210.51.50", + "80.210.51.82", + "80.210.51.202", + "80.210.52.240", + "80.210.53.34", + "80.210.53.35", + "80.210.53.70", + "80.210.53.97", + "80.210.53.209", + "80.210.53.213", + "80.210.54.38", + "80.210.54.40", + "80.210.54.49", + "80.210.54.52", + "80.210.54.60", + "80.210.54.68", + "80.210.54.95", + "80.210.54.118", + "80.210.54.185", + "80.210.54.199", + "80.210.54.217", + "80.210.54.221", + "80.210.54.243", + "80.210.55.19", + "80.210.55.56", + "80.210.55.87", + "80.210.55.118", + "80.210.55.120", + "80.210.55.131", + "80.210.55.146", + "80.210.55.161", + "80.210.56.0", + "80.210.56.83", + "80.210.56.102", + "80.210.56.141", + "80.210.56.143", + "80.210.56.169", + "80.210.56.186", + "80.210.56.199", + "80.210.56.205", + "80.210.56.225", + "80.210.56.233", + "80.210.56.253", + "80.210.57.117", + "80.210.58.1", + "80.210.58.7", + "80.210.58.29", + "80.210.58.59", + "80.210.58.73", + "80.210.58.86", + "80.210.58.89", + "80.210.58.111", + "80.210.58.117", + "80.210.58.133", + "80.210.58.142", + "80.210.58.146", + "80.210.58.163", + "80.210.58.229", + "80.210.58.243", + "80.210.59.100", + "80.210.60.109", + "80.210.60.150", + "80.210.60.155", + "80.210.60.211", + "80.210.61.20", + "80.210.61.51", + "80.210.61.123", + "80.210.61.124", + "80.210.61.128", + "80.210.61.141", + "80.210.61.159", + "80.210.61.174", + "80.210.61.178", + "80.210.62.57", + "80.210.62.109", + "80.210.62.128", + "80.210.62.146", + "80.210.62.155", + "80.210.62.173", + "80.210.62.195", + "80.210.62.203", + "80.210.62.206", + "80.210.62.210", + "80.210.62.237", + "80.210.63.3", + "80.210.63.13", + "80.210.63.21", + "80.210.63.26", + "80.210.63.63", + "80.210.63.107", + "80.210.63.142", + "80.210.63.153", + "80.210.63.185", + "80.210.63.194", + "80.210.63.233", + "80.210.63.243", + "80.210.63.251", + "80.210.128.198", + "80.210.131.213", + "80.210.132.9", + "80.210.132.50", + "80.210.132.100", + "80.210.132.101", + "80.210.132.191", + "80.210.133.249", + "80.210.134.163", + "80.210.135.10", + "80.210.135.125", + "80.210.137.2", + "80.210.139.10", + "80.210.139.68", + "80.210.141.214", + "80.210.141.239", + "80.210.142.6", + "80.210.142.54", + "80.210.143.142", + "80.210.160.23", + "80.210.164.73", + "80.210.164.78", + "80.210.165.60", + "80.210.165.199", + "80.210.168.207", + "80.210.170.150", + "80.210.171.56", + "80.210.171.115", + "80.210.172.44", + "80.210.172.76", + "80.210.172.118", + "80.210.174.109", + "80.210.176.194", + "80.210.177.164", + "80.250.200.1", + "80.253.133.144", + "80.253.133.145", + "80.253.152.222", + "81.12.1.27", + "81.12.5.22", + "81.12.5.152", + "81.12.5.229", + "81.12.5.247", + "81.12.5.250", + "81.12.6.34", + "81.12.6.58", + "81.12.7.57", + "81.12.7.219", + "81.12.7.220", + "81.12.7.221", + "81.12.12.233", + "81.12.24.167", + "81.12.24.170", + "81.12.24.198", + "81.12.24.203", + "81.12.24.205", + "81.12.24.224", + "81.12.24.250", + "81.12.25.25", + "81.12.26.27", + "81.12.26.28", + "81.12.26.29", + "81.12.26.31", + "81.12.26.32", + "81.12.26.112", + "81.12.26.113", + "81.12.26.114", + "81.12.26.115", + "81.12.26.116", + "81.12.26.117", + "81.12.26.118", + "81.12.26.119", + "81.12.26.127", + "81.12.26.128", + "81.12.26.129", + "81.12.27.123", + "81.12.27.252", + "81.12.32.164", + "81.12.33.99", + "81.12.36.37", + "81.12.36.58", + "81.12.36.94", + "81.12.36.155", + "81.12.36.178", + "81.12.36.190", + "81.12.36.217", + "81.12.39.30", + "81.12.39.136", + "81.12.39.142", + "81.12.39.145", + "81.12.39.192", + "81.12.39.217", + "81.12.39.232", + "81.12.39.234", + "81.12.39.245", + "81.12.40.14", + "81.12.40.86", + "81.12.40.165", + "81.12.41.54", + "81.12.41.178", + "81.12.41.251", + "81.12.41.252", + "81.12.41.253", + "81.12.41.254", + "81.12.43.66", + "81.12.43.238", + "81.12.43.242", + "81.12.44.26", + "81.12.47.94", + "81.12.47.234", + "81.12.47.235", + "81.12.47.236", + "81.12.47.237", + "81.12.47.238", + "81.12.52.94", + "81.12.53.132", + "81.12.53.163", + "81.12.53.235", + "81.12.53.236", + "81.12.64.10", + "81.12.64.11", + "81.12.64.13", + "81.12.64.14", + "81.12.64.66", + "81.12.64.67", + "81.12.64.68", + "81.12.64.69", + "81.12.64.71", + "81.12.64.72", + "81.12.64.73", + "81.12.64.77", + "81.12.65.42", + "81.12.65.135", + "81.12.69.174", + "81.12.70.98", + "81.12.70.178", + "81.12.71.10", + "81.12.71.98", + "81.12.72.108", + "81.12.72.162", + "81.12.72.238", + "81.12.72.251", + "81.12.73.150", + "81.12.74.16", + "81.12.75.30", + "81.12.75.58", + "81.12.75.243", + "81.12.76.250", + "81.12.78.130", + "81.12.79.82", + "81.12.85.237", + "81.12.85.241", + "81.12.85.245", + "81.12.87.2", + "81.12.87.136", + "81.12.87.138", + "81.12.87.139", + "81.12.87.141", + "81.12.87.142", + "81.12.87.226", + "81.12.88.51", + "81.12.89.74", + "81.12.89.86", + "81.12.89.243", + "81.12.90.74", + "81.12.90.246", + "81.12.93.106", + "81.12.94.74", + "81.12.94.106", + "81.12.95.18", + "81.12.98.2", + "81.12.98.125", + "81.12.99.22", + "81.12.99.98", + "81.12.99.99", + "81.12.99.100", + "81.12.99.101", + "81.12.99.102", + "81.12.99.103", + "81.12.99.105", + "81.12.99.138", + "81.12.99.227", + "81.12.100.174", + "81.12.100.216", + "81.12.100.222", + "81.12.102.145", + "81.12.102.149", + "81.12.102.153", + "81.12.104.34", + "81.12.104.36", + "81.12.104.37", + "81.12.104.38", + "81.12.104.39", + "81.12.104.40", + "81.12.104.42", + "81.12.104.43", + "81.12.104.44", + "81.12.104.45", + "81.12.104.46", + "81.12.104.66", + "81.12.104.67", + "81.12.104.68", + "81.12.104.69", + "81.12.104.70", + "81.12.104.71", + "81.12.104.73", + "81.12.104.74", + "81.12.105.50", + "81.12.105.56", + "81.12.105.57", + "81.12.105.58", + "81.12.105.62", + "81.12.106.27", + "81.12.106.130", + "81.12.107.26", + "81.12.108.90", + "81.12.108.92", + "81.12.108.93", + "81.12.108.94", + "81.12.108.98", + "81.12.108.115", + "81.12.108.116", + "81.12.108.118", + "81.12.109.19", + "81.12.109.22", + "81.12.109.134", + "81.12.109.170", + "81.12.109.174", + "81.12.110.130", + "81.12.111.166", + "81.12.112.10", + "81.12.113.18", + "81.12.113.101", + "81.12.113.118", + "81.12.113.196", + "81.12.113.198", + "81.12.113.212", + "81.12.114.62", + "81.12.116.10", + "81.12.116.174", + "81.12.117.114", + "81.12.117.119", + "81.12.117.120", + "81.12.118.34", + "81.12.118.35", + "81.12.118.36", + "81.12.118.37", + "81.12.118.38", + "81.12.118.39", + "81.12.118.41", + "81.12.118.42", + "81.12.118.43", + "81.12.118.44", + "81.12.118.45", + "81.12.118.47", + "81.12.118.48", + "81.12.118.49", + "81.12.118.50", + "81.12.118.51", + "81.12.118.52", + "81.12.118.53", + "81.12.118.57", + "81.12.118.58", + "81.12.118.59", + "81.12.118.60", + "81.12.118.61", + "81.12.118.62", + "81.12.118.210", + "81.12.118.211", + "81.12.118.212", + "81.12.118.213", + "81.12.119.114", + "81.12.119.138", + "81.12.119.214", + "81.12.120.26", + "81.12.120.62", + "81.12.120.132", + "81.12.120.220", + "81.12.120.222", + "81.12.120.234", + "81.12.120.235", + "81.12.120.236", + "81.12.121.19", + "81.12.122.118", + "81.12.123.66", + "81.12.124.33", + "81.12.124.105", + "81.12.124.114", + "81.12.125.130", + "81.12.125.146", + "81.12.125.148", + "81.12.125.152", + "81.12.125.153", + "81.12.125.156", + "81.12.127.54", + "81.16.112.6", + "81.16.112.154", + "81.16.112.213", + "81.16.112.215", + "81.16.113.82", + "81.16.113.85", + "81.16.113.88", + "81.16.113.110", + "81.16.113.118", + "81.16.113.122", + "81.16.113.165", + "81.16.114.84", + "81.16.114.86", + "81.16.114.127", + "81.16.114.134", + "81.16.114.155", + "81.16.116.18", + "81.16.116.89", + "81.16.116.92", + "81.16.116.97", + "81.16.116.211", + "81.16.116.220", + "81.16.116.228", + "81.16.116.233", + "81.16.117.127", + "81.16.117.129", + "81.16.117.205", + "81.16.118.109", + "81.16.118.156", + "81.16.119.27", + "81.16.119.55", + "81.16.119.78", + "81.16.119.196", + "81.16.119.229", + "81.16.120.60", + "81.16.120.93", + "81.16.120.119", + "81.16.120.122", + "81.16.120.236", + "81.16.121.93", + "81.16.121.114", + "81.16.121.115", + "81.16.121.178", + "81.16.121.221", + "81.16.121.226", + "81.16.122.194", + "81.16.122.212", + "81.16.123.3", + "81.16.123.248", + "81.16.124.33", + "81.16.124.54", + "81.16.124.67", + "81.16.124.71", + "81.16.124.72", + "81.16.124.80", + "81.16.124.147", + "81.16.124.148", + "81.16.124.184", + "81.16.124.243", + "81.16.125.8", + "81.16.125.9", + "81.16.125.21", + "81.16.125.46", + "81.16.125.181", + "81.16.125.190", + "81.16.125.194", + "81.16.125.213", + "81.16.125.221", + "81.16.126.44", + "81.16.126.67", + "81.16.126.81", + "81.16.126.92", + "81.16.126.137", + "81.16.126.144", + "81.16.126.196", + "81.16.127.47", + "81.16.127.72", + "81.16.127.94", + "81.16.127.112", + "81.16.127.155", + "81.16.127.160", + "81.16.127.186", + "81.16.127.189", + "81.16.127.192", + "81.16.127.199", + "81.16.127.204", + "81.16.127.205", + "81.16.127.254", + "81.28.36.146", + "81.28.43.30", + "81.28.43.162", + "81.28.47.2", + "81.28.50.2", + "81.28.57.2", + "81.28.60.102", + "81.28.252.58", + "81.28.252.59", + "81.28.252.136", + "81.28.252.171", + "81.28.252.243", + "81.28.253.103", + "81.28.253.144", + "81.28.253.157", + "81.28.253.175", + "81.29.248.22", + "81.29.248.38", + "81.29.248.109", + "81.29.248.202", + "81.29.249.86", + "81.29.249.190", + "81.29.249.242", + "81.29.249.246", + "81.29.255.170", + "81.29.255.218", + "81.31.250.2", + "81.31.250.5", + "81.31.250.6", + "81.31.250.11", + "81.31.250.13", + "81.31.250.15", + "81.31.250.18", + "81.31.250.21", + "81.31.250.130", + "81.31.250.146", + "81.31.250.147", + "81.31.250.232", + "81.31.250.250", + "81.90.145.19", + "81.90.145.37", + "81.90.145.76", + "81.90.145.94", + "81.90.145.116", + "81.90.145.120", + "81.90.146.66", + "81.90.148.106", + "81.90.149.178", + "81.90.149.234", + "81.90.149.254", + "81.90.152.38", + "81.90.152.86", + "81.90.154.178", + "81.90.154.179", + "81.90.154.180", + "81.90.154.181", + "81.90.154.182", + "81.90.154.183", + "81.90.154.184", + "81.90.154.186", + "81.90.154.187", + "81.90.154.188", + "81.90.154.190", + "81.90.157.82", + "81.90.158.110", + "81.90.159.158", + "81.91.129.228", + "81.91.132.20", + "81.91.136.134", + "81.91.137.34", + "81.91.138.3", + "81.91.138.67", + "81.91.138.114", + "81.91.138.182", + "81.91.139.18", + "81.91.144.116", + "81.91.145.2", + "81.91.145.7", + "81.91.145.66", + "81.91.145.78", + "81.91.145.192", + "81.91.145.193", + "81.91.145.194", + "81.91.145.195", + "81.91.146.30", + "81.91.146.138", + "81.91.146.202", + "81.91.146.203", + "81.91.146.214", + "81.91.147.30", + "81.91.150.66", + "81.91.150.190", + "81.91.152.6", + "81.91.152.70", + "81.91.152.86", + "81.91.153.2", + "81.91.153.10", + "81.91.153.26", + "81.91.153.210", + "81.91.153.211", + "81.91.153.234", + "81.91.154.69", + "81.91.154.82", + "81.91.154.230", + "81.91.155.2", + "81.91.155.22", + "81.91.156.67", + "81.91.156.134", + "81.91.156.150", + "81.91.157.6", + "81.91.157.50", + "81.91.157.51", + "81.91.157.134", + "81.91.158.126", + "81.91.158.130", + "81.91.158.132", + "81.91.158.133", + "81.91.158.218", + "81.91.159.34", + "81.91.159.82", + "81.91.159.106", + "81.91.159.170", + "81.91.159.234", + "81.163.0.33", + "81.163.0.79", + "81.163.0.96", + "81.163.0.97", + "81.163.0.110", + "81.163.0.117", + "81.163.0.138", + "81.163.1.66", + "81.163.1.105", + "81.163.1.106", + "81.163.1.107", + "81.163.1.108", + "81.163.1.109", + "81.163.1.180", + "81.163.3.1", + "81.163.3.2", + "81.163.4.85", + "81.163.6.30", + "81.163.6.107", + "81.163.7.49", + "81.163.7.57", + "81.163.7.71", + "82.99.194.34", + "82.99.194.106", + "82.99.194.197", + "82.99.195.82", + "82.99.195.110", + "82.99.195.114", + "82.99.201.162", + "82.99.201.163", + "82.99.201.164", + "82.99.201.166", + "82.99.201.180", + "82.99.201.182", + "82.99.202.157", + "82.99.202.209", + "82.99.202.210", + "82.99.202.218", + "82.99.202.225", + "82.99.202.227", + "82.99.204.1", + "82.99.204.18", + "82.99.204.21", + "82.99.204.249", + "82.99.206.98", + "82.99.206.99", + "82.99.206.191", + "82.99.213.62", + "82.99.213.206", + "82.99.214.106", + "82.99.214.107", + "82.99.214.109", + "82.99.214.110", + "82.99.230.170", + "82.99.247.45", + "82.99.248.106", + "82.99.248.114", + "82.99.248.214", + "82.99.249.230", + "82.99.250.178", + "82.99.250.228", + "82.99.252.247", + "82.99.253.81", + "82.99.254.250", + "83.97.72.1", + "83.97.72.50", + "83.97.72.254", + "83.147.193.5", + "83.150.193.79", + "84.47.225.198", + "84.47.226.106", + "84.47.226.108", + "84.47.226.111", + "84.47.226.242", + "84.47.227.194", + "84.47.236.57", + "84.47.236.59", + "84.47.236.74", + "84.47.236.112", + "84.47.236.113", + "84.47.236.200", + "84.47.239.21", + "84.241.0.3", + "84.241.0.62", + "84.241.0.119", + "84.241.0.123", + "84.241.0.156", + "84.241.0.223", + "84.241.0.226", + "84.241.0.232", + "84.241.1.36", + "84.241.1.71", + "84.241.1.76", + "84.241.1.99", + "84.241.1.119", + "84.241.1.203", + "84.241.1.248", + "84.241.1.254", + "84.241.2.85", + "84.241.2.90", + "84.241.2.198", + "84.241.2.206", + "84.241.2.235", + "84.241.3.50", + "84.241.3.98", + "84.241.3.105", + "84.241.3.137", + "84.241.3.149", + "84.241.3.199", + "84.241.3.235", + "84.241.4.8", + "84.241.4.33", + "84.241.4.68", + "84.241.4.86", + "84.241.4.140", + "84.241.4.192", + "84.241.5.18", + "84.241.5.20", + "84.241.5.45", + "84.241.5.52", + "84.241.5.79", + "84.241.5.94", + "84.241.5.140", + "84.241.5.176", + "84.241.5.253", + "84.241.6.19", + "84.241.6.72", + "84.241.6.105", + "84.241.6.150", + "84.241.6.165", + "84.241.6.197", + "84.241.6.236", + "84.241.6.248", + "84.241.7.8", + "84.241.7.46", + "84.241.7.48", + "84.241.7.61", + "84.241.7.136", + "84.241.7.233", + "84.241.8.39", + "84.241.8.50", + "84.241.8.225", + "84.241.8.238", + "84.241.9.7", + "84.241.9.51", + "84.241.9.69", + "84.241.9.79", + "84.241.9.96", + "84.241.9.105", + "84.241.9.108", + "84.241.9.115", + "84.241.9.171", + "84.241.9.191", + "84.241.9.200", + "84.241.9.223", + "84.241.9.233", + "84.241.9.234", + "84.241.9.236", + "84.241.10.57", + "84.241.10.70", + "84.241.10.99", + "84.241.10.131", + "84.241.10.164", + "84.241.10.193", + "84.241.10.212", + "84.241.10.216", + "84.241.11.10", + "84.241.11.26", + "84.241.11.61", + "84.241.11.66", + "84.241.11.73", + "84.241.11.138", + "84.241.11.144", + "84.241.11.145", + "84.241.11.172", + "84.241.11.190", + "84.241.11.211", + "84.241.12.24", + "84.241.12.49", + "84.241.12.69", + "84.241.12.104", + "84.241.12.112", + "84.241.12.138", + "84.241.12.153", + "84.241.12.165", + "84.241.12.197", + "84.241.13.93", + "84.241.13.133", + "84.241.13.136", + "84.241.13.171", + "84.241.13.210", + "84.241.13.235", + "84.241.13.241", + "84.241.14.30", + "84.241.14.50", + "84.241.14.126", + "84.241.14.157", + "84.241.14.198", + "84.241.14.239", + "84.241.15.2", + "84.241.15.6", + "84.241.15.56", + "84.241.15.75", + "84.241.15.127", + "84.241.15.147", + "84.241.15.161", + "84.241.15.189", + "84.241.15.203", + "84.241.15.208", + "84.241.15.220", + "84.241.15.230", + "84.241.15.253", + "84.241.15.254", + "84.241.16.13", + "84.241.16.17", + "84.241.16.44", + "84.241.16.54", + "84.241.16.66", + "84.241.16.126", + "84.241.16.165", + "84.241.16.195", + "84.241.16.219", + "84.241.16.222", + "84.241.16.232", + "84.241.18.90", + "84.241.18.108", + "84.241.18.161", + "84.241.18.185", + "84.241.18.195", + "84.241.18.197", + "84.241.18.246", + "84.241.19.9", + "84.241.19.22", + "84.241.19.52", + "84.241.19.111", + "84.241.19.115", + "84.241.19.134", + "84.241.19.172", + "84.241.19.186", + "84.241.19.229", + "84.241.20.91", + "84.241.21.48", + "84.241.21.127", + "84.241.21.213", + "84.241.21.216", + "84.241.21.245", + "84.241.22.38", + "84.241.22.52", + "84.241.22.61", + "84.241.22.94", + "84.241.22.157", + "84.241.22.245", + "84.241.23.238", + "84.241.24.10", + "84.241.24.55", + "84.241.24.58", + "84.241.24.59", + "84.241.24.132", + "84.241.24.204", + "84.241.24.207", + "84.241.24.233", + "84.241.24.249", + "84.241.24.254", + "84.241.25.4", + "84.241.25.30", + "84.241.25.79", + "84.241.25.124", + "84.241.25.131", + "84.241.25.178", + "84.241.25.187", + "84.241.25.200", + "84.241.25.205", + "84.241.25.239", + "84.241.25.244", + "84.241.26.9", + "84.241.26.29", + "84.241.26.34", + "84.241.26.40", + "84.241.26.58", + "84.241.26.65", + "84.241.26.98", + "84.241.26.100", + "84.241.26.149", + "84.241.26.240", + "84.241.27.13", + "84.241.27.86", + "84.241.27.124", + "84.241.27.145", + "84.241.27.180", + "84.241.27.189", + "84.241.28.10", + "84.241.28.27", + "84.241.28.36", + "84.241.28.49", + "84.241.28.80", + "84.241.28.85", + "84.241.28.101", + "84.241.29.26", + "84.241.29.70", + "84.241.29.119", + "84.241.29.129", + "84.241.29.194", + "84.241.29.206", + "84.241.29.228", + "84.241.30.13", + "84.241.30.33", + "84.241.30.57", + "84.241.30.96", + "84.241.30.103", + "84.241.30.111", + "84.241.30.190", + "84.241.30.224", + "84.241.31.0", + "84.241.31.7", + "84.241.31.53", + "84.241.31.90", + "84.241.31.100", + "84.241.32.19", + "84.241.32.78", + "84.241.32.89", + "84.241.32.203", + "84.241.32.254", + "84.241.33.19", + "84.241.33.183", + "84.241.34.132", + "84.241.35.0", + "84.241.35.155", + "84.241.36.15", + "84.241.36.86", + "84.241.36.104", + "84.241.36.116", + "84.241.36.138", + "84.241.36.150", + "84.241.37.79", + "84.241.37.202", + "84.241.37.211", + "84.241.37.234", + "84.241.38.130", + "84.241.38.131", + "84.241.38.177", + "84.241.40.188", + "84.241.41.21", + "84.241.41.131", + "84.241.41.197", + "84.241.41.225", + "84.241.42.178", + "84.241.43.18", + "84.241.43.118", + "84.241.43.119", + "84.241.44.96", + "84.241.44.106", + "84.241.44.211", + "84.241.44.245", + "84.241.45.14", + "84.241.45.183", + "84.241.45.194", + "84.241.46.127", + "84.241.46.177", + "84.241.47.6", + "84.241.47.109", + "84.241.47.190", + "84.241.47.244", + "84.241.51.38", + "84.241.51.93", + "84.241.53.153", + "84.241.53.175", + "84.241.53.190", + "84.241.53.243", + "84.241.54.101", + "84.241.55.13", + "84.241.55.14", + "84.241.55.30", + "84.241.55.57", + "84.241.55.234", + "84.241.55.250", + "84.241.56.80", + "84.241.59.241", + "84.241.59.249", + "84.241.59.251", + "84.241.59.252", + "84.241.59.253", + "84.241.59.254", + "84.241.60.21", + "84.241.60.253", + "84.241.61.1", + "84.241.61.193", + "84.241.63.25", + "85.9.65.241", + "85.9.65.242", + "85.9.71.126", + "85.9.71.233", + "85.9.79.217", + "85.9.85.111", + "85.9.85.113", + "85.9.86.54", + "85.9.86.239", + "85.9.86.254", + "85.9.86.255", + "85.9.87.53", + "85.9.87.54", + "85.9.87.62", + "85.9.87.102", + "85.9.87.121", + "85.9.91.25", + "85.9.91.211", + "85.9.92.36", + "85.9.94.166", + "85.9.94.182", + "85.9.94.208", + "85.9.94.238", + "85.9.95.234", + "85.9.97.10", + "85.9.97.179", + "85.9.97.209", + "85.9.105.36", + "85.9.105.39", + "85.9.105.40", + "85.9.105.42", + "85.9.105.43", + "85.9.105.44", + "85.9.105.45", + "85.9.105.202", + "85.9.105.204", + "85.9.105.205", + "85.9.107.178", + "85.9.107.179", + "85.9.107.180", + "85.9.107.187", + "85.9.107.226", + "85.9.108.169", + "85.9.109.55", + "85.9.111.9", + "85.9.111.11", + "85.9.111.12", + "85.9.111.15", + "85.9.111.17", + "85.9.111.18", + "85.9.111.22", + "85.9.111.124", + "85.9.113.235", + "85.9.113.237", + "85.9.113.238", + "85.9.113.239", + "85.9.113.240", + "85.9.113.242", + "85.9.113.244", + "85.9.114.84", + "85.9.119.237", + "85.9.119.255", + "85.9.121.33", + "85.9.123.72", + "85.9.123.252", + "85.9.124.89", + "85.9.124.111", + "85.9.124.233", + "85.9.124.245", + "85.9.125.68", + "85.15.1.115", + "85.15.1.120", + "85.15.1.188", + "85.15.3.5", + "85.15.45.187", + "85.15.47.176", + "85.15.47.236", + "85.133.130.99", + "85.133.130.114", + "85.133.130.132", + "85.133.133.102", + "85.133.133.104", + "85.133.133.109", + "85.133.133.111", + "85.133.133.138", + "85.133.133.148", + "85.133.133.161", + "85.133.133.162", + "85.133.133.165", + "85.133.133.173", + "85.133.133.192", + "85.133.133.199", + "85.133.133.201", + "85.133.133.214", + "85.133.133.225", + "85.133.133.235", + "85.133.133.239", + "85.133.134.5", + "85.133.134.28", + "85.133.134.30", + "85.133.134.40", + "85.133.134.53", + "85.133.134.63", + "85.133.134.71", + "85.133.134.72", + "85.133.134.78", + "85.133.134.87", + "85.133.134.112", + "85.133.134.118", + "85.133.134.122", + "85.133.134.129", + "85.133.134.138", + "85.133.134.142", + "85.133.134.152", + "85.133.134.173", + "85.133.134.175", + "85.133.134.184", + "85.133.134.186", + "85.133.134.189", + "85.133.134.195", + "85.133.134.204", + "85.133.134.211", + "85.133.134.242", + "85.133.135.146", + "85.133.135.190", + "85.133.136.242", + "85.133.137.190", + "85.133.138.10", + "85.133.138.53", + "85.133.138.99", + "85.133.138.101", + "85.133.138.130", + "85.133.138.154", + "85.133.138.168", + "85.133.138.178", + "85.133.138.187", + "85.133.138.188", + "85.133.138.209", + "85.133.139.34", + "85.133.139.45", + "85.133.139.79", + "85.133.139.81", + "85.133.139.83", + "85.133.139.88", + "85.133.139.104", + "85.133.139.114", + "85.133.139.120", + "85.133.139.121", + "85.133.139.130", + "85.133.139.132", + "85.133.139.183", + "85.133.139.187", + "85.133.139.198", + "85.133.139.199", + "85.133.139.207", + "85.133.139.210", + "85.133.139.215", + "85.133.139.219", + "85.133.139.237", + "85.133.139.240", + "85.133.145.37", + "85.133.145.159", + "85.133.149.42", + "85.133.152.3", + "85.133.152.25", + "85.133.152.27", + "85.133.152.34", + "85.133.152.49", + "85.133.152.57", + "85.133.152.80", + "85.133.152.87", + "85.133.152.90", + "85.133.152.103", + "85.133.152.123", + "85.133.152.136", + "85.133.152.137", + "85.133.152.146", + "85.133.152.152", + "85.133.152.155", + "85.133.152.172", + "85.133.152.184", + "85.133.152.185", + "85.133.152.198", + "85.133.152.217", + "85.133.152.221", + "85.133.152.222", + "85.133.152.227", + "85.133.152.232", + "85.133.152.236", + "85.133.152.238", + "85.133.152.244", + "85.133.152.252", + "85.133.153.171", + "85.133.154.130", + "85.133.154.154", + "85.133.154.155", + "85.133.154.190", + "85.133.155.130", + "85.133.155.131", + "85.133.155.132", + "85.133.155.133", + "85.133.155.134", + "85.133.155.162", + "85.133.156.179", + "85.133.157.4", + "85.133.157.26", + "85.133.157.47", + "85.133.157.72", + "85.133.157.79", + "85.133.157.86", + "85.133.157.90", + "85.133.157.93", + "85.133.157.103", + "85.133.157.106", + "85.133.157.112", + "85.133.157.113", + "85.133.157.128", + "85.133.157.129", + "85.133.157.152", + "85.133.157.153", + "85.133.157.157", + "85.133.157.174", + "85.133.157.175", + "85.133.157.180", + "85.133.157.197", + "85.133.157.217", + "85.133.157.240", + "85.133.158.122", + "85.133.158.195", + "85.133.158.196", + "85.133.158.198", + "85.133.158.201", + "85.133.160.180", + "85.133.160.226", + "85.133.161.24", + "85.133.161.56", + "85.133.161.87", + "85.133.161.202", + "85.133.162.16", + "85.133.162.17", + "85.133.162.19", + "85.133.162.44", + "85.133.162.95", + "85.133.162.140", + "85.133.162.151", + "85.133.162.172", + "85.133.162.182", + "85.133.163.2", + "85.133.163.31", + "85.133.163.85", + "85.133.163.94", + "85.133.163.114", + "85.133.163.158", + "85.133.167.62", + "85.133.167.107", + "85.133.167.108", + "85.133.167.246", + "85.133.168.130", + "85.133.168.202", + "85.133.171.34", + "85.133.171.35", + "85.133.171.36", + "85.133.171.37", + "85.133.171.38", + "85.133.171.178", + "85.133.172.3", + "85.133.172.68", + "85.133.172.84", + "85.133.172.102", + "85.133.172.118", + "85.133.172.129", + "85.133.172.144", + "85.133.172.158", + "85.133.172.162", + "85.133.172.164", + "85.133.172.172", + "85.133.172.180", + "85.133.172.206", + "85.133.172.211", + "85.133.172.213", + "85.133.172.217", + "85.133.172.218", + "85.133.172.228", + "85.133.172.244", + "85.133.173.66", + "85.133.173.90", + "85.133.173.98", + "85.133.175.18", + "85.133.175.110", + "85.133.175.199", + "85.133.176.13", + "85.133.176.127", + "85.133.178.30", + "85.133.179.46", + "85.133.179.189", + "85.133.180.170", + "85.133.181.26", + "85.133.181.102", + "85.133.181.118", + "85.133.181.190", + "85.133.182.12", + "85.133.182.17", + "85.133.182.23", + "85.133.182.27", + "85.133.182.36", + "85.133.182.40", + "85.133.182.42", + "85.133.182.52", + "85.133.182.75", + "85.133.182.86", + "85.133.182.88", + "85.133.182.98", + "85.133.182.106", + "85.133.182.110", + "85.133.182.116", + "85.133.182.121", + "85.133.182.125", + "85.133.182.134", + "85.133.182.141", + "85.133.182.158", + "85.133.182.193", + "85.133.182.205", + "85.133.182.217", + "85.133.182.222", + "85.133.182.236", + "85.133.182.240", + "85.133.182.241", + "85.133.182.246", + "85.133.183.62", + "85.133.183.106", + "85.133.183.110", + "85.133.183.182", + "85.133.183.186", + "85.133.184.51", + "85.133.184.158", + "85.133.185.26", + "85.133.185.102", + "85.133.185.106", + "85.133.185.110", + "85.133.185.130", + "85.133.185.150", + "85.133.186.26", + "85.133.186.149", + "85.133.186.178", + "85.133.186.179", + "85.133.186.180", + "85.133.186.181", + "85.133.186.182", + "85.133.186.190", + "85.133.187.59", + "85.133.187.178", + "85.133.188.218", + "85.133.189.147", + "85.133.189.222", + "85.133.190.40", + "85.133.190.171", + "85.133.190.172", + "85.133.190.173", + "85.133.190.174", + "85.133.191.198", + "85.133.193.36", + "85.133.193.41", + "85.133.193.54", + "85.133.193.72", + "85.133.193.74", + "85.133.193.114", + "85.133.193.136", + "85.133.197.203", + "85.133.202.86", + "85.133.205.212", + "85.133.212.234", + "85.133.212.251", + "85.133.216.14", + "85.133.216.41", + "85.133.217.68", + "85.133.225.160", + "85.133.225.162", + "85.133.225.172", + "85.133.225.187", + "85.133.229.115", + "85.133.229.120", + "85.133.229.129", + "85.133.229.210", + "85.133.237.26", + "85.133.237.93", + "85.133.240.33", + "85.133.240.35", + "85.133.240.38", + "85.133.240.41", + "85.133.240.44", + "85.133.240.48", + "85.133.240.59", + "85.133.240.60", + "85.133.240.61", + "85.133.240.62", + "85.133.240.151", + "85.133.240.155", + "85.133.240.156", + "85.133.240.157", + "85.133.240.159", + "85.133.240.161", + "85.133.240.165", + "85.133.240.166", + "85.133.240.167", + "85.133.240.171", + "85.133.240.173", + "85.133.240.174", + "85.133.240.175", + "85.133.240.176", + "85.133.240.178", + "85.133.240.179", + "85.133.240.180", + "85.133.240.181", + "85.133.240.183", + "85.133.240.184", + "85.133.240.185", + "85.133.240.186", + "85.133.240.188", + "85.133.240.189", + "85.133.240.197", + "85.133.240.199", + "85.133.240.203", + "85.133.240.204", + "85.133.240.205", + "85.133.240.206", + "85.133.240.207", + "85.133.240.208", + "85.133.240.210", + "85.133.240.212", + "85.133.241.116", + "85.133.241.138", + "85.133.241.188", + "85.133.253.101", + "85.133.253.118", + "85.133.253.119", + "85.133.254.71", + "85.185.4.19", + "85.185.4.20", + "85.185.4.62", + "85.185.4.146", + "85.185.4.218", + "85.185.4.219", + "85.185.6.1", + "85.185.6.2", + "85.185.6.3", + "85.185.6.4", + "85.185.6.5", + "85.185.6.6", + "85.185.6.8", + "85.185.6.9", + "85.185.6.10", + "85.185.6.11", + "85.185.6.12", + "85.185.6.13", + "85.185.6.209", + "85.185.8.75", + "85.185.13.35", + "85.185.13.41", + "85.185.13.161", + "85.185.14.41", + "85.185.17.124", + "85.185.20.185", + "85.185.20.226", + "85.185.21.78", + "85.185.22.114", + "85.185.28.141", + "85.185.29.6", + "85.185.29.30", + "85.185.29.90", + "85.185.29.178", + "85.185.41.85", + "85.185.41.87", + "85.185.41.104", + "85.185.41.123", + "85.185.42.106", + "85.185.52.176", + "85.185.54.229", + "85.185.55.245", + "85.185.55.252", + "85.185.56.80", + "85.185.57.27", + "85.185.58.20", + "85.185.60.200", + "85.185.62.57", + "85.185.62.72", + "85.185.62.196", + "85.185.64.8", + "85.185.64.61", + "85.185.64.89", + "85.185.64.90", + "85.185.64.91", + "85.185.64.92", + "85.185.64.93", + "85.185.64.205", + "85.185.64.219", + "85.185.66.81", + "85.185.66.179", + "85.185.66.193", + "85.185.66.216", + "85.185.66.225", + "85.185.67.38", + "85.185.67.43", + "85.185.67.165", + "85.185.67.177", + "85.185.67.178", + "85.185.75.11", + "85.185.75.38", + "85.185.75.187", + "85.185.75.210", + "85.185.75.211", + "85.185.75.212", + "85.185.76.130", + "85.185.76.131", + "85.185.76.132", + "85.185.76.133", + "85.185.76.134", + "85.185.76.138", + "85.185.76.142", + "85.185.76.194", + "85.185.81.82", + "85.185.81.99", + "85.185.84.194", + "85.185.84.226", + "85.185.86.211", + "85.185.88.133", + "85.185.88.137", + "85.185.88.139", + "85.185.93.114", + "85.185.95.185", + "85.185.104.17", + "85.185.105.101", + "85.185.105.104", + "85.185.105.217", + "85.185.106.20", + "85.185.110.226", + "85.185.110.252", + "85.185.123.6", + "85.185.128.1", + "85.185.130.136", + "85.185.138.2", + "85.185.138.5", + "85.185.138.6", + "85.185.138.18", + "85.185.138.28", + "85.185.138.29", + "85.185.138.30", + "85.185.138.58", + "85.185.138.122", + "85.185.138.154", + "85.185.138.158", + "85.185.138.178", + "85.185.138.182", + "85.185.138.186", + "85.185.138.218", + "85.185.138.226", + "85.185.139.46", + "85.185.140.67", + "85.185.149.18", + "85.185.149.24", + "85.185.149.30", + "85.185.158.146", + "85.185.159.74", + "85.185.159.76", + "85.185.159.77", + "85.185.161.214", + "85.185.161.227", + "85.185.161.240", + "85.185.164.111", + "85.185.165.124", + "85.185.165.125", + "85.185.165.126", + "85.185.165.127", + "85.185.166.138", + "85.185.169.42", + "85.185.170.2", + "85.185.185.10", + "85.185.190.141", + "85.185.198.135", + "85.185.201.16", + "85.185.201.19", + "85.185.202.98", + "85.185.202.178", + "85.185.202.242", + "85.185.206.193", + "85.185.206.222", + "85.185.207.178", + "85.185.216.106", + "85.185.218.11", + "85.185.218.15", + "85.185.218.102", + "85.185.218.127", + "85.185.222.240", + "85.185.229.9", + "85.185.229.84", + "85.185.229.90", + "85.185.229.163", + "85.185.232.42", + "85.185.235.43", + "85.185.235.59", + "85.185.235.188", + "85.185.235.190", + "85.185.235.235", + "85.185.236.131", + "85.185.236.154", + "85.185.237.58", + "85.185.237.82", + "85.185.237.83", + "85.185.237.84", + "85.185.237.85", + "85.185.239.26", + "85.185.239.178", + "85.185.239.179", + "85.185.239.180", + "85.185.239.181", + "85.185.239.182", + "85.185.239.202", + "85.185.241.106", + "85.185.241.107", + "85.185.241.170", + "85.185.241.171", + "85.185.241.172", + "85.185.241.173", + "85.185.241.236", + "85.185.244.115", + "85.185.244.178", + "85.185.244.194", + "85.185.244.242", + "85.185.246.18", + "85.185.246.218", + "85.185.247.68", + "85.185.247.138", + "85.185.249.66", + "85.185.249.67", + "85.185.251.1", + "85.185.251.57", + "85.185.251.89", + "85.185.251.177", + "85.185.251.178", + "85.185.251.193", + "85.185.251.218", + "85.185.252.10", + "85.185.253.160", + "85.185.254.15", + "85.185.255.8", + "85.185.255.20", + "85.185.255.36", + "85.185.255.133", + "85.198.0.113", + "85.198.2.5", + "85.198.24.54", + "85.198.24.166", + "85.198.25.50", + "85.198.25.66", + "85.198.25.93", + "85.198.25.98", + "85.198.25.99", + "85.198.25.100", + "85.198.25.101", + "85.198.25.102", + "85.198.25.146", + "85.198.25.147", + "85.198.25.234", + "85.198.26.82", + "85.198.26.177", + "85.198.26.250", + "85.198.27.86", + "85.198.28.10", + "85.198.28.27", + "85.198.28.38", + "85.198.28.112", + "85.198.28.123", + "85.198.28.125", + "85.198.28.197", + "85.198.28.198", + "85.198.29.33", + "85.198.29.55", + "85.198.29.56", + "85.198.29.127", + "85.198.29.161", + "85.198.30.3", + "85.198.30.4", + "85.198.30.15", + "85.198.30.31", + "85.198.30.52", + "85.198.30.60", + "85.198.30.70", + "85.198.30.84", + "85.198.30.123", + "85.198.30.213", + "85.198.30.224", + "85.198.30.244", + "85.198.30.254", + "85.198.31.18", + "85.198.31.35", + "85.198.31.40", + "85.198.31.146", + "85.198.31.169", + "85.198.31.183", + "85.198.31.192", + "85.198.31.232", + "85.204.30.148", + "85.204.30.192", + "85.204.76.71", + "85.204.76.222", + "85.204.77.105", + "85.204.77.153", + "85.204.77.217", + "85.204.80.224", + "85.204.83.87", + "85.204.83.145", + "85.204.84.122", + "85.204.85.173", + "85.204.87.34", + "85.204.87.94", + "85.204.88.117", + "85.204.88.234", + "85.204.90.40", + "85.204.91.128", + "85.204.91.149", + "85.204.92.246", + "85.204.93.198", + "85.204.104.181", + "85.204.104.218", + "85.204.104.238", + "85.204.105.43", + "85.204.105.122", + "85.204.105.169", + "85.204.105.215", + "85.204.105.253", + "85.204.128.134", + "85.204.128.252", + "85.204.129.59", + "85.204.130.34", + "85.204.211.136", + "85.204.211.220", + "85.204.213.50", + "85.204.213.86", + "85.204.213.169", + "85.204.215.106", + "85.204.215.153", + "85.204.217.165", + "85.204.218.216", + "85.204.219.79", + "85.204.219.135", + "85.204.220.90", + "85.204.223.114", + "85.208.253.12", + "85.208.253.25", + "85.208.253.43", + "85.208.253.62", + "85.208.253.119", + "85.208.253.144", + "85.208.253.152", + "85.208.253.232", + "85.208.253.243", + "85.208.254.61", + "85.208.254.63", + "85.208.254.152", + "85.208.254.209", + "85.208.254.213", + "86.104.32.106", + "86.104.81.59", + "86.104.96.213", + "86.104.96.253", + "86.104.104.116", + "86.104.104.176", + "86.104.107.7", + "86.104.110.81", + "86.104.243.225", + "86.104.244.130", + "86.104.244.136", + "86.104.244.176", + "86.104.244.225", + "86.104.244.241", + "86.105.44.36", + "86.105.47.48", + "86.105.47.82", + "86.105.47.235", + "86.106.142.1", + "86.106.142.44", + "86.106.142.117", + "86.107.1.133", + "86.107.8.2", + "86.107.8.18", + "86.107.8.75", + "86.107.14.14", + "86.107.14.21", + "86.107.14.45", + "86.107.14.48", + "86.107.14.54", + "86.107.14.62", + "86.107.14.69", + "86.107.14.77", + "86.107.14.87", + "86.107.14.102", + "86.107.14.103", + "86.107.14.122", + "86.107.14.152", + "86.107.14.156", + "86.107.14.160", + "86.107.14.165", + "86.107.14.170", + "86.107.14.181", + "86.107.14.189", + "86.107.14.191", + "86.107.14.204", + "86.107.14.219", + "86.107.14.234", + "86.107.14.236", + "86.107.14.243", + "86.107.14.244", + "86.107.14.251", + "86.107.15.4", + "86.107.15.14", + "86.107.15.16", + "86.107.15.22", + "86.107.15.32", + "86.107.15.35", + "86.107.15.56", + "86.107.15.62", + "86.107.15.90", + "86.107.15.91", + "86.107.15.123", + "86.107.15.127", + "86.107.15.198", + "86.107.15.201", + "86.107.15.212", + "86.107.15.215", + "86.107.15.221", + "86.107.15.228", + "86.107.15.236", + "86.107.15.249", + "86.107.15.254", + "86.107.144.227", + "86.107.146.168", + "86.107.147.238", + "86.107.150.6", + "86.107.150.35", + "86.107.151.171", + "86.107.151.172", + "86.107.152.190", + "86.107.152.194", + "86.107.152.248", + "86.107.153.163", + "86.107.153.179", + "86.107.157.155", + "86.107.157.232", + "86.107.158.161", + "86.107.158.181", + "86.107.159.246", + "86.109.45.154", + "86.109.45.186", + "86.109.45.187", + "86.109.45.188", + "86.109.54.134", + "87.107.6.17", + "87.107.6.23", + "87.107.6.36", + "87.107.6.59", + "87.107.6.60", + "87.107.6.84", + "87.107.6.97", + "87.107.6.202", + "87.107.8.85", + "87.107.8.87", + "87.107.8.88", + "87.107.8.89", + "87.107.8.166", + "87.107.9.170", + "87.107.9.171", + "87.107.9.173", + "87.107.10.74", + "87.107.10.241", + "87.107.11.91", + "87.107.11.93", + "87.107.12.221", + "87.107.16.30", + "87.107.16.214", + "87.107.16.222", + "87.107.17.222", + "87.107.18.2", + "87.107.18.14", + "87.107.18.22", + "87.107.18.66", + "87.107.18.82", + "87.107.19.14", + "87.107.19.68", + "87.107.19.104", + "87.107.19.196", + "87.107.19.198", + "87.107.28.90", + "87.107.29.158", + "87.107.30.6", + "87.107.30.86", + "87.107.30.166", + "87.107.38.230", + "87.107.44.2", + "87.107.48.7", + "87.107.48.26", + "87.107.48.63", + "87.107.48.66", + "87.107.48.81", + "87.107.48.124", + "87.107.48.144", + "87.107.48.159", + "87.107.48.173", + "87.107.48.184", + "87.107.48.229", + "87.107.48.233", + "87.107.49.19", + "87.107.49.88", + "87.107.49.106", + "87.107.49.163", + "87.107.49.173", + "87.107.49.195", + "87.107.49.220", + "87.107.52.99", + "87.107.52.235", + "87.107.54.69", + "87.107.54.228", + "87.107.54.233", + "87.107.55.56", + "87.107.55.134", + "87.107.55.203", + "87.107.68.38", + "87.107.68.43", + "87.107.68.49", + "87.107.68.87", + "87.107.73.116", + "87.107.74.34", + "87.107.74.36", + "87.107.74.37", + "87.107.75.32", + "87.107.77.18", + "87.107.77.94", + "87.107.78.18", + "87.107.78.130", + "87.107.78.178", + "87.107.78.242", + "87.107.78.246", + "87.107.79.62", + "87.107.79.152", + "87.107.82.201", + "87.107.82.213", + "87.107.83.18", + "87.107.83.64", + "87.107.83.77", + "87.107.83.120", + "87.107.83.238", + "87.107.83.240", + "87.107.87.5", + "87.107.87.118", + "87.107.94.3", + "87.107.95.138", + "87.107.100.160", + "87.107.101.13", + "87.107.101.26", + "87.107.101.61", + "87.107.101.63", + "87.107.101.70", + "87.107.101.72", + "87.107.101.88", + "87.107.101.97", + "87.107.101.99", + "87.107.101.100", + "87.107.101.101", + "87.107.101.251", + "87.107.102.195", + "87.107.102.227", + "87.107.103.226", + "87.107.103.250", + "87.107.104.8", + "87.107.104.177", + "87.107.104.214", + "87.107.105.27", + "87.107.105.31", + "87.107.108.185", + "87.107.108.208", + "87.107.108.221", + "87.107.109.107", + "87.107.110.10", + "87.107.110.22", + "87.107.110.138", + "87.107.111.178", + "87.107.111.180", + "87.107.111.181", + "87.107.111.182", + "87.107.111.202", + "87.107.111.203", + "87.107.111.204", + "87.107.111.206", + "87.107.121.85", + "87.107.124.117", + "87.107.124.124", + "87.107.132.35", + "87.107.135.242", + "87.107.136.42", + "87.107.137.50", + "87.107.137.82", + "87.107.137.250", + "87.107.137.251", + "87.107.138.50", + "87.107.138.52", + "87.107.138.53", + "87.107.138.58", + "87.107.138.70", + "87.107.138.151", + "87.107.138.226", + "87.107.139.218", + "87.107.139.220", + "87.107.139.222", + "87.107.139.254", + "87.107.141.1", + "87.107.141.37", + "87.107.141.79", + "87.107.141.95", + "87.107.141.105", + "87.107.141.113", + "87.107.142.18", + "87.107.143.4", + "87.107.143.12", + "87.107.143.15", + "87.107.143.19", + "87.107.143.21", + "87.107.143.29", + "87.107.143.130", + "87.107.143.131", + "87.107.143.132", + "87.107.143.133", + "87.107.143.134", + "87.107.143.135", + "87.107.143.136", + "87.107.143.137", + "87.107.143.138", + "87.107.143.139", + "87.107.143.140", + "87.107.143.141", + "87.107.143.142", + "87.107.144.54", + "87.107.144.62", + "87.107.144.217", + "87.107.146.29", + "87.107.146.39", + "87.107.146.141", + "87.107.146.243", + "87.107.152.35", + "87.107.152.211", + "87.107.152.212", + "87.107.152.214", + "87.107.152.246", + "87.107.153.116", + "87.107.154.27", + "87.107.154.69", + "87.107.154.174", + "87.107.155.82", + "87.107.155.238", + "87.107.164.15", + "87.107.164.28", + "87.107.164.29", + "87.107.164.65", + "87.107.164.179", + "87.107.164.215", + "87.107.165.65", + "87.107.165.135", + "87.107.166.6", + "87.107.166.122", + "87.107.166.150", + "87.107.167.67", + "87.107.167.92", + "87.107.167.182", + "87.107.167.183", + "87.107.167.185", + "87.107.167.186", + "87.107.174.43", + "87.107.174.114", + "87.107.174.134", + "87.107.174.139", + "87.107.184.98", + "87.107.185.204", + "87.107.185.216", + "87.236.38.233", + "87.236.39.134", + "87.236.209.21", + "87.236.209.173", + "87.236.209.229", + "87.236.210.54", + "87.236.210.126", + "87.236.211.5", + "87.236.211.11", + "87.236.211.70", + "87.236.211.75", + "87.236.211.119", + "87.236.211.220", + "87.236.211.222", + "87.236.212.82", + "87.236.212.132", + "87.236.212.146", + "87.236.212.178", + "87.236.213.20", + "87.236.213.33", + "87.236.213.97", + "87.236.213.129", + "87.236.213.161", + "87.236.213.249", + "87.236.214.20", + "87.236.214.47", + "87.236.214.129", + "87.236.214.225", + "87.236.214.249", + "87.236.215.169", + "87.236.215.170", + "87.236.215.227", + "87.247.168.101", + "87.247.168.190", + "87.247.170.156", + "87.247.171.4", + "87.247.171.74", + "87.247.174.146", + "87.247.174.214", + "87.247.176.68", + "87.247.176.242", + "87.247.176.252", + "87.247.176.254", + "87.248.130.28", + "87.248.130.42", + "87.248.130.91", + "87.248.130.133", + "87.248.130.186", + "87.248.137.243", + "87.248.138.33", + "87.248.138.162", + "87.248.138.163", + "87.248.138.164", + "87.248.138.165", + "87.248.138.166", + "87.248.139.186", + "87.248.139.236", + "87.248.139.237", + "87.248.140.6", + "87.248.140.65", + "87.248.140.123", + "87.248.140.171", + "87.248.140.195", + "87.248.140.197", + "87.248.141.133", + "87.248.141.142", + "87.248.141.144", + "87.248.141.152", + "87.248.141.196", + "87.248.141.211", + "87.248.145.145", + "87.248.147.154", + "87.248.147.155", + "87.248.150.118", + "87.248.151.5", + "87.248.151.38", + "87.248.151.59", + "87.248.151.117", + "87.248.151.124", + "87.248.152.44", + "87.248.152.189", + "87.248.153.230", + "87.248.154.31", + "87.248.154.89", + "87.248.154.103", + "87.248.154.113", + "87.248.154.129", + "87.248.154.194", + "87.248.154.207", + "87.248.155.14", + "87.248.155.79", + "87.248.155.81", + "87.248.155.245", + "87.248.156.62", + "87.248.156.134", + "87.248.156.195", + "88.135.38.143", + "88.135.38.146", + "88.135.38.147", + "88.135.38.148", + "88.135.38.149", + "88.135.38.150", + "88.135.38.151", + "88.135.39.29", + "88.135.68.2", + "88.135.68.15", + "88.135.68.96", + "88.135.75.148", + "88.135.75.201", + "88.218.16.58", + "88.218.16.123", + "88.218.16.124", + "89.32.197.12", + "89.32.197.30", + "89.32.197.51", + "89.32.197.52", + "89.32.197.87", + "89.32.197.111", + "89.32.197.182", + "89.32.197.214", + "89.32.248.1", + "89.32.248.36", + "89.32.248.65", + "89.32.248.73", + "89.32.248.81", + "89.32.248.129", + "89.32.248.130", + "89.32.248.134", + "89.32.248.137", + "89.32.248.145", + "89.32.248.161", + "89.32.248.162", + "89.32.248.163", + "89.32.248.194", + "89.32.248.201", + "89.32.248.203", + "89.32.248.209", + "89.32.248.225", + "89.32.249.1", + "89.32.249.65", + "89.32.249.97", + "89.32.249.129", + "89.32.249.145", + "89.32.249.153", + "89.32.249.161", + "89.32.249.193", + "89.32.251.1", + "89.32.251.17", + "89.32.251.33", + "89.32.251.41", + "89.32.251.65", + "89.32.251.95", + "89.32.251.107", + "89.32.251.129", + "89.32.251.161", + "89.33.18.10", + "89.33.18.18", + "89.33.18.20", + "89.33.18.21", + "89.33.18.29", + "89.33.18.42", + "89.33.18.50", + "89.33.18.52", + "89.33.18.54", + "89.33.18.55", + "89.33.18.61", + "89.33.18.68", + "89.33.18.70", + "89.33.18.84", + "89.33.18.93", + "89.33.18.99", + "89.33.18.101", + "89.33.18.104", + "89.33.18.106", + "89.33.18.111", + "89.33.18.114", + "89.33.18.142", + "89.33.18.145", + "89.33.18.150", + "89.33.18.153", + "89.33.18.157", + "89.33.18.160", + "89.33.18.168", + "89.33.18.174", + "89.33.18.186", + "89.33.18.189", + "89.33.18.190", + "89.33.18.191", + "89.33.18.192", + "89.33.18.193", + "89.33.18.206", + "89.33.18.212", + "89.33.18.225", + "89.33.18.231", + "89.33.18.235", + "89.33.18.242", + "89.33.18.246", + "89.33.18.249", + "89.33.18.251", + "89.33.19.13", + "89.33.19.14", + "89.33.19.30", + "89.33.19.31", + "89.33.19.45", + "89.33.19.49", + "89.33.19.50", + "89.33.19.51", + "89.33.19.62", + "89.33.19.65", + "89.33.19.67", + "89.33.19.68", + "89.33.19.73", + "89.33.19.94", + "89.33.19.99", + "89.33.19.107", + "89.33.19.112", + "89.33.19.117", + "89.33.19.118", + "89.33.19.119", + "89.33.19.125", + "89.33.19.126", + "89.33.19.130", + "89.33.19.141", + "89.33.19.152", + "89.33.19.156", + "89.33.19.165", + "89.33.19.169", + "89.33.19.170", + "89.33.19.173", + "89.33.19.176", + "89.33.19.179", + "89.33.19.182", + "89.33.19.190", + "89.33.19.197", + "89.33.19.200", + "89.33.19.207", + "89.33.19.209", + "89.33.19.216", + "89.33.19.218", + "89.33.19.220", + "89.33.19.227", + "89.33.19.229", + "89.33.19.231", + "89.33.19.235", + "89.33.19.236", + "89.33.19.239", + "89.33.19.240", + "89.33.19.244", + "89.33.19.246", + "89.33.19.251", + "89.33.129.167", + "89.33.129.170", + "89.33.205.167", + "89.33.234.12", + "89.33.240.41", + "89.34.21.101", + "89.34.169.18", + "89.34.169.108", + "89.34.169.116", + "89.34.169.222", + "89.34.176.58", + "89.34.176.66", + "89.34.176.105", + "89.34.176.185", + "89.34.177.113", + "89.34.200.91", + "89.34.200.94", + "89.34.200.117", + "89.34.201.120", + "89.34.254.147", + "89.35.58.51", + "89.35.132.69", + "89.35.132.78", + "89.35.132.122", + "89.35.133.153", + "89.35.133.210", + "89.35.133.223", + "89.35.133.237", + "89.36.226.24", + "89.36.226.29", + "89.37.1.10", + "89.37.1.58", + "89.37.1.59", + "89.37.1.60", + "89.37.1.61", + "89.37.1.62", + "89.37.1.115", + "89.37.3.182", + "89.37.4.54", + "89.37.9.118", + "89.37.11.210", + "89.37.12.84", + "89.37.13.250", + "89.37.15.11", + "89.37.15.12", + "89.37.15.34", + "89.37.15.90", + "89.37.15.91", + "89.37.15.92", + "89.37.103.14", + "89.37.103.30", + "89.37.103.65", + "89.37.103.81", + "89.37.103.103", + "89.37.146.97", + "89.37.147.29", + "89.37.148.152", + "89.37.148.159", + "89.37.148.182", + "89.37.153.246", + "89.37.155.233", + "89.37.198.147", + "89.37.199.125", + "89.37.199.209", + "89.37.219.29", + "89.37.219.30", + "89.37.219.51", + "89.37.219.220", + "89.37.241.17", + "89.37.242.36", + "89.37.242.162", + "89.37.245.11", + "89.37.245.55", + "89.37.246.36", + "89.37.247.74", + "89.37.248.10", + "89.37.248.120", + "89.37.249.127", + "89.37.250.29", + "89.37.250.157", + "89.37.251.210", + "89.37.252.31", + "89.37.253.74", + "89.37.253.85", + "89.37.253.125", + "89.37.253.130", + "89.37.253.240", + "89.37.254.33", + "89.37.254.83", + "89.37.255.217", + "89.38.81.43", + "89.38.81.101", + "89.38.82.199", + "89.38.83.102", + "89.38.84.75", + "89.38.84.110", + "89.38.85.197", + "89.38.86.67", + "89.38.87.69", + "89.38.87.96", + "89.38.87.192", + "89.38.88.198", + "89.38.89.20", + "89.38.89.32", + "89.38.89.171", + "89.38.90.62", + "89.38.90.169", + "89.38.91.21", + "89.38.92.26", + "89.38.92.163", + "89.38.93.89", + "89.38.93.111", + "89.38.94.189", + "89.38.94.214", + "89.38.95.197", + "89.38.102.107", + "89.38.102.227", + "89.38.102.251", + "89.38.103.22", + "89.38.103.58", + "89.38.103.81", + "89.38.103.139", + "89.38.103.166", + "89.38.103.219", + "89.38.246.58", + "89.39.208.9", + "89.39.208.17", + "89.39.208.21", + "89.39.208.25", + "89.39.208.33", + "89.39.208.39", + "89.39.208.73", + "89.39.208.77", + "89.39.208.81", + "89.39.208.97", + "89.39.208.105", + "89.39.208.113", + "89.39.208.126", + "89.39.208.129", + "89.39.208.133", + "89.39.208.145", + "89.39.208.171", + "89.39.208.177", + "89.39.208.182", + "89.40.78.162", + "89.40.78.189", + "89.40.78.208", + "89.40.78.219", + "89.40.79.27", + "89.40.79.53", + "89.40.79.71", + "89.40.79.117", + "89.40.79.123", + "89.40.79.185", + "89.40.79.211", + "89.40.79.226", + "89.40.106.17", + "89.40.106.139", + "89.40.107.24", + "89.40.107.223", + "89.40.107.244", + "89.40.159.148", + "89.40.246.2", + "89.40.246.135", + "89.40.246.212", + "89.40.247.10", + "89.40.247.138", + "89.40.247.154", + "89.41.40.56", + "89.41.40.106", + "89.41.40.171", + "89.41.41.83", + "89.41.42.52", + "89.41.42.193", + "89.41.42.230", + "89.41.42.234", + "89.41.43.48", + "89.41.43.64", + "89.41.43.135", + "89.41.43.163", + "89.42.44.211", + "89.42.96.21", + "89.42.96.80", + "89.42.96.167", + "89.42.97.57", + "89.42.100.11", + "89.42.100.28", + "89.42.100.50", + "89.42.100.56", + "89.42.101.45", + "89.42.101.182", + "89.42.102.130", + "89.42.102.184", + "89.42.103.21", + "89.42.103.160", + "89.42.103.237", + "89.42.136.30", + "89.42.136.114", + "89.42.136.115", + "89.42.136.116", + "89.42.136.218", + "89.42.138.122", + "89.42.139.89", + "89.42.150.51", + "89.42.151.230", + "89.42.197.6", + "89.42.197.17", + "89.42.197.18", + "89.42.197.21", + "89.42.197.25", + "89.42.197.78", + "89.42.198.4", + "89.42.198.12", + "89.42.198.21", + "89.42.198.30", + "89.42.198.31", + "89.42.198.43", + "89.42.198.57", + "89.42.198.73", + "89.42.198.86", + "89.42.198.110", + "89.42.198.116", + "89.42.198.131", + "89.42.198.180", + "89.42.198.216", + "89.42.199.59", + "89.42.199.212", + "89.42.208.1", + "89.42.208.65", + "89.42.208.97", + "89.42.208.112", + "89.42.208.129", + "89.42.208.145", + "89.42.208.153", + "89.42.208.161", + "89.42.208.177", + "89.42.208.193", + "89.42.208.209", + "89.42.208.211", + "89.42.208.217", + "89.42.208.225", + "89.42.208.233", + "89.42.208.241", + "89.42.209.18", + "89.42.209.32", + "89.42.209.55", + "89.42.209.65", + "89.42.209.85", + "89.42.209.90", + "89.42.209.97", + "89.42.209.129", + "89.42.209.145", + "89.42.209.149", + "89.42.209.161", + "89.42.209.177", + "89.42.209.185", + "89.42.209.193", + "89.42.209.201", + "89.42.209.225", + "89.42.209.233", + "89.42.209.249", + "89.42.209.253", + "89.42.210.1", + "89.42.210.18", + "89.42.210.56", + "89.42.210.59", + "89.42.210.129", + "89.42.210.161", + "89.42.210.193", + "89.42.210.209", + "89.42.210.233", + "89.42.210.241", + "89.42.210.242", + "89.42.210.249", + "89.42.211.1", + "89.42.211.17", + "89.42.211.33", + "89.42.211.97", + "89.42.211.113", + "89.42.211.129", + "89.42.211.161", + "89.42.211.193", + "89.42.211.225", + "89.42.211.233", + "89.43.2.90", + "89.43.3.78", + "89.43.3.224", + "89.43.7.205", + "89.43.11.226", + "89.43.13.7", + "89.43.36.175", + "89.43.144.185", + "89.43.216.15", + "89.43.217.102", + "89.43.222.45", + "89.43.222.109", + "89.43.223.216", + "89.43.226.5", + "89.43.227.38", + "89.43.227.44", + "89.43.229.90", + "89.43.230.171", + "89.43.230.207", + "89.43.231.200", + "89.44.112.4", + "89.44.112.6", + "89.44.112.7", + "89.44.112.8", + "89.44.112.9", + "89.44.118.4", + "89.44.118.20", + "89.44.118.21", + "89.44.129.129", + "89.44.129.255", + "89.44.130.118", + "89.44.132.3", + "89.44.134.62", + "89.44.176.34", + "89.44.176.246", + "89.44.177.25", + "89.44.177.66", + "89.44.179.14", + "89.44.179.172", + "89.44.179.254", + "89.44.180.39", + "89.44.183.233", + "89.44.240.59", + "89.44.240.200", + "89.44.240.220", + "89.45.56.19", + "89.45.56.25", + "89.45.56.35", + "89.45.56.52", + "89.45.56.74", + "89.45.56.78", + "89.45.56.82", + "89.45.56.86", + "89.45.56.111", + "89.45.56.113", + "89.45.56.122", + "89.45.56.125", + "89.45.56.140", + "89.45.56.179", + "89.45.56.186", + "89.45.89.1", + "89.45.89.90", + "89.45.89.248", + "89.45.89.249", + "89.45.89.251", + "89.45.89.252", + "89.45.89.253", + "89.45.89.254", + "89.45.115.201", + "89.46.60.49", + "89.46.61.219", + "89.46.94.24", + "89.46.94.71", + "89.46.94.197", + "89.46.94.202", + "89.46.94.231", + "89.46.95.220", + "89.46.95.222", + "89.46.216.5", + "89.46.216.13", + "89.46.216.14", + "89.46.216.41", + "89.46.216.208", + "89.46.218.17", + "89.46.218.41", + "89.46.218.145", + "89.46.219.1", + "89.46.219.4", + "89.46.219.33", + "89.46.219.34", + "89.46.219.36", + "89.46.219.37", + "89.46.219.38", + "89.46.219.40", + "89.46.219.41", + "89.46.219.42", + "89.46.219.44", + "89.46.219.45", + "89.46.219.46", + "89.47.196.131", + "89.47.196.137", + "89.47.196.143", + "89.47.199.106", + "89.47.199.158", + "89.47.199.230", + "89.47.201.38", + "89.47.202.101", + "89.144.132.178", + "89.144.133.130", + "89.144.133.136", + "89.144.133.193", + "89.144.135.118", + "89.144.137.200", + "89.144.139.180", + "89.144.139.181", + "89.144.140.81", + "89.144.141.129", + "89.144.143.162", + "89.144.143.163", + "89.144.143.165", + "89.144.149.93", + "89.144.151.82", + "89.144.151.88", + "89.144.152.1", + "89.144.152.2", + "89.144.153.48", + "89.144.155.82", + "89.144.157.164", + "89.144.160.25", + "89.144.169.244", + "89.144.172.248", + "89.144.173.180", + "89.144.174.181", + "89.144.174.248", + "89.144.175.220", + "89.144.180.65", + "89.144.183.65", + "89.144.183.241", + "89.144.184.9", + "89.144.185.201", + "89.144.186.10", + "89.144.187.121", + "89.144.188.168", + "89.144.189.72", + "89.165.0.138", + "89.165.3.242", + "89.165.7.118", + "89.165.40.10", + "89.165.44.194", + "89.165.45.194", + "89.165.56.12", + "89.165.64.8", + "89.219.64.56", + "89.219.64.250", + "89.219.66.253", + "89.219.67.35", + "89.219.67.204", + "89.219.68.206", + "89.219.68.251", + "89.219.69.245", + "89.219.70.146", + "89.219.70.157", + "89.219.71.200", + "89.219.72.72", + "89.219.72.195", + "89.219.74.112", + "89.219.75.26", + "89.219.76.119", + "89.219.78.58", + "89.219.81.228", + "89.219.84.28", + "89.219.86.59", + "89.219.86.83", + "89.219.86.161", + "89.219.88.197", + "89.219.89.194", + "89.219.90.152", + "89.219.91.179", + "89.219.91.185", + "89.219.91.252", + "89.219.93.59", + "89.219.93.170", + "89.219.94.38", + "89.219.94.79", + "89.219.95.230", + "89.219.100.4", + "89.219.100.20", + "89.219.100.47", + "89.219.105.68", + "89.219.107.184", + "89.219.108.107", + "89.219.108.139", + "89.219.108.250", + "89.219.111.145", + "89.219.112.154", + "89.219.114.134", + "89.219.115.186", + "89.219.117.58", + "89.219.118.20", + "89.219.118.67", + "89.219.119.157", + "89.219.120.250", + "89.219.122.59", + "89.219.124.202", + "89.219.125.162", + "89.219.194.91", + "89.219.194.254", + "89.219.195.206", + "89.219.200.100", + "89.219.202.212", + "89.219.203.130", + "89.219.207.174", + "89.219.208.238", + "89.219.209.174", + "89.219.218.83", + "89.219.221.97", + "89.219.221.160", + "89.219.225.235", + "89.219.226.15", + "89.219.229.89", + "89.219.236.121", + "89.219.243.103", + "89.219.245.17", + "89.219.246.98", + "89.219.246.103", + "89.219.247.62", + "89.219.252.29", + "89.219.252.79", + "89.219.255.238", + "89.221.84.84", + "89.221.84.88", + "89.221.85.56", + "89.221.85.110", + "89.221.85.122", + "89.221.85.137", + "89.221.85.140", + "89.221.85.142", + "89.221.85.217", + "89.221.86.76", + "89.221.86.171", + "89.221.86.173", + "89.221.86.202", + "89.221.86.213", + "89.235.79.11", + "89.235.79.12", + "89.235.79.14", + "89.235.79.17", + "89.235.79.179", + "89.235.93.201", + "89.235.95.206", + "89.235.96.26", + "89.235.99.208", + "89.235.104.1", + "89.235.112.1", + "89.235.116.213", + "91.92.114.105", + "91.92.114.198", + "91.92.114.239", + "91.92.121.13", + "91.92.121.59", + "91.92.121.77", + "91.92.121.115", + "91.92.121.122", + "91.92.121.157", + "91.92.122.4", + "91.92.122.22", + "91.92.122.90", + "91.92.122.95", + "91.92.122.138", + "91.92.122.144", + "91.92.122.219", + "91.92.123.159", + "91.92.123.214", + "91.92.124.1", + "91.92.124.40", + "91.92.124.81", + "91.92.124.90", + "91.92.124.147", + "91.92.124.172", + "91.92.124.220", + "91.92.124.248", + "91.92.125.160", + "91.92.125.219", + "91.92.125.243", + "91.92.126.4", + "91.92.126.48", + "91.92.126.49", + "91.92.126.71", + "91.92.126.85", + "91.92.126.97", + "91.92.126.117", + "91.92.126.123", + "91.92.126.238", + "91.92.127.14", + "91.92.127.37", + "91.92.127.53", + "91.92.127.75", + "91.92.127.105", + "91.92.127.145", + "91.92.127.167", + "91.92.127.191", + "91.92.127.228", + "91.92.129.59", + "91.92.129.87", + "91.92.129.162", + "91.92.129.188", + "91.92.129.216", + "91.92.130.4", + "91.92.130.49", + "91.92.130.111", + "91.92.130.112", + "91.92.130.148", + "91.92.130.157", + "91.92.130.168", + "91.92.130.197", + "91.92.130.202", + "91.92.130.231", + "91.92.131.57", + "91.92.131.107", + "91.92.131.115", + "91.92.131.135", + "91.92.131.151", + "91.92.131.155", + "91.92.131.166", + "91.92.131.168", + "91.92.131.196", + "91.92.131.199", + "91.92.131.201", + "91.92.131.204", + "91.92.131.208", + "91.92.131.218", + "91.92.131.248", + "91.92.132.17", + "91.92.133.8", + "91.92.133.75", + "91.92.133.80", + "91.92.133.83", + "91.92.133.87", + "91.92.133.89", + "91.92.133.97", + "91.92.133.121", + "91.92.133.135", + "91.92.133.145", + "91.92.133.158", + "91.92.133.195", + "91.92.149.37", + "91.92.151.37", + "91.92.156.9", + "91.92.157.82", + "91.92.158.66", + "91.92.158.72", + "91.92.159.114", + "91.92.159.131", + "91.92.159.241", + "91.92.164.17", + "91.92.164.200", + "91.92.165.41", + "91.92.165.51", + "91.92.166.52", + "91.92.166.201", + "91.92.167.216", + "91.92.180.19", + "91.92.180.30", + "91.92.180.36", + "91.92.180.72", + "91.92.180.80", + "91.92.180.87", + "91.92.180.106", + "91.92.180.109", + "91.92.180.125", + "91.92.180.127", + "91.92.180.131", + "91.92.180.139", + "91.92.180.182", + "91.92.180.216", + "91.92.180.230", + "91.92.180.253", + "91.92.181.44", + "91.92.181.61", + "91.92.181.113", + "91.92.181.138", + "91.92.181.142", + "91.92.181.205", + "91.92.182.57", + "91.92.182.77", + "91.92.182.120", + "91.92.182.150", + "91.92.182.181", + "91.92.182.200", + "91.92.182.251", + "91.92.183.23", + "91.92.183.70", + "91.92.183.106", + "91.92.183.172", + "91.92.185.29", + "91.92.185.70", + "91.92.185.92", + "91.92.186.17", + "91.92.186.108", + "91.92.186.132", + "91.92.186.154", + "91.92.186.167", + "91.92.186.214", + "91.92.186.225", + "91.92.187.10", + "91.92.187.30", + "91.92.188.38", + "91.92.188.142", + "91.92.188.218", + "91.92.188.223", + "91.92.189.39", + "91.92.189.183", + "91.92.190.18", + "91.92.190.87", + "91.92.190.126", + "91.92.190.168", + "91.92.190.190", + "91.92.190.193", + "91.92.190.215", + "91.92.191.30", + "91.92.191.36", + "91.92.191.81", + "91.92.191.167", + "91.92.191.168", + "91.92.191.177", + "91.92.191.178", + "91.92.191.221", + "91.92.192.17", + "91.92.204.27", + "91.92.204.130", + "91.92.204.165", + "91.92.204.178", + "91.92.204.194", + "91.92.204.211", + "91.92.205.179", + "91.92.205.208", + "91.92.206.34", + "91.92.206.90", + "91.92.207.152", + "91.92.207.195", + "91.92.207.221", + "91.92.207.228", + "91.92.207.238", + "91.92.208.3", + "91.92.208.10", + "91.92.208.21", + "91.92.208.38", + "91.92.208.43", + "91.92.208.51", + "91.92.208.63", + "91.92.208.108", + "91.92.208.149", + "91.92.208.173", + "91.92.208.192", + "91.92.208.205", + "91.92.208.229", + "91.92.208.245", + "91.92.209.2", + "91.92.209.5", + "91.92.209.9", + "91.92.209.27", + "91.92.209.32", + "91.92.209.38", + "91.92.209.43", + "91.92.209.45", + "91.92.209.75", + "91.92.209.115", + "91.92.209.165", + "91.92.209.167", + "91.92.209.173", + "91.92.209.192", + "91.92.209.194", + "91.92.209.247", + "91.92.210.135", + "91.92.211.24", + "91.92.211.55", + "91.92.211.66", + "91.92.211.77", + "91.92.211.78", + "91.92.211.81", + "91.92.211.108", + "91.92.211.135", + "91.92.211.151", + "91.92.211.160", + "91.92.211.174", + "91.92.211.218", + "91.92.211.232", + "91.92.211.245", + "91.92.211.250", + "91.92.211.253", + "91.92.212.33", + "91.92.212.69", + "91.92.212.157", + "91.92.212.167", + "91.92.212.171", + "91.92.212.194", + "91.92.212.240", + "91.92.212.246", + "91.92.213.37", + "91.92.213.82", + "91.92.213.148", + "91.92.214.41", + "91.92.214.46", + "91.92.214.65", + "91.92.214.85", + "91.92.214.93", + "91.92.214.104", + "91.92.214.143", + "91.92.214.188", + "91.92.214.190", + "91.92.214.202", + "91.92.214.211", + "91.92.214.218", + "91.92.214.236", + "91.92.214.242", + "91.92.215.0", + "91.92.215.10", + "91.92.215.13", + "91.92.215.25", + "91.92.215.48", + "91.92.215.74", + "91.92.215.81", + "91.92.215.98", + "91.92.215.106", + "91.92.215.129", + "91.92.215.132", + "91.92.215.231", + "91.92.231.55", + "91.92.231.92", + "91.92.231.112", + "91.92.231.203", + "91.106.64.25", + "91.106.64.49", + "91.106.64.94", + "91.106.66.32", + "91.106.66.173", + "91.106.67.85", + "91.106.67.252", + "91.106.68.164", + "91.106.70.14", + "91.106.70.206", + "91.106.70.213", + "91.106.72.95", + "91.106.74.77", + "91.106.76.129", + "91.106.83.25", + "91.106.83.26", + "91.106.83.193", + "91.106.84.20", + "91.106.84.25", + "91.106.85.2", + "91.106.85.5", + "91.106.85.6", + "91.106.85.19", + "91.106.85.22", + "91.106.85.33", + "91.106.85.34", + "91.106.85.35", + "91.106.85.36", + "91.106.85.37", + "91.106.85.38", + "91.106.85.39", + "91.106.85.41", + "91.106.85.42", + "91.106.85.44", + "91.106.85.45", + "91.106.85.46", + "91.106.85.47", + "91.106.91.55", + "91.106.91.69", + "91.106.91.85", + "91.106.91.192", + "91.106.91.232", + "91.106.92.139", + "91.106.94.145", + "91.106.95.130", + "91.108.128.114", + "91.108.128.174", + "91.108.129.98", + "91.108.129.99", + "91.108.129.100", + "91.108.129.101", + "91.108.129.122", + "91.108.130.112", + "91.108.131.9", + "91.108.131.10", + "91.108.138.41", + "91.108.140.3", + "91.108.140.4", + "91.108.140.5", + "91.108.145.15", + "91.108.145.68", + "91.108.150.2", + "91.108.150.24", + "91.108.150.41", + "91.108.152.6", + "91.108.155.44", + "91.108.155.194", + "91.108.155.195", + "91.108.155.198", + "91.108.155.210", + "91.108.155.244", + "91.185.148.110", + "91.186.193.222", + "91.199.9.18", + "91.199.9.117", + "91.199.9.118", + "91.199.9.232", + "91.199.9.234", + "91.199.18.5", + "91.199.18.20", + "91.199.18.37", + "91.199.18.39", + "91.199.18.68", + "91.199.18.69", + "91.199.18.70", + "91.199.18.71", + "91.199.18.242", + "91.199.18.243", + "91.199.18.255", + "91.199.27.177", + "91.199.27.178", + "91.199.27.211", + "91.199.27.214", + "91.199.27.240", + "91.199.27.241", + "91.199.30.254", + "91.199.215.34", + "91.199.215.238", + "91.206.177.2", + "91.206.177.3", + "91.207.205.3", + "91.207.205.7", + "91.208.163.195", + "91.212.174.20", + "91.212.174.32", + "91.212.174.78", + "91.212.174.246", + "91.212.252.48", + "91.220.113.36", + "91.220.113.121", + "91.220.113.247", + "91.221.241.243", + "91.222.196.8", + "91.222.196.9", + "91.222.196.53", + "91.222.197.22", + "91.222.197.25", + "91.222.198.42", + "91.222.198.43", + "91.223.116.150", + "91.223.116.173", + "91.223.116.176", + "91.223.116.179", + "91.223.116.238", + "91.223.146.17", + "91.224.20.253", + "91.224.176.0", + "91.224.176.9", + "91.227.86.244", + "91.227.246.49", + "91.227.246.132", + "91.227.246.150", + "91.227.246.167", + "91.227.246.168", + "91.227.246.197", + "91.227.246.224", + "91.227.246.225", + "91.228.132.5", + "91.232.72.30", + "91.232.72.70", + "91.234.39.76", + "91.234.39.98", + "91.234.39.193", + "91.234.39.231", + "91.236.168.144", + "91.236.168.168", + "91.236.169.161", + "91.236.169.162", + "91.236.169.163", + "91.236.169.164", + "91.236.169.165", + "91.236.169.166", + "91.236.169.167", + "91.236.169.168", + "91.236.169.169", + "91.236.169.170", + "91.237.255.130", + "91.237.255.224", + "91.240.60.65", + "91.240.60.66", + "91.240.60.230", + "91.240.61.17", + "91.240.61.45", + "91.240.61.164", + "91.240.61.165", + "91.240.61.166", + "91.240.61.217", + "91.240.61.230", + "91.240.61.238", + "91.240.61.240", + "91.240.61.248", + "91.240.61.254", + "91.240.63.15", + "91.240.63.25", + "91.240.63.59", + "91.240.63.129", + "91.240.63.214", + "91.241.20.98", + "91.241.20.181", + "91.241.20.182", + "91.241.20.183", + "91.241.20.193", + "91.241.21.17", + "91.241.21.66", + "91.241.21.82", + "91.241.21.116", + "91.241.21.117", + "91.241.21.118", + "91.241.21.119", + "91.241.21.137", + "91.241.21.139", + "91.241.21.140", + "91.241.21.142", + "91.241.21.237", + "91.242.44.2", + "91.242.44.96", + "91.242.44.253", + "91.242.45.14", + "91.242.45.37", + "91.242.45.249", + "91.243.127.222", + "91.243.160.13", + "91.243.160.62", + "91.243.160.90", + "91.243.160.104", + "91.243.160.123", + "91.243.160.144", + "91.243.160.180", + "91.243.160.211", + "91.243.161.6", + "91.243.161.35", + "91.243.161.44", + "91.243.161.77", + "91.243.161.124", + "91.243.161.164", + "91.243.161.180", + "91.243.161.188", + "91.243.162.90", + "91.243.164.4", + "91.243.164.87", + "91.243.164.132", + "91.243.164.183", + "91.243.164.188", + "91.243.164.203", + "91.243.164.227", + "91.243.164.241", + "91.243.165.43", + "91.243.165.48", + "91.243.165.59", + "91.243.165.143", + "91.243.165.192", + "91.243.165.196", + "91.243.165.245", + "91.243.168.44", + "91.243.168.106", + "91.243.168.140", + "91.243.168.251", + "91.243.170.15", + "91.243.170.211", + "91.243.170.212", + "91.243.170.213", + "91.243.170.214", + "91.243.171.169", + "91.243.171.173", + "91.243.171.193", + "91.243.171.198", + "91.243.172.27", + "91.243.172.67", + "91.243.172.123", + "91.243.172.165", + "91.243.172.218", + "91.243.172.247", + "91.243.175.129", + "91.243.175.145", + "91.245.228.241", + "91.245.229.2", + "91.245.230.16", + "91.245.230.17", + "91.245.230.25", + "91.245.231.3", + "91.246.31.108", + "91.247.66.10", + "91.247.66.46", + "91.247.66.53", + "91.247.66.66", + "91.247.66.68", + "91.247.66.90", + "91.247.66.95", + "91.247.66.97", + "91.247.66.113", + "91.247.66.138", + "91.247.66.152", + "91.247.66.156", + "91.247.66.157", + "91.247.66.162", + "91.247.66.163", + "91.247.66.164", + "91.247.66.178", + "91.247.66.193", + "91.247.66.201", + "91.247.66.206", + "91.247.66.218", + "91.247.66.235", + "91.247.66.249", + "91.247.67.222", + "91.247.67.237", + "91.247.67.244", + "91.247.67.247", + "91.247.171.167", + "91.247.171.183", + "92.42.207.4", + "92.43.162.37", + "92.43.162.95", + "92.43.163.134", + "92.43.163.179", + "92.114.19.11", + "92.114.26.73", + "92.114.26.125", + "92.114.29.91", + "92.114.29.246", + "92.114.29.247", + "92.114.30.127", + "92.114.48.60", + "92.114.48.97", + "92.114.48.140", + "92.114.48.157", + "92.114.48.171", + "92.114.48.207", + "92.114.48.210", + "92.114.49.78", + "92.114.49.154", + "92.114.49.196", + "92.114.49.243", + "92.114.49.246", + "92.119.57.85", + "92.242.192.176", + "92.242.192.178", + "92.242.198.74", + "92.242.198.202", + "92.242.198.203", + "92.242.198.204", + "92.242.198.206", + "92.242.198.254", + "92.242.202.130", + "92.242.202.186", + "92.242.202.187", + "92.242.202.188", + "92.242.202.189", + "92.242.202.190", + "92.242.204.66", + "92.242.204.67", + "92.242.204.68", + "92.242.204.69", + "92.242.204.70", + "92.242.204.71", + "92.242.204.75", + "92.242.204.150", + "92.242.206.224", + "92.242.207.43", + "92.242.207.68", + "92.242.212.50", + "92.242.212.51", + "92.242.212.99", + "92.242.217.169", + "92.242.217.174", + "92.242.219.202", + "92.242.219.203", + "92.242.219.204", + "92.242.219.205", + "92.242.219.213", + "92.242.220.181", + "92.242.220.188", + "92.242.223.10", + "92.242.223.178", + "92.246.144.10", + "92.246.144.58", + "92.246.144.59", + "92.246.144.130", + "92.246.144.225", + "92.246.145.96", + "92.246.145.164", + "92.246.145.165", + "92.246.145.166", + "92.246.146.12", + "92.246.146.22", + "92.246.146.46", + "92.246.146.74", + "92.246.146.81", + "92.246.146.89", + "92.246.146.110", + "92.246.146.141", + "92.246.146.151", + "92.246.146.202", + "92.246.147.96", + "92.246.147.212", + "92.246.147.225", + "92.249.56.18", + "92.249.56.28", + "93.113.224.82", + "93.113.227.2", + "93.113.233.3", + "93.113.233.4", + "93.113.234.70", + "93.113.234.74", + "93.113.236.13", + "93.113.236.14", + "93.113.236.15", + "93.113.236.17", + "93.114.104.1", + "93.114.104.45", + "93.114.104.91", + "93.114.104.133", + "93.114.104.139", + "93.114.104.154", + "93.114.104.185", + "93.114.104.200", + "93.114.105.11", + "93.114.105.57", + "93.114.105.69", + "93.114.105.88", + "93.114.105.93", + "93.114.105.145", + "93.114.105.153", + "93.114.105.200", + "93.114.105.233", + "93.114.106.51", + "93.114.106.94", + "93.114.106.187", + "93.114.106.188", + "93.114.106.203", + "93.114.108.15", + "93.114.108.58", + "93.114.108.62", + "93.114.108.138", + "93.114.108.140", + "93.114.108.234", + "93.114.108.241", + "93.114.109.56", + "93.114.109.77", + "93.114.109.82", + "93.114.109.137", + "93.114.109.140", + "93.114.109.158", + "93.114.109.204", + "93.114.110.11", + "93.114.110.92", + "93.114.110.123", + "93.114.110.166", + "93.114.110.193", + "93.114.110.196", + "93.114.110.218", + "93.114.110.224", + "93.114.111.49", + "93.114.111.125", + "93.114.111.147", + "93.114.111.216", + "93.114.111.244", + "93.115.120.4", + "93.115.120.98", + "93.115.120.168", + "93.115.120.181", + "93.115.120.231", + "93.115.121.0", + "93.115.121.62", + "93.115.121.68", + "93.115.121.79", + "93.115.121.106", + "93.115.121.161", + "93.115.121.223", + "93.115.121.229", + "93.115.122.8", + "93.115.122.104", + "93.115.122.144", + "93.115.122.171", + "93.115.123.15", + "93.115.123.240", + "93.115.124.114", + "93.115.124.131", + "93.115.124.189", + "93.115.124.191", + "93.115.124.200", + "93.115.124.230", + "93.115.124.236", + "93.115.125.21", + "93.115.125.53", + "93.115.125.106", + "93.115.125.123", + "93.115.125.125", + "93.115.125.157", + "93.115.125.158", + "93.115.125.171", + "93.115.125.179", + "93.115.125.221", + "93.115.125.252", + "93.115.126.1", + "93.115.126.157", + "93.115.126.183", + "93.115.126.223", + "93.115.126.224", + "93.115.126.244", + "93.115.127.71", + "93.115.127.75", + "93.115.127.119", + "93.115.127.220", + "93.115.144.1", + "93.115.144.4", + "93.115.144.13", + "93.115.144.40", + "93.115.144.45", + "93.115.144.50", + "93.115.144.62", + "93.115.144.81", + "93.115.144.88", + "93.115.144.121", + "93.115.144.130", + "93.115.144.134", + "93.115.144.137", + "93.115.144.159", + "93.115.144.167", + "93.115.144.193", + "93.115.144.195", + "93.115.144.210", + "93.115.144.214", + "93.115.144.220", + "93.115.144.234", + "93.115.144.246", + "93.115.145.22", + "93.115.145.25", + "93.115.145.30", + "93.115.145.71", + "93.115.145.83", + "93.115.145.94", + "93.115.145.105", + "93.115.145.125", + "93.115.145.141", + "93.115.145.185", + "93.115.145.198", + "93.115.145.229", + "93.115.145.239", + "93.115.146.4", + "93.115.146.17", + "93.115.146.52", + "93.115.146.53", + "93.115.146.74", + "93.115.146.81", + "93.115.146.178", + "93.115.146.215", + "93.115.146.219", + "93.115.146.234", + "93.115.146.237", + "93.115.147.71", + "93.115.147.113", + "93.115.147.115", + "93.115.147.122", + "93.115.147.126", + "93.115.147.189", + "93.115.147.215", + "93.115.147.218", + "93.115.147.225", + "93.115.147.230", + "93.115.148.16", + "93.115.148.27", + "93.115.148.29", + "93.115.148.47", + "93.115.148.64", + "93.115.148.66", + "93.115.148.82", + "93.115.148.102", + "93.115.148.129", + "93.115.148.136", + "93.115.148.156", + "93.115.148.165", + "93.115.148.186", + "93.115.148.189", + "93.115.148.207", + "93.115.148.211", + "93.115.148.237", + "93.115.148.248", + "93.115.149.8", + "93.115.149.10", + "93.115.149.29", + "93.115.149.49", + "93.115.149.54", + "93.115.149.57", + "93.115.149.58", + "93.115.149.75", + "93.115.149.86", + "93.115.149.92", + "93.115.149.136", + "93.115.149.166", + "93.115.149.173", + "93.115.149.178", + "93.115.149.198", + "93.115.149.207", + "93.115.149.230", + "93.115.150.30", + "93.115.151.130", + "93.115.151.131", + "93.115.151.132", + "93.115.151.133", + "93.115.151.136", + "93.115.151.137", + "93.115.151.139", + "93.115.151.140", + "93.115.151.141", + "93.115.151.143", + "93.115.151.144", + "93.115.151.148", + "93.115.151.153", + "93.115.151.155", + "93.115.151.157", + "93.115.151.158", + "93.115.151.160", + "93.115.151.161", + "93.115.151.162", + "93.115.151.163", + "93.115.151.164", + "93.115.151.166", + "93.115.151.170", + "93.115.151.180", + "93.115.151.181", + "93.115.151.182", + "93.115.151.183", + "93.115.151.184", + "93.115.151.185", + "93.115.151.186", + "93.115.151.188", + "93.115.151.189", + "93.115.151.190", + "93.117.0.57", + "93.117.0.248", + "93.117.4.197", + "93.117.7.237", + "93.117.10.108", + "93.117.11.67", + "93.117.13.70", + "93.117.14.55", + "93.117.16.212", + "93.117.19.20", + "93.117.20.234", + "93.117.23.104", + "93.117.23.251", + "93.117.24.200", + "93.117.28.7", + "93.117.28.137", + "93.117.31.137", + "93.117.33.30", + "93.117.33.209", + "93.117.33.239", + "93.117.34.68", + "93.117.35.213", + "93.117.36.21", + "93.117.36.179", + "93.117.37.169", + "93.117.38.3", + "93.117.39.183", + "93.117.40.249", + "93.117.42.5", + "93.117.42.113", + "93.117.42.253", + "93.117.43.51", + "93.117.43.72", + "93.117.44.18", + "93.117.44.23", + "93.117.44.93", + "93.117.44.135", + "93.117.46.170", + "93.117.46.171", + "93.117.46.214", + "93.117.97.109", + "93.117.127.70", + "93.117.127.73", + "93.117.127.91", + "93.117.127.107", + "93.117.127.164", + "93.117.127.218", + "93.118.96.24", + "93.118.96.35", + "93.118.96.68", + "93.118.96.188", + "93.118.96.210", + "93.118.97.33", + "93.118.97.63", + "93.118.97.88", + "93.118.97.92", + "93.118.97.121", + "93.118.97.135", + "93.118.97.147", + "93.118.98.13", + "93.118.98.90", + "93.118.98.119", + "93.118.98.244", + "93.118.99.20", + "93.118.99.31", + "93.118.99.74", + "93.118.99.150", + "93.118.100.139", + "93.118.100.158", + "93.118.101.10", + "93.118.101.25", + "93.118.101.48", + "93.118.101.94", + "93.118.101.129", + "93.118.101.139", + "93.118.101.153", + "93.118.101.154", + "93.118.101.155", + "93.118.101.179", + "93.118.101.232", + "93.118.101.243", + "93.118.102.6", + "93.118.103.45", + "93.118.103.208", + "93.118.105.62", + "93.118.105.139", + "93.118.105.144", + "93.118.105.170", + "93.118.105.178", + "93.118.106.95", + "93.118.106.99", + "93.118.106.182", + "93.118.107.84", + "93.118.108.18", + "93.118.108.32", + "93.118.108.90", + "93.118.108.146", + "93.118.108.177", + "93.118.108.216", + "93.118.108.225", + "93.118.108.232", + "93.118.109.52", + "93.118.109.58", + "93.118.109.61", + "93.118.109.82", + "93.118.109.117", + "93.118.109.127", + "93.118.109.140", + "93.118.109.167", + "93.118.109.200", + "93.118.110.10", + "93.118.110.24", + "93.118.110.43", + "93.118.110.73", + "93.118.110.100", + "93.118.110.156", + "93.118.110.190", + "93.118.110.215", + "93.118.110.216", + "93.118.110.231", + "93.118.110.235", + "93.118.111.49", + "93.118.111.64", + "93.118.111.109", + "93.118.112.17", + "93.118.112.19", + "93.118.112.31", + "93.118.112.46", + "93.118.112.62", + "93.118.112.102", + "93.118.112.145", + "93.118.112.161", + "93.118.112.191", + "93.118.112.236", + "93.118.113.23", + "93.118.113.133", + "93.118.113.152", + "93.118.113.224", + "93.118.114.59", + "93.118.114.127", + "93.118.114.138", + "93.118.114.165", + "93.118.114.196", + "93.118.114.240", + "93.118.115.13", + "93.118.115.43", + "93.118.115.45", + "93.118.115.108", + "93.118.115.112", + "93.118.115.131", + "93.118.115.138", + "93.118.115.143", + "93.118.115.171", + "93.118.115.207", + "93.118.115.216", + "93.118.115.234", + "93.118.115.236", + "93.118.115.240", + "93.118.116.33", + "93.118.116.37", + "93.118.116.66", + "93.118.116.127", + "93.118.116.138", + "93.118.116.174", + "93.118.116.239", + "93.118.116.247", + "93.118.117.31", + "93.118.117.50", + "93.118.117.97", + "93.118.117.122", + "93.118.117.129", + "93.118.117.177", + "93.118.119.87", + "93.118.119.110", + "93.118.119.117", + "93.118.119.169", + "93.118.119.223", + "93.118.120.5", + "93.118.120.93", + "93.118.120.119", + "93.118.120.133", + "93.118.120.139", + "93.118.120.148", + "93.118.120.207", + "93.118.120.214", + "93.118.121.32", + "93.118.122.17", + "93.118.122.34", + "93.118.122.126", + "93.118.122.154", + "93.118.123.9", + "93.118.123.11", + "93.118.123.70", + "93.118.123.83", + "93.118.123.101", + "93.118.123.121", + "93.118.123.155", + "93.118.123.176", + "93.118.123.177", + "93.118.123.196", + "93.118.123.211", + "93.118.123.218", + "93.118.123.229", + "93.118.124.38", + "93.118.124.182", + "93.118.124.254", + "93.118.125.5", + "93.118.125.28", + "93.118.125.34", + "93.118.125.143", + "93.118.125.201", + "93.118.125.205", + "93.118.126.23", + "93.118.126.41", + "93.118.126.100", + "93.118.126.107", + "93.118.126.170", + "93.118.126.205", + "93.118.126.229", + "93.118.126.251", + "93.118.127.10", + "93.118.127.49", + "93.118.127.54", + "93.118.127.60", + "93.118.127.182", + "93.118.127.206", + "93.118.127.230", + "93.118.127.249", + "93.118.128.27", + "93.118.128.37", + "93.118.128.38", + "93.118.128.56", + "93.118.128.66", + "93.118.128.101", + "93.118.128.104", + "93.118.128.124", + "93.118.129.28", + "93.118.129.96", + "93.118.129.191", + "93.118.130.149", + "93.118.130.183", + "93.118.130.185", + "93.118.130.243", + "93.118.131.12", + "93.118.131.106", + "93.118.132.12", + "93.118.132.41", + "93.118.132.213", + "93.118.133.12", + "93.118.133.69", + "93.118.133.170", + "93.118.133.198", + "93.118.133.212", + "93.118.134.13", + "93.118.135.61", + "93.118.135.97", + "93.118.135.103", + "93.118.135.170", + "93.118.135.225", + "93.118.136.25", + "93.118.136.36", + "93.118.136.44", + "93.118.136.83", + "93.118.136.169", + "93.118.136.203", + "93.118.136.207", + "93.118.136.232", + "93.118.137.14", + "93.118.137.38", + "93.118.137.49", + "93.118.137.65", + "93.118.137.91", + "93.118.137.98", + "93.118.137.221", + "93.118.138.36", + "93.118.138.52", + "93.118.138.73", + "93.118.138.88", + "93.118.138.114", + "93.118.138.223", + "93.118.138.246", + "93.118.139.36", + "93.118.139.84", + "93.118.139.90", + "93.118.139.97", + "93.118.139.130", + "93.118.139.144", + "93.118.139.145", + "93.118.139.151", + "93.118.139.155", + "93.118.139.210", + "93.118.139.246", + "93.118.140.18", + "93.118.140.56", + "93.118.140.70", + "93.118.140.102", + "93.118.140.148", + "93.118.140.152", + "93.118.140.196", + "93.118.140.209", + "93.118.140.211", + "93.118.140.224", + "93.118.140.225", + "93.118.141.27", + "93.118.141.188", + "93.118.141.201", + "93.118.141.232", + "93.118.141.252", + "93.118.142.209", + "93.118.142.222", + "93.118.143.2", + "93.118.143.9", + "93.118.143.64", + "93.118.143.67", + "93.118.143.86", + "93.118.143.89", + "93.118.143.95", + "93.118.143.97", + "93.118.143.111", + "93.118.143.112", + "93.118.143.143", + "93.118.143.155", + "93.118.143.195", + "93.118.143.211", + "93.118.143.226", + "93.118.143.231", + "93.118.144.0", + "93.118.144.16", + "93.118.144.41", + "93.118.144.74", + "93.118.144.114", + "93.118.144.119", + "93.118.144.124", + "93.118.144.141", + "93.118.144.168", + "93.118.144.228", + "93.118.144.242", + "93.118.145.25", + "93.118.145.213", + "93.118.145.245", + "93.118.145.248", + "93.118.146.59", + "93.118.146.88", + "93.118.146.176", + "93.118.146.253", + "93.118.147.14", + "93.118.147.158", + "93.118.147.176", + "93.118.147.180", + "93.118.147.193", + "93.118.148.1", + "93.118.148.7", + "93.118.148.26", + "93.118.148.30", + "93.118.148.34", + "93.118.148.70", + "93.118.148.118", + "93.118.148.140", + "93.118.148.189", + "93.118.148.199", + "93.118.149.228", + "93.118.150.42", + "93.118.151.217", + "93.118.151.236", + "93.118.151.249", + "93.118.152.46", + "93.118.152.81", + "93.118.152.168", + "93.118.152.177", + "93.118.152.247", + "93.118.152.253", + "93.118.153.92", + "93.118.153.104", + "93.118.153.108", + "93.118.153.109", + "93.118.153.110", + "93.118.153.119", + "93.118.153.126", + "93.118.153.131", + "93.118.153.137", + "93.118.153.157", + "93.118.153.229", + "93.118.153.252", + "93.118.154.12", + "93.118.154.56", + "93.118.154.174", + "93.118.154.188", + "93.118.154.205", + "93.118.154.225", + "93.118.154.226", + "93.118.155.66", + "93.118.155.69", + "93.118.155.100", + "93.118.155.109", + "93.118.156.42", + "93.118.156.46", + "93.118.156.78", + "93.118.156.149", + "93.118.156.176", + "93.118.157.57", + "93.118.157.146", + "93.118.157.210", + "93.118.157.223", + "93.118.158.0", + "93.118.158.20", + "93.118.158.37", + "93.118.158.78", + "93.118.158.92", + "93.118.158.166", + "93.118.158.172", + "93.118.158.204", + "93.118.159.64", + "93.118.159.114", + "93.118.159.149", + "93.118.159.200", + "93.118.160.27", + "93.118.160.109", + "93.118.160.123", + "93.118.160.175", + "93.118.160.189", + "93.118.160.213", + "93.118.161.8", + "93.118.161.30", + "93.118.161.39", + "93.118.161.69", + "93.118.161.71", + "93.118.161.168", + "93.118.161.225", + "93.118.161.228", + "93.118.161.233", + "93.118.162.13", + "93.118.162.161", + "93.118.163.9", + "93.118.163.140", + "93.118.163.146", + "93.118.163.230", + "93.118.163.252", + "93.118.164.28", + "93.118.164.41", + "93.118.164.59", + "93.118.164.183", + "93.118.164.199", + "93.118.165.55", + "93.118.165.136", + "93.118.165.182", + "93.118.166.36", + "93.118.166.62", + "93.118.166.73", + "93.118.166.131", + "93.118.166.150", + "93.118.166.193", + "93.118.166.235", + "93.118.166.245", + "93.118.167.33", + "93.118.167.58", + "93.118.167.64", + "93.118.167.147", + "93.118.167.152", + "93.118.167.176", + "93.118.167.208", + "93.118.167.219", + "93.118.167.223", + "93.118.167.236", + "93.118.180.129", + "93.118.180.245", + "93.118.181.27", + "93.118.181.112", + "93.118.181.114", + "93.118.181.149", + "93.118.181.225", + "93.118.181.238", + "93.118.182.9", + "93.118.182.21", + "93.118.182.35", + "93.118.182.63", + "93.118.183.167", + "93.118.183.188", + "93.119.54.205", + "93.126.2.252", + "93.126.3.22", + "93.126.3.98", + "93.126.3.229", + "93.126.4.73", + "93.126.5.81", + "93.126.5.94", + "93.126.5.106", + "93.126.5.127", + "93.126.6.39", + "93.126.6.61", + "93.126.7.168", + "93.126.8.121", + "93.126.9.1", + "93.126.9.2", + "93.126.9.3", + "93.126.9.10", + "93.126.9.57", + "93.126.9.86", + "93.126.9.153", + "93.126.9.166", + "93.126.9.186", + "93.126.9.233", + "93.126.9.234", + "93.126.9.235", + "93.126.9.236", + "93.126.9.237", + "93.126.9.238", + "93.126.10.17", + "93.126.10.70", + "93.126.11.234", + "93.126.12.22", + "93.126.12.254", + "93.126.13.84", + "93.126.13.132", + "93.126.15.129", + "93.126.17.130", + "93.126.19.246", + "93.126.20.107", + "93.126.21.4", + "93.126.21.57", + "93.126.21.58", + "93.126.21.117", + "93.126.25.33", + "93.126.29.14", + "93.126.29.44", + "93.126.29.53", + "93.126.29.64", + "93.126.29.96", + "93.126.29.108", + "93.126.29.109", + "93.126.31.140", + "93.126.31.161", + "93.126.34.81", + "93.126.40.7", + "93.126.40.17", + "93.126.40.18", + "93.126.40.19", + "93.126.40.22", + "93.126.40.23", + "93.126.40.24", + "93.126.40.30", + "93.126.40.67", + "93.126.40.68", + "93.126.40.69", + "93.126.40.70", + "93.126.40.71", + "93.126.40.72", + "93.126.40.73", + "93.126.40.76", + "93.126.41.0", + "93.126.41.1", + "93.126.41.2", + "93.126.41.3", + "93.126.41.4", + "93.126.41.6", + "93.126.41.7", + "93.126.41.8", + "93.126.41.9", + "93.126.41.11", + "93.126.41.12", + "93.126.41.13", + "93.126.41.14", + "93.126.41.15", + "93.126.41.16", + "93.126.41.17", + "93.126.41.18", + "93.126.41.19", + "93.126.41.20", + "93.126.41.23", + "93.126.41.24", + "93.126.41.26", + "93.126.41.27", + "93.126.41.28", + "93.126.41.29", + "93.126.41.30", + "93.126.41.31", + "93.126.41.34", + "93.126.41.35", + "93.126.41.36", + "93.126.41.37", + "93.126.41.38", + "93.126.41.39", + "93.126.41.40", + "93.126.41.41", + "93.126.41.42", + "93.126.41.44", + "93.126.41.45", + "93.126.41.46", + "93.126.41.47", + "93.126.41.48", + "93.126.41.49", + "93.126.41.50", + "93.126.41.51", + "93.126.41.52", + "93.126.41.53", + "93.126.41.55", + "93.126.41.56", + "93.126.41.57", + "93.126.41.58", + "93.126.41.60", + "93.126.41.61", + "93.126.41.62", + "93.126.41.63", + "93.126.41.64", + "93.126.41.65", + "93.126.41.66", + "93.126.41.67", + "93.126.41.68", + "93.126.41.69", + "93.126.41.70", + "93.126.41.71", + "93.126.41.72", + "93.126.41.73", + "93.126.41.74", + "93.126.41.76", + "93.126.41.77", + "93.126.41.78", + "93.126.41.80", + "93.126.41.81", + "93.126.41.82", + "93.126.41.83", + "93.126.41.84", + "93.126.41.85", + "93.126.41.86", + "93.126.41.87", + "93.126.41.88", + "93.126.41.89", + "93.126.41.90", + "93.126.41.91", + "93.126.41.92", + "93.126.41.93", + "93.126.41.95", + "93.126.41.96", + "93.126.41.97", + "93.126.41.99", + "93.126.41.100", + "93.126.41.101", + "93.126.41.102", + "93.126.41.103", + "93.126.41.104", + "93.126.41.105", + "93.126.41.106", + "93.126.41.107", + "93.126.41.108", + "93.126.41.109", + "93.126.41.110", + "93.126.41.111", + "93.126.41.112", + "93.126.41.113", + "93.126.41.114", + "93.126.41.115", + "93.126.41.116", + "93.126.41.117", + "93.126.41.118", + "93.126.41.119", + "93.126.41.120", + "93.126.41.121", + "93.126.41.122", + "93.126.41.123", + "93.126.41.124", + "93.126.41.125", + "93.126.41.126", + "93.126.41.127", + "93.126.41.128", + "93.126.41.129", + "93.126.41.130", + "93.126.41.131", + "93.126.41.132", + "93.126.41.133", + "93.126.41.134", + "93.126.41.135", + "93.126.41.136", + "93.126.41.137", + "93.126.41.138", + "93.126.41.139", + "93.126.41.140", + "93.126.41.141", + "93.126.41.142", + "93.126.41.143", + "93.126.41.144", + "93.126.41.145", + "93.126.41.147", + "93.126.41.148", + "93.126.41.149", + "93.126.41.150", + "93.126.41.151", + "93.126.41.153", + "93.126.41.154", + "93.126.41.155", + "93.126.41.156", + "93.126.41.157", + "93.126.41.158", + "93.126.41.159", + "93.126.41.160", + "93.126.41.161", + "93.126.41.163", + "93.126.41.164", + "93.126.41.165", + "93.126.41.166", + "93.126.41.167", + "93.126.41.168", + "93.126.41.169", + "93.126.41.170", + "93.126.41.171", + "93.126.41.172", + "93.126.41.173", + "93.126.41.174", + "93.126.41.175", + "93.126.41.176", + "93.126.41.177", + "93.126.41.178", + "93.126.41.179", + "93.126.41.180", + "93.126.41.181", + "93.126.41.182", + "93.126.41.183", + "93.126.41.184", + "93.126.41.185", + "93.126.41.186", + "93.126.41.187", + "93.126.41.188", + "93.126.41.189", + "93.126.41.190", + "93.126.41.192", + "93.126.41.193", + "93.126.41.194", + "93.126.41.195", + "93.126.41.196", + "93.126.41.198", + "93.126.41.200", + "93.126.41.201", + "93.126.41.202", + "93.126.41.203", + "93.126.41.204", + "93.126.41.205", + "93.126.41.207", + "93.126.41.208", + "93.126.41.210", + "93.126.41.211", + "93.126.41.212", + "93.126.41.213", + "93.126.41.214", + "93.126.41.215", + "93.126.41.216", + "93.126.41.217", + "93.126.41.218", + "93.126.41.219", + "93.126.41.220", + "93.126.41.221", + "93.126.41.222", + "93.126.41.223", + "93.126.41.225", + "93.126.41.226", + "93.126.41.227", + "93.126.41.228", + "93.126.41.229", + "93.126.41.230", + "93.126.41.231", + "93.126.41.232", + "93.126.41.233", + "93.126.41.234", + "93.126.41.235", + "93.126.41.236", + "93.126.41.237", + "93.126.41.238", + "93.126.41.239", + "93.126.44.161", + "93.126.44.162", + "93.126.44.163", + "93.126.44.164", + "93.126.44.165", + "93.126.44.166", + "93.126.44.167", + "93.126.44.169", + "93.126.44.170", + "93.126.49.151", + "93.126.51.142", + "93.126.51.144", + "93.126.51.179", + "93.126.57.4", + "93.126.58.43", + "93.126.58.69", + "93.126.58.245", + "94.74.128.169", + "94.74.128.185", + "94.74.128.251", + "94.74.129.52", + "94.74.130.172", + "94.74.132.135", + "94.74.133.174", + "94.74.133.178", + "94.74.135.180", + "94.74.136.120", + "94.74.136.145", + "94.74.136.187", + "94.74.136.240", + "94.74.138.44", + "94.74.138.54", + "94.74.138.123", + "94.74.138.124", + "94.74.138.146", + "94.74.138.174", + "94.74.138.201", + "94.74.138.210", + "94.74.138.226", + "94.74.139.44", + "94.74.139.149", + "94.74.139.168", + "94.74.139.216", + "94.74.139.252", + "94.74.141.70", + "94.74.141.176", + "94.74.142.165", + "94.74.144.14", + "94.74.144.25", + "94.74.144.31", + "94.74.144.166", + "94.74.145.2", + "94.74.145.28", + "94.74.145.110", + "94.74.146.192", + "94.74.146.200", + "94.74.146.251", + "94.74.149.5", + "94.74.149.71", + "94.74.150.75", + "94.74.150.184", + "94.74.151.34", + "94.74.160.61", + "94.74.160.86", + "94.74.160.88", + "94.74.161.41", + "94.74.161.51", + "94.74.161.101", + "94.74.161.166", + "94.74.161.169", + "94.74.161.237", + "94.74.161.251", + "94.74.163.22", + "94.74.163.185", + "94.74.163.186", + "94.74.165.190", + "94.74.166.42", + "94.74.166.99", + "94.74.167.161", + "94.74.168.200", + "94.74.168.201", + "94.74.168.202", + "94.74.168.203", + "94.74.168.206", + "94.74.169.9", + "94.74.169.10", + "94.74.169.29", + "94.74.169.30", + "94.74.172.64", + "94.74.172.104", + "94.74.172.155", + "94.74.172.173", + "94.74.174.73", + "94.74.174.122", + "94.74.174.232", + "94.74.174.235", + "94.74.175.55", + "94.74.177.157", + "94.74.177.220", + "94.74.178.235", + "94.74.179.219", + "94.74.180.242", + "94.74.183.30", + "94.74.183.32", + "94.74.183.196", + "94.74.186.1", + "94.74.186.9", + "94.74.186.53", + "94.74.186.103", + "94.74.186.187", + "94.74.188.48", + "94.74.188.53", + "94.74.188.151", + "94.74.188.199", + "94.74.188.210", + "94.74.190.92", + "94.101.128.11", + "94.101.128.124", + "94.101.128.174", + "94.101.129.174", + "94.101.131.160", + "94.101.131.176", + "94.101.132.45", + "94.101.133.28", + "94.101.133.133", + "94.101.133.192", + "94.101.140.10", + "94.101.177.11", + "94.101.177.92", + "94.101.178.227", + "94.101.179.5", + "94.101.179.22", + "94.101.179.216", + "94.101.180.83", + "94.101.180.180", + "94.101.184.113", + "94.101.185.66", + "94.101.185.103", + "94.101.185.253", + "94.101.186.9", + "94.101.186.145", + "94.101.187.203", + "94.139.180.63", + "94.139.180.84", + "94.139.180.131", + "94.139.182.140", + "94.139.183.152", + "94.139.183.153", + "94.139.191.205", + "94.139.191.216", + "94.182.0.43", + "94.182.0.131", + "94.182.0.152", + "94.182.0.217", + "94.182.1.188", + "94.182.1.195", + "94.182.1.227", + "94.182.2.14", + "94.182.2.48", + "94.182.2.99", + "94.182.2.110", + "94.182.2.126", + "94.182.2.174", + "94.182.2.193", + "94.182.2.245", + "94.182.10.166", + "94.182.16.81", + "94.182.16.82", + "94.182.16.83", + "94.182.16.84", + "94.182.16.85", + "94.182.16.86", + "94.182.16.87", + "94.182.16.88", + "94.182.16.90", + "94.182.17.163", + "94.182.18.5", + "94.182.18.26", + "94.182.18.46", + "94.182.18.118", + "94.182.18.137", + "94.182.18.150", + "94.182.18.162", + "94.182.18.191", + "94.182.18.207", + "94.182.18.208", + "94.182.18.210", + "94.182.18.225", + "94.182.18.248", + "94.182.18.251", + "94.182.19.97", + "94.182.20.8", + "94.182.20.22", + "94.182.22.18", + "94.182.22.20", + "94.182.22.24", + "94.182.22.26", + "94.182.22.31", + "94.182.26.26", + "94.182.26.28", + "94.182.26.58", + "94.182.26.82", + "94.182.27.58", + "94.182.27.101", + "94.182.27.102", + "94.182.27.103", + "94.182.27.107", + "94.182.27.142", + "94.182.27.230", + "94.182.31.3", + "94.182.31.13", + "94.182.31.25", + "94.182.31.37", + "94.182.31.57", + "94.182.31.61", + "94.182.31.68", + "94.182.31.73", + "94.182.31.91", + "94.182.31.98", + "94.182.31.106", + "94.182.31.112", + "94.182.31.119", + "94.182.31.123", + "94.182.31.126", + "94.182.31.130", + "94.182.31.143", + "94.182.31.148", + "94.182.31.165", + "94.182.31.175", + "94.182.31.180", + "94.182.31.183", + "94.182.31.187", + "94.182.34.59", + "94.182.35.22", + "94.182.35.86", + "94.182.35.150", + "94.182.35.167", + "94.182.35.187", + "94.182.35.201", + "94.182.35.210", + "94.182.35.246", + "94.182.35.247", + "94.182.35.253", + "94.182.36.26", + "94.182.36.121", + "94.182.36.134", + "94.182.36.182", + "94.182.36.192", + "94.182.36.202", + "94.182.36.214", + "94.182.36.225", + "94.182.36.231", + "94.182.36.251", + "94.182.37.9", + "94.182.37.113", + "94.182.37.163", + "94.182.37.213", + "94.182.37.234", + "94.182.38.2", + "94.182.38.74", + "94.182.38.88", + "94.182.38.89", + "94.182.38.90", + "94.182.38.91", + "94.182.38.113", + "94.182.39.195", + "94.182.39.224", + "94.182.39.225", + "94.182.39.226", + "94.182.39.227", + "94.182.39.228", + "94.182.39.229", + "94.182.39.231", + "94.182.39.232", + "94.182.39.233", + "94.182.39.234", + "94.182.39.235", + "94.182.39.236", + "94.182.39.237", + "94.182.39.238", + "94.182.39.239", + "94.182.42.7", + "94.182.42.8", + "94.182.43.2", + "94.182.43.30", + "94.182.43.86", + "94.182.44.157", + "94.182.44.174", + "94.182.46.46", + "94.182.46.197", + "94.182.47.138", + "94.182.48.4", + "94.182.48.11", + "94.182.48.14", + "94.182.48.49", + "94.182.48.50", + "94.182.48.69", + "94.182.48.70", + "94.182.48.71", + "94.182.49.6", + "94.182.49.54", + "94.182.49.106", + "94.182.49.113", + "94.182.49.227", + "94.182.49.232", + "94.182.49.240", + "94.182.50.49", + "94.182.50.50", + "94.182.50.51", + "94.182.50.52", + "94.182.50.53", + "94.182.50.54", + "94.182.50.55", + "94.182.50.60", + "94.182.50.62", + "94.182.50.66", + "94.182.50.67", + "94.182.50.69", + "94.182.50.70", + "94.182.50.73", + "94.182.50.74", + "94.182.50.78", + "94.182.50.94", + "94.182.50.129", + "94.182.50.147", + "94.182.52.49", + "94.182.52.50", + "94.182.52.68", + "94.182.52.72", + "94.182.52.91", + "94.182.52.92", + "94.182.53.66", + "94.182.53.94", + "94.182.53.161", + "94.182.53.162", + "94.182.53.178", + "94.182.53.186", + "94.182.53.189", + "94.182.53.237", + "94.182.53.246", + "94.182.54.2", + "94.182.54.7", + "94.182.54.12", + "94.182.54.38", + "94.182.54.77", + "94.182.54.200", + "94.182.54.202", + "94.182.54.204", + "94.182.54.207", + "94.182.54.218", + "94.182.56.6", + "94.182.56.20", + "94.182.56.33", + "94.182.56.58", + "94.182.56.60", + "94.182.56.254", + "94.182.62.1", + "94.182.83.203", + "94.182.83.215", + "94.182.85.86", + "94.182.87.148", + "94.182.87.213", + "94.182.87.221", + "94.182.91.212", + "94.182.92.46", + "94.182.92.87", + "94.182.95.178", + "94.182.104.157", + "94.182.110.254", + "94.182.129.67", + "94.182.134.138", + "94.182.136.21", + "94.182.136.107", + "94.182.136.163", + "94.182.137.91", + "94.182.146.6", + "94.182.146.7", + "94.182.146.92", + "94.182.149.69", + "94.182.149.80", + "94.182.150.184", + "94.182.155.132", + "94.182.155.135", + "94.182.155.200", + "94.182.156.116", + "94.182.156.188", + "94.182.157.184", + "94.182.168.168", + "94.182.168.220", + "94.182.169.56", + "94.182.169.113", + "94.182.170.150", + "94.182.170.196", + "94.182.170.242", + "94.182.172.195", + "94.182.172.201", + "94.182.172.252", + "94.182.175.3", + "94.182.175.29", + "94.182.175.30", + "94.182.175.57", + "94.182.176.141", + "94.182.176.143", + "94.182.176.144", + "94.182.178.96", + "94.182.178.106", + "94.182.178.139", + "94.182.180.141", + "94.182.183.154", + "94.182.186.227", + "94.182.186.228", + "94.182.192.4", + "94.182.192.30", + "94.182.192.31", + "94.182.192.46", + "94.182.192.50", + "94.182.192.57", + "94.182.192.79", + "94.182.192.85", + "94.182.192.107", + "94.182.192.120", + "94.182.192.139", + "94.182.192.169", + "94.182.192.170", + "94.182.192.173", + "94.182.192.192", + "94.182.192.228", + "94.182.192.231", + "94.182.192.248", + "94.182.192.253", + "94.182.193.3", + "94.182.193.12", + "94.182.193.36", + "94.182.193.40", + "94.182.193.44", + "94.182.193.52", + "94.182.193.76", + "94.182.193.78", + "94.182.193.93", + "94.182.193.98", + "94.182.193.106", + "94.182.193.112", + "94.182.193.113", + "94.182.193.140", + "94.182.193.141", + "94.182.193.155", + "94.182.193.171", + "94.182.193.175", + "94.182.193.183", + "94.182.193.184", + "94.182.193.186", + "94.182.193.189", + "94.182.193.230", + "94.182.193.235", + "94.182.193.238", + "94.182.193.243", + "94.182.193.244", + "94.182.194.73", + "94.182.194.74", + "94.182.194.83", + "94.182.194.98", + "94.182.194.120", + "94.182.194.149", + "94.182.194.151", + "94.182.194.181", + "94.182.194.184", + "94.182.194.185", + "94.182.194.195", + "94.182.194.198", + "94.182.194.206", + "94.182.194.227", + "94.182.194.231", + "94.182.195.14", + "94.182.195.58", + "94.182.195.61", + "94.182.195.160", + "94.182.196.62", + "94.182.196.82", + "94.182.196.238", + "94.182.197.62", + "94.182.197.142", + "94.182.197.153", + "94.182.197.154", + "94.182.197.174", + "94.182.197.198", + "94.182.198.6", + "94.182.198.30", + "94.182.198.34", + "94.182.198.78", + "94.182.198.194", + "94.182.198.214", + "94.182.198.238", + "94.182.199.106", + "94.182.199.226", + "94.182.199.246", + "94.182.199.250", + "94.182.201.9", + "94.182.201.25", + "94.182.201.58", + "94.182.201.59", + "94.182.201.115", + "94.182.202.91", + "94.182.202.185", + "94.182.202.216", + "94.182.202.217", + "94.182.203.114", + "94.182.203.115", + "94.182.203.116", + "94.182.203.117", + "94.182.203.178", + "94.182.209.2", + "94.182.211.62", + "94.182.211.250", + "94.182.211.251", + "94.182.214.179", + "94.182.214.180", + "94.182.214.181", + "94.182.214.183", + "94.182.214.184", + "94.182.214.186", + "94.182.214.187", + "94.182.214.188", + "94.182.214.190", + "94.182.216.1", + "94.182.216.2", + "94.182.219.26", + "94.182.219.54", + "94.182.219.129", + "94.182.219.154", + "94.182.219.162", + "94.182.219.246", + "94.182.219.247", + "94.182.220.6", + "94.182.225.139", + "94.182.225.152", + "94.182.225.164", + "94.182.225.175", + "94.182.225.183", + "94.182.225.231", + "94.182.225.233", + "94.182.225.238", + "94.182.225.254", + "94.182.227.48", + "94.182.227.93", + "94.182.227.139", + "94.182.227.177", + "94.182.250.3", + "94.182.250.4", + "94.182.250.5", + "94.182.250.50", + "94.182.251.115", + "94.182.251.125", + "94.182.251.151", + "94.182.253.71", + "94.182.253.135", + "94.182.254.5", + "94.182.254.117", + "94.182.254.121", + "94.182.254.122", + "94.182.255.139", + "94.182.255.200", + "94.183.0.37", + "94.183.0.46", + "94.183.0.54", + "94.183.0.85", + "94.183.0.97", + "94.183.0.125", + "94.183.0.167", + "94.183.0.241", + "94.183.1.21", + "94.183.1.50", + "94.183.1.98", + "94.183.1.229", + "94.183.1.246", + "94.183.2.47", + "94.183.2.151", + "94.183.2.211", + "94.183.3.130", + "94.183.3.150", + "94.183.3.196", + "94.183.5.95", + "94.183.6.186", + "94.183.6.229", + "94.183.6.245", + "94.183.7.129", + "94.183.7.199", + "94.183.7.207", + "94.183.11.9", + "94.183.11.29", + "94.183.11.36", + "94.183.11.87", + "94.183.11.100", + "94.183.11.160", + "94.183.13.233", + "94.183.14.202", + "94.183.15.43", + "94.183.15.64", + "94.183.15.245", + "94.183.16.15", + "94.183.16.45", + "94.183.17.128", + "94.183.17.141", + "94.183.18.36", + "94.183.20.50", + "94.183.20.147", + "94.183.21.119", + "94.183.22.114", + "94.183.23.1", + "94.183.23.2", + "94.183.23.163", + "94.183.23.207", + "94.183.24.57", + "94.183.24.98", + "94.183.26.10", + "94.183.26.122", + "94.183.26.130", + "94.183.26.132", + "94.183.27.72", + "94.183.27.83", + "94.183.27.96", + "94.183.27.154", + "94.183.27.159", + "94.183.27.223", + "94.183.27.235", + "94.183.28.24", + "94.183.28.60", + "94.183.28.85", + "94.183.28.92", + "94.183.28.102", + "94.183.28.111", + "94.183.28.141", + "94.183.28.159", + "94.183.28.195", + "94.183.28.235", + "94.183.29.4", + "94.183.29.100", + "94.183.29.112", + "94.183.29.113", + "94.183.29.137", + "94.183.29.167", + "94.183.29.187", + "94.183.30.36", + "94.183.30.38", + "94.183.30.42", + "94.183.30.43", + "94.183.30.46", + "94.183.30.65", + "94.183.30.78", + "94.183.30.88", + "94.183.30.107", + "94.183.30.116", + "94.183.30.117", + "94.183.30.128", + "94.183.30.161", + "94.183.30.164", + "94.183.30.167", + "94.183.30.170", + "94.183.30.171", + "94.183.30.199", + "94.183.30.202", + "94.183.30.204", + "94.183.30.212", + "94.183.30.216", + "94.183.31.23", + "94.183.31.63", + "94.183.31.73", + "94.183.31.124", + "94.183.31.156", + "94.183.31.202", + "94.183.31.205", + "94.183.32.25", + "94.183.32.86", + "94.183.33.78", + "94.183.33.240", + "94.183.34.55", + "94.183.34.98", + "94.183.34.232", + "94.183.35.74", + "94.183.35.226", + "94.183.37.2", + "94.183.37.76", + "94.183.38.141", + "94.183.40.253", + "94.183.42.107", + "94.183.44.111", + "94.183.46.108", + "94.183.46.116", + "94.183.48.10", + "94.183.48.15", + "94.183.49.6", + "94.183.49.21", + "94.183.49.41", + "94.183.49.47", + "94.183.49.130", + "94.183.49.206", + "94.183.50.97", + "94.183.50.174", + "94.183.50.187", + "94.183.50.231", + "94.183.51.95", + "94.183.51.144", + "94.183.51.165", + "94.183.51.215", + "94.183.52.51", + "94.183.52.67", + "94.183.52.94", + "94.183.52.215", + "94.183.52.227", + "94.183.53.29", + "94.183.53.99", + "94.183.53.113", + "94.183.53.133", + "94.183.56.163", + "94.183.56.191", + "94.183.56.229", + "94.183.56.251", + "94.183.57.45", + "94.183.57.66", + "94.183.57.215", + "94.183.58.29", + "94.183.58.79", + "94.183.58.188", + "94.183.58.198", + "94.183.59.32", + "94.183.59.75", + "94.183.59.104", + "94.183.59.114", + "94.183.59.197", + "94.183.59.245", + "94.183.60.19", + "94.183.60.28", + "94.183.61.126", + "94.183.61.145", + "94.183.61.218", + "94.183.62.125", + "94.183.62.141", + "94.183.62.222", + "94.183.63.68", + "94.183.67.57", + "94.183.67.84", + "94.183.68.106", + "94.183.70.115", + "94.183.70.140", + "94.183.72.99", + "94.183.72.116", + "94.183.72.164", + "94.183.72.187", + "94.183.72.243", + "94.183.73.76", + "94.183.73.77", + "94.183.73.78", + "94.183.73.111", + "94.183.73.214", + "94.183.73.218", + "94.183.73.246", + "94.183.74.15", + "94.183.74.58", + "94.183.74.78", + "94.183.74.101", + "94.183.74.184", + "94.183.75.49", + "94.183.78.74", + "94.183.80.22", + "94.183.82.7", + "94.183.82.216", + "94.183.83.1", + "94.183.83.19", + "94.183.83.115", + "94.183.83.183", + "94.183.83.211", + "94.183.83.241", + "94.183.84.144", + "94.183.84.236", + "94.183.85.31", + "94.183.85.115", + "94.183.85.139", + "94.183.85.190", + "94.183.85.232", + "94.183.88.131", + "94.183.88.134", + "94.183.90.106", + "94.183.90.166", + "94.183.90.221", + "94.183.90.227", + "94.183.91.32", + "94.183.91.82", + "94.183.91.241", + "94.183.92.64", + "94.183.92.70", + "94.183.92.77", + "94.183.92.84", + "94.183.92.92", + "94.183.92.95", + "94.183.92.112", + "94.183.92.121", + "94.183.92.227", + "94.183.92.232", + "94.183.93.28", + "94.183.93.48", + "94.183.93.51", + "94.183.93.52", + "94.183.93.74", + "94.183.93.103", + "94.183.93.106", + "94.183.93.109", + "94.183.93.117", + "94.183.93.123", + "94.183.93.200", + "94.183.93.210", + "94.183.93.228", + "94.183.93.239", + "94.183.94.29", + "94.183.94.38", + "94.183.94.39", + "94.183.94.40", + "94.183.94.54", + "94.183.94.55", + "94.183.95.114", + "94.183.95.122", + "94.183.95.146", + "94.183.95.171", + "94.183.95.206", + "94.183.95.208", + "94.183.97.22", + "94.183.99.63", + "94.183.99.81", + "94.183.99.111", + "94.183.99.176", + "94.183.99.210", + "94.183.99.218", + "94.183.99.222", + "94.183.99.226", + "94.183.99.237", + "94.183.99.238", + "94.183.100.59", + "94.183.100.202", + "94.183.100.251", + "94.183.102.151", + "94.183.102.226", + "94.183.103.139", + "94.183.104.107", + "94.183.106.110", + "94.183.107.102", + "94.183.108.65", + "94.183.109.224", + "94.183.110.113", + "94.183.110.223", + "94.183.111.6", + "94.183.113.41", + "94.183.113.86", + "94.183.114.81", + "94.183.114.92", + "94.183.115.73", + "94.183.115.167", + "94.183.115.187", + "94.183.115.188", + "94.183.116.18", + "94.183.116.56", + "94.183.116.126", + "94.183.116.220", + "94.183.117.124", + "94.183.117.200", + "94.183.118.6", + "94.183.118.14", + "94.183.118.42", + "94.183.118.107", + "94.183.118.123", + "94.183.118.127", + "94.183.119.28", + "94.183.119.88", + "94.183.119.104", + "94.183.119.202", + "94.183.119.246", + "94.183.120.17", + "94.183.120.34", + "94.183.120.86", + "94.183.120.104", + "94.183.120.224", + "94.183.122.16", + "94.183.122.17", + "94.183.122.64", + "94.183.122.126", + "94.183.122.174", + "94.183.123.128", + "94.183.123.129", + "94.183.123.132", + "94.183.124.16", + "94.183.124.21", + "94.183.124.30", + "94.183.124.45", + "94.183.124.77", + "94.183.124.130", + "94.183.124.140", + "94.183.124.187", + "94.183.124.194", + "94.183.125.56", + "94.183.125.172", + "94.183.125.187", + "94.183.126.39", + "94.183.126.41", + "94.183.126.42", + "94.183.126.111", + "94.183.126.211", + "94.183.127.13", + "94.183.127.27", + "94.183.127.40", + "94.183.127.76", + "94.183.127.100", + "94.183.127.114", + "94.183.127.123", + "94.183.127.163", + "94.183.129.209", + "94.183.132.5", + "94.183.132.100", + "94.183.132.119", + "94.183.132.126", + "94.183.132.128", + "94.183.132.143", + "94.183.132.144", + "94.183.132.174", + "94.183.132.214", + "94.183.133.82", + "94.183.133.113", + "94.183.133.132", + "94.183.133.146", + "94.183.133.159", + "94.183.133.175", + "94.183.133.223", + "94.183.133.235", + "94.183.133.247", + "94.183.134.150", + "94.183.134.153", + "94.183.134.202", + "94.183.134.255", + "94.183.135.140", + "94.183.138.109", + "94.183.139.136", + "94.183.140.110", + "94.183.140.138", + "94.183.141.68", + "94.183.144.185", + "94.183.145.89", + "94.183.145.147", + "94.183.145.155", + "94.183.145.214", + "94.183.146.186", + "94.183.146.203", + "94.183.146.221", + "94.183.147.241", + "94.183.148.65", + "94.183.148.98", + "94.183.148.129", + "94.183.148.184", + "94.183.148.238", + "94.183.149.65", + "94.183.149.85", + "94.183.149.140", + "94.183.149.146", + "94.183.149.171", + "94.183.149.204", + "94.183.149.249", + "94.183.149.252", + "94.183.154.207", + "94.183.156.128", + "94.183.156.137", + "94.183.163.52", + "94.183.163.60", + "94.183.163.75", + "94.183.176.45", + "94.183.180.21", + "94.184.3.203", + "94.184.3.226", + "94.184.3.227", + "94.184.3.230", + "94.184.10.98", + "94.184.16.7", + "94.184.16.178", + "94.184.17.213", + "94.184.18.24", + "94.184.18.44", + "94.184.18.189", + "94.184.21.66", + "94.184.22.6", + "94.184.22.158", + "94.184.23.65", + "94.184.23.243", + "94.184.29.33", + "94.184.29.218", + "94.184.90.225", + "94.184.177.129", + "94.184.208.77", + "94.184.208.102", + "94.199.0.1", + "94.199.0.41", + "94.199.0.60", + "94.199.0.78", + "94.199.0.212", + "94.232.168.162", + "94.232.171.82", + "94.232.171.83", + "94.232.171.84", + "94.232.171.85", + "94.232.171.91", + "94.232.172.109", + "94.232.173.8", + "94.232.173.136", + "94.232.173.192", + "94.232.175.210", + "94.232.175.211", + "95.38.10.9", + "95.38.10.65", + "95.38.10.153", + "95.38.10.195", + "95.38.10.197", + "95.38.10.219", + "95.38.10.230", + "95.38.10.253", + "95.38.11.1", + "95.38.11.26", + "95.38.11.42", + "95.38.11.46", + "95.38.11.84", + "95.38.11.89", + "95.38.11.94", + "95.38.11.95", + "95.38.11.201", + "95.38.11.223", + "95.38.15.205", + "95.38.15.247", + "95.38.20.66", + "95.38.20.67", + "95.38.24.146", + "95.38.25.0", + "95.38.27.16", + "95.38.27.32", + "95.38.27.36", + "95.38.34.30", + "95.38.34.58", + "95.38.34.59", + "95.38.34.60", + "95.38.34.242", + "95.38.35.44", + "95.38.38.75", + "95.38.38.83", + "95.38.39.173", + "95.38.46.84", + "95.38.46.130", + "95.38.47.173", + "95.38.48.253", + "95.38.49.157", + "95.38.50.102", + "95.38.52.218", + "95.38.53.118", + "95.38.60.5", + "95.38.60.11", + "95.38.60.42", + "95.38.60.252", + "95.38.63.47", + "95.38.63.59", + "95.38.63.200", + "95.38.63.234", + "95.38.65.2", + "95.38.65.3", + "95.38.65.5", + "95.38.65.234", + "95.38.66.23", + "95.38.72.23", + "95.38.75.75", + "95.38.75.79", + "95.38.76.42", + "95.38.76.160", + "95.38.77.190", + "95.38.78.69", + "95.38.78.239", + "95.38.86.50", + "95.38.86.51", + "95.38.86.52", + "95.38.86.53", + "95.38.86.54", + "95.38.86.55", + "95.38.86.56", + "95.38.86.59", + "95.38.86.60", + "95.38.86.61", + "95.38.86.62", + "95.38.89.16", + "95.38.89.17", + "95.38.89.18", + "95.38.89.19", + "95.38.89.21", + "95.38.89.77", + "95.38.89.78", + "95.38.89.80", + "95.38.89.90", + "95.38.89.95", + "95.38.89.107", + "95.38.89.112", + "95.38.89.192", + "95.38.89.199", + "95.38.94.14", + "95.38.94.21", + "95.38.94.218", + "95.38.96.230", + "95.38.99.146", + "95.38.102.89", + "95.38.103.70", + "95.38.103.218", + "95.38.103.254", + "95.38.105.66", + "95.38.105.71", + "95.38.105.122", + "95.38.106.146", + "95.38.123.254", + "95.38.130.108", + "95.38.131.28", + "95.38.131.44", + "95.38.131.59", + "95.38.131.81", + "95.38.131.93", + "95.38.131.95", + "95.38.131.110", + "95.38.131.143", + "95.38.131.144", + "95.38.131.159", + "95.38.131.163", + "95.38.131.194", + "95.38.131.199", + "95.38.131.216", + "95.38.131.243", + "95.38.131.247", + "95.38.131.248", + "95.38.134.24", + "95.38.134.26", + "95.38.134.50", + "95.38.134.85", + "95.38.134.190", + "95.38.134.202", + "95.38.135.12", + "95.38.135.140", + "95.38.138.38", + "95.38.138.154", + "95.38.138.174", + "95.38.138.202", + "95.38.142.34", + "95.38.142.62", + "95.38.144.16", + "95.38.144.17", + "95.38.144.26", + "95.38.144.41", + "95.38.144.127", + "95.38.144.158", + "95.38.144.178", + "95.38.144.203", + "95.38.145.12", + "95.38.145.68", + "95.38.145.73", + "95.38.145.74", + "95.38.145.78", + "95.38.145.95", + "95.38.146.86", + "95.38.146.202", + "95.38.146.217", + "95.38.147.86", + "95.38.147.133", + "95.38.147.134", + "95.38.147.190", + "95.38.148.15", + "95.38.148.59", + "95.38.148.163", + "95.38.148.171", + "95.38.148.198", + "95.38.149.2", + "95.38.149.176", + "95.38.153.226", + "95.38.155.50", + "95.38.155.74", + "95.38.156.45", + "95.38.156.53", + "95.38.156.56", + "95.38.156.60", + "95.38.157.90", + "95.38.157.120", + "95.38.159.194", + "95.38.159.254", + "95.38.169.118", + "95.38.169.154", + "95.38.169.202", + "95.38.169.222", + "95.38.170.16", + "95.38.170.17", + "95.38.174.38", + "95.38.174.65", + "95.38.174.81", + "95.38.174.83", + "95.38.174.85", + "95.38.174.87", + "95.38.174.106", + "95.38.174.226", + "95.38.192.99", + "95.38.192.140", + "95.38.192.167", + "95.38.192.183", + "95.38.196.129", + "95.38.196.134", + "95.38.196.190", + "95.38.196.216", + "95.38.197.134", + "95.38.197.254", + "95.38.199.0", + "95.38.199.10", + "95.38.199.18", + "95.38.201.30", + "95.38.201.114", + "95.38.201.201", + "95.38.201.211", + "95.38.203.178", + "95.38.209.226", + "95.38.209.227", + "95.38.209.229", + "95.38.209.230", + "95.38.209.231", + "95.38.209.232", + "95.38.209.233", + "95.38.209.234", + "95.38.209.235", + "95.38.211.243", + "95.38.211.250", + "95.38.224.86", + "95.38.224.151", + "95.38.224.202", + "95.38.224.218", + "95.38.241.65", + "95.38.243.46", + "95.38.243.250", + "95.38.244.50", + "95.38.244.114", + "95.38.244.115", + "95.38.244.120", + "95.38.244.121", + "95.38.244.122", + "95.38.244.124", + "95.38.244.126", + "95.38.245.14", + "95.38.245.33", + "95.38.245.58", + "95.38.245.62", + "95.38.245.146", + "95.38.245.147", + "95.38.245.148", + "95.38.245.149", + "95.38.245.150", + "95.38.246.19", + "95.38.248.184", + "95.38.248.194", + "95.38.248.246", + "95.38.248.250", + "95.38.250.184", + "95.38.250.195", + "95.38.250.230", + "95.80.140.2", + "95.80.140.26", + "95.80.140.32", + "95.80.140.53", + "95.80.140.64", + "95.80.140.80", + "95.80.140.91", + "95.80.140.92", + "95.80.140.110", + "95.80.140.126", + "95.80.140.159", + "95.80.140.162", + "95.80.140.205", + "95.80.140.210", + "95.80.140.229", + "95.80.140.233", + "95.80.140.238", + "95.80.141.2", + "95.80.141.12", + "95.80.141.15", + "95.80.141.18", + "95.80.141.37", + "95.80.141.48", + "95.80.141.71", + "95.80.141.74", + "95.80.141.81", + "95.80.141.84", + "95.80.141.103", + "95.80.141.110", + "95.80.141.112", + "95.80.141.117", + "95.80.141.124", + "95.80.141.142", + "95.80.141.150", + "95.80.141.151", + "95.80.141.162", + "95.80.141.167", + "95.80.141.176", + "95.80.141.190", + "95.80.141.214", + "95.80.141.220", + "95.80.141.238", + "95.80.141.253", + "95.80.142.4", + "95.80.142.9", + "95.80.142.29", + "95.80.142.33", + "95.80.142.42", + "95.80.142.54", + "95.80.142.57", + "95.80.142.81", + "95.80.142.96", + "95.80.142.102", + "95.80.142.106", + "95.80.142.117", + "95.80.142.124", + "95.80.142.140", + "95.80.142.150", + "95.80.142.167", + "95.80.142.177", + "95.80.142.199", + "95.80.142.217", + "95.80.142.224", + "95.80.142.228", + "95.80.142.244", + "95.80.142.248", + "95.80.143.1", + "95.80.143.11", + "95.80.143.28", + "95.80.143.31", + "95.80.143.45", + "95.80.143.82", + "95.80.143.83", + "95.80.143.119", + "95.80.143.134", + "95.80.143.137", + "95.80.143.138", + "95.80.143.151", + "95.80.143.160", + "95.80.143.163", + "95.80.143.169", + "95.80.143.183", + "95.80.143.198", + "95.80.143.224", + "95.80.157.65", + "95.80.159.242", + "95.80.160.58", + "95.80.160.66", + "95.80.160.180", + "95.80.161.130", + "95.80.168.12", + "95.80.168.22", + "95.80.168.56", + "95.80.168.63", + "95.80.168.69", + "95.80.168.90", + "95.80.168.115", + "95.80.168.151", + "95.80.168.161", + "95.80.168.194", + "95.80.168.203", + "95.80.168.221", + "95.80.168.233", + "95.80.169.1", + "95.80.169.8", + "95.80.169.14", + "95.80.169.20", + "95.80.169.48", + "95.80.169.61", + "95.80.169.62", + "95.80.169.78", + "95.80.169.101", + "95.80.169.131", + "95.80.169.138", + "95.80.169.152", + "95.80.169.177", + "95.80.169.185", + "95.80.169.190", + "95.80.169.191", + "95.80.169.198", + "95.80.169.215", + "95.80.170.4", + "95.80.170.24", + "95.80.170.25", + "95.80.170.36", + "95.80.170.42", + "95.80.170.53", + "95.80.170.81", + "95.80.170.95", + "95.80.170.104", + "95.80.170.125", + "95.80.170.133", + "95.80.170.134", + "95.80.170.142", + "95.80.170.167", + "95.80.170.176", + "95.80.170.180", + "95.80.170.225", + "95.80.170.251", + "95.80.171.12", + "95.80.171.23", + "95.80.171.32", + "95.80.171.93", + "95.80.171.94", + "95.80.171.113", + "95.80.171.122", + "95.80.171.141", + "95.80.171.168", + "95.80.171.197", + "95.80.171.216", + "95.80.171.249", + "95.80.172.18", + "95.80.172.36", + "95.80.172.55", + "95.80.172.88", + "95.80.172.123", + "95.80.172.128", + "95.80.172.138", + "95.80.172.140", + "95.80.172.157", + "95.80.172.165", + "95.80.172.191", + "95.80.172.222", + "95.80.172.242", + "95.80.172.248", + "95.80.172.250", + "95.80.173.89", + "95.80.173.99", + "95.80.173.137", + "95.80.173.141", + "95.80.173.146", + "95.80.173.156", + "95.80.173.158", + "95.80.173.168", + "95.80.173.228", + "95.80.173.236", + "95.80.173.244", + "95.80.173.253", + "95.80.174.2", + "95.80.174.3", + "95.80.174.12", + "95.80.174.19", + "95.80.174.83", + "95.80.174.100", + "95.80.174.109", + "95.80.174.152", + "95.80.174.160", + "95.80.174.180", + "95.80.174.181", + "95.80.174.187", + "95.80.174.191", + "95.80.174.196", + "95.80.174.200", + "95.80.174.214", + "95.80.174.222", + "95.80.174.229", + "95.80.174.237", + "95.80.175.21", + "95.80.175.25", + "95.80.175.33", + "95.80.175.34", + "95.80.175.57", + "95.80.175.64", + "95.80.175.93", + "95.80.175.99", + "95.80.175.127", + "95.80.175.167", + "95.80.175.184", + "95.80.175.205", + "95.80.175.228", + "95.80.175.235", + "95.80.190.10", + "95.80.190.11", + "95.130.56.214", + "95.130.58.25", + "95.130.58.40", + "95.130.60.34", + "95.130.62.46", + "95.130.225.12", + "95.130.225.65", + "95.142.226.13", + "95.142.237.244", + "95.156.236.25", + "95.156.236.27", + "95.156.236.58", + "95.156.236.59", + "95.156.236.60", + "95.156.253.34", + "95.156.253.51", + "95.215.59.140", + "95.215.160.1", + "95.215.160.22", + "95.215.160.62", + "95.215.160.108", + "95.215.160.126", + "95.215.160.157", + "95.215.160.195", + "95.215.160.217", + "95.215.160.223", + "95.215.161.106", + "95.215.161.125", + "95.215.161.137", + "95.215.161.187", + "95.215.161.231", + "95.215.161.233", + "95.215.161.246", + "103.215.220.216", + "103.215.223.64", + "103.215.223.91", + "103.216.61.171", + "103.216.63.202", + "103.216.63.203", + "103.216.63.204", + "103.231.136.0", + "103.231.136.255", + "103.231.137.0", + "103.231.137.255", + "103.231.138.0", + "103.231.138.1", + "103.231.139.0", + "103.231.139.255", + "109.70.237.25", + "109.70.237.44", + "109.70.237.50", + "109.70.237.67", + "109.70.237.94", + "109.70.237.118", + "109.70.237.123", + "109.70.237.137", + "109.70.237.177", + "109.70.237.207", + "109.70.237.233", + "109.70.237.235", + "109.70.237.245", + "109.72.195.128", + "109.72.195.139", + "109.72.195.159", + "109.72.195.161", + "109.72.195.178", + "109.72.195.184", + "109.72.195.188", + "109.72.195.192", + "109.72.195.203", + "109.72.195.211", + "109.72.195.221", + "109.72.195.223", + "109.72.195.225", + "109.72.195.226", + "109.72.195.230", + "109.72.195.234", + "109.72.195.237", + "109.72.195.239", + "109.72.195.243", + "109.72.195.251", + "109.72.195.255", + "109.72.201.9", + "109.72.201.14", + "109.72.201.17", + "109.72.201.29", + "109.72.201.36", + "109.72.201.44", + "109.72.201.47", + "109.72.201.51", + "109.72.201.54", + "109.72.201.61", + "109.72.201.65", + "109.72.201.66", + "109.72.201.73", + "109.72.201.80", + "109.72.201.81", + "109.72.201.84", + "109.72.201.87", + "109.72.201.115", + "109.72.201.125", + "109.72.201.127", + "109.72.201.128", + "109.72.201.154", + "109.72.201.155", + "109.72.201.163", + "109.72.201.167", + "109.72.201.183", + "109.72.201.187", + "109.72.201.193", + "109.72.201.216", + "109.72.201.223", + "109.72.201.231", + "109.72.201.239", + "109.72.201.242", + "109.74.226.188", + "109.74.228.195", + "109.74.230.245", + "109.74.238.178", + "109.94.164.100", + "109.94.164.170", + "109.95.60.4", + "109.95.60.33", + "109.95.60.38", + "109.95.60.56", + "109.95.60.57", + "109.95.60.59", + "109.95.60.61", + "109.95.60.77", + "109.95.60.97", + "109.95.60.139", + "109.95.60.149", + "109.95.60.242", + "109.95.60.255", + "109.95.61.201", + "109.95.61.204", + "109.95.61.245", + "109.95.61.254", + "109.95.62.134", + "109.95.63.192", + "109.95.67.100", + "109.95.71.25", + "109.109.42.61", + "109.109.43.186", + "109.109.43.199", + "109.109.46.76", + "109.109.52.167", + "109.109.54.104", + "109.109.54.150", + "109.109.55.91", + "109.109.63.58", + "109.122.227.126", + "109.122.228.6", + "109.122.228.152", + "109.122.230.26", + "109.122.231.102", + "109.122.232.193", + "109.122.233.36", + "109.122.236.62", + "109.122.238.53", + "109.122.238.121", + "109.122.240.4", + "109.122.240.6", + "109.122.240.16", + "109.122.240.17", + "109.122.240.19", + "109.122.240.20", + "109.122.240.22", + "109.122.245.53", + "109.122.245.58", + "109.125.128.28", + "109.125.128.59", + "109.125.128.166", + "109.125.128.236", + "109.125.129.72", + "109.125.129.84", + "109.125.129.170", + "109.125.129.226", + "109.125.130.3", + "109.125.130.50", + "109.125.130.111", + "109.125.130.176", + "109.125.131.80", + "109.125.131.90", + "109.125.131.101", + "109.125.131.136", + "109.125.131.162", + "109.125.132.73", + "109.125.132.147", + "109.125.132.154", + "109.125.133.22", + "109.125.133.102", + "109.125.133.206", + "109.125.134.30", + "109.125.134.83", + "109.125.134.87", + "109.125.134.254", + "109.125.135.50", + "109.125.135.62", + "109.125.135.160", + "109.125.135.213", + "109.125.136.12", + "109.125.136.149", + "109.125.136.234", + "109.125.136.239", + "109.125.137.32", + "109.125.137.170", + "109.125.137.212", + "109.125.138.89", + "109.125.140.57", + "109.125.140.127", + "109.125.140.150", + "109.125.140.211", + "109.125.141.45", + "109.125.141.57", + "109.125.141.67", + "109.125.141.78", + "109.125.141.148", + "109.125.141.166", + "109.125.141.235", + "109.125.141.246", + "109.125.142.212", + "109.125.143.33", + "109.125.144.51", + "109.125.144.96", + "109.125.144.143", + "109.125.145.79", + "109.125.145.97", + "109.125.146.166", + "109.125.146.173", + "109.125.148.226", + "109.125.160.3", + "109.125.160.7", + "109.125.160.9", + "109.125.160.10", + "109.125.160.65", + "109.125.160.144", + "109.125.160.147", + "109.125.160.160", + "109.125.160.176", + "109.125.160.191", + "109.125.160.205", + "109.125.160.209", + "109.125.160.226", + "109.125.160.252", + "109.125.168.1", + "109.125.168.35", + "109.125.168.41", + "109.125.168.53", + "109.125.168.83", + "109.125.168.119", + "109.125.168.126", + "109.125.168.141", + "109.125.168.218", + "109.125.168.226", + "109.125.168.234", + "109.125.178.101", + "109.125.191.2", + "109.125.191.54", + "109.162.128.193", + "109.162.158.62", + "109.162.158.111", + "109.162.196.66", + "109.162.196.138", + "109.162.197.28", + "109.162.197.65", + "109.162.198.243", + "109.162.230.136", + "109.162.251.14", + "109.162.251.43", + "109.162.251.46", + "109.162.251.110", + "109.162.251.158", + "109.201.0.17", + "109.201.0.58", + "109.201.2.81", + "109.201.8.84", + "109.201.8.85", + "109.201.8.86", + "109.201.13.186", + "109.201.13.187", + "109.201.13.189", + "109.201.13.190", + "109.201.15.10", + "109.201.15.38", + "109.201.15.86", + "109.201.15.161", + "109.201.17.57", + "109.201.19.177", + "109.201.19.213", + "109.201.19.241", + "109.201.19.244", + "109.201.19.246", + "109.201.23.6", + "109.201.23.25", + "109.201.23.41", + "109.201.23.43", + "109.201.23.48", + "109.201.23.63", + "109.201.23.77", + "109.201.23.96", + "109.201.23.138", + "109.201.23.147", + "109.201.23.195", + "109.201.24.138", + "109.201.24.194", + "109.201.24.196", + "109.201.27.79", + "109.201.27.146", + "109.206.254.122", + "109.206.254.123", + "109.206.254.124", + "109.206.254.125", + "109.206.254.126", + "109.206.254.130", + "109.230.72.32", + "109.230.72.53", + "109.230.72.63", + "109.230.72.97", + "109.230.72.98", + "109.230.72.99", + "109.230.72.110", + "109.230.72.137", + "109.230.72.145", + "109.230.72.162", + "109.230.72.172", + "109.230.72.222", + "109.230.72.224", + "109.230.72.226", + "109.230.72.227", + "109.230.72.228", + "109.230.72.231", + "109.230.72.236", + "109.230.72.242", + "109.230.72.243", + "109.230.72.245", + "109.230.73.109", + "109.230.73.151", + "109.230.74.86", + "109.230.74.105", + "109.230.75.51", + "109.230.77.76", + "109.230.77.77", + "109.230.77.78", + "109.230.77.79", + "109.230.78.3", + "109.230.78.13", + "109.230.78.86", + "109.230.78.113", + "109.230.78.114", + "109.230.78.115", + "109.230.78.116", + "109.230.78.117", + "109.230.78.118", + "109.230.78.119", + "109.230.78.120", + "109.230.78.121", + "109.230.78.122", + "109.230.78.123", + "109.230.78.124", + "109.230.78.125", + "109.230.78.126", + "109.230.78.245", + "109.230.79.1", + "109.230.79.2", + "109.230.79.4", + "109.230.79.10", + "109.230.79.198", + "109.230.79.208", + "109.230.79.248", + "109.230.79.250", + "109.230.79.251", + "109.230.80.126", + "109.230.80.186", + "109.230.80.218", + "109.230.80.219", + "109.230.80.220", + "109.230.80.221", + "109.230.80.222", + "109.230.80.242", + "109.230.81.50", + "109.230.81.54", + "109.230.81.98", + "109.230.81.102", + "109.230.81.201", + "109.230.82.70", + "109.230.83.155", + "109.230.83.210", + "109.230.83.212", + "109.230.83.214", + "109.230.83.245", + "109.230.88.22", + "109.230.88.42", + "109.230.88.226", + "109.230.88.234", + "109.230.88.235", + "109.230.88.236", + "109.230.88.237", + "109.230.89.114", + "109.230.89.115", + "109.230.89.116", + "109.230.89.118", + "109.230.89.126", + "109.230.89.139", + "109.230.89.140", + "109.230.89.154", + "109.230.89.155", + "109.230.89.156", + "109.230.89.158", + "109.230.89.210", + "109.230.90.146", + "109.230.90.238", + "109.230.91.226", + "109.230.91.227", + "109.230.91.228", + "109.230.92.98", + "109.230.92.158", + "109.230.92.166", + "109.230.92.174", + "109.230.92.178", + "109.230.92.182", + "109.230.92.242", + "109.230.93.51", + "109.230.93.82", + "109.230.93.138", + "109.230.93.206", + "109.230.94.50", + "109.230.94.130", + "109.230.94.182", + "109.230.95.62", + "109.230.95.82", + "109.230.95.194", + "109.230.95.196", + "109.230.95.208", + "109.230.95.226", + "109.230.95.234", + "109.230.200.83", + "109.230.200.84", + "109.230.200.85", + "109.230.204.64", + "109.230.205.174", + "109.230.221.18", + "109.230.221.19", + "109.230.221.20", + "109.230.221.66", + "109.230.221.67", + "109.230.221.221", + "109.230.221.225", + "109.230.242.53", + "109.230.242.90", + "109.230.242.227", + "109.232.0.34", + "109.232.0.36", + "109.232.0.76", + "109.232.0.85", + "109.232.0.248", + "109.232.0.250", + "109.232.0.251", + "109.232.1.46", + "109.232.1.84", + "109.232.1.105", + "109.232.1.145", + "109.232.1.158", + "109.232.1.255", + "109.232.2.7", + "109.232.2.13", + "109.232.2.31", + "109.232.2.44", + "109.232.2.73", + "109.232.2.74", + "109.232.2.107", + "109.232.2.227", + "109.232.2.253", + "109.232.2.255", + "109.232.3.3", + "109.232.3.10", + "109.232.3.11", + "109.232.3.87", + "109.232.3.108", + "109.232.3.128", + "109.232.3.205", + "109.232.4.34", + "109.232.4.53", + "109.232.4.54", + "109.232.4.83", + "109.238.181.9", + "109.238.187.93", + "109.238.187.241", + "109.238.187.246", + "109.238.188.49", + "109.238.188.148", + "109.238.188.149", + "109.238.189.70", + "109.238.190.41", + "109.238.190.49", + "109.238.190.93", + "109.238.191.34", + "109.238.191.225", + "128.65.176.41", + "128.65.176.57", + "128.65.176.64", + "128.65.176.82", + "128.65.176.139", + "128.65.176.154", + "128.65.177.117", + "128.65.177.254", + "128.65.178.130", + "128.65.178.172", + "128.65.178.213", + "128.65.179.52", + "128.65.179.62", + "128.65.179.126", + "128.65.179.151", + "128.65.179.177", + "128.65.179.229", + "128.65.179.252", + "128.65.181.35", + "128.65.181.50", + "128.65.181.122", + "128.65.181.167", + "128.65.181.224", + "128.65.181.247", + "128.65.182.14", + "128.65.182.27", + "128.65.182.34", + "128.65.182.87", + "128.65.183.18", + "128.65.183.128", + "128.65.183.141", + "128.65.183.144", + "128.65.183.194", + "128.65.184.36", + "128.65.184.66", + "128.65.184.84", + "128.65.184.195", + "128.65.184.207", + "128.65.185.45", + "128.65.185.82", + "128.65.185.167", + "128.65.185.227", + "128.65.185.247", + "128.65.186.99", + "128.65.187.46", + "128.65.188.23", + "128.65.188.67", + "128.65.188.113", + "128.65.188.209", + "128.65.189.162", + "128.65.190.48", + "128.65.190.182", + "128.65.191.19", + "130.185.72.101", + "130.185.72.147", + "130.185.72.188", + "130.185.72.220", + "130.185.72.222", + "130.185.72.239", + "130.185.73.133", + "130.185.73.173", + "130.185.73.202", + "130.185.73.213", + "130.185.74.10", + "130.185.74.55", + "130.185.74.182", + "130.185.74.196", + "130.185.74.200", + "130.185.75.48", + "130.185.75.161", + "130.185.75.199", + "130.185.76.29", + "130.185.76.93", + "130.185.76.98", + "130.185.76.159", + "130.185.76.167", + "130.185.76.186", + "130.185.76.222", + "130.185.76.242", + "130.185.77.163", + "130.185.77.211", + "130.185.77.223", + "130.185.77.235", + "130.185.78.32", + "130.185.78.97", + "130.193.77.3", + "130.193.77.65", + "130.193.77.72", + "130.193.77.135", + "130.193.77.200", + "134.255.200.202", + "134.255.201.90", + "134.255.201.198", + "134.255.206.5", + "146.19.212.24", + "146.19.212.29", + "146.19.212.90", + "146.19.212.125", + "146.19.212.169", + "146.19.212.189", + "151.232.1.192", + "151.232.1.193", + "151.232.1.194", + "151.232.1.197", + "151.232.1.198", + "151.232.1.199", + "151.232.1.201", + "151.232.2.118", + "151.232.3.162", + "151.232.8.14", + "151.232.8.80", + "151.232.8.82", + "151.232.8.84", + "151.232.8.85", + "151.232.8.87", + "151.232.8.88", + "151.232.8.89", + "151.232.8.94", + "151.232.9.3", + "151.232.9.48", + "151.232.9.64", + "151.232.9.222", + "151.232.17.161", + "151.232.18.66", + "151.232.18.69", + "151.232.23.53", + "151.232.36.11", + "151.232.36.129", + "151.232.37.137", + "151.232.76.21", + "151.232.76.142", + "151.232.80.214", + "151.232.81.227", + "151.232.83.174", + "151.232.83.205", + "151.232.84.74", + "151.232.87.195", + "151.232.124.57", + "151.232.130.87", + "151.232.143.14", + "151.232.143.214", + "151.232.193.44", + "151.232.195.120", + "151.232.201.125", + "151.232.202.148", + "151.232.203.139", + "151.232.207.162", + "151.233.48.38", + "151.233.48.61", + "151.233.48.113", + "151.233.49.80", + "151.233.49.238", + "151.233.50.9", + "151.233.50.51", + "151.233.50.57", + "151.233.50.58", + "151.233.50.82", + "151.233.50.91", + "151.233.50.158", + "151.233.50.203", + "151.233.50.251", + "151.233.51.43", + "151.233.51.64", + "151.233.51.90", + "151.233.51.169", + "151.233.51.187", + "151.233.51.192", + "151.233.51.208", + "151.233.51.223", + "151.233.51.239", + "151.233.52.97", + "151.233.52.245", + "151.233.52.255", + "151.233.53.29", + "151.233.53.53", + "151.233.53.100", + "151.233.53.106", + "151.233.53.170", + "151.233.53.226", + "151.233.54.20", + "151.233.54.44", + "151.233.54.60", + "151.233.54.65", + "151.233.54.80", + "151.233.54.182", + "151.233.54.240", + "151.233.55.15", + "151.233.55.108", + "151.233.55.125", + "151.233.55.133", + "151.233.55.149", + "151.233.56.140", + "151.233.56.149", + "151.233.56.164", + "151.233.57.7", + "151.233.57.13", + "151.233.57.89", + "151.233.57.129", + "151.233.57.151", + "151.233.57.182", + "151.233.57.255", + "151.233.58.3", + "151.233.58.16", + "151.233.58.74", + "151.233.58.92", + "151.233.58.93", + "151.233.58.227", + "151.233.59.39", + "151.233.59.136", + "151.233.75.0", + "151.233.76.239", + "151.233.81.99", + "151.233.89.126", + "151.233.103.231", + "151.233.144.2", + "151.233.170.0", + "151.233.172.5", + "151.234.24.238", + "151.234.25.204", + "151.234.26.244", + "151.234.27.0", + "151.234.27.197", + "151.234.29.147", + "151.234.30.219", + "151.234.30.223", + "151.234.31.188", + "151.234.31.191", + "151.234.31.221", + "151.234.72.89", + "151.234.73.63", + "151.234.73.92", + "151.234.73.155", + "151.234.75.100", + "151.234.75.105", + "151.234.77.38", + "151.234.77.219", + "151.234.87.1", + "151.234.87.40", + "151.234.87.114", + "151.234.87.165", + "151.234.87.194", + "151.234.87.210", + "151.234.87.211", + "151.234.87.242", + "151.234.92.135", + "151.234.113.106", + "151.234.115.64", + "151.234.150.188", + "151.234.151.114", + "151.234.156.241", + "151.234.157.118", + "151.234.158.28", + "151.234.158.113", + "151.234.158.197", + "151.234.161.54", + "151.234.161.74", + "151.234.161.176", + "151.234.161.204", + "151.234.161.224", + "151.234.161.241", + "151.234.161.242", + "151.234.161.244", + "151.234.186.191", + "151.234.189.135", + "151.234.191.195", + "151.234.191.217", + "151.234.213.48", + "151.234.219.129", + "151.234.225.130", + "151.234.226.164", + "151.234.242.190", + "151.234.245.221", + "151.234.249.4", + "151.234.249.182", + "151.234.249.224", + "151.234.250.47", + "151.234.251.59", + "151.234.251.135", + "151.235.0.146", + "151.235.0.237", + "151.235.5.101", + "151.235.10.118", + "151.235.10.218", + "151.235.11.43", + "151.235.11.227", + "151.235.13.114", + "151.235.13.222", + "151.235.13.252", + "151.235.17.25", + "151.235.18.33", + "151.235.18.73", + "151.235.28.168", + "151.235.29.252", + "151.235.34.200", + "151.235.34.224", + "151.235.36.180", + "151.235.37.161", + "151.235.128.57", + "151.235.128.147", + "151.235.128.153", + "151.235.129.153", + "151.235.130.71", + "151.235.133.10", + "151.235.134.174", + "151.235.135.50", + "151.235.135.181", + "151.235.135.186", + "151.235.136.191", + "151.235.136.230", + "151.235.137.37", + "151.235.138.46", + "151.235.138.247", + "151.235.140.42", + "151.235.140.80", + "151.235.140.246", + "151.235.141.196", + "151.235.142.241", + "151.235.164.118", + "151.235.165.6", + "151.235.167.214", + "151.235.169.72", + "151.235.175.211", + "151.235.180.120", + "151.235.183.250", + "151.235.186.159", + "151.235.187.218", + "151.235.188.185", + "151.235.190.187", + "151.235.190.205", + "151.235.192.69", + "151.235.193.122", + "151.235.196.22", + "151.235.197.125", + "151.235.198.102", + "151.235.201.56", + "151.235.201.162", + "151.235.204.31", + "151.235.206.184", + "151.235.208.44", + "151.235.210.61", + "151.235.210.73", + "151.235.212.131", + "151.235.213.71", + "151.235.214.4", + "151.235.215.175", + "151.235.215.224", + "151.235.216.23", + "151.235.216.27", + "151.235.217.215", + "151.235.218.134", + "151.235.220.179", + "151.235.220.218", + "151.235.221.71", + "151.235.223.17", + "151.235.224.240", + "151.235.225.172", + "151.235.227.237", + "151.235.229.66", + "151.235.229.140", + "151.235.229.210", + "151.235.232.49", + "151.235.233.108", + "151.235.233.146", + "151.235.234.18", + "151.235.234.38", + "151.235.236.56", + "151.235.236.152", + "151.235.237.71", + "151.235.237.171", + "151.235.239.155", + "151.235.239.169", + "151.235.239.212", + "151.235.240.147", + "151.235.242.23", + "151.235.243.133", + "151.235.249.16", + "151.235.250.210", + "151.235.253.108", + "151.235.253.221", + "151.235.254.196", + "151.235.255.118", + "151.235.255.131", + "151.238.184.86", + "151.238.184.246", + "151.238.185.102", + "151.238.186.13", + "151.238.186.173", + "151.238.187.46", + "151.238.187.116", + "151.238.190.155", + "151.238.190.165", + "151.238.193.134", + "151.238.193.178", + "151.238.193.182", + "151.238.194.110", + "151.238.195.0", + "151.238.195.35", + "151.238.196.29", + "151.238.196.207", + "151.238.196.251", + "151.238.197.123", + "151.238.198.106", + "151.238.198.134", + "151.238.204.159", + "151.238.205.88", + "151.238.205.150", + "151.238.207.135", + "151.238.207.197", + "151.238.208.237", + "151.238.208.239", + "151.238.209.137", + "151.238.210.27", + "151.238.210.154", + "151.238.211.61", + "151.238.212.194", + "151.238.212.219", + "151.238.213.21", + "151.238.213.62", + "151.238.213.99", + "151.238.216.107", + "151.238.218.31", + "151.238.218.114", + "151.238.220.172", + "151.238.220.253", + "151.238.222.203", + "151.238.222.237", + "151.238.223.235", + "151.238.224.2", + "151.238.225.244", + "151.238.226.19", + "151.238.232.186", + "151.238.233.90", + "151.238.234.231", + "151.238.240.186", + "151.238.242.23", + "151.238.243.33", + "151.238.244.103", + "151.238.248.152", + "151.238.248.203", + "151.238.250.71", + "151.238.251.250", + "151.238.252.32", + "151.238.252.162", + "151.238.253.25", + "151.238.253.54", + "151.238.254.63", + "151.238.254.142", + "151.238.254.254", + "151.238.255.176", + "151.239.1.37", + "151.239.1.66", + "151.239.11.99", + "151.239.11.159", + "151.239.14.27", + "151.239.14.73", + "151.239.15.154", + "151.239.15.226", + "151.239.16.160", + "151.239.17.80", + "151.239.18.84", + "151.239.18.137", + "151.239.18.224", + "151.239.19.56", + "151.239.19.131", + "151.239.19.178", + "151.239.22.1", + "151.239.22.42", + "151.239.22.65", + "151.239.24.135", + "151.239.25.75", + "151.239.25.111", + "151.239.38.241", + "151.239.39.41", + "151.239.40.194", + "151.239.40.206", + "151.239.40.239", + "151.239.42.33", + "151.239.42.90", + "151.239.43.114", + "151.239.44.93", + "151.239.44.204", + "151.239.45.80", + "151.239.45.162", + "151.239.49.100", + "151.239.51.84", + "151.239.52.50", + "151.239.52.84", + "151.239.55.108", + "151.239.57.23", + "151.239.57.138", + "151.239.57.141", + "151.239.57.144", + "151.239.59.104", + "151.239.60.8", + "151.239.60.48", + "151.239.62.85", + "151.239.63.111", + "151.239.64.86", + "151.239.64.154", + "151.239.64.179", + "151.239.66.174", + "151.239.69.194", + "151.239.72.162", + "151.239.76.67", + "151.239.76.69", + "151.239.76.126", + "151.239.77.139", + "151.239.79.39", + "151.239.79.85", + "151.239.79.146", + "151.239.87.78", + "151.239.88.240", + "151.239.89.88", + "151.239.89.147", + "151.239.89.152", + "151.239.89.215", + "151.239.91.162", + "151.239.92.206", + "151.239.94.229", + "151.239.95.199", + "151.239.96.66", + "151.239.97.53", + "151.239.98.194", + "151.239.100.227", + "151.239.103.11", + "151.239.103.119", + "151.239.104.254", + "151.239.105.172", + "151.239.105.228", + "151.239.111.149", + "151.239.112.50", + "151.239.113.8", + "151.239.113.218", + "151.239.113.242", + "151.239.114.86", + "151.239.115.141", + "151.239.116.70", + "151.239.116.152", + "151.239.117.135", + "157.119.188.208", + "157.119.188.209", + "157.119.188.218", + "157.119.191.243", + "157.119.191.244", + "157.119.191.249", + "158.58.184.98", + "158.58.185.43", + "158.58.185.106", + "158.58.185.170", + "158.58.187.5", + "158.58.187.21", + "158.58.187.43", + "158.58.187.140", + "158.58.187.188", + "158.58.188.22", + "158.58.190.44", + "158.255.74.7", + "158.255.74.18", + "158.255.74.28", + "158.255.74.37", + "158.255.74.92", + "158.255.74.139", + "158.255.74.161", + "158.255.74.222", + "158.255.74.227", + "159.20.96.100", + "159.20.102.234", + "159.20.109.48", + "164.138.16.46", + "164.138.16.181", + "164.138.16.191", + "164.138.17.234", + "164.138.18.73", + "164.138.19.203", + "164.138.19.217", + "164.138.19.218", + "164.138.19.219", + "164.138.19.220", + "164.138.19.221", + "164.138.20.154", + "164.138.21.3", + "164.138.22.115", + "164.138.22.116", + "164.138.128.0", + "164.138.128.1", + "164.138.128.2", + "164.138.128.3", + "164.138.128.7", + "164.138.128.8", + "164.138.203.1", + "164.215.59.116", + "171.22.24.19", + "171.22.24.71", + "171.22.24.99", + "171.22.24.173", + "171.22.24.213", + "171.22.25.17", + "171.22.25.38", + "171.22.25.67", + "171.22.25.74", + "171.22.25.85", + "171.22.25.184", + "171.22.26.61", + "171.22.26.65", + "171.22.26.71", + "171.22.26.213", + "171.22.27.22", + "171.22.27.105", + "171.22.27.186", + "176.10.95.144", + "176.10.95.176", + "176.10.95.225", + "176.10.95.253", + "176.65.176.61", + "176.65.177.21", + "176.65.177.237", + "176.65.178.227", + "176.65.180.244", + "176.65.183.179", + "176.65.184.237", + "176.65.187.129", + "176.65.191.87", + "176.65.191.237", + "176.65.232.19", + "176.65.233.3", + "176.65.238.47", + "176.65.240.82", + "176.65.240.83", + "176.65.240.84", + "176.65.240.85", + "176.65.240.86", + "176.65.240.88", + "176.65.240.90", + "176.65.240.91", + "176.65.240.92", + "176.65.240.93", + "176.65.240.94", + "176.65.240.130", + "176.65.241.110", + "176.65.241.163", + "176.65.241.166", + "176.65.241.194", + "176.65.241.236", + "176.65.242.68", + "176.65.242.100", + "176.65.242.130", + "176.65.242.132", + "176.65.243.55", + "176.65.243.237", + "176.65.243.245", + "176.65.252.2", + "176.65.252.15", + "176.65.252.26", + "176.65.252.29", + "176.65.252.53", + "176.65.252.60", + "176.65.252.71", + "176.65.252.72", + "176.65.252.96", + "176.65.252.116", + "176.65.252.150", + "176.65.252.198", + "176.65.252.214", + "176.65.252.243", + "176.65.253.21", + "176.65.253.32", + "176.65.253.44", + "176.65.253.49", + "176.65.253.69", + "176.65.253.87", + "176.65.253.132", + "176.65.253.135", + "176.65.253.149", + "176.65.253.205", + "176.65.253.240", + "176.65.253.241", + "176.65.253.242", + "176.65.253.243", + "176.65.253.244", + "176.65.253.245", + "176.65.253.246", + "176.65.253.255", + "176.65.254.50", + "176.65.254.80", + "176.65.254.85", + "176.65.254.86", + "176.65.254.120", + "176.65.254.121", + "176.65.254.130", + "176.65.254.162", + "176.65.254.202", + "176.65.254.212", + "176.65.254.239", + "176.65.254.241", + "176.97.218.20", + "176.97.218.22", + "176.97.218.39", + "176.97.218.55", + "176.97.218.184", + "176.97.220.65", + "176.97.220.149", + "176.101.32.2", + "176.101.32.62", + "176.101.32.90", + "176.101.32.98", + "176.101.32.146", + "176.101.32.200", + "176.101.33.190", + "176.101.33.214", + "176.101.48.250", + "176.102.224.50", + "176.102.233.11", + "176.102.233.253", + "176.102.240.123", + "176.102.240.200", + "176.102.251.1", + "176.102.253.1", + "176.120.16.220", + "176.120.16.242", + "176.122.210.1", + "176.122.210.5", + "176.122.210.9", + "176.122.210.13", + "176.122.210.17", + "176.122.210.21", + "176.122.210.25", + "176.122.210.29", + "176.122.210.34", + "176.122.210.37", + "176.122.210.40", + "176.122.210.41", + "176.122.210.42", + "176.122.210.43", + "176.122.210.44", + "176.122.210.45", + "176.122.210.46", + "176.122.210.57", + "176.122.210.89", + "176.122.210.97", + "176.122.210.101", + "176.122.210.190", + "176.122.211.0", + "176.122.211.48", + "176.122.211.255", + "176.221.66.2", + "176.221.66.9", + "178.21.161.14", + "178.21.161.139", + "178.21.163.54", + "178.21.163.66", + "178.21.163.83", + "178.21.163.84", + "178.21.163.85", + "178.21.164.176", + "178.21.164.177", + "178.22.121.211", + "178.22.121.213", + "178.22.121.254", + "178.22.122.102", + "178.22.122.246", + "178.22.122.250", + "178.22.122.253", + "178.22.123.6", + "178.22.126.90", + "178.131.9.23", + "178.131.16.8", + "178.131.17.162", + "178.131.17.214", + "178.131.18.13", + "178.131.18.63", + "178.131.19.140", + "178.131.19.192", + "178.131.20.57", + "178.131.20.111", + "178.131.20.156", + "178.131.20.180", + "178.131.23.33", + "178.131.28.7", + "178.131.30.3", + "178.131.30.244", + "178.131.40.9", + "178.131.40.56", + "178.131.40.83", + "178.131.41.99", + "178.131.41.212", + "178.131.42.225", + "178.131.42.254", + "178.131.43.32", + "178.131.43.78", + "178.131.44.3", + "178.131.44.142", + "178.131.46.16", + "178.131.47.218", + "178.131.47.220", + "178.131.48.19", + "178.131.49.249", + "178.131.50.56", + "178.131.50.203", + "178.131.51.39", + "178.131.54.56", + "178.131.55.72", + "178.131.57.93", + "178.131.59.45", + "178.131.59.80", + "178.131.61.47", + "178.131.62.127", + "178.131.63.36", + "178.131.63.166", + "178.131.63.182", + "178.131.63.206", + "178.131.64.50", + "178.131.64.66", + "178.131.65.18", + "178.131.65.111", + "178.131.65.232", + "178.131.66.192", + "178.131.67.252", + "178.131.68.20", + "178.131.68.123", + "178.131.69.104", + "178.131.69.213", + "178.131.70.80", + "178.131.71.200", + "178.131.73.105", + "178.131.73.211", + "178.131.74.27", + "178.131.74.97", + "178.131.76.7", + "178.131.77.156", + "178.131.77.251", + "178.131.78.185", + "178.131.78.222", + "178.131.78.227", + "178.131.79.133", + "178.131.79.169", + "178.131.80.5", + "178.131.80.23", + "178.131.80.41", + "178.131.82.47", + "178.131.83.57", + "178.131.84.118", + "178.131.85.103", + "178.131.86.77", + "178.131.86.133", + "178.131.86.141", + "178.131.87.166", + "178.131.87.170", + "178.131.88.87", + "178.131.88.156", + "178.131.89.205", + "178.131.89.249", + "178.131.90.103", + "178.131.93.109", + "178.131.93.112", + "178.131.93.188", + "178.131.93.195", + "178.131.93.205", + "178.131.102.148", + "178.131.105.110", + "178.131.107.34", + "178.131.108.6", + "178.131.109.241", + "178.131.110.99", + "178.131.111.49", + "178.131.114.21", + "178.131.115.48", + "178.131.116.190", + "178.131.125.33", + "178.131.126.12", + "178.131.133.113", + "178.131.140.116", + "178.131.142.33", + "178.131.142.190", + "178.131.145.1", + "178.131.157.34", + "178.131.165.18", + "178.131.172.252", + "178.131.177.36", + "178.131.180.73", + "178.131.181.136", + "178.131.181.227", + "178.131.182.149", + "178.131.182.155", + "178.131.183.122", + "178.131.183.130", + "178.131.190.216", + "178.131.190.237", + "178.157.0.86", + "178.157.0.113", + "178.157.0.140", + "178.157.0.144", + "178.157.0.166", + "178.173.128.88", + "178.173.130.10", + "178.173.130.36", + "178.173.131.93", + "178.173.131.128", + "178.173.132.25", + "178.173.132.37", + "178.173.132.86", + "178.173.132.104", + "178.173.132.114", + "178.173.132.117", + "178.173.134.42", + "178.173.134.57", + "178.173.134.72", + "178.173.134.183", + "178.173.134.236", + "178.173.134.239", + "178.173.137.96", + "178.173.137.140", + "178.173.137.176", + "178.173.137.192", + "178.173.137.215", + "178.173.137.231", + "178.173.138.53", + "178.173.138.169", + "178.173.138.235", + "178.173.138.252", + "178.173.139.11", + "178.173.139.49", + "178.173.139.120", + "178.173.139.149", + "178.173.139.171", + "178.173.139.173", + "178.173.139.237", + "178.173.140.148", + "178.173.140.161", + "178.173.140.250", + "178.173.141.63", + "178.173.141.83", + "178.173.141.147", + "178.173.141.158", + "178.173.141.194", + "178.173.141.244", + "178.173.142.5", + "178.173.142.37", + "178.173.142.68", + "178.173.142.134", + "178.173.142.159", + "178.173.142.173", + "178.173.142.206", + "178.173.142.210", + "178.173.142.219", + "178.173.143.29", + "178.173.143.50", + "178.173.143.112", + "178.173.143.150", + "178.173.143.209", + "178.173.144.45", + "178.173.144.59", + "178.173.144.71", + "178.173.144.149", + "178.173.144.175", + "178.173.144.240", + "178.173.145.17", + "178.173.145.83", + "178.173.145.149", + "178.173.145.217", + "178.173.147.25", + "178.173.147.43", + "178.173.147.58", + "178.173.147.132", + "178.173.147.133", + "178.173.147.149", + "178.173.147.169", + "178.173.147.238", + "178.173.147.240", + "178.173.147.242", + "178.173.148.20", + "178.173.148.128", + "178.173.148.129", + "178.173.148.130", + "178.173.148.140", + "178.173.148.142", + "178.173.148.143", + "178.173.148.145", + "178.173.148.240", + "178.173.149.21", + "178.173.149.36", + "178.173.149.210", + "178.173.149.213", + "178.173.149.247", + "178.173.149.249", + "178.173.151.94", + "178.173.151.166", + "178.173.151.211", + "178.173.151.226", + "178.173.160.70", + "178.173.163.66", + "178.173.163.67", + "178.173.163.77", + "178.173.163.85", + "178.173.163.117", + "178.173.163.166", + "178.173.163.196", + "178.173.163.216", + "178.173.163.217", + "178.173.163.233", + "178.173.169.12", + "178.173.169.100", + "178.173.169.125", + "178.173.169.150", + "178.173.169.179", + "178.173.169.203", + "178.173.170.42", + "178.173.170.68", + "178.173.170.124", + "178.173.170.222", + "178.173.170.250", + "178.173.171.58", + "178.173.171.242", + "178.173.171.243", + "178.173.171.244", + "178.173.171.245", + "178.173.171.246", + "178.173.171.247", + "178.173.171.248", + "178.173.171.250", + "178.173.171.251", + "178.173.172.10", + "178.173.172.71", + "178.173.172.74", + "178.173.172.241", + "178.173.172.243", + "178.173.172.245", + "178.173.172.247", + "178.173.172.252", + "178.173.174.145", + "178.173.174.182", + "178.173.174.240", + "178.173.175.47", + "178.173.175.84", + "178.173.175.90", + "178.173.175.91", + "178.173.175.156", + "178.173.175.199", + "178.173.175.227", + "178.173.182.27", + "178.173.182.40", + "178.173.182.48", + "178.173.182.128", + "178.173.182.186", + "178.173.182.213", + "178.173.182.249", + "178.173.183.19", + "178.173.183.81", + "178.173.183.168", + "178.173.183.233", + "178.173.184.106", + "178.173.184.232", + "178.173.186.80", + "178.173.186.121", + "178.173.186.157", + "178.173.186.186", + "178.173.186.229", + "178.173.186.253", + "178.173.187.45", + "178.173.187.47", + "178.173.187.62", + "178.173.187.92", + "178.173.187.115", + "178.173.187.122", + "178.173.187.164", + "178.173.187.179", + "178.173.187.182", + "178.173.187.223", + "178.173.188.9", + "178.173.188.128", + "178.173.188.133", + "178.173.188.191", + "178.173.188.207", + "178.173.188.240", + "178.173.189.15", + "178.173.189.35", + "178.173.189.43", + "178.173.189.78", + "178.173.189.133", + "178.173.189.141", + "178.173.189.189", + "178.173.189.207", + "178.173.189.213", + "178.173.189.247", + "178.173.190.62", + "178.173.190.222", + "178.173.190.244", + "178.173.191.3", + "178.173.191.149", + "178.173.192.32", + "178.173.192.122", + "178.173.192.139", + "178.173.192.146", + "178.173.192.169", + "178.173.193.10", + "178.173.193.45", + "178.173.193.78", + "178.173.193.84", + "178.173.193.139", + "178.173.193.144", + "178.173.195.24", + "178.173.195.52", + "178.173.195.60", + "178.173.195.70", + "178.173.195.86", + "178.173.195.123", + "178.173.195.138", + "178.173.195.163", + "178.173.195.170", + "178.173.195.188", + "178.173.195.195", + "178.173.195.243", + "178.173.196.45", + "178.173.196.164", + "178.173.196.212", + "178.173.197.82", + "178.173.197.132", + "178.173.198.3", + "178.173.198.16", + "178.173.198.25", + "178.173.198.39", + "178.173.198.237", + "178.173.198.246", + "178.173.199.48", + "178.173.199.76", + "178.173.199.162", + "178.173.200.44", + "178.173.200.72", + "178.173.200.107", + "178.173.200.212", + "178.173.210.128", + "178.173.210.212", + "178.173.210.249", + "178.173.211.22", + "178.173.211.33", + "178.173.211.80", + "178.173.211.164", + "178.173.211.177", + "178.173.211.240", + "178.173.212.39", + "178.173.212.168", + "178.173.212.169", + "178.173.212.171", + "178.173.212.203", + "178.173.213.48", + "178.173.213.51", + "178.173.213.112", + "178.173.213.217", + "178.173.214.85", + "178.173.214.152", + "178.173.214.172", + "178.173.214.250", + "178.173.215.25", + "178.173.215.57", + "178.173.215.89", + "178.173.215.92", + "178.173.215.166", + "178.173.215.168", + "178.173.215.212", + "178.173.215.236", + "178.173.216.34", + "178.173.216.40", + "178.173.216.131", + "178.173.216.159", + "178.173.216.190", + "178.173.217.29", + "178.173.217.145", + "178.173.217.253", + "178.173.218.29", + "178.173.218.35", + "178.173.218.64", + "178.173.218.107", + "178.173.218.214", + "178.173.218.220", + "178.173.219.10", + "178.173.219.128", + "178.173.219.229", + "178.173.219.242", + "178.173.219.251", + "178.173.220.55", + "178.173.220.103", + "178.173.220.146", + "178.173.220.160", + "178.173.220.161", + "178.173.220.215", + "178.173.221.2", + "178.173.221.9", + "178.173.221.46", + "178.173.221.125", + "178.173.221.195", + "178.173.222.2", + "178.173.222.3", + "178.173.222.177", + "178.173.222.200", + "178.173.222.206", + "178.216.248.235", + "178.216.248.236", + "178.216.248.237", + "178.216.249.213", + "178.216.250.190", + "178.216.251.236", + "178.216.251.237", + "178.216.251.238", + "178.236.33.129", + "178.236.33.161", + "178.236.100.59", + "178.236.104.48", + "178.236.104.118", + "178.236.104.192", + "178.236.104.240", + "178.236.105.223", + "178.236.106.91", + "178.236.106.227", + "178.236.107.71", + "178.236.108.175", + "178.236.109.34", + "178.236.109.97", + "178.238.192.60", + "178.238.192.112", + "178.238.192.166", + "178.238.193.205", + "178.238.194.22", + "178.238.194.118", + "178.238.194.139", + "178.238.195.99", + "178.238.195.240", + "178.238.197.73", + "178.238.199.3", + "178.239.144.129", + "178.239.147.133", + "178.239.151.4", + "178.239.151.49", + "178.239.151.80", + "178.239.151.94", + "178.239.151.158", + "178.239.151.229", + "178.239.152.57", + "178.239.152.145", + "178.239.152.150", + "178.239.152.160", + "178.239.152.179", + "178.239.152.184", + "178.239.156.1", + "178.239.156.5", + "178.239.156.56", + "178.239.156.61", + "178.239.156.67", + "178.239.156.126", + "178.239.158.66", + "178.239.158.129", + "178.239.159.73", + "178.239.159.160", + "178.239.159.255", + "178.252.129.30", + "178.252.132.125", + "178.252.132.217", + "178.252.132.218", + "178.252.132.222", + "178.252.132.228", + "178.252.133.85", + "178.252.133.222", + "178.252.134.178", + "178.252.134.213", + "178.252.134.229", + "178.252.134.238", + "178.252.135.2", + "178.252.135.26", + "178.252.135.53", + "178.252.135.54", + "178.252.135.117", + "178.252.135.196", + "178.252.135.202", + "178.252.135.206", + "178.252.136.2", + "178.252.136.18", + "178.252.136.130", + "178.252.136.132", + "178.252.136.133", + "178.252.136.250", + "178.252.140.98", + "178.252.140.99", + "178.252.140.104", + "178.252.140.107", + "178.252.141.50", + "178.252.141.134", + "178.252.141.226", + "178.252.142.78", + "178.252.143.22", + "178.252.143.99", + "178.252.143.102", + "178.252.143.234", + "178.252.144.18", + "178.252.144.58", + "178.252.144.102", + "178.252.144.240", + "178.252.147.35", + "178.252.147.54", + "178.252.147.65", + "178.252.147.82", + "178.252.147.83", + "178.252.147.84", + "178.252.147.85", + "178.252.147.86", + "178.252.147.122", + "178.252.147.123", + "178.252.147.124", + "178.252.147.146", + "178.252.147.148", + "178.252.147.150", + "178.252.147.154", + "178.252.147.186", + "178.252.147.187", + "178.252.147.188", + "178.252.147.189", + "178.252.148.204", + "178.252.149.202", + "178.252.151.18", + "178.252.151.22", + "178.252.151.42", + "178.252.151.43", + "178.252.151.44", + "178.252.151.46", + "178.252.151.154", + "178.252.151.162", + "178.252.151.238", + "178.252.151.250", + "178.252.151.251", + "178.252.152.56", + "178.252.152.57", + "178.252.152.58", + "178.252.152.61", + "178.252.152.62", + "178.252.153.106", + "178.252.153.122", + "178.252.165.24", + "178.252.165.82", + "178.252.165.83", + "178.252.165.84", + "178.252.165.85", + "178.252.165.102", + "178.252.165.118", + "178.252.165.193", + "178.252.165.230", + "178.252.166.10", + "178.252.166.98", + "178.252.166.153", + "178.252.166.182", + "178.252.167.51", + "178.252.167.90", + "178.252.170.106", + "178.252.170.166", + "178.252.170.222", + "178.252.171.70", + "178.252.171.74", + "178.252.171.226", + "178.252.171.228", + "178.252.171.229", + "178.252.171.234", + "178.252.176.10", + "178.252.176.68", + "178.252.176.115", + "178.252.176.130", + "178.252.176.131", + "178.252.176.146", + "178.252.177.6", + "178.252.177.8", + "178.252.177.10", + "178.252.177.12", + "178.252.177.170", + "178.252.178.205", + "178.252.178.209", + "178.252.178.210", + "178.252.178.222", + "178.252.183.28", + "178.252.183.99", + "178.252.183.250", + "178.252.183.251", + "178.252.183.252", + "178.252.183.253", + "178.252.183.254", + "178.252.184.70", + "178.252.184.138", + "178.252.184.139", + "178.252.184.141", + "178.252.184.154", + "178.252.189.82", + "178.252.189.83", + "178.252.189.84", + "178.252.189.213", + "178.252.189.214", + "178.252.191.19", + "178.252.191.163", + "178.252.191.164", + "178.252.191.169", + "185.2.14.94", + "185.2.14.253", + "185.3.124.62", + "185.3.124.83", + "185.3.124.85", + "185.3.124.86", + "185.3.125.2", + "185.3.125.3", + "185.3.125.4", + "185.3.125.87", + "185.3.125.144", + "185.3.201.197", + "185.3.202.42", + "185.3.202.57", + "185.3.203.19", + "185.3.203.77", + "185.3.203.84", + "185.3.203.85", + "185.3.203.91", + "185.3.212.76", + "185.3.212.141", + "185.3.213.61", + "185.3.215.200", + "185.3.215.226", + "185.4.1.24", + "185.4.1.25", + "185.4.1.45", + "185.4.1.96", + "185.4.3.2", + "185.4.3.73", + "185.4.30.6", + "185.4.30.46", + "185.4.30.58", + "185.4.30.72", + "185.4.30.100", + "185.7.172.1", + "185.7.172.2", + "185.7.172.7", + "185.7.172.17", + "185.7.172.33", + "185.7.172.49", + "185.7.172.65", + "185.7.172.81", + "185.7.172.97", + "185.7.172.113", + "185.7.172.129", + "185.7.172.145", + "185.7.212.35", + "185.7.212.89", + "185.7.212.133", + "185.7.212.150", + "185.8.172.49", + "185.8.172.207", + "185.8.173.27", + "185.8.173.91", + "185.8.174.13", + "185.8.174.184", + "185.8.174.207", + "185.8.175.44", + "185.8.175.153", + "185.8.175.187", + "185.8.175.229", + "185.8.175.241", + "185.8.175.249", + "185.10.71.235", + "185.10.72.124", + "185.10.74.112", + "185.10.74.234", + "185.10.75.77", + "185.10.75.108", + "185.10.75.113", + "185.10.75.119", + "185.10.75.121", + "185.10.75.196", + "185.11.69.55", + "185.11.69.56", + "185.11.69.148", + "185.11.69.150", + "185.11.69.174", + "185.11.70.18", + "185.11.70.55", + "185.11.70.211", + "185.11.70.217", + "185.11.70.230", + "185.11.70.233", + "185.11.71.169", + "185.13.229.253", + "185.13.229.254", + "185.13.230.46", + "185.13.230.116", + "185.13.230.155", + "185.13.230.158", + "185.13.231.49", + "185.14.81.252", + "185.14.161.36", + "185.14.161.67", + "185.14.161.82", + "185.14.161.84", + "185.14.161.85", + "185.14.162.5", + "185.14.162.18", + "185.14.163.162", + "185.14.163.163", + "185.14.163.164", + "185.14.163.165", + "185.14.163.166", + "185.14.163.167", + "185.14.163.168", + "185.14.163.169", + "185.14.163.170", + "185.14.163.171", + "185.14.163.172", + "185.14.163.174", + "185.14.163.210", + "185.14.163.234", + "185.14.163.235", + "185.18.212.67", + "185.18.213.26", + "185.18.213.47", + "185.18.213.67", + "185.18.213.76", + "185.18.213.98", + "185.18.213.180", + "185.18.214.4", + "185.18.214.120", + "185.18.214.138", + "185.18.214.244", + "185.18.215.26", + "185.18.215.36", + "185.18.215.38", + "185.18.215.90", + "185.18.215.208", + "185.18.215.240", + "185.19.201.102", + "185.19.201.188", + "185.19.201.193", + "185.19.201.199", + "185.21.68.53", + "185.21.70.41", + "185.21.70.233", + "185.21.71.46", + "185.21.71.199", + "185.21.71.216", + "185.21.71.246", + "185.22.30.174", + "185.22.31.226", + "185.22.31.227", + "185.23.128.147", + "185.23.128.148", + "185.24.252.43", + "185.24.252.65", + "185.24.252.76", + "185.24.252.111", + "185.24.252.115", + "185.24.252.129", + "185.24.252.242", + "185.24.252.250", + "185.24.253.1", + "185.24.253.8", + "185.24.253.48", + "185.24.253.65", + "185.24.253.96", + "185.24.253.120", + "185.24.253.129", + "185.24.255.109", + "185.24.255.115", + "185.24.255.161", + "185.24.255.169", + "185.24.255.187", + "185.26.33.213", + "185.26.34.227", + "185.26.35.47", + "185.26.35.242", + "185.26.235.53", + "185.29.223.233", + "185.33.25.86", + "185.36.228.170", + "185.36.231.37", + "185.36.231.53", + "185.36.231.140", + "185.37.53.144", + "185.37.53.146", + "185.37.53.147", + "185.37.53.148", + "185.37.53.149", + "185.37.53.150", + "185.37.54.10", + "185.37.54.186", + "185.39.181.6", + "185.39.181.17", + "185.39.181.21", + "185.39.181.23", + "185.39.181.92", + "185.39.181.110", + "185.39.181.142", + "185.39.181.179", + "185.39.181.229", + "185.39.181.243", + "185.39.182.34", + "185.39.183.157", + "185.41.3.5", + "185.42.224.170", + "185.42.225.68", + "185.42.225.74", + "185.42.225.90", + "185.42.225.196", + "185.42.225.199", + "185.42.226.26", + "185.42.226.27", + "185.42.226.28", + "185.42.226.30", + "185.42.227.93", + "185.42.227.94", + "185.42.227.238", + "185.44.36.3", + "185.44.36.30", + "185.44.36.50", + "185.44.36.193", + "185.44.112.13", + "185.44.112.170", + "185.44.112.206", + "185.44.114.153", + "185.46.109.0", + "185.46.109.2", + "185.46.109.10", + "185.46.109.123", + "185.46.109.126", + "185.46.109.127", + "185.46.109.130", + "185.46.109.131", + "185.46.109.146", + "185.46.109.149", + "185.46.109.151", + "185.46.109.161", + "185.46.109.167", + "185.46.109.172", + "185.46.109.175", + "185.46.109.179", + "185.46.110.92", + "185.46.110.122", + "185.46.111.43", + "185.46.111.45", + "185.46.111.74", + "185.46.111.106", + "185.46.111.219", + "185.46.111.228", + "185.46.111.231", + "185.46.217.105", + "185.46.218.77", + "185.46.218.127", + "185.46.218.198", + "185.46.219.18", + "185.46.219.87", + "185.46.219.126", + "185.46.219.220", + "185.47.48.6", + "185.47.48.10", + "185.47.48.13", + "185.47.48.14", + "185.47.48.131", + "185.47.48.178", + "185.47.48.201", + "185.47.48.225", + "185.47.48.253", + "185.47.48.254", + "185.47.49.46", + "185.47.49.53", + "185.47.49.89", + "185.47.49.127", + "185.47.49.138", + "185.47.49.168", + "185.47.49.172", + "185.47.49.226", + "185.47.49.229", + "185.47.49.245", + "185.47.49.252", + "185.47.50.62", + "185.47.50.95", + "185.47.50.238", + "185.47.51.2", + "185.47.51.201", + "185.49.84.122", + "185.49.84.123", + "185.49.84.124", + "185.49.84.125", + "185.49.84.126", + "185.49.85.202", + "185.49.86.35", + "185.49.86.228", + "185.49.86.229", + "185.49.86.230", + "185.49.86.241", + "185.49.87.156", + "185.49.96.168", + "185.49.96.207", + "185.49.96.209", + "185.49.97.49", + "185.49.97.69", + "185.49.97.83", + "185.49.97.108", + "185.49.97.170", + "185.49.97.186", + "185.49.97.187", + "185.49.97.228", + "185.50.38.1", + "185.50.38.2", + "185.50.38.3", + "185.51.40.10", + "185.51.42.255", + "185.51.43.96", + "185.51.43.130", + "185.51.201.68", + "185.51.201.69", + "185.51.201.70", + "185.53.140.9", + "185.53.140.14", + "185.53.140.18", + "185.53.140.20", + "185.53.140.25", + "185.53.140.37", + "185.53.140.38", + "185.53.140.69", + "185.53.140.75", + "185.53.140.85", + "185.53.140.106", + "185.53.140.107", + "185.53.140.119", + "185.53.140.122", + "185.53.140.147", + "185.53.140.150", + "185.53.140.151", + "185.53.140.155", + "185.53.140.157", + "185.53.140.181", + "185.53.140.184", + "185.53.140.186", + "185.53.140.187", + "185.53.140.189", + "185.53.140.194", + "185.53.140.195", + "185.53.140.196", + "185.53.140.198", + "185.53.140.206", + "185.53.140.238", + "185.53.140.239", + "185.53.140.240", + "185.53.140.242", + "185.53.140.243", + "185.53.140.245", + "185.53.140.246", + "185.53.140.248", + "185.53.140.249", + "185.53.140.250", + "185.53.140.251", + "185.53.140.252", + "185.53.141.101", + "185.53.142.12", + "185.53.142.44", + "185.53.142.101", + "185.53.142.248", + "185.53.143.13", + "185.53.143.50", + "185.53.143.51", + "185.53.143.52", + "185.53.143.53", + "185.53.143.55", + "185.53.143.56", + "185.53.143.57", + "185.53.143.58", + "185.53.143.59", + "185.53.143.60", + "185.53.143.78", + "185.55.224.58", + "185.55.224.79", + "185.55.224.89", + "185.55.224.92", + "185.55.224.95", + "185.55.224.103", + "185.55.224.115", + "185.55.224.131", + "185.55.224.193", + "185.55.225.101", + "185.55.225.158", + "185.55.225.176", + "185.55.225.189", + "185.55.226.19", + "185.55.226.25", + "185.55.226.40", + "185.55.227.25", + "185.55.227.38", + "185.55.227.43", + "185.55.227.52", + "185.55.227.90", + "185.55.227.101", + "185.55.227.121", + "185.55.227.143", + "185.55.227.147", + "185.55.227.188", + "185.55.227.200", + "185.56.93.29", + "185.56.93.30", + "185.56.93.70", + "185.56.93.122", + "185.56.94.1", + "185.56.94.16", + "185.56.94.17", + "185.56.95.186", + "185.57.132.27", + "185.57.133.62", + "185.57.133.109", + "185.57.133.140", + "185.57.133.235", + "185.57.133.245", + "185.57.135.9", + "185.57.167.225", + "185.58.240.145", + "185.58.240.165", + "185.58.241.128", + "185.58.242.64", + "185.58.242.113", + "185.58.242.220", + "185.58.243.169", + "185.60.59.2", + "185.60.59.3", + "185.60.59.4", + "185.60.59.73", + "185.60.136.28", + "185.60.136.65", + "185.60.136.74", + "185.60.136.193", + "185.63.113.12", + "185.63.113.164", + "185.63.113.165", + "185.63.113.206", + "185.63.113.216", + "185.63.237.10", + "185.64.176.19", + "185.64.176.103", + "185.66.224.14", + "185.66.224.110", + "185.66.224.148", + "185.66.226.43", + "185.66.226.130", + "185.66.226.235", + "185.66.227.59", + "185.66.227.102", + "185.66.227.113", + "185.66.227.115", + "185.66.227.118", + "185.66.227.123", + "185.66.227.124", + "185.66.227.224", + "185.66.227.227", + "185.66.228.1", + "185.66.228.2", + "185.66.230.18", + "185.66.230.155", + "185.66.230.255", + "185.71.194.140", + "185.71.194.141", + "185.71.194.153", + "185.71.194.163", + "185.72.25.85", + "185.72.25.86", + "185.72.25.94", + "185.72.25.95", + "185.72.25.121", + "185.72.25.122", + "185.72.25.124", + "185.72.25.130", + "185.72.25.146", + "185.72.26.62", + "185.72.26.114", + "185.72.26.146", + "185.72.26.147", + "185.72.26.148", + "185.72.26.149", + "185.72.26.150", + "185.72.26.218", + "185.72.27.50", + "185.72.27.97", + "185.72.27.162", + "185.72.27.232", + "185.73.112.114", + "185.73.112.123", + "185.73.112.253", + "185.73.114.228", + "185.73.226.235", + "185.74.221.11", + "185.74.221.12", + "185.74.221.14", + "185.75.207.100", + "185.75.207.210", + "185.75.207.223", + "185.78.21.48", + "185.79.96.35", + "185.79.96.36", + "185.79.96.37", + "185.79.96.92", + "185.79.96.93", + "185.79.96.94", + "185.79.96.123", + "185.79.96.124", + "185.79.97.2", + "185.79.97.5", + "185.79.97.19", + "185.79.97.131", + "185.79.97.132", + "185.79.97.133", + "185.79.97.195", + "185.79.97.196", + "185.79.97.197", + "185.79.98.131", + "185.79.98.132", + "185.79.98.133", + "185.79.156.39", + "185.79.156.55", + "185.79.156.77", + "185.79.156.82", + "185.79.156.109", + "185.79.156.170", + "185.79.156.191", + "185.79.156.208", + "185.79.156.209", + "185.79.156.210", + "185.79.156.211", + "185.79.156.212", + "185.79.156.213", + "185.79.156.230", + "185.79.156.238", + "185.79.156.239", + "185.79.156.251", + "185.79.156.253", + "185.79.157.130", + "185.79.157.131", + "185.79.157.133", + "185.79.157.162", + "185.79.157.190", + "185.79.158.12", + "185.79.158.14", + "185.79.158.19", + "185.79.158.133", + "185.79.159.130", + "185.81.99.52", + "185.82.65.172", + "185.82.65.205", + "185.82.67.2", + "185.82.67.35", + "185.82.67.36", + "185.82.67.37", + "185.82.67.144", + "185.82.67.146", + "185.82.67.147", + "185.82.67.169", + "185.82.67.175", + "185.82.67.178", + "185.82.136.149", + "185.82.136.163", + "185.82.136.188", + "185.82.136.207", + "185.82.136.215", + "185.82.136.250", + "185.82.136.254", + "185.82.137.14", + "185.82.137.18", + "185.82.137.24", + "185.82.137.33", + "185.82.137.47", + "185.82.137.55", + "185.82.137.84", + "185.82.137.97", + "185.82.137.100", + "185.82.139.9", + "185.82.139.114", + "185.82.139.124", + "185.82.139.140", + "185.82.139.168", + "185.82.139.206", + "185.82.139.227", + "185.82.139.237", + "185.82.139.238", + "185.82.165.10", + "185.82.165.66", + "185.82.165.110", + "185.83.88.9", + "185.83.88.11", + "185.83.88.13", + "185.83.88.15", + "185.83.88.18", + "185.83.88.22", + "185.83.88.44", + "185.83.88.66", + "185.83.88.89", + "185.83.88.112", + "185.83.88.113", + "185.83.88.118", + "185.83.88.156", + "185.83.88.162", + "185.83.88.190", + "185.83.88.193", + "185.83.88.241", + "185.83.89.114", + "185.83.89.194", + "185.83.91.236", + "185.83.112.19", + "185.83.113.29", + "185.83.114.220", + "185.83.115.121", + "185.83.115.122", + "185.83.115.193", + "185.83.115.204", + "185.83.180.41", + "185.83.180.145", + "185.83.180.151", + "185.83.182.20", + "185.83.182.90", + "185.83.182.197", + "185.83.182.218", + "185.83.183.96", + "185.83.183.126", + "185.83.183.137", + "185.83.183.162", + "185.83.183.251", + "185.83.196.13", + "185.83.196.14", + "185.83.196.17", + "185.83.196.30", + "185.83.196.90", + "185.83.196.102", + "185.83.196.134", + "185.83.196.150", + "185.83.196.249", + "185.83.197.42", + "185.83.197.43", + "185.83.197.45", + "185.83.198.154", + "185.83.198.156", + "185.83.198.162", + "185.83.199.83", + "185.83.199.96", + "185.83.199.99", + "185.83.199.100", + "185.83.199.101", + "185.83.199.102", + "185.83.199.123", + "185.83.199.178", + "185.83.199.234", + "185.83.208.134", + "185.83.208.166", + "185.83.208.181", + "185.83.208.185", + "185.84.158.17", + "185.84.158.36", + "185.84.158.55", + "185.85.136.1", + "185.85.136.3", + "185.85.136.5", + "185.85.136.12", + "185.85.136.18", + "185.85.136.21", + "185.85.136.22", + "185.85.136.24", + "185.85.136.26", + "185.85.136.39", + "185.85.136.253", + "185.85.138.130", + "185.85.138.200", + "185.85.138.201", + "185.85.138.202", + "185.85.138.203", + "185.85.139.201", + "185.85.139.203", + "185.85.139.205", + "185.85.139.206", + "185.85.139.207", + "185.85.139.208", + "185.85.139.209", + "185.85.139.210", + "185.85.139.211", + "185.85.139.212", + "185.85.139.213", + "185.85.139.214", + "185.85.139.215", + "185.85.139.217", + "185.85.139.218", + "185.85.139.219", + "185.85.139.222", + "185.85.139.223", + "185.85.139.240", + "185.85.139.242", + "185.85.139.243", + "185.85.139.245", + "185.85.139.246", + "185.85.139.247", + "185.85.139.248", + "185.85.139.249", + "185.85.139.250", + "185.85.139.251", + "185.85.139.252", + "185.85.139.253", + "185.85.139.254", + "185.86.36.113", + "185.86.181.52", + "185.86.181.55", + "185.86.181.84", + "185.86.181.167", + "185.86.183.160", + "185.88.50.38", + "185.88.50.53", + "185.88.50.88", + "185.88.50.177", + "185.88.50.186", + "185.88.51.17", + "185.88.51.41", + "185.88.51.96", + "185.88.51.97", + "185.88.51.98", + "185.88.51.99", + "185.88.51.100", + "185.88.51.101", + "185.88.51.102", + "185.88.51.103", + "185.88.152.54", + "185.88.152.90", + "185.88.152.198", + "185.88.152.251", + "185.88.153.3", + "185.88.153.12", + "185.88.153.48", + "185.88.153.136", + "185.88.153.160", + "185.88.153.202", + "185.88.153.248", + "185.88.154.8", + "185.88.154.68", + "185.88.154.244", + "185.88.176.10", + "185.88.176.44", + "185.88.177.23", + "185.88.177.39", + "185.88.177.65", + "185.88.177.93", + "185.88.177.108", + "185.88.177.137", + "185.88.177.189", + "185.88.177.208", + "185.88.177.213", + "185.88.178.8", + "185.88.178.10", + "185.88.178.12", + "185.88.178.14", + "185.88.178.19", + "185.88.178.25", + "185.88.178.34", + "185.88.178.37", + "185.88.178.54", + "185.88.178.70", + "185.88.178.74", + "185.88.178.78", + "185.88.178.120", + "185.88.178.121", + "185.88.178.184", + "185.88.179.232", + "185.88.252.71", + "185.88.252.79", + "185.88.252.87", + "185.88.252.107", + "185.88.252.117", + "185.88.252.146", + "185.88.253.11", + "185.89.112.58", + "185.89.112.68", + "185.89.112.162", + "185.89.112.212", + "185.89.113.106", + "185.89.113.126", + "185.89.113.200", + "185.89.115.185", + "185.92.40.1", + "185.92.40.40", + "185.92.42.89", + "185.94.96.1", + "185.94.96.5", + "185.94.96.9", + "185.94.96.17", + "185.94.96.21", + "185.94.96.25", + "185.94.96.37", + "185.94.96.41", + "185.94.96.45", + "185.94.96.49", + "185.94.96.65", + "185.94.96.97", + "185.94.96.105", + "185.94.96.117", + "185.94.96.121", + "185.94.96.129", + "185.94.96.133", + "185.94.96.137", + "185.94.96.141", + "185.94.96.145", + "185.94.96.157", + "185.94.96.161", + "185.94.96.165", + "185.94.96.169", + "185.94.96.173", + "185.94.96.177", + "185.94.96.181", + "185.94.96.185", + "185.94.96.189", + "185.94.96.193", + "185.94.96.225", + "185.94.96.241", + "185.94.96.245", + "185.94.96.249", + "185.94.97.1", + "185.94.97.17", + "185.94.97.21", + "185.94.97.22", + "185.94.97.25", + "185.94.97.29", + "185.94.97.33", + "185.94.97.38", + "185.94.97.41", + "185.94.97.45", + "185.94.97.49", + "185.94.97.53", + "185.94.97.57", + "185.94.97.61", + "185.94.97.65", + "185.94.97.69", + "185.94.97.73", + "185.94.97.77", + "185.94.97.81", + "185.94.97.97", + "185.94.97.105", + "185.94.97.109", + "185.94.97.117", + "185.94.97.145", + "185.94.97.149", + "185.94.97.157", + "185.94.97.197", + "185.94.97.201", + "185.94.97.205", + "185.94.97.209", + "185.94.97.213", + "185.94.97.217", + "185.94.97.225", + "185.94.97.232", + "185.94.97.241", + "185.94.97.245", + "185.94.97.249", + "185.94.97.253", + "185.94.98.1", + "185.94.98.37", + "185.94.98.41", + "185.94.98.49", + "185.94.98.53", + "185.94.98.57", + "185.94.98.61", + "185.94.98.73", + "185.94.98.81", + "185.94.98.89", + "185.94.98.97", + "185.94.98.101", + "185.94.98.105", + "185.94.98.109", + "185.94.98.113", + "185.94.98.129", + "185.94.98.145", + "185.94.98.165", + "185.94.98.193", + "185.94.98.225", + "185.94.98.233", + "185.94.98.241", + "185.94.98.249", + "185.94.99.1", + "185.94.99.5", + "185.94.99.33", + "185.94.99.65", + "185.94.99.129", + "185.94.99.137", + "185.94.99.141", + "185.94.99.145", + "185.94.99.153", + "185.94.99.157", + "185.94.99.161", + "185.94.99.165", + "185.94.99.169", + "185.94.99.177", + "185.94.99.185", + "185.94.99.193", + "185.94.99.208", + "185.94.99.212", + "185.94.99.225", + "185.95.152.33", + "185.95.152.105", + "185.95.152.130", + "185.95.152.132", + "185.95.152.148", + "185.95.182.1", + "185.97.117.1", + "185.97.117.112", + "185.97.118.189", + "185.97.118.202", + "185.97.119.194", + "185.97.119.241", + "185.99.212.43", + "185.99.212.92", + "185.99.213.56", + "185.100.44.28", + "185.100.44.106", + "185.100.44.183", + "185.100.44.222", + "185.100.45.22", + "185.100.45.102", + "185.100.46.56", + "185.100.46.167", + "185.100.46.168", + "185.100.47.161", + "185.100.47.166", + "185.100.47.249", + "185.101.231.186", + "185.101.231.188", + "185.101.231.190", + "185.101.231.242", + "185.103.84.1", + "185.103.84.16", + "185.103.87.30", + "185.103.129.69", + "185.103.129.70", + "185.103.129.110", + "185.103.130.34", + "185.103.130.146", + "185.103.130.162", + "185.103.130.170", + "185.103.130.238", + "185.103.131.10", + "185.103.131.131", + "185.103.248.13", + "185.103.248.150", + "185.103.250.253", + "185.104.229.202", + "185.104.229.203", + "185.104.229.204", + "185.104.229.205", + "185.104.229.206", + "185.104.243.80", + "185.105.101.1", + "185.105.184.125", + "185.105.187.133", + "185.105.187.148", + "185.105.187.172", + "185.105.237.98", + "185.105.238.157", + "185.106.136.153", + "185.106.136.154", + "185.106.137.93", + "185.106.138.209", + "185.106.144.29", + "185.106.145.98", + "185.106.146.221", + "185.106.201.7", + "185.107.31.90", + "185.107.31.91", + "185.107.32.62", + "185.107.244.195", + "185.107.245.100", + "185.107.245.177", + "185.107.245.240", + "185.107.246.26", + "185.107.246.129", + "185.107.246.131", + "185.107.246.140", + "185.107.246.141", + "185.107.246.142", + "185.107.246.146", + "185.107.246.154", + "185.107.247.215", + "185.109.60.43", + "185.109.60.45", + "185.109.61.7", + "185.109.61.41", + "185.109.61.228", + "185.109.62.69", + "185.109.62.105", + "185.109.62.108", + "185.109.63.106", + "185.109.63.107", + "185.109.63.108", + "185.109.63.109", + "185.109.63.158", + "185.109.63.192", + "185.109.74.1", + "185.109.74.87", + "185.109.74.119", + "185.109.74.124", + "185.109.74.175", + "185.109.74.195", + "185.109.74.199", + "185.109.74.241", + "185.109.75.41", + "185.109.81.91", + "185.109.82.73", + "185.109.82.77", + "185.109.83.55", + "185.109.244.188", + "185.109.245.114", + "185.109.246.82", + "185.109.246.114", + "185.109.247.18", + "185.109.247.19", + "185.109.247.20", + "185.109.247.21", + "185.109.247.85", + "185.109.247.99", + "185.109.247.226", + "185.109.248.126", + "185.110.28.1", + "185.110.28.8", + "185.110.28.9", + "185.110.28.20", + "185.110.28.30", + "185.110.28.54", + "185.110.28.79", + "185.110.28.97", + "185.110.28.98", + "185.110.28.99", + "185.110.28.100", + "185.110.28.101", + "185.110.28.102", + "185.110.28.103", + "185.110.28.105", + "185.110.28.193", + "185.110.28.194", + "185.110.28.245", + "185.110.28.249", + "185.110.28.253", + "185.110.29.38", + "185.110.29.113", + "185.110.29.153", + "185.110.29.193", + "185.110.29.249", + "185.110.30.14", + "185.110.30.62", + "185.110.30.70", + "185.110.30.79", + "185.110.30.145", + "185.110.30.177", + "185.110.30.241", + "185.110.31.10", + "185.110.31.17", + "185.110.31.140", + "185.110.31.170", + "185.110.219.25", + "185.110.219.57", + "185.110.219.131", + "185.110.236.0", + "185.110.236.33", + "185.110.236.36", + "185.110.236.66", + "185.110.237.0", + "185.110.237.58", + "185.110.237.197", + "185.110.238.0", + "185.110.239.1", + "185.110.244.55", + "185.110.247.23", + "185.110.252.41", + "185.110.252.49", + "185.110.252.97", + "185.110.252.145", + "185.110.252.159", + "185.110.252.167", + "185.110.252.229", + "185.110.253.208", + "185.111.65.28", + "185.111.81.117", + "185.111.81.118", + "185.111.82.139", + "185.111.83.6", + "185.111.83.73", + "185.111.83.144", + "185.111.83.163", + "185.111.83.221", + "185.112.32.26", + "185.112.32.42", + "185.112.32.186", + "185.112.33.80", + "185.112.33.88", + "185.112.33.89", + "185.112.33.108", + "185.112.33.109", + "185.112.33.178", + "185.112.33.179", + "185.112.33.253", + "185.112.34.115", + "185.112.35.84", + "185.112.35.202", + "185.112.35.205", + "185.112.35.206", + "185.112.36.8", + "185.112.36.17", + "185.112.36.51", + "185.112.36.70", + "185.112.36.94", + "185.112.36.106", + "185.112.36.134", + "185.112.36.188", + "185.112.36.197", + "185.112.36.200", + "185.112.36.201", + "185.112.36.205", + "185.112.36.215", + "185.112.36.223", + "185.112.36.253", + "185.112.37.5", + "185.112.37.43", + "185.112.37.56", + "185.112.37.57", + "185.112.37.65", + "185.112.37.83", + "185.112.37.94", + "185.112.37.110", + "185.112.37.112", + "185.112.37.119", + "185.112.37.128", + "185.112.37.155", + "185.112.37.181", + "185.112.37.186", + "185.112.37.214", + "185.112.37.218", + "185.112.37.238", + "185.112.38.16", + "185.112.38.18", + "185.112.38.44", + "185.112.38.46", + "185.112.38.55", + "185.112.38.64", + "185.112.38.71", + "185.112.38.91", + "185.112.38.96", + "185.112.38.104", + "185.112.38.112", + "185.112.38.118", + "185.112.38.121", + "185.112.38.131", + "185.112.38.137", + "185.112.38.225", + "185.112.38.227", + "185.112.38.230", + "185.112.39.7", + "185.112.39.21", + "185.112.39.36", + "185.112.39.45", + "185.112.39.60", + "185.112.39.88", + "185.112.39.123", + "185.112.39.129", + "185.112.39.151", + "185.112.39.160", + "185.112.150.24", + "185.112.150.26", + "185.112.151.4", + "185.112.151.178", + "185.112.151.207", + "185.112.151.223", + "185.112.151.246", + "185.113.9.56", + "185.113.9.124", + "185.113.9.211", + "185.113.9.246", + "185.113.56.1", + "185.113.56.11", + "185.113.56.19", + "185.113.56.30", + "185.113.56.35", + "185.113.56.125", + "185.113.56.130", + "185.113.57.17", + "185.113.57.208", + "185.113.58.71", + "185.113.58.73", + "185.113.58.92", + "185.113.58.221", + "185.113.58.229", + "185.113.58.231", + "185.113.58.235", + "185.113.59.161", + "185.115.149.29", + "185.115.149.228", + "185.115.168.1", + "185.115.168.3", + "185.115.168.4", + "185.115.168.11", + "185.115.168.24", + "185.115.168.55", + "185.115.168.109", + "185.115.168.157", + "185.115.168.181", + "185.115.168.195", + "185.115.169.29", + "185.115.169.46", + "185.115.169.90", + "185.115.169.162", + "185.115.169.179", + "185.115.169.186", + "185.115.169.223", + "185.115.171.5", + "185.115.171.29", + "185.115.171.31", + "185.115.171.42", + "185.115.171.45", + "185.115.171.46", + "185.115.171.48", + "185.115.171.81", + "185.115.171.91", + "185.115.171.110", + "185.115.171.171", + "185.115.171.174", + "185.115.171.184", + "185.115.171.187", + "185.115.171.194", + "185.115.171.219", + "185.115.171.252", + "185.116.20.97", + "185.116.20.169", + "185.116.20.173", + "185.116.20.190", + "185.116.21.75", + "185.116.21.76", + "185.116.21.77", + "185.116.22.5", + "185.116.22.8", + "185.116.22.49", + "185.116.22.50", + "185.116.22.66", + "185.116.22.81", + "185.116.22.177", + "185.116.23.2", + "185.116.23.13", + "185.116.23.17", + "185.116.23.57", + "185.116.23.129", + "185.116.23.193", + "185.116.160.30", + "185.116.160.73", + "185.116.160.125", + "185.116.160.131", + "185.116.161.31", + "185.116.161.108", + "185.116.161.114", + "185.116.162.37", + "185.116.162.88", + "185.116.163.32", + "185.116.163.71", + "185.116.163.76", + "185.116.163.95", + "185.116.163.99", + "185.116.163.105", + "185.116.163.142", + "185.116.163.214", + "185.117.48.248", + "185.117.139.83", + "185.117.139.84", + "185.117.139.89", + "185.117.139.91", + "185.117.139.132", + "185.117.139.162", + "185.117.139.163", + "185.117.139.164", + "185.117.139.166", + "185.117.139.167", + "185.117.139.170", + "185.117.139.171", + "185.117.139.172", + "185.117.139.174", + "185.118.13.101", + "185.118.13.140", + "185.118.13.185", + "185.118.13.229", + "185.118.14.22", + "185.118.14.83", + "185.118.14.99", + "185.118.14.113", + "185.118.14.122", + "185.118.14.130", + "185.118.14.142", + "185.118.14.149", + "185.118.14.165", + "185.118.14.176", + "185.118.14.191", + "185.118.14.199", + "185.118.14.201", + "185.118.14.203", + "185.118.152.13", + "185.118.152.17", + "185.118.152.21", + "185.118.152.22", + "185.118.152.30", + "185.118.152.93", + "185.118.152.101", + "185.118.152.169", + "185.118.152.182", + "185.118.152.185", + "185.118.152.193", + "185.118.152.214", + "185.118.152.248", + "185.118.152.249", + "185.118.153.19", + "185.118.153.98", + "185.118.155.25", + "185.118.155.33", + "185.118.155.84", + "185.118.155.95", + "185.118.155.114", + "185.118.155.122", + "185.118.155.212", + "185.118.155.233", + "185.118.155.237", + "185.119.164.61", + "185.119.164.65", + "185.119.164.66", + "185.119.164.67", + "185.119.164.69", + "185.119.164.70", + "185.119.164.77", + "185.119.164.166", + "185.119.166.8", + "185.119.166.165", + "185.120.162.49", + "185.120.162.60", + "185.120.162.61", + "185.120.162.62", + "185.120.162.97", + "185.120.169.150", + "185.120.201.34", + "185.120.201.35", + "185.120.201.42", + "185.120.201.56", + "185.120.220.27", + "185.120.220.28", + "185.120.220.155", + "185.120.220.193", + "185.120.220.194", + "185.120.221.84", + "185.120.221.180", + "185.120.221.249", + "185.120.222.138", + "185.120.222.166", + "185.120.222.184", + "185.120.222.198", + "185.120.222.211", + "185.120.222.217", + "185.120.222.218", + "185.120.222.232", + "185.120.222.242", + "185.120.249.175", + "185.120.250.225", + "185.121.128.102", + "185.121.128.115", + "185.121.128.119", + "185.121.128.130", + "185.121.128.162", + "185.121.128.163", + "185.121.128.164", + "185.121.129.66", + "185.121.129.67", + "185.121.129.69", + "185.121.129.75", + "185.121.129.85", + "185.121.129.86", + "185.121.129.89", + "185.121.129.134", + "185.121.129.158", + "185.121.129.170", + "185.121.130.106", + "185.121.130.154", + "185.121.130.210", + "185.121.130.230", + "185.121.131.6", + "185.121.131.105", + "185.121.131.107", + "185.121.131.109", + "185.121.131.110", + "185.121.131.186", + "185.124.112.51", + "185.124.112.59", + "185.124.112.153", + "185.124.113.224", + "185.124.114.37", + "185.124.114.40", + "185.124.114.96", + "185.124.114.131", + "185.124.114.214", + "185.124.114.220", + "185.124.115.104", + "185.124.115.137", + "185.124.173.90", + "185.124.173.100", + "185.125.20.4", + "185.125.20.5", + "185.125.20.32", + "185.125.20.102", + "185.125.20.119", + "185.125.20.204", + "185.125.20.205", + "185.125.20.229", + "185.125.20.254", + "185.125.23.106", + "185.125.23.107", + "185.125.23.108", + "185.125.23.109", + "185.125.23.113", + "185.125.23.203", + "185.125.23.218", + "185.125.23.228", + "185.125.23.229", + "185.125.248.129", + "185.125.248.154", + "185.125.248.217", + "185.125.248.229", + "185.125.249.42", + "185.125.251.90", + "185.125.252.77", + "185.125.252.102", + "185.125.252.190", + "185.125.252.226", + "185.125.255.82", + "185.125.255.83", + "185.125.255.84", + "185.125.255.85", + "185.125.255.86", + "185.125.255.88", + "185.125.255.89", + "185.125.255.90", + "185.125.255.91", + "185.125.255.92", + "185.125.255.93", + "185.125.255.113", + "185.125.255.114", + "185.125.255.119", + "185.125.255.120", + "185.125.255.241", + "185.126.0.84", + "185.126.1.4", + "185.126.1.166", + "185.126.2.44", + "185.126.3.91", + "185.126.3.92", + "185.126.3.132", + "185.126.3.133", + "185.126.3.134", + "185.126.3.135", + "185.126.3.136", + "185.126.3.137", + "185.126.3.138", + "185.126.3.140", + "185.126.3.141", + "185.126.3.142", + "185.126.3.143", + "185.126.3.145", + "185.126.3.146", + "185.126.3.147", + "185.126.3.149", + "185.126.3.151", + "185.126.3.152", + "185.126.3.153", + "185.126.3.154", + "185.126.3.155", + "185.126.3.158", + "185.126.4.62", + "185.126.4.170", + "185.126.5.14", + "185.126.5.40", + "185.126.5.41", + "185.126.5.49", + "185.126.5.221", + "185.126.6.174", + "185.126.6.241", + "185.126.8.18", + "185.126.8.19", + "185.126.8.21", + "185.126.8.22", + "185.126.9.147", + "185.126.9.148", + "185.126.9.150", + "185.126.9.186", + "185.126.10.180", + "185.126.10.205", + "185.126.11.97", + "185.126.14.109", + "185.126.14.110", + "185.126.14.114", + "185.126.14.115", + "185.126.14.116", + "185.126.14.117", + "185.126.14.118", + "185.126.14.119", + "185.126.14.120", + "185.126.14.121", + "185.126.14.122", + "185.126.14.123", + "185.126.14.124", + "185.126.14.126", + "185.126.14.196", + "185.126.14.202", + "185.126.14.204", + "185.126.14.205", + "185.126.14.212", + "185.126.14.244", + "185.126.14.245", + "185.126.15.13", + "185.126.15.202", + "185.126.16.9", + "185.126.18.9", + "185.126.40.95", + "185.126.40.106", + "185.126.40.109", + "185.126.40.125", + "185.126.40.175", + "185.126.40.178", + "185.126.40.226", + "185.126.201.3", + "185.126.201.4", + "185.126.201.5", + "185.126.201.6", + "185.126.201.7", + "185.126.201.8", + "185.126.201.9", + "185.126.201.19", + "185.126.201.22", + "185.126.201.25", + "185.126.201.26", + "185.126.201.27", + "185.126.201.235", + "185.126.202.12", + "185.126.202.13", + "185.126.202.202", + "185.126.203.26", + "185.126.203.44", + "185.128.40.16", + "185.128.80.32", + "185.128.80.68", + "185.128.81.50", + "185.128.81.225", + "185.128.136.12", + "185.128.137.200", + "185.128.137.202", + "185.128.138.2", + "185.128.138.100", + "185.128.138.102", + "185.128.138.103", + "185.128.138.175", + "185.128.139.73", + "185.128.139.133", + "185.128.152.13", + "185.128.152.59", + "185.128.152.137", + "185.128.152.201", + "185.128.152.246", + "185.129.116.74", + "185.129.116.75", + "185.129.116.76", + "185.129.116.77", + "185.129.117.67", + "185.129.117.68", + "185.129.117.69", + "185.129.117.105", + "185.129.117.195", + "185.129.117.196", + "185.129.117.197", + "185.129.118.2", + "185.129.118.3", + "185.129.118.5", + "185.129.118.6", + "185.129.118.53", + "185.129.118.131", + "185.129.118.132", + "185.129.118.237", + "185.129.119.3", + "185.129.119.4", + "185.129.119.5", + "185.129.119.131", + "185.129.119.132", + "185.129.119.133", + "185.129.168.41", + "185.129.168.166", + "185.129.168.193", + "185.129.169.131", + "185.129.169.132", + "185.129.169.137", + "185.129.170.87", + "185.129.197.155", + "185.129.197.173", + "185.129.197.215", + "185.129.197.226", + "185.129.197.227", + "185.129.197.233", + "185.129.197.235", + "185.129.199.74", + "185.129.199.186", + "185.129.200.201", + "185.129.202.131", + "185.129.212.178", + "185.129.213.145", + "185.129.213.149", + "185.129.213.170", + "185.129.213.189", + "185.129.213.203", + "185.129.213.210", + "185.129.213.221", + "185.129.214.25", + "185.129.214.47", + "185.129.214.200", + "185.129.215.29", + "185.129.215.45", + "185.129.215.165", + "185.129.216.19", + "185.129.216.30", + "185.129.216.152", + "185.129.217.10", + "185.129.217.38", + "185.129.217.59", + "185.129.217.116", + "185.129.217.129", + "185.129.217.203", + "185.129.218.0", + "185.129.230.18", + "185.129.230.213", + "185.129.236.49", + "185.129.236.194", + "185.129.236.195", + "185.129.237.45", + "185.129.238.52", + "185.129.238.178", + "185.129.238.255", + "185.129.239.37", + "185.129.239.43", + "185.129.239.48", + "185.129.239.93", + "185.129.239.123", + "185.129.239.171", + "185.129.239.232", + "185.129.239.235", + "185.130.50.92", + "185.130.50.113", + "185.130.50.130", + "185.130.50.141", + "185.130.50.142", + "185.130.50.154", + "185.130.50.248", + "185.130.79.230", + "185.131.30.14", + "185.131.30.18", + "185.131.30.21", + "185.131.30.49", + "185.131.30.50", + "185.131.30.58", + "185.131.30.83", + "185.131.30.104", + "185.131.30.122", + "185.131.30.135", + "185.131.30.168", + "185.131.30.205", + "185.131.30.237", + "185.131.30.250", + "185.131.117.125", + "185.131.117.145", + "185.131.119.78", + "185.131.155.109", + "185.132.80.3", + "185.132.80.18", + "185.132.80.20", + "185.132.80.30", + "185.132.80.65", + "185.132.80.131", + "185.132.80.228", + "185.132.80.232", + "185.132.80.244", + "185.132.81.17", + "185.132.81.19", + "185.132.81.25", + "185.132.81.53", + "185.132.81.74", + "185.132.81.109", + "185.132.81.148", + "185.132.81.172", + "185.132.81.197", + "185.132.81.200", + "185.132.81.249", + "185.132.82.7", + "185.132.82.19", + "185.132.82.20", + "185.132.82.58", + "185.132.82.84", + "185.132.82.94", + "185.132.82.128", + "185.132.82.160", + "185.132.82.168", + "185.132.82.177", + "185.132.82.178", + "185.132.82.191", + "185.134.96.114", + "185.134.96.150", + "185.134.96.161", + "185.134.96.162", + "185.134.96.230", + "185.134.97.98", + "185.134.97.230", + "185.134.97.250", + "185.134.98.10", + "185.134.98.130", + "185.134.98.142", + "185.134.98.144", + "185.134.98.166", + "185.134.98.196", + "185.134.98.213", + "185.134.98.220", + "185.134.98.250", + "185.134.99.250", + "185.135.47.70", + "185.135.228.209", + "185.135.229.72", + "185.135.229.227", + "185.135.230.4", + "185.135.230.84", + "185.135.230.246", + "185.135.230.251", + "185.135.231.122", + "185.136.100.43", + "185.136.100.78", + "185.136.101.31", + "185.136.101.142", + "185.136.102.96", + "185.136.102.124", + "185.136.102.229", + "185.136.103.90", + "185.136.103.125", + "185.136.133.36", + "185.136.133.37", + "185.136.133.38", + "185.136.133.106", + "185.136.133.107", + "185.136.180.9", + "185.136.180.75", + "185.136.180.91", + "185.136.180.102", + "185.136.180.176", + "185.136.180.184", + "185.136.180.218", + "185.136.180.221", + "185.136.180.234", + "185.136.180.255", + "185.136.182.160", + "185.136.182.185", + "185.136.183.168", + "185.136.183.180", + "185.136.183.210", + "185.136.183.220", + "185.136.183.252", + "185.136.192.18", + "185.136.192.114", + "185.136.193.22", + "185.136.193.41", + "185.136.193.207", + "185.136.193.233", + "185.136.194.33", + "185.136.194.34", + "185.136.194.36", + "185.136.194.38", + "185.136.194.39", + "185.136.194.42", + "185.136.194.43", + "185.136.194.44", + "185.136.194.45", + "185.136.194.46", + "185.136.195.206", + "185.137.25.70", + "185.137.25.98", + "185.137.25.210", + "185.137.26.54", + "185.137.26.138", + "185.137.26.155", + "185.137.26.182", + "185.137.60.112", + "185.137.60.150", + "185.137.60.202", + "185.137.108.1", + "185.137.108.106", + "185.137.108.128", + "185.137.108.137", + "185.137.108.163", + "185.137.108.192", + "185.137.108.193", + "185.137.108.197", + "185.137.108.198", + "185.137.108.211", + "185.137.108.215", + "185.137.108.230", + "185.137.108.238", + "185.137.109.0", + "185.137.109.1", + "185.137.109.2", + "185.137.109.13", + "185.137.109.36", + "185.137.109.49", + "185.137.109.131", + "185.137.109.254", + "185.137.109.255", + "185.137.110.0", + "185.137.110.18", + "185.137.110.25", + "185.137.110.31", + "185.137.110.57", + "185.137.110.64", + "185.137.110.74", + "185.137.110.76", + "185.137.110.106", + "185.137.110.109", + "185.137.111.0", + "185.140.4.8", + "185.140.4.14", + "185.140.4.24", + "185.140.4.26", + "185.140.4.27", + "185.140.4.28", + "185.140.4.29", + "185.140.4.65", + "185.140.4.66", + "185.140.4.67", + "185.140.4.72", + "185.140.4.73", + "185.140.4.76", + "185.140.4.77", + "185.140.4.78", + "185.140.4.79", + "185.140.5.6", + "185.140.7.130", + "185.140.7.131", + "185.140.7.142", + "185.140.56.21", + "185.140.58.179", + "185.140.59.3", + "185.140.59.162", + "185.140.59.186", + "185.140.232.9", + "185.140.232.28", + "185.140.232.47", + "185.140.232.57", + "185.140.232.66", + "185.140.232.242", + "185.140.232.249", + "185.140.232.251", + "185.140.232.252", + "185.140.234.1", + "185.140.234.47", + "185.140.234.251", + "185.140.235.2", + "185.140.240.1", + "185.140.240.16", + "185.140.240.49", + "185.140.240.50", + "185.140.240.54", + "185.140.240.89", + "185.140.240.94", + "185.140.240.159", + "185.140.240.161", + "185.140.240.162", + "185.140.240.239", + "185.140.240.250", + "185.140.240.252", + "185.140.241.148", + "185.140.242.33", + "185.140.242.35", + "185.140.242.41", + "185.140.242.42", + "185.140.242.44", + "185.140.242.49", + "185.140.242.50", + "185.140.242.129", + "185.140.242.133", + "185.140.242.134", + "185.140.242.146", + "185.140.242.155", + "185.140.243.13", + "185.140.243.104", + "185.140.243.121", + "185.140.243.128", + "185.140.243.133", + "185.140.243.169", + "185.140.243.175", + "185.140.243.176", + "185.140.243.179", + "185.140.243.182", + "185.140.243.254", + "185.140.243.255", + "185.141.36.58", + "185.141.36.67", + "185.141.39.3", + "185.141.39.21", + "185.141.39.30", + "185.141.39.39", + "185.141.39.114", + "185.141.39.225", + "185.141.49.129", + "185.141.104.120", + "185.141.104.209", + "185.141.105.209", + "185.141.105.210", + "185.141.105.211", + "185.141.105.212", + "185.141.105.213", + "185.141.105.235", + "185.141.105.236", + "185.141.106.51", + "185.141.106.217", + "185.141.106.219", + "185.141.106.220", + "185.141.106.221", + "185.141.106.222", + "185.141.106.223", + "185.141.107.137", + "185.141.107.220", + "185.141.132.64", + "185.141.132.65", + "185.141.132.66", + "185.141.133.164", + "185.141.134.48", + "185.141.134.149", + "185.141.134.150", + "185.141.134.219", + "185.141.134.253", + "185.141.168.12", + "185.141.168.98", + "185.141.168.118", + "185.141.169.233", + "185.141.171.114", + "185.141.171.115", + "185.141.171.116", + "185.141.171.117", + "185.141.213.130", + "185.141.213.169", + "185.141.213.246", + "185.141.213.252", + "185.141.214.19", + "185.141.214.92", + "185.141.215.61", + "185.142.92.104", + "185.142.92.107", + "185.142.92.111", + "185.142.92.117", + "185.142.92.156", + "185.142.92.166", + "185.142.92.177", + "185.142.92.184", + "185.142.92.198", + "185.142.92.199", + "185.142.92.214", + "185.142.92.220", + "185.142.92.222", + "185.142.93.27", + "185.142.93.48", + "185.142.93.64", + "185.142.93.81", + "185.142.93.100", + "185.142.93.137", + "185.142.93.138", + "185.142.93.144", + "185.142.93.146", + "185.142.93.173", + "185.142.93.200", + "185.142.93.203", + "185.142.93.208", + "185.142.93.214", + "185.142.93.230", + "185.142.93.234", + "185.142.93.235", + "185.142.93.236", + "185.142.93.243", + "185.142.94.6", + "185.142.94.16", + "185.142.94.31", + "185.142.94.38", + "185.142.94.42", + "185.142.94.48", + "185.142.94.53", + "185.142.94.66", + "185.142.94.68", + "185.142.94.77", + "185.142.94.79", + "185.142.94.82", + "185.142.94.121", + "185.142.94.143", + "185.142.94.152", + "185.142.94.158", + "185.142.94.159", + "185.142.94.160", + "185.142.95.52", + "185.142.95.53", + "185.142.95.55", + "185.142.95.98", + "185.142.95.178", + "185.142.124.18", + "185.142.124.19", + "185.142.124.21", + "185.142.124.65", + "185.142.124.66", + "185.142.124.67", + "185.142.124.68", + "185.142.124.69", + "185.142.124.70", + "185.142.124.103", + "185.142.124.241", + "185.142.125.6", + "185.142.125.8", + "185.142.125.13", + "185.142.125.14", + "185.142.125.46", + "185.142.125.60", + "185.142.125.63", + "185.142.125.64", + "185.142.125.84", + "185.142.125.110", + "185.142.125.119", + "185.142.125.123", + "185.142.125.132", + "185.142.125.133", + "185.142.125.143", + "185.142.125.151", + "185.142.125.155", + "185.142.125.160", + "185.142.125.170", + "185.142.125.181", + "185.142.125.212", + "185.142.125.216", + "185.142.125.220", + "185.142.126.2", + "185.142.126.25", + "185.142.126.27", + "185.142.126.28", + "185.142.126.30", + "185.142.126.31", + "185.142.126.50", + "185.142.126.59", + "185.142.126.65", + "185.142.126.66", + "185.142.126.76", + "185.142.126.82", + "185.142.126.96", + "185.142.126.130", + "185.142.126.134", + "185.142.126.177", + "185.142.126.184", + "185.142.126.188", + "185.142.126.198", + "185.142.126.224", + "185.142.126.226", + "185.142.126.236", + "185.142.126.243", + "185.142.127.58", + "185.142.127.76", + "185.142.127.101", + "185.142.127.109", + "185.142.127.126", + "185.142.127.131", + "185.142.127.161", + "185.142.127.166", + "185.142.127.212", + "185.142.127.214", + "185.142.156.216", + "185.142.156.226", + "185.142.157.54", + "185.142.157.94", + "185.142.157.250", + "185.142.159.66", + "185.142.159.85", + "185.142.159.100", + "185.142.159.110", + "185.142.159.170", + "185.142.159.187", + "185.142.159.250", + "185.142.159.252", + "185.143.205.13", + "185.143.205.62", + "185.143.205.96", + "185.143.205.141", + "185.143.205.146", + "185.143.205.150", + "185.143.205.160", + "185.143.205.226", + "185.143.207.3", + "185.143.207.13", + "185.143.207.92", + "185.143.207.111", + "185.143.207.114", + "185.143.207.117", + "185.143.207.130", + "185.143.207.142", + "185.143.207.148", + "185.143.207.150", + "185.143.207.151", + "185.143.207.182", + "185.143.207.253", + "185.145.8.254", + "185.145.9.76", + "185.145.9.80", + "185.145.9.161", + "185.145.184.91", + "185.145.184.94", + "185.145.184.145", + "185.145.184.147", + "185.145.184.148", + "185.145.184.151", + "185.145.184.175", + "185.145.184.183", + "185.145.184.192", + "185.145.184.193", + "185.145.184.195", + "185.145.184.235", + "185.145.185.13", + "185.145.185.14", + "185.145.185.15", + "185.145.185.87", + "185.145.185.96", + "185.145.185.98", + "185.145.185.99", + "185.145.185.111", + "185.145.185.127", + "185.145.185.132", + "185.145.185.170", + "185.145.185.176", + "185.145.186.45", + "185.145.186.80", + "185.145.186.134", + "185.145.186.141", + "185.145.186.161", + "185.145.186.170", + "185.145.186.240", + "185.145.186.241", + "185.145.186.242", + "185.145.186.243", + "185.145.186.244", + "185.145.186.245", + "185.145.186.246", + "185.145.186.247", + "185.145.187.13", + "185.145.187.161", + "185.145.187.169", + "185.145.187.190", + "185.145.187.196", + "185.145.187.221", + "185.145.187.229", + "185.145.187.235", + "185.145.187.241", + "185.145.187.243", + "185.147.40.10", + "185.147.40.39", + "185.147.40.88", + "185.147.40.210", + "185.147.41.28", + "185.147.41.42", + "185.147.41.176", + "185.147.160.66", + "185.147.160.101", + "185.147.161.195", + "185.147.161.200", + "185.147.161.205", + "185.147.162.11", + "185.147.163.206", + "185.149.192.20", + "185.149.192.27", + "185.149.192.37", + "185.149.192.41", + "185.149.192.137", + "185.149.192.138", + "185.153.185.11", + "185.153.185.254", + "185.153.208.6", + "185.155.9.102", + "185.155.10.3", + "185.155.10.6", + "185.155.10.7", + "185.155.10.60", + "185.155.10.66", + "185.155.10.163", + "185.155.10.169", + "185.155.10.212", + "185.155.14.83", + "185.155.14.95", + "185.155.14.126", + "185.155.14.174", + "185.155.14.199", + "185.155.14.226", + "185.155.15.27", + "185.155.15.56", + "185.155.15.92", + "185.155.15.189", + "185.155.15.244", + "185.158.172.26", + "185.158.172.30", + "185.158.172.116", + "185.158.172.129", + "185.158.172.245", + "185.158.173.53", + "185.158.173.119", + "185.158.173.181", + "185.158.174.73", + "185.158.174.237", + "185.159.153.14", + "185.159.153.28", + "185.159.153.41", + "185.159.153.117", + "185.159.153.144", + "185.159.153.188", + "185.159.153.195", + "185.159.153.206", + "185.159.153.217", + "185.159.153.229", + "185.159.154.12", + "185.159.154.121", + "185.159.154.122", + "185.159.154.148", + "185.159.154.156", + "185.159.154.203", + "185.160.205.6", + "185.161.38.18", + "185.161.38.117", + "185.161.38.121", + "185.161.38.145", + "185.161.38.253", + "185.161.39.72", + "185.161.39.73", + "185.161.39.77", + "185.161.39.116", + "185.161.39.170", + "185.161.39.194", + "185.161.39.195", + "185.161.39.196", + "185.161.39.197", + "185.161.39.222", + "185.161.112.231", + "185.161.112.249", + "185.161.113.130", + "185.161.113.134", + "185.161.113.226", + "185.161.113.228", + "185.161.113.242", + "185.161.114.10", + "185.161.114.22", + "185.161.114.74", + "185.161.114.138", + "185.161.114.189", + "185.161.115.178", + "185.161.115.243", + "185.162.40.27", + "185.162.41.69", + "185.162.41.122", + "185.164.72.41", + "185.164.72.54", + "185.164.72.90", + "185.164.72.123", + "185.164.72.158", + "185.164.72.234", + "185.164.73.172", + "185.164.73.253", + "185.164.74.240", + "185.164.252.27", + "185.164.252.28", + "185.164.252.130", + "185.164.253.24", + "185.165.30.80", + "185.165.40.11", + "185.165.41.221", + "185.165.41.222", + "185.165.43.11", + "185.165.116.18", + "185.165.116.33", + "185.165.116.35", + "185.165.116.138", + "185.165.116.147", + "185.165.116.240", + "185.165.118.55", + "185.165.118.65", + "185.165.119.49", + "185.166.104.55", + "185.169.6.62", + "185.169.6.120", + "185.169.6.197", + "185.169.6.204", + "185.169.20.34", + "185.169.20.74", + "185.169.20.234", + "185.169.21.50", + "185.169.21.118", + "185.169.22.102", + "185.170.236.18", + "185.170.236.125", + "185.170.236.130", + "185.170.236.145", + "185.170.236.171", + "185.170.236.205", + "185.170.237.141", + "185.170.237.161", + "185.170.237.204", + "185.170.237.209", + "185.170.238.64", + "185.170.238.70", + "185.170.238.159", + "185.170.238.194", + "185.170.238.200", + "185.170.239.5", + "185.170.239.59", + "185.170.239.72", + "185.170.239.130", + "185.170.239.150", + "185.170.239.210", + "185.171.53.150", + "185.171.54.18", + "185.171.54.20", + "185.171.54.21", + "185.171.54.26", + "185.171.54.29", + "185.171.54.30", + "185.171.54.34", + "185.171.54.35", + "185.171.54.36", + "185.171.54.42", + "185.171.54.67", + "185.171.54.68", + "185.171.54.69", + "185.171.54.84", + "185.171.54.86", + "185.171.54.90", + "185.171.54.138", + "185.171.54.139", + "185.171.54.140", + "185.171.54.141", + "185.171.54.156", + "185.171.54.170", + "185.171.54.234", + "185.171.54.237", + "185.171.54.238", + "185.171.55.138", + "185.171.55.139", + "185.171.55.140", + "185.171.55.141", + "185.171.55.162", + "185.171.55.163", + "185.171.55.164", + "185.171.55.219", + "185.171.55.220", + "185.171.55.221", + "185.171.55.222", + "185.171.55.233", + "185.172.0.42", + "185.172.0.206", + "185.172.0.218", + "185.172.0.238", + "185.172.1.70", + "185.172.1.126", + "185.172.1.202", + "185.172.1.214", + "185.172.2.194", + "185.172.2.195", + "185.172.2.196", + "185.172.2.198", + "185.172.3.162", + "185.172.3.242", + "185.172.68.1", + "185.172.68.3", + "185.172.68.4", + "185.172.68.13", + "185.172.68.16", + "185.172.68.31", + "185.172.68.32", + "185.172.68.37", + "185.172.68.41", + "185.172.68.64", + "185.172.68.71", + "185.172.68.75", + "185.172.68.76", + "185.172.69.9", + "185.172.69.37", + "185.172.70.70", + "185.173.104.53", + "185.173.104.194", + "185.173.104.200", + "185.173.104.208", + "185.173.105.78", + "185.173.105.141", + "185.173.105.142", + "185.173.106.117", + "185.173.107.26", + "185.173.129.35", + "185.173.129.91", + "185.173.129.185", + "185.173.129.203", + "185.173.129.240", + "185.173.129.243", + "185.174.132.21", + "185.174.132.25", + "185.174.132.130", + "185.174.132.217", + "185.174.132.254", + "185.174.134.66", + "185.175.76.162", + "185.175.76.164", + "185.175.76.165", + "185.175.76.166", + "185.175.77.67", + "185.175.79.89", + "185.175.79.177", + "185.175.79.193", + "185.176.32.38", + "185.176.33.38", + "185.176.34.16", + "185.176.34.25", + "185.176.34.94", + "185.176.35.130", + "185.176.56.253", + "185.176.57.98", + "185.176.57.138", + "185.176.59.48", + "185.176.59.49", + "185.176.59.201", + "185.176.59.209", + "185.176.59.246", + "185.176.59.254", + "185.177.158.50", + "185.177.158.53", + "185.177.158.54", + "185.177.158.60", + "185.177.159.84", + "185.177.159.227", + "185.177.159.247", + "185.179.90.107", + "185.179.168.3", + "185.179.168.4", + "185.179.168.13", + "185.179.168.14", + "185.179.168.16", + "185.179.168.17", + "185.179.168.18", + "185.179.168.19", + "185.179.168.31", + "185.179.168.36", + "185.179.168.47", + "185.179.168.114", + "185.179.168.115", + "185.179.168.116", + "185.179.168.117", + "185.179.168.118", + "185.179.168.119", + "185.179.168.120", + "185.179.168.121", + "185.179.168.122", + "185.179.168.123", + "185.179.168.124", + "185.179.168.125", + "185.179.168.126", + "185.179.168.169", + "185.179.169.28", + "185.179.169.82", + "185.179.169.141", + "185.179.169.166", + "185.179.169.169", + "185.179.169.180", + "185.179.169.203", + "185.179.169.208", + "185.179.169.212", + "185.179.169.221", + "185.179.169.244", + "185.179.170.2", + "185.179.170.124", + "185.179.170.127", + "185.179.170.217", + "185.179.170.230", + "185.179.170.239", + "185.179.171.0", + "185.179.171.25", + "185.179.171.55", + "185.179.171.72", + "185.179.171.110", + "185.179.171.121", + "185.179.171.130", + "185.179.171.200", + "185.179.221.3", + "185.179.221.168", + "185.180.128.22", + "185.180.128.54", + "185.180.128.64", + "185.180.128.69", + "185.180.128.70", + "185.180.128.100", + "185.180.129.2", + "185.180.129.4", + "185.180.129.5", + "185.180.129.6", + "185.180.129.7", + "185.180.129.40", + "185.180.129.41", + "185.180.129.100", + "185.180.129.138", + "185.180.129.142", + "185.180.129.216", + "185.180.129.222", + "185.180.129.228", + "185.180.129.229", + "185.180.129.230", + "185.180.129.231", + "185.180.129.234", + "185.180.130.1", + "185.180.130.8", + "185.180.130.47", + "185.180.130.133", + "185.180.130.136", + "185.180.130.182", + "185.180.130.184", + "185.180.130.231", + "185.180.130.240", + "185.180.130.246", + "185.180.130.254", + "185.181.180.138", + "185.181.181.64", + "185.181.181.65", + "185.181.181.67", + "185.181.181.68", + "185.181.181.69", + "185.181.181.70", + "185.181.181.71", + "185.181.181.72", + "185.181.181.73", + "185.181.181.75", + "185.181.181.77", + "185.181.181.79", + "185.181.181.167", + "185.181.182.39", + "185.181.182.61", + "185.181.182.141", + "185.181.182.169", + "185.181.182.235", + "185.181.183.4", + "185.181.183.12", + "185.181.183.17", + "185.181.183.51", + "185.181.183.100", + "185.181.183.105", + "185.181.183.117", + "185.186.50.221", + "185.186.51.49", + "185.186.240.9", + "185.186.240.64", + "185.186.240.65", + "185.186.240.66", + "185.186.240.67", + "185.186.240.177", + "185.186.240.179", + "185.186.240.180", + "185.186.240.182", + "185.186.240.232", + "185.186.242.66", + "185.186.242.146", + "185.186.243.23", + "185.186.243.30", + "185.186.243.31", + "185.186.243.39", + "185.186.243.48", + "185.186.243.57", + "185.186.243.98", + "185.186.243.99", + "185.186.243.112", + "185.186.243.124", + "185.186.243.148", + "185.186.243.176", + "185.186.243.240", + "185.186.243.241", + "185.186.243.243", + "185.186.243.245", + "185.186.243.246", + "185.187.48.30", + "185.187.48.74", + "185.187.48.122", + "185.187.49.12", + "185.187.49.15", + "185.187.49.56", + "185.187.49.60", + "185.187.49.70", + "185.187.50.39", + "185.187.50.43", + "185.187.51.0", + "185.187.51.32", + "185.187.51.42", + "185.187.51.48", + "185.187.51.49", + "185.187.51.94", + "185.187.51.245", + "185.187.51.255", + "185.187.87.253", + "185.188.112.149", + "185.188.112.154", + "185.188.112.172", + "185.188.112.193", + "185.188.112.207", + "185.188.113.21", + "185.188.113.104", + "185.188.113.113", + "185.188.115.5", + "185.188.115.10", + "185.190.39.59", + "185.190.39.242", + "185.191.76.26", + "185.191.77.170", + "185.191.77.174", + "185.191.77.253", + "185.191.78.86", + "185.191.78.233", + "185.191.78.245", + "185.191.79.6", + "185.191.79.154", + "185.191.79.210", + "185.192.112.69", + "185.192.112.73", + "185.192.112.143", + "185.192.112.252", + "185.192.113.83", + "185.192.113.87", + "185.192.113.108", + "185.192.113.121", + "185.192.113.148", + "185.196.148.111", + "185.196.151.7", + "185.201.48.17", + "185.201.48.141", + "185.201.48.172", + "185.201.48.184", + "185.201.48.231", + "185.201.49.146", + "185.201.50.90", + "185.201.51.41", + "185.201.51.67", + "185.201.51.69", + "185.201.51.75", + "185.201.51.76", + "185.201.51.116", + "185.201.51.161", + "185.201.51.217", + "185.201.51.221", + "185.201.51.229", + "185.202.56.22", + "185.202.56.74", + "185.202.56.86", + "185.202.56.98", + "185.202.56.102", + "185.202.56.210", + "185.202.56.214", + "185.202.58.150", + "185.202.58.183", + "185.202.58.236", + "185.202.59.114", + "185.204.182.10", + "185.204.197.53", + "185.204.197.149", + "185.204.197.226", + "185.205.203.242", + "185.206.92.63", + "185.206.93.203", + "185.206.94.42", + "185.206.95.9", + "185.206.229.21", + "185.206.229.22", + "185.206.229.23", + "185.206.229.24", + "185.206.229.25", + "185.206.229.30", + "185.206.229.31", + "185.206.229.32", + "185.206.229.35", + "185.206.229.36", + "185.206.229.50", + "185.206.229.72", + "185.206.229.103", + "185.206.229.104", + "185.206.231.31", + "185.206.231.68", + "185.206.231.79", + "185.206.231.216", + "185.206.238.178", + "185.208.76.101", + "185.208.76.102", + "185.208.76.103", + "185.208.76.104", + "185.208.76.105", + "185.208.76.106", + "185.208.77.85", + "185.208.77.178", + "185.208.77.179", + "185.208.78.100", + "185.208.78.101", + "185.208.79.95", + "185.208.148.3", + "185.208.148.48", + "185.208.148.216", + "185.208.148.241", + "185.208.149.138", + "185.208.174.44", + "185.208.174.68", + "185.208.174.153", + "185.208.175.11", + "185.208.175.167", + "185.208.175.209", + "185.208.175.235", + "185.208.180.9", + "185.208.180.170", + "185.208.181.66", + "185.208.181.67", + "185.208.181.68", + "185.208.181.86", + "185.208.182.3", + "185.208.182.77", + "185.208.182.117", + "185.208.183.14", + "185.208.183.164", + "185.208.183.188", + "185.209.42.6", + "185.209.42.50", + "185.209.42.95", + "185.211.56.126", + "185.211.57.166", + "185.211.58.79", + "185.211.58.146", + "185.211.59.115", + "185.211.59.134", + "185.211.59.160", + "185.212.50.5", + "185.212.50.10", + "185.212.51.73", + "185.212.51.119", + "185.212.51.144", + "185.212.51.160", + "185.212.51.201", + "185.212.51.202", + "185.212.51.203", + "185.212.51.204", + "185.212.51.205", + "185.212.51.206", + "185.212.51.241", + "185.212.193.4", + "185.212.193.21", + "185.212.193.22", + "185.212.193.23", + "185.212.193.97", + "185.212.193.160", + "185.212.193.162", + "185.212.193.163", + "185.212.193.165", + "185.212.193.170", + "185.212.195.34", + "185.212.195.35", + "185.212.195.36", + "185.212.195.37", + "185.212.195.38", + "185.212.195.41", + "185.212.195.58", + "185.212.195.113", + "185.212.195.114", + "185.212.195.116", + "185.212.195.154", + "185.212.195.161", + "185.212.195.167", + "185.212.195.174", + "185.212.195.177", + "185.212.195.178", + "185.212.195.254", + "185.213.10.99", + "185.213.10.101", + "185.213.11.23", + "185.213.11.59", + "185.213.11.75", + "185.213.11.77", + "185.213.11.93", + "185.213.164.4", + "185.213.164.5", + "185.213.164.25", + "185.213.164.105", + "185.213.164.151", + "185.213.164.191", + "185.213.164.242", + "185.213.165.30", + "185.213.165.31", + "185.213.165.32", + "185.213.165.62", + "185.213.165.70", + "185.213.165.82", + "185.213.165.97", + "185.213.165.104", + "185.213.165.224", + "185.214.39.156", + "185.215.154.23", + "185.217.6.30", + "185.217.6.130", + "185.218.139.80", + "185.218.139.100", + "185.218.139.133", + "185.221.192.6", + "185.221.192.27", + "185.221.192.28", + "185.221.192.101", + "185.221.192.215", + "185.221.194.133", + "185.221.194.195", + "185.221.194.228", + "185.221.194.238", + "185.221.194.250", + "185.221.194.252", + "185.221.195.94", + "185.221.195.95", + "185.221.195.227", + "185.221.195.235", + "185.221.239.51", + "185.221.239.59", + "185.221.239.73", + "185.221.239.89", + "185.221.239.92", + "185.221.239.164", + "185.221.239.220", + "185.222.163.1", + "185.222.163.2", + "185.222.163.65", + "185.222.163.81", + "185.222.163.89", + "185.222.163.93", + "185.222.163.97", + "185.222.163.165", + "185.222.163.169", + "185.222.163.177", + "185.222.163.185", + "185.222.163.193", + "185.222.163.201", + "185.222.163.209", + "185.224.176.14", + "185.224.176.24", + "185.224.176.41", + "185.224.176.82", + "185.224.176.112", + "185.224.176.125", + "185.224.176.127", + "185.224.176.159", + "185.224.176.163", + "185.224.176.177", + "185.224.176.190", + "185.224.176.215", + "185.224.176.220", + "185.224.177.33", + "185.224.177.91", + "185.224.177.112", + "185.224.177.163", + "185.224.177.187", + "185.224.177.244", + "185.224.179.16", + "185.224.179.17", + "185.224.179.27", + "185.224.179.38", + "185.224.179.45", + "185.224.179.47", + "185.224.179.61", + "185.224.179.75", + "185.224.179.81", + "185.224.179.90", + "185.224.179.118", + "185.224.179.176", + "185.224.179.186", + "185.224.179.204", + "185.224.179.213", + "185.224.179.227", + "185.225.80.25", + "185.225.80.73", + "185.225.80.102", + "185.225.80.130", + "185.225.80.233", + "185.225.80.234", + "185.225.82.154", + "185.225.82.162", + "185.225.82.195", + "185.225.83.64", + "185.225.183.2", + "185.225.240.138", + "185.225.240.150", + "185.225.241.230", + "185.225.241.242", + "185.225.241.250", + "185.225.242.162", + "185.225.242.178", + "185.225.242.243", + "185.225.243.83", + "185.225.243.84", + "185.225.243.86", + "185.225.243.87", + "185.225.243.88", + "185.225.243.89", + "185.225.243.91", + "185.225.243.94", + "185.226.116.89", + "185.226.116.243", + "185.226.117.8", + "185.226.117.111", + "185.226.117.156", + "185.226.117.161", + "185.226.117.193", + "185.226.117.231", + "185.226.118.2", + "185.226.119.93", + "185.226.119.113", + "185.226.119.143", + "185.226.119.220", + "185.228.59.189", + "185.229.28.46", + "185.229.31.47", + "185.229.31.73", + "185.229.31.171", + "185.229.31.193", + "185.229.133.40", + "185.229.204.98", + "185.229.204.99", + "185.229.204.100", + "185.229.204.101", + "185.229.204.145", + "185.231.65.2", + "185.231.65.213", + "185.231.112.64", + "185.231.112.114", + "185.231.112.169", + "185.231.115.28", + "185.231.115.126", + "185.231.115.201", + "185.231.115.248", + "185.231.180.157", + "185.231.181.20", + "185.231.181.30", + "185.231.182.164", + "185.231.182.250", + "185.231.183.143", + "185.232.154.125", + "185.233.131.19", + "185.233.131.30", + "185.233.131.152", + "185.233.131.154", + "185.233.131.156", + "185.233.131.170", + "185.233.131.171", + "185.233.131.183", + "185.233.131.186", + "185.235.136.161", + "185.235.136.184", + "185.235.139.9", + "185.235.139.41", + "185.235.139.57", + "185.235.139.70", + "185.235.139.151", + "185.235.196.3", + "185.235.196.6", + "185.235.196.21", + "185.235.196.41", + "185.235.196.42", + "185.235.196.44", + "185.235.196.51", + "185.235.196.54", + "185.235.196.58", + "185.235.196.75", + "185.235.196.91", + "185.235.196.105", + "185.235.196.109", + "185.235.196.118", + "185.235.196.144", + "185.235.197.39", + "185.235.245.149", + "185.235.245.186", + "185.236.36.154", + "185.236.37.101", + "185.236.37.199", + "185.236.38.41", + "185.236.38.42", + "185.236.38.43", + "185.236.38.47", + "185.236.38.84", + "185.237.84.36", + "185.237.84.49", + "185.237.84.188", + "185.237.84.195", + "185.237.84.241", + "185.237.84.243", + "185.237.84.245", + "185.237.85.220", + "185.237.86.140", + "185.237.87.6", + "185.237.87.65", + "185.237.87.99", + "185.237.87.128", + "185.237.87.141", + "185.237.87.158", + "185.237.87.165", + "185.237.87.195", + "185.237.87.214", + "185.238.44.24", + "185.238.44.61", + "185.238.44.72", + "185.238.44.121", + "185.238.44.134", + "185.238.44.205", + "185.238.44.212", + "185.238.44.215", + "185.238.44.232", + "185.238.44.234", + "185.238.45.15", + "185.238.45.17", + "185.238.45.25", + "185.238.45.47", + "185.238.45.71", + "185.238.45.76", + "185.238.45.119", + "185.238.45.121", + "185.238.45.129", + "185.238.45.156", + "185.238.45.173", + "185.238.45.176", + "185.238.45.181", + "185.238.45.211", + "185.238.45.214", + "185.238.45.246", + "185.238.45.250", + "185.238.46.8", + "185.238.46.18", + "185.238.46.54", + "185.238.46.59", + "185.238.46.62", + "185.238.46.118", + "185.238.46.138", + "185.238.46.140", + "185.238.46.146", + "185.238.46.158", + "185.238.46.164", + "185.238.46.172", + "185.238.46.181", + "185.238.46.200", + "185.238.46.214", + "185.238.46.215", + "185.238.46.232", + "185.238.46.237", + "185.238.46.244", + "185.238.47.6", + "185.238.47.11", + "185.238.47.13", + "185.238.47.21", + "185.238.47.36", + "185.238.47.62", + "185.238.47.109", + "185.238.47.111", + "185.238.47.115", + "185.238.47.124", + "185.238.47.147", + "185.238.47.152", + "185.238.47.185", + "185.238.47.199", + "185.238.47.216", + "185.238.47.252", + "185.238.140.75", + "185.239.0.72", + "185.239.1.30", + "185.239.1.62", + "185.239.2.3", + "185.239.3.51", + "185.239.3.69", + "185.239.3.166", + "185.239.3.189", + "185.239.106.121", + "185.243.48.103", + "185.243.48.133", + "185.243.48.190", + "185.243.49.158", + "185.243.50.1", + "185.243.50.2", + "185.246.6.94", + "185.246.6.102", + "185.251.76.53", + "185.251.76.193", + "185.251.76.194", + "185.251.77.130", + "185.252.29.116", + "185.252.29.233", + "185.252.30.130", + "185.252.31.36", + "185.252.31.58", + "185.252.31.68", + "185.252.31.113", + "185.252.31.130", + "185.252.31.156", + "185.252.31.200", + "185.252.31.210", + "185.252.86.16", + "185.252.86.40", + "185.252.86.154", + "185.252.86.182", + "185.255.88.108", + "185.255.88.114", + "185.255.88.138", + "185.255.88.155", + "185.255.88.166", + "185.255.88.204", + "185.255.88.237", + "185.255.89.69", + "185.255.89.80", + "185.255.89.100", + "185.255.89.101", + "185.255.89.108", + "185.255.89.130", + "185.255.89.146", + "185.255.89.156", + "185.255.89.158", + "185.255.89.164", + "185.255.89.246", + "185.255.90.24", + "185.255.90.114", + "185.255.90.130", + "185.255.90.140", + "185.255.90.147", + "185.255.91.1", + "185.255.91.2", + "185.255.91.10", + "185.255.91.42", + "185.255.91.84", + "185.255.91.207", + "185.255.208.35", + "185.255.208.179", + "185.255.208.194", + "185.255.209.132", + "185.255.209.134", + "185.255.209.137", + "185.255.209.224", + "185.255.209.227", + "185.255.209.253", + "185.255.210.32", + "185.255.210.151", + "185.255.210.152", + "185.255.210.167", + "185.255.210.175", + "185.255.210.195", + "185.255.210.198", + "185.255.210.199", + "185.255.210.209", + "185.255.211.6", + "185.255.211.30", + "185.255.211.48", + "185.255.211.67", + "185.255.211.121", + "185.255.211.150", + "185.255.211.158", + "185.255.211.195", + "188.0.240.2", + "188.0.240.3", + "188.0.240.5", + "188.0.240.6", + "188.0.241.253", + "188.0.244.246", + "188.0.244.254", + "188.0.245.1", + "188.0.246.55", + "188.0.246.94", + "188.0.247.47", + "188.0.247.245", + "188.0.249.124", + "188.0.249.204", + "188.0.249.219", + "188.0.251.180", + "188.0.252.126", + "188.0.254.153", + "188.0.255.161", + "188.75.65.201", + "188.75.78.42", + "188.75.78.131", + "188.75.78.173", + "188.75.78.181", + "188.75.80.42", + "188.75.80.73", + "188.75.80.192", + "188.75.80.226", + "188.75.80.227", + "188.75.80.228", + "188.75.80.234", + "188.75.81.162", + "188.75.81.163", + "188.75.81.165", + "188.75.81.166", + "188.75.87.195", + "188.75.88.147", + "188.75.88.148", + "188.75.88.149", + "188.75.88.150", + "188.75.93.203", + "188.75.93.227", + "188.75.104.114", + "188.75.104.115", + "188.75.104.116", + "188.75.104.117", + "188.75.104.118", + "188.75.126.18", + "188.75.126.74", + "188.94.188.1", + "188.121.96.87", + "188.121.100.30", + "188.121.100.110", + "188.121.100.111", + "188.121.100.169", + "188.121.100.176", + "188.121.100.203", + "188.121.101.25", + "188.121.101.133", + "188.121.101.140", + "188.121.102.5", + "188.121.102.200", + "188.121.103.53", + "188.121.103.86", + "188.121.103.248", + "188.121.104.17", + "188.121.104.40", + "188.121.105.16", + "188.121.105.93", + "188.121.105.118", + "188.121.105.171", + "188.121.106.202", + "188.121.107.141", + "188.121.107.164", + "188.121.108.55", + "188.121.108.56", + "188.121.109.133", + "188.121.110.36", + "188.121.110.61", + "188.121.110.172", + "188.121.110.202", + "188.121.111.40", + "188.121.111.127", + "188.121.112.15", + "188.121.113.136", + "188.121.116.47", + "188.121.116.238", + "188.121.117.164", + "188.121.117.166", + "188.121.118.101", + "188.121.118.105", + "188.121.118.128", + "188.121.118.162", + "188.121.118.197", + "188.121.118.206", + "188.121.118.216", + "188.121.119.99", + "188.121.119.145", + "188.121.119.191", + "188.121.119.217", + "188.121.120.91", + "188.121.120.194", + "188.121.122.208", + "188.121.123.70", + "188.121.123.188", + "188.121.124.67", + "188.121.128.40", + "188.121.129.122", + "188.121.129.226", + "188.121.129.227", + "188.121.129.228", + "188.121.129.238", + "188.121.144.30", + "188.121.144.162", + "188.121.144.194", + "188.121.144.238", + "188.121.145.6", + "188.121.145.22", + "188.121.145.78", + "188.121.145.122", + "188.121.145.154", + "188.121.145.182", + "188.121.146.50", + "188.121.146.142", + "188.121.146.198", + "188.121.146.226", + "188.121.147.34", + "188.121.147.38", + "188.121.147.42", + "188.121.147.46", + "188.121.147.50", + "188.121.147.58", + "188.121.147.62", + "188.121.147.66", + "188.121.147.70", + "188.121.147.74", + "188.121.147.78", + "188.121.147.90", + "188.121.147.118", + "188.121.147.150", + "188.121.147.166", + "188.121.148.46", + "188.121.148.66", + "188.121.148.94", + "188.121.148.162", + "188.121.148.174", + "188.121.148.186", + "188.121.148.190", + "188.121.149.50", + "188.121.149.62", + "188.121.149.114", + "188.121.149.138", + "188.121.149.186", + "188.121.149.242", + "188.121.156.211", + "188.121.157.133", + "188.121.157.170", + "188.121.157.181", + "188.121.157.186", + "188.121.157.250", + "188.121.159.2", + "188.121.159.34", + "188.121.159.36", + "188.121.159.37", + "188.136.130.1", + "188.136.130.160", + "188.136.133.33", + "188.136.133.111", + "188.136.133.114", + "188.136.133.123", + "188.136.133.218", + "188.136.133.224", + "188.136.133.225", + "188.136.133.243", + "188.136.142.81", + "188.136.142.133", + "188.136.142.134", + "188.136.142.166", + "188.136.143.13", + "188.136.143.20", + "188.136.144.38", + "188.136.144.131", + "188.136.144.143", + "188.136.154.38", + "188.136.154.51", + "188.136.154.137", + "188.136.154.186", + "188.136.154.187", + "188.136.154.188", + "188.136.154.189", + "188.136.155.41", + "188.136.162.21", + "188.136.162.30", + "188.136.162.104", + "188.136.162.213", + "188.136.162.216", + "188.136.162.217", + "188.136.162.218", + "188.136.162.220", + "188.136.162.221", + "188.136.165.1", + "188.136.172.33", + "188.136.172.34", + "188.136.172.35", + "188.136.172.37", + "188.136.172.38", + "188.136.172.44", + "188.136.172.45", + "188.136.172.47", + "188.136.172.53", + "188.136.172.76", + "188.136.172.83", + "188.136.172.86", + "188.136.172.97", + "188.136.172.101", + "188.136.172.103", + "188.136.172.120", + "188.136.172.121", + "188.136.172.126", + "188.136.172.127", + "188.136.172.129", + "188.136.172.132", + "188.136.172.136", + "188.136.172.142", + "188.136.172.148", + "188.136.172.152", + "188.136.172.157", + "188.136.172.158", + "188.136.172.160", + "188.136.172.161", + "188.136.172.165", + "188.136.172.172", + "188.136.172.176", + "188.136.172.183", + "188.136.172.184", + "188.136.172.185", + "188.136.172.186", + "188.136.172.187", + "188.136.172.188", + "188.136.172.189", + "188.136.172.190", + "188.136.172.193", + "188.136.172.224", + "188.136.172.226", + "188.136.172.227", + "188.136.172.229", + "188.136.172.230", + "188.136.172.231", + "188.136.172.232", + "188.136.172.234", + "188.136.172.235", + "188.136.172.237", + "188.136.172.240", + "188.136.172.241", + "188.136.172.242", + "188.136.172.245", + "188.136.172.247", + "188.136.172.250", + "188.136.172.251", + "188.136.172.253", + "188.136.174.34", + "188.136.174.35", + "188.136.174.42", + "188.136.174.86", + "188.136.174.118", + "188.136.174.130", + "188.136.174.131", + "188.136.174.133", + "188.136.174.134", + "188.136.174.214", + "188.136.174.234", + "188.136.174.242", + "188.136.194.54", + "188.136.194.238", + "188.136.196.1", + "188.136.196.29", + "188.136.196.36", + "188.136.196.125", + "188.136.196.164", + "188.136.196.167", + "188.136.196.178", + "188.136.196.180", + "188.136.196.216", + "188.136.196.217", + "188.136.196.220", + "188.136.196.221", + "188.136.196.222", + "188.136.196.224", + "188.136.196.226", + "188.136.196.228", + "188.136.196.229", + "188.136.208.6", + "188.136.208.82", + "188.136.208.83", + "188.136.208.114", + "188.136.208.210", + "188.136.208.211", + "188.136.208.212", + "188.136.208.242", + "188.136.208.244", + "188.136.208.250", + "188.136.208.251", + "188.136.208.253", + "188.136.208.254", + "188.208.56.88", + "188.208.56.94", + "188.208.56.134", + "188.208.56.230", + "188.208.63.30", + "188.208.63.66", + "188.208.144.7", + "188.208.144.84", + "188.208.144.141", + "188.208.144.191", + "188.208.144.218", + "188.208.145.14", + "188.208.145.39", + "188.208.145.67", + "188.208.145.83", + "188.208.145.97", + "188.208.145.124", + "188.208.145.184", + "188.208.145.187", + "188.208.146.177", + "188.208.146.210", + "188.208.147.201", + "188.208.149.13", + "188.208.149.118", + "188.208.149.200", + "188.208.149.213", + "188.208.149.246", + "188.208.150.5", + "188.208.150.68", + "188.208.150.73", + "188.208.150.91", + "188.208.150.117", + "188.208.150.161", + "188.208.150.165", + "188.208.150.171", + "188.208.150.174", + "188.208.150.179", + "188.208.150.229", + "188.208.151.8", + "188.208.151.20", + "188.208.151.188", + "188.208.151.194", + "188.208.151.216", + "188.208.151.231", + "188.208.151.235", + "188.208.151.239", + "188.208.152.48", + "188.209.5.105", + "188.209.7.237", + "188.209.64.130", + "188.209.67.86", + "188.209.68.119", + "188.209.68.154", + "188.209.70.75", + "188.209.71.99", + "188.209.73.96", + "188.209.73.213", + "188.209.75.111", + "188.209.152.41", + "188.209.152.44", + "188.209.152.45", + "188.210.87.44", + "188.210.101.67", + "188.210.102.82", + "188.210.106.213", + "188.210.108.144", + "188.210.111.45", + "188.210.119.248", + "188.210.122.152", + "188.210.124.2", + "188.211.0.21", + "188.211.36.106", + "188.211.37.41", + "188.211.37.66", + "188.211.37.241", + "188.211.38.53", + "188.211.38.123", + "188.211.39.239", + "188.211.64.58", + "188.211.67.61", + "188.211.68.251", + "188.211.71.224", + "188.211.72.217", + "188.211.73.160", + "188.211.74.213", + "188.211.75.219", + "188.211.77.39", + "188.211.81.212", + "188.211.83.2", + "188.211.83.111", + "188.211.85.38", + "188.211.86.144", + "188.211.87.74", + "188.211.87.109", + "188.211.88.213", + "188.211.94.162", + "188.211.97.75", + "188.211.97.191", + "188.211.101.64", + "188.211.102.1", + "188.211.102.63", + "188.211.103.103", + "188.211.104.86", + "188.211.106.247", + "188.211.108.91", + "188.211.108.225", + "188.211.108.233", + "188.211.109.198", + "188.211.109.231", + "188.211.111.1", + "188.211.111.88", + "188.211.112.52", + "188.211.116.32", + "188.211.116.106", + "188.211.117.99", + "188.211.119.76", + "188.211.119.151", + "188.211.120.127", + "188.211.122.83", + "188.211.122.114", + "188.211.122.196", + "188.211.200.215", + "188.211.201.191", + "188.211.202.114", + "188.211.203.139", + "188.211.204.58", + "188.211.205.92", + "188.211.205.231", + "188.211.206.93", + "188.211.206.213", + "188.212.22.1", + "188.212.22.9", + "188.212.22.33", + "188.212.22.65", + "188.212.22.129", + "188.212.22.137", + "188.212.22.141", + "188.212.22.145", + "188.212.22.162", + "188.212.22.177", + "188.212.22.181", + "188.212.22.193", + "188.212.22.225", + "188.212.22.233", + "188.212.22.241", + "188.212.22.245", + "188.212.64.211", + "188.212.65.46", + "188.212.66.9", + "188.212.66.23", + "188.212.66.156", + "188.212.68.65", + "188.212.68.198", + "188.212.71.143", + "188.212.71.247", + "188.212.73.158", + "188.212.74.73", + "188.212.79.110", + "188.212.82.196", + "188.212.85.146", + "188.212.85.156", + "188.212.88.208", + "188.212.88.236", + "188.212.89.105", + "188.212.89.188", + "188.212.90.171", + "188.212.92.230", + "188.212.92.247", + "188.212.93.138", + "188.212.93.197", + "188.212.95.78", + "188.212.148.162", + "188.212.160.208", + "188.212.161.115", + "188.212.169.84", + "188.212.171.199", + "188.212.172.58", + "188.212.173.54", + "188.212.177.247", + "188.212.178.9", + "188.212.178.118", + "188.212.178.182", + "188.212.183.13", + "188.212.183.19", + "188.212.186.53", + "188.212.187.42", + "188.212.242.211", + "188.212.244.182", + "188.213.66.15", + "188.213.66.20", + "188.213.66.25", + "188.213.66.90", + "188.213.66.131", + "188.213.66.132", + "188.213.66.133", + "188.213.67.70", + "188.213.67.98", + "188.213.67.108", + "188.213.67.131", + "188.213.68.146", + "188.213.71.62", + "188.213.71.207", + "188.213.76.19", + "188.213.76.94", + "188.213.76.183", + "188.213.76.240", + "188.213.77.26", + "188.213.78.22", + "188.213.79.21", + "188.213.79.116", + "188.213.79.221", + "188.213.79.240", + "188.213.176.97", + "188.213.178.0", + "188.213.178.61", + "188.213.178.62", + "188.213.179.251", + "188.213.180.79", + "188.213.181.176", + "188.213.189.255", + "188.213.191.85", + "188.213.195.243", + "188.213.196.20", + "188.213.196.137", + "188.213.196.138", + "188.213.196.185", + "188.213.198.13", + "188.213.198.219", + "188.213.209.124", + "188.213.209.146", + "188.213.209.149", + "188.213.209.156", + "188.213.209.168", + "188.213.209.178", + "188.213.209.218", + "188.213.211.25", + "188.213.211.170", + "188.214.96.65", + "188.214.96.106", + "188.214.96.146", + "188.214.96.150", + "188.214.97.41", + "188.214.98.114", + "188.214.98.215", + "188.214.98.253", + "188.214.99.182", + "188.214.173.57", + "188.214.216.26", + "188.214.216.59", + "188.214.216.154", + "188.214.216.255", + "188.214.217.240", + "188.214.218.34", + "188.214.219.48", + "188.214.219.103", + "188.214.219.142", + "188.214.219.206", + "188.214.219.248", + "188.214.220.225", + "188.214.221.32", + "188.214.221.78", + "188.214.222.47", + "188.214.223.109", + "188.214.223.160", + "188.214.223.232", + "188.215.140.46", + "188.215.140.220", + "188.215.160.200", + "188.215.162.36", + "188.215.163.71", + "188.215.164.144", + "188.215.166.176", + "188.215.167.35", + "188.215.168.146", + "188.215.168.152", + "188.215.174.188", + "188.215.213.76", + "188.215.214.66", + "188.240.212.9", + "193.3.255.1", + "193.3.255.2", + "193.3.255.3", + "193.3.255.4", + "193.3.255.5", + "193.3.255.6", + "193.3.255.7", + "193.3.255.8", + "193.3.255.9", + "193.3.255.12", + "193.3.255.15", + "193.3.255.16", + "193.3.255.18", + "193.3.255.19", + "193.3.255.21", + "193.3.255.22", + "193.3.255.24", + "193.3.255.25", + "193.3.255.28", + "193.3.255.29", + "193.3.255.30", + "193.3.255.31", + "193.3.255.32", + "193.3.255.33", + "193.3.255.34", + "193.3.255.35", + "193.3.255.36", + "193.3.255.37", + "193.3.255.38", + "193.3.255.39", + "193.3.255.40", + "193.3.255.41", + "193.3.255.42", + "193.3.255.43", + "193.3.255.44", + "193.3.255.45", + "193.3.255.46", + "193.3.255.47", + "193.3.255.48", + "193.3.255.49", + "193.3.255.50", + "193.3.255.51", + "193.3.255.52", + "193.3.255.53", + "193.3.255.54", + "193.3.255.55", + "193.3.255.56", + "193.3.255.57", + "193.3.255.59", + "193.3.255.60", + "193.3.255.61", + "193.3.255.62", + "193.3.255.63", + "193.3.255.65", + "193.3.255.66", + "193.3.255.67", + "193.3.255.68", + "193.3.255.69", + "193.3.255.70", + "193.3.255.71", + "193.3.255.72", + "193.3.255.73", + "193.3.255.74", + "193.3.255.75", + "193.3.255.76", + "193.3.255.77", + "193.3.255.79", + "193.3.255.80", + "193.3.255.81", + "193.3.255.82", + "193.3.255.83", + "193.3.255.84", + "193.3.255.85", + "193.3.255.86", + "193.3.255.87", + "193.3.255.88", + "193.3.255.89", + "193.3.255.90", + "193.3.255.91", + "193.3.255.92", + "193.3.255.93", + "193.3.255.95", + "193.3.255.96", + "193.3.255.98", + "193.3.255.99", + "193.3.255.100", + "193.3.255.102", + "193.3.255.103", + "193.3.255.104", + "193.3.255.105", + "193.3.255.106", + "193.3.255.107", + "193.3.255.108", + "193.3.255.109", + "193.3.255.110", + "193.3.255.111", + "193.3.255.112", + "193.3.255.113", + "193.3.255.114", + "193.3.255.115", + "193.3.255.116", + "193.3.255.117", + "193.3.255.118", + "193.3.255.119", + "193.3.255.120", + "193.3.255.121", + "193.3.255.122", + "193.3.255.123", + "193.3.255.124", + "193.3.255.125", + "193.3.255.126", + "193.3.255.128", + "193.3.255.129", + "193.3.255.130", + "193.3.255.131", + "193.3.255.133", + "193.3.255.134", + "193.3.255.135", + "193.3.255.137", + "193.3.255.139", + "193.3.255.140", + "193.3.255.141", + "193.3.255.142", + "193.3.255.143", + "193.3.255.144", + "193.3.255.145", + "193.3.255.146", + "193.3.255.147", + "193.3.255.148", + "193.3.255.149", + "193.3.255.150", + "193.3.255.151", + "193.3.255.152", + "193.3.255.153", + "193.3.255.154", + "193.3.255.155", + "193.3.255.156", + "193.3.255.157", + "193.3.255.158", + "193.3.255.160", + "193.3.255.161", + "193.3.255.164", + "193.3.255.165", + "193.3.255.166", + "193.3.255.167", + "193.3.255.168", + "193.3.255.169", + "193.3.255.170", + "193.3.255.171", + "193.3.255.172", + "193.3.255.174", + "193.3.255.175", + "193.3.255.176", + "193.3.255.177", + "193.3.255.178", + "193.3.255.179", + "193.3.255.180", + "193.3.255.182", + "193.3.255.184", + "193.3.255.185", + "193.3.255.186", + "193.3.255.187", + "193.3.255.188", + "193.3.255.191", + "193.3.255.192", + "193.3.255.193", + "193.3.255.197", + "193.3.255.198", + "193.3.255.199", + "193.3.255.200", + "193.3.255.201", + "193.3.255.202", + "193.3.255.203", + "193.3.255.204", + "193.3.255.205", + "193.3.255.206", + "193.3.255.207", + "193.3.255.208", + "193.3.255.209", + "193.3.255.210", + "193.3.255.211", + "193.3.255.212", + "193.3.255.213", + "193.3.255.214", + "193.3.255.216", + "193.3.255.217", + "193.3.255.218", + "193.3.255.219", + "193.3.255.220", + "193.3.255.221", + "193.3.255.222", + "193.3.255.223", + "193.3.255.224", + "193.3.255.225", + "193.3.255.226", + "193.3.255.227", + "193.3.255.228", + "193.3.255.229", + "193.3.255.230", + "193.3.255.231", + "193.3.255.232", + "193.3.255.233", + "193.3.255.234", + "193.3.255.235", + "193.3.255.236", + "193.3.255.237", + "193.3.255.238", + "193.3.255.239", + "193.3.255.240", + "193.3.255.241", + "193.3.255.242", + "193.3.255.243", + "193.3.255.244", + "193.3.255.245", + "193.3.255.246", + "193.3.255.248", + "193.3.255.249", + "193.3.255.250", + "193.3.255.251", + "193.3.255.252", + "193.3.255.253", + "193.5.44.24", + "193.5.44.96", + "193.5.44.201", + "193.19.144.9", + "193.19.144.10", + "193.22.20.2", + "193.24.103.1", + "193.24.103.2", + "193.35.230.2", + "193.35.230.9", + "193.35.230.34", + "193.37.37.2", + "193.37.37.3", + "193.37.37.248", + "193.39.9.66", + "193.39.9.101", + "193.39.9.115", + "193.56.59.10", + "193.56.61.140", + "193.56.61.164", + "193.56.107.107", + "193.56.107.180", + "193.56.107.196", + "193.56.107.236", + "193.56.107.238", + "193.56.118.24", + "193.56.118.26", + "193.56.118.33", + "193.56.118.126", + "193.56.118.199", + "193.58.119.39", + "193.84.255.67", + "193.104.29.50", + "193.105.6.203", + "193.105.234.5", + "193.105.234.74", + "193.105.234.122", + "193.105.234.141", + "193.105.234.153", + "193.111.234.83", + "193.111.234.117", + "193.111.234.145", + "193.111.234.205", + "193.111.234.217", + "193.111.235.31", + "193.111.235.42", + "193.111.235.91", + "193.111.235.95", + "193.111.235.231", + "193.111.235.232", + "193.111.235.235", + "193.111.235.236", + "193.111.236.136", + "193.111.236.139", + "193.111.236.180", + "193.134.100.74", + "193.134.100.108", + "193.134.100.243", + "193.134.101.67", + "193.134.101.132", + "193.141.64.6", + "193.141.64.75", + "193.141.64.79", + "193.141.64.101", + "193.141.64.106", + "193.141.64.113", + "193.141.64.135", + "193.141.64.156", + "193.141.64.163", + "193.141.64.188", + "193.141.64.236", + "193.141.65.130", + "193.148.65.41", + "193.148.65.69", + "193.148.65.240", + "193.148.65.244", + "193.148.66.21", + "193.148.66.165", + "193.148.67.117", + "193.150.66.62", + "193.150.66.134", + "193.151.132.30", + "193.151.137.70", + "193.151.145.132", + "193.151.153.134", + "193.151.154.156", + "193.151.157.155", + "193.151.157.157", + "193.151.157.158", + "193.151.157.175", + "193.151.157.190", + "193.151.157.191", + "193.151.157.192", + "193.151.157.193", + "193.151.159.229", + "193.151.159.248", + "193.176.97.103", + "193.176.97.128", + "193.176.97.141", + "193.177.242.20", + "193.178.200.20", + "193.186.32.83", + "193.186.32.102", + "193.186.32.158", + "193.186.32.226", + "193.186.215.9", + "193.200.148.10", + "193.200.148.17", + "193.200.148.48", + "193.200.148.55", + "193.200.148.58", + "193.200.148.68", + "193.200.148.149", + "193.200.148.151", + "193.200.148.162", + "193.200.148.186", + "193.200.148.197", + "193.200.148.205", + "193.201.192.250", + "193.228.90.29", + "193.228.90.30", + "193.228.90.96", + "193.228.90.118", + "193.228.90.138", + "193.228.90.153", + "193.228.91.26", + "193.228.91.27", + "193.228.91.153", + "193.228.136.128", + "193.228.136.133", + "193.228.136.138", + "193.228.136.139", + "193.228.136.140", + "193.228.136.144", + "193.228.136.145", + "193.228.136.151", + "193.228.136.155", + "193.228.136.158", + "193.228.136.192", + "193.228.136.193", + "193.228.136.204", + "193.228.136.209", + "193.228.136.210", + "193.228.136.215", + "193.228.136.218", + "193.228.136.219", + "193.228.136.220", + "193.228.136.221", + "193.228.136.223", + "193.228.168.52", + "193.228.168.162", + "193.228.168.234", + "193.228.169.10", + "193.228.169.21", + "193.228.169.25", + "193.242.125.66", + "193.246.200.1", + "193.246.200.33", + "193.246.200.34", + "193.246.200.65", + "193.246.200.81", + "193.246.200.82", + "193.246.200.114", + "193.246.200.117", + "193.246.200.119", + "193.246.200.120", + "193.246.200.124", + "193.246.200.125", + "193.246.200.126", + "193.246.201.46", + "194.1.155.9", + "194.1.155.54", + "194.5.175.112", + "194.5.175.159", + "194.5.175.225", + "194.5.176.42", + "194.5.177.1", + "194.5.179.1", + "194.5.188.44", + "194.5.188.81", + "194.5.188.109", + "194.5.188.160", + "194.5.195.19", + "194.5.195.97", + "194.5.195.166", + "194.5.195.189", + "194.5.205.51", + "194.5.205.159", + "194.5.205.227", + "194.5.205.241", + "194.9.57.32", + "194.9.57.119", + "194.9.57.148", + "194.9.57.160", + "194.9.57.167", + "194.9.57.186", + "194.9.57.199", + "194.9.57.217", + "194.9.80.21", + "194.9.80.35", + "194.9.80.38", + "194.9.80.56", + "194.9.80.84", + "194.9.80.125", + "194.9.80.127", + "194.26.117.37", + "194.26.117.40", + "194.26.117.57", + "194.26.117.68", + "194.31.108.22", + "194.31.108.45", + "194.31.108.250", + "194.31.108.251", + "194.31.194.184", + "194.31.194.203", + "194.33.105.11", + "194.33.105.12", + "194.33.105.53", + "194.33.105.96", + "194.33.105.185", + "194.33.107.242", + "194.34.163.55", + "194.36.174.30", + "194.36.174.157", + "194.36.174.251", + "194.39.254.54", + "194.41.48.21", + "194.48.198.50", + "194.48.198.204", + "194.53.118.3", + "194.53.119.3", + "194.53.122.41", + "194.53.122.61", + "194.53.122.70", + "194.53.122.75", + "194.53.122.76", + "194.53.122.83", + "194.53.122.101", + "194.53.122.119", + "194.53.122.123", + "194.53.122.126", + "194.53.122.141", + "194.53.122.150", + "194.53.122.178", + "194.53.122.248", + "194.56.148.5", + "194.56.148.129", + "194.56.148.170", + "194.56.148.177", + "194.56.148.225", + "194.56.148.235", + "194.56.148.243", + "194.59.170.3", + "194.59.170.235", + "194.59.171.12", + "194.59.171.32", + "194.59.171.71", + "194.59.214.78", + "194.59.214.89", + "194.59.214.254", + "194.59.215.114", + "194.60.209.10", + "194.60.209.11", + "194.60.209.12", + "194.60.209.14", + "194.60.209.26", + "194.60.209.98", + "194.60.211.105", + "194.60.211.125", + "194.60.230.60", + "194.60.230.118", + "194.60.231.14", + "194.60.231.55", + "194.60.231.73", + "194.60.231.153", + "194.60.231.173", + "194.60.231.226", + "194.60.231.229", + "194.62.17.34", + "194.62.17.171", + "194.62.17.246", + "194.62.43.52", + "194.62.43.119", + "194.62.43.147", + "194.62.43.173", + "194.107.116.115", + "194.107.116.116", + "194.107.116.117", + "194.107.116.118", + "194.107.116.254", + "194.146.151.21", + "194.147.142.11", + "194.147.142.226", + "194.147.167.48", + "194.147.167.85", + "194.147.167.221", + "194.147.222.122", + "194.150.68.0", + "194.150.68.98", + "194.150.68.101", + "194.150.68.128", + "194.150.68.144", + "194.150.68.145", + "194.150.68.147", + "194.150.68.148", + "194.150.68.149", + "194.150.68.150", + "194.150.68.151", + "194.150.68.152", + "194.150.68.153", + "194.150.68.156", + "194.150.68.158", + "194.150.68.199", + "194.150.68.207", + "194.150.68.248", + "194.150.68.249", + "194.150.68.250", + "194.150.68.251", + "194.150.69.148", + "194.150.69.159", + "194.150.69.181", + "194.150.69.197", + "194.150.69.198", + "194.150.69.199", + "194.150.70.38", + "194.150.70.44", + "194.150.70.45", + "194.150.70.46", + "194.150.70.47", + "194.150.70.55", + "194.150.70.91", + "194.150.70.180", + "194.150.71.17", + "194.150.71.37", + "194.150.71.45", + "194.150.71.57", + "194.150.71.58", + "194.150.71.174", + "194.150.71.234", + "194.150.71.251", + "194.156.140.111", + "194.156.140.167", + "194.180.11.149", + "194.180.11.197", + "194.180.11.203", + "194.180.11.252", + "194.180.224.21", + "194.225.24.15", + "194.225.24.113", + "194.225.26.41", + "194.225.50.88", + "194.225.70.5", + "194.225.70.83", + "194.225.101.1", + "194.225.101.2", + "194.225.101.3", + "194.225.101.5", + "194.225.101.7", + "194.225.101.9", + "194.225.101.11", + "194.225.101.12", + "194.225.101.13", + "194.225.101.14", + "194.225.101.15", + "194.225.101.16", + "194.225.101.17", + "194.225.101.18", + "194.225.101.19", + "194.225.101.20", + "194.225.101.21", + "194.225.101.22", + "194.225.101.24", + "194.225.101.26", + "194.225.101.27", + "194.225.101.32", + "194.225.116.10", + "194.225.136.254", + "194.225.230.72", + "195.2.234.254", + "195.8.102.50", + "195.18.10.15", + "195.26.27.158", + "195.28.11.12", + "195.28.11.56", + "195.28.11.100", + "195.28.11.107", + "195.28.11.126", + "195.28.11.227", + "195.62.4.28", + "195.62.4.29", + "195.88.189.5", + "195.88.189.18", + "195.88.189.33", + "195.88.189.38", + "195.88.189.56", + "195.88.189.67", + "195.88.189.85", + "195.88.189.89", + "195.88.189.103", + "195.88.189.105", + "195.88.189.119", + "195.88.189.144", + "195.88.189.153", + "195.88.189.189", + "195.88.189.195", + "195.110.38.130", + "195.110.38.233", + "195.114.8.145", + "195.114.8.146", + "195.114.8.147", + "195.114.8.148", + "195.114.8.149", + "195.114.8.150", + "195.114.8.151", + "195.177.255.7", + "195.177.255.92", + "195.177.255.133", + "195.177.255.138", + "195.177.255.149", + "195.177.255.198", + "195.177.255.211", + "195.181.36.29", + "195.181.36.41", + "195.181.36.63", + "195.181.36.80", + "195.181.37.11", + "195.181.37.49", + "195.181.37.75", + "195.181.37.79", + "195.181.37.190", + "195.181.38.44", + "195.181.38.48", + "195.181.38.56", + "195.181.38.128", + "195.181.38.211", + "195.181.38.234", + "195.181.39.40", + "195.181.40.13", + "195.181.40.70", + "195.181.41.140", + "195.181.42.99", + "195.181.42.100", + "195.181.42.101", + "195.181.42.102", + "195.181.42.103", + "195.181.42.105", + "195.181.42.106", + "195.181.42.107", + "195.181.42.109", + "195.181.42.110", + "195.181.57.219", + "195.181.58.29", + "195.181.58.63", + "195.181.59.49", + "195.181.67.97", + "195.181.67.208", + "195.181.68.14", + "195.181.68.235", + "195.181.69.11", + "195.181.69.69", + "195.181.69.251", + "195.181.70.117", + "195.181.71.73", + "195.181.74.147", + "195.181.75.174", + "195.181.76.140", + "195.181.77.166", + "195.181.78.111", + "195.181.78.163", + "195.181.79.112", + "195.181.79.121", + "195.181.79.153", + "195.181.80.176", + "195.181.81.96", + "195.181.82.25", + "195.181.82.183", + "195.181.83.149", + "195.181.85.207", + "195.181.88.31", + "195.181.88.147", + "195.181.88.219", + "195.181.88.223", + "195.181.89.140", + "195.181.94.39", + "195.181.110.185", + "195.181.112.94", + "195.181.112.252", + "195.181.113.38", + "195.181.113.53", + "195.181.113.189", + "195.181.113.219", + "195.181.113.255", + "195.181.114.59", + "195.181.114.81", + "195.181.114.107", + "195.181.114.196", + "195.181.114.224", + "195.181.117.163", + "195.181.118.41", + "195.181.118.57", + "195.181.119.171", + "195.181.119.186", + "195.191.44.6", + "195.191.44.7", + "195.191.44.20", + "195.191.44.70", + "195.191.44.98", + "195.191.44.115", + "195.191.44.142", + "195.191.44.154", + "195.191.44.161", + "195.191.44.170", + "195.191.44.176", + "195.191.44.191", + "195.191.44.207", + "195.191.44.213", + "195.191.44.214", + "195.191.44.254", + "195.191.44.255", + "195.191.45.12", + "195.191.45.14", + "195.191.45.26", + "195.191.45.44", + "195.191.45.45", + "195.191.45.47", + "195.191.45.61", + "195.191.45.75", + "195.191.45.88", + "195.191.45.130", + "195.191.45.149", + "195.191.45.151", + "195.191.45.155", + "195.191.45.179", + "195.191.45.189", + "195.191.45.241", + "195.191.45.250", + "195.211.44.20", + "195.211.44.25", + "195.211.44.164", + "195.211.44.165", + "195.211.44.166", + "195.211.44.175", + "195.211.45.150", + "195.211.45.222", + "195.211.45.228", + "195.211.46.63", + "195.211.46.208", + "195.211.46.243", + "195.211.46.244", + "195.211.47.66", + "195.211.47.135", + "195.211.47.195", + "195.214.235.6", + "195.214.235.27", + "195.214.235.115", + "195.214.235.120", + "195.214.235.140", + "195.214.235.173", + "195.214.235.174", + "195.214.235.208", + "195.226.223.12", + "195.226.223.27", + "195.226.223.28", + "195.226.223.29", + "195.226.223.30", + "195.226.223.214", + "195.230.107.93", + "195.238.231.97", + "195.238.240.83", + "195.238.240.84", + "195.238.240.89", + "195.245.70.1", + "195.245.70.65", + "212.16.68.41", + "212.16.68.124", + "212.16.75.253", + "212.16.77.19", + "212.16.77.20", + "212.16.77.21", + "212.16.77.22", + "212.16.84.128", + "212.16.86.17", + "212.16.86.86", + "212.16.86.112", + "212.16.86.137", + "212.16.86.139", + "212.23.201.166", + "212.23.201.238", + "212.23.216.12", + "212.23.216.99", + "212.23.216.116", + "212.23.216.123", + "212.23.216.149", + "212.33.195.77", + "212.33.195.84", + "212.33.195.201", + "212.33.197.9", + "212.33.198.66", + "212.33.198.98", + "212.33.198.245", + "212.33.199.2", + "212.33.201.73", + "212.33.201.227", + "212.33.202.146", + "212.33.202.180", + "212.33.204.94", + "212.33.205.10", + "212.33.205.59", + "212.33.205.72", + "212.33.205.148", + "212.33.206.9", + "212.33.206.21", + "212.33.206.22", + "212.33.206.23", + "212.33.206.24", + "212.33.206.25", + "212.33.206.27", + "212.33.206.181", + "212.33.206.185", + "212.33.206.186", + "212.33.206.188", + "212.33.206.189", + "212.33.206.190", + "212.33.206.191", + "212.33.212.180", + "212.33.213.154", + "212.33.215.66", + "212.33.216.151", + "212.33.216.213", + "212.33.218.29", + "212.33.218.62", + "212.33.218.134", + "212.33.219.209", + "212.33.222.251", + "212.33.223.56", + "212.33.223.66", + "212.80.8.8", + "212.80.8.58", + "212.80.8.152", + "212.80.10.78", + "212.80.10.91", + "212.80.20.98", + "212.80.20.103", + "212.80.24.14", + "212.86.64.2", + "212.86.64.68", + "212.86.64.79", + "212.86.64.83", + "212.86.64.101", + "212.86.64.102", + "212.86.64.103", + "212.86.64.105", + "212.86.65.25", + "212.86.65.40", + "212.86.65.60", + "212.86.65.126", + "212.86.72.0", + "212.86.72.11", + "212.86.72.46", + "212.86.72.56", + "212.86.72.72", + "212.86.72.91", + "212.86.72.123", + "212.86.72.217", + "212.86.73.4", + "212.86.73.41", + "212.86.73.56", + "212.86.73.97", + "212.86.73.173", + "212.86.73.175", + "212.86.73.200", + "212.86.73.217", + "212.86.73.220", + "212.86.74.0", + "212.86.74.6", + "212.86.74.10", + "212.86.74.49", + "212.86.74.60", + "212.86.74.67", + "212.86.74.76", + "212.86.74.79", + "212.86.74.158", + "212.86.74.160", + "212.86.74.188", + "212.86.74.194", + "212.86.74.216", + "212.86.74.218", + "212.86.74.225", + "212.86.75.0", + "212.86.75.36", + "212.86.75.42", + "212.86.75.52", + "212.86.75.71", + "212.86.75.94", + "212.86.75.96", + "212.86.75.160", + "212.86.75.205", + "212.86.75.240", + "212.86.75.243", + "212.86.76.41", + "212.86.79.177", + "212.86.79.179", + "212.86.79.183", + "212.86.79.186", + "212.86.79.187", + "212.86.82.225", + "212.86.87.251", + "212.86.93.93", + "212.86.94.20", + "212.86.94.74", + "212.86.94.113", + "212.86.94.144", + "212.86.94.207", + "212.108.125.92", + "212.120.192.236", + "212.120.192.254", + "212.120.193.20", + "212.120.193.241", + "212.120.194.72", + "212.120.196.90", + "212.120.197.31", + "212.120.198.232", + "212.120.201.203", + "212.120.203.88", + "212.120.204.95", + "212.120.210.84", + "212.120.218.141", + "212.120.219.9", + "212.120.219.153", + "212.120.221.10", + "212.120.221.141", + "212.120.222.106", + "212.120.223.163", + "212.120.223.165", + "213.108.240.139", + "213.108.240.153", + "213.108.240.206", + "213.108.241.134", + "213.108.242.161", + "213.108.242.193", + "213.108.242.225", + "213.108.242.227", + "213.108.242.245", + "213.109.199.3", + "213.109.248.105", + "213.109.250.84", + "213.109.252.154", + "213.109.253.62", + "213.109.253.187", + "213.109.254.199", + "213.176.4.6", + "213.176.6.230", + "213.176.7.117", + "213.176.28.53", + "213.176.28.147", + "213.176.29.10", + "213.176.69.211", + "213.176.120.16", + "213.176.120.74", + "213.176.122.5", + "213.176.122.28", + "213.176.122.56", + "213.176.124.10", + "213.176.124.55", + "213.176.127.130", + "213.177.176.3", + "213.177.176.103", + "213.177.176.107", + "213.177.176.112", + "213.177.178.212", + "213.195.22.51", + "213.195.22.67", + "213.195.22.109", + "213.195.22.122", + "213.195.56.45", + "213.195.58.44", + "213.195.58.177", + "213.195.59.0", + "213.195.59.10", + "213.195.59.11", + "213.195.59.179", + "213.195.60.19", + "213.195.61.119", + "213.195.61.124", + "213.195.61.254", + "213.195.62.126", + "213.207.195.118", + "213.207.195.194", + "213.207.196.30", + "213.207.196.114", + "213.207.196.138", + "213.207.196.214", + "213.207.198.22", + "213.207.198.91", + "213.207.198.180", + "213.207.198.226", + "213.207.200.114", + "213.207.200.115", + "213.207.200.162", + "213.207.200.186", + "213.207.200.226", + "213.207.202.44", + "213.207.202.94", + "213.207.202.235", + "213.207.202.242", + "213.207.203.2", + "213.207.203.30", + "213.207.203.106", + "213.207.203.122", + "213.207.203.123", + "213.207.203.124", + "213.207.204.70", + "213.207.204.104", + "213.207.204.107", + "213.207.204.154", + "213.207.204.186", + "213.207.204.226", + "213.207.204.242", + "213.207.250.159", + "213.207.250.218", + "213.232.124.210", + "213.232.124.250", + "213.232.127.18", + "213.232.127.28", + "213.232.127.30", + "213.233.177.103", + "213.233.178.35", + "213.233.181.18", + "213.233.181.26", + "213.233.181.27", + "213.233.181.106", + "213.233.182.146", + "213.233.182.229", + "213.233.184.40", + "217.11.18.50", + "217.11.18.51", + "217.11.18.52", + "217.11.18.53", + "217.11.18.54", + "217.11.18.58", + "217.11.18.59", + "217.11.18.61", + "217.11.18.62", + "217.11.18.114", + "217.11.18.178", + "217.11.18.179", + "217.11.18.180", + "217.11.18.183", + "217.11.18.189", + "217.11.22.34", + "217.11.24.162", + "217.11.24.249", + "217.11.26.6", + "217.11.27.138", + "217.11.27.139", + "217.11.27.140", + "217.11.27.211", + "217.11.27.213", + "217.11.28.74", + "217.11.28.138", + "217.11.28.237", + "217.11.29.185", + "217.11.29.189", + "217.11.30.34", + "217.11.30.182", + "217.24.144.122", + "217.24.145.5", + "217.24.146.16", + "217.24.147.76", + "217.24.148.1", + "217.24.151.89", + "217.24.151.109", + "217.24.151.141", + "217.24.153.18", + "217.24.155.149", + "217.24.155.244", + "217.24.157.131", + "217.25.48.63", + "217.25.56.90", + "217.26.222.235", + "217.60.238.99", + "217.60.243.183", + "217.66.195.26", + "217.66.200.206", + "217.66.200.219", + "217.66.201.115", + "217.66.201.116", + "217.66.201.118", + "217.66.203.166", + "217.66.207.1", + "217.66.212.246", + "217.66.213.5", + "217.66.213.105", + "217.66.213.133", + "217.66.213.134", + "217.66.213.145", + "217.66.215.86", + "217.66.215.138", + "217.66.221.21", + "217.114.40.23", + "217.114.40.244", + "217.114.46.130", + "217.144.104.1", + "217.144.104.45", + "217.144.104.104", + "217.144.104.115", + "217.144.104.124", + "217.144.104.252", + "217.144.105.1", + "217.144.105.92", + "217.144.106.1", + "217.144.106.42", + "217.144.106.65", + "217.144.106.97", + "217.144.106.98", + "217.144.106.161", + "217.144.106.213", + "217.144.107.1", + "217.144.107.122", + "217.144.107.165", + "217.144.107.206", + "217.146.208.2", + "217.146.209.178", + "217.146.209.179", + "217.146.215.100", + "217.146.217.247", + "217.146.218.141", + "217.146.218.142", + "217.171.145.233", + "217.171.148.147", + "217.171.148.190", + "217.171.148.236", + "217.171.149.81", + "217.171.149.188", + "217.171.149.201", + "217.171.150.82", + "217.171.150.242", + "217.171.151.3", + "217.171.151.16", + "217.172.98.11", + "217.172.98.73", + "217.172.98.107", + "217.172.98.130", + "217.172.98.134", + "217.172.104.161", + "217.172.108.198", + "217.172.112.36", + "217.172.113.123", + "217.172.113.235", + "217.172.114.74", + "217.172.114.92", + "217.172.116.133", + "217.172.117.38", + "217.172.121.49", + "217.172.121.115", + "217.172.121.116", + "217.172.121.210", + "217.172.122.154", + "217.172.122.155", + "217.172.122.156", + "217.172.122.157", + "217.172.122.158", + "217.172.126.1", + "217.172.126.20", + "217.172.126.209", + "217.172.126.215", + "217.172.126.234", + "217.172.126.237", + "217.172.126.253", + "217.172.127.43", + "217.172.127.77", + "217.172.127.78", + "217.172.127.81", + "217.172.127.93", + "217.172.127.101", + "217.172.127.118", + "217.172.127.136", + "217.172.127.186", + "217.172.127.189", + "217.198.190.41", + "217.198.190.96", + "217.218.3.51", + "217.218.3.163", + "217.218.3.233", + "217.218.42.1", + "217.218.42.21", + "217.218.42.22", + "217.218.42.246", + "217.218.44.189", + "217.218.46.34", + "217.218.46.35", + "217.218.46.36", + "217.218.51.78", + "217.218.54.250", + "217.218.56.227", + "217.218.56.228", + "217.218.56.235", + "217.218.62.62", + "217.218.74.14", + "217.218.77.131", + "217.218.81.137", + "217.218.82.32", + "217.218.82.33", + "217.218.82.34", + "217.218.82.102", + "217.218.83.138", + "217.218.85.245", + "217.218.87.29", + "217.218.87.51", + "217.218.91.8", + "217.218.100.216", + "217.218.100.239", + "217.218.103.120", + "217.218.108.177", + "217.218.113.130", + "217.218.114.240", + "217.218.114.241", + "217.218.114.242", + "217.218.114.244", + "217.218.114.245", + "217.218.114.247", + "217.218.114.250", + "217.218.114.253", + "217.218.115.42", + "217.218.115.43", + "217.218.115.44", + "217.218.115.46", + "217.218.115.178", + "217.218.115.179", + "217.218.120.2", + "217.218.120.216", + "217.218.121.54", + "217.218.125.21", + "217.218.126.164", + "217.218.134.57", + "217.218.134.58", + "217.218.134.59", + "217.218.134.60", + "217.218.134.61", + "217.218.134.62", + "217.218.135.87", + "217.218.136.230", + "217.218.139.255", + "217.218.159.68", + "217.218.162.62", + "217.218.164.77", + "217.218.180.42", + "217.218.180.225", + "217.218.192.210", + "217.218.192.211", + "217.218.195.26", + "217.218.196.183", + "217.218.201.16", + "217.218.201.24", + "217.218.201.49", + "217.218.201.101", + "217.218.201.113", + "217.218.201.140", + "217.218.201.175", + "217.218.201.184", + "217.218.201.198", + "217.218.201.213", + "217.218.204.114", + "217.218.204.115", + "217.218.204.117", + "217.218.204.130", + "217.218.204.138", + "217.218.204.170", + "217.218.206.34", + "217.218.206.189", + "217.218.212.136", + "217.218.212.137", + "217.218.213.13", + "217.218.219.105", + "217.218.219.201", + "217.218.222.187", + "217.218.223.154", + "217.218.224.2", + "217.218.224.156", + "217.218.224.157", + "217.218.224.158", + "217.218.224.210", + "217.218.224.211", + "217.218.224.213", + "217.218.224.230", + "217.218.225.190", + "217.218.226.98", + "217.218.226.131", + "217.218.227.2", + "217.218.227.3", + "217.218.227.4", + "217.218.227.5", + "217.218.227.6", + "217.218.227.10", + "217.218.227.50", + "217.218.227.245", + "217.218.227.246", + "217.218.235.138", + "217.218.236.2", + "217.218.236.68", + "217.218.236.70", + "217.218.236.76", + "217.218.236.85", + "217.218.236.86", + "217.218.236.88", + "217.218.236.89", + "217.218.236.162", + "217.218.236.171", + "217.218.237.26", + "217.218.237.27", + "217.218.237.28", + "217.218.238.76", + "217.218.242.24", + "217.218.242.75", + "217.218.243.191", + "217.218.243.204", + "217.218.243.207", + "217.218.243.249", + "217.218.245.13", + "217.218.248.170", + "217.218.249.17", + "217.218.249.63", + "217.218.249.240", + "217.218.250.16", + "217.218.250.145", + "217.218.250.172", + "217.218.252.33", + "217.218.252.64", + "217.218.252.66", + "217.218.253.93", + "217.218.253.110", + "217.219.1.1", + "217.219.14.162", + "217.219.14.164", + "217.219.16.1", + "217.219.21.26", + "217.219.26.145", + "217.219.27.58", + "217.219.27.65", + "217.219.29.186", + "217.219.29.202", + "217.219.29.203", + "217.219.29.250", + "217.219.30.153", + "217.219.30.156", + "217.219.31.90", + "217.219.33.127", + "217.219.34.6", + "217.219.34.15", + "217.219.34.57", + "217.219.34.66", + "217.219.34.95", + "217.219.35.3", + "217.219.35.7", + "217.219.35.10", + "217.219.35.14", + "217.219.35.32", + "217.219.35.49", + "217.219.35.55", + "217.219.35.63", + "217.219.35.69", + "217.219.35.73", + "217.219.35.90", + "217.219.35.129", + "217.219.35.134", + "217.219.35.171", + "217.219.35.183", + "217.219.35.196", + "217.219.35.201", + "217.219.35.254", + "217.219.37.3", + "217.219.39.74", + "217.219.39.75", + "217.219.39.76", + "217.219.39.77", + "217.219.39.122", + "217.219.39.234", + "217.219.42.135", + "217.219.45.2", + "217.219.45.3", + "217.219.45.5", + "217.219.45.138", + "217.219.45.178", + "217.219.45.210", + "217.219.45.222", + "217.219.47.66", + "217.219.47.122", + "217.219.47.138", + "217.219.47.139", + "217.219.47.146", + "217.219.47.170", + "217.219.47.186", + "217.219.47.194", + "217.219.47.195", + "217.219.64.66", + "217.219.64.117", + "217.219.64.196", + "217.219.67.166", + "217.219.67.179", + "217.219.67.221", + "217.219.68.169", + "217.219.68.190", + "217.219.68.250", + "217.219.70.61", + "217.219.70.122", + "217.219.71.242", + "217.219.72.10", + "217.219.72.18", + "217.219.72.19", + "217.219.72.98", + "217.219.72.102", + "217.219.72.111", + "217.219.72.115", + "217.219.72.116", + "217.219.72.119", + "217.219.72.121", + "217.219.72.123", + "217.219.72.194", + "217.219.72.199", + "217.219.72.200", + "217.219.72.203", + "217.219.72.206", + "217.219.73.193", + "217.219.77.69", + "217.219.77.114", + "217.219.77.194", + "217.219.77.250", + "217.219.78.151", + "217.219.80.154", + "217.219.80.241", + "217.219.81.2", + "217.219.81.3", + "217.219.81.18", + "217.219.81.74", + "217.219.81.249", + "217.219.82.110", + "217.219.82.218", + "217.219.82.226", + "217.219.83.146", + "217.219.84.83", + "217.219.85.6", + "217.219.86.98", + "217.219.86.99", + "217.219.86.100", + "217.219.87.2", + "217.219.87.26", + "217.219.87.42", + "217.219.87.122", + "217.219.87.154", + "217.219.87.170", + "217.219.90.87", + "217.219.106.144", + "217.219.106.146", + "217.219.113.164", + "217.219.113.180", + "217.219.121.156", + "217.219.124.78", + "217.219.124.96", + "217.219.124.97", + "217.219.124.98", + "217.219.124.99", + "217.219.124.100", + "217.219.124.101", + "217.219.124.102", + "217.219.124.103", + "217.219.124.104", + "217.219.124.105", + "217.219.124.106", + "217.219.124.107", + "217.219.124.108", + "217.219.124.109", + "217.219.124.110", + "217.219.124.111", + "217.219.129.19", + "217.219.129.213", + "217.219.129.215", + "217.219.129.247", + "217.219.131.14", + "217.219.131.116", + "217.219.131.130", + "217.219.132.14", + "217.219.132.22", + "217.219.132.65", + "217.219.132.75", + "217.219.132.88", + "217.219.132.104", + "217.219.133.86", + "217.219.135.137", + "217.219.136.77", + "217.219.136.156", + "217.219.138.206", + "217.219.141.170", + "217.219.143.83", + "217.219.143.87", + "217.219.146.81", + "217.219.146.201", + "217.219.147.89", + "217.219.147.90", + "217.219.148.34", + "217.219.148.209", + "217.219.148.217", + "217.219.149.137", + "217.219.149.201", + "217.219.150.49", + "217.219.155.36", + "217.219.157.65", + "217.219.157.88", + "217.219.157.121", + "217.219.158.50", + "217.219.158.113", + "217.219.161.172", + "217.219.161.190", + "217.219.162.178", + "217.219.163.1", + "217.219.163.3", + "217.219.163.10", + "217.219.163.11", + "217.219.163.13", + "217.219.163.14", + "217.219.163.15", + "217.219.163.16", + "217.219.163.17", + "217.219.163.18", + "217.219.163.19", + "217.219.163.21", + "217.219.163.23", + "217.219.163.24", + "217.219.163.25", + "217.219.163.26", + "217.219.163.28", + "217.219.163.30", + "217.219.163.31", + "217.219.163.32", + "217.219.163.33", + "217.219.163.34", + "217.219.163.35", + "217.219.163.36", + "217.219.163.37", + "217.219.163.38", + "217.219.163.39", + "217.219.163.40", + "217.219.163.41", + "217.219.163.42", + "217.219.163.43", + "217.219.163.44", + "217.219.163.47", + "217.219.163.48", + "217.219.163.49", + "217.219.163.50", + "217.219.163.51", + "217.219.163.52", + "217.219.163.54", + "217.219.163.55", + "217.219.163.56", + "217.219.163.57", + "217.219.163.59", + "217.219.163.60", + "217.219.163.62", + "217.219.163.63", + "217.219.163.64", + "217.219.163.65", + "217.219.163.66", + "217.219.163.67", + "217.219.163.68", + "217.219.163.69", + "217.219.163.71", + "217.219.163.73", + "217.219.163.74", + "217.219.163.75", + "217.219.163.76", + "217.219.163.77", + "217.219.163.80", + "217.219.163.81", + "217.219.163.82", + "217.219.163.85", + "217.219.163.88", + "217.219.163.90", + "217.219.163.95", + "217.219.163.96", + "217.219.163.99", + "217.219.163.100", + "217.219.163.101", + "217.219.163.102", + "217.219.163.103", + "217.219.163.104", + "217.219.163.105", + "217.219.163.110", + "217.219.163.111", + "217.219.163.121", + "217.219.163.122", + "217.219.163.123", + "217.219.163.125", + "217.219.163.126", + "217.219.163.133", + "217.219.163.134", + "217.219.163.143", + "217.219.163.150", + "217.219.163.152", + "217.219.163.153", + "217.219.163.154", + "217.219.163.155", + "217.219.163.156", + "217.219.163.157", + "217.219.163.159", + "217.219.163.160", + "217.219.163.161", + "217.219.163.164", + "217.219.163.165", + "217.219.163.166", + "217.219.163.170", + "217.219.163.171", + "217.219.163.174", + "217.219.163.180", + "217.219.163.190", + "217.219.163.200", + "217.219.163.201", + "217.219.163.210", + "217.219.163.211", + "217.219.163.222", + "217.219.163.223", + "217.219.163.252", + "217.219.164.105", + "217.219.164.106", + "217.219.164.129", + "217.219.164.194", + "217.219.165.201", + "217.219.166.3", + "217.219.166.228", + "217.219.166.229", + "217.219.166.231", + "217.219.166.235", + "217.219.168.23", + "217.219.169.161", + "217.219.169.180", + "217.219.172.106", + "217.219.172.185", + "217.219.172.238", + "217.219.175.61", + "217.219.175.66", + "217.219.178.154", + "217.219.179.5", + "217.219.179.6", + "217.219.179.18", + "217.219.179.33", + "217.219.179.61", + "217.219.179.62", + "217.219.181.228", + "217.219.191.169", + "217.219.192.117", + "217.219.196.67", + "217.219.196.114", + "217.219.196.115", + "217.219.196.116", + "217.219.196.117", + "217.219.196.170", + "217.219.196.218", + "217.219.196.226", + "217.219.199.50", + "217.219.202.234", + "217.219.203.210", + "217.219.206.144", + "217.219.207.226", + "217.219.209.125", + "217.219.209.130", + "217.219.216.123", + "217.219.217.128", + "217.219.217.141", + "217.219.217.217", + "217.219.217.249", + "217.219.219.10", + "217.219.219.137", + "217.219.219.138", + "217.219.219.145", + "217.219.219.150", + "217.219.222.76", + "217.219.223.1", + "217.219.223.2", + "217.219.223.14", + "217.219.223.106", + "217.219.224.2", + "217.219.224.162", + "217.219.224.194", + "217.219.225.2", + "217.219.226.70", + "217.219.227.34", + "217.219.227.40", + "217.219.228.36", + "217.219.228.88", + "217.219.228.130", + "217.219.229.154", + "217.219.229.155", + "217.219.229.156", + "217.219.229.157", + "217.219.230.22", + "217.219.230.221", + "217.219.243.10", + "217.219.244.6", + "217.219.245.3", + "217.219.245.14", + "217.219.245.70", + "217.219.245.95", + "217.219.245.106", + "217.219.245.136", + "217.219.245.175", + "217.219.245.191", + "217.219.246.98", + "217.219.247.99", + "217.219.247.122", + "2.144.2.111", + "2.144.4.21", + "2.144.5.134", + "2.144.5.203", + "2.144.6.52", + "2.144.6.158", + "2.144.20.43", + "2.144.21.174", + "2.144.22.179", + "2.144.23.66", + "2.144.247.175", + "2.176.61.28", + "2.176.193.50", + "2.176.197.47", + "2.176.199.118", + "2.176.202.164", + "2.176.205.179", + "2.176.231.4", + "2.176.232.223", + "2.176.237.47", + "2.176.239.230", + "2.176.240.92", + "2.176.243.141", + "2.176.244.41", + "2.176.245.72", + "2.176.245.231", + "2.176.246.166", + "2.176.250.217", + "2.176.255.77", + "2.177.13.185", + "2.177.14.0", + "2.177.14.32", + "2.177.14.43", + "2.177.31.166", + "2.177.36.242", + "2.177.37.184", + "2.177.41.202", + "2.177.48.152", + "2.177.51.194", + "2.177.64.43", + "2.177.68.25", + "2.177.73.48", + "2.177.73.79", + "2.177.79.37", + "2.177.83.219", + "2.177.99.220", + "2.177.100.226", + "2.177.107.104", + "2.177.113.124", + "2.177.117.169", + "2.177.118.6", + "2.177.129.148", + "2.177.130.105", + "2.177.137.86", + "2.177.142.148", + "2.177.151.132", + "2.177.151.156", + "2.177.165.9", + "2.177.166.150", + "2.177.168.234", + "2.177.171.89", + "2.177.182.115", + "2.177.185.122", + "2.177.187.200", + "2.177.197.115", + "2.177.198.53", + "2.177.199.51", + "2.177.199.107", + "2.177.199.245", + "2.177.204.214", + "2.177.211.79", + "2.177.216.209", + "2.177.223.128", + "2.177.225.220", + "2.177.226.55", + "2.177.227.249", + "2.177.228.177", + "2.177.230.204", + "2.177.231.85", + "2.177.233.155", + "2.177.240.13", + "2.177.241.251", + "2.177.242.157", + "2.177.248.198", + "2.177.249.33", + "2.178.10.49", + "2.178.26.120", + "2.178.29.129", + "2.178.33.172", + "2.178.39.222", + "2.178.47.252", + "2.178.50.226", + "2.178.68.197", + "2.178.75.96", + "2.178.82.205", + "2.178.96.216", + "2.178.97.192", + "2.178.106.14", + "2.178.165.235", + "2.178.166.233", + "2.178.178.132", + "2.178.190.214", + "2.178.196.1", + "2.178.212.26", + "2.178.213.139", + "2.178.222.197", + "2.178.224.182", + "2.178.248.58", + "2.178.248.241", + "2.179.110.69", + "2.179.111.38", + "2.179.127.252", + "2.179.165.153", + "2.179.175.184", + "2.179.232.32", + "2.180.40.2", + "2.180.160.254", + "2.180.198.17", + "2.180.203.21", + "2.180.228.113", + "2.181.165.5", + "2.181.167.6", + "2.181.194.140", + "2.181.198.224", + "2.181.241.244", + "2.182.46.37", + "2.182.56.10", + "2.182.184.108", + "2.182.185.9", + "2.182.188.221", + "2.182.197.16", + "2.182.215.254", + "2.182.245.1", + "2.183.173.236", + "2.183.203.2", + "2.185.17.37", + "2.185.18.125", + "2.185.108.67", + "2.185.109.142", + "2.185.122.248", + "2.185.126.202", + "2.185.142.5", + "2.185.228.204", + "2.185.230.220", + "2.185.247.106", + "2.185.248.133", + "2.185.249.127", + "2.185.249.210", + "2.185.251.86", + "2.187.18.3", + "2.187.140.164", + "2.188.3.60", + "2.188.168.129", + "2.188.175.242", + "2.189.1.0", + "2.189.9.97", + "2.189.11.159", + "2.189.86.97", + "2.189.145.17", + "2.189.145.173", + "2.189.157.249", + "2.189.217.144", + "2.189.219.152", + "2.189.242.12", + "2.190.3.235", + "2.190.26.244", + "2.190.28.221", + "2.190.28.244", + "2.190.29.197", + "2.190.40.249", + "2.190.49.235", + "2.190.59.172", + "2.190.60.13", + "2.190.60.27", + "2.190.61.4", + "2.190.61.175", + "2.190.62.132", + "2.190.71.116", + "2.190.72.84", + "2.190.87.152", + "2.190.95.36", + "2.190.97.50", + "2.190.103.243", + "2.190.110.83", + "2.190.117.30", + "2.190.131.33", + "2.190.140.44", + "2.190.142.104", + "2.190.157.58", + "2.190.159.232", + "2.190.168.235", + "2.190.173.111", + "2.190.174.81", + "2.190.180.13", + "2.190.180.245", + "2.190.182.58", + "2.190.189.139", + "2.190.193.93", + "2.190.205.31", + "2.190.210.124", + "2.190.212.127", + "2.190.216.0", + "2.190.222.0", + "2.190.226.150", + "2.190.234.39", + "2.190.240.24", + "2.191.5.220", + "2.191.6.55", + "2.191.11.21", + "2.191.19.182", + "2.191.21.202", + "2.191.26.194", + "2.191.34.72", + "2.191.40.236", + "2.191.42.139", + "2.191.46.127", + "2.191.49.238", + "2.191.52.197", + "2.191.54.134", + "2.191.57.99", + "2.191.59.64", + "2.191.61.46", + "2.191.69.203", + "2.191.73.215", + "2.191.79.184", + "2.191.88.247", + "2.191.99.161", + "2.191.100.238", + "2.191.103.229", + "2.191.111.210", + "2.191.115.176", + "2.191.116.220", + "5.10.248.174", + "5.10.249.91", + "5.42.223.178", + "5.57.32.117", + "5.57.32.181", + "5.57.32.184", + "5.57.39.168", + "5.63.8.22", + "5.74.7.121", + "5.74.21.26", + "5.74.26.180", + "5.74.26.234", + "5.74.38.29", + "5.74.47.64", + "5.74.52.237", + "5.74.53.212", + "5.74.56.152", + "5.74.68.57", + "5.74.69.171", + "5.74.72.160", + "5.74.76.182", + "5.74.86.57", + "5.74.90.172", + "5.74.99.0", + "5.74.101.93", + "5.74.105.40", + "5.74.109.253", + "5.74.111.45", + "5.74.113.74", + "5.74.115.250", + "5.74.133.146", + "5.74.137.184", + "5.74.139.166", + "5.74.142.206", + "5.74.160.227", + "5.74.173.21", + "5.74.175.47", + "5.74.184.125", + "5.74.188.152", + "5.74.201.91", + "5.74.219.145", + "5.74.239.134", + "5.74.239.234", + "5.74.246.169", + "5.144.129.5", + "5.144.129.11", + "5.144.129.32", + "5.144.129.40", + "5.144.129.63", + "5.144.129.64", + "5.144.129.66", + "5.144.129.69", + "5.144.129.110", + "5.144.129.148", + "5.144.129.151", + "5.144.129.166", + "5.144.129.216", + "5.144.130.19", + "5.144.130.26", + "5.144.130.82", + "5.144.130.96", + "5.144.130.117", + "5.144.130.159", + "5.144.130.196", + "5.144.130.214", + "5.144.130.219", + "5.144.130.245", + "5.144.130.249", + "5.144.131.245", + "5.159.48.21", + "5.159.48.40", + "5.159.51.194", + "5.159.51.207", + "5.159.53.8", + "5.160.40.6", + "5.160.46.203", + "5.160.55.3", + "5.160.55.13", + "5.160.79.248", + "5.160.95.165", + "5.160.96.210", + "5.160.118.66", + "5.160.186.19", + "5.160.225.168", + "5.160.242.53", + "5.182.44.177", + "5.190.86.86", + "5.190.87.87", + "5.198.161.51", + "5.198.161.55", + "5.200.134.206", + "5.200.144.52", + "5.200.150.169", + "5.200.183.90", + "5.200.186.226", + "5.200.238.224", + "5.201.165.192", + "5.202.5.131", + "5.202.50.82", + "5.202.52.36", + "5.202.67.5", + "5.202.97.22", + "5.232.125.241", + "5.232.135.255", + "5.233.57.47", + "5.233.68.161", + "5.233.183.139", + "5.233.193.222", + "5.234.3.0", + "5.234.8.25", + "5.234.9.41", + "5.234.15.25", + "5.234.17.120", + "5.234.18.35", + "5.234.18.94", + "5.234.22.170", + "5.234.28.171", + "5.234.160.153", + "5.234.198.228", + "5.234.207.158", + "5.234.208.42", + "5.234.211.176", + "5.235.163.209", + "5.235.164.4", + "5.235.166.97", + "5.235.166.254", + "5.235.168.38", + "5.235.174.230", + "5.235.176.185", + "5.235.183.53", + "5.235.198.109", + "5.236.64.61", + "5.236.75.164", + "5.236.80.59", + "5.236.117.196", + "5.236.125.34", + "5.236.193.186", + "5.236.218.37", + "5.236.224.25", + "5.236.231.47", + "5.236.248.172", + "5.236.251.85", + "5.237.171.184", + "5.237.182.67", + "5.237.184.184", + "5.237.190.101", + "5.237.200.102", + "5.237.249.162", + "5.238.77.204", + "5.238.84.14", + "5.238.128.102", + "5.238.145.107", + "5.238.162.247", + "5.238.169.180", + "5.238.171.106", + "5.238.179.1", + "5.238.187.194", + "5.238.189.61", + "5.238.192.15", + "5.238.205.201", + "5.238.213.205", + "5.238.219.22", + "5.238.225.98", + "5.238.225.134", + "5.238.226.108", + "5.238.226.157", + "5.238.229.48", + "5.238.229.227", + "5.238.236.60", + "5.238.243.239", + "5.239.10.166", + "5.239.18.23", + "5.239.32.209", + "5.239.34.3", + "5.239.43.82", + "5.239.46.65", + "5.239.53.249", + "5.239.62.129", + "5.239.77.105", + "5.239.79.88", + "5.239.81.68", + "5.239.82.223", + "5.239.84.107", + "5.239.89.157", + "5.239.92.43", + "5.239.105.226", + "5.239.106.140", + "5.239.120.67", + "31.7.72.178", + "31.7.111.214", + "31.47.40.31", + "31.170.50.93", + "31.171.222.195", + "31.171.222.199", + "31.184.140.207", + "31.216.62.142", + "37.32.10.23", + "37.148.24.109", + "37.148.46.201", + "37.148.50.109", + "37.148.51.63", + "37.148.82.202", + "37.148.82.225", + "37.152.176.135", + "37.152.191.247", + "37.156.31.22", + "37.202.155.93", + "37.202.225.135", + "37.202.225.137", + "37.202.225.156", + "37.202.225.160", + "37.228.137.89", + "37.255.173.82", + "37.255.243.165", + "45.82.138.154", + "45.89.236.235", + "45.92.92.10", + "45.92.92.18", + "45.92.92.25", + "45.92.92.41", + "45.92.92.53", + "45.92.92.69", + "45.92.92.73", + "45.92.92.76", + "45.92.92.151", + "45.92.92.175", + "45.92.92.196", + "45.92.94.92", + "45.92.94.96", + "45.135.243.138", + "45.138.132.238", + "45.138.133.43", + "45.138.133.55", + "45.138.133.59", + "45.138.133.63", + "45.138.133.146", + "45.138.133.161", + "45.138.134.19", + "45.138.134.44", + "45.138.134.81", + "45.138.134.134", + "45.138.134.142", + "45.138.134.169", + "45.138.134.176", + "45.138.134.251", + "45.138.135.179", + "45.147.77.157", + "45.149.79.40", + "45.156.185.85", + "45.159.113.35", + "45.159.149.196", + "45.159.151.14", + "45.159.151.83", + "45.159.151.137", + "46.18.248.134", + "46.100.5.51", + "46.100.47.7", + "46.100.59.39", + "46.100.145.243", + "46.100.177.40", + "46.167.141.24", + "46.209.67.146", + "46.209.212.75", + "46.245.1.100", + "46.245.8.151", + "46.245.10.1", + "46.245.16.4", + "46.245.19.220", + "46.245.27.65", + "46.245.34.19", + "46.245.39.187", + "46.245.40.151", + "46.245.43.89", + "46.245.45.43", + "46.245.77.2", + "46.245.86.27", + "46.245.91.230", + "46.245.115.37", + "62.3.41.64", + "62.3.41.95", + "62.3.42.23", + "62.60.133.3", + "62.60.133.8", + "62.60.133.28", + "62.60.133.35", + "62.60.133.46", + "62.60.133.57", + "62.60.133.60", + "62.60.133.81", + "62.60.133.82", + "62.60.133.101", + "62.60.133.129", + "62.60.133.193", + "62.60.133.199", + "62.60.133.204", + "62.60.163.226", + "62.60.195.95", + "62.60.201.19", + "62.60.201.23", + "62.60.201.91", + "62.60.201.114", + "62.60.201.129", + "62.60.201.150", + "62.60.201.181", + "62.60.201.188", + "62.60.201.237", + "62.60.204.166", + "62.60.205.96", + "62.60.205.165", + "62.60.205.190", + "62.60.205.205", + "62.60.205.206", + "62.60.205.252", + "62.60.206.155", + "62.60.207.78", + "62.60.211.198", + "62.60.211.201", + "62.60.214.83", + "62.204.61.159", + "62.220.117.130", + "66.79.110.174", + "77.81.144.148", + "77.81.146.166", + "77.81.152.206", + "77.104.118.2", + "77.237.67.13", + "77.237.75.148", + "77.237.83.30", + "78.38.29.129", + "78.38.182.201", + "78.38.189.230", + "78.39.62.36", + "78.39.81.46", + "78.39.122.99", + "78.39.146.149", + "78.39.147.33", + "78.39.194.10", + "78.39.224.155", + "78.39.245.8", + "78.39.248.252", + "78.39.255.27", + "78.109.202.101", + "78.157.33.201", + "78.157.35.39", + "78.157.35.47", + "78.157.35.96", + "78.157.38.200", + "78.157.40.143", + "78.157.40.184", + "78.157.40.224", + "78.157.41.79", + "78.157.43.64", + "78.157.43.127", + "78.157.45.32", + "78.157.45.63", + "78.157.45.143", + "78.157.46.111", + "78.157.50.16", + "78.157.50.248", + "78.157.52.8", + "78.157.52.10", + "78.157.57.159", + "78.157.57.233", + "78.157.58.47", + "78.158.179.134", + "79.127.125.126", + "79.127.126.68", + "79.127.127.55", + "79.175.134.65", + "79.175.167.4", + "79.175.171.67", + "79.175.187.131", + "80.191.62.6", + "80.191.65.174", + "80.191.103.98", + "80.191.110.242", + "80.191.170.173", + "80.210.27.243", + "80.210.34.217", + "80.210.136.8", + "80.210.140.143", + "80.210.142.120", + "80.210.160.220", + "80.210.168.80", + "80.210.170.69", + "80.210.171.8", + "80.210.171.49", + "80.210.177.84", + "81.12.20.85", + "81.12.54.213", + "81.12.54.248", + "81.12.104.84", + "81.12.106.190", + "81.12.108.142", + "81.16.121.120", + "81.16.124.109", + "81.91.156.130", + "82.99.206.97", + "84.47.239.13", + "84.241.0.112", + "84.241.2.163", + "84.241.7.245", + "84.241.19.157", + "84.241.28.59", + "84.241.34.116", + "84.241.36.70", + "85.9.98.70", + "85.9.122.167", + "85.9.125.118", + "85.133.182.163", + "85.133.184.122", + "85.133.187.22", + "85.185.17.75", + "85.185.17.115", + "85.185.52.32", + "85.185.54.129", + "85.185.55.190", + "85.185.75.108", + "85.185.86.212", + "85.185.205.28", + "85.185.216.27", + "85.208.253.58", + "85.208.254.65", + "86.104.32.35", + "86.104.104.68", + "86.105.41.35", + "86.105.42.20", + "86.105.47.95", + "86.106.142.123", + "86.107.148.140", + "86.107.158.131", + "87.107.102.4", + "87.107.104.60", + "87.236.211.12", + "87.236.211.83", + "87.248.130.22", + "88.135.75.144", + "89.32.196.33", + "89.32.248.22", + "89.32.251.75", + "89.37.103.20", + "89.38.24.35", + "89.42.199.197", + "89.42.209.50", + "89.42.210.237", + "89.42.211.230", + "89.43.216.130", + "89.43.219.10", + "89.43.227.128", + "89.46.94.42", + "89.46.94.164", + "89.46.218.69", + "89.46.218.109", + "89.46.218.127", + "89.46.219.82", + "89.46.219.199", + "89.144.131.138", + "89.144.137.223", + "89.144.137.233", + "89.144.137.244", + "89.144.137.247", + "89.144.137.255", + "89.144.138.156", + "89.144.139.1", + "89.144.139.7", + "89.144.139.11", + "89.144.139.25", + "89.144.139.36", + "89.144.139.60", + "89.144.139.61", + "89.144.139.69", + "89.144.139.80", + "89.144.139.81", + "89.144.139.83", + "89.144.139.99", + "89.144.139.100", + "89.144.139.107", + "89.144.139.109", + "89.144.139.111", + "89.144.139.113", + "89.144.139.120", + "89.144.149.191", + "89.144.154.235", + "89.144.166.172", + "89.144.166.191", + "89.144.166.193", + "89.144.166.217", + "89.144.166.242", + "89.144.175.238", + "89.144.189.137", + "89.144.189.177", + "89.144.189.191", + "89.144.189.232", + "89.144.189.234", + "89.219.64.174", + "89.219.66.175", + "89.219.73.15", + "89.219.74.106", + "89.219.77.0", + "89.219.78.56", + "89.219.93.252", + "89.219.97.108", + "89.219.99.81", + "89.219.107.135", + "89.219.107.220", + "89.219.110.207", + "89.219.115.60", + "89.219.116.22", + "89.219.118.105", + "89.219.120.2", + "89.219.123.224", + "89.219.197.210", + "89.219.202.225", + "89.219.233.5", + "89.219.252.178", + "89.219.255.108", + "89.235.103.95", + "91.92.126.7", + "91.92.150.122", + "91.92.164.144", + "91.92.205.110", + "91.92.208.114", + "91.92.231.138", + "91.199.27.108", + "91.220.113.242", + "91.223.116.140", + "91.228.132.22", + "91.247.171.251", + "92.42.53.30", + "92.114.48.234", + "93.114.105.206", + "93.115.149.15", + "93.115.149.237", + "93.117.33.12", + "93.117.34.217", + "93.117.35.8", + "93.117.38.185", + "93.117.43.30", + "93.118.114.23", + "93.118.116.117", + "93.118.123.32", + "93.118.125.189", + "93.118.145.201", + "93.126.61.205", + "94.74.128.171", + "94.101.176.247", + "94.182.39.201", + "94.182.39.230", + "94.182.91.237", + "94.182.92.68", + "94.182.106.3", + "94.182.178.90", + "94.182.181.250", + "94.182.186.6", + "94.182.192.220", + "94.182.195.189", + "94.182.209.62", + "94.182.210.200", + "94.182.216.105", + "94.182.227.239", + "94.182.251.1", + "94.183.180.71", + "95.38.24.226", + "95.38.34.104", + "95.38.103.219", + "95.38.209.209", + "109.109.32.12", + "109.109.32.89", + "109.109.32.97", + "109.109.32.98", + "109.109.32.103", + "109.109.32.217", + "109.109.34.76", + "109.109.34.85", + "109.109.34.123", + "109.109.34.139", + "109.109.34.183", + "109.109.34.185", + "109.109.34.188", + "109.109.34.199", + "109.109.34.213", + "109.109.34.218", + "109.109.34.231", + "109.109.34.234", + "109.109.34.237", + "109.109.34.249", + "109.109.35.12", + "109.109.35.75", + "109.109.35.125", + "109.109.37.159", + "109.109.40.58", + "109.109.41.24", + "109.109.43.141", + "109.109.43.142", + "109.109.43.145", + "109.109.43.148", + "109.109.43.194", + "109.109.43.207", + "109.109.43.208", + "109.109.43.219", + "109.109.43.228", + "109.109.43.231", + "109.109.43.244", + "109.109.43.251", + "109.109.45.21", + "109.109.45.69", + "109.109.45.82", + "109.109.45.86", + "109.109.45.97", + "109.109.47.39", + "109.109.47.58", + "109.109.47.61", + "109.109.47.68", + "109.109.47.71", + "109.109.47.88", + "109.109.47.89", + "109.109.47.110", + "109.109.47.111", + "109.109.47.118", + "109.109.53.30", + "109.109.54.39", + "109.109.54.50", + "109.109.54.51", + "109.109.54.77", + "109.109.54.89", + "109.109.54.109", + "109.109.54.141", + "109.109.54.146", + "109.109.54.166", + "109.109.54.168", + "109.109.54.207", + "109.109.54.209", + "109.109.54.212", + "109.109.58.8", + "109.109.58.36", + "109.109.58.43", + "109.109.58.76", + "109.109.58.166", + "109.109.59.6", + "109.109.59.24", + "109.109.59.25", + "109.109.59.26", + "109.109.59.35", + "109.109.59.52", + "109.109.59.53", + "109.109.59.55", + "109.109.59.84", + "109.109.59.88", + "109.109.59.99", + "109.109.59.231", + "109.109.60.7", + "109.109.60.40", + "109.109.60.43", + "109.109.60.69", + "109.109.60.73", + "109.109.60.93", + "109.109.60.98", + "109.109.60.102", + "109.109.60.116", + "109.109.60.176", + "109.109.60.204", + "109.109.61.208", + "109.109.61.209", + "109.109.61.216", + "109.109.61.218", + "109.109.61.232", + "109.109.62.0", + "109.109.62.2", + "109.109.63.130", + "109.109.63.136", + "109.109.63.152", + "109.109.63.193", + "109.109.63.196", + "109.109.63.208", + "109.109.63.241", + "109.109.63.253", + "109.122.251.188", + "109.206.252.43", + "128.65.181.82", + "130.185.77.137", + "130.193.77.37", + "130.193.77.124", + "151.232.80.169", + "151.234.25.189", + "151.234.29.56", + "151.234.75.123", + "151.234.186.93", + "151.234.245.87", + "151.235.13.16", + "151.235.18.98", + "151.235.123.250", + "151.235.125.168", + "151.235.126.91", + "159.20.99.117", + "171.22.24.162", + "176.62.144.37", + "176.62.144.44", + "176.65.224.95", + "178.173.140.41", + "178.173.140.129", + "178.173.148.167", + "178.173.211.35", + "178.173.216.97", + "178.173.218.96", + "178.173.221.167", + "178.216.248.85", + "178.216.251.244", + "178.236.105.153", + "178.236.106.202", + "178.238.196.223", + "178.239.147.81", + "178.252.134.188", + "178.252.166.146", + "178.252.181.101", + "185.3.202.129", + "185.4.30.50", + "185.8.175.98", + "185.10.75.8", + "185.10.75.75", + "185.10.75.199", + "185.18.213.84", + "185.41.3.106", + "185.42.225.197", + "185.44.36.51", + "185.53.142.229", + "185.55.224.119", + "185.55.225.182", + "185.58.241.89", + "185.59.112.25", + "185.73.112.229", + "185.73.226.28", + "185.79.156.242", + "185.79.157.196", + "185.83.113.170", + "185.83.183.95", + "185.83.183.125", + "185.84.158.30", + "185.88.153.190", + "185.88.154.59", + "185.88.154.223", + "185.88.178.43", + "185.88.178.53", + "185.94.97.11", + "185.94.99.26", + "185.94.99.37", + "185.94.99.245", + "185.97.116.171", + "185.100.46.141", + "185.103.84.39", + "185.109.74.238", + "185.110.219.40", + "185.111.83.10", + "185.112.149.97", + "185.112.149.115", + "185.112.149.118", + "185.113.9.175", + "185.113.58.197", + "185.113.59.202", + "185.117.50.230", + "185.118.155.62", + "185.119.167.5", + "185.120.219.76", + "185.120.221.228", + "185.124.115.15", + "185.129.169.78", + "185.129.200.200", + "185.131.88.224", + "185.131.152.196", + "185.137.27.47", + "185.140.240.8", + "185.141.104.86", + "185.141.105.237", + "185.141.105.238", + "185.141.132.69", + "185.141.134.205", + "185.147.41.71", + "185.149.192.24", + "185.149.192.165", + "185.164.72.9", + "185.164.73.213", + "185.165.116.184", + "185.165.118.34", + "185.165.118.245", + "185.169.6.195", + "185.190.39.68", + "185.191.76.146", + "185.192.10.2", + "185.192.112.4", + "185.192.112.37", + "185.192.114.72", + "185.192.114.109", + "185.192.114.136", + "185.192.114.152", + "185.192.114.251", + "185.208.174.78", + "185.208.174.125", + "185.208.175.79", + "185.211.59.123", + "185.231.112.172", + "185.231.115.129", + "185.231.115.167", + "185.231.181.225", + "185.232.154.79", + "185.235.196.141", + "185.235.197.79", + "185.243.50.0", + "185.243.50.3", + "185.243.50.4", + "185.243.50.5", + "185.243.50.6", + "185.243.50.8", + "185.243.50.9", + "185.243.50.10", + "185.243.50.11", + "185.243.50.12", + "185.243.50.13", + "185.243.50.14", + "185.243.50.15", + "185.243.50.17", + "185.243.50.18", + "185.243.50.19", + "185.243.50.20", + "185.243.50.21", + "185.243.50.22", + "185.243.50.23", + "185.243.50.24", + "185.243.50.25", + "185.243.50.26", + "185.243.50.27", + "185.243.50.28", + "185.243.50.29", + "185.243.50.30", + "185.243.50.31", + "185.243.50.32", + "185.243.50.34", + "185.243.50.35", + "185.243.50.36", + "185.243.50.37", + "185.243.50.38", + "185.243.50.39", + "185.243.50.40", + "185.243.50.41", + "185.243.50.42", + "185.243.50.43", + "185.243.50.44", + "185.243.50.45", + "185.243.50.46", + "185.243.50.47", + "185.243.50.48", + "185.243.50.49", + "185.243.50.50", + "185.243.50.51", + "185.243.50.52", + "185.243.50.53", + "185.243.50.54", + "185.243.50.55", + "185.243.50.56", + "185.243.50.57", + "185.243.50.59", + "185.243.50.60", + "185.243.50.61", + "185.243.50.62", + "185.243.50.63", + "185.243.50.64", + "185.243.50.65", + "185.243.50.67", + "185.243.50.68", + "185.243.50.70", + "185.243.50.71", + "185.243.50.72", + "185.243.50.73", + "185.243.50.74", + "185.243.50.75", + "185.243.50.76", + "185.243.50.77", + "185.243.50.78", + "185.243.50.79", + "185.243.50.80", + "185.243.50.81", + "185.243.50.82", + "185.243.50.84", + "185.243.50.85", + "185.243.50.86", + "185.243.50.87", + "185.243.50.88", + "185.243.50.89", + "185.243.50.90", + "185.243.50.91", + "185.243.50.92", + "185.243.50.93", + "185.243.50.95", + "185.243.50.96", + "185.243.50.97", + "185.243.50.98", + "185.243.50.99", + "185.243.50.100", + "185.243.50.101", + "185.243.50.103", + "185.243.50.104", + "185.243.50.105", + "185.243.50.106", + "185.243.50.107", + "185.243.50.108", + "185.243.50.109", + "185.243.50.110", + "185.243.50.111", + "185.243.50.112", + "185.243.50.113", + "185.243.50.115", + "185.243.50.116", + "185.243.50.117", + "185.243.50.118", + "185.243.50.119", + "185.243.50.121", + "185.243.50.122", + "185.243.50.123", + "185.243.50.124", + "185.243.50.126", + "185.243.50.127", + "185.243.50.129", + "185.243.50.130", + "185.243.50.131", + "185.243.50.132", + "185.243.50.133", + "185.243.50.134", + "185.243.50.135", + "185.243.50.136", + "185.243.50.137", + "185.243.50.138", + "185.243.50.139", + "185.243.50.140", + "185.243.50.141", + "185.243.50.142", + "185.243.50.143", + "185.243.50.144", + "185.243.50.145", + "185.243.50.146", + "185.243.50.147", + "185.243.50.148", + "185.243.50.149", + "185.243.50.150", + "185.243.50.151", + "185.243.50.152", + "185.243.50.153", + "185.243.50.154", + "185.243.50.157", + "185.243.50.158", + "185.243.50.159", + "185.243.50.161", + "185.243.50.162", + "185.243.50.164", + "185.243.50.166", + "185.243.50.168", + "185.243.50.169", + "185.243.50.170", + "185.243.50.171", + "185.243.50.172", + "185.243.50.173", + "185.243.50.174", + "185.243.50.175", + "185.243.50.176", + "185.243.50.177", + "185.243.50.178", + "185.243.50.179", + "185.243.50.180", + "185.243.50.181", + "185.243.50.182", + "185.243.50.184", + "185.243.50.185", + "185.243.50.186", + "185.243.50.187", + "185.243.50.188", + "185.243.50.189", + "185.243.50.190", + "185.243.50.191", + "185.243.50.193", + "185.243.50.194", + "185.243.50.196", + "185.243.50.197", + "185.243.50.200", + "185.243.50.201", + "185.243.50.202", + "185.243.50.205", + "185.243.50.206", + "185.243.50.207", + "185.243.50.208", + "185.243.50.209", + "185.243.50.210", + "185.243.50.211", + "185.243.50.212", + "185.243.50.213", + "185.243.50.214", + "185.243.50.217", + "185.243.50.218", + "185.243.50.219", + "185.243.50.220", + "185.243.50.221", + "185.243.50.222", + "185.243.50.223", + "185.243.50.224", + "185.243.50.225", + "185.243.50.226", + "185.243.50.227", + "185.243.50.228", + "185.243.50.231", + "185.243.50.232", + "185.243.50.234", + "185.243.50.236", + "185.243.50.237", + "185.243.50.238", + "185.243.50.239", + "185.243.50.241", + "185.243.50.242", + "185.243.50.243", + "185.243.50.244", + "185.243.50.245", + "185.243.50.246", + "185.243.50.247", + "185.243.50.249", + "185.243.50.250", + "185.243.50.251", + "185.243.50.252", + "185.243.50.253", + "185.243.50.254", + "185.255.88.55", + "185.255.88.128", + "188.0.240.133", + "188.0.249.52", + "188.121.100.132", + "188.121.104.21", + "188.121.117.78", + "188.121.119.175", + "188.121.123.128", + "188.208.59.159", + "188.208.151.64", + "188.209.6.170", + "188.209.68.61", + "188.209.71.69", + "188.209.76.133", + "188.209.78.143", + "188.210.97.80", + "188.210.116.62", + "188.211.68.160", + "188.211.70.150", + "188.211.87.38", + "188.211.99.14", + "188.211.99.23", + "188.211.103.126", + "188.211.108.135", + "188.211.108.255", + "188.211.113.11", + "188.211.114.213", + "188.211.121.143", + "188.211.123.106", + "188.211.126.196", + "188.212.22.21", + "188.212.76.158", + "188.212.80.155", + "188.213.66.140", + "188.213.79.54", + "188.213.181.202", + "188.213.190.157", + "188.215.165.169", + "193.39.9.161", + "193.58.119.108", + "193.58.119.223", + "193.105.234.127", + "193.141.64.116", + "193.141.65.182", + "193.162.129.210", + "193.228.90.213", + "193.228.136.200", + "193.242.125.194", + "194.5.179.18", + "194.5.195.74", + "194.5.195.170", + "194.41.51.39", + "194.62.43.124", + "194.150.70.88", + "194.225.40.115", + "195.26.27.173", + "195.245.70.230", + "212.23.201.215", + "212.33.202.232", + "212.80.24.24", + "212.120.193.152", + "212.120.194.55", + "212.120.205.188", + "212.120.212.25", + "212.120.216.60", + "212.120.221.106", + "212.120.223.208", + "213.176.0.14", + "213.176.29.251", + "213.195.60.63", + "213.232.124.11", + "217.11.24.233", + "217.77.117.252", + "217.144.106.34", + "217.144.107.175", + "217.170.251.3", + "217.170.252.66", + "217.170.252.156", + "217.170.252.165", + "217.171.149.19", + "217.171.150.0", + "217.172.98.173", + "217.218.59.18", + "217.218.66.24", + "217.218.66.54", + "217.218.72.215", + "217.218.91.126", + "217.218.102.173", + "217.218.121.219", + "217.218.126.180", + "217.218.126.192", + "217.218.147.109", + "217.218.162.250", + "217.218.171.21", + "217.218.231.165", + "217.219.78.210", + "217.219.113.176", + "217.219.146.132", + "217.219.162.171", + "217.219.162.179", + "217.219.162.190", + "217.219.219.68", + "217.219.247.178", + "217.219.247.179", + "217.219.247.180", + "217.219.247.182", + "217.219.247.186", + "217.219.247.208", + "217.219.247.226", + "217.219.250.67", + "217.219.251.250", + "2.184.237.250", + "2.184.239.96", + "2.185.60.53", + "2.185.100.114", + "2.185.111.42", + "2.185.115.187", + "2.185.118.190", + "2.185.119.25", + "2.185.121.161", + "2.185.123.125", + "2.185.144.18", + "2.185.144.71", + "2.185.146.147", + "2.185.152.23", + "2.185.228.3", + "2.185.228.166", + "2.185.229.141", + "2.185.229.223", + "2.185.232.202", + "2.185.233.97", + "2.185.234.29", + "2.185.251.169", + "2.185.254.215", + "2.186.12.33", + "2.186.12.72", + "2.186.12.169", + "2.186.14.53", + "2.186.114.102", + "2.186.118.65", + "2.186.121.65", + "2.186.122.56", + "2.186.123.180", + "2.186.123.193", + "2.187.25.177", + "2.187.32.105", + "2.187.59.28", + "2.187.97.193", + "2.187.150.211", + "2.187.188.97", + "2.187.189.192", + "2.188.2.253", + "2.188.15.200", + "2.188.15.201", + "2.188.15.202", + "2.188.21.20", + "2.188.21.90", + "2.188.21.100", + "2.188.21.120", + "2.188.21.130", + "2.188.21.190", + "2.188.21.200", + "2.188.21.230", + "2.188.21.240", + "2.188.26.10", + "2.188.43.88", + "2.188.54.1", + "2.188.54.2", + "2.188.54.54", + "2.188.54.55", + "2.188.58.12", + "2.188.73.81", + "2.188.163.135", + "2.188.163.136", + "2.188.164.29", + "2.188.164.30", + "2.188.165.106", + "2.188.168.184", + "2.188.184.19", + "2.188.212.158", + "2.188.218.179", + "2.188.218.180", + "2.188.218.181", + "2.188.228.114", + "2.188.230.127", + "2.188.235.83", + "2.188.236.82", + "2.189.1.10", + "2.189.1.13", + "2.189.1.15", + "2.189.1.19", + "2.189.1.253", + "2.189.4.29", + "2.189.4.30", + "2.189.5.183", + "2.189.9.56", + "2.189.9.87", + "2.189.44.44", + "2.189.58.3", + "2.189.87.170", + "2.189.90.130", + "2.189.92.1", + "2.189.120.111", + "2.189.142.70", + "2.189.147.101", + "2.189.147.211", + "2.189.160.131", + "2.189.161.45", + "2.189.162.131", + "2.189.162.136", + "2.189.172.227", + "2.189.172.229", + "2.189.172.233", + "2.189.173.198", + "2.189.188.54", + "2.189.188.56", + "2.189.188.62", + "2.189.188.190", + "2.189.217.95", + "2.189.217.251", + "2.189.222.223", + "2.189.242.36", + "2.189.242.86", + "2.189.242.100", + "2.189.242.107", + "2.189.242.109", + "2.189.243.18", + "2.189.243.19", + "2.189.243.21", + "2.189.243.25", + "2.189.243.101", + "2.189.243.103", + "2.189.243.226", + "2.189.243.228", + "2.189.254.58", + "2.189.254.67", + "2.189.255.21", + "2.189.255.130", + "2.189.255.227", + "2.190.3.49", + "2.190.9.251", + "2.190.12.228", + "2.190.13.15", + "2.190.14.71", + "2.190.17.170", + "2.190.18.195", + "2.190.31.215", + "2.190.35.50", + "2.190.36.61", + "2.190.37.214", + "2.190.37.229", + "2.190.41.98", + "2.190.43.76", + "2.190.44.114", + "2.190.44.119", + "2.190.48.163", + "2.190.49.111", + "2.190.53.64", + "2.190.53.68", + "2.190.53.92", + "2.190.53.100", + "2.190.55.74", + "2.190.55.120", + "2.190.59.91", + "2.190.59.155", + "2.190.73.43", + "2.190.73.67", + "2.190.78.115", + "2.190.79.245", + "2.190.86.118", + "2.190.92.26", + "2.190.94.100", + "2.190.100.174", + "2.190.108.91", + "2.190.111.134", + "2.190.113.182", + "2.190.133.106", + "2.190.137.215", + "2.190.139.102", + "2.190.144.183", + "2.190.147.151", + "2.190.152.187", + "2.190.153.67", + "2.190.159.244", + "2.190.163.207", + "2.190.169.87", + "2.190.179.133", + "2.190.182.32", + "2.190.183.92", + "2.190.185.210", + "2.190.189.114", + "2.190.196.38", + "2.190.197.33", + "2.190.197.175", + "2.190.208.237", + "2.190.211.50", + "2.190.218.16", + "2.190.218.137", + "2.190.222.23", + "2.190.225.89", + "2.190.226.224", + "2.190.230.164", + "2.190.239.52", + "2.190.254.192", + "2.191.1.142", + "2.191.3.59", + "2.191.7.101", + "2.191.11.236", + "2.191.13.68", + "2.191.14.96", + "2.191.17.34", + "2.191.19.25", + "2.191.20.200", + "2.191.30.238", + "2.191.34.90", + "2.191.37.81", + "2.191.37.155", + "2.191.38.203", + "2.191.42.29", + "2.191.44.179", + "2.191.46.6", + "2.191.50.40", + "2.191.53.152", + "2.191.59.4", + "2.191.59.95", + "2.191.65.208", + "2.191.66.14", + "2.191.66.48", + "2.191.70.85", + "2.191.78.251", + "2.191.79.86", + "2.191.83.25", + "2.191.92.195", + "2.191.95.198", + "2.191.112.91", + "2.191.114.44", + "2.191.119.38", + "2.191.120.15", + "5.10.248.4", + "5.10.248.7", + "5.10.248.13", + "5.10.248.23", + "5.10.248.26", + "5.10.248.31", + "5.10.248.33", + "5.10.248.41", + "5.10.248.57", + "5.10.248.65", + "5.10.248.67", + "5.10.248.88", + "5.10.248.92", + "5.10.248.95", + "5.10.248.99", + "5.10.248.107", + "5.10.248.110", + "5.10.248.117", + "5.10.248.119", + "5.10.248.128", + "5.10.248.138", + "5.10.248.155", + "5.10.248.158", + "5.10.248.160", + "5.10.248.164", + "5.10.248.169", + "5.10.248.170", + "5.10.248.171", + "5.10.248.183", + "5.10.248.215", + "5.10.248.225", + "5.10.248.237", + "5.10.248.240", + "5.10.248.241", + "5.10.249.2", + "5.10.249.24", + "5.10.249.44", + "5.10.249.79", + "5.10.249.105", + "5.10.249.138", + "5.10.249.140", + "5.10.249.175", + "5.10.249.184", + "5.10.249.208", + "5.10.249.233", + "5.10.249.242", + "5.22.193.2", + "5.22.193.3", + "5.22.200.250", + "5.22.203.46", + "5.22.203.47", + "5.42.217.8", + "5.42.217.25", + "5.42.217.76", + "5.42.217.181", + "5.42.217.186", + "5.42.217.207", + "5.42.217.208", + "5.42.223.9", + "5.42.223.119", + "5.42.223.160", + "5.42.223.179", + "5.42.223.189", + "5.42.223.247", + "5.42.223.254", + "5.56.128.128", + "5.56.132.47", + "5.56.132.69", + "5.56.132.70", + "5.56.132.76", + "5.56.132.79", + "5.56.132.89", + "5.56.132.93", + "5.56.132.97", + "5.56.132.99", + "5.56.132.100", + "5.56.132.109", + "5.56.132.120", + "5.56.132.121", + "5.56.132.126", + "5.56.132.132", + "5.56.132.145", + "5.56.132.148", + "5.56.132.150", + "5.56.132.157", + "5.56.132.169", + "5.56.132.170", + "5.56.132.174", + "5.56.132.176", + "5.56.132.177", + "5.56.132.180", + "5.56.132.182", + "5.56.132.183", + "5.56.132.185", + "5.56.132.186", + "5.56.132.188", + "5.56.132.189", + "5.56.132.203", + "5.56.132.218", + "5.56.132.219", + "5.56.132.220", + "5.56.132.235", + "5.56.132.251", + "5.56.132.253", + "5.56.134.9", + "5.56.134.51", + "5.56.134.95", + "5.56.134.153", + "5.56.134.237", + "5.56.135.5", + "5.56.135.15", + "5.56.135.16", + "5.56.135.25", + "5.56.135.131", + "5.56.135.132", + "5.56.135.196", + "5.56.135.218", + "5.56.135.244", + "5.57.32.2", + "5.57.32.29", + "5.57.32.32", + "5.57.32.103", + "5.57.32.106", + "5.57.32.125", + "5.57.32.140", + "5.57.32.163", + "5.57.32.164", + "5.57.32.165", + "5.57.32.167", + "5.57.32.168", + "5.57.32.169", + "5.57.32.170", + "5.57.32.171", + "5.57.32.172", + "5.57.32.174", + "5.57.32.205", + "5.57.32.226", + "5.57.32.228", + "5.57.32.229", + "5.57.32.231", + "5.57.32.232", + "5.57.32.236", + "5.57.32.237", + "5.57.32.239", + "5.57.32.240", + "5.57.32.242", + "5.57.32.243", + "5.57.32.244", + "5.57.32.245", + "5.57.32.246", + "5.57.32.248", + "5.57.32.249", + "5.57.32.252", + "5.57.32.253", + "5.57.34.4", + "5.57.34.48", + "5.57.34.53", + "5.57.34.121", + "5.57.34.140", + "5.57.34.142", + "5.57.34.194", + "5.57.34.195", + "5.57.34.197", + "5.57.34.229", + "5.57.34.230", + "5.57.34.231", + "5.57.34.232", + "5.57.34.233", + "5.57.34.234", + "5.57.34.235", + "5.57.34.236", + "5.57.34.237", + "5.57.34.238", + "5.57.34.242", + "5.57.34.243", + "5.57.34.244", + "5.57.34.246", + "5.57.34.249", + "5.57.34.250", + "5.57.34.251", + "5.57.34.252", + "5.57.35.57", + "5.57.35.112", + "5.57.35.181", + "5.57.35.229", + "5.57.37.20", + "5.57.37.40", + "5.57.37.84", + "5.57.37.89", + "5.57.37.144", + "5.57.37.224", + "5.57.39.16", + "5.57.39.80", + "5.57.39.101", + "5.57.39.114", + "5.57.39.178", + "5.57.39.189", + "5.57.39.197", + "5.57.39.230", + "5.61.24.2", + "5.61.24.5", + "5.61.24.15", + "5.61.24.24", + "5.61.24.30", + "5.61.24.64", + "5.61.24.78", + "5.61.24.126", + "5.61.24.202", + "5.61.24.236", + "5.61.25.2", + "5.61.25.34", + "5.61.25.55", + "5.61.25.132", + "5.61.25.226", + "5.61.26.4", + "5.61.26.5", + "5.61.28.2", + "5.61.28.12", + "5.61.28.34", + "5.61.28.38", + "5.61.28.44", + "5.61.28.48", + "5.61.28.102", + "5.61.28.103", + "5.61.28.109", + "5.61.28.113", + "5.61.28.236", + "5.61.28.239", + "5.61.28.245", + "5.61.29.43", + "5.61.29.113", + "5.61.29.135", + "5.61.29.140", + "5.61.29.165", + "5.61.29.168", + "5.61.29.170", + "5.61.29.181", + "5.61.29.183", + "5.61.29.210", + "5.61.29.212", + "5.61.29.230", + "5.61.30.20", + "5.61.30.42", + "5.61.30.84", + "5.61.30.99", + "5.61.30.100", + "5.61.30.101", + "5.61.30.102", + "5.61.30.116", + "5.61.30.132", + "5.61.30.135", + "5.61.30.145", + "5.61.30.164", + "5.61.30.195", + "5.61.30.230", + "5.61.30.233", + "5.61.30.238", + "5.61.31.35", + "5.61.31.72", + "5.61.31.84", + "5.61.31.88", + "5.61.31.101", + "5.61.31.103", + "5.61.31.106", + "5.61.31.148", + "5.61.31.170", + "5.61.31.184", + "5.61.31.190", + "5.61.31.214", + "5.61.31.236", + "5.61.31.241", + "5.62.190.164", + "5.63.8.38", + "5.63.8.43", + "5.63.8.44", + "5.63.8.46", + "5.63.8.62", + "5.63.8.70", + "5.63.8.83", + "5.63.8.130", + "5.63.8.134", + "5.63.8.138", + "5.63.8.142", + "5.63.8.146", + "5.63.8.155", + "5.63.8.186", + "5.63.8.190", + "5.63.8.243", + "5.63.8.247", + "5.63.8.248", + "5.63.8.252", + "5.63.8.253", + "5.63.8.254", + "5.63.9.35", + "5.63.9.36", + "5.63.9.38", + "5.63.9.39", + "5.63.9.40", + "5.63.9.41", + "5.63.9.42", + "5.63.9.43", + "5.63.9.44", + "5.63.9.45", + "5.63.9.142", + "5.63.9.179", + "5.63.9.180", + "5.63.9.181", + "5.63.9.202", + "5.63.9.205", + "5.63.9.206", + "5.63.9.219", + "5.63.9.222", + "5.63.9.242", + "5.63.9.251", + "5.63.10.115", + "5.63.10.122", + "5.63.10.202", + "5.63.10.227", + "5.63.10.228", + "5.63.13.6", + "5.63.13.10", + "5.63.13.14", + "5.63.13.61", + "5.63.13.62", + "5.63.13.91", + "5.63.13.92", + "5.63.13.93", + "5.63.13.94", + "5.63.13.98", + "5.63.13.125", + "5.63.13.147", + "5.63.13.158", + "5.63.13.172", + "5.63.13.174", + "5.63.13.179", + "5.63.13.183", + "5.63.13.187", + "5.63.13.188", + "5.63.13.212", + "5.63.13.246", + "5.63.13.250", + "5.63.15.2", + "5.63.15.95", + "5.74.13.212", + "5.74.20.118", + "5.74.25.40", + "5.74.27.137", + "5.74.30.157", + "5.74.35.175", + "5.74.40.57", + "5.74.64.61", + "5.74.70.72", + "5.74.80.124", + "5.74.82.62", + "5.74.85.54", + "5.74.87.230", + "5.74.89.141", + "5.74.91.84", + "5.74.93.18", + "5.74.94.246", + "5.74.103.0", + "5.74.108.253", + "5.74.116.99", + "5.74.157.17", + "5.74.159.248", + "5.74.168.73", + "5.74.169.165", + "5.74.173.111", + "5.74.177.58", + "5.74.179.196", + "5.74.193.165", + "5.74.201.57", + "5.74.204.86", + "5.74.220.174", + "5.74.226.60", + "5.74.229.186", + "5.74.230.137", + "5.74.233.4", + "5.74.247.49", + "5.74.249.133", + "5.74.249.152", + "5.74.250.193", + "5.74.254.130", + "5.74.255.47", + "5.106.6.30", + "5.106.18.134", + "5.106.19.21", + "5.106.28.197", + "5.106.50.108", + "5.106.52.108", + "5.134.192.129", + "5.134.194.83", + "5.144.128.17", + "5.144.128.20", + "5.144.128.21", + "5.144.129.2", + "5.144.129.4", + "5.144.129.6", + "5.144.129.7", + "5.144.129.8", + "5.144.129.9", + "5.144.129.10", + "5.144.129.12", + "5.144.129.14", + "5.144.129.15", + "5.144.129.16", + "5.144.129.18", + "5.144.129.19", + "5.144.129.20", + "5.144.129.21", + "5.144.129.22", + "5.144.129.23", + "5.144.129.24", + "5.144.129.26", + "5.144.129.27", + "5.144.129.28", + "5.144.129.29", + "5.144.129.30", + "5.144.129.31", + "5.144.129.33", + "5.144.129.34", + "5.144.129.35", + "5.144.129.36", + "5.144.129.37", + "5.144.129.38", + "5.144.129.39", + "5.144.129.41", + "5.144.129.42", + "5.144.129.43", + "5.144.129.44", + "5.144.129.45", + "5.144.129.46", + "5.144.129.47", + "5.144.129.48", + "5.144.129.50", + "5.144.129.51", + "5.144.129.53", + "5.144.129.54", + "5.144.129.55", + "5.144.129.56", + "5.144.129.58", + "5.144.129.59", + "5.144.129.60", + "5.144.129.61", + "5.144.129.62", + "5.144.129.65", + "5.144.129.67", + "5.144.129.68", + "5.144.129.70", + "5.144.129.71", + "5.144.129.72", + "5.144.129.73", + "5.144.129.74", + "5.144.129.75", + "5.144.129.76", + "5.144.129.77", + "5.144.129.78", + "5.144.129.79", + "5.144.129.80", + "5.144.129.81", + "5.144.129.82", + "5.144.129.84", + "5.144.129.85", + "5.144.129.86", + "5.144.129.87", + "5.144.129.88", + "5.144.129.89", + "5.144.129.90", + "5.144.129.91", + "5.144.129.92", + "5.144.129.93", + "5.144.129.94", + "5.144.129.95", + "5.144.129.96", + "5.144.129.97", + "5.144.129.98", + "5.144.129.99", + "5.144.129.102", + "5.144.129.103", + "5.144.129.104", + "5.144.129.105", + "5.144.129.106", + "5.144.129.107", + "5.144.129.108", + "5.144.129.109", + "5.144.129.112", + "5.144.129.113", + "5.144.129.114", + "5.144.129.115", + "5.144.129.116", + "5.144.129.117", + "5.144.129.118", + "5.144.129.119", + "5.144.129.120", + "5.144.129.121", + "5.144.129.122", + "5.144.129.123", + "5.144.129.124", + "5.144.129.125", + "5.144.129.126", + "5.144.129.127", + "5.144.129.128", + "5.144.129.129", + "5.144.129.130", + "5.144.129.131", + "5.144.129.132", + "5.144.129.133", + "5.144.129.134", + "5.144.129.135", + "5.144.129.136", + "5.144.129.137", + "5.144.129.139", + "5.144.129.140", + "5.144.129.141", + "5.144.129.142", + "5.144.129.143", + "5.144.129.144", + "5.144.129.145", + "5.144.129.146", + "5.144.129.147", + "5.144.129.149", + "5.144.129.150", + "5.144.129.152", + "5.144.129.153", + "5.144.129.154", + "5.144.129.155", + "5.144.129.156", + "5.144.129.157", + "5.144.129.158", + "5.144.129.159", + "5.144.129.160", + "5.144.129.161", + "5.144.129.162", + "5.144.129.163", + "5.144.129.164", + "5.144.129.165", + "5.144.129.167", + "5.144.129.168", + "5.144.129.169", + "5.144.129.170", + "5.144.129.171", + "5.144.129.172", + "5.144.129.173", + "5.144.129.174", + "5.144.129.175", + "5.144.129.176", + "5.144.129.178", + "5.144.129.179", + "5.144.129.180", + "5.144.129.181", + "5.144.129.182", + "5.144.129.183", + "5.144.129.184", + "5.144.129.185", + "5.144.129.186", + "5.144.129.187", + "5.144.129.188", + "5.144.129.190", + "5.144.129.191", + "5.144.129.192", + "5.144.129.193", + "5.144.129.194", + "5.144.129.195", + "5.144.129.196", + "5.144.129.197", + "5.144.129.198", + "5.144.129.199", + "5.144.129.200", + "5.144.129.201", + "5.144.129.202", + "5.144.129.203", + "5.144.129.204", + "5.144.129.205", + "5.144.129.206", + "5.144.129.207", + "5.144.129.208", + "5.144.129.209", + "5.144.129.210", + "5.144.129.211", + "5.144.129.212", + "5.144.129.213", + "5.144.129.214", + "5.144.129.215", + "5.144.129.217", + "5.144.129.218", + "5.144.129.219", + "5.144.129.220", + "5.144.129.221", + "5.144.129.222", + "5.144.129.223", + "5.144.129.224", + "5.144.129.225", + "5.144.129.226", + "5.144.129.227", + "5.144.129.228", + "5.144.129.229", + "5.144.129.230", + "5.144.129.231", + "5.144.129.232", + "5.144.129.233", + "5.144.129.234", + "5.144.129.235", + "5.144.129.237", + "5.144.129.238", + "5.144.129.239", + "5.144.129.240", + "5.144.129.241", + "5.144.129.242", + "5.144.129.243", + "5.144.129.244", + "5.144.129.245", + "5.144.129.246", + "5.144.129.247", + "5.144.129.248", + "5.144.129.249", + "5.144.129.250", + "5.144.129.251", + "5.144.129.252", + "5.144.129.253", + "5.144.129.254", + "5.144.130.3", + "5.144.130.5", + "5.144.130.7", + "5.144.130.9", + "5.144.130.10", + "5.144.130.11", + "5.144.130.12", + "5.144.130.13", + "5.144.130.14", + "5.144.130.15", + "5.144.130.16", + "5.144.130.17", + "5.144.130.20", + "5.144.130.21", + "5.144.130.23", + "5.144.130.24", + "5.144.130.25", + "5.144.130.27", + "5.144.130.28", + "5.144.130.29", + "5.144.130.30", + "5.144.130.31", + "5.144.130.32", + "5.144.130.34", + "5.144.130.35", + "5.144.130.36", + "5.144.130.37", + "5.144.130.40", + "5.144.130.42", + "5.144.130.43", + "5.144.130.44", + "5.144.130.45", + "5.144.130.46", + "5.144.130.47", + "5.144.130.48", + "5.144.130.50", + "5.144.130.51", + "5.144.130.53", + "5.144.130.57", + "5.144.130.59", + "5.144.130.60", + "5.144.130.61", + "5.144.130.62", + "5.144.130.63", + "5.144.130.64", + "5.144.130.65", + "5.144.130.66", + "5.144.130.67", + "5.144.130.68", + "5.144.130.69", + "5.144.130.70", + "5.144.130.71", + "5.144.130.72", + "5.144.130.73", + "5.144.130.74", + "5.144.130.76", + "5.144.130.77", + "5.144.130.78", + "5.144.130.79", + "5.144.130.80", + "5.144.130.81", + "5.144.130.83", + "5.144.130.85", + "5.144.130.86", + "5.144.130.87", + "5.144.130.88", + "5.144.130.89", + "5.144.130.91", + "5.144.130.92", + "5.144.130.93", + "5.144.130.95", + "5.144.130.97", + "5.144.130.98", + "5.144.130.99", + "5.144.130.100", + "5.144.130.101", + "5.144.130.102", + "5.144.130.103", + "5.144.130.104", + "5.144.130.105", + "5.144.130.106", + "5.144.130.107", + "5.144.130.108", + "5.144.130.109", + "5.144.130.110", + "5.144.130.111", + "5.144.130.112", + "5.144.130.114", + "5.144.130.115", + "5.144.130.116", + "5.144.130.118", + "5.144.130.120", + "5.144.130.121", + "5.144.130.123", + "5.144.130.125", + "5.144.130.126", + "5.144.130.127", + "5.144.130.128", + "5.144.130.129", + "5.144.130.130", + "5.144.130.131", + "5.144.130.132", + "5.144.130.133", + "5.144.130.134", + "5.144.130.135", + "5.144.130.136", + "5.144.130.137", + "5.144.130.139", + "5.144.130.140", + "5.144.130.141", + "5.144.130.142", + "5.144.130.144", + "5.144.130.146", + "5.144.130.148", + "5.144.130.149", + "5.144.130.151", + "5.144.130.152", + "5.144.130.153", + "5.144.130.154", + "5.144.130.155", + "5.144.130.156", + "5.144.130.157", + "5.144.130.158", + "5.144.130.160", + "5.144.130.161", + "5.144.130.162", + "5.144.130.163", + "5.144.130.164", + "5.144.130.165", + "5.144.130.166", + "5.144.130.167", + "5.144.130.168", + "5.144.130.169", + "5.144.130.170", + "5.144.130.172", + "5.144.130.173", + "5.144.130.174", + "5.144.130.175", + "5.144.130.176", + "5.144.130.178", + "5.144.130.179", + "5.144.130.180", + "5.144.130.181", + "5.144.130.182", + "5.144.130.183", + "5.144.130.184", + "5.144.130.185", + "5.144.130.187", + "5.144.130.188", + "5.144.130.189", + "5.144.130.191", + "5.144.130.192", + "5.144.130.193", + "5.144.130.194", + "5.144.130.195", + "5.144.130.197", + "5.144.130.198", + "5.144.130.199", + "5.144.130.201", + "5.144.130.202", + "5.144.130.203", + "5.144.130.204", + "5.144.130.205", + "5.144.130.206", + "5.144.130.207", + "5.144.130.208", + "5.144.130.209", + "5.144.130.210", + "5.144.130.211", + "5.144.130.212", + "5.144.130.213", + "5.144.130.215", + "5.144.130.216", + "5.144.130.217", + "5.144.130.218", + "5.144.130.220", + "5.144.130.221", + "5.144.130.222", + "5.144.130.223", + "5.144.130.224", + "5.144.130.225", + "5.144.130.226", + "5.144.130.227", + "5.144.130.228", + "5.144.130.229", + "5.144.130.230", + "5.144.130.231", + "5.144.130.232", + "5.144.130.233", + "5.144.130.234", + "5.144.130.235", + "5.144.130.236", + "5.144.130.238", + "5.144.130.239", + "5.144.130.241", + "5.144.130.242", + "5.144.130.243", + "5.144.130.244", + "5.144.130.246", + "5.144.130.247", + "5.144.130.248", + "5.144.130.250", + "5.144.130.251", + "5.144.130.253", + "5.144.130.254", + "5.144.131.168", + "5.144.131.226", + "5.144.131.227", + "5.144.131.228", + "5.144.131.229", + "5.144.131.230", + "5.144.131.231", + "5.144.131.234", + "5.144.131.236", + "5.144.131.237", + "5.144.131.238", + "5.144.131.239", + "5.144.131.244", + "5.144.131.249", + "5.144.131.250", + "5.144.131.251", + "5.144.131.252", + "5.144.131.254", + "5.144.132.4", + "5.144.132.6", + "5.144.132.23", + "5.144.132.24", + "5.144.132.25", + "5.144.132.29", + "5.144.132.33", + "5.144.132.38", + "5.144.132.53", + "5.144.132.111", + "5.144.132.123", + "5.144.132.137", + "5.144.132.151", + "5.144.132.155", + "5.144.132.158", + "5.144.132.167", + "5.144.132.175", + "5.144.132.178", + "5.144.132.199", + "5.144.132.237", + "5.144.132.238", + "5.144.132.241", + "5.144.133.55", + "5.144.133.156", + "5.144.133.163", + "5.144.133.164", + "5.144.133.171", + "5.144.133.182", + "5.144.133.183", + "5.144.133.195", + "5.144.133.198", + "5.144.133.244", + "5.144.134.74", + "5.144.134.85", + "5.144.134.126", + "5.144.134.210", + "5.144.134.211", + "5.144.134.212", + "5.144.134.213", + "5.144.134.214", + "5.144.135.35", + "5.144.135.36", + "5.144.135.37", + "5.144.135.186", + "5.145.112.38", + "5.145.112.39", + "5.145.112.50", + "5.145.112.51", + "5.145.112.52", + "5.145.112.54", + "5.145.112.55", + "5.145.112.56", + "5.145.112.58", + "5.145.112.59", + "5.145.112.70", + "5.145.112.112", + "5.145.113.112", + "5.145.115.90", + "5.145.115.115", + "5.145.115.125", + "5.145.119.118", + "5.145.119.119", + "5.159.48.16", + "5.159.48.25", + "5.159.48.41", + "5.159.48.42", + "5.159.48.43", + "5.159.48.49", + "5.159.49.10", + "5.159.49.28", + "5.159.49.40", + "5.159.49.59", + "5.159.49.78", + "5.159.49.95", + "5.159.49.100", + "5.159.49.118", + "5.159.49.141", + "5.159.49.154", + "5.159.49.162", + "5.159.49.177", + "5.159.49.212", + "5.159.49.233", + "5.159.50.144", + "5.159.50.146", + "5.159.50.147", + "5.159.50.148", + "5.159.50.149", + "5.159.50.150", + "5.159.51.1", + "5.159.51.2", + "5.159.51.16", + "5.159.51.17", + "5.159.51.18", + "5.159.51.19", + "5.159.51.20", + "5.159.51.21", + "5.159.51.22", + "5.159.51.23", + "5.159.51.192", + "5.159.51.193", + "5.159.51.195", + "5.159.51.196", + "5.159.51.197", + "5.159.51.198", + "5.159.51.199", + "5.159.51.200", + "5.159.51.201", + "5.159.51.202", + "5.159.51.203", + "5.159.51.204", + "5.159.51.205", + "5.159.51.206", + "5.159.52.46", + "5.159.52.47", + "5.159.52.48", + "5.159.52.49", + "5.159.52.50", + "5.159.52.51", + "5.159.52.52", + "5.159.52.54", + "5.159.52.55", + "5.159.52.56", + "5.159.52.57", + "5.159.52.58", + "5.159.52.59", + "5.159.52.60", + "5.159.52.61", + "5.159.52.62", + "5.159.52.74", + "5.159.52.75", + "5.159.53.9", + "5.159.53.10", + "5.159.53.11", + "5.159.55.51", + "5.159.55.55", + "5.159.55.106", + "5.159.55.116", + "5.159.55.123", + "5.159.55.154", + "5.159.55.226", + "5.159.55.227", + "5.160.0.37", + "5.160.2.51", + "5.160.2.157", + "5.160.2.158", + "5.160.5.66", + "5.160.5.67", + "5.160.5.68", + "5.160.5.207", + "5.160.6.67", + "5.160.8.243", + "5.160.10.151", + "5.160.10.152", + "5.160.10.204", + "5.160.11.95", + "5.160.12.98", + "5.160.13.235", + "5.160.14.4", + "5.160.27.80", + "5.160.28.35", + "5.160.28.180", + "5.160.29.154", + "5.160.30.27", + "5.160.42.43", + "5.160.42.44", + "5.160.42.120", + "5.160.42.138", + "5.160.42.180", + "5.160.43.117", + "5.160.43.195", + "5.160.46.2", + "5.160.46.234", + "5.160.47.154", + "5.160.47.196", + "5.160.47.199", + "5.160.48.202", + "5.160.48.210", + "5.160.48.212", + "5.160.48.214", + "5.160.48.215", + "5.160.48.216", + "5.160.48.217", + "5.160.48.218", + "5.160.48.219", + "5.160.48.220", + "5.160.50.34", + "5.160.50.174", + "5.160.53.92", + "5.160.56.242", + "5.160.56.244", + "5.160.56.254", + "5.160.61.43", + "5.160.61.167", + "5.160.63.135", + "5.160.64.69", + "5.160.65.23", + "5.160.66.82", + "5.160.66.91", + "5.160.66.171", + "5.160.66.172", + "5.160.66.173", + "5.160.68.228", + "5.160.69.36", + "5.160.71.168", + "5.160.72.210", + "5.160.72.211", + "5.160.72.212", + "5.160.75.2", + "5.160.79.56", + "5.160.79.219", + "5.160.80.180", + "5.160.80.183", + "5.160.80.230", + "5.160.83.253", + "5.160.85.243", + "5.160.85.244", + "5.160.86.141", + "5.160.90.91", + "5.160.91.141", + "5.160.91.243", + "5.160.92.164", + "5.160.93.189", + "5.160.95.50", + "5.160.96.34", + "5.160.98.197", + "5.160.99.121", + "5.160.99.226", + "5.160.99.238", + "5.160.103.178", + "5.160.103.179", + "5.160.104.2", + "5.160.104.3", + "5.160.108.179", + "5.160.108.181", + "5.160.109.165", + "5.160.109.229", + "5.160.115.35", + "5.160.116.174", + "5.160.116.180", + "5.160.118.138", + "5.160.119.53", + "5.160.120.52", + "5.160.121.70", + "5.160.121.186", + "5.160.122.41", + "5.160.123.165", + "5.160.128.142", + "5.160.134.2", + "5.160.135.51", + "5.160.138.21", + "5.160.138.43", + "5.160.138.66", + "5.160.138.70", + "5.160.138.157", + "5.160.138.220", + "5.160.139.18", + "5.160.139.19", + "5.160.139.74", + "5.160.139.86", + "5.160.139.91", + "5.160.140.4", + "5.160.142.7", + "5.160.144.16", + "5.160.146.1", + "5.160.146.74", + "5.160.146.194", + "5.160.146.196", + "5.160.146.197", + "5.160.146.232", + "5.160.146.234", + "5.160.150.30", + "5.160.151.59", + "5.160.151.60", + "5.160.152.135", + "5.160.152.163", + "5.160.152.164", + "5.160.152.165", + "5.160.152.171", + "5.160.153.4", + "5.160.154.34", + "5.160.154.53", + "5.160.154.61", + "5.160.159.98", + "5.160.166.36", + "5.160.167.249", + "5.160.178.229", + "5.160.179.179", + "5.160.179.227", + "5.160.179.237", + "5.160.186.163", + "5.160.187.35", + "5.160.187.51", + "5.160.189.11", + "5.160.189.12", + "5.160.192.5", + "5.160.192.9", + "5.160.195.11", + "5.160.196.134", + "5.160.196.141", + "5.160.196.142", + "5.160.196.181", + "5.160.197.195", + "5.160.197.196", + "5.160.197.197", + "5.160.197.199", + "5.160.197.200", + "5.160.197.210", + "5.160.197.221", + "5.160.198.78", + "5.160.200.18", + "5.160.200.163", + "5.160.200.168", + "5.160.200.172", + "5.160.200.174", + "5.160.200.178", + "5.160.200.197", + "5.160.200.204", + "5.160.203.70", + "5.160.208.6", + "5.160.211.32", + "5.160.211.131", + "5.160.211.132", + "5.160.215.166", + "5.160.217.10", + "5.160.217.11", + "5.160.218.102", + "5.160.218.121", + "5.160.218.125", + "5.160.218.236", + "5.160.218.245", + "5.160.219.133", + "5.160.219.139", + "5.160.219.188", + "5.160.223.51", + "5.160.223.52", + "5.160.225.4", + "5.160.225.20", + "5.160.227.245", + "5.160.228.181", + "5.160.230.66", + "5.160.231.210", + "5.160.232.104", + "5.160.242.54", + "5.160.242.184", + "5.160.243.30", + "5.160.243.66", + "5.160.243.137", + "5.160.243.151", + "5.160.243.157", + "5.160.243.227", + "5.160.243.231", + "5.160.246.47", + "5.160.246.178", + "5.160.246.252", + "5.160.247.71", + "5.160.247.147", + "5.160.247.148", + "5.160.247.158", + "5.160.247.196", + "5.160.247.198", + "5.160.247.199", + "5.160.247.200", + "5.160.247.201", + "5.160.247.202", + "5.182.44.16", + "5.182.44.19", + "5.182.44.20", + "5.182.44.26", + "5.182.44.28", + "5.182.44.33", + "5.182.44.34", + "5.182.44.39", + "5.182.44.41", + "5.182.44.43", + "5.182.44.48", + "5.182.44.55", + "5.182.44.58", + "5.182.44.61", + "5.182.44.63", + "5.182.44.65", + "5.182.44.67", + "5.182.44.71", + "5.182.44.72", + "5.182.44.74", + "5.182.44.77", + "5.182.44.92", + "5.182.44.98", + "5.182.44.99", + "5.182.44.100", + "5.182.44.101", + "5.182.44.102", + "5.182.44.103", + "5.182.44.104", + "5.182.44.105", + "5.182.44.106", + "5.182.44.107", + "5.182.44.110", + "5.182.44.112", + "5.182.44.113", + "5.182.44.114", + "5.182.44.115", + "5.182.44.116", + "5.182.44.117", + "5.182.44.119", + "5.182.44.121", + "5.182.44.127", + "5.182.44.129", + "5.182.44.130", + "5.182.44.131", + "5.182.44.138", + "5.182.44.139", + "5.182.44.141", + "5.182.44.142", + "5.182.44.144", + "5.182.44.146", + "5.182.44.148", + "5.182.44.149", + "5.182.44.152", + "5.182.44.154", + "5.182.44.155", + "5.182.44.158", + "5.182.44.160", + "5.182.44.162", + "5.182.44.163", + "5.182.44.164", + "5.182.44.165", + "5.182.44.167", + "5.182.44.171", + "5.182.44.172", + "5.182.44.173", + "5.182.44.178", + "5.182.44.180", + "5.182.44.181", + "5.182.44.182", + "5.182.44.185", + "5.182.44.187", + "5.182.44.188", + "5.182.44.189", + "5.182.44.192", + "5.182.44.194", + "5.182.44.196", + "5.182.44.198", + "5.182.44.202", + "5.182.44.204", + "5.182.44.206", + "5.182.44.207", + "5.182.44.210", + "5.182.44.217", + "5.182.44.219", + "5.182.44.223", + "5.182.44.224", + "5.182.44.226", + "5.182.44.228", + "5.182.44.229", + "5.182.44.241", + "5.182.44.244", + "5.182.44.250", + "5.182.45.24", + "5.182.46.1", + "5.182.46.2", + "5.182.46.7", + "5.182.46.23", + "5.182.46.31", + "5.182.46.34", + "5.182.46.36", + "5.182.46.41", + "5.182.46.48", + "5.182.46.57", + "5.182.46.192", + "5.182.46.195", + "5.190.1.5", + "5.190.1.25", + "5.190.1.27", + "5.190.2.65", + "5.190.8.113", + "5.190.15.130", + "5.190.29.201", + "5.190.48.6", + "5.190.52.102", + "5.190.52.245", + "5.190.69.66", + "5.190.77.209", + "5.190.100.67", + "5.190.101.130", + "5.190.116.154", + "5.190.154.36", + "5.190.154.37", + "5.190.166.2", + "5.190.190.3", + "5.190.209.10", + "5.190.211.50", + "5.190.211.52", + "5.200.65.61", + "5.200.69.88", + "5.200.72.229", + "5.200.128.135", + "5.200.149.177", + "5.200.150.244", + "5.200.168.221", + "5.200.169.199", + "5.200.176.227", + "5.200.189.251", + "5.200.206.162", + "5.200.228.95", + "5.200.229.26", + "5.200.229.99", + "5.201.161.214", + "5.201.164.149", + "5.201.165.218", + "5.201.175.194", + "5.201.177.244", + "5.202.5.222", + "5.202.6.42", + "5.202.6.46", + "5.202.6.67", + "5.202.6.68", + "5.202.6.195", + "5.202.6.198", + "5.202.6.199", + "5.202.6.200", + "5.202.6.201", + "5.202.6.202", + "5.202.6.203", + "5.202.6.207", + "5.202.6.230", + "5.202.7.28", + "5.202.7.70", + "5.202.7.85", + "5.202.7.168", + "5.202.7.170", + "5.202.8.253", + "5.202.9.71", + "5.202.9.77", + "5.202.9.78", + "5.202.9.99", + "5.202.10.2", + "5.202.10.36", + "5.202.10.39", + "5.202.10.42", + "5.202.10.44", + "5.202.10.45", + "5.202.10.48", + "5.202.10.49", + "5.202.10.50", + "5.202.10.52", + "5.202.10.54", + "5.202.10.55", + "5.202.10.58", + "5.202.10.61", + "5.202.12.20", + "5.202.13.85", + "5.202.13.86", + "5.202.13.234", + "5.202.13.235", + "5.202.13.236", + "5.202.14.2", + "5.202.14.7", + "5.202.15.246", + "5.202.28.142", + "5.202.45.250", + "5.202.50.41", + "5.202.50.42", + "5.202.56.58", + "5.202.60.146", + "5.202.74.30", + "5.202.75.244", + "5.202.82.59", + "5.202.84.226", + "5.202.84.234", + "5.202.85.159", + "5.202.90.128", + "5.202.90.131", + "5.202.90.187", + "5.202.93.180", + "5.202.100.100", + "5.202.100.101", + "5.202.104.85", + "5.202.104.163", + "5.202.129.29", + "5.202.129.30", + "5.202.129.31", + "5.202.129.32", + "5.202.163.204", + "5.202.171.138", + "5.202.174.168", + "5.202.174.211", + "5.202.175.80", + "5.202.179.237", + "5.202.181.233", + "5.202.181.246", + "5.202.183.10", + "5.202.183.165", + "5.202.192.34", + "5.202.195.114", + "5.202.196.63", + "5.202.196.245", + "5.202.197.202", + "5.202.240.33", + "5.202.240.36", + "5.202.240.37", + "5.202.240.43", + "5.202.243.94", + "5.202.252.30", + "5.202.254.25", + "5.232.142.26", + "5.232.200.53", + "5.232.202.36", + "5.232.207.123", + "5.233.44.58", + "5.233.44.60", + "5.233.44.130", + "5.233.46.104", + "5.233.49.38", + "5.233.64.155", + "5.233.68.117", + "5.233.69.52", + "5.233.74.61", + "5.233.79.124", + "5.233.87.166", + "5.233.184.241", + "5.234.1.249", + "5.234.2.97", + "5.234.3.203", + "5.234.6.249", + "5.234.7.101", + "5.234.14.184", + "5.234.17.49", + "5.234.18.240", + "5.234.21.81", + "5.234.23.206", + "5.234.28.29", + "5.234.28.201", + "5.234.45.50", + "5.234.45.240", + "5.234.165.195", + "5.234.194.136", + "5.234.196.39", + "5.234.201.187", + "5.234.202.60", + "5.234.213.173", + "5.234.215.171", + "5.235.161.28", + "5.235.172.1", + "5.235.175.177", + "5.235.177.154", + "5.235.178.108", + "5.235.180.3", + "5.235.186.172", + "5.235.232.65", + "5.235.254.187", + "5.236.24.76", + "5.236.25.20", + "5.236.37.8", + "5.236.96.242", + "5.236.104.163", + "5.236.105.251", + "5.236.193.47", + "5.236.193.128", + "5.236.194.247", + "5.236.195.113", + "5.236.195.211", + "5.237.179.70", + "5.237.193.239", + "5.237.201.65", + "5.237.243.46", + "5.237.243.168", + "5.238.52.10", + "5.238.52.133", + "5.238.65.238", + "5.238.88.214", + "5.238.91.216", + "5.238.110.141", + "5.238.110.178", + "5.238.129.145", + "5.238.136.226", + "5.238.139.75", + "5.238.142.6", + "5.238.146.31", + "5.238.146.126", + "5.238.152.244", + "5.238.157.188", + "5.238.158.161", + "5.238.161.202", + "5.238.163.58", + "5.238.165.255", + "5.238.167.20", + "5.238.168.18", + "5.238.169.163", + "5.238.169.251", + "5.238.171.210", + "5.238.176.158", + "5.238.184.49", + "5.238.192.34", + "5.238.192.161", + "5.238.200.214", + "5.238.205.46", + "5.238.206.81", + "5.238.211.141", + "5.238.220.93", + "5.238.221.36", + "5.238.227.230", + "5.238.228.234", + "5.238.229.0", + "5.238.230.8", + "5.238.236.19", + "5.238.239.99", + "5.238.241.190", + "5.238.242.251", + "5.238.243.187", + "5.238.245.7", + "5.238.255.152", + "5.239.10.28", + "5.239.19.142", + "5.239.39.175", + "5.239.62.113", + "5.239.62.215", + "5.239.76.206", + "5.239.78.243", + "5.239.80.95", + "5.239.80.138", + "5.239.85.223", + "5.239.100.27", + "5.239.110.134", + "5.239.122.122", + "5.239.147.118", + "5.239.160.77", + "5.239.177.85", + "5.239.241.236", + "5.239.244.75", + "31.7.65.29", + "31.7.65.45", + "31.7.65.61", + "31.7.65.92", + "31.7.65.102", + "31.7.65.132", + "31.7.65.133", + "31.7.65.134", + "31.7.65.135", + "31.7.66.78", + "31.7.66.235", + "31.7.68.93", + "31.7.68.201", + "31.7.68.202", + "31.7.68.206", + "31.7.68.215", + "31.7.68.220", + "31.7.68.246", + "31.7.68.248", + "31.7.68.250", + "31.7.68.251", + "31.7.69.57", + "31.7.69.218", + "31.7.70.12", + "31.7.70.104", + "31.7.71.8", + "31.7.71.11", + "31.7.71.122", + "31.7.71.197", + "31.7.72.37", + "31.7.72.114", + "31.7.72.147", + "31.7.72.148", + "31.7.72.179", + "31.7.72.180", + "31.7.72.181", + "31.7.72.182", + "31.7.73.5", + "31.7.73.6", + "31.7.73.21", + "31.7.73.91", + "31.7.73.95", + "31.7.73.117", + "31.7.73.120", + "31.7.73.131", + "31.7.74.4", + "31.7.74.164", + "31.7.76.254", + "31.7.77.226", + "31.7.78.15", + "31.7.78.39", + "31.7.78.83", + "31.7.78.124", + "31.7.78.133", + "31.7.78.140", + "31.7.78.157", + "31.7.78.180", + "31.7.78.191", + "31.7.79.69", + "31.7.79.114", + "31.7.79.130", + "31.7.79.137", + "31.7.79.140", + "31.7.79.150", + "31.7.79.155", + "31.7.79.156", + "31.7.79.200", + "31.7.89.103", + "31.7.89.104", + "31.14.113.35", + "31.14.113.146", + "31.14.113.147", + "31.14.113.148", + "31.14.113.149", + "31.14.113.150", + "31.14.114.62", + "31.14.115.2", + "31.14.115.12", + "31.14.115.70", + "31.14.115.83", + "31.14.115.196", + "31.14.115.198", + "31.14.116.2", + "31.14.117.10", + "31.14.117.194", + "31.14.117.202", + "31.14.117.207", + "31.14.117.212", + "31.14.118.146", + "31.14.118.147", + "31.14.118.228", + "31.14.118.229", + "31.14.119.165", + "31.14.120.2", + "31.14.121.116", + "31.14.121.131", + "31.14.123.2", + "31.14.123.24", + "31.14.123.127", + "31.14.123.145", + "31.14.124.74", + "31.24.234.34", + "31.24.234.35", + "31.24.234.37", + "31.25.90.7", + "31.25.90.10", + "31.25.90.11", + "31.25.90.13", + "31.25.90.22", + "31.25.90.29", + "31.25.90.76", + "31.25.90.93", + "31.25.90.94", + "31.25.90.100", + "31.25.90.110", + "31.25.90.114", + "31.25.90.116", + "31.25.90.130", + "31.25.90.131", + "31.25.90.132", + "31.25.90.142", + "31.25.90.143", + "31.25.90.145", + "31.25.90.146", + "31.25.90.147", + "31.25.90.148", + "31.25.90.149", + "31.25.90.150", + "31.25.90.151", + "31.25.90.152", + "31.25.90.158", + "31.25.90.163", + "31.25.90.164", + "31.25.90.165", + "31.25.90.166", + "31.25.90.167", + "31.25.90.168", + "31.25.90.169", + "31.25.90.170", + "31.25.90.171", + "31.25.90.172", + "31.25.90.176", + "31.25.90.177", + "31.25.90.178", + "31.25.90.179", + "31.25.90.180", + "31.25.90.182", + "31.25.90.189", + "31.25.90.194", + "31.25.90.229", + "31.25.90.242", + "31.25.91.4", + "31.25.91.5", + "31.25.91.6", + "31.25.91.9", + "31.25.91.10", + "31.25.91.11", + "31.25.91.12", + "31.25.91.13", + "31.25.91.15", + "31.25.91.22", + "31.25.91.40", + "31.25.91.41", + "31.25.91.84", + "31.25.91.85", + "31.25.91.87", + "31.25.91.97", + "31.25.91.112", + "31.25.91.113", + "31.25.91.115", + "31.25.91.124", + "31.25.91.204", + "31.25.95.227", + "31.47.38.137", + "31.47.42.2", + "31.47.43.228", + "31.47.43.241", + "31.47.43.246", + "31.47.45.2", + "31.47.46.34", + "31.47.46.35", + "31.47.46.36", + "31.47.46.37", + "31.47.46.38", + "31.47.46.41", + "31.47.46.42", + "31.47.46.44", + "31.47.46.46", + "31.47.46.51", + "31.47.46.53", + "31.47.46.54", + "31.47.46.56", + "31.47.46.59", + "31.47.46.60", + "31.47.46.61", + "31.47.46.62", + "31.47.46.67", + "31.47.46.69", + "31.47.46.70", + "31.47.46.72", + "31.47.46.73", + "31.47.46.75", + "31.47.46.76", + "31.47.46.81", + "31.47.46.83", + "31.47.46.84", + "31.47.46.86", + "31.47.46.87", + "31.47.51.2", + "31.47.51.32", + "31.47.51.102", + "31.47.53.2", + "31.47.55.162", + "31.47.56.131", + "31.47.61.33", + "31.47.61.66", + "31.47.61.99", + "31.47.62.193", + "31.58.237.101", + "31.58.237.107", + "31.130.182.10", + "31.170.49.43", + "31.171.222.180", + "31.171.223.114", + "31.184.129.66", + "31.184.130.2", + "31.184.130.252", + "31.184.131.3", + "31.184.134.159", + "31.184.134.161", + "31.184.134.178", + "31.184.136.173", + "31.184.160.109", + "31.184.170.203", + "31.184.171.128", + "31.184.191.246", + "31.193.186.2", + "31.193.186.3", + "31.193.186.4", + "31.193.186.5", + "31.193.186.6", + "31.193.186.7", + "31.193.186.8", + "31.193.186.9", + "31.193.186.10", + "31.193.186.11", + "31.193.186.12", + "31.193.186.13", + "31.193.186.14", + "31.214.168.26", + "31.214.168.35", + "31.214.168.42", + "31.214.168.68", + "31.214.168.100", + "31.214.168.132", + "31.214.168.162", + "31.214.168.164", + "31.214.168.166", + "31.214.168.179", + "31.214.168.180", + "31.214.168.229", + "31.214.168.246", + "31.214.169.21", + "31.214.169.22", + "31.214.169.206", + "31.214.169.244", + "31.214.170.74", + "31.214.170.163", + "31.214.170.194", + "31.214.170.213", + "31.214.171.86", + "31.214.171.130", + "31.214.171.132", + "31.214.171.133", + "31.214.171.134", + "31.214.171.135", + "31.214.171.170", + "31.214.171.171", + "31.214.171.174", + "31.214.172.6", + "31.214.172.11", + "31.214.172.78", + "31.214.172.183", + "31.214.172.184", + "31.214.173.30", + "31.214.173.76", + "31.214.173.170", + "31.214.173.178", + "31.214.173.210", + "31.214.173.244", + "31.214.173.254", + "31.214.174.4", + "31.214.174.6", + "31.214.174.27", + "31.214.174.90", + "31.214.174.91", + "31.214.174.92", + "31.214.174.93", + "31.214.174.99", + "31.214.174.114", + "31.214.174.125", + "31.214.174.163", + "31.214.174.166", + "31.214.174.194", + "31.214.174.195", + "31.214.174.196", + "31.214.174.197", + "31.214.174.205", + "31.214.175.10", + "31.214.175.11", + "31.214.175.13", + "31.214.175.14", + "31.214.175.146", + "31.214.175.185", + "31.214.175.214", + "31.214.175.220", + "31.214.175.246", + "31.214.228.242", + "31.214.228.243", + "31.214.229.212", + "31.214.231.121", + "31.214.248.27", + "31.214.248.60", + "31.214.248.74", + "31.214.248.75", + "31.214.248.76", + "31.214.248.77", + "31.214.248.78", + "31.214.248.102", + "31.214.248.146", + "31.214.248.147", + "31.214.248.174", + "31.214.250.14", + "31.214.250.28", + "31.214.250.42", + "31.214.250.69", + "31.214.250.70", + "31.214.250.72", + "31.214.250.75", + "31.214.250.76", + "31.214.250.77", + "31.214.250.83", + "31.214.250.84", + "31.214.250.88", + "31.214.250.90", + "31.214.250.91", + "31.214.250.92", + "31.214.250.94", + "31.214.250.96", + "31.214.250.98", + "31.214.250.99", + "31.214.250.100", + "31.214.250.101", + "31.214.250.102", + "31.214.250.106", + "31.214.250.108", + "31.214.250.109", + "31.214.250.112", + "31.214.250.113", + "31.214.250.114", + "31.214.250.116", + "31.214.250.118", + "31.214.250.121", + "31.214.250.122", + "31.214.250.123", + "31.214.250.125", + "31.214.250.198", + "31.214.250.199", + "31.214.250.201", + "31.214.250.203", + "31.214.250.209", + "31.214.250.210", + "31.214.250.211", + "31.214.250.213", + "31.214.250.214", + "31.214.250.215", + "31.214.250.216", + "31.214.250.217", + "31.214.250.218", + "31.214.250.219", + "31.214.250.220", + "31.214.250.221", + "31.214.251.6", + "31.214.251.147", + "31.214.251.148", + "31.214.251.149", + "31.214.251.194", + "31.214.251.196", + "31.214.251.199", + "31.214.251.200", + "31.214.251.201", + "31.214.251.202", + "31.214.251.203", + "31.214.251.204", + "31.214.251.206", + "31.214.251.207", + "31.214.251.208", + "31.214.251.209", + "31.214.251.215", + "31.214.251.217", + "31.214.251.219", + "31.214.251.220", + "31.214.251.228", + "31.214.251.230", + "31.214.251.231", + "31.214.251.232", + "31.214.251.233", + "31.214.251.234", + "31.214.251.238", + "31.214.251.239", + "31.214.251.241", + "31.214.251.247", + "31.214.251.253", + "31.214.255.50", + "31.214.255.53", + "31.214.255.54", + "31.214.255.139", + "31.214.255.140", + "31.214.255.152", + "31.214.255.154", + "31.214.255.155", + "31.214.255.156", + "31.214.255.160", + "31.214.255.161", + "31.214.255.163", + "31.214.255.165", + "31.214.255.166", + "31.214.255.167", + "31.214.255.168", + "31.214.255.169", + "31.214.255.170", + "31.214.255.171", + "31.214.255.176", + "31.214.255.177", + "31.214.255.178", + "31.214.255.179", + "31.214.255.180", + "31.214.255.181", + "31.214.255.182", + "31.214.255.183", + "31.214.255.184", + "31.214.255.185", + "31.214.255.186", + "31.214.255.187", + "31.214.255.188", + "31.214.255.189", + "31.216.62.30", + "31.216.62.39", + "31.216.62.58", + "31.216.62.60", + "31.216.62.82", + "31.216.62.86", + "31.216.62.93", + "31.216.62.102", + "31.216.62.107", + "31.216.62.118", + "31.216.62.131", + "31.216.62.136", + "31.216.62.137", + "31.216.62.144", + "31.216.62.146", + "31.216.62.147", + "31.216.62.158", + "31.216.62.170", + "31.216.62.203", + "31.216.62.212", + "31.216.62.227", + "31.216.62.248", + "37.9.253.32", + "37.9.253.33", + "37.10.67.10", + "37.10.67.18", + "37.10.109.29", + "37.32.2.2", + "37.32.4.41", + "37.32.4.124", + "37.32.8.52", + "37.32.8.55", + "37.32.8.100", + "37.32.8.180", + "37.32.8.208", + "37.32.8.253", + "37.32.9.100", + "37.32.9.117", + "37.32.9.118", + "37.32.9.248", + "37.32.10.21", + "37.32.10.53", + "37.32.10.120", + "37.32.10.151", + "37.32.10.227", + "37.32.10.254", + "37.32.11.26", + "37.32.12.41", + "37.32.12.48", + "37.32.12.50", + "37.32.12.52", + "37.32.12.146", + "37.32.13.105", + "37.32.13.171", + "37.32.14.47", + "37.32.14.114", + "37.32.14.184", + "37.32.14.226", + "37.32.14.234", + "37.32.15.0", + "37.32.15.18", + "37.32.15.85", + "37.32.15.155", + "37.32.15.205", + "37.32.15.208", + "37.32.15.248", + "37.32.22.248", + "37.32.24.35", + "37.32.24.126", + "37.32.24.156", + "37.32.24.192", + "37.32.25.162", + "37.32.25.174", + "37.32.25.185", + "37.32.26.165", + "37.32.27.138", + "37.32.27.222", + "37.32.28.174", + "37.32.29.195", + "37.32.29.247", + "37.32.43.208", + "37.32.43.224", + "37.32.44.58", + "37.32.45.18", + "37.32.45.20", + "37.32.46.131", + "37.32.46.132", + "37.32.46.133", + "37.32.46.134", + "37.32.46.135", + "37.32.46.136", + "37.32.46.137", + "37.32.46.138", + "37.32.46.139", + "37.32.46.141", + "37.32.46.142", + "37.32.46.143", + "37.32.46.144", + "37.32.46.145", + "37.32.46.146", + "37.32.46.147", + "37.32.46.148", + "37.32.46.149", + "37.32.46.162", + "37.32.46.163", + "37.32.46.165", + "37.32.46.166", + "37.32.46.168", + "37.32.46.169", + "37.32.46.171", + "37.32.46.172", + "37.32.46.174", + "37.32.46.175", + "37.32.47.196", + "37.32.120.138", + "37.32.120.139", + "37.32.120.148", + "37.32.121.133", + "37.32.125.138", + "37.32.126.130", + "37.32.127.99", + "37.32.127.100", + "37.32.127.106", + "37.75.240.5", + "37.75.240.14", + "37.75.240.15", + "37.75.240.16", + "37.75.241.200", + "37.130.202.14", + "37.130.202.66", + "37.130.206.6", + "37.130.206.7", + "37.143.148.145", + "37.148.0.167", + "37.148.0.222", + "37.148.3.35", + "37.148.3.51", + "37.148.4.83", + "37.148.13.79", + "37.148.13.187", + "37.148.15.80", + "37.148.15.221", + "37.148.18.165", + "37.148.20.117", + "37.148.21.64", + "37.148.23.120", + "37.148.23.172", + "37.148.31.13", + "37.148.31.44", + "37.148.40.88", + "37.148.42.47", + "37.148.45.196", + "37.148.48.54", + "37.148.49.252", + "37.148.50.125", + "37.148.50.202", + "37.148.57.6", + "37.148.58.5", + "37.148.59.134", + "37.148.61.147", + "37.148.81.227", + "37.148.82.138", + "37.148.83.154", + "37.148.83.210", + "37.148.84.245", + "37.148.85.86", + "37.148.85.180", + "37.148.87.111", + "37.148.91.8", + "37.148.248.50", + "37.148.248.77", + "37.152.160.27", + "37.152.173.170", + "37.152.173.215", + "37.152.175.67", + "37.152.176.118", + "37.152.176.179", + "37.152.176.237", + "37.152.177.84", + "37.152.177.138", + "37.152.179.31", + "37.152.179.69", + "37.152.180.124", + "37.152.180.157", + "37.152.180.240", + "37.152.181.60", + "37.152.181.115", + "37.152.181.142", + "37.152.182.41", + "37.152.182.119", + "37.152.182.187", + "37.152.183.92", + "37.152.186.170", + "37.152.186.203", + "37.152.186.224", + "37.152.188.11", + "37.152.188.16", + "37.152.188.131", + "37.152.188.132", + "37.152.188.220", + "37.152.188.254", + "37.152.189.114", + "37.152.189.128", + "37.152.189.156", + "37.152.190.8", + "37.152.190.9", + "37.152.190.80", + "37.152.190.239", + "37.152.190.252", + "37.152.191.223", + "37.156.8.90", + "37.156.9.203", + "37.156.9.255", + "37.156.11.220", + "37.156.14.221", + "37.156.20.91", + "37.156.144.82", + "37.156.144.83", + "37.156.144.85", + "37.156.144.91", + "37.156.144.125", + "37.156.144.202", + "37.156.144.203", + "37.156.144.204", + "37.156.145.84", + "37.156.145.86", + "37.156.145.136", + "37.156.145.137", + "37.156.145.138", + "37.156.145.139", + "37.156.145.141", + "37.156.145.144", + "37.156.145.146", + "37.156.145.147", + "37.156.145.152", + "37.156.145.153", + "37.156.145.155", + "37.156.145.158", + "37.156.145.197", + "37.156.145.251", + "37.156.145.252", + "37.156.146.39", + "37.156.146.40", + "37.156.146.45", + "37.156.146.61", + "37.156.146.105", + "37.156.146.109", + "37.156.146.125", + "37.156.146.166", + "37.156.146.245", + "37.156.147.43", + "37.156.147.110", + "37.156.147.116", + "37.156.147.125", + "37.156.147.220", + "37.156.147.221", + "37.156.147.242", + "37.156.147.243", + "37.156.147.244", + "37.156.147.245", + "37.156.147.246", + "37.191.76.202", + "37.191.77.81", + "37.191.79.136", + "37.191.92.114", + "37.191.92.115", + "37.191.92.116", + "37.191.92.117", + "37.191.92.118", + "37.191.92.119", + "37.191.93.195", + "37.191.93.196", + "37.191.93.202", + "37.191.93.203", + "37.191.94.35", + "37.191.94.44", + "37.191.95.6", + "37.191.95.7", + "37.191.95.13", + "37.191.95.14", + "37.191.95.70", + "37.191.95.82", + "37.191.95.86", + "37.191.95.99", + "37.191.95.100", + "37.202.148.139", + "37.202.154.171", + "37.202.158.11", + "37.202.159.84", + "37.202.159.125", + "37.202.159.230", + "37.202.169.144", + "37.202.169.202", + "37.202.169.251", + "37.202.170.32", + "37.202.171.239", + "37.202.172.64", + "37.202.172.82", + "37.202.182.143", + "37.202.185.61", + "37.202.185.66", + "37.202.185.132", + "37.202.188.85", + "37.202.189.203", + "37.202.191.176", + "37.202.225.95", + "37.202.225.173", + "37.202.225.206", + "37.202.225.224", + "37.202.229.195", + "37.202.229.200", + "37.202.229.205", + "37.202.229.209", + "37.202.229.215", + "37.202.229.220", + "37.202.232.222", + "37.202.236.4", + "37.202.236.12", + "37.202.236.100", + "37.202.236.126", + "37.202.237.2", + "37.202.237.195", + "37.202.237.242", + "37.202.237.246", + "37.202.238.11", + "37.202.246.70", + "37.228.136.98", + "37.228.136.157", + "37.228.136.181", + "37.228.137.35", + "37.228.137.66", + "37.228.137.76", + "37.228.138.34", + "37.228.138.123", + "37.228.138.134", + "37.228.138.138", + "37.228.138.154", + "37.228.138.155", + "37.228.138.201", + "37.228.138.203", + "37.228.139.3", + "37.228.139.5", + "37.228.139.26", + "37.228.139.40", + "37.228.139.41", + "37.228.139.107", + "37.228.139.229", + "37.255.128.78", + "37.255.128.250", + "37.255.132.14", + "37.255.132.54", + "37.255.135.36", + "37.255.148.218", + "37.255.148.221", + "37.255.173.171", + "37.255.173.172", + "37.255.177.60", + "37.255.177.138", + "37.255.177.153", + "37.255.186.102", + "37.255.187.164", + "37.255.193.188", + "37.255.194.13", + "37.255.194.69", + "37.255.198.80", + "37.255.201.39", + "37.255.202.25", + "37.255.202.70", + "37.255.208.38", + "37.255.212.55", + "37.255.213.220", + "37.255.216.121", + "37.255.223.36", + "37.255.223.218", + "37.255.223.240", + "37.255.223.243", + "37.255.223.244", + "37.255.230.35", + "37.255.234.14", + "37.255.234.34", + "37.255.236.67", + "37.255.239.57", + "37.255.239.175", + "37.255.242.68", + "37.255.245.39", + "37.255.249.150", + "37.255.249.166", + "37.255.249.172", + "37.255.249.205", + "37.255.249.220", + "45.9.252.79", + "45.11.184.151", + "45.11.185.10", + "45.11.185.148", + "45.11.187.103", + "45.15.200.31", + "45.81.19.49", + "45.81.19.60", + "45.81.19.133", + "45.82.136.141", + "45.82.136.148", + "45.82.138.40", + "45.82.138.113", + "45.82.138.224", + "45.82.139.150", + "45.86.87.84", + "45.86.87.85", + "45.89.139.5", + "45.89.236.7", + "45.89.236.11", + "45.89.236.13", + "45.89.236.15", + "45.89.236.16", + "45.89.236.17", + "45.89.236.28", + "45.89.236.29", + "45.89.236.30", + "45.89.236.40", + "45.89.236.41", + "45.89.236.47", + "45.89.236.48", + "45.89.236.51", + "45.89.236.52", + "45.89.236.53", + "45.89.236.55", + "45.89.236.59", + "45.89.236.67", + "45.89.236.68", + "45.89.236.73", + "45.89.236.74", + "45.89.236.87", + "45.89.236.89", + "45.89.236.133", + "45.89.236.153", + "45.89.236.154", + "45.89.236.158", + "45.89.236.163", + "45.89.236.173", + "45.89.236.179", + "45.89.236.180", + "45.89.236.183", + "45.89.236.185", + "45.89.236.187", + "45.89.236.190", + "45.89.236.227", + "45.89.236.228", + "45.89.236.247", + "45.89.236.249", + "45.89.236.252", + "45.89.237.18", + "45.89.237.24", + "45.89.237.67", + "45.89.237.68", + "45.89.237.70", + "45.89.237.72", + "45.89.237.80", + "45.89.237.81", + "45.89.237.82", + "45.89.237.85", + "45.89.237.86", + "45.89.237.101", + "45.89.237.109", + "45.89.237.158", + "45.89.238.179", + "45.89.238.183", + "45.89.238.185", + "45.89.238.187", + "45.89.238.189", + "45.89.238.190", + "45.89.239.33", + "45.89.239.35", + "45.89.239.39", + "45.89.239.40", + "45.89.239.41", + "45.89.239.42", + "45.89.239.43", + "45.89.239.44", + "45.89.239.45", + "45.89.239.109", + "45.89.239.110", + "45.89.239.121", + "45.89.239.130", + "45.89.239.190", + "45.89.239.210", + "45.89.239.228", + "45.89.239.234", + "45.89.239.238", + "45.89.239.252", + "45.89.239.253", + "45.90.72.14", + "45.90.72.85", + "45.90.72.153", + "45.90.72.161", + "45.90.72.172", + "45.90.72.227", + "45.90.72.228", + "45.90.73.12", + "45.90.73.14", + "45.90.73.15", + "45.90.73.16", + "45.90.73.30", + "45.90.73.31", + "45.90.73.56", + "45.90.73.61", + "45.90.73.66", + "45.90.73.68", + "45.90.73.153", + "45.90.73.154", + "45.90.73.187", + "45.90.73.205", + "45.90.73.206", + "45.90.73.208", + "45.90.73.209", + "45.90.74.14", + "45.90.74.24", + "45.90.74.29", + "45.90.74.61", + "45.90.74.104", + "45.90.74.114", + "45.90.74.159", + "45.90.74.174", + "45.90.74.175", + "45.90.74.176", + "45.90.74.185", + "45.90.74.192", + "45.90.74.202", + "45.90.74.204", + "45.90.74.217", + "45.90.74.241", + "45.90.74.253", + "45.90.75.75", + "45.91.152.3", + "45.91.152.10", + "45.91.152.11", + "45.91.153.10", + "45.92.92.2", + "45.92.92.3", + "45.92.92.4", + "45.92.92.5", + "45.92.92.6", + "45.92.92.7", + "45.92.92.8", + "45.92.92.9", + "45.92.92.11", + "45.92.92.12", + "45.92.92.13", + "45.92.92.14", + "45.92.92.15", + "45.92.92.16", + "45.92.92.17", + "45.92.92.19", + "45.92.92.20", + "45.92.92.22", + "45.92.92.23", + "45.92.92.24", + "45.92.92.26", + "45.92.92.27", + "45.92.92.28", + "45.92.92.29", + "45.92.92.30", + "45.92.92.31", + "45.92.92.32", + "45.92.92.34", + "45.92.92.35", + "45.92.92.36", + "45.92.92.38", + "45.92.92.39", + "45.92.92.40", + "45.92.92.42", + "45.92.92.43", + "45.92.92.44", + "45.92.92.47", + "45.92.92.50", + "45.92.92.51", + "45.92.92.52", + "45.92.92.54", + "45.92.92.55", + "45.92.92.56", + "45.92.92.57", + "45.92.92.58", + "45.92.92.59", + "45.92.92.60", + "45.92.92.61", + "45.92.92.62", + "45.92.92.63", + "45.92.92.64", + "45.92.92.65", + "45.92.92.66", + "45.92.92.67", + "45.92.92.68", + "45.92.92.70", + "45.92.92.71", + "45.92.92.72", + "45.92.92.74", + "45.92.92.75", + "45.92.92.77", + "45.92.92.78", + "45.92.92.79", + "45.92.92.80", + "45.92.92.81", + "45.92.92.82", + "45.92.92.83", + "45.92.92.84", + "45.92.92.85", + "45.92.92.86", + "45.92.92.87", + "45.92.92.88", + "45.92.92.89", + "45.92.92.90", + "45.92.92.91", + "45.92.92.93", + "45.92.92.94", + "45.92.92.95", + "45.92.92.96", + "45.92.92.97", + "45.92.92.98", + "45.92.92.99", + "45.92.92.100", + "45.92.92.101", + "45.92.92.102", + "45.92.92.103", + "45.92.92.104", + "45.92.92.105", + "45.92.92.106", + "45.92.92.107", + "45.92.92.108", + "45.92.92.109", + "45.92.92.110", + "45.92.92.111", + "45.92.92.112", + "45.92.92.113", + "45.92.92.115", + "45.92.92.116", + "45.92.92.117", + "45.92.92.118", + "45.92.92.119", + "45.92.92.120", + "45.92.92.121", + "45.92.92.122", + "45.92.92.123", + "45.92.92.124", + "45.92.92.125", + "45.92.92.127", + "45.92.92.128", + "45.92.92.129", + "45.92.92.130", + "45.92.92.131", + "45.92.92.132", + "45.92.92.133", + "45.92.92.134", + "45.92.92.135", + "45.92.92.136", + "45.92.92.137", + "45.92.92.139", + "45.92.92.140", + "45.92.92.141", + "45.92.92.142", + "45.92.92.143", + "45.92.92.144", + "45.92.92.145", + "45.92.92.146", + "45.92.92.147", + "45.92.92.148", + "45.92.92.149", + "45.92.92.150", + "45.92.92.152", + "45.92.92.153", + "45.92.92.154", + "45.92.92.156", + "45.92.92.157", + "45.92.92.158", + "45.92.92.159", + "45.92.92.161", + "45.92.92.163", + "45.92.92.164", + "45.92.92.165", + "45.92.92.166", + "45.92.92.168", + "45.92.92.169", + "45.92.92.170", + "45.92.92.172", + "45.92.92.174", + "45.92.92.176", + "45.92.92.177", + "45.92.92.178", + "45.92.92.179", + "45.92.92.181", + "45.92.92.182", + "45.92.92.183", + "45.92.92.185", + "45.92.92.186", + "45.92.92.187", + "45.92.92.188", + "45.92.92.190", + "45.92.92.191", + "45.92.92.192", + "45.92.92.193", + "45.92.92.194", + "45.92.92.195", + "45.92.92.197", + "45.92.92.199", + "45.92.92.201", + "45.92.92.202", + "45.92.92.203", + "45.92.92.204", + "45.92.92.205", + "45.92.92.206", + "45.92.92.207", + "45.92.92.208", + "45.92.92.209", + "45.92.92.210", + "45.92.92.211", + "45.92.92.212", + "45.92.92.213", + "45.92.92.214", + "45.92.92.215", + "45.92.92.216", + "45.92.92.217", + "45.92.92.218", + "45.92.92.219", + "45.92.92.220", + "45.92.92.221", + "45.92.92.222", + "45.92.92.223", + "45.92.92.224", + "45.92.92.225", + "45.92.92.226", + "45.92.92.227", + "45.92.92.228", + "45.92.92.229", + "45.92.92.231", + "45.92.92.232", + "45.92.92.233", + "45.92.92.234", + "45.92.92.235", + "45.92.92.236", + "45.92.92.237", + "45.92.92.238", + "45.92.92.239", + "45.92.92.240", + "45.92.92.241", + "45.92.92.242", + "45.92.92.243", + "45.92.92.244", + "45.92.92.245", + "45.92.92.246", + "45.92.92.248", + "45.92.92.249", + "45.92.92.250", + "45.92.92.251", + "45.92.92.252", + "45.92.92.253", + "45.92.93.18", + "45.92.93.48", + "45.92.93.70", + "45.92.93.94", + "45.92.93.97", + "45.92.93.98", + "45.92.93.104", + "45.92.93.118", + "45.92.93.137", + "45.92.93.138", + "45.92.93.139", + "45.92.93.151", + "45.92.93.162", + "45.92.93.164", + "45.92.93.172", + "45.92.93.180", + "45.92.93.249", + "45.92.93.250", + "45.92.93.251", + "45.92.94.2", + "45.92.94.3", + "45.92.94.5", + "45.92.94.6", + "45.92.94.8", + "45.92.94.9", + "45.92.94.10", + "45.92.94.16", + "45.92.94.19", + "45.92.94.21", + "45.92.94.22", + "45.92.94.34", + "45.92.94.46", + "45.92.94.47", + "45.92.94.48", + "45.92.94.49", + "45.92.94.50", + "45.92.94.51", + "45.92.94.52", + "45.92.94.53", + "45.92.94.54", + "45.92.94.55", + "45.92.94.56", + "45.92.94.57", + "45.92.94.58", + "45.92.94.59", + "45.92.94.60", + "45.92.94.89", + "45.92.94.97", + "45.92.94.109", + "45.92.94.151", + "45.92.94.170", + "45.92.94.171", + "45.92.94.226", + "45.92.94.244", + "45.92.95.67", + "45.92.95.68", + "45.92.95.70", + "45.92.95.254", + "45.94.212.13", + "45.94.214.22", + "45.94.214.230", + "45.94.254.34", + "45.94.254.69", + "45.94.254.70", + "45.94.255.189", + "45.94.255.222", + "45.129.36.6", + "45.129.36.10", + "45.129.36.19", + "45.129.36.28", + "45.129.36.30", + "45.129.36.42", + "45.129.36.58", + "45.129.36.59", + "45.129.36.60", + "45.129.36.61", + "45.129.36.62", + "45.129.36.114", + "45.129.36.162", + "45.129.36.166", + "45.129.36.182", + "45.129.37.10", + "45.129.37.70", + "45.129.37.71", + "45.129.37.72", + "45.129.37.76", + "45.129.37.98", + "45.129.37.115", + "45.129.37.116", + "45.129.37.117", + "45.129.37.118", + "45.129.37.150", + "45.129.37.151", + "45.129.37.178", + "45.129.37.183", + "45.129.37.184", + "45.129.38.34", + "45.129.38.37", + "45.129.38.52", + "45.129.38.54", + "45.129.38.61", + "45.129.38.66", + "45.129.38.130", + "45.129.38.140", + "45.129.38.170", + "45.129.38.173", + "45.129.39.90", + "45.129.39.117", + "45.129.39.146", + "45.129.39.195", + "45.129.39.206", + "45.132.32.4", + "45.132.32.5", + "45.132.32.222", + "45.132.172.90", + "45.132.172.95", + "45.132.172.241", + "45.135.241.12", + "45.135.241.22", + "45.135.241.33", + "45.135.241.70", + "45.135.241.74", + "45.135.241.83", + "45.135.241.95", + "45.135.241.129", + "45.135.241.144", + "45.135.241.228", + "45.135.241.237", + "45.135.241.245", + "45.135.241.253", + "45.135.243.22", + "45.135.243.30", + "45.135.243.34", + "45.135.243.56", + "45.135.243.58", + "45.135.243.73", + "45.135.243.76", + "45.135.243.78", + "45.135.243.79", + "45.135.243.85", + "45.135.243.87", + "45.135.243.88", + "45.135.243.92", + "45.135.243.94", + "45.135.243.105", + "45.135.243.206", + "45.135.243.216", + "45.135.243.217", + "45.135.243.222", + "45.135.243.226", + "45.135.243.227", + "45.135.243.235", + "45.137.16.10", + "45.137.17.120", + "45.137.17.133", + "45.137.17.242", + "45.137.17.243", + "45.137.17.244", + "45.137.17.245", + "45.138.132.25", + "45.138.132.26", + "45.138.132.28", + "45.138.132.29", + "45.138.132.30", + "45.138.132.31", + "45.138.132.38", + "45.138.132.43", + "45.138.132.46", + "45.138.132.47", + "45.138.132.48", + "45.138.132.57", + "45.138.132.71", + "45.138.132.80", + "45.138.132.117", + "45.138.132.118", + "45.138.132.136", + "45.138.132.174", + "45.138.132.193", + "45.138.132.204", + "45.138.132.247", + "45.138.132.249", + "45.138.133.3", + "45.138.133.4", + "45.138.133.5", + "45.138.133.6", + "45.138.133.7", + "45.138.133.8", + "45.138.133.9", + "45.138.133.10", + "45.138.133.11", + "45.138.133.12", + "45.138.133.13", + "45.138.133.14", + "45.138.133.15", + "45.138.133.16", + "45.138.133.17", + "45.138.133.18", + "45.138.133.20", + "45.138.133.21", + "45.138.133.22", + "45.138.133.23", + "45.138.133.24", + "45.138.133.25", + "45.138.133.26", + "45.138.133.28", + "45.138.133.29", + "45.138.133.30", + "45.138.133.32", + "45.138.133.33", + "45.138.133.34", + "45.138.133.35", + "45.138.133.36", + "45.138.133.37", + "45.138.133.38", + "45.138.133.39", + "45.138.133.41", + "45.138.133.42", + "45.138.133.44", + "45.138.133.45", + "45.138.133.46", + "45.138.133.47", + "45.138.133.49", + "45.138.133.51", + "45.138.133.52", + "45.138.133.53", + "45.138.133.56", + "45.138.133.57", + "45.138.133.60", + "45.138.133.61", + "45.138.133.62", + "45.138.133.64", + "45.138.133.65", + "45.138.133.66", + "45.138.133.67", + "45.138.133.69", + "45.138.133.70", + "45.138.133.71", + "45.138.133.72", + "45.138.133.73", + "45.138.133.75", + "45.138.133.76", + "45.138.133.77", + "45.138.133.78", + "45.138.133.79", + "45.138.133.81", + "45.138.133.85", + "45.138.133.89", + "45.138.133.90", + "45.138.133.91", + "45.138.133.92", + "45.138.133.93", + "45.138.133.94", + "45.138.133.95", + "45.138.133.96", + "45.138.133.98", + "45.138.133.99", + "45.138.133.100", + "45.138.133.101", + "45.138.133.102", + "45.138.133.103", + "45.138.133.104", + "45.138.133.105", + "45.138.133.107", + "45.138.133.108", + "45.138.133.110", + "45.138.133.111", + "45.138.133.112", + "45.138.133.114", + "45.138.133.115", + "45.138.133.117", + "45.138.133.118", + "45.138.133.122", + "45.138.133.123", + "45.138.133.124", + "45.138.133.125", + "45.138.133.128", + "45.138.133.129", + "45.138.133.130", + "45.138.133.131", + "45.138.133.132", + "45.138.133.133", + "45.138.133.134", + "45.138.133.136", + "45.138.133.137", + "45.138.133.138", + "45.138.133.140", + "45.138.133.141", + "45.138.133.142", + "45.138.133.143", + "45.138.133.144", + "45.138.133.147", + "45.138.133.148", + "45.138.133.149", + "45.138.133.150", + "45.138.133.151", + "45.138.133.152", + "45.138.133.154", + "45.138.133.155", + "45.138.133.156", + "45.138.133.157", + "45.138.133.158", + "45.138.133.159", + "45.138.133.160", + "45.138.133.162", + "45.138.133.163", + "45.138.133.164", + "45.138.133.165", + "45.138.133.167", + "45.138.133.168", + "45.138.133.169", + "45.138.133.170", + "45.138.133.171", + "45.138.133.172", + "45.138.133.173", + "45.138.133.174", + "45.138.133.175", + "45.138.133.176", + "45.138.133.177", + "45.138.133.178", + "45.138.133.179", + "45.138.133.180", + "45.138.133.181", + "45.138.133.182", + "45.138.133.183", + "45.138.133.184", + "45.138.133.185", + "45.138.133.186", + "45.138.133.187", + "45.138.133.188", + "45.138.133.189", + "45.138.133.190", + "45.138.133.191", + "45.138.133.192", + "45.138.133.193", + "45.138.133.194", + "45.138.133.195", + "45.138.133.196", + "45.138.133.197", + "45.138.133.199", + "45.138.133.200", + "45.138.133.201", + "45.138.133.202", + "45.138.133.203", + "45.138.133.204", + "45.138.133.206", + "45.138.133.207", + "45.138.133.208", + "45.138.133.209", + "45.138.133.211", + "45.138.133.212", + "45.138.133.213", + "45.138.133.214", + "45.138.133.215", + "45.138.133.217", + "45.138.133.218", + "45.138.133.219", + "45.138.133.220", + "45.138.133.221", + "45.138.133.223", + "45.138.133.224", + "45.138.133.225", + "45.138.133.226", + "45.138.133.227", + "45.138.133.228", + "45.138.133.229", + "45.138.133.230", + "45.138.133.231", + "45.138.133.232", + "45.138.133.233", + "45.138.133.234", + "45.138.133.235", + "45.138.133.236", + "45.138.133.237", + "45.138.133.238", + "45.138.133.239", + "45.138.133.240", + "45.138.133.241", + "45.138.133.242", + "45.138.133.243", + "45.138.133.244", + "45.138.133.245", + "45.138.133.247", + "45.138.133.248", + "45.138.133.249", + "45.138.133.250", + "45.138.133.251", + "45.138.133.252", + "45.138.133.253", + "45.138.133.254", + "45.138.134.2", + "45.138.134.3", + "45.138.134.4", + "45.138.134.5", + "45.138.134.6", + "45.138.134.7", + "45.138.134.8", + "45.138.134.9", + "45.138.134.10", + "45.138.134.11", + "45.138.134.12", + "45.138.134.13", + "45.138.134.14", + "45.138.134.16", + "45.138.134.17", + "45.138.134.18", + "45.138.134.20", + "45.138.134.21", + "45.138.134.22", + "45.138.134.23", + "45.138.134.24", + "45.138.134.25", + "45.138.134.27", + "45.138.134.28", + "45.138.134.29", + "45.138.134.30", + "45.138.134.33", + "45.138.134.34", + "45.138.134.35", + "45.138.134.36", + "45.138.134.37", + "45.138.134.38", + "45.138.134.39", + "45.138.134.40", + "45.138.134.41", + "45.138.134.42", + "45.138.134.43", + "45.138.134.45", + "45.138.134.46", + "45.138.134.47", + "45.138.134.48", + "45.138.134.49", + "45.138.134.50", + "45.138.134.51", + "45.138.134.52", + "45.138.134.53", + "45.138.134.54", + "45.138.134.55", + "45.138.134.56", + "45.138.134.57", + "45.138.134.58", + "45.138.134.59", + "45.138.134.60", + "45.138.134.61", + "45.138.134.62", + "45.138.134.63", + "45.138.134.64", + "45.138.134.65", + "45.138.134.67", + "45.138.134.68", + "45.138.134.69", + "45.138.134.70", + "45.138.134.71", + "45.138.134.72", + "45.138.134.73", + "45.138.134.74", + "45.138.134.75", + "45.138.134.76", + "45.138.134.77", + "45.138.134.78", + "45.138.134.79", + "45.138.134.80", + "45.138.134.82", + "45.138.134.83", + "45.138.134.84", + "45.138.134.85", + "45.138.134.86", + "45.138.134.87", + "45.138.134.88", + "45.138.134.89", + "45.138.134.90", + "45.138.134.91", + "45.138.134.92", + "45.138.134.93", + "45.138.134.94", + "45.138.134.97", + "45.138.134.98", + "45.138.134.100", + "45.138.134.101", + "45.138.134.102", + "45.138.134.103", + "45.138.134.104", + "45.138.134.106", + "45.138.134.107", + "45.138.134.108", + "45.138.134.109", + "45.138.134.110", + "45.138.134.111", + "45.138.134.112", + "45.138.134.113", + "45.138.134.114", + "45.138.134.115", + "45.138.134.116", + "45.138.134.118", + "45.138.134.119", + "45.138.134.120", + "45.138.134.121", + "45.138.134.122", + "45.138.134.123", + "45.138.134.124", + "45.138.134.125", + "45.138.134.126", + "45.138.134.127", + "45.138.134.130", + "45.138.134.131", + "45.138.134.132", + "45.138.134.133", + "45.138.134.135", + "45.138.134.136", + "45.138.134.137", + "45.138.134.138", + "45.138.134.139", + "45.138.134.140", + "45.138.134.141", + "45.138.134.143", + "45.138.134.145", + "45.138.134.146", + "45.138.134.147", + "45.138.134.149", + "45.138.134.150", + "45.138.134.151", + "45.138.134.152", + "45.138.134.153", + "45.138.134.155", + "45.138.134.157", + "45.138.134.158", + "45.138.134.159", + "45.138.134.161", + "45.138.134.162", + "45.138.134.163", + "45.138.134.164", + "45.138.134.165", + "45.138.134.166", + "45.138.134.167", + "45.138.134.168", + "45.138.134.171", + "45.138.134.172", + "45.138.134.173", + "45.138.134.175", + "45.138.134.177", + "45.138.134.178", + "45.138.134.179", + "45.138.134.180", + "45.138.134.181", + "45.138.134.184", + "45.138.134.185", + "45.138.134.187", + "45.138.134.188", + "45.138.134.189", + "45.138.134.190", + "45.138.134.191", + "45.138.134.192", + "45.138.134.193", + "45.138.134.195", + "45.138.134.196", + "45.138.134.197", + "45.138.134.198", + "45.138.134.200", + "45.138.134.203", + "45.138.134.205", + "45.138.134.206", + "45.138.134.207", + "45.138.134.209", + "45.138.134.210", + "45.138.134.211", + "45.138.134.212", + "45.138.134.213", + "45.138.134.214", + "45.138.134.215", + "45.138.134.216", + "45.138.134.217", + "45.138.134.218", + "45.138.134.219", + "45.138.134.220", + "45.138.134.221", + "45.138.134.223", + "45.138.134.224", + "45.138.134.225", + "45.138.134.226", + "45.138.134.227", + "45.138.134.230", + "45.138.134.233", + "45.138.134.234", + "45.138.134.236", + "45.138.134.237", + "45.138.134.238", + "45.138.134.239", + "45.138.134.240", + "45.138.134.241", + "45.138.134.242", + "45.138.134.243", + "45.138.134.244", + "45.138.134.245", + "45.138.134.246", + "45.138.134.247", + "45.138.134.250", + "45.138.134.252", + "45.138.134.253", + "45.138.135.4", + "45.138.135.6", + "45.138.135.7", + "45.138.135.8", + "45.138.135.9", + "45.138.135.10", + "45.138.135.11", + "45.138.135.12", + "45.138.135.13", + "45.138.135.14", + "45.138.135.15", + "45.138.135.16", + "45.138.135.17", + "45.138.135.18", + "45.138.135.19", + "45.138.135.21", + "45.138.135.22", + "45.138.135.23", + "45.138.135.24", + "45.138.135.25", + "45.138.135.26", + "45.138.135.27", + "45.138.135.28", + "45.138.135.29", + "45.138.135.30", + "45.138.135.31", + "45.138.135.35", + "45.138.135.38", + "45.138.135.42", + "45.138.135.43", + "45.138.135.45", + "45.138.135.48", + "45.138.135.51", + "45.138.135.52", + "45.138.135.59", + "45.138.135.83", + "45.138.135.97", + "45.138.135.114", + "45.138.135.115", + "45.138.135.116", + "45.138.135.120", + "45.138.135.131", + "45.138.135.140", + "45.138.135.145", + "45.138.135.153", + "45.138.135.158", + "45.138.135.163", + "45.138.135.171", + "45.138.135.176", + "45.138.135.182", + "45.138.135.185", + "45.138.135.186", + "45.138.135.190", + "45.138.135.202", + "45.138.135.203", + "45.138.135.205", + "45.138.135.209", + "45.138.135.222", + "45.138.135.223", + "45.138.135.231", + "45.138.135.233", + "45.138.135.238", + "45.138.135.241", + "45.138.135.250", + "45.138.135.251", + "45.139.10.8", + "45.139.10.23", + "45.139.10.37", + "45.139.10.42", + "45.139.10.45", + "45.139.10.51", + "45.139.10.52", + "45.139.10.53", + "45.139.10.58", + "45.139.10.65", + "45.139.10.68", + "45.139.10.70", + "45.139.10.82", + "45.139.10.86", + "45.139.10.87", + "45.139.10.92", + "45.139.10.100", + "45.139.10.108", + "45.139.10.110", + "45.139.10.113", + "45.139.10.119", + "45.139.10.120", + "45.139.10.124", + "45.139.10.126", + "45.139.10.130", + "45.139.10.133", + "45.139.10.136", + "45.139.10.139", + "45.139.10.143", + "45.139.10.157", + "45.139.10.167", + "45.139.10.181", + "45.139.10.190", + "45.139.10.194", + "45.139.10.195", + "45.139.10.198", + "45.139.10.203", + "45.139.10.210", + "45.139.10.213", + "45.139.10.215", + "45.139.10.224", + "45.139.10.233", + "45.139.10.242", + "45.139.10.243", + "45.139.10.246", + "45.139.10.247", + "45.139.10.251", + "45.139.10.252", + "45.139.10.253", + "45.139.11.5", + "45.139.11.7", + "45.139.11.29", + "45.139.11.43", + "45.139.11.45", + "45.139.11.46", + "45.139.11.47", + "45.139.11.48", + "45.139.11.50", + "45.139.11.51", + "45.139.11.60", + "45.139.11.72", + "45.139.11.78", + "45.139.11.83", + "45.139.11.89", + "45.139.11.98", + "45.139.11.100", + "45.139.11.102", + "45.139.11.107", + "45.139.11.108", + "45.139.11.115", + "45.139.11.124", + "45.139.11.125", + "45.139.11.128", + "45.139.11.133", + "45.139.11.135", + "45.139.11.137", + "45.139.11.139", + "45.139.11.140", + "45.139.11.142", + "45.139.11.145", + "45.139.11.146", + "45.139.11.149", + "45.139.11.155", + "45.139.11.157", + "45.139.11.164", + "45.139.11.169", + "45.139.11.170", + "45.139.11.174", + "45.139.11.177", + "45.139.11.179", + "45.139.11.187", + "45.139.11.194", + "45.139.11.212", + "45.139.11.216", + "45.139.11.219", + "45.139.11.222", + "45.139.11.226", + "45.139.11.229", + "45.139.11.244", + "45.142.189.25", + "45.142.189.68", + "45.142.189.114", + "45.142.189.200", + "45.142.191.152", + "45.144.18.2", + "45.144.18.6", + "45.144.18.10", + "45.144.18.12", + "45.144.18.17", + "45.144.18.20", + "45.144.18.50", + "45.144.18.51", + "45.144.18.58", + "45.144.18.59", + "45.144.18.60", + "45.144.18.66", + "45.144.18.72", + "45.144.18.73", + "45.144.18.100", + "45.144.18.102", + "45.144.18.104", + "45.144.18.110", + "45.144.18.115", + "45.144.18.118", + "45.144.18.119", + "45.144.18.124", + "45.144.18.129", + "45.144.18.130", + "45.147.76.66", + "45.147.76.115", + "45.147.76.118", + "45.147.76.119", + "45.147.76.120", + "45.147.76.121", + "45.147.76.138", + "45.147.76.171", + "45.147.76.173", + "45.147.76.211", + "45.147.76.249", + "45.147.76.250", + "45.147.76.251", + "45.147.76.253", + "45.147.76.254", + "45.147.77.50", + "45.147.77.53", + "45.147.77.82", + "45.147.77.83", + "45.147.77.91", + "45.147.77.221", + "45.147.78.21", + "45.147.78.40", + "45.147.78.99", + "45.147.78.198", + "45.147.78.200", + "45.147.78.201", + "45.147.78.204", + "45.147.78.243", + "45.148.250.246", + "45.149.76.11", + "45.149.76.13", + "45.149.76.14", + "45.149.76.15", + "45.149.76.16", + "45.149.76.17", + "45.149.76.22", + "45.149.76.26", + "45.149.76.27", + "45.149.76.28", + "45.149.76.29", + "45.149.76.31", + "45.149.76.34", + "45.149.76.36", + "45.149.76.37", + "45.149.76.39", + "45.149.76.47", + "45.149.76.49", + "45.149.76.50", + "45.149.76.59", + "45.149.76.64", + "45.149.76.69", + "45.149.76.87", + "45.149.76.90", + "45.149.76.93", + "45.149.76.94", + "45.149.76.96", + "45.149.76.117", + "45.149.76.130", + "45.149.76.131", + "45.149.76.141", + "45.149.76.144", + "45.149.76.145", + "45.149.76.148", + "45.149.76.151", + "45.149.76.154", + "45.149.76.156", + "45.149.76.157", + "45.149.76.164", + "45.149.76.170", + "45.149.76.172", + "45.149.76.173", + "45.149.76.174", + "45.149.76.187", + "45.149.76.192", + "45.149.76.204", + "45.149.76.210", + "45.149.76.213", + "45.149.76.218", + "45.149.76.226", + "45.149.76.229", + "45.149.76.230", + "45.149.76.231", + "45.149.76.234", + "45.149.76.244", + "45.149.76.249", + "45.149.76.250", + "45.149.77.5", + "45.149.77.7", + "45.149.77.12", + "45.149.77.25", + "45.149.77.30", + "45.149.77.31", + "45.149.77.35", + "45.149.77.45", + "45.149.77.51", + "45.149.77.61", + "45.149.77.73", + "45.149.77.77", + "45.149.77.80", + "45.149.77.84", + "45.149.77.85", + "45.149.77.87", + "45.149.77.92", + "45.149.77.94", + "45.149.77.95", + "45.149.77.97", + "45.149.77.100", + "45.149.77.101", + "45.149.77.108", + "45.149.77.126", + "45.149.77.131", + "45.149.77.139", + "45.149.77.143", + "45.149.77.147", + "45.149.77.154", + "45.149.77.162", + "45.149.77.166", + "45.149.77.167", + "45.149.77.175", + "45.149.77.181", + "45.149.77.182", + "45.149.77.191", + "45.149.77.196", + "45.149.77.204", + "45.149.77.222", + "45.149.77.224", + "45.149.77.232", + "45.149.77.233", + "45.149.77.236", + "45.149.77.247", + "45.149.77.248", + "45.149.78.5", + "45.149.78.16", + "45.149.78.17", + "45.149.78.20", + "45.149.78.21", + "45.149.78.30", + "45.149.78.33", + "45.149.78.44", + "45.149.78.45", + "45.149.78.53", + "45.149.78.56", + "45.149.78.59", + "45.149.78.61", + "45.149.78.64", + "45.149.78.69", + "45.149.78.75", + "45.149.78.82", + "45.149.78.83", + "45.149.78.87", + "45.149.78.89", + "45.149.78.92", + "45.149.78.95", + "45.149.78.96", + "45.149.78.101", + "45.149.78.106", + "45.149.78.114", + "45.149.78.120", + "45.149.78.121", + "45.149.78.131", + "45.149.78.132", + "45.149.78.135", + "45.149.78.142", + "45.149.78.143", + "45.149.78.146", + "45.149.78.159", + "45.149.78.169", + "45.149.78.172", + "45.149.78.176", + "45.149.78.180", + "45.149.78.197", + "45.149.78.207", + "45.149.78.211", + "45.149.78.220", + "45.149.78.224", + "45.149.78.233", + "45.149.78.234", + "45.149.78.246", + "45.149.78.248", + "45.149.78.253", + "45.149.79.5", + "45.149.79.8", + "45.149.79.9", + "45.149.79.13", + "45.149.79.15", + "45.149.79.18", + "45.149.79.25", + "45.149.79.26", + "45.149.79.29", + "45.149.79.36", + "45.149.79.50", + "45.149.79.54", + "45.149.79.62", + "45.149.79.64", + "45.149.79.67", + "45.149.79.71", + "45.149.79.80", + "45.149.79.84", + "45.149.79.88", + "45.149.79.98", + "45.149.79.104", + "45.149.79.105", + "45.149.79.111", + "45.149.79.114", + "45.149.79.125", + "45.149.79.127", + "45.149.79.132", + "45.149.79.133", + "45.149.79.134", + "45.149.79.139", + "45.149.79.142", + "45.149.79.152", + "45.149.79.160", + "45.149.79.162", + "45.149.79.164", + "45.149.79.165", + "45.149.79.167", + "45.149.79.169", + "45.149.79.171", + "45.149.79.174", + "45.149.79.185", + "45.149.79.186", + "45.149.79.189", + "45.149.79.190", + "45.149.79.194", + "45.149.79.199", + "45.149.79.204", + "45.149.79.210", + "45.149.79.212", + "45.149.79.223", + "45.149.79.243", + "45.149.79.250", + "45.156.184.24", + "45.156.184.25", + "45.156.184.27", + "45.156.184.28", + "45.156.184.29", + "45.156.184.30", + "45.156.184.31", + "45.156.184.33", + "45.156.184.34", + "45.156.184.35", + "45.156.184.37", + "45.156.184.38", + "45.156.184.39", + "45.156.184.41", + "45.156.184.42", + "45.156.184.47", + "45.156.184.51", + "45.156.184.104", + "45.156.184.243", + "45.156.185.5", + "45.156.185.8", + "45.156.185.43", + "45.156.185.45", + "45.156.185.48", + "45.156.185.53", + "45.156.185.69", + "45.156.185.77", + "45.156.185.78", + "45.156.185.83", + "45.156.185.88", + "45.156.185.91", + "45.156.185.92", + "45.156.185.98", + "45.156.185.131", + "45.156.185.151", + "45.156.185.157", + "45.156.185.166", + "45.156.185.170", + "45.156.185.171", + "45.156.185.188", + "45.156.185.195", + "45.156.185.199", + "45.156.185.222", + "45.156.185.230", + "45.156.185.233", + "45.156.185.249", + "45.156.186.17", + "45.156.186.19", + "45.156.186.25", + "45.156.186.41", + "45.156.186.42", + "45.156.186.92", + "45.156.186.128", + "45.156.186.183", + "45.156.186.200", + "45.156.186.246", + "45.156.187.38", + "45.156.187.48", + "45.156.187.62", + "45.156.187.99", + "45.156.187.102", + "45.156.187.108", + "45.156.187.110", + "45.156.187.134", + "45.157.244.200", + "45.157.244.201", + "45.159.112.5", + "45.159.112.11", + "45.159.112.12", + "45.159.112.14", + "45.159.112.16", + "45.159.112.17", + "45.159.112.19", + "45.159.112.21", + "45.159.112.25", + "45.159.112.30", + "45.159.112.33", + "45.159.112.34", + "45.159.112.35", + "45.159.112.36", + "45.159.112.37", + "45.159.112.60", + "45.159.112.63", + "45.159.112.65", + "45.159.112.66", + "45.159.112.68", + "45.159.112.70", + "45.159.112.72", + "45.159.112.73", + "45.159.112.81", + "45.159.112.82", + "45.159.112.85", + "45.159.112.92", + "45.159.112.96", + "45.159.112.115", + "45.159.112.116", + "45.159.112.117", + "45.159.112.118", + "45.159.112.124", + "45.159.112.130", + "45.159.112.131", + "45.159.112.132", + "45.159.112.135", + "45.159.112.136", + "45.159.112.141", + "45.159.112.147", + "45.159.112.149", + "45.159.112.152", + "45.159.112.168", + "45.159.112.171", + "45.159.112.186", + "45.159.112.203", + "45.159.112.205", + "45.159.112.216", + "45.159.113.16", + "45.159.113.18", + "45.159.113.22", + "45.159.113.39", + "45.159.113.41", + "45.159.113.51", + "45.159.113.64", + "45.159.113.66", + "45.159.113.67", + "45.159.113.72", + "45.159.113.88", + "45.159.113.91", + "45.159.113.98", + "45.159.113.101", + "45.159.113.102", + "45.159.113.103", + "45.159.113.106", + "45.159.113.108", + "45.159.113.110", + "45.159.113.111", + "45.159.113.116", + "45.159.113.117", + "45.159.113.118", + "45.159.113.119", + "45.159.113.121", + "45.159.113.123", + "45.159.113.124", + "45.159.113.126", + "45.159.113.128", + "45.159.113.129", + "45.159.113.133", + "45.159.113.134", + "45.159.113.135", + "45.159.113.137", + "45.159.113.146", + "45.159.113.149", + "45.159.113.154", + "45.159.113.157", + "45.159.113.160", + "45.159.113.163", + "45.159.113.171", + "45.159.113.194", + "45.159.113.200", + "45.159.113.202", + "45.159.113.203", + "45.159.113.204", + "45.159.113.206", + "45.159.113.208", + "45.159.113.211", + "45.159.113.216", + "45.159.113.224", + "45.159.113.225", + "45.159.113.228", + "45.159.113.236", + "45.159.113.249", + "45.159.114.211", + "45.159.114.213", + "45.159.114.215", + "45.159.114.219", + "45.159.114.220", + "45.159.114.227", + "45.159.114.229", + "45.159.114.230", + "45.159.114.231", + "45.159.114.232", + "45.159.114.239", + "45.159.114.241", + "45.159.114.251", + "45.159.115.7", + "45.159.115.10", + "45.159.115.11", + "45.159.115.12", + "45.159.115.14", + "45.159.115.15", + "45.159.115.21", + "45.159.115.32", + "45.159.115.38", + "45.159.115.39", + "45.159.115.40", + "45.159.115.44", + "45.159.115.51", + "45.159.115.52", + "45.159.115.68", + "45.159.115.79", + "45.159.115.81", + "45.159.115.86", + "45.159.115.88", + "45.159.115.90", + "45.159.115.91", + "45.159.115.92", + "45.159.115.93", + "45.159.115.98", + "45.159.115.100", + "45.159.115.101", + "45.159.115.103", + "45.159.115.105", + "45.159.115.107", + "45.159.115.108", + "45.159.115.109", + "45.159.115.110", + "45.159.115.112", + "45.159.115.114", + "45.159.115.115", + "45.159.115.116", + "45.159.115.118", + "45.159.115.120", + "45.159.115.123", + "45.159.115.124", + "45.159.115.126", + "45.159.115.128", + "45.159.115.130", + "45.159.115.136", + "45.159.115.137", + "45.159.115.138", + "45.159.115.139", + "45.159.115.141", + "45.159.115.142", + "45.159.115.146", + "45.159.115.147", + "45.159.115.149", + "45.159.115.154", + "45.159.115.156", + "45.159.115.157", + "45.159.115.158", + "45.159.115.159", + "45.159.115.162", + "45.159.115.163", + "45.159.115.165", + "45.159.115.170", + "45.159.115.173", + "45.159.115.174", + "45.159.115.175", + "45.159.115.179", + "45.159.115.180", + "45.159.115.181", + "45.159.115.183", + "45.159.115.185", + "45.159.115.186", + "45.159.115.188", + "45.159.115.190", + "45.159.115.197", + "45.159.115.200", + "45.159.115.205", + "45.159.115.215", + "45.159.115.218", + "45.159.115.230", + "45.159.115.231", + "45.159.115.232", + "45.159.115.234", + "45.159.115.238", + "45.159.149.5", + "45.159.149.6", + "45.159.149.7", + "45.159.149.8", + "45.159.149.13", + "45.159.149.15", + "45.159.149.17", + "45.159.149.19", + "45.159.149.25", + "45.159.149.29", + "45.159.149.31", + "45.159.149.42", + "45.159.149.52", + "45.159.149.55", + "45.159.149.63", + "45.159.149.64", + "45.159.149.70", + "45.159.149.79", + "45.159.149.83", + "45.159.149.89", + "45.159.149.91", + "45.159.149.92", + "45.159.149.95", + "45.159.149.101", + "45.159.149.105", + "45.159.149.109", + "45.159.149.125", + "45.159.149.127", + "45.159.149.135", + "45.159.149.146", + "45.159.149.147", + "45.159.149.154", + "45.159.149.160", + "45.159.149.165", + "45.159.149.172", + "45.159.149.180", + "45.159.149.186", + "45.159.149.209", + "45.159.149.211", + "45.159.149.215", + "45.159.149.217", + "45.159.149.232", + "45.159.149.252", + "45.159.149.253", + "45.159.150.5", + "45.159.150.12", + "45.159.150.14", + "45.159.150.20", + "45.159.150.26", + "45.159.150.27", + "45.159.150.41", + "45.159.150.53", + "45.159.150.69", + "45.159.150.75", + "45.159.150.78", + "45.159.150.88", + "45.159.150.89", + "45.159.150.100", + "45.159.150.107", + "45.159.150.115", + "45.159.150.129", + "45.159.150.133", + "45.159.150.136", + "45.159.150.142", + "45.159.150.146", + "45.159.150.152", + "45.159.150.166", + "45.159.150.173", + "45.159.150.179", + "45.159.150.181", + "45.159.150.184", + "45.159.150.194", + "45.159.150.204", + "45.159.150.221", + "45.159.150.222", + "45.159.150.223", + "45.159.150.224", + "45.159.150.231", + "45.159.150.242", + "45.159.150.244", + "45.159.150.245", + "45.159.151.8", + "45.159.151.25", + "45.159.151.33", + "45.159.151.34", + "45.159.151.35", + "45.159.151.42", + "45.159.151.44", + "45.159.151.48", + "45.159.151.53", + "45.159.151.55", + "45.159.151.56", + "45.159.151.58", + "45.159.151.59", + "45.159.151.61", + "45.159.151.63", + "45.159.151.64", + "45.159.151.65", + "45.159.151.66", + "45.159.151.67", + "45.159.151.70", + "45.159.151.71", + "45.159.151.73", + "45.159.151.74", + "45.159.151.75", + "45.159.151.76", + "45.159.151.77", + "45.159.151.78", + "45.159.151.79", + "45.159.151.80", + "45.159.151.81", + "45.159.151.82", + "45.159.151.84", + "45.159.151.85", + "45.159.151.86", + "45.159.151.87", + "45.159.151.88", + "45.159.151.89", + "45.159.151.90", + "45.159.151.91", + "45.159.151.92", + "45.159.151.93", + "45.159.151.94", + "45.159.151.95", + "45.159.151.99", + "45.159.151.100", + "45.159.151.101", + "45.159.151.102", + "45.159.151.109", + "45.159.151.110", + "45.159.151.111", + "45.159.151.112", + "45.159.151.113", + "45.159.151.114", + "45.159.151.115", + "45.159.151.116", + "45.159.151.117", + "45.159.151.118", + "45.159.151.119", + "45.159.151.120", + "45.159.151.121", + "45.159.151.122", + "45.159.151.123", + "45.159.151.124", + "45.159.151.125", + "45.159.151.126", + "45.159.151.129", + "45.159.151.133", + "45.159.151.134", + "45.159.151.135", + "45.159.151.136", + "45.159.151.138", + "45.159.151.145", + "45.159.151.146", + "45.159.151.147", + "45.159.151.149", + "45.159.151.150", + "45.159.151.151", + "45.159.151.152", + "45.159.151.154", + "45.159.151.155", + "45.159.151.156", + "45.159.151.158", + "45.159.151.159", + "45.159.151.160", + "45.159.151.161", + "45.159.151.162", + "45.159.151.163", + "45.159.151.164", + "45.159.151.165", + "45.159.151.166", + "45.159.151.167", + "45.159.151.168", + "45.159.151.170", + "45.159.151.171", + "45.159.196.5", + "45.159.197.66", + "45.159.197.99", + "45.159.197.139", + "45.159.197.141", + "45.159.198.4", + "45.159.198.5", + "45.159.198.9", + "45.159.198.10", + "45.159.198.182", + "45.159.198.251", + "45.159.199.130", + "46.18.248.136", + "46.28.74.35", + "46.28.78.11", + "46.28.78.12", + "46.32.4.137", + "46.32.4.165", + "46.32.4.244", + "46.32.10.50", + "46.32.11.130", + "46.32.11.131", + "46.32.12.72", + "46.32.15.196", + "46.32.16.68", + "46.32.17.68", + "46.32.17.70", + "46.32.26.18", + "46.32.30.74", + "46.32.30.81", + "46.32.31.29", + "46.32.31.30", + "46.34.160.41", + "46.34.160.56", + "46.34.162.76", + "46.34.163.85", + "46.34.165.1", + "46.34.166.60", + "46.34.166.210", + "46.34.167.120", + "46.36.96.1", + "46.36.96.65", + "46.36.96.66", + "46.36.107.98", + "46.36.107.99", + "46.36.111.2", + "46.36.111.3", + "46.38.138.47", + "46.38.138.84", + "46.38.138.89", + "46.38.138.92", + "46.38.138.156", + "46.38.138.168", + "46.38.142.66", + "46.38.146.131", + "46.38.146.163", + "46.38.147.10", + "46.38.150.195", + "46.38.150.243", + "46.38.159.21", + "46.38.159.23", + "46.100.5.49", + "46.100.8.21", + "46.100.10.64", + "46.100.12.158", + "46.100.12.255", + "46.100.13.60", + "46.100.41.110", + "46.100.41.240", + "46.100.45.147", + "46.100.47.154", + "46.100.48.239", + "46.100.58.214", + "46.100.63.131", + "46.100.74.83", + "46.100.84.60", + "46.100.85.5", + "46.100.90.13", + "46.100.90.93", + "46.100.90.166", + "46.100.95.206", + "46.100.166.125", + "46.100.167.215", + "46.100.167.222", + "46.100.177.148", + "46.100.218.22", + "46.100.249.63", + "46.100.249.195", + "46.102.129.202", + "46.102.129.203", + "46.102.130.2", + "46.102.130.31", + "46.102.130.116", + "46.102.130.165", + "46.102.130.166", + "46.102.130.215", + "46.102.130.229", + "46.102.132.112", + "46.102.136.2", + "46.102.136.33", + "46.102.136.34", + "46.102.136.35", + "46.102.136.36", + "46.102.136.75", + "46.102.136.77", + "46.102.136.89", + "46.102.136.93", + "46.102.137.18", + "46.102.138.219", + "46.102.138.222", + "46.102.139.45", + "46.102.139.66", + "46.102.139.67", + "46.102.139.68", + "46.102.139.69", + "46.102.139.70", + "46.102.139.98", + "46.102.139.118", + "46.102.140.16", + "46.102.140.45", + "46.102.141.156", + "46.148.34.202", + "46.148.35.162", + "46.148.35.163", + "46.148.36.9", + "46.148.41.47", + "46.148.47.1", + "46.148.47.27", + "46.167.135.183", + "46.167.139.74", + "46.167.139.125", + "46.167.141.33", + "46.167.141.196", + "46.167.144.175", + "46.167.146.37", + "46.167.146.174", + "46.167.146.202", + "46.167.146.231", + "46.167.158.79", + "46.167.159.116", + "46.209.7.114", + "46.209.7.115", + "46.209.7.116", + "46.209.7.136", + "46.209.9.178", + "46.209.11.8", + "46.209.11.30", + "46.209.16.181", + "46.209.20.86", + "46.209.20.154", + "46.209.20.181", + "46.209.22.219", + "46.209.25.195", + "46.209.30.13", + "46.209.31.248", + "46.209.44.74", + "46.209.50.15", + "46.209.50.133", + "46.209.59.66", + "46.209.59.67", + "46.209.62.250", + "46.209.67.150", + "46.209.69.121", + "46.209.69.123", + "46.209.73.211", + "46.209.89.19", + "46.209.92.54", + "46.209.92.147", + "46.209.115.6", + "46.209.128.217", + "46.209.150.220", + "46.209.157.179", + "46.209.191.3", + "46.209.191.4", + "46.209.206.205", + "46.209.207.153", + "46.209.207.154", + "46.209.207.202", + "46.209.207.203", + "46.209.209.209", + "46.209.211.13", + "46.209.213.114", + "46.209.216.247", + "46.209.221.92", + "46.209.239.189", + "46.209.239.190", + "46.209.240.130", + "46.209.241.66", + "46.209.244.246", + "46.209.251.12", + "46.235.77.195", + "46.245.0.105", + "46.245.4.205", + "46.245.5.30", + "46.245.6.140", + "46.245.8.140", + "46.245.13.16", + "46.245.16.181", + "46.245.17.163", + "46.245.24.138", + "46.245.28.206", + "46.245.34.157", + "46.245.38.100", + "46.245.38.101", + "46.245.39.140", + "46.245.39.163", + "46.245.43.60", + "46.245.43.87", + "46.245.45.87", + "46.245.45.174", + "46.245.64.24", + "46.245.64.36", + "46.245.64.37", + "46.245.64.38", + "46.245.64.55", + "46.245.64.132", + "46.245.66.13", + "46.245.66.26", + "46.245.66.61", + "46.245.66.72", + "46.245.66.91", + "46.245.66.120", + "46.245.66.137", + "46.245.67.36", + "46.245.67.101", + "46.245.67.141", + "46.245.67.162", + "46.245.68.169", + "46.245.69.35", + "46.245.69.94", + "46.245.69.109", + "46.245.69.110", + "46.245.69.133", + "46.245.69.134", + "46.245.69.135", + "46.245.69.215", + "46.245.69.222", + "46.245.69.229", + "46.245.69.234", + "46.245.72.183", + "46.245.73.17", + "46.245.73.26", + "46.245.76.4", + "46.245.76.5", + "46.245.76.6", + "46.245.76.7", + "46.245.76.8", + "46.245.76.9", + "46.245.76.10", + "46.245.76.11", + "46.245.76.12", + "46.245.76.13", + "46.245.76.14", + "46.245.76.28", + "46.245.76.29", + "46.245.76.50", + "46.245.76.51", + "46.245.76.52", + "46.245.76.53", + "46.245.76.54", + "46.245.76.116", + "46.245.76.162", + "46.245.76.171", + "46.245.76.172", + "46.245.76.173", + "46.245.76.174", + "46.245.77.7", + "46.245.77.8", + "46.245.77.70", + "46.245.77.75", + "46.245.77.76", + "46.245.77.77", + "46.245.77.78", + "46.245.78.18", + "46.245.78.19", + "46.245.78.227", + "46.245.79.11", + "46.245.79.134", + "46.245.79.135", + "46.245.79.136", + "46.245.79.137", + "46.245.79.235", + "46.245.80.35", + "46.245.80.75", + "46.245.80.77", + "46.245.80.82", + "46.245.80.206", + "46.245.81.54", + "46.245.81.61", + "46.245.81.68", + "46.245.81.90", + "46.245.83.130", + "46.245.84.44", + "46.245.85.3", + "46.245.85.22", + "46.245.85.27", + "46.245.85.28", + "46.245.85.29", + "46.245.85.36", + "46.245.85.37", + "46.245.85.44", + "46.245.85.179", + "46.245.86.26", + "46.245.86.28", + "46.245.86.29", + "46.245.86.30", + "46.245.86.177", + "46.245.86.186", + "46.245.86.190", + "46.245.86.242", + "46.245.86.245", + "46.245.87.68", + "46.245.87.69", + "46.245.87.72", + "46.245.87.74", + "46.245.87.75", + "46.245.87.105", + "46.245.87.110", + "46.245.87.132", + "46.245.87.222", + "46.245.88.170", + "46.245.88.171", + "46.245.88.172", + "46.245.88.173", + "46.245.88.174", + "46.245.89.100", + "46.245.89.130", + "46.245.89.131", + "46.245.89.133", + "46.245.89.136", + "46.245.89.138", + "46.245.89.141", + "46.245.89.203", + "46.245.91.214", + "46.245.91.227", + "46.245.91.228", + "46.245.91.229", + "46.245.91.231", + "46.245.91.233", + "46.245.91.237", + "46.245.91.238", + "46.245.91.245", + "46.245.92.2", + "46.245.92.58", + "46.245.92.85", + "46.245.92.86", + "46.245.92.107", + "46.245.92.108", + "46.245.92.109", + "46.245.92.116", + "46.245.92.185", + "46.245.93.42", + "46.245.93.115", + "46.245.93.201", + "46.245.93.202", + "46.245.93.209", + "46.245.94.109", + "46.245.94.163", + "46.245.94.170", + "46.245.94.171", + "46.245.94.172", + "46.245.95.99", + "46.245.95.133", + "46.245.95.136", + "46.245.95.152", + "46.245.95.160", + "46.245.95.162", + "46.245.95.165", + "46.245.95.237", + "46.245.96.216", + "46.245.98.13", + "46.245.99.40", + "46.245.99.142", + "46.245.100.90", + "46.245.107.28", + "46.245.107.29", + "46.245.107.164", + "46.245.107.195", + "46.245.107.198", + "46.245.107.199", + "46.245.107.201", + "46.245.107.202", + "46.245.107.205", + "46.245.112.61", + "46.245.114.228", + "46.245.115.51", + "46.245.115.56", + "46.245.115.201", + "46.245.115.213", + "46.245.120.150", + "46.245.122.8", + "46.245.122.24", + "46.245.122.25", + "46.245.123.19", + "46.245.123.20", + "46.245.123.21", + "46.245.123.23", + "46.245.123.25", + "46.245.124.20", + "46.245.124.48", + "46.249.122.162", + "46.249.125.22", + "62.3.14.59", + "62.3.14.68", + "62.3.14.132", + "62.3.14.133", + "62.3.14.134", + "62.3.14.135", + "62.3.14.140", + "62.3.14.153", + "62.3.14.154", + "62.3.14.162", + "62.3.14.163", + "62.3.14.164", + "62.3.14.167", + "62.3.14.168", + "62.3.14.169", + "62.3.14.203", + "62.3.14.204", + "62.3.14.205", + "62.3.41.8", + "62.3.41.9", + "62.3.41.28", + "62.3.41.32", + "62.3.41.36", + "62.3.41.45", + "62.3.41.47", + "62.3.41.50", + "62.3.41.53", + "62.3.41.66", + "62.3.41.73", + "62.3.41.77", + "62.3.41.82", + "62.3.41.87", + "62.3.41.89", + "62.3.41.92", + "62.3.41.111", + "62.3.41.114", + "62.3.41.117", + "62.3.41.122", + "62.3.41.129", + "62.3.41.132", + "62.3.41.135", + "62.3.41.149", + "62.3.41.159", + "62.3.41.160", + "62.3.41.178", + "62.3.41.179", + "62.3.41.191", + "62.3.41.194", + "62.3.41.198", + "62.3.41.199", + "62.3.41.200", + "62.3.41.210", + "62.3.41.215", + "62.3.41.226", + "62.3.41.227", + "62.3.41.228", + "62.3.41.229", + "62.3.41.233", + "62.3.41.237", + "62.3.41.242", + "62.3.41.253", + "62.3.42.17", + "62.3.42.32", + "62.3.42.34", + "62.3.42.47", + "62.3.42.59", + "62.3.42.85", + "62.3.42.90", + "62.3.42.99", + "62.3.42.105", + "62.3.42.106", + "62.3.42.111", + "62.3.42.119", + "62.3.42.126", + "62.3.42.131", + "62.3.42.134", + "62.3.42.137", + "62.3.42.139", + "62.3.42.145", + "62.3.42.171", + "62.3.42.176", + "62.3.42.187", + "62.3.42.198", + "62.3.42.206", + "62.3.42.215", + "62.3.42.237", + "62.3.42.238", + "62.60.128.8", + "62.60.128.11", + "62.60.128.42", + "62.60.128.49", + "62.60.128.54", + "62.60.128.56", + "62.60.128.58", + "62.60.128.59", + "62.60.128.89", + "62.60.128.105", + "62.60.128.119", + "62.60.128.120", + "62.60.128.122", + "62.60.128.124", + "62.60.128.125", + "62.60.128.126", + "62.60.128.127", + "62.60.128.209", + "62.60.129.186", + "62.60.129.189", + "62.60.132.61", + "62.60.132.133", + "62.60.132.174", + "62.60.132.248", + "62.60.133.4", + "62.60.133.5", + "62.60.133.6", + "62.60.133.7", + "62.60.133.9", + "62.60.133.10", + "62.60.133.11", + "62.60.133.12", + "62.60.133.13", + "62.60.133.14", + "62.60.133.15", + "62.60.133.16", + "62.60.133.17", + "62.60.133.18", + "62.60.133.19", + "62.60.133.20", + "62.60.133.22", + "62.60.133.23", + "62.60.133.24", + "62.60.133.25", + "62.60.133.26", + "62.60.133.27", + "62.60.133.29", + "62.60.133.30", + "62.60.133.31", + "62.60.133.32", + "62.60.133.33", + "62.60.133.34", + "62.60.133.36", + "62.60.133.37", + "62.60.133.38", + "62.60.133.39", + "62.60.133.40", + "62.60.133.41", + "62.60.133.42", + "62.60.133.43", + "62.60.133.44", + "62.60.133.45", + "62.60.133.47", + "62.60.133.48", + "62.60.133.49", + "62.60.133.50", + "62.60.133.51", + "62.60.133.52", + "62.60.133.53", + "62.60.133.54", + "62.60.133.55", + "62.60.133.56", + "62.60.133.58", + "62.60.133.59", + "62.60.133.61", + "62.60.133.62", + "62.60.133.63", + "62.60.133.64", + "62.60.133.65", + "62.60.133.66", + "62.60.133.67", + "62.60.133.68", + "62.60.133.69", + "62.60.133.70", + "62.60.133.71", + "62.60.133.72", + "62.60.133.73", + "62.60.133.74", + "62.60.133.75", + "62.60.133.76", + "62.60.133.77", + "62.60.133.78", + "62.60.133.79", + "62.60.133.80", + "62.60.133.83", + "62.60.133.84", + "62.60.133.85", + "62.60.133.86", + "62.60.133.87", + "62.60.133.88", + "62.60.133.89", + "62.60.133.90", + "62.60.133.91", + "62.60.133.92", + "62.60.133.93", + "62.60.133.94", + "62.60.133.95", + "62.60.133.96", + "62.60.133.97", + "62.60.133.98", + "62.60.133.99", + "62.60.133.100", + "62.60.133.102", + "62.60.133.103", + "62.60.133.104", + "62.60.133.105", + "62.60.133.106", + "62.60.133.107", + "62.60.133.109", + "62.60.133.110", + "62.60.133.111", + "62.60.133.112", + "62.60.133.113", + "62.60.133.114", + "62.60.133.115", + "62.60.133.116", + "62.60.133.117", + "62.60.133.118", + "62.60.133.119", + "62.60.133.120", + "62.60.133.121", + "62.60.133.122", + "62.60.133.123", + "62.60.133.124", + "62.60.133.125", + "62.60.133.126", + "62.60.133.127", + "62.60.133.128", + "62.60.133.130", + "62.60.133.131", + "62.60.133.132", + "62.60.133.133", + "62.60.133.134", + "62.60.133.135", + "62.60.133.136", + "62.60.133.137", + "62.60.133.138", + "62.60.133.139", + "62.60.133.140", + "62.60.133.141", + "62.60.133.142", + "62.60.133.143", + "62.60.133.144", + "62.60.133.145", + "62.60.133.146", + "62.60.133.147", + "62.60.133.148", + "62.60.133.149", + "62.60.133.150", + "62.60.133.152", + "62.60.133.153", + "62.60.133.154", + "62.60.133.155", + "62.60.133.156", + "62.60.133.157", + "62.60.133.158", + "62.60.133.159", + "62.60.133.160", + "62.60.133.161", + "62.60.133.162", + "62.60.133.163", + "62.60.133.164", + "62.60.133.165", + "62.60.133.166", + "62.60.133.167", + "62.60.133.168", + "62.60.133.169", + "62.60.133.170", + "62.60.133.171", + "62.60.133.172", + "62.60.133.173", + "62.60.133.174", + "62.60.133.175", + "62.60.133.176", + "62.60.133.177", + "62.60.133.178", + "62.60.133.179", + "62.60.133.180", + "62.60.133.181", + "62.60.133.182", + "62.60.133.183", + "62.60.133.184", + "62.60.133.185", + "62.60.133.186", + "62.60.133.187", + "62.60.133.188", + "62.60.133.189", + "62.60.133.190", + "62.60.133.191", + "62.60.133.192", + "62.60.133.194", + "62.60.133.195", + "62.60.133.196", + "62.60.133.197", + "62.60.133.198", + "62.60.133.201", + "62.60.133.202", + "62.60.133.203", + "62.60.133.205", + "62.60.133.207", + "62.60.133.208", + "62.60.133.209", + "62.60.133.210", + "62.60.133.228", + "62.60.133.238", + "62.60.133.243", + "62.60.133.245", + "62.60.133.246", + "62.60.133.247", + "62.60.133.249", + "62.60.133.250", + "62.60.133.251", + "62.60.133.252", + "62.60.133.253", + "62.60.133.254", + "62.60.136.105", + "62.60.136.158", + "62.60.136.190", + "62.60.136.215", + "62.60.136.225", + "62.60.137.2", + "62.60.140.47", + "62.60.140.251", + "62.60.141.4", + "62.60.144.44", + "62.60.144.45", + "62.60.144.68", + "62.60.144.85", + "62.60.144.87", + "62.60.160.28", + "62.60.160.33", + "62.60.160.34", + "62.60.160.38", + "62.60.160.40", + "62.60.160.75", + "62.60.160.79", + "62.60.160.80", + "62.60.160.100", + "62.60.161.109", + "62.60.161.115", + "62.60.162.96", + "62.60.162.100", + "62.60.162.102", + "62.60.162.104", + "62.60.162.109", + "62.60.162.110", + "62.60.162.206", + "62.60.162.247", + "62.60.163.203", + "62.60.164.19", + "62.60.164.32", + "62.60.164.43", + "62.60.164.49", + "62.60.164.133", + "62.60.164.137", + "62.60.164.158", + "62.60.164.191", + "62.60.164.197", + "62.60.164.198", + "62.60.164.206", + "62.60.164.207", + "62.60.164.214", + "62.60.164.216", + "62.60.164.217", + "62.60.164.219", + "62.60.164.242", + "62.60.165.23", + "62.60.165.151", + "62.60.165.203", + "62.60.165.216", + "62.60.165.219", + "62.60.165.230", + "62.60.165.233", + "62.60.165.236", + "62.60.165.239", + "62.60.165.241", + "62.60.165.246", + "62.60.165.248", + "62.60.166.50", + "62.60.167.38", + "62.60.167.66", + "62.60.167.67", + "62.60.167.68", + "62.60.167.69", + "62.60.167.74", + "62.60.167.194", + "62.60.167.202", + "62.60.167.203", + "62.60.167.204", + "62.60.167.205", + "62.60.167.206", + "62.60.167.210", + "62.60.167.234", + "62.60.167.242", + "62.60.167.250", + "62.60.190.61", + "62.60.196.13", + "62.60.196.26", + "62.60.196.44", + "62.60.196.235", + "62.60.196.238", + "62.60.197.3", + "62.60.197.4", + "62.60.197.5", + "62.60.197.6", + "62.60.197.7", + "62.60.197.8", + "62.60.197.9", + "62.60.197.10", + "62.60.197.12", + "62.60.197.14", + "62.60.197.83", + "62.60.197.85", + "62.60.197.222", + "62.60.198.38", + "62.60.198.239", + "62.60.200.93", + "62.60.200.94", + "62.60.200.118", + "62.60.200.131", + "62.60.200.144", + "62.60.200.196", + "62.60.200.197", + "62.60.200.203", + "62.60.200.214", + "62.60.200.252", + "62.60.201.3", + "62.60.201.4", + "62.60.201.5", + "62.60.201.6", + "62.60.201.7", + "62.60.201.8", + "62.60.201.9", + "62.60.201.10", + "62.60.201.12", + "62.60.201.13", + "62.60.201.14", + "62.60.201.15", + "62.60.201.16", + "62.60.201.17", + "62.60.201.18", + "62.60.201.20", + "62.60.201.21", + "62.60.201.22", + "62.60.201.24", + "62.60.201.25", + "62.60.201.26", + "62.60.201.27", + "62.60.201.28", + "62.60.201.29", + "62.60.201.30", + "62.60.201.31", + "62.60.201.32", + "62.60.201.33", + "62.60.201.34", + "62.60.201.62", + "62.60.201.63", + "62.60.201.64", + "62.60.201.65", + "62.60.201.66", + "62.60.201.67", + "62.60.201.68", + "62.60.201.69", + "62.60.201.70", + "62.60.201.71", + "62.60.201.72", + "62.60.201.73", + "62.60.201.74", + "62.60.201.75", + "62.60.201.76", + "62.60.201.77", + "62.60.201.78", + "62.60.201.79", + "62.60.201.80", + "62.60.201.81", + "62.60.201.82", + "62.60.201.83", + "62.60.201.84", + "62.60.201.85", + "62.60.201.86", + "62.60.201.87", + "62.60.201.88", + "62.60.201.89", + "62.60.201.90", + "62.60.201.92", + "62.60.201.93", + "62.60.201.94", + "62.60.201.95", + "62.60.201.97", + "62.60.201.99", + "62.60.201.100", + "62.60.201.102", + "62.60.201.103", + "62.60.201.104", + "62.60.201.105", + "62.60.201.106", + "62.60.201.107", + "62.60.201.108", + "62.60.201.109", + "62.60.201.110", + "62.60.201.111", + "62.60.201.112", + "62.60.201.113", + "62.60.201.115", + "62.60.201.116", + "62.60.201.117", + "62.60.201.118", + "62.60.201.119", + "62.60.201.120", + "62.60.201.121", + "62.60.201.122", + "62.60.201.123", + "62.60.201.124", + "62.60.201.125", + "62.60.201.126", + "62.60.201.127", + "62.60.201.128", + "62.60.201.130", + "62.60.201.131", + "62.60.201.132", + "62.60.201.133", + "62.60.201.134", + "62.60.201.135", + "62.60.201.136", + "62.60.201.137", + "62.60.201.138", + "62.60.201.139", + "62.60.201.140", + "62.60.201.141", + "62.60.201.142", + "62.60.201.143", + "62.60.201.144", + "62.60.201.145", + "62.60.201.146", + "62.60.201.147", + "62.60.201.148", + "62.60.201.149", + "62.60.201.151", + "62.60.201.152", + "62.60.201.153", + "62.60.201.154", + "62.60.201.155", + "62.60.201.156", + "62.60.201.157", + "62.60.201.158", + "62.60.201.159", + "62.60.201.160", + "62.60.201.162", + "62.60.201.163", + "62.60.201.164", + "62.60.201.165", + "62.60.201.166", + "62.60.201.167", + "62.60.201.168", + "62.60.201.169", + "62.60.201.170", + "62.60.201.171", + "62.60.201.173", + "62.60.201.174", + "62.60.201.175", + "62.60.201.176", + "62.60.201.177", + "62.60.201.180", + "62.60.201.182", + "62.60.201.183", + "62.60.201.184", + "62.60.201.185", + "62.60.201.186", + "62.60.201.187", + "62.60.201.189", + "62.60.201.190", + "62.60.201.191", + "62.60.201.192", + "62.60.201.193", + "62.60.201.194", + "62.60.201.196", + "62.60.201.197", + "62.60.201.198", + "62.60.201.199", + "62.60.201.200", + "62.60.201.201", + "62.60.201.203", + "62.60.201.204", + "62.60.201.205", + "62.60.201.206", + "62.60.201.207", + "62.60.201.208", + "62.60.201.209", + "62.60.201.210", + "62.60.201.211", + "62.60.201.212", + "62.60.201.213", + "62.60.201.214", + "62.60.201.215", + "62.60.201.218", + "62.60.201.219", + "62.60.201.220", + "62.60.201.221", + "62.60.201.222", + "62.60.201.223", + "62.60.201.224", + "62.60.201.225", + "62.60.201.226", + "62.60.201.227", + "62.60.201.228", + "62.60.201.229", + "62.60.201.230", + "62.60.201.231", + "62.60.201.232", + "62.60.201.233", + "62.60.201.234", + "62.60.201.235", + "62.60.201.236", + "62.60.201.238", + "62.60.201.239", + "62.60.201.240", + "62.60.201.241", + "62.60.201.243", + "62.60.201.244", + "62.60.201.245", + "62.60.201.246", + "62.60.201.247", + "62.60.201.248", + "62.60.201.249", + "62.60.201.250", + "62.60.201.251", + "62.60.201.252", + "62.60.201.253", + "62.60.202.5", + "62.60.202.33", + "62.60.202.50", + "62.60.202.61", + "62.60.202.158", + "62.60.202.243", + "62.60.203.14", + "62.60.203.16", + "62.60.203.23", + "62.60.203.46", + "62.60.203.47", + "62.60.204.5", + "62.60.204.17", + "62.60.204.123", + "62.60.204.162", + "62.60.204.187", + "62.60.204.207", + "62.60.204.233", + "62.60.204.234", + "62.60.205.3", + "62.60.205.4", + "62.60.205.5", + "62.60.205.6", + "62.60.205.7", + "62.60.205.8", + "62.60.205.9", + "62.60.205.10", + "62.60.205.11", + "62.60.205.12", + "62.60.205.13", + "62.60.205.14", + "62.60.205.15", + "62.60.205.16", + "62.60.205.17", + "62.60.205.18", + "62.60.205.19", + "62.60.205.20", + "62.60.205.21", + "62.60.205.22", + "62.60.205.23", + "62.60.205.25", + "62.60.205.26", + "62.60.205.27", + "62.60.205.28", + "62.60.205.29", + "62.60.205.30", + "62.60.205.31", + "62.60.205.32", + "62.60.205.33", + "62.60.205.34", + "62.60.205.35", + "62.60.205.36", + "62.60.205.37", + "62.60.205.38", + "62.60.205.39", + "62.60.205.40", + "62.60.205.41", + "62.60.205.42", + "62.60.205.43", + "62.60.205.44", + "62.60.205.45", + "62.60.205.46", + "62.60.205.47", + "62.60.205.48", + "62.60.205.49", + "62.60.205.50", + "62.60.205.51", + "62.60.205.52", + "62.60.205.53", + "62.60.205.54", + "62.60.205.55", + "62.60.205.56", + "62.60.205.57", + "62.60.205.58", + "62.60.205.59", + "62.60.205.60", + "62.60.205.61", + "62.60.205.62", + "62.60.205.63", + "62.60.205.64", + "62.60.205.65", + "62.60.205.68", + "62.60.205.69", + "62.60.205.70", + "62.60.205.71", + "62.60.205.72", + "62.60.205.73", + "62.60.205.74", + "62.60.205.76", + "62.60.205.77", + "62.60.205.78", + "62.60.205.79", + "62.60.205.80", + "62.60.205.81", + "62.60.205.83", + "62.60.205.84", + "62.60.205.85", + "62.60.205.86", + "62.60.205.88", + "62.60.205.89", + "62.60.205.90", + "62.60.205.91", + "62.60.205.92", + "62.60.205.93", + "62.60.205.94", + "62.60.205.95", + "62.60.205.97", + "62.60.205.98", + "62.60.205.99", + "62.60.205.101", + "62.60.205.102", + "62.60.205.103", + "62.60.205.104", + "62.60.205.105", + "62.60.205.106", + "62.60.205.107", + "62.60.205.108", + "62.60.205.109", + "62.60.205.110", + "62.60.205.111", + "62.60.205.112", + "62.60.205.113", + "62.60.205.115", + "62.60.205.116", + "62.60.205.117", + "62.60.205.118", + "62.60.205.119", + "62.60.205.120", + "62.60.205.121", + "62.60.205.122", + "62.60.205.123", + "62.60.205.124", + "62.60.205.125", + "62.60.205.126", + "62.60.205.128", + "62.60.205.129", + "62.60.205.131", + "62.60.205.132", + "62.60.205.133", + "62.60.205.134", + "62.60.205.135", + "62.60.205.136", + "62.60.205.137", + "62.60.205.138", + "62.60.205.139", + "62.60.205.140", + "62.60.205.141", + "62.60.205.142", + "62.60.205.143", + "62.60.205.144", + "62.60.205.145", + "62.60.205.146", + "62.60.205.147", + "62.60.205.148", + "62.60.205.150", + "62.60.205.151", + "62.60.205.152", + "62.60.205.153", + "62.60.205.154", + "62.60.205.155", + "62.60.205.156", + "62.60.205.157", + "62.60.205.158", + "62.60.205.159", + "62.60.205.160", + "62.60.205.161", + "62.60.205.162", + "62.60.205.163", + "62.60.205.164", + "62.60.205.166", + "62.60.205.167", + "62.60.205.168", + "62.60.205.169", + "62.60.205.170", + "62.60.205.171", + "62.60.205.172", + "62.60.205.173", + "62.60.205.174", + "62.60.205.175", + "62.60.205.176", + "62.60.205.177", + "62.60.205.178", + "62.60.205.179", + "62.60.205.180", + "62.60.205.181", + "62.60.205.182", + "62.60.205.183", + "62.60.205.184", + "62.60.205.185", + "62.60.205.186", + "62.60.205.187", + "62.60.205.188", + "62.60.205.189", + "62.60.205.191", + "62.60.205.192", + "62.60.205.193", + "62.60.205.194", + "62.60.205.195", + "62.60.205.196", + "62.60.205.197", + "62.60.205.198", + "62.60.205.199", + "62.60.205.200", + "62.60.205.201", + "62.60.205.202", + "62.60.205.203", + "62.60.205.204", + "62.60.205.207", + "62.60.205.208", + "62.60.205.209", + "62.60.205.210", + "62.60.205.211", + "62.60.205.212", + "62.60.205.213", + "62.60.205.214", + "62.60.205.215", + "62.60.205.216", + "62.60.205.217", + "62.60.205.218", + "62.60.205.219", + "62.60.205.220", + "62.60.205.221", + "62.60.205.222", + "62.60.205.223", + "62.60.205.224", + "62.60.205.225", + "62.60.205.226", + "62.60.205.227", + "62.60.205.228", + "62.60.205.229", + "62.60.205.231", + "62.60.205.232", + "62.60.205.233", + "62.60.205.234", + "62.60.205.235", + "62.60.205.236", + "62.60.205.237", + "62.60.205.238", + "62.60.205.239", + "62.60.205.240", + "62.60.205.241", + "62.60.205.242", + "62.60.205.243", + "62.60.205.244", + "62.60.205.245", + "62.60.205.246", + "62.60.205.248", + "62.60.205.249", + "62.60.205.250", + "62.60.205.251", + "62.60.205.253", + "62.60.206.35", + "62.60.206.50", + "62.60.206.65", + "62.60.206.71", + "62.60.206.83", + "62.60.206.111", + "62.60.206.132", + "62.60.206.133", + "62.60.206.182", + "62.60.206.205", + "62.60.207.14", + "62.60.207.22", + "62.60.207.47", + "62.60.207.70", + "62.60.207.75", + "62.60.207.77", + "62.60.207.97", + "62.60.207.104", + "62.60.207.132", + "62.60.207.138", + "62.60.207.223", + "62.60.207.241", + "62.60.210.3", + "62.60.210.4", + "62.60.210.5", + "62.60.210.10", + "62.60.210.17", + "62.60.210.24", + "62.60.210.68", + "62.60.210.69", + "62.60.210.70", + "62.60.210.74", + "62.60.210.77", + "62.60.210.85", + "62.60.210.91", + "62.60.210.94", + "62.60.210.95", + "62.60.210.96", + "62.60.210.102", + "62.60.210.110", + "62.60.210.130", + "62.60.210.131", + "62.60.210.141", + "62.60.210.143", + "62.60.210.176", + "62.60.210.181", + "62.60.210.186", + "62.60.210.199", + "62.60.210.234", + "62.60.210.235", + "62.60.210.248", + "62.60.210.250", + "62.60.211.56", + "62.60.211.85", + "62.60.211.90", + "62.60.211.104", + "62.60.211.114", + "62.60.211.118", + "62.60.211.119", + "62.60.211.123", + "62.60.211.145", + "62.60.211.153", + "62.60.211.155", + "62.60.211.156", + "62.60.211.163", + "62.60.211.164", + "62.60.211.174", + "62.60.211.175", + "62.60.211.183", + "62.60.211.184", + "62.60.211.185", + "62.60.211.189", + "62.60.211.190", + "62.60.211.191", + "62.60.211.192", + "62.60.211.195", + "62.60.211.196", + "62.60.211.197", + "62.60.211.199", + "62.60.211.203", + "62.60.211.211", + "62.60.211.212", + "62.60.211.213", + "62.60.211.215", + "62.60.211.218", + "62.60.211.222", + "62.60.211.226", + "62.60.211.229", + "62.60.211.230", + "62.60.211.234", + "62.60.211.235", + "62.60.211.237", + "62.60.211.239", + "62.60.211.241", + "62.60.211.248", + "62.60.211.251", + "62.60.211.253", + "62.60.212.26", + "62.60.212.27", + "62.60.212.32", + "62.60.212.91", + "62.60.212.158", + "62.60.212.159", + "62.60.212.193", + "62.60.212.222", + "62.60.212.232", + "62.60.212.243", + "62.60.212.247", + "62.60.213.10", + "62.60.213.11", + "62.60.213.14", + "62.60.213.26", + "62.60.213.58", + "62.60.213.66", + "62.60.213.122", + "62.60.213.126", + "62.60.213.206", + "62.60.213.220", + "62.60.214.60", + "62.60.214.64", + "62.60.214.80", + "62.60.214.91", + "62.60.214.151", + "62.60.214.228", + "62.60.219.3", + "62.106.95.14", + "62.106.95.25", + "62.106.95.28", + "62.106.95.34", + "62.106.95.37", + "62.106.95.43", + "62.106.95.74", + "62.106.95.76", + "62.106.95.81", + "62.106.95.87", + "62.106.95.99", + "62.106.95.106", + "62.106.95.129", + "62.106.95.134", + "62.106.95.141", + "62.106.95.147", + "62.106.95.149", + "62.106.95.152", + "62.106.95.153", + "62.106.95.156", + "62.106.95.165", + "62.106.95.169", + "62.106.95.182", + "62.106.95.192", + "62.106.95.193", + "62.106.95.198", + "62.106.95.199", + "62.106.95.217", + "62.106.95.239", + "62.106.95.242", + "62.106.95.247", + "62.106.95.251", + "62.106.95.252", + "62.193.13.5", + "62.193.13.9", + "62.204.61.2", + "62.204.61.5", + "62.204.61.6", + "62.204.61.7", + "62.204.61.8", + "62.204.61.9", + "62.204.61.12", + "62.204.61.13", + "62.204.61.14", + "62.204.61.15", + "62.204.61.17", + "62.204.61.18", + "62.204.61.19", + "62.204.61.22", + "62.204.61.23", + "62.204.61.25", + "62.204.61.30", + "62.204.61.33", + "62.204.61.35", + "62.204.61.36", + "62.204.61.37", + "62.204.61.44", + "62.204.61.46", + "62.204.61.47", + "62.204.61.60", + "62.204.61.63", + "62.204.61.72", + "62.204.61.93", + "62.204.61.112", + "62.204.61.114", + "62.204.61.115", + "62.204.61.116", + "62.204.61.117", + "62.204.61.118", + "62.204.61.119", + "62.204.61.122", + "62.204.61.130", + "62.204.61.141", + "62.204.61.143", + "62.204.61.145", + "62.204.61.148", + "62.204.61.155", + "62.204.61.156", + "62.204.61.160", + "62.204.61.164", + "62.204.61.177", + "62.204.61.185", + "62.204.61.202", + "62.204.61.222", + "62.204.61.224", + "62.204.61.233", + "62.204.61.254", + "62.220.98.5", + "62.220.100.168", + "62.220.100.182", + "62.220.100.187", + "62.220.112.46", + "62.220.112.71", + "62.220.114.204", + "62.220.114.253", + "62.220.115.101", + "62.220.126.218", + "62.220.127.13", + "62.220.127.165", + "77.36.160.4", + "77.36.160.7", + "77.36.164.20", + "77.36.164.21", + "77.36.164.34", + "77.36.164.36", + "77.36.227.3", + "77.74.202.74", + "77.74.202.202", + "77.81.40.244", + "77.81.76.2", + "77.81.76.4", + "77.81.76.7", + "77.81.76.8", + "77.81.76.10", + "77.81.76.12", + "77.81.76.13", + "77.81.76.14", + "77.81.76.15", + "77.81.76.17", + "77.81.76.18", + "77.81.76.19", + "77.81.76.20", + "77.81.76.21", + "77.81.76.22", + "77.81.76.23", + "77.81.76.24", + "77.81.76.25", + "77.81.76.26", + "77.81.76.27", + "77.81.76.28", + "77.81.76.30", + "77.81.76.32", + "77.81.76.33", + "77.81.76.34", + "77.81.76.35", + "77.81.76.37", + "77.81.76.39", + "77.81.76.40", + "77.81.76.41", + "77.81.76.42", + "77.81.76.43", + "77.81.76.44", + "77.81.76.45", + "77.81.76.46", + "77.81.76.47", + "77.81.76.48", + "77.81.76.51", + "77.81.76.52", + "77.81.76.55", + "77.81.76.59", + "77.81.76.62", + "77.81.76.64", + "77.81.76.66", + "77.81.76.68", + "77.81.76.72", + "77.81.76.77", + "77.81.76.82", + "77.81.76.83", + "77.81.76.84", + "77.81.76.85", + "77.81.76.87", + "77.81.76.99", + "77.81.76.100", + "77.81.76.101", + "77.81.76.102", + "77.81.76.103", + "77.81.76.104", + "77.81.76.105", + "77.81.76.118", + "77.81.76.120", + "77.81.147.31", + "77.81.149.211", + "77.81.157.153", + "77.81.157.202", + "77.81.158.4", + "77.81.159.61", + "77.104.65.73", + "77.104.66.41", + "77.104.66.165", + "77.104.67.55", + "77.104.67.56", + "77.104.69.40", + "77.104.73.147", + "77.104.73.229", + "77.104.74.71", + "77.104.76.45", + "77.104.79.30", + "77.104.80.3", + "77.104.81.127", + "77.104.81.242", + "77.104.82.2", + "77.104.83.201", + "77.104.83.206", + "77.104.85.163", + "77.104.88.139", + "77.104.90.18", + "77.104.98.115", + "77.104.98.205", + "77.104.98.231", + "77.104.103.147", + "77.104.104.104", + "77.104.106.55", + "77.104.116.15", + "77.104.117.2", + "77.104.119.6", + "77.237.64.209", + "77.237.66.66", + "77.237.66.67", + "77.237.66.71", + "77.237.66.76", + "77.237.66.77", + "77.237.66.78", + "77.237.66.80", + "77.237.66.81", + "77.237.66.84", + "77.237.66.89", + "77.237.66.98", + "77.237.66.126", + "77.237.66.195", + "77.237.66.238", + "77.237.67.10", + "77.237.67.11", + "77.237.67.12", + "77.237.67.14", + "77.237.67.15", + "77.237.67.16", + "77.237.67.17", + "77.237.67.18", + "77.237.67.20", + "77.237.67.21", + "77.237.67.22", + "77.237.67.23", + "77.237.67.24", + "77.237.67.25", + "77.237.70.140", + "77.237.70.170", + "77.237.71.19", + "77.237.71.20", + "77.237.79.5", + "77.237.79.6", + "77.237.81.6", + "77.237.82.123", + "77.237.85.210", + "77.237.87.187", + "77.237.87.188", + "77.237.87.189", + "77.237.91.141", + "77.238.104.132", + "77.238.106.15", + "77.238.108.91", + "77.238.108.100", + "77.238.108.124", + "77.238.108.125", + "77.238.109.67", + "77.238.109.76", + "77.238.109.77", + "77.238.110.42", + "77.238.110.45", + "77.238.110.80", + "77.238.110.83", + "77.238.110.84", + "77.238.110.85", + "77.238.110.86", + "77.238.110.106", + "77.238.110.140", + "77.238.110.157", + "77.238.110.158", + "77.238.110.162", + "77.238.110.181", + "77.238.110.187", + "77.238.110.191", + "77.238.110.226", + "77.238.110.250", + "77.238.110.251", + "77.238.110.252", + "77.238.111.234", + "77.238.111.236", + "77.238.111.237", + "77.238.111.238", + "77.238.113.8", + "77.238.117.53", + "77.238.117.158", + "77.238.120.17", + "77.238.120.21", + "77.238.120.52", + "77.238.120.98", + "77.238.120.99", + "77.238.120.100", + "77.238.120.101", + "77.238.120.102", + "77.238.120.121", + "77.238.121.54", + "77.238.121.55", + "77.238.121.107", + "77.238.121.155", + "77.238.121.242", + "77.238.121.243", + "77.238.121.245", + "77.238.121.247", + "77.238.121.249", + "77.238.122.130", + "77.238.122.132", + "77.238.122.133", + "77.238.122.134", + "77.238.122.135", + "77.238.122.136", + "77.238.122.139", + "77.238.122.140", + "77.238.122.141", + "77.238.122.142", + "77.238.122.143", + "77.238.122.145", + "77.238.122.147", + "77.238.122.148", + "77.238.122.149", + "77.238.122.152", + "77.238.122.153", + "77.238.122.156", + "77.238.122.157", + "77.238.122.158", + "77.238.122.159", + "77.238.122.160", + "77.238.122.161", + "77.238.122.162", + "77.238.122.164", + "77.238.122.165", + "77.238.122.166", + "77.238.122.167", + "77.238.122.169", + "77.238.122.172", + "77.238.122.174", + "77.238.122.175", + "77.238.122.178", + "77.238.122.179", + "77.238.122.180", + "77.238.122.181", + "77.238.122.182", + "77.238.122.185", + "77.238.122.187", + "77.238.122.189", + "77.238.122.196", + "77.238.122.206", + "77.238.123.84", + "77.238.123.179", + "77.238.123.237", + "78.31.232.3", + "78.31.235.119", + "78.38.0.131", + "78.38.0.132", + "78.38.17.28", + "78.38.25.2", + "78.38.25.3", + "78.38.34.125", + "78.38.35.11", + "78.38.41.96", + "78.38.45.2", + "78.38.45.3", + "78.38.49.84", + "78.38.50.218", + "78.38.64.156", + "78.38.69.2", + "78.38.76.251", + "78.38.77.2", + "78.38.77.3", + "78.38.80.251", + "78.38.84.11", + "78.38.90.70", + "78.38.100.34", + "78.38.101.37", + "78.38.108.48", + "78.38.109.130", + "78.38.113.2", + "78.38.114.69", + "78.38.114.85", + "78.38.114.99", + "78.38.114.102", + "78.38.120.95", + "78.38.120.131", + "78.38.121.66", + "78.38.123.247", + "78.38.131.114", + "78.38.138.158", + "78.38.138.159", + "78.38.152.74", + "78.38.153.74", + "78.38.153.86", + "78.38.153.132", + "78.38.156.68", + "78.38.156.72", + "78.38.157.241", + "78.38.158.1", + "78.38.166.162", + "78.38.174.30", + "78.38.175.2", + "78.38.175.3", + "78.38.176.10", + "78.38.201.120", + "78.38.208.156", + "78.38.221.187", + "78.38.227.2", + "78.38.227.3", + "78.38.237.49", + "78.38.239.67", + "78.38.239.134", + "78.38.239.202", + "78.38.248.66", + "78.38.248.94", + "78.38.248.107", + "78.39.9.27", + "78.39.9.224", + "78.39.9.251", + "78.39.10.44", + "78.39.11.42", + "78.39.35.65", + "78.39.35.66", + "78.39.39.139", + "78.39.40.11", + "78.39.43.137", + "78.39.51.25", + "78.39.51.26", + "78.39.53.204", + "78.39.56.43", + "78.39.56.114", + "78.39.57.172", + "78.39.57.254", + "78.39.59.246", + "78.39.62.35", + "78.39.80.9", + "78.39.80.190", + "78.39.80.199", + "78.39.81.33", + "78.39.81.220", + "78.39.85.8", + "78.39.85.9", + "78.39.98.10", + "78.39.98.27", + "78.39.98.28", + "78.39.101.2", + "78.39.103.28", + "78.39.103.54", + "78.39.103.252", + "78.39.107.114", + "78.39.107.115", + "78.39.108.132", + "78.39.112.126", + "78.39.114.34", + "78.39.121.110", + "78.39.121.117", + "78.39.122.251", + "78.39.129.20", + "78.39.129.148", + "78.39.129.149", + "78.39.136.13", + "78.39.136.49", + "78.39.136.67", + "78.39.136.97", + "78.39.136.98", + "78.39.136.101", + "78.39.136.106", + "78.39.136.114", + "78.39.136.247", + "78.39.136.251", + "78.39.136.254", + "78.39.140.113", + "78.39.141.71", + "78.39.152.60", + "78.39.152.135", + "78.39.152.249", + "78.39.159.3", + "78.39.159.12", + "78.39.159.48", + "78.39.159.49", + "78.39.159.61", + "78.39.159.111", + "78.39.164.212", + "78.39.180.34", + "78.39.188.253", + "78.39.188.254", + "78.39.205.34", + "78.39.205.35", + "78.39.208.212", + "78.39.212.8", + "78.39.212.9", + "78.39.217.156", + "78.39.217.170", + "78.39.226.36", + "78.39.226.209", + "78.39.227.3", + "78.39.234.140", + "78.39.234.230", + "78.39.234.237", + "78.39.250.101", + "78.39.250.128", + "78.39.251.25", + "78.39.252.167", + "78.41.62.20", + "78.41.62.196", + "78.109.193.2", + "78.109.194.2", + "78.109.195.42", + "78.109.195.50", + "78.109.195.148", + "78.109.195.149", + "78.109.196.135", + "78.109.196.153", + "78.109.196.159", + "78.109.196.173", + "78.109.196.177", + "78.109.198.60", + "78.109.198.61", + "78.109.199.2", + "78.109.199.5", + "78.109.199.13", + "78.109.199.36", + "78.109.199.65", + "78.109.199.203", + "78.109.199.218", + "78.109.200.2", + "78.109.200.29", + "78.109.200.64", + "78.109.200.199", + "78.109.200.207", + "78.109.201.2", + "78.109.201.71", + "78.109.201.78", + "78.109.201.119", + "78.109.201.201", + "78.109.202.100", + "78.109.206.2", + "78.109.206.91", + "78.109.206.102", + "78.109.206.124", + "78.109.206.186", + "78.109.206.206", + "78.110.120.64", + "78.110.120.67", + "78.110.120.87", + "78.110.120.96", + "78.110.120.97", + "78.110.120.98", + "78.110.120.99", + "78.110.120.120", + "78.110.120.124", + "78.110.120.145", + "78.110.120.150", + "78.110.120.220", + "78.110.120.231", + "78.110.120.234", + "78.110.120.243", + "78.110.121.92", + "78.110.121.93", + "78.110.121.94", + "78.110.121.95", + "78.110.121.96", + "78.110.121.127", + "78.110.121.130", + "78.110.121.155", + "78.110.121.156", + "78.110.121.157", + "78.110.121.158", + "78.110.121.166", + "78.110.121.169", + "78.110.121.170", + "78.110.121.184", + "78.110.121.185", + "78.110.122.56", + "78.110.122.57", + "78.110.122.61", + "78.110.122.62", + "78.110.122.112", + "78.110.122.148", + "78.110.122.150", + "78.110.122.181", + "78.110.122.182", + "78.110.122.191", + "78.110.122.236", + "78.110.123.71", + "78.110.125.131", + "78.111.0.67", + "78.111.2.84", + "78.111.5.186", + "78.111.5.190", + "78.111.5.194", + "78.111.5.198", + "78.111.13.100", + "78.157.33.200", + "78.157.34.68", + "78.157.34.69", + "78.157.34.85", + "78.157.34.125", + "78.157.34.210", + "78.157.35.8", + "78.157.35.15", + "78.157.35.32", + "78.157.35.59", + "78.157.35.70", + "78.157.35.75", + "78.157.35.76", + "78.157.35.77", + "78.157.35.78", + "78.157.35.127", + "78.157.36.186", + "78.157.37.90", + "78.157.37.141", + "78.157.37.142", + "78.157.37.152", + "78.157.37.157", + "78.157.37.158", + "78.157.37.255", + "78.157.38.66", + "78.157.38.67", + "78.157.38.68", + "78.157.38.69", + "78.157.38.70", + "78.157.38.72", + "78.157.38.73", + "78.157.38.74", + "78.157.38.75", + "78.157.38.76", + "78.157.38.78", + "78.157.38.79", + "78.157.38.80", + "78.157.38.81", + "78.157.38.82", + "78.157.38.83", + "78.157.38.84", + "78.157.38.86", + "78.157.38.87", + "78.157.38.88", + "78.157.38.90", + "78.157.38.91", + "78.157.38.92", + "78.157.38.93", + "78.157.38.97", + "78.157.38.98", + "78.157.38.99", + "78.157.38.101", + "78.157.38.104", + "78.157.38.106", + "78.157.38.107", + "78.157.38.108", + "78.157.38.111", + "78.157.38.112", + "78.157.38.113", + "78.157.38.116", + "78.157.38.117", + "78.157.38.120", + "78.157.38.121", + "78.157.38.122", + "78.157.38.123", + "78.157.38.168", + "78.157.38.192", + "78.157.38.199", + "78.157.38.203", + "78.157.38.208", + "78.157.38.215", + "78.157.39.21", + "78.157.39.22", + "78.157.39.60", + "78.157.39.106", + "78.157.40.34", + "78.157.40.35", + "78.157.40.38", + "78.157.40.39", + "78.157.40.40", + "78.157.40.41", + "78.157.40.42", + "78.157.40.43", + "78.157.40.44", + "78.157.40.46", + "78.157.40.70", + "78.157.40.158", + "78.157.40.168", + "78.157.40.175", + "78.157.40.191", + "78.157.40.192", + "78.157.40.200", + "78.157.40.201", + "78.157.40.207", + "78.157.41.60", + "78.157.41.63", + "78.157.41.119", + "78.157.41.127", + "78.157.41.149", + "78.157.41.175", + "78.157.41.224", + "78.157.41.231", + "78.157.42.23", + "78.157.42.62", + "78.157.42.201", + "78.157.42.202", + "78.157.42.203", + "78.157.42.204", + "78.157.42.205", + "78.157.42.209", + "78.157.42.210", + "78.157.42.214", + "78.157.42.215", + "78.157.42.216", + "78.157.42.217", + "78.157.42.218", + "78.157.42.219", + "78.157.42.235", + "78.157.43.229", + "78.157.43.230", + "78.157.43.241", + "78.157.44.74", + "78.157.44.212", + "78.157.44.215", + "78.157.45.47", + "78.157.45.210", + "78.157.45.212", + "78.157.45.214", + "78.157.45.215", + "78.157.46.95", + "78.157.46.104", + "78.157.46.120", + "78.157.46.128", + "78.157.46.143", + "78.157.46.213", + "78.157.47.201", + "78.157.47.202", + "78.157.47.203", + "78.157.47.204", + "78.157.47.205", + "78.157.47.206", + "78.157.47.207", + "78.157.47.209", + "78.157.47.210", + "78.157.48.16", + "78.157.48.44", + "78.157.48.51", + "78.157.48.52", + "78.157.48.112", + "78.157.48.135", + "78.157.48.150", + "78.157.48.162", + "78.157.48.175", + "78.157.48.176", + "78.157.49.22", + "78.157.49.100", + "78.157.49.105", + "78.157.49.110", + "78.157.49.111", + "78.157.49.120", + "78.157.50.22", + "78.157.50.24", + "78.157.50.31", + "78.157.50.255", + "78.157.51.3", + "78.157.51.5", + "78.157.51.28", + "78.157.51.49", + "78.157.51.64", + "78.157.51.93", + "78.157.51.136", + "78.157.51.159", + "78.157.51.163", + "78.157.51.185", + "78.157.51.192", + "78.157.51.203", + "78.157.51.232", + "78.157.51.240", + "78.157.51.248", + "78.157.53.67", + "78.157.53.187", + "78.157.56.19", + "78.157.56.24", + "78.157.56.25", + "78.157.56.53", + "78.157.56.133", + "78.157.56.181", + "78.157.56.215", + "78.157.57.39", + "78.157.57.120", + "78.157.57.152", + "78.157.57.154", + "78.157.57.162", + "78.157.57.199", + "78.157.57.224", + "78.157.57.230", + "78.157.57.231", + "78.157.57.234", + "78.157.57.241", + "78.157.57.245", + "78.157.57.247", + "78.157.57.252", + "78.157.57.255", + "78.157.58.38", + "78.157.58.39", + "78.157.58.42", + "78.157.58.170", + "78.157.59.20", + "78.157.59.22", + "78.157.59.23", + "78.157.59.24", + "78.157.59.25", + "78.157.59.28", + "78.157.59.29", + "78.157.59.34", + "78.157.59.195", + "78.157.59.196", + "78.157.59.197", + "78.157.59.198", + "78.157.59.201", + "78.157.60.77", + "78.157.61.79", + "78.157.62.99", + "78.157.63.105", + "78.157.63.121", + "78.158.161.146", + "78.158.166.38", + "78.158.166.66", + "78.158.166.71", + "79.127.2.30", + "79.127.2.168", + "79.127.4.203", + "79.127.4.207", + "79.127.5.97", + "79.127.5.98", + "79.127.6.1", + "79.127.7.239", + "79.127.12.89", + "79.127.12.156", + "79.127.41.218", + "79.127.45.26", + "79.127.47.173", + "79.127.48.173", + "79.127.52.5", + "79.127.52.76", + "79.127.61.70", + "79.127.67.36", + "79.127.69.230", + "79.127.69.233", + "79.127.73.202", + "79.127.87.170", + "79.127.87.174", + "79.127.92.60", + "79.127.94.222", + "79.127.96.153", + "79.127.106.228", + "79.127.114.46", + "79.127.125.7", + "79.127.125.13", + "79.127.125.36", + "79.127.125.37", + "79.127.125.38", + "79.127.125.80", + "79.127.125.156", + "79.127.125.178", + "79.127.125.179", + "79.127.125.180", + "79.127.125.181", + "79.127.125.182", + "79.127.125.250", + "79.127.125.252", + "79.127.126.26", + "79.127.126.27", + "79.127.126.28", + "79.127.126.29", + "79.127.126.30", + "79.127.126.66", + "79.127.126.69", + "79.127.126.70", + "79.127.126.71", + "79.127.126.72", + "79.127.126.73", + "79.127.126.74", + "79.127.126.76", + "79.127.126.77", + "79.127.126.78", + "79.127.126.85", + "79.127.126.92", + "79.127.126.104", + "79.127.126.111", + "79.127.126.112", + "79.127.126.113", + "79.127.126.116", + "79.127.126.123", + "79.127.126.124", + "79.127.126.126", + "79.127.126.133", + "79.127.126.226", + "79.127.126.228", + "79.127.126.242", + "79.127.126.245", + "79.127.126.246", + "79.127.126.247", + "79.127.126.249", + "79.127.126.251", + "79.127.127.6", + "79.127.127.11", + "79.127.127.17", + "79.127.127.18", + "79.127.127.20", + "79.127.127.23", + "79.127.127.24", + "79.127.127.25", + "79.127.127.27", + "79.127.127.28", + "79.127.127.29", + "79.127.127.31", + "79.127.127.32", + "79.127.127.33", + "79.127.127.34", + "79.127.127.35", + "79.127.127.36", + "79.127.127.37", + "79.127.127.38", + "79.127.127.39", + "79.127.127.43", + "79.127.127.44", + "79.127.127.45", + "79.127.127.46", + "79.127.127.47", + "79.127.127.49", + "79.127.127.51", + "79.127.127.62", + "79.127.127.64", + "79.127.127.65", + "79.127.127.66", + "79.127.127.67", + "79.127.127.68", + "79.127.127.69", + "79.127.127.70", + "79.127.127.72", + "79.127.127.74", + "79.127.127.75", + "79.127.127.76", + "79.127.127.77", + "79.127.127.78", + "79.127.127.79", + "79.127.127.80", + "79.127.127.82", + "79.127.127.84", + "79.127.127.85", + "79.127.127.86", + "79.127.127.87", + "79.127.127.88", + "79.127.127.90", + "79.127.127.91", + "79.127.127.93", + "79.127.127.94", + "79.127.127.95", + "79.127.127.96", + "79.127.127.97", + "79.127.127.98", + "79.127.127.99", + "79.127.127.100", + "79.127.127.101", + "79.127.127.103", + "79.127.127.105", + "79.127.127.106", + "79.127.127.111", + "79.127.127.113", + "79.127.127.114", + "79.127.127.115", + "79.127.127.116", + "79.127.127.118", + "79.127.127.119", + "79.127.127.120", + "79.127.127.123", + "79.127.127.125", + "79.127.127.134", + "79.127.127.138", + "79.127.127.151", + "79.127.127.154", + "79.127.127.190", + "79.127.127.210", + "79.127.127.213", + "79.127.127.215", + "79.132.192.15", + "79.132.192.82", + "79.132.192.196", + "79.132.192.233", + "79.132.192.238", + "79.132.193.26", + "79.132.193.36", + "79.132.193.38", + "79.132.193.39", + "79.132.193.43", + "79.132.193.44", + "79.132.193.46", + "79.132.193.70", + "79.132.193.72", + "79.132.193.74", + "79.132.193.78", + "79.132.193.83", + "79.132.193.94", + "79.132.193.96", + "79.132.193.101", + "79.132.193.105", + "79.132.193.107", + "79.132.193.116", + "79.132.193.125", + "79.132.193.133", + "79.132.193.134", + "79.132.193.135", + "79.132.193.143", + "79.132.193.144", + "79.132.193.147", + "79.132.193.148", + "79.132.193.150", + "79.132.193.153", + "79.132.193.155", + "79.132.193.162", + "79.132.193.168", + "79.132.193.180", + "79.132.193.186", + "79.132.193.194", + "79.132.193.200", + "79.132.193.201", + "79.132.193.206", + "79.132.193.210", + "79.132.193.216", + "79.132.193.225", + "79.132.193.226", + "79.132.193.228", + "79.132.193.229", + "79.132.193.236", + "79.132.193.238", + "79.132.193.239", + "79.132.193.240", + "79.132.193.243", + "79.132.193.245", + "79.132.193.248", + "79.132.193.249", + "79.132.193.251", + "79.132.193.253", + "79.132.206.249", + "79.132.206.250", + "79.132.210.66", + "79.143.84.18", + "79.143.84.77", + "79.143.84.114", + "79.143.84.118", + "79.143.84.126", + "79.143.84.162", + "79.143.84.166", + "79.143.86.4", + "79.143.86.6", + "79.143.86.7", + "79.143.86.154", + "79.143.86.156", + "79.143.86.165", + "79.143.86.166", + "79.143.86.172", + "79.174.161.219", + "79.174.161.222", + "79.174.165.135", + "79.174.165.136", + "79.175.129.2", + "79.175.130.58", + "79.175.130.59", + "79.175.130.60", + "79.175.130.61", + "79.175.130.62", + "79.175.131.17", + "79.175.131.19", + "79.175.131.38", + "79.175.131.85", + "79.175.131.145", + "79.175.133.5", + "79.175.133.6", + "79.175.133.14", + "79.175.133.31", + "79.175.133.93", + "79.175.133.102", + "79.175.133.108", + "79.175.133.127", + "79.175.133.138", + "79.175.133.149", + "79.175.133.154", + "79.175.133.196", + "79.175.133.220", + "79.175.133.233", + "79.175.133.239", + "79.175.133.241", + "79.175.134.2", + "79.175.134.20", + "79.175.134.68", + "79.175.134.85", + "79.175.134.141", + "79.175.134.172", + "79.175.134.176", + "79.175.134.220", + "79.175.136.2", + "79.175.136.3", + "79.175.136.4", + "79.175.136.13", + "79.175.136.72", + "79.175.136.86", + "79.175.136.194", + "79.175.137.2", + "79.175.137.3", + "79.175.137.4", + "79.175.138.2", + "79.175.138.4", + "79.175.138.5", + "79.175.139.179", + "79.175.139.180", + "79.175.139.181", + "79.175.139.182", + "79.175.139.183", + "79.175.139.184", + "79.175.139.187", + "79.175.139.188", + "79.175.139.189", + "79.175.139.190", + "79.175.142.147", + "79.175.142.150", + "79.175.145.90", + "79.175.145.147", + "79.175.145.149", + "79.175.146.74", + "79.175.148.66", + "79.175.148.67", + "79.175.148.200", + "79.175.148.242", + "79.175.149.194", + "79.175.151.2", + "79.175.151.27", + "79.175.151.31", + "79.175.151.40", + "79.175.151.57", + "79.175.151.63", + "79.175.151.74", + "79.175.151.80", + "79.175.151.84", + "79.175.151.90", + "79.175.151.102", + "79.175.151.122", + "79.175.151.130", + "79.175.151.173", + "79.175.151.174", + "79.175.151.213", + "79.175.151.226", + "79.175.151.233", + "79.175.151.250", + "79.175.154.20", + "79.175.155.2", + "79.175.155.3", + "79.175.155.66", + "79.175.155.68", + "79.175.155.69", + "79.175.155.82", + "79.175.155.83", + "79.175.155.84", + "79.175.155.88", + "79.175.155.91", + "79.175.157.2", + "79.175.157.3", + "79.175.157.4", + "79.175.157.5", + "79.175.157.6", + "79.175.157.66", + "79.175.157.67", + "79.175.157.68", + "79.175.157.69", + "79.175.157.70", + "79.175.157.102", + "79.175.157.103", + "79.175.157.163", + "79.175.160.90", + "79.175.162.2", + "79.175.164.171", + "79.175.164.172", + "79.175.165.3", + "79.175.165.170", + "79.175.166.90", + "79.175.166.98", + "79.175.166.100", + "79.175.166.102", + "79.175.166.146", + "79.175.166.147", + "79.175.167.2", + "79.175.167.238", + "79.175.168.67", + "79.175.168.94", + "79.175.168.142", + "79.175.168.219", + "79.175.169.51", + "79.175.169.53", + "79.175.169.54", + "79.175.169.55", + "79.175.169.56", + "79.175.169.57", + "79.175.169.58", + "79.175.169.59", + "79.175.169.62", + "79.175.169.120", + "79.175.169.125", + "79.175.169.138", + "79.175.169.139", + "79.175.169.141", + "79.175.169.217", + "79.175.169.218", + "79.175.169.219", + "79.175.169.222", + "79.175.169.226", + "79.175.169.229", + "79.175.171.179", + "79.175.172.98", + "79.175.172.101", + "79.175.172.147", + "79.175.172.150", + "79.175.172.155", + "79.175.172.156", + "79.175.173.179", + "79.175.173.198", + "79.175.173.217", + "79.175.173.234", + "79.175.174.124", + "79.175.174.125", + "79.175.176.3", + "79.175.176.22", + "79.175.176.36", + "79.175.176.37", + "79.175.176.46", + "79.175.176.49", + "79.175.176.60", + "79.175.176.69", + "79.175.176.70", + "79.175.176.74", + "79.175.176.76", + "79.175.176.99", + "79.175.176.118", + "79.175.176.123", + "79.175.176.136", + "79.175.176.177", + "79.175.176.184", + "79.175.176.188", + "79.175.176.191", + "79.175.176.209", + "79.175.177.2", + "79.175.177.17", + "79.175.177.30", + "79.175.177.67", + "79.175.177.103", + "79.175.177.112", + "79.175.177.113", + "79.175.177.136", + "79.175.177.138", + "79.175.181.148", + "79.175.181.150", + "79.175.181.153", + "79.175.181.156", + "79.175.181.158", + "79.175.181.171", + "79.175.181.203", + "79.175.181.206", + "79.175.181.213", + "79.175.186.162", + "79.175.188.2", + "79.175.188.41", + "79.175.188.135", + "79.175.188.186", + "79.175.189.104", + "79.175.190.162", + "79.175.190.166", + "79.175.190.180", + "79.175.190.181", + "79.175.190.182", + "79.175.190.237", + "80.66.177.4", + "80.66.177.5", + "80.66.177.22", + "80.66.177.49", + "80.71.119.2", + "80.71.149.3", + "80.71.149.4", + "80.71.149.40", + "80.71.149.42", + "80.71.149.127", + "80.71.149.152", + "80.71.149.158", + "80.71.149.175", + "80.71.149.241", + "80.71.149.249", + "80.75.0.2", + "80.75.1.42", + "80.75.2.15", + "80.75.2.135", + "80.75.3.7", + "80.75.3.17", + "80.75.3.86", + "80.75.3.93", + "80.75.3.227", + "80.75.4.101", + "80.75.4.167", + "80.75.4.168", + "80.75.7.10", + "80.75.7.26", + "80.75.7.188", + "80.75.9.7", + "80.75.10.134", + "80.75.13.179", + "80.75.14.70", + "80.75.14.71", + "80.75.14.96", + "80.75.14.99", + "80.75.14.102", + "80.75.14.103", + "80.75.14.108", + "80.75.14.115", + "80.75.14.116", + "80.75.14.118", + "80.75.14.124", + "80.75.213.70", + "80.75.213.90", + "80.75.213.131", + "80.75.213.146", + "80.75.213.147", + "80.75.213.148", + "80.75.213.149", + "80.75.213.155", + "80.75.213.253", + "80.91.208.18", + "80.91.208.206", + "80.191.0.17", + "80.191.0.18", + "80.191.2.218", + "80.191.8.40", + "80.191.11.134", + "80.191.24.2", + "80.191.24.9", + "80.191.27.199", + "80.191.27.203", + "80.191.27.204", + "80.191.27.205", + "80.191.27.206", + "80.191.32.3", + "80.191.32.56", + "80.191.36.20", + "80.191.41.83", + "80.191.41.84", + "80.191.47.2", + "80.191.49.130", + "80.191.49.131", + "80.191.49.133", + "80.191.56.7", + "80.191.56.100", + "80.191.56.110", + "80.191.62.5", + "80.191.65.241", + "80.191.68.247", + "80.191.70.34", + "80.191.75.110", + "80.191.80.9", + "80.191.89.50", + "80.191.89.98", + "80.191.90.54", + "80.191.90.62", + "80.191.92.15", + "80.191.92.179", + "80.191.92.188", + "80.191.92.199", + "80.191.92.233", + "80.191.100.246", + "80.191.103.92", + "80.191.105.200", + "80.191.105.245", + "80.191.105.249", + "80.191.105.252", + "80.191.110.138", + "80.191.111.4", + "80.191.111.5", + "80.191.111.170", + "80.191.131.131", + "80.191.153.109", + "80.191.156.134", + "80.191.156.234", + "80.191.163.249", + "80.191.163.251", + "80.191.170.194", + "80.191.172.4", + "80.191.172.69", + "80.191.174.111", + "80.191.174.112", + "80.191.192.6", + "80.191.192.115", + "80.191.193.40", + "80.191.202.15", + "80.191.203.10", + "80.191.214.20", + "80.191.216.2", + "80.191.221.12", + "80.191.221.14", + "80.191.221.19", + "80.191.221.20", + "80.191.221.21", + "80.191.221.22", + "80.191.221.25", + "80.191.221.28", + "80.191.221.29", + "80.191.221.30", + "80.191.221.32", + "80.191.221.40", + "80.191.221.41", + "80.191.221.61", + "80.191.221.72", + "80.191.228.3", + "80.191.236.54", + "80.191.242.1", + "80.191.242.90", + "80.191.242.228", + "80.191.248.61", + "80.191.248.62", + "80.210.17.2", + "80.210.20.226", + "80.210.22.97", + "80.210.24.8", + "80.210.26.114", + "80.210.26.166", + "80.210.30.38", + "80.210.30.78", + "80.210.30.156", + "80.210.32.30", + "80.210.32.38", + "80.210.32.132", + "80.210.32.162", + "80.210.38.9", + "80.210.38.11", + "80.210.40.54", + "80.210.40.208", + "80.210.41.96", + "80.210.48.145", + "80.210.51.88", + "80.210.53.80", + "80.210.57.212", + "80.210.59.19", + "80.210.59.98", + "80.210.61.28", + "80.210.62.28", + "80.210.62.245", + "80.210.63.54", + "80.210.141.120", + "80.210.166.118", + "80.210.167.23", + "80.210.168.153", + "80.210.173.249", + "80.253.136.12", + "80.253.145.150", + "80.253.147.222", + "81.12.0.3", + "81.12.4.10", + "81.12.5.164", + "81.12.5.221", + "81.12.7.54", + "81.12.12.201", + "81.12.12.202", + "81.12.13.11", + "81.12.13.74", + "81.12.25.53", + "81.12.27.38", + "81.12.27.122", + "81.12.27.171", + "81.12.27.210", + "81.12.27.218", + "81.12.27.222", + "81.12.28.206", + "81.12.30.13", + "81.12.30.82", + "81.12.30.139", + "81.12.30.140", + "81.12.30.141", + "81.12.30.195", + "81.12.31.48", + "81.12.34.177", + "81.12.35.100", + "81.12.39.2", + "81.12.39.3", + "81.12.39.4", + "81.12.39.8", + "81.12.39.9", + "81.12.39.10", + "81.12.39.11", + "81.12.39.12", + "81.12.39.13", + "81.12.39.14", + "81.12.39.15", + "81.12.39.16", + "81.12.39.17", + "81.12.39.18", + "81.12.39.19", + "81.12.39.20", + "81.12.39.21", + "81.12.39.22", + "81.12.39.23", + "81.12.39.24", + "81.12.39.25", + "81.12.39.26", + "81.12.39.27", + "81.12.39.28", + "81.12.39.29", + "81.12.39.31", + "81.12.39.54", + "81.12.39.81", + "81.12.39.116", + "81.12.39.134", + "81.12.39.135", + "81.12.39.137", + "81.12.39.138", + "81.12.39.159", + "81.12.39.193", + "81.12.39.194", + "81.12.39.195", + "81.12.39.197", + "81.12.39.198", + "81.12.39.199", + "81.12.39.202", + "81.12.39.210", + "81.12.39.233", + "81.12.52.4", + "81.12.52.58", + "81.12.52.90", + "81.12.52.122", + "81.12.52.126", + "81.12.52.146", + "81.12.52.155", + "81.12.52.158", + "81.12.52.162", + "81.12.52.172", + "81.12.52.178", + "81.12.52.190", + "81.12.52.235", + "81.12.53.76", + "81.12.53.90", + "81.12.53.115", + "81.12.53.116", + "81.12.53.124", + "81.12.53.126", + "81.12.53.131", + "81.12.53.150", + "81.12.53.162", + "81.12.53.182", + "81.12.53.206", + "81.12.53.217", + "81.12.54.123", + "81.12.55.235", + "81.12.59.137", + "81.12.59.138", + "81.12.62.101", + "81.12.63.109", + "81.12.65.141", + "81.12.65.144", + "81.12.70.4", + "81.12.72.123", + "81.12.72.218", + "81.12.75.140", + "81.12.87.104", + "81.12.88.52", + "81.12.91.51", + "81.12.91.52", + "81.12.91.53", + "81.12.93.250", + "81.12.93.251", + "81.12.93.252", + "81.12.93.253", + "81.12.97.60", + "81.12.97.124", + "81.12.98.18", + "81.12.98.19", + "81.12.98.21", + "81.12.98.22", + "81.12.101.62", + "81.12.105.194", + "81.12.108.99", + "81.12.108.100", + "81.12.108.101", + "81.12.108.102", + "81.12.112.180", + "81.12.113.26", + "81.12.116.130", + "81.12.116.131", + "81.12.116.133", + "81.12.116.134", + "81.12.116.135", + "81.12.116.136", + "81.12.116.137", + "81.12.116.138", + "81.12.116.139", + "81.12.116.140", + "81.12.120.27", + "81.12.120.131", + "81.12.122.130", + "81.12.123.151", + "81.16.114.126", + "81.16.115.130", + "81.16.116.178", + "81.16.121.124", + "81.16.121.161", + "81.16.123.125", + "81.16.124.169", + "81.16.126.103", + "81.16.126.237", + "81.16.127.253", + "81.28.52.30", + "81.28.61.72", + "81.29.245.169", + "81.29.255.35", + "81.31.160.34", + "81.31.160.35", + "81.31.169.19", + "81.31.250.184", + "81.31.250.186", + "81.31.250.188", + "81.31.250.189", + "81.90.144.210", + "81.90.147.2", + "81.90.147.189", + "81.90.152.150", + "81.90.158.26", + "81.90.159.170", + "81.91.129.226", + "81.91.129.227", + "81.91.129.229", + "81.91.129.230", + "81.91.139.109", + "81.91.139.118", + "81.91.140.90", + "81.91.145.181", + "81.91.146.254", + "81.91.147.3", + "81.91.147.250", + "81.91.151.172", + "81.91.155.46", + "81.91.155.94", + "81.91.157.181", + "82.99.202.102", + "82.99.202.212", + "82.99.204.74", + "82.99.204.93", + "82.99.227.214", + "82.99.240.57", + "82.99.250.149", + "82.99.252.117", + "82.99.253.93", + "83.147.193.2", + "83.147.193.3", + "83.147.193.4", + "83.147.193.240", + "83.150.192.12", + "83.150.193.12", + "84.47.239.2", + "84.47.239.3", + "84.47.239.4", + "84.47.239.6", + "84.47.239.7", + "84.47.239.8", + "84.47.239.9", + "84.47.239.10", + "84.47.239.11", + "84.47.239.12", + "84.47.239.14", + "84.47.239.15", + "84.47.239.16", + "84.47.239.17", + "84.47.239.18", + "84.47.239.19", + "84.47.239.20", + "84.47.239.22", + "84.47.239.23", + "84.47.239.25", + "84.241.0.237", + "84.241.1.202", + "84.241.2.22", + "84.241.2.42", + "84.241.2.100", + "84.241.2.253", + "84.241.4.59", + "84.241.7.39", + "84.241.7.200", + "84.241.10.229", + "84.241.11.102", + "84.241.11.222", + "84.241.13.118", + "84.241.14.122", + "84.241.16.25", + "84.241.16.49", + "84.241.19.230", + "84.241.22.21", + "84.241.22.121", + "84.241.22.236", + "84.241.26.155", + "84.241.27.20", + "84.241.27.165", + "84.241.28.236", + "84.241.30.11", + "84.241.30.192", + "84.241.32.141", + "84.241.35.82", + "84.241.38.8", + "84.241.39.230", + "84.241.39.250", + "84.241.41.23", + "84.241.41.93", + "84.241.41.195", + "84.241.43.83", + "84.241.45.39", + "84.241.47.146", + "84.241.56.89", + "84.241.63.178", + "85.9.66.19", + "85.9.71.176", + "85.9.91.133", + "85.9.104.2", + "85.9.105.37", + "85.9.105.46", + "85.9.107.3", + "85.9.107.123", + "85.9.107.154", + "85.9.107.163", + "85.9.107.165", + "85.9.107.169", + "85.9.107.217", + "85.9.107.228", + "85.9.107.229", + "85.9.108.3", + "85.9.108.4", + "85.9.108.98", + "85.9.108.100", + "85.9.108.101", + "85.9.108.102", + "85.9.108.103", + "85.9.108.104", + "85.9.108.108", + "85.9.109.5", + "85.9.111.122", + "85.9.111.123", + "85.9.111.125", + "85.9.111.126", + "85.9.111.154", + "85.9.111.172", + "85.9.111.186", + "85.9.112.212", + "85.9.112.227", + "85.9.112.228", + "85.9.112.230", + "85.9.112.232", + "85.9.120.53", + "85.9.123.33", + "85.9.125.22", + "85.15.6.12", + "85.15.6.13", + "85.15.24.14", + "85.133.128.248", + "85.133.128.249", + "85.133.148.126", + "85.133.149.54", + "85.133.153.149", + "85.133.153.170", + "85.133.153.172", + "85.133.153.173", + "85.133.153.174", + "85.133.155.216", + "85.133.155.217", + "85.133.155.218", + "85.133.155.219", + "85.133.155.220", + "85.133.155.221", + "85.133.155.222", + "85.133.155.223", + "85.133.159.76", + "85.133.159.94", + "85.133.167.166", + "85.133.167.187", + "85.133.168.52", + "85.133.168.115", + "85.133.169.53", + "85.133.169.61", + "85.133.171.186", + "85.133.171.187", + "85.133.173.53", + "85.133.176.113", + "85.133.179.34", + "85.133.180.90", + "85.133.187.102", + "85.133.188.38", + "85.133.193.31", + "85.133.193.38", + "85.133.193.43", + "85.133.193.53", + "85.133.193.55", + "85.133.193.58", + "85.133.193.59", + "85.133.193.71", + "85.133.193.79", + "85.133.193.80", + "85.133.193.82", + "85.133.193.83", + "85.133.193.128", + "85.133.193.139", + "85.133.193.143", + "85.133.193.148", + "85.133.193.152", + "85.133.193.153", + "85.133.193.199", + "85.133.195.212", + "85.133.195.216", + "85.133.197.63", + "85.133.197.198", + "85.133.198.72", + "85.133.198.73", + "85.133.200.9", + "85.133.200.250", + "85.133.200.254", + "85.133.205.40", + "85.133.205.76", + "85.133.205.113", + "85.133.205.133", + "85.133.206.130", + "85.133.206.137", + "85.133.206.138", + "85.133.206.139", + "85.133.206.142", + "85.133.207.169", + "85.133.212.54", + "85.133.212.218", + "85.133.212.243", + "85.133.212.244", + "85.133.212.246", + "85.133.212.247", + "85.133.212.249", + "85.133.217.9", + "85.133.217.13", + "85.133.217.14", + "85.133.217.26", + "85.133.217.66", + "85.133.217.67", + "85.133.217.69", + "85.133.217.70", + "85.133.217.110", + "85.133.217.242", + "85.133.217.253", + "85.133.217.254", + "85.133.220.130", + "85.133.227.61", + "85.133.235.180", + "85.133.237.237", + "85.133.240.32", + "85.133.243.5", + "85.133.243.6", + "85.133.243.8", + "85.133.243.9", + "85.133.243.10", + "85.133.243.12", + "85.133.243.13", + "85.133.243.14", + "85.133.243.15", + "85.133.243.16", + "85.133.243.18", + "85.133.243.19", + "85.133.243.20", + "85.133.243.21", + "85.133.243.22", + "85.133.243.23", + "85.133.243.24", + "85.133.243.25", + "85.133.243.26", + "85.133.243.27", + "85.133.243.28", + "85.133.243.29", + "85.133.243.30", + "85.133.243.31", + "85.133.243.32", + "85.133.243.33", + "85.133.243.35", + "85.133.243.36", + "85.133.243.37", + "85.133.243.38", + "85.133.243.39", + "85.133.243.40", + "85.133.243.41", + "85.133.243.42", + "85.133.243.43", + "85.133.243.44", + "85.133.243.45", + "85.133.243.46", + "85.133.243.47", + "85.133.243.48", + "85.133.243.49", + "85.133.243.50", + "85.133.243.51", + "85.133.243.52", + "85.133.243.53", + "85.133.243.54", + "85.133.243.55", + "85.133.243.56", + "85.133.243.57", + "85.133.243.58", + "85.133.243.60", + "85.133.243.61", + "85.133.243.62", + "85.133.243.63", + "85.133.243.64", + "85.133.243.65", + "85.133.243.66", + "85.133.243.67", + "85.133.243.68", + "85.133.243.69", + "85.133.243.70", + "85.133.243.71", + "85.133.243.72", + "85.133.243.73", + "85.133.243.74", + "85.133.243.75", + "85.133.243.76", + "85.133.243.77", + "85.133.243.78", + "85.133.243.79", + "85.133.243.80", + "85.133.243.81", + "85.133.243.82", + "85.133.243.83", + "85.133.243.84", + "85.133.243.85", + "85.133.243.86", + "85.133.243.87", + "85.133.243.88", + "85.133.243.89", + "85.133.243.90", + "85.133.243.91", + "85.133.243.92", + "85.133.243.150", + "85.133.250.125", + "85.133.250.126", + "85.133.250.184", + "85.133.253.100", + "85.133.253.103", + "85.185.1.10", + "85.185.1.125", + "85.185.8.204", + "85.185.27.131", + "85.185.36.132", + "85.185.41.131", + "85.185.45.50", + "85.185.53.30", + "85.185.54.61", + "85.185.67.253", + "85.185.67.254", + "85.185.69.83", + "85.185.75.110", + "85.185.75.202", + "85.185.81.116", + "85.185.81.130", + "85.185.81.132", + "85.185.82.100", + "85.185.82.130", + "85.185.82.152", + "85.185.83.67", + "85.185.83.172", + "85.185.85.195", + "85.185.88.138", + "85.185.91.3", + "85.185.93.64", + "85.185.93.69", + "85.185.93.90", + "85.185.93.115", + "85.185.93.117", + "85.185.93.118", + "85.185.95.134", + "85.185.95.141", + "85.185.99.235", + "85.185.101.66", + "85.185.101.67", + "85.185.101.69", + "85.185.104.21", + "85.185.105.99", + "85.185.123.3", + "85.185.123.4", + "85.185.123.5", + "85.185.123.121", + "85.185.123.122", + "85.185.152.66", + "85.185.153.10", + "85.185.157.2", + "85.185.158.236", + "85.185.159.197", + "85.185.161.4", + "85.185.161.5", + "85.185.161.25", + "85.185.161.30", + "85.185.161.49", + "85.185.161.73", + "85.185.161.77", + "85.185.161.83", + "85.185.161.140", + "85.185.163.4", + "85.185.163.101", + "85.185.163.106", + "85.185.163.123", + "85.185.165.12", + "85.185.165.13", + "85.185.170.245", + "85.185.201.6", + "85.185.201.7", + "85.185.201.11", + "85.185.201.13", + "85.185.201.18", + "85.185.201.20", + "85.185.201.21", + "85.185.201.22", + "85.185.201.26", + "85.185.201.35", + "85.185.206.226", + "85.185.211.17", + "85.185.211.19", + "85.185.215.43", + "85.185.231.22", + "85.185.231.42", + "85.185.232.135", + "85.185.236.66", + "85.185.237.114", + "85.185.250.67", + "85.185.250.250", + "85.185.251.253", + "85.185.254.95", + "85.198.1.10", + "85.198.1.26", + "85.198.9.221", + "85.198.12.178", + "85.198.14.3", + "85.198.16.43", + "85.198.17.29", + "85.198.17.91", + "85.198.26.46", + "85.198.29.12", + "85.204.77.179", + "85.204.104.34", + "85.204.104.47", + "85.208.253.13", + "85.208.253.15", + "85.208.253.16", + "85.208.253.19", + "85.208.253.22", + "85.208.253.26", + "85.208.253.37", + "85.208.253.39", + "85.208.253.40", + "85.208.253.44", + "85.208.253.48", + "85.208.253.50", + "85.208.253.52", + "85.208.253.53", + "85.208.253.60", + "85.208.253.75", + "85.208.253.77", + "85.208.253.78", + "85.208.253.91", + "85.208.253.92", + "85.208.253.102", + "85.208.253.103", + "85.208.253.106", + "85.208.253.107", + "85.208.253.112", + "85.208.253.127", + "85.208.253.129", + "85.208.253.133", + "85.208.253.142", + "85.208.253.149", + "85.208.253.150", + "85.208.253.151", + "85.208.253.153", + "85.208.253.155", + "85.208.253.159", + "85.208.253.169", + "85.208.253.173", + "85.208.253.180", + "85.208.253.181", + "85.208.253.182", + "85.208.253.183", + "85.208.253.186", + "85.208.253.191", + "85.208.253.194", + "85.208.253.196", + "85.208.253.199", + "85.208.253.202", + "85.208.253.205", + "85.208.253.208", + "85.208.253.211", + "85.208.253.218", + "85.208.253.219", + "85.208.253.224", + "85.208.253.226", + "85.208.253.235", + "85.208.253.237", + "85.208.253.239", + "85.208.253.242", + "85.208.253.249", + "85.208.253.253", + "85.208.254.38", + "85.208.254.51", + "85.208.254.55", + "85.208.254.58", + "85.208.254.59", + "85.208.254.64", + "85.208.254.143", + "85.208.254.145", + "85.208.254.147", + "85.208.254.155", + "85.208.254.156", + "85.208.254.168", + "85.208.254.173", + "85.208.254.175", + "85.208.254.177", + "85.208.254.183", + "85.208.254.192", + "85.208.254.194", + "85.208.254.195", + "85.208.254.198", + "85.208.254.199", + "85.208.254.202", + "85.208.254.208", + "85.208.254.214", + "85.208.254.216", + "85.208.254.225", + "85.208.254.228", + "85.208.254.231", + "85.208.254.234", + "85.208.254.237", + "85.208.254.248", + "85.208.254.250", + "86.104.32.2", + "86.104.32.36", + "86.104.32.60", + "86.104.32.73", + "86.104.32.84", + "86.104.32.119", + "86.104.32.131", + "86.104.32.163", + "86.104.32.188", + "86.104.32.192", + "86.104.32.245", + "86.104.32.248", + "86.104.32.252", + "86.104.33.230", + "86.104.33.235", + "86.104.35.182", + "86.104.35.183", + "86.104.35.184", + "86.104.35.186", + "86.104.35.187", + "86.104.35.188", + "86.104.35.189", + "86.104.37.4", + "86.104.37.8", + "86.104.37.12", + "86.104.37.37", + "86.104.37.63", + "86.104.37.66", + "86.104.37.106", + "86.104.39.4", + "86.104.39.5", + "86.104.39.101", + "86.104.44.51", + "86.104.44.52", + "86.104.44.53", + "86.104.44.54", + "86.104.44.55", + "86.104.44.56", + "86.104.44.57", + "86.104.44.58", + "86.104.44.59", + "86.104.244.145", + "86.106.142.7", + "86.106.142.10", + "86.106.142.12", + "86.106.142.13", + "86.106.142.14", + "86.106.142.17", + "86.106.142.18", + "86.106.142.19", + "86.106.142.20", + "86.106.142.27", + "86.106.142.31", + "86.106.142.32", + "86.106.142.33", + "86.106.142.42", + "86.106.142.45", + "86.106.142.47", + "86.106.142.53", + "86.106.142.54", + "86.106.142.66", + "86.106.142.101", + "86.106.142.109", + "86.106.142.111", + "86.106.142.115", + "86.106.142.118", + "86.106.142.120", + "86.106.142.121", + "86.106.142.122", + "86.106.142.124", + "86.107.14.240", + "86.107.144.157", + "86.107.146.145", + "86.107.155.83", + "86.107.158.199", + "87.107.6.2", + "87.107.6.3", + "87.107.6.4", + "87.107.6.5", + "87.107.6.6", + "87.107.6.7", + "87.107.6.8", + "87.107.6.9", + "87.107.6.10", + "87.107.6.11", + "87.107.6.12", + "87.107.6.13", + "87.107.6.14", + "87.107.6.15", + "87.107.6.16", + "87.107.6.18", + "87.107.6.19", + "87.107.6.20", + "87.107.6.21", + "87.107.6.22", + "87.107.6.24", + "87.107.6.25", + "87.107.6.26", + "87.107.6.27", + "87.107.6.28", + "87.107.6.29", + "87.107.6.30", + "87.107.6.31", + "87.107.6.32", + "87.107.6.33", + "87.107.6.34", + "87.107.6.35", + "87.107.6.37", + "87.107.6.38", + "87.107.6.39", + "87.107.6.40", + "87.107.6.41", + "87.107.6.42", + "87.107.6.43", + "87.107.6.44", + "87.107.6.45", + "87.107.6.46", + "87.107.6.47", + "87.107.6.48", + "87.107.6.49", + "87.107.6.50", + "87.107.6.51", + "87.107.6.52", + "87.107.6.53", + "87.107.6.54", + "87.107.6.55", + "87.107.6.56", + "87.107.6.57", + "87.107.6.58", + "87.107.6.61", + "87.107.6.62", + "87.107.6.63", + "87.107.6.64", + "87.107.6.65", + "87.107.6.66", + "87.107.6.67", + "87.107.6.68", + "87.107.6.69", + "87.107.6.70", + "87.107.6.71", + "87.107.6.72", + "87.107.6.73", + "87.107.6.74", + "87.107.6.75", + "87.107.6.76", + "87.107.6.77", + "87.107.6.78", + "87.107.6.79", + "87.107.6.80", + "87.107.6.81", + "87.107.6.82", + "87.107.6.83", + "87.107.6.85", + "87.107.6.86", + "87.107.6.87", + "87.107.6.88", + "87.107.6.89", + "87.107.6.90", + "87.107.6.91", + "87.107.6.92", + "87.107.6.93", + "87.107.6.94", + "87.107.6.95", + "87.107.6.96", + "87.107.6.98", + "87.107.6.99", + "87.107.6.100", + "87.107.6.101", + "87.107.6.102", + "87.107.6.103", + "87.107.6.104", + "87.107.6.105", + "87.107.6.106", + "87.107.6.107", + "87.107.6.108", + "87.107.6.109", + "87.107.6.110", + "87.107.6.111", + "87.107.6.112", + "87.107.6.113", + "87.107.6.114", + "87.107.6.115", + "87.107.6.116", + "87.107.6.117", + "87.107.6.118", + "87.107.6.119", + "87.107.6.120", + "87.107.6.121", + "87.107.6.122", + "87.107.6.123", + "87.107.6.124", + "87.107.6.125", + "87.107.6.126", + "87.107.6.171", + "87.107.6.178", + "87.107.6.198", + "87.107.7.19", + "87.107.7.30", + "87.107.7.54", + "87.107.7.67", + "87.107.7.69", + "87.107.7.70", + "87.107.7.71", + "87.107.7.74", + "87.107.7.75", + "87.107.7.78", + "87.107.7.81", + "87.107.7.88", + "87.107.7.92", + "87.107.7.110", + "87.107.7.168", + "87.107.7.173", + "87.107.7.210", + "87.107.8.21", + "87.107.8.86", + "87.107.8.92", + "87.107.8.98", + "87.107.8.138", + "87.107.8.170", + "87.107.8.174", + "87.107.8.227", + "87.107.9.37", + "87.107.9.67", + "87.107.9.68", + "87.107.9.69", + "87.107.9.71", + "87.107.9.75", + "87.107.9.76", + "87.107.9.78", + "87.107.9.91", + "87.107.9.206", + "87.107.9.232", + "87.107.9.233", + "87.107.9.236", + "87.107.9.237", + "87.107.9.238", + "87.107.9.241", + "87.107.9.248", + "87.107.10.46", + "87.107.10.99", + "87.107.10.100", + "87.107.10.147", + "87.107.10.210", + "87.107.10.234", + "87.107.11.82", + "87.107.12.4", + "87.107.12.70", + "87.107.12.71", + "87.107.12.124", + "87.107.12.136", + "87.107.12.188", + "87.107.12.211", + "87.107.12.217", + "87.107.13.138", + "87.107.13.139", + "87.107.13.140", + "87.107.13.141", + "87.107.13.142", + "87.107.13.190", + "87.107.19.195", + "87.107.19.197", + "87.107.27.3", + "87.107.28.26", + "87.107.28.27", + "87.107.28.222", + "87.107.29.82", + "87.107.29.83", + "87.107.29.84", + "87.107.29.230", + "87.107.30.149", + "87.107.45.9", + "87.107.49.14", + "87.107.50.36", + "87.107.52.98", + "87.107.52.198", + "87.107.52.202", + "87.107.52.206", + "87.107.52.212", + "87.107.54.18", + "87.107.54.47", + "87.107.54.49", + "87.107.54.76", + "87.107.54.144", + "87.107.54.153", + "87.107.54.156", + "87.107.54.212", + "87.107.54.224", + "87.107.54.243", + "87.107.55.10", + "87.107.55.11", + "87.107.55.55", + "87.107.55.70", + "87.107.55.85", + "87.107.55.101", + "87.107.55.106", + "87.107.55.117", + "87.107.55.178", + "87.107.55.179", + "87.107.55.180", + "87.107.55.182", + "87.107.55.186", + "87.107.55.194", + "87.107.68.36", + "87.107.68.52", + "87.107.68.59", + "87.107.68.62", + "87.107.68.69", + "87.107.68.79", + "87.107.68.92", + "87.107.68.156", + "87.107.68.171", + "87.107.69.3", + "87.107.69.4", + "87.107.69.163", + "87.107.69.169", + "87.107.69.171", + "87.107.69.172", + "87.107.69.179", + "87.107.69.180", + "87.107.73.55", + "87.107.73.61", + "87.107.73.212", + "87.107.73.214", + "87.107.76.91", + "87.107.76.92", + "87.107.82.245", + "87.107.86.240", + "87.107.86.241", + "87.107.87.67", + "87.107.87.68", + "87.107.87.69", + "87.107.91.66", + "87.107.100.215", + "87.107.102.5", + "87.107.102.6", + "87.107.102.7", + "87.107.102.15", + "87.107.102.16", + "87.107.102.18", + "87.107.102.19", + "87.107.102.21", + "87.107.102.23", + "87.107.102.24", + "87.107.102.25", + "87.107.102.27", + "87.107.102.28", + "87.107.102.29", + "87.107.102.30", + "87.107.102.61", + "87.107.102.68", + "87.107.102.90", + "87.107.102.122", + "87.107.102.126", + "87.107.104.14", + "87.107.104.21", + "87.107.104.26", + "87.107.104.33", + "87.107.104.74", + "87.107.104.81", + "87.107.104.86", + "87.107.104.91", + "87.107.104.107", + "87.107.104.117", + "87.107.104.120", + "87.107.104.122", + "87.107.104.124", + "87.107.104.144", + "87.107.104.147", + "87.107.104.211", + "87.107.104.247", + "87.107.104.250", + "87.107.104.252", + "87.107.105.20", + "87.107.105.37", + "87.107.105.51", + "87.107.105.65", + "87.107.105.67", + "87.107.105.72", + "87.107.105.84", + "87.107.105.86", + "87.107.105.97", + "87.107.105.102", + "87.107.105.137", + "87.107.105.139", + "87.107.105.163", + "87.107.105.179", + "87.107.105.181", + "87.107.105.190", + "87.107.105.230", + "87.107.105.234", + "87.107.108.13", + "87.107.108.15", + "87.107.108.28", + "87.107.108.43", + "87.107.108.44", + "87.107.108.61", + "87.107.108.86", + "87.107.108.87", + "87.107.108.90", + "87.107.108.111", + "87.107.108.152", + "87.107.108.158", + "87.107.108.162", + "87.107.108.172", + "87.107.108.212", + "87.107.108.216", + "87.107.109.11", + "87.107.109.12", + "87.107.109.13", + "87.107.109.22", + "87.107.109.34", + "87.107.109.77", + "87.107.109.84", + "87.107.109.105", + "87.107.109.106", + "87.107.109.109", + "87.107.109.110", + "87.107.109.143", + "87.107.109.174", + "87.107.109.254", + "87.107.110.6", + "87.107.110.7", + "87.107.110.8", + "87.107.110.11", + "87.107.110.12", + "87.107.110.13", + "87.107.110.14", + "87.107.110.15", + "87.107.110.20", + "87.107.110.21", + "87.107.110.24", + "87.107.110.27", + "87.107.110.29", + "87.107.110.30", + "87.107.110.109", + "87.107.110.110", + "87.107.110.165", + "87.107.110.196", + "87.107.111.21", + "87.107.111.32", + "87.107.111.37", + "87.107.111.38", + "87.107.111.58", + "87.107.111.104", + "87.107.111.113", + "87.107.111.123", + "87.107.116.4", + "87.107.116.218", + "87.107.121.16", + "87.107.121.24", + "87.107.121.59", + "87.107.121.60", + "87.107.121.61", + "87.107.121.66", + "87.107.121.70", + "87.107.121.84", + "87.107.121.110", + "87.107.121.197", + "87.107.121.227", + "87.107.121.234", + "87.107.124.19", + "87.107.124.100", + "87.107.124.109", + "87.107.124.116", + "87.107.124.118", + "87.107.132.36", + "87.107.132.38", + "87.107.132.50", + "87.107.132.77", + "87.107.133.73", + "87.107.133.81", + "87.107.134.94", + "87.107.139.60", + "87.107.143.24", + "87.107.144.26", + "87.107.144.51", + "87.107.144.53", + "87.107.144.55", + "87.107.144.56", + "87.107.144.57", + "87.107.144.58", + "87.107.144.60", + "87.107.144.61", + "87.107.144.212", + "87.107.144.234", + "87.107.144.246", + "87.107.146.13", + "87.107.146.21", + "87.107.146.41", + "87.107.146.43", + "87.107.146.54", + "87.107.146.70", + "87.107.146.72", + "87.107.146.79", + "87.107.146.83", + "87.107.146.95", + "87.107.146.133", + "87.107.146.157", + "87.107.146.160", + "87.107.146.170", + "87.107.146.171", + "87.107.146.178", + "87.107.146.199", + "87.107.146.205", + "87.107.146.214", + "87.107.146.221", + "87.107.147.34", + "87.107.147.46", + "87.107.147.86", + "87.107.147.106", + "87.107.147.122", + "87.107.147.158", + "87.107.150.10", + "87.107.152.10", + "87.107.152.22", + "87.107.152.23", + "87.107.152.84", + "87.107.152.118", + "87.107.152.133", + "87.107.152.134", + "87.107.152.139", + "87.107.152.150", + "87.107.152.186", + "87.107.153.5", + "87.107.153.10", + "87.107.153.11", + "87.107.153.60", + "87.107.153.84", + "87.107.153.102", + "87.107.153.131", + "87.107.153.254", + "87.107.154.18", + "87.107.154.20", + "87.107.154.21", + "87.107.154.22", + "87.107.154.42", + "87.107.154.43", + "87.107.154.44", + "87.107.154.94", + "87.107.154.107", + "87.107.154.109", + "87.107.154.123", + "87.107.154.140", + "87.107.155.59", + "87.107.155.63", + "87.107.155.70", + "87.107.155.78", + "87.107.155.94", + "87.107.155.95", + "87.107.155.98", + "87.107.155.110", + "87.107.155.119", + "87.107.155.143", + "87.107.155.147", + "87.107.155.157", + "87.107.155.181", + "87.107.155.221", + "87.107.155.230", + "87.107.155.243", + "87.107.159.57", + "87.107.164.10", + "87.107.164.68", + "87.107.164.81", + "87.107.164.85", + "87.107.164.99", + "87.107.164.119", + "87.107.164.120", + "87.107.164.134", + "87.107.164.138", + "87.107.164.142", + "87.107.164.167", + "87.107.164.191", + "87.107.164.223", + "87.107.164.230", + "87.107.164.233", + "87.107.164.234", + "87.107.164.238", + "87.107.164.241", + "87.107.165.4", + "87.107.165.10", + "87.107.165.24", + "87.107.165.66", + "87.107.165.86", + "87.107.165.87", + "87.107.165.95", + "87.107.165.113", + "87.107.165.114", + "87.107.165.119", + "87.107.165.149", + "87.107.165.168", + "87.107.165.175", + "87.107.165.184", + "87.107.165.207", + "87.107.165.217", + "87.107.165.222", + "87.107.165.249", + "87.107.166.10", + "87.107.166.35", + "87.107.166.203", + "87.107.166.213", + "87.107.172.8", + "87.107.172.84", + "87.107.174.19", + "87.107.174.52", + "87.107.174.100", + "87.107.174.182", + "87.107.174.189", + "87.107.175.5", + "87.107.175.7", + "87.107.175.104", + "87.107.175.107", + "87.107.175.162", + "87.107.175.166", + "87.107.175.182", + "87.107.175.218", + "87.107.184.40", + "87.107.184.123", + "87.107.185.33", + "87.107.185.34", + "87.107.185.234", + "87.107.186.4", + "87.107.190.4", + "87.107.190.117", + "87.107.191.49", + "87.236.38.106", + "87.236.38.107", + "87.236.39.40", + "87.236.39.43", + "87.236.39.120", + "87.236.39.133", + "87.236.209.20", + "87.236.209.38", + "87.236.209.77", + "87.236.209.154", + "87.236.209.196", + "87.236.209.199", + "87.236.209.201", + "87.236.209.203", + "87.236.209.207", + "87.236.209.209", + "87.236.209.210", + "87.236.209.211", + "87.236.209.219", + "87.236.209.220", + "87.236.209.222", + "87.236.209.227", + "87.236.209.228", + "87.236.209.235", + "87.236.209.236", + "87.236.210.4", + "87.236.210.5", + "87.236.210.6", + "87.236.210.9", + "87.236.210.11", + "87.236.210.12", + "87.236.210.15", + "87.236.210.18", + "87.236.210.20", + "87.236.210.21", + "87.236.210.22", + "87.236.210.25", + "87.236.210.26", + "87.236.210.27", + "87.236.210.28", + "87.236.210.30", + "87.236.210.32", + "87.236.210.36", + "87.236.210.37", + "87.236.210.38", + "87.236.210.39", + "87.236.210.44", + "87.236.210.45", + "87.236.210.47", + "87.236.210.51", + "87.236.210.52", + "87.236.210.55", + "87.236.210.56", + "87.236.210.58", + "87.236.210.59", + "87.236.210.61", + "87.236.210.66", + "87.236.210.67", + "87.236.210.68", + "87.236.210.71", + "87.236.210.72", + "87.236.210.73", + "87.236.210.74", + "87.236.210.77", + "87.236.210.79", + "87.236.210.81", + "87.236.210.82", + "87.236.210.85", + "87.236.210.86", + "87.236.210.87", + "87.236.210.88", + "87.236.210.89", + "87.236.210.92", + "87.236.210.93", + "87.236.210.99", + "87.236.210.101", + "87.236.210.102", + "87.236.210.106", + "87.236.210.107", + "87.236.210.108", + "87.236.210.109", + "87.236.210.110", + "87.236.210.112", + "87.236.210.114", + "87.236.210.115", + "87.236.210.116", + "87.236.210.117", + "87.236.210.118", + "87.236.210.120", + "87.236.210.122", + "87.236.210.123", + "87.236.210.124", + "87.236.210.127", + "87.236.210.130", + "87.236.210.131", + "87.236.210.132", + "87.236.210.133", + "87.236.210.134", + "87.236.210.136", + "87.236.210.138", + "87.236.210.139", + "87.236.210.140", + "87.236.210.142", + "87.236.210.143", + "87.236.210.144", + "87.236.210.145", + "87.236.210.148", + "87.236.210.149", + "87.236.210.150", + "87.236.210.153", + "87.236.210.154", + "87.236.210.155", + "87.236.210.156", + "87.236.210.158", + "87.236.210.159", + "87.236.210.160", + "87.236.210.161", + "87.236.210.162", + "87.236.210.163", + "87.236.210.164", + "87.236.210.170", + "87.236.210.171", + "87.236.210.173", + "87.236.210.175", + "87.236.210.182", + "87.236.210.183", + "87.236.210.185", + "87.236.210.188", + "87.236.210.189", + "87.236.210.190", + "87.236.210.193", + "87.236.210.195", + "87.236.210.196", + "87.236.210.199", + "87.236.210.202", + "87.236.210.203", + "87.236.210.205", + "87.236.210.207", + "87.236.210.208", + "87.236.210.209", + "87.236.210.212", + "87.236.210.214", + "87.236.210.215", + "87.236.210.216", + "87.236.210.217", + "87.236.210.218", + "87.236.210.221", + "87.236.210.224", + "87.236.210.225", + "87.236.210.228", + "87.236.211.3", + "87.236.211.8", + "87.236.211.10", + "87.236.211.13", + "87.236.211.14", + "87.236.211.16", + "87.236.211.21", + "87.236.211.22", + "87.236.211.23", + "87.236.211.24", + "87.236.211.28", + "87.236.211.29", + "87.236.211.67", + "87.236.211.69", + "87.236.211.72", + "87.236.211.74", + "87.236.211.81", + "87.236.211.82", + "87.236.211.85", + "87.236.211.87", + "87.236.211.88", + "87.236.211.89", + "87.236.211.90", + "87.236.211.91", + "87.236.211.93", + "87.236.211.96", + "87.236.211.98", + "87.236.211.99", + "87.236.211.100", + "87.236.211.101", + "87.236.211.104", + "87.236.211.106", + "87.236.211.107", + "87.236.211.108", + "87.236.211.109", + "87.236.211.110", + "87.236.211.111", + "87.236.211.113", + "87.236.211.114", + "87.236.211.115", + "87.236.211.116", + "87.236.211.117", + "87.236.211.118", + "87.236.211.123", + "87.236.211.126", + "87.236.211.210", + "87.236.211.211", + "87.236.211.212", + "87.236.211.213", + "87.236.211.214", + "87.236.211.215", + "87.236.211.218", + "87.236.211.219", + "87.236.211.221", + "87.236.211.226", + "87.236.211.227", + "87.236.211.228", + "87.236.211.229", + "87.236.211.231", + "87.236.211.232", + "87.236.211.234", + "87.236.211.237", + "87.236.212.68", + "87.236.212.126", + "87.236.212.162", + "87.236.212.195", + "87.236.213.139", + "87.236.215.42", + "87.236.215.46", + "87.236.215.106", + "87.236.215.166", + "87.236.215.246", + "87.247.171.247", + "87.247.174.180", + "87.247.175.72", + "87.247.175.212", + "87.247.176.22", + "87.247.176.46", + "87.247.176.49", + "87.247.176.50", + "87.247.176.54", + "87.247.176.55", + "87.247.176.56", + "87.247.176.64", + "87.247.176.67", + "87.247.176.241", + "87.247.177.4", + "87.247.177.6", + "87.247.177.22", + "87.247.177.23", + "87.247.177.26", + "87.247.177.28", + "87.247.177.31", + "87.247.177.62", + "87.247.177.68", + "87.247.177.69", + "87.247.177.70", + "87.247.177.71", + "87.247.177.72", + "87.247.177.73", + "87.247.177.74", + "87.247.177.80", + "87.247.177.109", + "87.247.177.131", + "87.247.177.241", + "87.247.177.242", + "87.247.185.75", + "87.247.185.123", + "87.247.186.129", + "87.247.186.200", + "87.247.188.212", + "87.248.128.10", + "87.248.128.11", + "87.248.128.20", + "87.248.128.23", + "87.248.128.26", + "87.248.128.128", + "87.248.128.207", + "87.248.130.71", + "87.248.130.80", + "87.248.130.97", + "87.248.130.122", + "87.248.130.146", + "87.248.130.151", + "87.248.130.166", + "87.248.130.192", + "87.248.130.206", + "87.248.130.212", + "87.248.130.244", + "87.248.133.90", + "87.248.133.115", + "87.248.133.139", + "87.248.137.2", + "87.248.137.11", + "87.248.137.75", + "87.248.137.170", + "87.248.138.93", + "87.248.138.114", + "87.248.138.170", + "87.248.138.210", + "87.248.138.218", + "87.248.138.246", + "87.248.139.45", + "87.248.139.70", + "87.248.139.74", + "87.248.139.92", + "87.248.139.98", + "87.248.139.179", + "87.248.139.197", + "87.248.139.200", + "87.248.139.201", + "87.248.139.216", + "87.248.139.252", + "87.248.145.5", + "87.248.145.81", + "87.248.145.90", + "87.248.145.121", + "87.248.145.164", + "87.248.145.179", + "87.248.145.201", + "87.248.145.207", + "87.248.145.208", + "87.248.145.246", + "87.248.147.224", + "87.248.147.227", + "87.248.147.228", + "87.248.147.233", + "87.248.147.239", + "87.248.147.248", + "87.248.147.251", + "87.248.147.252", + "87.248.150.4", + "87.248.150.9", + "87.248.150.40", + "87.248.150.41", + "87.248.150.52", + "87.248.150.138", + "87.248.150.142", + "87.248.150.170", + "87.248.150.171", + "87.248.150.172", + "87.248.150.175", + "87.248.150.176", + "87.248.150.204", + "87.248.150.208", + "87.248.150.222", + "87.248.150.250", + "87.248.151.100", + "87.248.151.102", + "87.248.151.114", + "87.248.151.115", + "87.248.151.116", + "87.248.151.118", + "87.248.151.122", + "87.248.151.123", + "87.248.151.125", + "87.248.151.126", + "87.248.151.183", + "87.248.151.194", + "87.248.152.10", + "87.248.152.16", + "87.248.152.29", + "87.248.152.40", + "87.248.152.53", + "87.248.152.79", + "87.248.152.88", + "87.248.152.102", + "87.248.152.124", + "87.248.152.125", + "87.248.152.138", + "87.248.152.140", + "87.248.152.149", + "87.248.152.161", + "87.248.152.210", + "87.248.152.223", + "87.248.152.240", + "87.248.152.247", + "87.248.153.24", + "87.248.153.38", + "87.248.153.43", + "87.248.153.52", + "87.248.153.62", + "87.248.153.64", + "87.248.153.83", + "87.248.153.86", + "87.248.153.87", + "87.248.153.88", + "87.248.153.90", + "87.248.153.93", + "87.248.153.95", + "87.248.153.96", + "87.248.153.101", + "87.248.153.109", + "87.248.153.133", + "87.248.153.141", + "87.248.153.142", + "87.248.153.147", + "87.248.153.167", + "87.248.153.192", + "87.248.153.217", + "87.248.153.220", + "87.248.153.244", + "87.248.153.252", + "87.248.153.253", + "87.248.155.12", + "87.248.155.27", + "87.248.155.38", + "87.248.155.44", + "87.248.155.56", + "87.248.155.113", + "87.248.155.122", + "87.248.155.129", + "87.248.155.167", + "87.248.155.175", + "87.248.155.179", + "87.248.155.203", + "87.248.155.206", + "87.248.155.216", + "87.248.155.222", + "87.248.155.227", + "87.248.155.237", + "87.248.156.14", + "87.248.156.15", + "87.248.156.17", + "87.248.156.31", + "87.248.156.41", + "87.248.156.75", + "87.248.156.91", + "87.248.156.98", + "87.248.156.108", + "87.248.156.113", + "87.248.156.127", + "87.248.156.128", + "87.248.156.129", + "87.248.156.140", + "87.248.156.150", + "87.248.156.162", + "87.248.156.183", + "87.248.156.191", + "87.248.156.204", + "87.248.156.239", + "87.248.156.242", + "88.135.36.35", + "88.135.36.39", + "88.135.36.65", + "88.135.37.47", + "88.135.37.70", + "88.135.37.85", + "88.135.38.19", + "88.135.38.54", + "88.135.39.28", + "88.135.68.1", + "88.135.68.3", + "88.135.68.5", + "88.135.68.6", + "88.135.68.7", + "88.135.68.9", + "88.135.68.10", + "88.135.68.11", + "88.135.68.12", + "88.135.68.13", + "88.135.68.16", + "88.135.68.17", + "88.135.68.25", + "88.135.68.26", + "88.135.68.27", + "88.135.68.28", + "88.135.68.29", + "88.135.68.30", + "88.135.68.98", + "88.135.68.99", + "88.135.68.100", + "88.135.68.247", + "88.135.68.248", + "88.135.68.249", + "88.135.75.22", + "88.135.75.23", + "88.135.75.31", + "88.135.75.132", + "88.135.75.133", + "88.135.75.134", + "88.135.75.135", + "88.135.75.138", + "88.135.75.141", + "88.135.75.142", + "88.135.75.146", + "88.135.75.150", + "88.135.75.151", + "88.135.75.152", + "88.135.75.153", + "88.135.75.155", + "88.135.75.156", + "88.135.75.157", + "88.135.75.178", + "88.135.75.179", + "88.135.75.181", + "88.135.75.194", + "88.135.75.202", + "88.135.75.206", + "88.218.16.122", + "89.32.197.22", + "89.32.197.226", + "89.32.248.3", + "89.32.248.5", + "89.32.248.6", + "89.32.248.7", + "89.32.248.8", + "89.32.248.10", + "89.32.248.11", + "89.32.248.12", + "89.32.248.13", + "89.32.248.14", + "89.32.248.16", + "89.32.248.18", + "89.32.248.19", + "89.32.248.20", + "89.32.248.23", + "89.32.248.24", + "89.32.248.25", + "89.32.248.26", + "89.32.248.27", + "89.32.248.28", + "89.32.248.35", + "89.32.248.37", + "89.32.248.38", + "89.32.248.41", + "89.32.248.49", + "89.32.248.50", + "89.32.248.51", + "89.32.248.54", + "89.32.248.60", + "89.32.248.66", + "89.32.248.67", + "89.32.248.68", + "89.32.248.69", + "89.32.248.70", + "89.32.248.74", + "89.32.248.75", + "89.32.248.76", + "89.32.248.77", + "89.32.248.83", + "89.32.248.84", + "89.32.248.86", + "89.32.248.88", + "89.32.248.89", + "89.32.248.90", + "89.32.248.91", + "89.32.248.93", + "89.32.248.94", + "89.32.248.131", + "89.32.248.138", + "89.32.248.139", + "89.32.248.140", + "89.32.248.142", + "89.32.248.158", + "89.32.248.164", + "89.32.248.170", + "89.32.248.190", + "89.32.248.193", + "89.32.248.195", + "89.32.248.196", + "89.32.248.197", + "89.32.248.198", + "89.32.248.205", + "89.32.248.211", + "89.32.248.212", + "89.32.248.213", + "89.32.248.216", + "89.32.248.219", + "89.32.248.231", + "89.32.248.233", + "89.32.248.235", + "89.32.248.236", + "89.32.248.238", + "89.32.248.245", + "89.32.248.246", + "89.32.248.247", + "89.32.248.251", + "89.32.248.252", + "89.32.249.3", + "89.32.249.10", + "89.32.249.14", + "89.32.249.15", + "89.32.249.27", + "89.32.249.29", + "89.32.249.30", + "89.32.249.66", + "89.32.249.67", + "89.32.249.69", + "89.32.249.72", + "89.32.249.73", + "89.32.249.75", + "89.32.249.76", + "89.32.249.77", + "89.32.249.78", + "89.32.249.84", + "89.32.249.87", + "89.32.249.88", + "89.32.249.101", + "89.32.249.103", + "89.32.249.106", + "89.32.249.117", + "89.32.249.118", + "89.32.249.119", + "89.32.249.120", + "89.32.249.121", + "89.32.249.124", + "89.32.249.125", + "89.32.249.130", + "89.32.249.146", + "89.32.249.147", + "89.32.249.148", + "89.32.249.150", + "89.32.249.154", + "89.32.249.155", + "89.32.249.156", + "89.32.249.158", + "89.32.249.166", + "89.32.249.167", + "89.32.249.168", + "89.32.249.169", + "89.32.249.171", + "89.32.249.172", + "89.32.249.178", + "89.32.249.187", + "89.32.249.194", + "89.32.249.202", + "89.32.249.215", + "89.32.249.232", + "89.32.249.238", + "89.32.249.241", + "89.32.249.242", + "89.32.249.245", + "89.32.249.246", + "89.32.249.247", + "89.32.249.253", + "89.32.251.2", + "89.32.251.3", + "89.32.251.4", + "89.32.251.5", + "89.32.251.6", + "89.32.251.7", + "89.32.251.9", + "89.32.251.10", + "89.32.251.11", + "89.32.251.14", + "89.32.251.70", + "89.32.251.71", + "89.32.251.73", + "89.32.251.79", + "89.32.251.80", + "89.32.251.84", + "89.32.251.85", + "89.32.251.90", + "89.32.251.94", + "89.32.251.96", + "89.32.251.102", + "89.32.251.103", + "89.32.251.104", + "89.32.251.105", + "89.32.251.110", + "89.32.251.112", + "89.32.251.115", + "89.32.251.122", + "89.32.251.124", + "89.32.251.125", + "89.32.251.132", + "89.32.251.167", + "89.33.129.171", + "89.33.241.44", + "89.34.176.99", + "89.35.133.178", + "89.35.176.20", + "89.35.176.24", + "89.37.0.211", + "89.37.5.189", + "89.37.6.162", + "89.37.12.76", + "89.37.13.115", + "89.37.15.116", + "89.37.219.155", + "89.37.219.186", + "89.37.219.187", + "89.37.250.122", + "89.39.208.11", + "89.39.208.13", + "89.39.208.14", + "89.39.208.18", + "89.39.208.19", + "89.39.208.20", + "89.39.208.22", + "89.39.208.28", + "89.39.208.30", + "89.39.208.34", + "89.39.208.35", + "89.39.208.36", + "89.39.208.38", + "89.39.208.41", + "89.39.208.42", + "89.39.208.44", + "89.39.208.45", + "89.39.208.48", + "89.39.208.49", + "89.39.208.50", + "89.39.208.55", + "89.39.208.57", + "89.39.208.62", + "89.39.208.66", + "89.39.208.67", + "89.39.208.68", + "89.39.208.70", + "89.39.208.84", + "89.39.208.86", + "89.39.208.87", + "89.39.208.88", + "89.39.208.89", + "89.39.208.90", + "89.39.208.91", + "89.39.208.93", + "89.39.208.94", + "89.39.208.98", + "89.39.208.99", + "89.39.208.100", + "89.39.208.101", + "89.39.208.102", + "89.39.208.110", + "89.39.208.119", + "89.39.208.130", + "89.39.208.131", + "89.39.208.132", + "89.39.208.134", + "89.39.208.135", + "89.39.208.136", + "89.39.208.137", + "89.39.208.146", + "89.39.208.147", + "89.39.208.148", + "89.39.208.152", + "89.39.208.153", + "89.39.208.154", + "89.39.208.155", + "89.39.208.162", + "89.39.208.163", + "89.39.208.164", + "89.39.208.165", + "89.39.208.170", + "89.39.208.173", + "89.39.208.178", + "89.39.208.179", + "89.39.208.180", + "89.39.208.181", + "89.39.208.184", + "89.39.208.185", + "89.39.208.186", + "89.39.208.187", + "89.39.208.188", + "89.39.208.190", + "89.39.208.195", + "89.39.208.196", + "89.39.208.197", + "89.39.208.198", + "89.39.208.199", + "89.39.208.200", + "89.39.208.202", + "89.39.208.203", + "89.39.208.204", + "89.39.208.205", + "89.39.208.206", + "89.39.208.209", + "89.39.208.210", + "89.39.208.212", + "89.39.208.213", + "89.39.208.216", + "89.39.208.218", + "89.39.208.219", + "89.39.208.240", + "89.39.208.241", + "89.39.208.242", + "89.39.208.246", + "89.39.208.247", + "89.39.208.250", + "89.39.208.251", + "89.40.78.156", + "89.40.246.40", + "89.40.247.81", + "89.42.136.6", + "89.42.136.67", + "89.42.136.94", + "89.42.137.119", + "89.42.137.122", + "89.42.137.195", + "89.42.138.21", + "89.42.138.66", + "89.42.138.71", + "89.42.138.74", + "89.42.138.98", + "89.42.138.124", + "89.42.138.212", + "89.42.138.243", + "89.42.138.244", + "89.42.138.245", + "89.42.138.246", + "89.42.138.247", + "89.42.138.248", + "89.42.138.249", + "89.42.138.250", + "89.42.138.251", + "89.42.138.252", + "89.42.138.253", + "89.42.138.254", + "89.42.139.142", + "89.42.139.180", + "89.42.139.181", + "89.42.139.182", + "89.42.139.187", + "89.42.197.254", + "89.42.199.49", + "89.42.199.137", + "89.42.208.14", + "89.42.208.15", + "89.42.208.16", + "89.42.208.17", + "89.42.208.21", + "89.42.208.25", + "89.42.208.26", + "89.42.208.27", + "89.42.208.28", + "89.42.208.29", + "89.42.208.33", + "89.42.208.34", + "89.42.208.40", + "89.42.208.46", + "89.42.208.51", + "89.42.208.53", + "89.42.208.66", + "89.42.208.99", + "89.42.208.101", + "89.42.208.105", + "89.42.208.109", + "89.42.208.111", + "89.42.208.119", + "89.42.208.121", + "89.42.208.124", + "89.42.208.139", + "89.42.208.146", + "89.42.208.147", + "89.42.208.150", + "89.42.208.155", + "89.42.208.156", + "89.42.208.157", + "89.42.208.179", + "89.42.208.180", + "89.42.208.181", + "89.42.208.182", + "89.42.208.195", + "89.42.208.196", + "89.42.208.202", + "89.42.208.203", + "89.42.208.210", + "89.42.208.213", + "89.42.208.214", + "89.42.208.219", + "89.42.208.246", + "89.42.208.249", + "89.42.209.4", + "89.42.209.13", + "89.42.209.16", + "89.42.209.17", + "89.42.209.19", + "89.42.209.23", + "89.42.209.26", + "89.42.209.35", + "89.42.209.40", + "89.42.209.43", + "89.42.209.45", + "89.42.209.48", + "89.42.209.49", + "89.42.209.58", + "89.42.209.68", + "89.42.209.69", + "89.42.209.70", + "89.42.209.71", + "89.42.209.72", + "89.42.209.73", + "89.42.209.74", + "89.42.209.76", + "89.42.209.77", + "89.42.209.79", + "89.42.209.80", + "89.42.209.81", + "89.42.209.82", + "89.42.209.87", + "89.42.209.108", + "89.42.209.110", + "89.42.209.167", + "89.42.209.190", + "89.42.209.194", + "89.42.209.196", + "89.42.209.197", + "89.42.209.198", + "89.42.209.203", + "89.42.209.204", + "89.42.209.205", + "89.42.209.206", + "89.42.209.226", + "89.42.209.227", + "89.42.209.229", + "89.42.209.230", + "89.42.209.234", + "89.42.209.235", + "89.42.209.236", + "89.42.209.237", + "89.42.209.238", + "89.42.209.242", + "89.42.209.244", + "89.42.209.246", + "89.42.210.8", + "89.42.210.16", + "89.42.210.30", + "89.42.210.36", + "89.42.210.39", + "89.42.210.45", + "89.42.210.47", + "89.42.210.48", + "89.42.210.49", + "89.42.210.51", + "89.42.210.52", + "89.42.210.54", + "89.42.210.57", + "89.42.210.64", + "89.42.210.74", + "89.42.210.83", + "89.42.210.113", + "89.42.210.115", + "89.42.210.116", + "89.42.210.118", + "89.42.210.119", + "89.42.210.122", + "89.42.210.123", + "89.42.210.146", + "89.42.210.162", + "89.42.210.165", + "89.42.210.166", + "89.42.210.168", + "89.42.210.169", + "89.42.210.171", + "89.42.210.176", + "89.42.210.177", + "89.42.210.179", + "89.42.210.180", + "89.42.210.181", + "89.42.210.182", + "89.42.210.183", + "89.42.210.186", + "89.42.210.187", + "89.42.210.188", + "89.42.210.189", + "89.42.210.200", + "89.42.210.201", + "89.42.210.222", + "89.42.210.227", + "89.42.210.228", + "89.42.210.229", + "89.42.210.230", + "89.42.210.234", + "89.42.210.236", + "89.42.210.243", + "89.42.210.244", + "89.42.210.245", + "89.42.210.246", + "89.42.210.250", + "89.42.210.251", + "89.42.210.254", + "89.42.211.2", + "89.42.211.3", + "89.42.211.10", + "89.42.211.13", + "89.42.211.14", + "89.42.211.18", + "89.42.211.22", + "89.42.211.23", + "89.42.211.44", + "89.42.211.47", + "89.42.211.48", + "89.42.211.50", + "89.42.211.52", + "89.42.211.55", + "89.42.211.67", + "89.42.211.70", + "89.42.211.71", + "89.42.211.72", + "89.42.211.74", + "89.42.211.77", + "89.42.211.78", + "89.42.211.80", + "89.42.211.81", + "89.42.211.82", + "89.42.211.85", + "89.42.211.87", + "89.42.211.93", + "89.42.211.98", + "89.42.211.99", + "89.42.211.101", + "89.42.211.102", + "89.42.211.108", + "89.42.211.109", + "89.42.211.110", + "89.42.211.114", + "89.42.211.162", + "89.42.211.163", + "89.42.211.164", + "89.42.211.165", + "89.42.211.166", + "89.42.211.170", + "89.42.211.171", + "89.42.211.172", + "89.42.211.173", + "89.42.211.174", + "89.42.211.220", + "89.42.211.222", + "89.42.211.226", + "89.42.211.227", + "89.42.211.228", + "89.42.211.236", + "89.43.0.33", + "89.43.0.232", + "89.43.4.178", + "89.43.5.218", + "89.43.5.219", + "89.43.7.222", + "89.43.10.43", + "89.43.10.44", + "89.43.224.135", + "89.43.226.192", + "89.43.231.91", + "89.43.231.203", + "89.44.112.5", + "89.44.118.10", + "89.44.134.50", + "89.44.182.229", + "89.45.89.3", + "89.45.89.8", + "89.45.89.9", + "89.46.60.176", + "89.46.61.56", + "89.46.216.6", + "89.46.216.7", + "89.46.216.20", + "89.46.216.21", + "89.46.216.22", + "89.46.216.23", + "89.46.216.25", + "89.46.216.26", + "89.46.216.28", + "89.46.216.29", + "89.46.216.30", + "89.46.216.31", + "89.46.216.112", + "89.46.216.113", + "89.46.216.114", + "89.46.216.115", + "89.46.216.116", + "89.46.216.117", + "89.46.216.118", + "89.46.216.119", + "89.46.216.200", + "89.46.216.201", + "89.46.216.202", + "89.46.216.203", + "89.46.216.204", + "89.46.216.205", + "89.46.216.206", + "89.46.216.207", + "89.46.218.12", + "89.46.218.13", + "89.46.218.14", + "89.46.218.15", + "89.46.218.24", + "89.46.218.50", + "89.46.218.56", + "89.46.218.57", + "89.46.218.58", + "89.46.218.59", + "89.46.218.60", + "89.46.218.61", + "89.46.218.62", + "89.46.218.63", + "89.46.218.64", + "89.46.218.65", + "89.46.218.66", + "89.46.218.67", + "89.46.218.68", + "89.46.218.70", + "89.46.218.71", + "89.46.218.72", + "89.46.218.73", + "89.46.218.74", + "89.46.218.75", + "89.46.218.76", + "89.46.218.77", + "89.46.218.78", + "89.46.218.79", + "89.46.218.80", + "89.46.218.81", + "89.46.218.82", + "89.46.218.83", + "89.46.218.84", + "89.46.218.85", + "89.46.218.86", + "89.46.218.87", + "89.46.218.88", + "89.46.218.89", + "89.46.218.90", + "89.46.218.91", + "89.46.218.92", + "89.46.218.93", + "89.46.218.94", + "89.46.218.95", + "89.46.218.96", + "89.46.218.97", + "89.46.218.98", + "89.46.218.99", + "89.46.218.100", + "89.46.218.101", + "89.46.218.102", + "89.46.218.103", + "89.46.218.104", + "89.46.218.105", + "89.46.218.106", + "89.46.218.107", + "89.46.218.108", + "89.46.218.110", + "89.46.218.111", + "89.46.218.112", + "89.46.218.113", + "89.46.218.114", + "89.46.218.115", + "89.46.218.116", + "89.46.218.117", + "89.46.218.118", + "89.46.218.119", + "89.46.218.120", + "89.46.218.121", + "89.46.218.122", + "89.46.218.123", + "89.46.218.124", + "89.46.218.125", + "89.46.218.126", + "89.46.218.128", + "89.46.218.129", + "89.46.218.130", + "89.46.218.131", + "89.46.218.132", + "89.46.219.16", + "89.46.219.18", + "89.46.219.64", + "89.46.219.65", + "89.46.219.66", + "89.46.219.67", + "89.46.219.68", + "89.46.219.69", + "89.46.219.70", + "89.46.219.71", + "89.46.219.80", + "89.46.219.81", + "89.46.219.83", + "89.46.219.84", + "89.46.219.85", + "89.46.219.86", + "89.46.219.87", + "89.46.219.88", + "89.46.219.89", + "89.46.219.90", + "89.46.219.91", + "89.46.219.92", + "89.46.219.93", + "89.46.219.94", + "89.46.219.95", + "89.46.219.104", + "89.46.219.105", + "89.46.219.106", + "89.46.219.107", + "89.46.219.108", + "89.46.219.109", + "89.46.219.110", + "89.46.219.111", + "89.46.219.112", + "89.46.219.113", + "89.46.219.114", + "89.46.219.115", + "89.46.219.116", + "89.46.219.117", + "89.46.219.118", + "89.46.219.119", + "89.46.219.176", + "89.46.219.177", + "89.46.219.178", + "89.46.219.179", + "89.46.219.180", + "89.46.219.181", + "89.46.219.182", + "89.46.219.183", + "89.46.219.192", + "89.46.219.193", + "89.46.219.194", + "89.46.219.195", + "89.46.219.196", + "89.46.219.197", + "89.46.219.198", + "89.46.219.220", + "89.46.219.221", + "89.46.219.222", + "89.46.219.223", + "89.144.128.21", + "89.144.129.18", + "89.144.129.234", + "89.144.131.136", + "89.144.131.137", + "89.144.131.139", + "89.144.131.140", + "89.144.131.141", + "89.144.131.142", + "89.144.131.143", + "89.144.132.168", + "89.144.132.169", + "89.144.132.170", + "89.144.132.171", + "89.144.132.172", + "89.144.132.174", + "89.144.132.175", + "89.144.132.184", + "89.144.132.185", + "89.144.132.186", + "89.144.132.187", + "89.144.132.188", + "89.144.132.190", + "89.144.132.191", + "89.144.132.193", + "89.144.132.194", + "89.144.137.192", + "89.144.137.193", + "89.144.137.194", + "89.144.137.195", + "89.144.137.196", + "89.144.137.198", + "89.144.137.201", + "89.144.137.202", + "89.144.137.203", + "89.144.137.205", + "89.144.137.206", + "89.144.137.209", + "89.144.137.211", + "89.144.137.213", + "89.144.137.214", + "89.144.137.216", + "89.144.137.218", + "89.144.137.219", + "89.144.137.221", + "89.144.137.222", + "89.144.137.224", + "89.144.137.225", + "89.144.137.226", + "89.144.137.227", + "89.144.137.228", + "89.144.137.230", + "89.144.137.231", + "89.144.137.232", + "89.144.137.235", + "89.144.137.239", + "89.144.137.241", + "89.144.137.242", + "89.144.137.243", + "89.144.137.245", + "89.144.137.246", + "89.144.137.248", + "89.144.137.249", + "89.144.137.250", + "89.144.137.252", + "89.144.138.138", + "89.144.138.153", + "89.144.138.154", + "89.144.138.157", + "89.144.138.158", + "89.144.138.159", + "89.144.139.0", + "89.144.139.2", + "89.144.139.3", + "89.144.139.5", + "89.144.139.6", + "89.144.139.8", + "89.144.139.9", + "89.144.139.10", + "89.144.139.12", + "89.144.139.13", + "89.144.139.15", + "89.144.139.17", + "89.144.139.18", + "89.144.139.20", + "89.144.139.21", + "89.144.139.22", + "89.144.139.23", + "89.144.139.24", + "89.144.139.26", + "89.144.139.27", + "89.144.139.28", + "89.144.139.29", + "89.144.139.30", + "89.144.139.31", + "89.144.139.32", + "89.144.139.33", + "89.144.139.34", + "89.144.139.35", + "89.144.139.37", + "89.144.139.38", + "89.144.139.39", + "89.144.139.40", + "89.144.139.41", + "89.144.139.42", + "89.144.139.43", + "89.144.139.44", + "89.144.139.45", + "89.144.139.46", + "89.144.139.47", + "89.144.139.48", + "89.144.139.49", + "89.144.139.50", + "89.144.139.51", + "89.144.139.52", + "89.144.139.53", + "89.144.139.54", + "89.144.139.55", + "89.144.139.56", + "89.144.139.57", + "89.144.139.59", + "89.144.139.62", + "89.144.139.63", + "89.144.139.64", + "89.144.139.65", + "89.144.139.66", + "89.144.139.67", + "89.144.139.68", + "89.144.139.70", + "89.144.139.71", + "89.144.139.72", + "89.144.139.73", + "89.144.139.74", + "89.144.139.75", + "89.144.139.76", + "89.144.139.77", + "89.144.139.78", + "89.144.139.79", + "89.144.139.82", + "89.144.139.84", + "89.144.139.85", + "89.144.139.87", + "89.144.139.88", + "89.144.139.89", + "89.144.139.90", + "89.144.139.91", + "89.144.139.92", + "89.144.139.93", + "89.144.139.95", + "89.144.139.96", + "89.144.139.97", + "89.144.139.102", + "89.144.139.103", + "89.144.139.104", + "89.144.139.105", + "89.144.139.106", + "89.144.139.108", + "89.144.139.110", + "89.144.139.112", + "89.144.139.114", + "89.144.139.115", + "89.144.139.116", + "89.144.139.117", + "89.144.139.118", + "89.144.139.121", + "89.144.139.122", + "89.144.139.123", + "89.144.139.124", + "89.144.139.125", + "89.144.139.126", + "89.144.139.127", + "89.144.140.34", + "89.144.141.159", + "89.144.141.220", + "89.144.143.130", + "89.144.144.118", + "89.144.144.144", + "89.144.145.210", + "89.144.145.212", + "89.144.145.213", + "89.144.145.214", + "89.144.145.215", + "89.144.151.22", + "89.144.151.251", + "89.144.151.252", + "89.144.154.98", + "89.144.156.249", + "89.144.166.144", + "89.144.166.145", + "89.144.166.146", + "89.144.166.147", + "89.144.166.148", + "89.144.166.149", + "89.144.166.150", + "89.144.166.151", + "89.144.166.152", + "89.144.166.153", + "89.144.166.154", + "89.144.166.155", + "89.144.166.156", + "89.144.166.157", + "89.144.166.158", + "89.144.166.159", + "89.144.166.160", + "89.144.166.166", + "89.144.166.167", + "89.144.166.168", + "89.144.166.169", + "89.144.166.170", + "89.144.166.171", + "89.144.166.173", + "89.144.166.174", + "89.144.166.175", + "89.144.166.176", + "89.144.166.177", + "89.144.166.178", + "89.144.166.179", + "89.144.166.180", + "89.144.166.181", + "89.144.166.182", + "89.144.166.183", + "89.144.166.184", + "89.144.166.185", + "89.144.166.186", + "89.144.166.187", + "89.144.166.189", + "89.144.166.190", + "89.144.166.192", + "89.144.166.194", + "89.144.166.195", + "89.144.166.196", + "89.144.166.197", + "89.144.166.198", + "89.144.166.199", + "89.144.166.200", + "89.144.166.201", + "89.144.166.202", + "89.144.166.203", + "89.144.166.204", + "89.144.166.205", + "89.144.166.206", + "89.144.166.207", + "89.144.166.208", + "89.144.166.209", + "89.144.166.210", + "89.144.166.211", + "89.144.166.212", + "89.144.166.213", + "89.144.166.214", + "89.144.166.215", + "89.144.166.216", + "89.144.166.218", + "89.144.166.219", + "89.144.166.220", + "89.144.166.221", + "89.144.166.222", + "89.144.166.223", + "89.144.166.224", + "89.144.166.225", + "89.144.166.226", + "89.144.166.227", + "89.144.166.228", + "89.144.166.229", + "89.144.166.230", + "89.144.166.231", + "89.144.166.240", + "89.144.166.241", + "89.144.166.243", + "89.144.166.244", + "89.144.166.245", + "89.144.166.246", + "89.144.166.247", + "89.144.166.248", + "89.144.166.249", + "89.144.166.250", + "89.144.166.251", + "89.144.166.252", + "89.144.166.253", + "89.144.166.254", + "89.144.166.255", + "89.144.176.190", + "89.144.182.3", + "89.144.183.0", + "89.144.183.1", + "89.144.183.2", + "89.144.183.3", + "89.144.183.4", + "89.144.183.5", + "89.144.183.6", + "89.144.183.7", + "89.144.183.16", + "89.144.183.17", + "89.144.183.18", + "89.144.183.19", + "89.144.183.22", + "89.144.183.23", + "89.144.183.42", + "89.144.187.66", + "89.144.187.67", + "89.144.187.68", + "89.144.188.87", + "89.144.189.112", + "89.144.189.115", + "89.144.189.118", + "89.144.189.119", + "89.144.189.121", + "89.144.189.122", + "89.144.189.123", + "89.144.189.125", + "89.144.189.126", + "89.144.189.127", + "89.144.189.128", + "89.144.189.130", + "89.144.189.131", + "89.144.189.134", + "89.144.189.135", + "89.144.189.139", + "89.144.189.141", + "89.144.189.143", + "89.144.189.144", + "89.144.189.145", + "89.144.189.146", + "89.144.189.148", + "89.144.189.149", + "89.144.189.150", + "89.144.189.158", + "89.144.189.163", + "89.144.189.167", + "89.144.189.169", + "89.144.189.171", + "89.144.189.173", + "89.144.189.175", + "89.144.189.178", + "89.144.189.179", + "89.144.189.180", + "89.144.189.182", + "89.144.189.183", + "89.144.189.184", + "89.144.189.187", + "89.144.189.188", + "89.144.189.190", + "89.144.189.193", + "89.144.189.195", + "89.144.189.199", + "89.144.189.200", + "89.144.189.201", + "89.144.189.203", + "89.144.189.205", + "89.144.189.206", + "89.144.189.210", + "89.144.189.212", + "89.144.189.215", + "89.144.189.216", + "89.144.189.217", + "89.144.189.218", + "89.144.189.220", + "89.144.189.221", + "89.144.189.222", + "89.144.189.224", + "89.144.189.225", + "89.144.189.226", + "89.144.189.229", + "89.144.189.231", + "89.144.189.233", + "89.144.189.235", + "89.144.189.238", + "89.144.189.242", + "89.144.189.243", + "89.144.189.244", + "89.144.189.245", + "89.144.189.246", + "89.144.189.247", + "89.144.189.249", + "89.144.189.250", + "89.144.189.251", + "89.144.189.253", + "89.144.189.254", + "89.144.189.255", + "89.165.0.13", + "89.165.0.14", + "89.165.1.212", + "89.165.1.232", + "89.165.56.19", + "89.219.64.58", + "89.219.64.157", + "89.219.65.151", + "89.219.66.83", + "89.219.66.163", + "89.219.79.161", + "89.219.81.18", + "89.219.83.86", + "89.219.83.227", + "89.219.84.161", + "89.219.84.162", + "89.219.89.61", + "89.219.92.21", + "89.219.94.12", + "89.219.96.28", + "89.219.112.149", + "89.219.192.22", + "89.219.200.102", + "89.219.216.206", + "89.219.219.255", + "89.219.220.231", + "89.219.233.8", + "89.219.237.50", + "89.219.237.81", + "89.219.237.239", + "89.219.239.108", + "89.219.252.233", + "89.221.80.9", + "89.221.80.10", + "89.221.81.31", + "89.221.81.140", + "89.221.83.12", + "89.221.83.20", + "89.235.64.65", + "89.235.64.66", + "89.235.66.5", + "89.235.66.6", + "89.235.67.4", + "89.235.67.5", + "89.235.68.2", + "89.235.68.5", + "89.235.69.2", + "89.235.79.4", + "89.235.79.5", + "89.235.79.6", + "89.235.79.8", + "89.235.79.9", + "89.235.79.13", + "89.235.79.15", + "89.235.79.16", + "89.235.79.19", + "89.235.79.20", + "89.235.79.21", + "89.235.79.22", + "89.235.79.23", + "89.235.79.24", + "89.235.79.26", + "89.235.79.27", + "89.235.79.29", + "89.235.79.32", + "89.235.79.35", + "89.235.79.39", + "89.235.79.41", + "89.235.79.43", + "89.235.79.44", + "89.235.79.45", + "89.235.79.46", + "89.235.79.47", + "89.235.79.48", + "89.235.79.51", + "89.235.79.52", + "89.235.79.54", + "89.235.79.55", + "89.235.79.56", + "89.235.79.57", + "89.235.79.58", + "89.235.79.59", + "89.235.79.60", + "89.235.79.67", + "89.235.79.68", + "89.235.79.110", + "89.235.116.201", + "89.235.116.202", + "89.235.116.203", + "89.235.116.211", + "89.235.116.216", + "91.92.114.189", + "91.92.121.183", + "91.92.121.200", + "91.92.125.90", + "91.92.126.55", + "91.92.127.236", + "91.92.130.216", + "91.92.132.42", + "91.92.158.85", + "91.92.158.146", + "91.92.158.186", + "91.92.159.116", + "91.92.180.77", + "91.92.180.198", + "91.92.182.182", + "91.92.186.205", + "91.92.187.146", + "91.92.188.192", + "91.92.190.5", + "91.92.190.17", + "91.92.191.165", + "91.92.204.208", + "91.92.204.220", + "91.92.209.6", + "91.92.209.151", + "91.92.213.228", + "91.92.214.28", + "91.92.214.110", + "91.92.214.241", + "91.92.215.66", + "91.92.215.250", + "91.106.65.46", + "91.106.65.47", + "91.106.78.194", + "91.106.85.10", + "91.106.86.218", + "91.106.87.54", + "91.106.91.5", + "91.106.91.6", + "91.108.128.18", + "91.108.129.121", + "91.108.130.18", + "91.108.130.203", + "91.108.130.204", + "91.108.130.206", + "91.108.152.115", + "91.108.152.116", + "91.185.133.81", + "91.186.193.4", + "91.186.193.20", + "91.186.193.21", + "91.186.193.29", + "91.186.193.32", + "91.186.193.35", + "91.186.193.41", + "91.186.193.89", + "91.186.193.108", + "91.186.193.125", + "91.186.193.130", + "91.186.193.131", + "91.186.193.132", + "91.186.193.133", + "91.186.193.134", + "91.186.193.162", + "91.186.193.192", + "91.186.193.193", + "91.186.193.194", + "91.186.193.196", + "91.186.193.229", + "91.186.193.230", + "91.186.193.232", + "91.197.242.5", + "91.197.242.6", + "91.199.9.87", + "91.199.9.199", + "91.199.27.62", + "91.199.27.83", + "91.199.27.185", + "91.199.27.237", + "91.199.30.171", + "91.199.30.196", + "91.199.215.3", + "91.199.215.26", + "91.199.215.30", + "91.199.215.69", + "91.199.215.70", + "91.199.215.72", + "91.199.215.73", + "91.199.215.77", + "91.199.215.78", + "91.199.215.115", + "91.199.215.248", + "91.207.138.55", + "91.207.205.4", + "91.207.205.5", + "91.207.205.6", + "91.207.205.8", + "91.207.205.9", + "91.207.205.10", + "91.207.205.11", + "91.207.205.12", + "91.207.205.13", + "91.207.205.14", + "91.207.205.15", + "91.207.205.16", + "91.207.205.17", + "91.207.205.18", + "91.207.205.19", + "91.207.205.20", + "91.207.205.21", + "91.207.205.22", + "91.207.205.24", + "91.207.205.26", + "91.207.205.29", + "91.207.205.30", + "91.207.205.34", + "91.207.205.35", + "91.207.205.36", + "91.207.205.37", + "91.207.205.38", + "91.207.205.41", + "91.207.205.44", + "91.207.205.46", + "91.207.205.47", + "91.207.205.48", + "91.207.205.50", + "91.208.163.196", + "91.212.174.5", + "91.212.174.10", + "91.212.174.21", + "91.212.174.28", + "91.212.174.39", + "91.212.174.43", + "91.212.174.48", + "91.212.174.50", + "91.212.174.56", + "91.212.174.80", + "91.212.174.86", + "91.212.174.95", + "91.212.174.98", + "91.212.174.100", + "91.212.174.105", + "91.212.174.114", + "91.212.174.119", + "91.212.174.131", + "91.212.174.133", + "91.212.174.136", + "91.212.174.138", + "91.212.174.141", + "91.212.174.148", + "91.212.174.149", + "91.212.174.163", + "91.212.174.178", + "91.212.174.183", + "91.212.174.184", + "91.212.174.185", + "91.212.174.191", + "91.212.174.196", + "91.212.174.205", + "91.212.174.226", + "91.212.174.233", + "91.212.174.250", + "91.212.252.53", + "91.212.252.54", + "91.212.252.184", + "91.212.252.200", + "91.212.252.201", + "91.216.171.3", + "91.216.171.4", + "91.216.171.8", + "91.220.113.2", + "91.220.113.7", + "91.220.113.18", + "91.220.113.22", + "91.220.113.40", + "91.220.113.47", + "91.220.113.119", + "91.220.113.125", + "91.220.113.151", + "91.220.113.157", + "91.220.113.167", + "91.220.113.172", + "91.220.113.185", + "91.220.113.199", + "91.220.113.200", + "91.220.113.207", + "91.220.113.221", + "91.220.113.231", + "91.220.113.239", + "91.220.113.241", + "91.220.113.245", + "91.220.113.246", + "91.220.113.251", + "91.220.113.252", + "91.221.241.200", + "91.222.199.9", + "91.222.199.95", + "91.222.199.98", + "91.222.199.250", + "91.222.199.251", + "91.222.199.252", + "91.223.116.1", + "91.223.116.15", + "91.223.116.50", + "91.223.116.52", + "91.223.116.61", + "91.223.116.116", + "91.223.116.128", + "91.223.116.130", + "91.223.116.134", + "91.223.116.135", + "91.223.116.136", + "91.223.116.137", + "91.223.116.138", + "91.223.116.143", + "91.223.116.144", + "91.223.116.146", + "91.223.116.147", + "91.223.116.152", + "91.223.116.153", + "91.223.116.156", + "91.223.116.158", + "91.223.116.161", + "91.223.116.163", + "91.223.116.167", + "91.223.116.168", + "91.223.116.170", + "91.223.116.171", + "91.223.116.172", + "91.223.116.175", + "91.223.116.187", + "91.223.116.194", + "91.223.116.196", + "91.223.116.200", + "91.223.116.206", + "91.223.116.220", + "91.223.116.221", + "91.223.116.234", + "91.223.116.249", + "91.223.146.10", + "91.223.146.20", + "91.224.110.2", + "91.224.110.56", + "91.224.110.58", + "91.224.111.101", + "91.225.54.67", + "91.225.54.70", + "91.228.132.6", + "91.228.132.21", + "91.228.132.23", + "91.228.132.91", + "91.228.133.91", + "91.232.73.11", + "91.232.73.12", + "91.232.73.55", + "91.232.73.56", + "91.232.73.66", + "91.232.73.67", + "91.234.39.11", + "91.234.39.15", + "91.234.39.16", + "91.234.39.32", + "91.234.39.33", + "91.234.39.44", + "91.234.39.58", + "91.234.39.61", + "91.234.39.68", + "91.234.39.112", + "91.234.39.115", + "91.234.39.179", + "91.234.39.181", + "91.234.39.203", + "91.234.39.208", + "91.234.39.214", + "91.234.39.228", + "91.234.39.234", + "91.236.168.56", + "91.236.168.68", + "91.236.168.84", + "91.236.168.121", + "91.236.168.145", + "91.236.168.164", + "91.236.168.167", + "91.236.168.178", + "91.236.168.179", + "91.236.168.208", + "91.236.168.210", + "91.236.168.220", + "91.236.168.249", + "91.236.168.250", + "91.236.168.251", + "91.236.168.252", + "91.236.168.253", + "91.236.169.187", + "91.239.14.3", + "91.239.14.4", + "91.239.214.2", + "91.239.214.130", + "91.240.60.5", + "91.240.60.10", + "91.240.60.15", + "91.240.60.60", + "91.240.60.61", + "91.240.61.110", + "91.240.62.2", + "91.240.62.10", + "91.240.62.11", + "91.240.63.37", + "91.240.63.114", + "91.240.63.115", + "91.240.180.10", + "91.240.180.11", + "91.240.180.215", + "91.240.180.216", + "91.240.182.210", + "91.240.182.211", + "91.242.44.228", + "91.243.127.102", + "91.243.171.83", + "91.243.171.172", + "91.245.228.96", + "91.245.228.101", + "91.245.228.102", + "91.245.228.250", + "91.245.228.251", + "91.245.229.1", + "91.245.229.66", + "91.246.31.25", + "91.246.44.9", + "91.246.44.112", + "91.247.171.14", + "91.247.171.36", + "91.247.171.61", + "91.247.171.65", + "91.247.171.75", + "91.247.171.80", + "91.247.171.135", + "91.247.171.146", + "91.247.171.154", + "91.247.171.203", + "91.247.171.231", + "92.42.50.58", + "92.42.50.209", + "92.42.50.210", + "92.42.51.109", + "92.42.51.209", + "92.42.53.28", + "92.42.53.29", + "92.42.207.35", + "92.43.163.198", + "92.61.181.55", + "92.61.181.181", + "92.61.181.253", + "92.61.181.254", + "92.61.183.8", + "92.61.183.136", + "92.61.183.141", + "92.61.183.146", + "92.114.17.228", + "92.114.17.229", + "92.114.17.230", + "92.114.18.46", + "92.114.18.100", + "92.114.18.102", + "92.114.18.104", + "92.114.18.111", + "92.114.18.116", + "92.114.18.117", + "92.114.19.9", + "92.114.19.28", + "92.114.19.29", + "92.114.19.41", + "92.114.19.226", + "92.114.19.251", + "92.114.19.252", + "92.114.24.110", + "92.114.28.66", + "92.114.31.112", + "92.114.50.2", + "92.114.51.151", + "92.119.57.68", + "92.119.57.75", + "92.119.57.82", + "92.119.57.108", + "92.119.57.109", + "92.119.57.110", + "92.119.57.134", + "92.119.57.147", + "92.119.57.164", + "92.119.57.176", + "92.119.57.210", + "92.119.58.22", + "92.119.58.32", + "92.119.58.59", + "92.119.58.60", + "92.119.58.62", + "92.119.58.114", + "92.119.58.158", + "92.242.195.53", + "92.242.195.54", + "92.242.198.7", + "92.242.198.21", + "92.242.198.22", + "92.242.198.42", + "92.242.198.50", + "92.242.198.51", + "92.242.198.52", + "92.242.198.123", + "92.242.198.166", + "92.242.198.181", + "92.242.198.230", + "92.242.198.233", + "92.242.198.253", + "92.242.201.2", + "92.242.201.10", + "92.242.201.11", + "92.242.203.85", + "92.242.204.73", + "92.242.204.74", + "92.242.205.40", + "92.242.207.200", + "92.242.210.6", + "92.242.212.103", + "92.242.216.50", + "92.242.216.150", + "92.242.216.248", + "92.242.220.116", + "92.242.220.122", + "92.242.220.162", + "92.242.220.200", + "92.242.220.228", + "92.242.220.238", + "92.242.223.115", + "92.242.223.116", + "92.246.144.179", + "92.246.144.205", + "92.246.145.99", + "92.246.146.139", + "93.113.224.58", + "93.113.224.60", + "93.113.224.62", + "93.113.225.7", + "93.113.225.8", + "93.113.226.82", + "93.113.234.62", + "93.113.234.67", + "93.113.235.105", + "93.113.235.106", + "93.113.235.107", + "93.113.235.108", + "93.113.236.50", + "93.113.236.52", + "93.113.236.53", + "93.113.237.170", + "93.113.238.2", + "93.113.239.55", + "93.113.239.56", + "93.113.239.57", + "93.113.239.58", + "93.114.110.20", + "93.115.126.14", + "93.115.127.9", + "93.115.144.212", + "93.115.148.150", + "93.115.149.88", + "93.115.150.27", + "93.115.150.28", + "93.115.150.29", + "93.115.150.66", + "93.115.150.68", + "93.115.150.229", + "93.115.150.230", + "93.115.150.231", + "93.115.150.235", + "93.115.150.236", + "93.115.150.237", + "93.115.150.238", + "93.115.151.75", + "93.115.151.76", + "93.115.151.77", + "93.115.151.78", + "93.115.151.100", + "93.115.151.138", + "93.115.219.48", + "93.117.27.61", + "93.117.30.40", + "93.117.30.53", + "93.117.36.33", + "93.117.37.237", + "93.117.39.171", + "93.117.43.82", + "93.117.46.181", + "93.117.46.187", + "93.117.47.43", + "93.117.127.141", + "93.118.99.182", + "93.118.104.137", + "93.118.108.162", + "93.118.109.213", + "93.118.110.144", + "93.118.112.28", + "93.118.112.76", + "93.118.112.118", + "93.118.115.173", + "93.118.116.13", + "93.118.117.12", + "93.118.121.130", + "93.118.122.27", + "93.118.123.140", + "93.118.125.167", + "93.118.126.176", + "93.118.126.196", + "93.118.127.96", + "93.118.132.34", + "93.118.134.242", + "93.118.135.237", + "93.118.137.33", + "93.118.137.209", + "93.118.138.109", + "93.118.140.95", + "93.118.140.160", + "93.118.144.69", + "93.118.145.139", + "93.118.146.112", + "93.118.148.134", + "93.118.153.59", + "93.118.153.218", + "93.118.156.84", + "93.118.160.148", + "93.118.160.151", + "93.118.163.159", + "93.118.163.238", + "93.118.164.87", + "93.118.164.193", + "93.118.165.89", + "93.118.166.2", + "93.118.167.175", + "93.118.182.221", + "93.118.183.120", + "93.119.57.119", + "93.126.2.3", + "93.126.2.4", + "93.126.2.77", + "93.126.2.212", + "93.126.3.99", + "93.126.3.169", + "93.126.6.131", + "93.126.6.132", + "93.126.6.133", + "93.126.6.134", + "93.126.6.145", + "93.126.6.146", + "93.126.6.147", + "93.126.6.150", + "93.126.6.151", + "93.126.6.176", + "93.126.6.178", + "93.126.6.182", + "93.126.6.186", + "93.126.23.245", + "93.126.23.251", + "93.126.25.8", + "93.126.25.10", + "93.126.25.15", + "93.126.31.166", + "94.74.128.2", + "94.74.128.3", + "94.74.128.4", + "94.74.128.12", + "94.74.128.13", + "94.74.128.18", + "94.74.128.110", + "94.74.128.129", + "94.74.128.234", + "94.74.128.252", + "94.74.145.11", + "94.74.145.14", + "94.74.145.16", + "94.74.145.23", + "94.74.145.24", + "94.74.145.27", + "94.74.145.122", + "94.74.145.130", + "94.74.145.155", + "94.74.145.173", + "94.74.145.178", + "94.74.145.191", + "94.74.145.202", + "94.74.163.130", + "94.74.168.36", + "94.74.168.179", + "94.74.170.119", + "94.74.170.162", + "94.74.170.167", + "94.101.136.138", + "94.101.140.196", + "94.101.176.219", + "94.101.177.8", + "94.101.177.34", + "94.101.177.39", + "94.101.178.79", + "94.101.179.19", + "94.101.179.98", + "94.101.179.105", + "94.101.180.19", + "94.101.180.39", + "94.101.180.41", + "94.101.184.158", + "94.101.184.190", + "94.101.184.222", + "94.101.184.254", + "94.101.185.65", + "94.101.185.73", + "94.101.185.75", + "94.101.185.151", + "94.101.186.22", + "94.101.186.40", + "94.101.186.107", + "94.101.186.138", + "94.101.186.151", + "94.101.187.89", + "94.101.187.195", + "94.101.190.208", + "94.101.190.209", + "94.182.17.202", + "94.182.17.205", + "94.182.17.206", + "94.182.17.214", + "94.182.18.246", + "94.182.20.10", + "94.182.28.3", + "94.182.28.56", + "94.182.37.50", + "94.182.40.34", + "94.182.40.35", + "94.182.40.70", + "94.182.40.78", + "94.182.42.194", + "94.182.44.54", + "94.182.50.77", + "94.182.83.197", + "94.182.83.200", + "94.182.83.201", + "94.182.83.202", + "94.182.83.205", + "94.182.83.206", + "94.182.83.209", + "94.182.83.210", + "94.182.83.211", + "94.182.83.212", + "94.182.83.214", + "94.182.83.216", + "94.182.83.219", + "94.182.83.233", + "94.182.83.234", + "94.182.83.235", + "94.182.83.236", + "94.182.83.237", + "94.182.83.238", + "94.182.83.246", + "94.182.83.247", + "94.182.83.248", + "94.182.83.252", + "94.182.84.172", + "94.182.85.227", + "94.182.85.231", + "94.182.85.232", + "94.182.85.237", + "94.182.85.244", + "94.182.85.250", + "94.182.85.252", + "94.182.86.187", + "94.182.86.190", + "94.182.87.209", + "94.182.87.210", + "94.182.87.211", + "94.182.87.212", + "94.182.87.214", + "94.182.87.215", + "94.182.87.216", + "94.182.87.217", + "94.182.87.218", + "94.182.87.219", + "94.182.87.220", + "94.182.87.222", + "94.182.88.18", + "94.182.88.20", + "94.182.88.21", + "94.182.88.22", + "94.182.88.116", + "94.182.88.120", + "94.182.89.41", + "94.182.89.107", + "94.182.89.131", + "94.182.89.132", + "94.182.89.133", + "94.182.89.135", + "94.182.89.136", + "94.182.89.137", + "94.182.89.138", + "94.182.89.139", + "94.182.89.142", + "94.182.89.143", + "94.182.89.145", + "94.182.89.146", + "94.182.89.147", + "94.182.89.148", + "94.182.89.150", + "94.182.89.152", + "94.182.89.154", + "94.182.89.155", + "94.182.89.156", + "94.182.89.157", + "94.182.89.159", + "94.182.89.161", + "94.182.89.163", + "94.182.89.167", + "94.182.89.169", + "94.182.89.170", + "94.182.89.174", + "94.182.89.175", + "94.182.89.179", + "94.182.89.182", + "94.182.89.224", + "94.182.89.227", + "94.182.89.235", + "94.182.89.236", + "94.182.89.237", + "94.182.89.247", + "94.182.90.18", + "94.182.90.19", + "94.182.90.20", + "94.182.90.21", + "94.182.90.66", + "94.182.90.149", + "94.182.90.170", + "94.182.90.200", + "94.182.90.210", + "94.182.90.213", + "94.182.90.216", + "94.182.90.217", + "94.182.91.114", + "94.182.91.126", + "94.182.91.130", + "94.182.91.133", + "94.182.91.134", + "94.182.92.19", + "94.182.92.57", + "94.182.92.80", + "94.182.92.165", + "94.182.93.45", + "94.182.93.133", + "94.182.94.5", + "94.182.94.67", + "94.182.94.75", + "94.182.94.121", + "94.182.94.170", + "94.182.94.217", + "94.182.95.2", + "94.182.95.3", + "94.182.95.4", + "94.182.95.6", + "94.182.95.8", + "94.182.95.246", + "94.182.97.45", + "94.182.102.102", + "94.182.102.200", + "94.182.104.11", + "94.182.104.12", + "94.182.104.13", + "94.182.104.14", + "94.182.105.213", + "94.182.105.215", + "94.182.107.227", + "94.182.108.98", + "94.182.108.150", + "94.182.110.234", + "94.182.110.235", + "94.182.110.236", + "94.182.110.237", + "94.182.110.238", + "94.182.113.117", + "94.182.115.130", + "94.182.115.173", + "94.182.128.123", + "94.182.128.124", + "94.182.129.5", + "94.182.129.114", + "94.182.129.121", + "94.182.130.2", + "94.182.130.91", + "94.182.130.93", + "94.182.133.6", + "94.182.133.36", + "94.182.133.70", + "94.182.133.73", + "94.182.133.130", + "94.182.133.254", + "94.182.135.77", + "94.182.135.254", + "94.182.136.19", + "94.182.136.39", + "94.182.136.45", + "94.182.136.50", + "94.182.136.51", + "94.182.136.106", + "94.182.138.37", + "94.182.140.18", + "94.182.140.19", + "94.182.140.20", + "94.182.140.21", + "94.182.140.22", + "94.182.142.40", + "94.182.142.41", + "94.182.142.42", + "94.182.142.43", + "94.182.142.45", + "94.182.142.48", + "94.182.142.49", + "94.182.142.50", + "94.182.142.54", + "94.182.142.152", + "94.182.142.176", + "94.182.142.177", + "94.182.142.201", + "94.182.142.205", + "94.182.142.216", + "94.182.142.248", + "94.182.146.2", + "94.182.146.116", + "94.182.146.191", + "94.182.146.252", + "94.182.147.138", + "94.182.150.180", + "94.182.150.181", + "94.182.151.14", + "94.182.153.4", + "94.182.153.26", + "94.182.153.30", + "94.182.153.34", + "94.182.153.38", + "94.182.153.78", + "94.182.153.99", + "94.182.153.100", + "94.182.153.164", + "94.182.153.169", + "94.182.153.170", + "94.182.153.173", + "94.182.153.174", + "94.182.153.175", + "94.182.153.197", + "94.182.154.12", + "94.182.154.20", + "94.182.154.22", + "94.182.154.67", + "94.182.154.68", + "94.182.154.69", + "94.182.154.71", + "94.182.154.105", + "94.182.154.111", + "94.182.154.201", + "94.182.154.220", + "94.182.154.246", + "94.182.154.247", + "94.182.154.251", + "94.182.155.10", + "94.182.155.111", + "94.182.155.121", + "94.182.156.91", + "94.182.156.118", + "94.182.156.122", + "94.182.156.187", + "94.182.156.220", + "94.182.157.181", + "94.182.157.185", + "94.182.157.187", + "94.182.159.34", + "94.182.159.35", + "94.182.159.36", + "94.182.159.37", + "94.182.159.38", + "94.182.159.41", + "94.182.159.51", + "94.182.159.55", + "94.182.159.56", + "94.182.169.52", + "94.182.169.120", + "94.182.169.124", + "94.182.170.219", + "94.182.171.83", + "94.182.171.85", + "94.182.171.94", + "94.182.172.17", + "94.182.172.18", + "94.182.172.21", + "94.182.172.36", + "94.182.172.46", + "94.182.172.47", + "94.182.172.48", + "94.182.172.49", + "94.182.172.51", + "94.182.172.52", + "94.182.172.53", + "94.182.172.194", + "94.182.172.196", + "94.182.172.197", + "94.182.172.198", + "94.182.172.200", + "94.182.172.203", + "94.182.172.204", + "94.182.172.206", + "94.182.172.209", + "94.182.172.210", + "94.182.172.211", + "94.182.172.212", + "94.182.172.213", + "94.182.172.216", + "94.182.172.218", + "94.182.172.219", + "94.182.172.251", + "94.182.172.254", + "94.182.173.82", + "94.182.173.189", + "94.182.174.43", + "94.182.174.165", + "94.182.174.172", + "94.182.174.173", + "94.182.175.2", + "94.182.175.4", + "94.182.175.5", + "94.182.175.6", + "94.182.175.10", + "94.182.175.11", + "94.182.175.12", + "94.182.175.14", + "94.182.175.15", + "94.182.175.17", + "94.182.175.20", + "94.182.175.35", + "94.182.175.38", + "94.182.175.44", + "94.182.175.50", + "94.182.175.53", + "94.182.175.55", + "94.182.175.56", + "94.182.176.184", + "94.182.176.188", + "94.182.177.43", + "94.182.177.67", + "94.182.177.68", + "94.182.177.70", + "94.182.177.72", + "94.182.177.73", + "94.182.177.80", + "94.182.177.81", + "94.182.177.82", + "94.182.178.50", + "94.182.178.52", + "94.182.178.53", + "94.182.178.54", + "94.182.178.55", + "94.182.178.71", + "94.182.178.73", + "94.182.178.75", + "94.182.178.80", + "94.182.178.81", + "94.182.178.83", + "94.182.178.85", + "94.182.178.88", + "94.182.178.91", + "94.182.178.92", + "94.182.178.93", + "94.182.178.94", + "94.182.178.95", + "94.182.178.97", + "94.182.178.98", + "94.182.178.100", + "94.182.178.107", + "94.182.178.109", + "94.182.178.110", + "94.182.178.111", + "94.182.178.112", + "94.182.178.113", + "94.182.178.114", + "94.182.178.115", + "94.182.178.116", + "94.182.178.117", + "94.182.178.118", + "94.182.178.119", + "94.182.178.120", + "94.182.178.121", + "94.182.178.122", + "94.182.178.132", + "94.182.178.233", + "94.182.179.232", + "94.182.180.140", + "94.182.181.50", + "94.182.181.222", + "94.182.182.98", + "94.182.183.211", + "94.182.184.190", + "94.182.185.95", + "94.182.185.97", + "94.182.185.126", + "94.182.185.226", + "94.182.185.227", + "94.182.186.79", + "94.182.186.231", + "94.182.186.232", + "94.182.186.250", + "94.182.187.148", + "94.182.187.149", + "94.182.190.229", + "94.182.190.245", + "94.182.192.178", + "94.182.195.42", + "94.182.195.54", + "94.182.195.107", + "94.182.195.158", + "94.182.195.164", + "94.182.195.182", + "94.182.195.183", + "94.182.195.188", + "94.182.195.194", + "94.182.195.218", + "94.182.195.223", + "94.182.195.225", + "94.182.195.231", + "94.182.195.243", + "94.182.195.247", + "94.182.196.187", + "94.182.198.62", + "94.182.198.146", + "94.182.199.222", + "94.182.202.78", + "94.182.208.45", + "94.182.208.46", + "94.182.209.58", + "94.182.209.66", + "94.182.209.70", + "94.182.209.71", + "94.182.214.34", + "94.182.214.182", + "94.182.215.6", + "94.182.215.72", + "94.182.215.75", + "94.182.215.90", + "94.182.227.5", + "94.182.227.8", + "94.182.227.10", + "94.182.227.19", + "94.182.227.34", + "94.182.227.37", + "94.182.227.38", + "94.182.227.40", + "94.182.227.42", + "94.182.227.56", + "94.182.227.61", + "94.182.227.80", + "94.182.227.83", + "94.182.227.85", + "94.182.227.96", + "94.182.227.99", + "94.182.227.103", + "94.182.227.120", + "94.182.227.121", + "94.182.227.131", + "94.182.227.134", + "94.182.227.164", + "94.182.227.165", + "94.182.227.166", + "94.182.227.167", + "94.182.227.170", + "94.182.227.181", + "94.182.227.194", + "94.182.227.195", + "94.182.227.200", + "94.182.227.209", + "94.182.227.219", + "94.182.227.225", + "94.182.227.229", + "94.182.227.237", + "94.182.227.238", + "94.182.227.241", + "94.182.227.242", + "94.182.227.246", + "94.182.227.247", + "94.182.242.13", + "94.182.249.11", + "94.182.250.30", + "94.182.251.16", + "94.182.255.154", + "94.183.0.19", + "94.183.2.164", + "94.183.3.131", + "94.183.3.199", + "94.183.3.233", + "94.183.8.70", + "94.183.10.192", + "94.183.10.193", + "94.183.12.86", + "94.183.14.129", + "94.183.14.154", + "94.183.17.153", + "94.183.25.223", + "94.183.26.90", + "94.183.27.1", + "94.183.29.64", + "94.183.30.17", + "94.183.31.47", + "94.183.32.45", + "94.183.33.193", + "94.183.36.82", + "94.183.42.239", + "94.183.47.239", + "94.183.51.96", + "94.183.51.149", + "94.183.53.16", + "94.183.53.17", + "94.183.53.18", + "94.183.53.19", + "94.183.57.172", + "94.183.58.73", + "94.183.59.36", + "94.183.62.83", + "94.183.62.232", + "94.183.84.184", + "94.183.84.227", + "94.183.85.177", + "94.183.88.99", + "94.183.90.243", + "94.183.100.148", + "94.183.108.245", + "94.183.110.118", + "94.183.117.238", + "94.183.118.64", + "94.183.119.8", + "94.183.122.41", + "94.183.123.216", + "94.183.124.28", + "94.183.124.97", + "94.183.124.129", + "94.183.125.86", + "94.183.125.191", + "94.183.126.155", + "94.183.127.185", + "94.183.128.221", + "94.183.130.253", + "94.183.132.106", + "94.183.132.251", + "94.183.137.113", + "94.183.141.177", + "94.183.146.170", + "94.183.146.198", + "94.183.146.205", + "94.183.156.141", + "94.183.157.7", + "94.183.157.188", + "94.183.160.175", + "94.183.160.239", + "94.183.163.63", + "94.183.163.90", + "94.183.163.132", + "94.183.163.180", + "94.183.163.181", + "94.183.176.60", + "94.183.176.65", + "94.183.177.100", + "94.184.9.31", + "94.184.10.82", + "94.184.16.6", + "94.184.17.197", + "94.184.22.5", + "94.184.22.62", + "94.184.29.217", + "94.184.29.219", + "94.184.29.220", + "94.184.89.2", + "94.184.120.67", + "94.184.141.137", + "94.184.141.141", + "94.184.176.5", + "94.184.176.6", + "94.184.176.201", + "94.184.224.196", + "94.184.225.25", + "94.184.236.20", + "94.184.236.32", + "94.184.236.50", + "94.184.239.20", + "94.199.0.166", + "94.232.168.26", + "94.232.168.103", + "94.232.168.105", + "94.232.168.151", + "94.232.168.211", + "94.232.170.65", + "94.232.170.66", + "94.232.170.67", + "94.232.170.68", + "94.232.171.86", + "94.232.171.140", + "94.232.171.141", + "94.232.171.150", + "94.232.171.151", + "94.232.172.2", + "94.232.172.96", + "94.232.172.104", + "94.232.172.135", + "94.232.173.4", + "94.232.173.28", + "94.232.173.52", + "94.232.173.69", + "94.232.173.70", + "94.232.173.71", + "94.232.173.100", + "94.232.173.114", + "94.232.173.128", + "94.232.173.137", + "94.232.173.148", + "94.232.173.150", + "94.232.173.153", + "94.232.173.154", + "94.232.173.155", + "94.232.173.199", + "94.232.173.229", + "94.232.174.1", + "94.232.174.28", + "94.232.174.35", + "94.232.174.84", + "94.232.174.176", + "94.232.174.181", + "94.232.174.222", + "94.232.174.247", + "94.232.174.249", + "94.232.174.250", + "94.232.175.29", + "94.232.175.32", + "94.232.175.85", + "94.232.175.194", + "95.38.13.239", + "95.38.24.138", + "95.38.34.53", + "95.38.40.169", + "95.38.47.170", + "95.38.47.171", + "95.38.47.172", + "95.38.47.174", + "95.38.48.56", + "95.38.60.141", + "95.38.60.173", + "95.38.60.185", + "95.38.60.251", + "95.38.65.161", + "95.38.65.237", + "95.38.72.30", + "95.38.77.158", + "95.38.86.17", + "95.38.102.86", + "95.38.102.117", + "95.38.102.190", + "95.38.103.189", + "95.38.103.220", + "95.38.103.221", + "95.38.103.222", + "95.38.125.44", + "95.38.130.112", + "95.38.130.131", + "95.38.134.99", + "95.38.135.150", + "95.38.137.129", + "95.38.138.3", + "95.38.153.168", + "95.38.155.35", + "95.38.155.58", + "95.38.201.199", + "95.38.209.3", + "95.38.245.135", + "95.38.250.86", + "95.80.141.218", + "95.80.155.159", + "95.80.164.5", + "95.80.164.6", + "95.80.164.10", + "95.80.168.55", + "95.80.173.169", + "95.80.182.185", + "95.80.184.15", + "95.80.184.19", + "95.80.184.180", + "95.80.184.184", + "95.80.185.180", + "95.80.185.185", + "95.130.56.37", + "95.130.56.40", + "95.130.245.66", + "95.142.224.142", + "95.142.226.4", + "95.142.226.26", + "95.142.227.6", + "95.142.227.7", + "95.142.227.50", + "95.142.227.178", + "95.142.227.195", + "95.142.227.206", + "95.142.227.227", + "95.142.227.230", + "95.142.227.231", + "95.142.231.137", + "95.142.235.40", + "95.142.237.3", + "95.142.237.4", + "95.142.237.20", + "95.142.237.52", + "95.142.237.82", + "95.142.237.190", + "95.142.237.254", + "95.156.252.219", + "95.156.253.8", + "95.156.253.9", + "95.156.253.10", + "95.156.253.11", + "95.156.253.12", + "95.156.253.13", + "95.156.253.14", + "95.156.253.17", + "95.156.253.18", + "95.156.253.20", + "95.156.253.21", + "95.156.253.24", + "95.156.253.27", + "95.156.253.30", + "95.156.253.31", + "95.156.253.33", + "95.156.253.36", + "95.156.253.38", + "95.156.253.39", + "95.156.253.43", + "95.156.253.44", + "95.156.253.45", + "95.156.253.49", + "95.156.253.52", + "95.156.253.53", + "95.156.253.54", + "95.156.253.55", + "95.156.253.59", + "95.156.253.61", + "95.156.253.99", + "95.156.253.100", + "95.156.253.174", + "95.156.253.177", + "95.156.254.227", + "95.156.254.228", + "95.156.254.229", + "95.156.254.234", + "95.156.254.235", + "95.156.254.236", + "95.215.59.90", + "95.215.59.121", + "95.215.59.147", + "95.215.59.173", + "95.215.59.189", + "95.215.59.195", + "95.215.173.178", + "95.215.173.180", + "103.130.144.1", + "103.130.144.2", + "103.130.144.3", + "103.215.222.11", + "103.215.222.12", + "103.215.222.33", + "103.215.222.44", + "103.215.222.55", + "103.215.222.66", + "103.215.222.102", + "103.215.222.103", + "103.215.222.104", + "103.215.222.105", + "103.215.222.106", + "103.215.222.107", + "103.215.222.108", + "103.215.222.109", + "103.215.222.112", + "103.215.222.113", + "103.215.222.114", + "103.215.222.115", + "103.215.222.116", + "103.215.222.118", + "103.215.222.119", + "103.215.222.122", + "103.215.222.124", + "103.215.222.126", + "103.215.222.127", + "103.215.222.128", + "103.215.222.129", + "103.215.222.130", + "103.215.222.131", + "103.215.222.132", + "103.215.222.133", + "103.215.222.135", + "103.215.222.136", + "103.215.222.140", + "103.215.222.141", + "103.215.222.142", + "103.215.222.143", + "103.215.222.144", + "103.215.222.145", + "103.215.222.146", + "103.215.222.147", + "103.215.222.149", + "103.215.222.150", + "103.215.222.151", + "103.215.222.153", + "103.215.222.154", + "103.215.222.157", + "103.215.223.4", + "103.215.223.9", + "103.215.223.10", + "103.215.223.11", + "103.215.223.14", + "103.215.223.19", + "103.215.223.26", + "103.215.223.40", + "103.215.223.42", + "103.215.223.58", + "103.215.223.166", + "103.215.223.214", + "103.215.223.240", + "103.215.223.241", + "103.216.60.29", + "103.216.60.37", + "103.216.60.40", + "103.216.60.46", + "103.216.61.154", + "103.216.62.68", + "103.216.62.102", + "109.72.192.37", + "109.72.192.40", + "109.72.197.38", + "109.95.61.174", + "109.95.61.207", + "109.95.61.239", + "109.95.61.249", + "109.95.61.251", + "109.95.67.210", + "109.95.68.12", + "109.95.68.14", + "109.95.68.16", + "109.95.68.21", + "109.95.68.29", + "109.95.68.32", + "109.95.70.246", + "109.95.71.29", + "109.109.32.0", + "109.109.32.1", + "109.109.32.4", + "109.109.32.5", + "109.109.32.6", + "109.109.32.7", + "109.109.32.8", + "109.109.32.10", + "109.109.32.11", + "109.109.32.13", + "109.109.32.14", + "109.109.32.15", + "109.109.32.16", + "109.109.32.17", + "109.109.32.18", + "109.109.32.19", + "109.109.32.20", + "109.109.32.21", + "109.109.32.22", + "109.109.32.23", + "109.109.32.24", + "109.109.32.25", + "109.109.32.26", + "109.109.32.27", + "109.109.32.28", + "109.109.32.29", + "109.109.32.30", + "109.109.32.31", + "109.109.32.44", + "109.109.32.45", + "109.109.32.47", + "109.109.32.56", + "109.109.32.58", + "109.109.32.59", + "109.109.32.60", + "109.109.32.61", + "109.109.32.62", + "109.109.32.63", + "109.109.32.64", + "109.109.32.65", + "109.109.32.66", + "109.109.32.67", + "109.109.32.68", + "109.109.32.69", + "109.109.32.70", + "109.109.32.71", + "109.109.32.72", + "109.109.32.73", + "109.109.32.74", + "109.109.32.75", + "109.109.32.76", + "109.109.32.77", + "109.109.32.78", + "109.109.32.79", + "109.109.32.88", + "109.109.32.90", + "109.109.32.91", + "109.109.32.92", + "109.109.32.93", + "109.109.32.94", + "109.109.32.95", + "109.109.32.96", + "109.109.32.99", + "109.109.32.100", + "109.109.32.101", + "109.109.32.102", + "109.109.32.104", + "109.109.32.105", + "109.109.32.106", + "109.109.32.107", + "109.109.32.108", + "109.109.32.109", + "109.109.32.110", + "109.109.32.111", + "109.109.32.112", + "109.109.32.113", + "109.109.32.114", + "109.109.32.115", + "109.109.32.116", + "109.109.32.117", + "109.109.32.118", + "109.109.32.119", + "109.109.32.120", + "109.109.32.121", + "109.109.32.122", + "109.109.32.123", + "109.109.32.124", + "109.109.32.125", + "109.109.32.126", + "109.109.32.152", + "109.109.32.153", + "109.109.32.154", + "109.109.32.155", + "109.109.32.156", + "109.109.32.158", + "109.109.32.159", + "109.109.32.216", + "109.109.32.218", + "109.109.32.219", + "109.109.32.220", + "109.109.32.221", + "109.109.32.222", + "109.109.32.223", + "109.109.34.72", + "109.109.34.73", + "109.109.34.74", + "109.109.34.75", + "109.109.34.77", + "109.109.34.78", + "109.109.34.79", + "109.109.34.80", + "109.109.34.81", + "109.109.34.82", + "109.109.34.83", + "109.109.34.84", + "109.109.34.86", + "109.109.34.87", + "109.109.34.88", + "109.109.34.89", + "109.109.34.90", + "109.109.34.91", + "109.109.34.92", + "109.109.34.93", + "109.109.34.94", + "109.109.34.95", + "109.109.34.96", + "109.109.34.98", + "109.109.34.99", + "109.109.34.100", + "109.109.34.101", + "109.109.34.102", + "109.109.34.103", + "109.109.34.104", + "109.109.34.105", + "109.109.34.106", + "109.109.34.107", + "109.109.34.108", + "109.109.34.109", + "109.109.34.110", + "109.109.34.111", + "109.109.34.112", + "109.109.34.113", + "109.109.34.114", + "109.109.34.115", + "109.109.34.116", + "109.109.34.117", + "109.109.34.118", + "109.109.34.119", + "109.109.34.120", + "109.109.34.121", + "109.109.34.122", + "109.109.34.124", + "109.109.34.125", + "109.109.34.127", + "109.109.34.128", + "109.109.34.129", + "109.109.34.130", + "109.109.34.131", + "109.109.34.132", + "109.109.34.133", + "109.109.34.134", + "109.109.34.135", + "109.109.34.136", + "109.109.34.137", + "109.109.34.138", + "109.109.34.140", + "109.109.34.141", + "109.109.34.142", + "109.109.34.143", + "109.109.34.144", + "109.109.34.145", + "109.109.34.146", + "109.109.34.147", + "109.109.34.148", + "109.109.34.149", + "109.109.34.150", + "109.109.34.151", + "109.109.34.152", + "109.109.34.153", + "109.109.34.154", + "109.109.34.155", + "109.109.34.156", + "109.109.34.157", + "109.109.34.158", + "109.109.34.159", + "109.109.34.160", + "109.109.34.161", + "109.109.34.162", + "109.109.34.163", + "109.109.34.164", + "109.109.34.165", + "109.109.34.167", + "109.109.34.168", + "109.109.34.169", + "109.109.34.170", + "109.109.34.171", + "109.109.34.172", + "109.109.34.173", + "109.109.34.174", + "109.109.34.175", + "109.109.34.176", + "109.109.34.177", + "109.109.34.178", + "109.109.34.179", + "109.109.34.180", + "109.109.34.181", + "109.109.34.182", + "109.109.34.184", + "109.109.34.186", + "109.109.34.187", + "109.109.34.189", + "109.109.34.191", + "109.109.34.192", + "109.109.34.193", + "109.109.34.194", + "109.109.34.195", + "109.109.34.196", + "109.109.34.197", + "109.109.34.198", + "109.109.34.200", + "109.109.34.201", + "109.109.34.202", + "109.109.34.203", + "109.109.34.204", + "109.109.34.205", + "109.109.34.206", + "109.109.34.207", + "109.109.34.208", + "109.109.34.209", + "109.109.34.210", + "109.109.34.211", + "109.109.34.212", + "109.109.34.214", + "109.109.34.215", + "109.109.34.216", + "109.109.34.217", + "109.109.34.219", + "109.109.34.220", + "109.109.34.221", + "109.109.34.222", + "109.109.34.223", + "109.109.34.224", + "109.109.34.225", + "109.109.34.226", + "109.109.34.227", + "109.109.34.228", + "109.109.34.229", + "109.109.34.230", + "109.109.34.232", + "109.109.34.233", + "109.109.34.235", + "109.109.34.236", + "109.109.34.238", + "109.109.34.239", + "109.109.34.240", + "109.109.34.241", + "109.109.34.242", + "109.109.34.243", + "109.109.34.244", + "109.109.34.245", + "109.109.34.246", + "109.109.34.247", + "109.109.34.248", + "109.109.34.250", + "109.109.34.251", + "109.109.34.252", + "109.109.34.253", + "109.109.34.254", + "109.109.35.0", + "109.109.35.1", + "109.109.35.2", + "109.109.35.3", + "109.109.35.4", + "109.109.35.5", + "109.109.35.6", + "109.109.35.7", + "109.109.35.8", + "109.109.35.9", + "109.109.35.10", + "109.109.35.11", + "109.109.35.13", + "109.109.35.14", + "109.109.35.15", + "109.109.35.16", + "109.109.35.17", + "109.109.35.18", + "109.109.35.19", + "109.109.35.20", + "109.109.35.21", + "109.109.35.22", + "109.109.35.23", + "109.109.35.24", + "109.109.35.25", + "109.109.35.26", + "109.109.35.27", + "109.109.35.28", + "109.109.35.29", + "109.109.35.30", + "109.109.35.31", + "109.109.35.32", + "109.109.35.33", + "109.109.35.34", + "109.109.35.35", + "109.109.35.36", + "109.109.35.37", + "109.109.35.38", + "109.109.35.39", + "109.109.35.40", + "109.109.35.41", + "109.109.35.42", + "109.109.35.43", + "109.109.35.44", + "109.109.35.45", + "109.109.35.46", + "109.109.35.47", + "109.109.35.48", + "109.109.35.49", + "109.109.35.50", + "109.109.35.51", + "109.109.35.52", + "109.109.35.53", + "109.109.35.54", + "109.109.35.55", + "109.109.35.56", + "109.109.35.57", + "109.109.35.59", + "109.109.35.60", + "109.109.35.61", + "109.109.35.62", + "109.109.35.63", + "109.109.35.64", + "109.109.35.65", + "109.109.35.66", + "109.109.35.67", + "109.109.35.69", + "109.109.35.70", + "109.109.35.71", + "109.109.35.72", + "109.109.35.73", + "109.109.35.76", + "109.109.35.77", + "109.109.35.78", + "109.109.35.79", + "109.109.35.80", + "109.109.35.81", + "109.109.35.82", + "109.109.35.83", + "109.109.35.84", + "109.109.35.85", + "109.109.35.86", + "109.109.35.88", + "109.109.35.89", + "109.109.35.90", + "109.109.35.91", + "109.109.35.92", + "109.109.35.93", + "109.109.35.94", + "109.109.35.95", + "109.109.35.96", + "109.109.35.97", + "109.109.35.98", + "109.109.35.99", + "109.109.35.100", + "109.109.35.101", + "109.109.35.102", + "109.109.35.103", + "109.109.35.104", + "109.109.35.105", + "109.109.35.106", + "109.109.35.108", + "109.109.35.109", + "109.109.35.110", + "109.109.35.111", + "109.109.35.112", + "109.109.35.113", + "109.109.35.114", + "109.109.35.115", + "109.109.35.116", + "109.109.35.117", + "109.109.35.118", + "109.109.35.119", + "109.109.35.120", + "109.109.35.121", + "109.109.35.122", + "109.109.35.123", + "109.109.35.124", + "109.109.35.126", + "109.109.35.127", + "109.109.42.73", + "109.109.43.128", + "109.109.43.130", + "109.109.43.131", + "109.109.43.133", + "109.109.43.134", + "109.109.43.135", + "109.109.43.138", + "109.109.43.139", + "109.109.43.140", + "109.109.43.143", + "109.109.43.144", + "109.109.43.151", + "109.109.43.155", + "109.109.43.162", + "109.109.43.164", + "109.109.43.168", + "109.109.43.172", + "109.109.43.173", + "109.109.43.174", + "109.109.43.175", + "109.109.43.177", + "109.109.43.180", + "109.109.43.183", + "109.109.43.185", + "109.109.43.188", + "109.109.43.191", + "109.109.43.193", + "109.109.43.195", + "109.109.43.196", + "109.109.43.200", + "109.109.43.201", + "109.109.43.206", + "109.109.43.209", + "109.109.43.212", + "109.109.43.213", + "109.109.43.215", + "109.109.43.220", + "109.109.43.221", + "109.109.43.223", + "109.109.43.229", + "109.109.43.230", + "109.109.43.232", + "109.109.43.233", + "109.109.43.235", + "109.109.43.236", + "109.109.43.241", + "109.109.43.242", + "109.109.43.243", + "109.109.43.247", + "109.109.43.250", + "109.109.43.255", + "109.109.45.0", + "109.109.45.1", + "109.109.45.2", + "109.109.45.3", + "109.109.45.4", + "109.109.45.5", + "109.109.45.6", + "109.109.45.7", + "109.109.45.8", + "109.109.45.9", + "109.109.45.10", + "109.109.45.11", + "109.109.45.12", + "109.109.45.13", + "109.109.45.14", + "109.109.45.15", + "109.109.45.16", + "109.109.45.17", + "109.109.45.18", + "109.109.45.19", + "109.109.45.20", + "109.109.45.22", + "109.109.45.23", + "109.109.45.24", + "109.109.45.25", + "109.109.45.26", + "109.109.45.27", + "109.109.45.28", + "109.109.45.29", + "109.109.45.30", + "109.109.45.31", + "109.109.45.40", + "109.109.45.41", + "109.109.45.42", + "109.109.45.43", + "109.109.45.44", + "109.109.45.45", + "109.109.45.46", + "109.109.45.47", + "109.109.45.48", + "109.109.45.49", + "109.109.45.50", + "109.109.45.51", + "109.109.45.52", + "109.109.45.54", + "109.109.45.55", + "109.109.45.56", + "109.109.45.57", + "109.109.45.58", + "109.109.45.59", + "109.109.45.60", + "109.109.45.61", + "109.109.45.62", + "109.109.45.63", + "109.109.45.64", + "109.109.45.65", + "109.109.45.66", + "109.109.45.67", + "109.109.45.68", + "109.109.45.70", + "109.109.45.71", + "109.109.45.72", + "109.109.45.73", + "109.109.45.74", + "109.109.45.75", + "109.109.45.76", + "109.109.45.77", + "109.109.45.78", + "109.109.45.79", + "109.109.45.80", + "109.109.45.81", + "109.109.45.84", + "109.109.45.87", + "109.109.45.88", + "109.109.45.89", + "109.109.45.90", + "109.109.45.91", + "109.109.45.92", + "109.109.45.93", + "109.109.45.94", + "109.109.45.95", + "109.109.45.96", + "109.109.45.98", + "109.109.45.99", + "109.109.45.100", + "109.109.45.101", + "109.109.45.102", + "109.109.45.103", + "109.109.45.104", + "109.109.45.105", + "109.109.45.106", + "109.109.45.107", + "109.109.45.108", + "109.109.45.109", + "109.109.45.110", + "109.109.45.111", + "109.109.45.112", + "109.109.45.113", + "109.109.45.115", + "109.109.45.116", + "109.109.45.117", + "109.109.45.119", + "109.109.45.120", + "109.109.45.121", + "109.109.45.123", + "109.109.45.124", + "109.109.45.125", + "109.109.45.126", + "109.109.45.127", + "109.109.47.0", + "109.109.47.2", + "109.109.47.3", + "109.109.47.4", + "109.109.47.5", + "109.109.47.8", + "109.109.47.10", + "109.109.47.13", + "109.109.47.16", + "109.109.47.17", + "109.109.47.18", + "109.109.47.19", + "109.109.47.23", + "109.109.47.25", + "109.109.47.27", + "109.109.47.29", + "109.109.47.32", + "109.109.47.34", + "109.109.47.35", + "109.109.47.43", + "109.109.47.50", + "109.109.47.51", + "109.109.47.52", + "109.109.47.54", + "109.109.47.57", + "109.109.47.59", + "109.109.47.62", + "109.109.47.63", + "109.109.47.64", + "109.109.47.65", + "109.109.47.67", + "109.109.47.70", + "109.109.47.72", + "109.109.47.73", + "109.109.47.74", + "109.109.47.76", + "109.109.47.78", + "109.109.47.80", + "109.109.47.81", + "109.109.47.82", + "109.109.47.83", + "109.109.47.84", + "109.109.47.85", + "109.109.47.86", + "109.109.47.87", + "109.109.47.90", + "109.109.47.91", + "109.109.47.92", + "109.109.47.94", + "109.109.47.95", + "109.109.47.97", + "109.109.47.101", + "109.109.47.102", + "109.109.47.103", + "109.109.47.104", + "109.109.47.106", + "109.109.47.107", + "109.109.47.108", + "109.109.47.109", + "109.109.47.113", + "109.109.47.115", + "109.109.47.116", + "109.109.47.117", + "109.109.47.119", + "109.109.47.120", + "109.109.47.121", + "109.109.47.122", + "109.109.47.123", + "109.109.47.124", + "109.109.47.125", + "109.109.47.126", + "109.109.51.111", + "109.109.51.178", + "109.109.52.111", + "109.109.54.0", + "109.109.54.4", + "109.109.54.7", + "109.109.54.11", + "109.109.54.12", + "109.109.54.13", + "109.109.54.14", + "109.109.54.20", + "109.109.54.22", + "109.109.54.23", + "109.109.54.34", + "109.109.54.35", + "109.109.54.36", + "109.109.54.37", + "109.109.54.40", + "109.109.54.41", + "109.109.54.42", + "109.109.54.45", + "109.109.54.49", + "109.109.54.52", + "109.109.54.56", + "109.109.54.57", + "109.109.54.59", + "109.109.54.61", + "109.109.54.63", + "109.109.54.64", + "109.109.54.65", + "109.109.54.66", + "109.109.54.67", + "109.109.54.70", + "109.109.54.71", + "109.109.54.72", + "109.109.54.73", + "109.109.54.74", + "109.109.54.75", + "109.109.54.76", + "109.109.54.78", + "109.109.54.79", + "109.109.54.80", + "109.109.54.81", + "109.109.54.82", + "109.109.54.85", + "109.109.54.88", + "109.109.54.90", + "109.109.54.95", + "109.109.54.96", + "109.109.54.99", + "109.109.54.100", + "109.109.54.102", + "109.109.54.103", + "109.109.54.105", + "109.109.54.106", + "109.109.54.107", + "109.109.54.110", + "109.109.54.111", + "109.109.54.112", + "109.109.54.121", + "109.109.54.123", + "109.109.54.124", + "109.109.54.125", + "109.109.54.127", + "109.109.54.136", + "109.109.54.137", + "109.109.54.138", + "109.109.54.139", + "109.109.54.140", + "109.109.54.143", + "109.109.54.144", + "109.109.54.145", + "109.109.54.147", + "109.109.54.149", + "109.109.54.151", + "109.109.54.154", + "109.109.54.157", + "109.109.54.158", + "109.109.54.160", + "109.109.54.161", + "109.109.54.162", + "109.109.54.163", + "109.109.54.165", + "109.109.54.167", + "109.109.54.170", + "109.109.54.172", + "109.109.54.175", + "109.109.54.176", + "109.109.54.179", + "109.109.54.180", + "109.109.54.181", + "109.109.54.182", + "109.109.54.184", + "109.109.54.185", + "109.109.54.186", + "109.109.54.188", + "109.109.54.190", + "109.109.54.191", + "109.109.54.192", + "109.109.54.193", + "109.109.54.194", + "109.109.54.195", + "109.109.54.196", + "109.109.54.197", + "109.109.54.201", + "109.109.54.202", + "109.109.54.203", + "109.109.54.204", + "109.109.54.205", + "109.109.54.206", + "109.109.54.208", + "109.109.54.210", + "109.109.54.211", + "109.109.54.213", + "109.109.54.214", + "109.109.54.215", + "109.109.54.216", + "109.109.54.217", + "109.109.54.218", + "109.109.54.220", + "109.109.54.221", + "109.109.54.222", + "109.109.54.223", + "109.109.54.224", + "109.109.54.225", + "109.109.54.226", + "109.109.54.227", + "109.109.54.228", + "109.109.54.229", + "109.109.54.230", + "109.109.54.231", + "109.109.54.237", + "109.109.54.238", + "109.109.54.240", + "109.109.54.241", + "109.109.54.242", + "109.109.54.243", + "109.109.54.244", + "109.109.54.245", + "109.109.54.246", + "109.109.54.247", + "109.109.54.248", + "109.109.54.249", + "109.109.54.250", + "109.109.54.251", + "109.109.54.252", + "109.109.54.254", + "109.109.54.255", + "109.109.56.137", + "109.109.58.0", + "109.109.58.1", + "109.109.58.2", + "109.109.58.3", + "109.109.58.4", + "109.109.58.5", + "109.109.58.6", + "109.109.58.7", + "109.109.58.9", + "109.109.58.10", + "109.109.58.11", + "109.109.58.12", + "109.109.58.13", + "109.109.58.14", + "109.109.58.15", + "109.109.58.16", + "109.109.58.17", + "109.109.58.18", + "109.109.58.19", + "109.109.58.20", + "109.109.58.21", + "109.109.58.22", + "109.109.58.23", + "109.109.58.24", + "109.109.58.25", + "109.109.58.26", + "109.109.58.27", + "109.109.58.28", + "109.109.58.29", + "109.109.58.30", + "109.109.58.31", + "109.109.58.32", + "109.109.58.33", + "109.109.58.34", + "109.109.58.35", + "109.109.58.37", + "109.109.58.38", + "109.109.58.39", + "109.109.58.40", + "109.109.58.41", + "109.109.58.42", + "109.109.58.44", + "109.109.58.45", + "109.109.58.46", + "109.109.58.47", + "109.109.58.48", + "109.109.58.49", + "109.109.58.50", + "109.109.58.51", + "109.109.58.52", + "109.109.58.53", + "109.109.58.54", + "109.109.58.55", + "109.109.58.56", + "109.109.58.57", + "109.109.58.58", + "109.109.58.59", + "109.109.58.60", + "109.109.58.61", + "109.109.58.62", + "109.109.58.63", + "109.109.58.128", + "109.109.58.129", + "109.109.58.131", + "109.109.58.132", + "109.109.58.133", + "109.109.58.134", + "109.109.58.135", + "109.109.58.136", + "109.109.58.137", + "109.109.58.138", + "109.109.58.139", + "109.109.58.140", + "109.109.58.141", + "109.109.58.142", + "109.109.58.143", + "109.109.58.160", + "109.109.58.161", + "109.109.58.162", + "109.109.58.163", + "109.109.58.164", + "109.109.58.165", + "109.109.58.168", + "109.109.58.169", + "109.109.58.170", + "109.109.58.171", + "109.109.58.172", + "109.109.58.173", + "109.109.58.174", + "109.109.58.175", + "109.109.59.0", + "109.109.59.2", + "109.109.59.3", + "109.109.59.4", + "109.109.59.5", + "109.109.59.7", + "109.109.59.32", + "109.109.59.33", + "109.109.59.37", + "109.109.59.38", + "109.109.59.39", + "109.109.59.40", + "109.109.59.41", + "109.109.59.42", + "109.109.59.43", + "109.109.59.44", + "109.109.59.45", + "109.109.59.46", + "109.109.59.47", + "109.109.59.48", + "109.109.59.49", + "109.109.59.50", + "109.109.59.51", + "109.109.59.60", + "109.109.59.61", + "109.109.59.62", + "109.109.59.63", + "109.109.59.64", + "109.109.59.65", + "109.109.59.66", + "109.109.59.67", + "109.109.59.68", + "109.109.59.69", + "109.109.59.70", + "109.109.59.71", + "109.109.59.72", + "109.109.59.73", + "109.109.59.74", + "109.109.59.75", + "109.109.59.76", + "109.109.59.78", + "109.109.59.80", + "109.109.59.81", + "109.109.59.82", + "109.109.59.83", + "109.109.59.85", + "109.109.59.87", + "109.109.59.89", + "109.109.59.90", + "109.109.59.91", + "109.109.59.92", + "109.109.59.93", + "109.109.59.94", + "109.109.59.95", + "109.109.59.96", + "109.109.59.97", + "109.109.59.98", + "109.109.59.100", + "109.109.59.101", + "109.109.59.102", + "109.109.59.103", + "109.109.59.104", + "109.109.59.105", + "109.109.59.106", + "109.109.59.107", + "109.109.59.109", + "109.109.59.110", + "109.109.59.111", + "109.109.59.112", + "109.109.59.113", + "109.109.59.114", + "109.109.59.115", + "109.109.59.116", + "109.109.59.117", + "109.109.59.118", + "109.109.59.119", + "109.109.59.120", + "109.109.59.121", + "109.109.59.122", + "109.109.59.123", + "109.109.59.124", + "109.109.59.125", + "109.109.59.126", + "109.109.59.127", + "109.109.60.0", + "109.109.60.1", + "109.109.60.2", + "109.109.60.3", + "109.109.60.4", + "109.109.60.5", + "109.109.60.6", + "109.109.60.8", + "109.109.60.10", + "109.109.60.11", + "109.109.60.12", + "109.109.60.13", + "109.109.60.14", + "109.109.60.15", + "109.109.60.17", + "109.109.60.18", + "109.109.60.19", + "109.109.60.20", + "109.109.60.21", + "109.109.60.22", + "109.109.60.27", + "109.109.60.28", + "109.109.60.29", + "109.109.60.30", + "109.109.60.33", + "109.109.60.35", + "109.109.60.37", + "109.109.60.38", + "109.109.60.41", + "109.109.60.45", + "109.109.60.47", + "109.109.60.50", + "109.109.60.52", + "109.109.60.53", + "109.109.60.55", + "109.109.60.57", + "109.109.60.58", + "109.109.60.59", + "109.109.60.60", + "109.109.60.61", + "109.109.60.62", + "109.109.60.63", + "109.109.60.64", + "109.109.60.65", + "109.109.60.66", + "109.109.60.67", + "109.109.60.68", + "109.109.60.72", + "109.109.60.76", + "109.109.60.77", + "109.109.60.79", + "109.109.60.84", + "109.109.60.85", + "109.109.60.87", + "109.109.60.88", + "109.109.60.89", + "109.109.60.92", + "109.109.60.94", + "109.109.60.96", + "109.109.60.97", + "109.109.60.103", + "109.109.60.104", + "109.109.60.106", + "109.109.60.109", + "109.109.60.110", + "109.109.60.111", + "109.109.60.112", + "109.109.60.114", + "109.109.60.117", + "109.109.60.118", + "109.109.60.120", + "109.109.60.122", + "109.109.60.123", + "109.109.60.125", + "109.109.60.126", + "109.109.60.127", + "109.109.60.128", + "109.109.60.129", + "109.109.60.130", + "109.109.60.131", + "109.109.60.132", + "109.109.60.133", + "109.109.60.134", + "109.109.60.135", + "109.109.60.136", + "109.109.60.137", + "109.109.60.138", + "109.109.60.139", + "109.109.60.140", + "109.109.60.141", + "109.109.60.142", + "109.109.60.143", + "109.109.60.144", + "109.109.60.145", + "109.109.60.146", + "109.109.60.147", + "109.109.60.148", + "109.109.60.149", + "109.109.60.152", + "109.109.60.153", + "109.109.60.154", + "109.109.60.155", + "109.109.60.156", + "109.109.60.157", + "109.109.60.158", + "109.109.60.159", + "109.109.60.160", + "109.109.60.161", + "109.109.60.162", + "109.109.60.163", + "109.109.60.164", + "109.109.60.165", + "109.109.60.166", + "109.109.60.167", + "109.109.60.168", + "109.109.60.169", + "109.109.60.170", + "109.109.60.171", + "109.109.60.172", + "109.109.60.173", + "109.109.60.174", + "109.109.60.175", + "109.109.60.177", + "109.109.60.178", + "109.109.60.179", + "109.109.60.180", + "109.109.60.181", + "109.109.60.182", + "109.109.60.183", + "109.109.60.184", + "109.109.60.185", + "109.109.60.186", + "109.109.60.187", + "109.109.60.188", + "109.109.60.189", + "109.109.60.190", + "109.109.60.191", + "109.109.60.192", + "109.109.60.193", + "109.109.60.194", + "109.109.60.195", + "109.109.60.196", + "109.109.60.197", + "109.109.60.198", + "109.109.60.199", + "109.109.60.200", + "109.109.60.201", + "109.109.60.202", + "109.109.60.203", + "109.109.60.205", + "109.109.60.207", + "109.109.60.208", + "109.109.60.209", + "109.109.60.210", + "109.109.60.211", + "109.109.60.212", + "109.109.60.213", + "109.109.60.214", + "109.109.60.215", + "109.109.60.216", + "109.109.60.217", + "109.109.60.218", + "109.109.60.219", + "109.109.60.220", + "109.109.60.221", + "109.109.60.222", + "109.109.60.223", + "109.109.60.224", + "109.109.60.225", + "109.109.60.226", + "109.109.60.227", + "109.109.60.228", + "109.109.60.229", + "109.109.60.230", + "109.109.60.231", + "109.109.60.232", + "109.109.60.233", + "109.109.60.234", + "109.109.60.235", + "109.109.60.236", + "109.109.60.237", + "109.109.60.238", + "109.109.60.239", + "109.109.61.99", + "109.109.61.192", + "109.109.61.193", + "109.109.61.194", + "109.109.61.196", + "109.109.61.197", + "109.109.61.198", + "109.109.61.199", + "109.109.61.200", + "109.109.61.201", + "109.109.61.202", + "109.109.61.203", + "109.109.61.204", + "109.109.61.205", + "109.109.61.206", + "109.109.61.207", + "109.109.61.210", + "109.109.61.211", + "109.109.61.212", + "109.109.61.213", + "109.109.61.214", + "109.109.61.215", + "109.109.61.217", + "109.109.61.219", + "109.109.61.220", + "109.109.61.221", + "109.109.61.222", + "109.109.61.223", + "109.109.61.224", + "109.109.61.225", + "109.109.61.226", + "109.109.61.227", + "109.109.61.228", + "109.109.61.229", + "109.109.61.230", + "109.109.61.231", + "109.109.61.233", + "109.109.61.234", + "109.109.61.235", + "109.109.61.236", + "109.109.61.237", + "109.109.61.238", + "109.109.61.240", + "109.109.61.241", + "109.109.61.242", + "109.109.61.243", + "109.109.61.244", + "109.109.61.245", + "109.109.61.246", + "109.109.61.247", + "109.109.61.248", + "109.109.61.249", + "109.109.61.250", + "109.109.61.251", + "109.109.61.252", + "109.109.61.253", + "109.109.61.254", + "109.109.61.255", + "109.109.62.1", + "109.109.62.3", + "109.109.62.4", + "109.109.62.5", + "109.109.62.6", + "109.109.62.40", + "109.109.62.41", + "109.109.62.42", + "109.109.62.43", + "109.109.62.44", + "109.109.62.45", + "109.109.62.46", + "109.109.62.47", + "109.109.62.48", + "109.109.62.49", + "109.109.62.50", + "109.109.62.51", + "109.109.62.52", + "109.109.62.54", + "109.109.62.55", + "109.109.63.128", + "109.109.63.129", + "109.109.63.131", + "109.109.63.132", + "109.109.63.133", + "109.109.63.134", + "109.109.63.135", + "109.109.63.137", + "109.109.63.138", + "109.109.63.139", + "109.109.63.140", + "109.109.63.141", + "109.109.63.142", + "109.109.63.143", + "109.109.63.144", + "109.109.63.145", + "109.109.63.146", + "109.109.63.147", + "109.109.63.148", + "109.109.63.149", + "109.109.63.150", + "109.109.63.151", + "109.109.63.153", + "109.109.63.154", + "109.109.63.155", + "109.109.63.156", + "109.109.63.157", + "109.109.63.158", + "109.109.63.159", + "109.109.63.160", + "109.109.63.161", + "109.109.63.162", + "109.109.63.163", + "109.109.63.164", + "109.109.63.165", + "109.109.63.166", + "109.109.63.167", + "109.109.63.168", + "109.109.63.169", + "109.109.63.170", + "109.109.63.171", + "109.109.63.172", + "109.109.63.173", + "109.109.63.174", + "109.109.63.175", + "109.109.63.184", + "109.109.63.185", + "109.109.63.186", + "109.109.63.187", + "109.109.63.188", + "109.109.63.189", + "109.109.63.190", + "109.109.63.191", + "109.109.63.192", + "109.109.63.194", + "109.109.63.195", + "109.109.63.197", + "109.109.63.198", + "109.109.63.199", + "109.109.63.200", + "109.109.63.201", + "109.109.63.202", + "109.109.63.203", + "109.109.63.204", + "109.109.63.205", + "109.109.63.206", + "109.109.63.207", + "109.109.63.209", + "109.109.63.210", + "109.109.63.211", + "109.109.63.212", + "109.109.63.213", + "109.109.63.214", + "109.109.63.215", + "109.109.63.224", + "109.109.63.226", + "109.109.63.227", + "109.109.63.228", + "109.109.63.229", + "109.109.63.230", + "109.109.63.231", + "109.109.63.232", + "109.109.63.233", + "109.109.63.234", + "109.109.63.235", + "109.109.63.236", + "109.109.63.237", + "109.109.63.238", + "109.109.63.239", + "109.109.63.240", + "109.109.63.242", + "109.109.63.243", + "109.109.63.244", + "109.109.63.245", + "109.109.63.246", + "109.109.63.247", + "109.109.63.248", + "109.109.63.249", + "109.109.63.250", + "109.109.63.251", + "109.109.63.252", + "109.109.63.254", + "109.109.63.255", + "109.122.235.163", + "109.122.240.164", + "109.122.245.12", + "109.122.245.14", + "109.122.245.20", + "109.122.245.28", + "109.122.245.32", + "109.122.245.34", + "109.122.245.37", + "109.122.245.47", + "109.122.245.48", + "109.122.245.49", + "109.122.245.50", + "109.122.245.52", + "109.122.245.55", + "109.122.245.57", + "109.122.245.83", + "109.122.245.90", + "109.122.245.94", + "109.122.245.114", + "109.122.245.115", + "109.122.245.116", + "109.122.245.117", + "109.122.245.118", + "109.122.245.122", + "109.122.245.138", + "109.122.245.163", + "109.122.245.196", + "109.122.246.22", + "109.122.246.32", + "109.122.246.41", + "109.122.246.51", + "109.122.246.122", + "109.122.246.172", + "109.122.246.210", + "109.122.246.226", + "109.122.246.250", + "109.122.246.252", + "109.122.251.4", + "109.122.251.5", + "109.122.251.10", + "109.122.251.12", + "109.122.251.13", + "109.122.251.118", + "109.122.253.18", + "109.125.129.255", + "109.125.130.0", + "109.125.131.87", + "109.125.135.109", + "109.125.150.34", + "109.125.151.67", + "109.125.160.14", + "109.125.160.170", + "109.201.24.195", + "109.201.24.197", + "109.201.24.198", + "109.201.24.199", + "109.201.24.200", + "109.201.24.201", + "109.201.24.202", + "109.201.24.203", + "109.201.24.204", + "109.201.24.205", + "109.201.24.206", + "109.206.252.44", + "109.206.252.198", + "109.206.254.13", + "109.206.254.140", + "109.206.255.13", + "109.206.255.146", + "109.206.255.148", + "109.206.255.149", + "109.206.255.178", + "109.206.255.188", + "109.230.81.238", + "109.230.82.34", + "109.230.83.227", + "109.230.83.234", + "109.230.88.171", + "109.230.89.74", + "109.230.89.75", + "109.230.89.76", + "109.230.89.77", + "109.230.89.78", + "109.230.89.90", + "109.230.89.91", + "109.230.89.94", + "109.230.92.50", + "109.230.94.82", + "109.230.95.153", + "109.230.95.164", + "109.230.200.117", + "109.230.200.118", + "109.230.200.119", + "109.230.200.120", + "109.230.200.188", + "109.230.204.17", + "109.230.204.95", + "109.230.204.180", + "109.230.205.16", + "109.230.205.20", + "109.230.205.21", + "109.230.205.139", + "109.230.205.154", + "109.230.205.164", + "109.230.206.171", + "109.230.206.174", + "109.230.206.175", + "109.230.206.180", + "109.230.221.102", + "109.230.221.103", + "109.230.221.106", + "109.230.223.74", + "109.230.223.75", + "109.230.223.170", + "109.230.223.182", + "109.230.242.117", + "109.232.1.6", + "109.232.1.59", + "109.232.1.60", + "109.232.1.62", + "109.232.1.138", + "109.232.1.217", + "109.232.1.219", + "109.232.2.123", + "109.238.191.251", + "128.65.181.137", + "128.65.183.223", + "128.65.184.126", + "128.65.187.239", + "128.65.188.31", + "130.185.72.53", + "130.185.72.61", + "130.185.72.64", + "130.185.72.84", + "130.185.72.85", + "130.185.72.94", + "130.185.72.98", + "130.185.72.105", + "130.185.72.107", + "130.185.72.108", + "130.185.72.115", + "130.185.72.119", + "130.185.72.120", + "130.185.72.125", + "130.185.72.131", + "130.185.72.138", + "130.185.72.142", + "130.185.72.149", + "130.185.72.170", + "130.185.72.172", + "130.185.72.181", + "130.185.72.213", + "130.185.72.216", + "130.185.72.223", + "130.185.72.248", + "130.185.72.249", + "130.185.73.3", + "130.185.73.4", + "130.185.73.5", + "130.185.73.6", + "130.185.73.8", + "130.185.73.12", + "130.185.73.13", + "130.185.73.18", + "130.185.73.19", + "130.185.73.22", + "130.185.73.30", + "130.185.73.32", + "130.185.73.34", + "130.185.73.58", + "130.185.73.64", + "130.185.73.65", + "130.185.73.70", + "130.185.73.88", + "130.185.73.90", + "130.185.73.96", + "130.185.73.100", + "130.185.73.102", + "130.185.73.104", + "130.185.73.105", + "130.185.73.106", + "130.185.73.108", + "130.185.73.111", + "130.185.73.126", + "130.185.73.131", + "130.185.73.134", + "130.185.73.137", + "130.185.73.140", + "130.185.73.141", + "130.185.73.144", + "130.185.73.146", + "130.185.73.149", + "130.185.73.153", + "130.185.73.156", + "130.185.73.160", + "130.185.73.165", + "130.185.73.176", + "130.185.73.189", + "130.185.73.190", + "130.185.73.194", + "130.185.73.197", + "130.185.73.200", + "130.185.73.209", + "130.185.73.214", + "130.185.73.217", + "130.185.73.220", + "130.185.73.230", + "130.185.73.239", + "130.185.73.242", + "130.185.73.245", + "130.185.73.246", + "130.185.74.11", + "130.185.74.18", + "130.185.74.26", + "130.185.74.28", + "130.185.74.33", + "130.185.74.43", + "130.185.74.47", + "130.185.74.51", + "130.185.74.65", + "130.185.74.76", + "130.185.74.79", + "130.185.74.84", + "130.185.74.85", + "130.185.74.86", + "130.185.74.90", + "130.185.74.91", + "130.185.74.94", + "130.185.74.96", + "130.185.74.97", + "130.185.74.105", + "130.185.74.106", + "130.185.74.116", + "130.185.74.120", + "130.185.74.122", + "130.185.74.123", + "130.185.74.125", + "130.185.74.126", + "130.185.74.133", + "130.185.74.134", + "130.185.74.136", + "130.185.74.140", + "130.185.74.145", + "130.185.74.147", + "130.185.74.150", + "130.185.74.152", + "130.185.74.155", + "130.185.74.160", + "130.185.74.161", + "130.185.74.163", + "130.185.74.168", + "130.185.74.170", + "130.185.74.183", + "130.185.74.185", + "130.185.74.193", + "130.185.74.197", + "130.185.74.198", + "130.185.74.204", + "130.185.74.208", + "130.185.74.218", + "130.185.74.227", + "130.185.74.234", + "130.185.74.239", + "130.185.74.245", + "130.185.74.246", + "130.185.74.249", + "130.185.74.250", + "130.185.75.5", + "130.185.75.7", + "130.185.75.8", + "130.185.75.11", + "130.185.75.15", + "130.185.75.27", + "130.185.75.29", + "130.185.75.33", + "130.185.75.40", + "130.185.75.43", + "130.185.75.46", + "130.185.75.54", + "130.185.75.57", + "130.185.75.75", + "130.185.75.76", + "130.185.75.78", + "130.185.75.80", + "130.185.75.85", + "130.185.75.92", + "130.185.75.93", + "130.185.75.100", + "130.185.75.116", + "130.185.75.121", + "130.185.75.123", + "130.185.75.135", + "130.185.75.138", + "130.185.75.144", + "130.185.75.149", + "130.185.75.152", + "130.185.75.159", + "130.185.75.163", + "130.185.75.165", + "130.185.75.168", + "130.185.75.170", + "130.185.75.179", + "130.185.75.180", + "130.185.75.182", + "130.185.75.184", + "130.185.75.185", + "130.185.75.186", + "130.185.75.189", + "130.185.75.190", + "130.185.75.197", + "130.185.75.206", + "130.185.75.207", + "130.185.75.216", + "130.185.75.217", + "130.185.75.219", + "130.185.75.224", + "130.185.75.229", + "130.185.75.232", + "130.185.75.233", + "130.185.75.234", + "130.185.75.241", + "130.185.75.243", + "130.185.75.244", + "130.185.75.252", + "130.185.76.7", + "130.185.76.9", + "130.185.76.12", + "130.185.76.20", + "130.185.76.21", + "130.185.76.26", + "130.185.76.28", + "130.185.76.31", + "130.185.76.48", + "130.185.76.52", + "130.185.76.53", + "130.185.76.64", + "130.185.76.66", + "130.185.76.69", + "130.185.76.70", + "130.185.76.74", + "130.185.76.78", + "130.185.76.84", + "130.185.76.86", + "130.185.76.91", + "130.185.76.94", + "130.185.76.103", + "130.185.76.104", + "130.185.76.106", + "130.185.76.114", + "130.185.76.123", + "130.185.76.127", + "130.185.76.132", + "130.185.76.140", + "130.185.76.149", + "130.185.76.150", + "130.185.76.152", + "130.185.76.156", + "130.185.76.162", + "130.185.76.165", + "130.185.76.170", + "130.185.76.172", + "130.185.76.176", + "130.185.76.185", + "130.185.76.188", + "130.185.76.191", + "130.185.76.197", + "130.185.76.200", + "130.185.76.205", + "130.185.76.225", + "130.185.76.231", + "130.185.76.238", + "130.185.76.240", + "130.185.76.243", + "130.185.76.248", + "130.185.76.251", + "130.185.77.10", + "130.185.77.12", + "130.185.77.16", + "130.185.77.17", + "130.185.77.22", + "130.185.77.29", + "130.185.77.34", + "130.185.77.40", + "130.185.77.43", + "130.185.77.44", + "130.185.77.46", + "130.185.77.48", + "130.185.77.54", + "130.185.77.61", + "130.185.77.62", + "130.185.77.63", + "130.185.77.64", + "130.185.77.67", + "130.185.77.69", + "130.185.77.71", + "130.185.77.72", + "130.185.77.77", + "130.185.77.78", + "130.185.77.79", + "130.185.77.86", + "130.185.77.88", + "130.185.77.89", + "130.185.77.91", + "130.185.77.97", + "130.185.77.101", + "130.185.77.114", + "130.185.77.118", + "130.185.77.124", + "130.185.77.129", + "130.185.77.131", + "130.185.77.132", + "130.185.77.133", + "130.185.77.145", + "130.185.77.146", + "130.185.77.158", + "130.185.77.160", + "130.185.77.162", + "130.185.77.165", + "130.185.77.173", + "130.185.77.181", + "130.185.77.195", + "130.185.77.199", + "130.185.77.206", + "130.185.77.214", + "130.185.77.218", + "130.185.77.221", + "130.185.77.222", + "130.185.77.225", + "130.185.77.227", + "130.185.77.230", + "130.185.77.237", + "130.185.77.242", + "130.185.77.246", + "130.185.77.247", + "130.185.77.249", + "130.185.78.5", + "130.185.78.18", + "130.185.78.40", + "130.185.78.44", + "130.185.78.51", + "130.185.78.64", + "130.185.78.67", + "130.185.78.72", + "130.185.78.74", + "130.185.78.76", + "130.185.78.81", + "130.185.78.84", + "130.185.78.85", + "130.185.78.87", + "130.185.78.95", + "130.185.78.100", + "130.185.78.101", + "130.185.78.113", + "130.185.78.117", + "130.185.78.124", + "130.185.78.127", + "130.185.78.140", + "130.185.78.148", + "130.185.78.151", + "130.185.78.152", + "130.185.78.155", + "130.185.78.167", + "130.185.78.170", + "130.185.78.171", + "130.185.78.177", + "130.185.78.179", + "130.185.78.182", + "130.185.78.184", + "130.185.78.185", + "130.185.78.186", + "130.185.78.189", + "130.185.78.201", + "130.185.78.212", + "130.185.78.217", + "130.185.78.218", + "130.185.78.220", + "130.185.78.223", + "130.185.78.225", + "130.185.78.228", + "130.185.78.229", + "130.185.78.230", + "130.185.78.233", + "130.185.78.234", + "130.185.78.247", + "130.185.78.248", + "130.185.78.249", + "130.185.79.138", + "130.185.79.170", + "130.185.79.180", + "130.185.79.181", + "130.185.79.184", + "130.185.79.187", + "130.185.79.197", + "130.193.77.5", + "130.193.77.35", + "130.193.77.61", + "130.193.77.62", + "130.193.77.82", + "130.193.77.91", + "130.193.77.98", + "130.193.77.99", + "130.193.77.100", + "130.193.77.101", + "130.193.77.102", + "130.193.77.103", + "130.193.77.104", + "130.193.77.105", + "130.193.77.106", + "130.193.77.107", + "130.193.77.108", + "130.193.77.109", + "130.193.77.110", + "130.193.77.111", + "130.193.77.112", + "130.193.77.113", + "130.193.77.114", + "130.193.77.115", + "130.193.77.116", + "130.193.77.117", + "130.193.77.118", + "130.193.77.119", + "130.193.77.120", + "130.193.77.121", + "130.193.77.122", + "130.193.77.123", + "130.193.77.126", + "130.193.77.198", + "130.193.77.199", + "130.193.77.201", + "130.193.77.202", + "130.193.77.204", + "130.193.77.205", + "130.193.77.207", + "130.193.77.211", + "130.193.77.212", + "130.193.77.214", + "130.193.77.215", + "130.193.77.216", + "130.193.77.217", + "130.193.77.222", + "130.193.77.236", + "130.193.77.247", + "130.193.77.248", + "130.193.77.249", + "134.255.200.50", + "134.255.200.52", + "134.255.200.54", + "134.255.200.130", + "134.255.200.133", + "134.255.200.147", + "134.255.200.151", + "134.255.200.154", + "134.255.200.218", + "134.255.200.250", + "134.255.201.29", + "134.255.201.91", + "134.255.201.92", + "134.255.201.94", + "134.255.201.162", + "134.255.202.7", + "134.255.202.82", + "134.255.202.164", + "134.255.202.188", + "134.255.203.50", + "134.255.203.67", + "134.255.203.68", + "134.255.203.142", + "134.255.203.171", + "134.255.203.172", + "134.255.203.174", + "134.255.204.3", + "146.19.212.5", + "146.19.212.10", + "146.19.212.12", + "146.19.212.14", + "146.19.212.22", + "146.19.212.34", + "146.19.212.51", + "146.19.212.69", + "146.19.212.70", + "146.19.212.71", + "146.19.212.74", + "146.19.212.79", + "146.19.212.80", + "146.19.212.87", + "146.19.212.88", + "146.19.212.94", + "146.19.212.99", + "146.19.212.103", + "146.19.212.104", + "146.19.212.107", + "146.19.212.108", + "146.19.212.120", + "146.19.212.121", + "146.19.212.122", + "146.19.212.136", + "146.19.212.144", + "146.19.212.145", + "146.19.212.154", + "146.19.212.156", + "146.19.212.161", + "146.19.212.163", + "146.19.212.172", + "146.19.212.173", + "146.19.212.178", + "146.19.212.183", + "146.19.212.185", + "146.19.212.186", + "146.19.212.187", + "146.19.212.192", + "146.19.212.206", + "146.19.212.212", + "146.19.212.215", + "146.19.212.217", + "146.19.212.220", + "146.19.212.224", + "146.19.212.225", + "146.19.212.227", + "146.19.212.245", + "146.19.212.249", + "146.66.134.55", + "146.66.135.55", + "151.232.0.133", + "151.232.8.90", + "151.232.10.25", + "151.232.20.66", + "151.232.36.4", + "151.232.36.5", + "151.232.36.131", + "151.232.36.132", + "151.232.39.202", + "151.232.103.94", + "151.232.103.130", + "151.232.133.244", + "151.233.50.143", + "151.233.50.216", + "151.233.52.156", + "151.233.53.44", + "151.233.54.25", + "151.233.54.200", + "151.233.55.181", + "151.233.56.110", + "151.233.59.182", + "151.233.74.80", + "151.234.29.148", + "151.234.121.165", + "151.234.123.36", + "151.234.127.139", + "151.234.161.32", + "151.234.161.33", + "151.234.161.34", + "151.235.91.187", + "151.235.98.108", + "151.235.99.85", + "151.235.102.146", + "151.235.113.194", + "151.235.113.225", + "151.235.117.185", + "151.235.122.252", + "151.235.127.103", + "151.235.136.138", + "151.235.181.165", + "151.235.218.81", + "151.235.237.253", + "157.119.188.90", + "157.119.188.122", + "157.119.188.140", + "157.119.190.146", + "157.119.190.148", + "157.119.190.150", + "157.119.190.151", + "157.119.190.153", + "157.119.190.154", + "157.119.190.155", + "157.119.190.156", + "157.119.190.157", + "157.119.190.229", + "157.119.191.50", + "157.119.191.132", + "157.119.191.134", + "157.119.191.181", + "158.58.184.68", + "158.58.184.73", + "158.58.184.74", + "158.58.184.75", + "158.58.184.76", + "158.58.184.118", + "158.58.184.138", + "158.58.184.147", + "158.58.184.148", + "158.58.184.166", + "158.58.184.170", + "158.58.184.180", + "158.58.184.243", + "158.58.184.250", + "158.58.184.251", + "158.58.184.252", + "158.58.184.253", + "158.58.184.254", + "158.58.185.5", + "158.58.185.6", + "158.58.185.7", + "158.58.185.8", + "158.58.185.12", + "158.58.185.13", + "158.58.185.38", + "158.58.185.39", + "158.58.185.56", + "158.58.185.58", + "158.58.185.59", + "158.58.185.60", + "158.58.185.61", + "158.58.185.62", + "158.58.185.72", + "158.58.185.78", + "158.58.185.79", + "158.58.185.83", + "158.58.185.85", + "158.58.185.88", + "158.58.185.89", + "158.58.185.94", + "158.58.185.95", + "158.58.185.100", + "158.58.185.103", + "158.58.185.108", + "158.58.185.111", + "158.58.185.114", + "158.58.185.115", + "158.58.185.117", + "158.58.185.120", + "158.58.185.121", + "158.58.185.122", + "158.58.185.164", + "158.58.185.174", + "158.58.185.209", + "158.58.185.210", + "158.58.185.244", + "158.58.185.246", + "158.58.185.248", + "158.58.185.250", + "158.58.185.252", + "158.58.186.34", + "158.58.187.4", + "158.58.187.6", + "158.58.187.12", + "158.58.187.13", + "158.58.187.14", + "158.58.187.20", + "158.58.187.22", + "158.58.187.38", + "158.58.187.44", + "158.58.187.50", + "158.58.187.68", + "158.58.187.70", + "158.58.187.75", + "158.58.187.76", + "158.58.187.78", + "158.58.187.91", + "158.58.187.108", + "158.58.187.109", + "158.58.187.116", + "158.58.187.118", + "158.58.187.124", + "158.58.187.131", + "158.58.187.132", + "158.58.187.134", + "158.58.187.139", + "158.58.187.141", + "158.58.187.142", + "158.58.187.148", + "158.58.187.149", + "158.58.187.155", + "158.58.187.157", + "158.58.187.163", + "158.58.187.164", + "158.58.187.165", + "158.58.187.166", + "158.58.187.171", + "158.58.187.172", + "158.58.187.173", + "158.58.187.179", + "158.58.187.180", + "158.58.187.187", + "158.58.187.194", + "158.58.187.203", + "158.58.187.204", + "158.58.187.205", + "158.58.187.211", + "158.58.187.214", + "158.58.187.220", + "158.58.187.221", + "158.58.187.226", + "158.58.187.234", + "158.58.187.243", + "158.58.187.244", + "158.58.187.246", + "158.58.188.147", + "158.58.188.148", + "158.58.188.152", + "158.58.188.155", + "158.58.190.43", + "158.58.191.20", + "158.58.191.21", + "158.58.191.36", + "158.58.191.37", + "158.58.191.39", + "158.58.191.40", + "158.58.191.41", + "158.58.191.42", + "158.58.191.43", + "158.58.191.44", + "158.58.191.45", + "158.58.191.107", + "158.58.191.108", + "158.58.191.109", + "158.58.191.130", + "158.58.191.186", + "158.58.191.190", + "158.255.74.8", + "158.255.74.27", + "158.255.74.33", + "158.255.74.38", + "158.255.74.49", + "158.255.74.52", + "158.255.74.54", + "158.255.74.60", + "158.255.74.69", + "158.255.74.71", + "158.255.74.75", + "158.255.74.79", + "158.255.74.89", + "158.255.74.100", + "158.255.74.117", + "158.255.74.124", + "158.255.74.130", + "158.255.74.133", + "158.255.74.144", + "158.255.74.150", + "158.255.74.159", + "158.255.74.164", + "158.255.74.166", + "158.255.74.167", + "158.255.74.172", + "158.255.74.174", + "158.255.74.177", + "158.255.74.184", + "158.255.74.185", + "158.255.74.190", + "158.255.74.194", + "158.255.74.196", + "158.255.74.198", + "158.255.74.200", + "158.255.74.208", + "158.255.74.212", + "158.255.74.216", + "158.255.74.218", + "158.255.74.221", + "158.255.74.223", + "158.255.74.229", + "158.255.74.246", + "158.255.74.251", + "158.255.78.168", + "159.20.96.122", + "159.20.102.136", + "159.20.102.137", + "159.20.102.138", + "159.20.102.139", + "159.20.102.140", + "159.20.102.141", + "159.20.102.142", + "159.20.102.143", + "164.138.16.1", + "164.138.16.2", + "164.138.16.4", + "164.138.16.6", + "164.138.16.7", + "164.138.16.9", + "164.138.16.40", + "164.138.16.41", + "164.138.16.42", + "164.138.16.43", + "164.138.16.44", + "164.138.16.45", + "164.138.16.47", + "164.138.16.48", + "164.138.16.49", + "164.138.16.50", + "164.138.16.51", + "164.138.16.52", + "164.138.16.53", + "164.138.16.54", + "164.138.16.55", + "164.138.16.56", + "164.138.16.57", + "164.138.16.58", + "164.138.16.59", + "164.138.16.89", + "164.138.16.100", + "164.138.16.110", + "164.138.16.111", + "164.138.16.112", + "164.138.16.130", + "164.138.16.131", + "164.138.16.132", + "164.138.16.133", + "164.138.16.134", + "164.138.16.135", + "164.138.16.136", + "164.138.16.137", + "164.138.16.138", + "164.138.16.139", + "164.138.16.140", + "164.138.16.141", + "164.138.16.142", + "164.138.16.143", + "164.138.16.144", + "164.138.16.145", + "164.138.16.150", + "164.138.16.151", + "164.138.16.164", + "164.138.16.180", + "164.138.16.183", + "164.138.16.187", + "164.138.17.1", + "164.138.17.9", + "164.138.17.10", + "164.138.17.11", + "164.138.17.12", + "164.138.17.13", + "164.138.17.66", + "164.138.17.99", + "164.138.17.100", + "164.138.17.114", + "164.138.17.122", + "164.138.17.137", + "164.138.17.161", + "164.138.17.162", + "164.138.17.163", + "164.138.17.164", + "164.138.17.165", + "164.138.17.170", + "164.138.17.201", + "164.138.17.233", + "164.138.17.235", + "164.138.17.236", + "164.138.17.250", + "164.138.17.251", + "164.138.17.252", + "164.138.17.253", + "164.138.18.65", + "164.138.18.66", + "164.138.18.67", + "164.138.18.68", + "164.138.18.69", + "164.138.18.71", + "164.138.18.72", + "164.138.18.74", + "164.138.18.75", + "164.138.18.76", + "164.138.18.105", + "164.138.18.106", + "164.138.18.153", + "164.138.18.154", + "164.138.18.155", + "164.138.18.156", + "164.138.18.157", + "164.138.18.169", + "164.138.18.170", + "164.138.18.171", + "164.138.18.172", + "164.138.18.173", + "164.138.19.1", + "164.138.19.2", + "164.138.19.4", + "164.138.19.5", + "164.138.19.6", + "164.138.19.7", + "164.138.19.8", + "164.138.19.11", + "164.138.19.13", + "164.138.19.17", + "164.138.19.18", + "164.138.19.19", + "164.138.19.34", + "164.138.19.35", + "164.138.19.49", + "164.138.19.50", + "164.138.19.97", + "164.138.19.98", + "164.138.19.99", + "164.138.19.100", + "164.138.19.101", + "164.138.19.139", + "164.138.19.185", + "164.138.19.193", + "164.138.19.194", + "164.138.19.195", + "164.138.19.196", + "164.138.19.197", + "164.138.19.198", + "164.138.19.199", + "164.138.19.200", + "164.138.19.201", + "164.138.19.202", + "164.138.19.204", + "164.138.19.225", + "164.138.19.226", + "164.138.19.227", + "164.138.19.234", + "164.138.19.250", + "164.138.19.251", + "164.138.19.252", + "164.138.19.253", + "164.138.20.81", + "164.138.20.82", + "164.138.20.83", + "164.138.20.84", + "164.138.20.85", + "164.138.20.89", + "164.138.20.90", + "164.138.20.91", + "164.138.20.92", + "164.138.20.93", + "164.138.20.98", + "164.138.20.99", + "164.138.20.100", + "164.138.20.101", + "164.138.20.121", + "164.138.20.122", + "164.138.20.123", + "164.138.20.124", + "164.138.20.125", + "164.138.20.129", + "164.138.20.130", + "164.138.20.131", + "164.138.20.132", + "164.138.20.155", + "164.138.20.156", + "164.138.20.217", + "164.138.20.218", + "164.138.20.221", + "164.138.21.1", + "164.138.21.2", + "164.138.21.4", + "164.138.21.17", + "164.138.21.18", + "164.138.21.49", + "164.138.21.57", + "164.138.21.58", + "164.138.21.59", + "164.138.21.60", + "164.138.21.73", + "164.138.21.81", + "164.138.21.82", + "164.138.21.83", + "164.138.21.84", + "164.138.21.85", + "164.138.21.105", + "164.138.21.106", + "164.138.21.107", + "164.138.21.108", + "164.138.21.109", + "164.138.21.121", + "164.138.21.122", + "164.138.21.123", + "164.138.21.124", + "164.138.21.169", + "164.138.21.170", + "164.138.21.171", + "164.138.21.172", + "164.138.21.173", + "164.138.21.201", + "164.138.21.202", + "164.138.21.204", + "164.138.21.209", + "164.138.21.210", + "164.138.21.211", + "164.138.22.9", + "164.138.22.10", + "164.138.22.11", + "164.138.22.13", + "164.138.22.65", + "164.138.22.66", + "164.138.22.114", + "164.138.22.117", + "164.138.22.118", + "164.138.22.119", + "164.138.22.120", + "164.138.22.121", + "164.138.22.122", + "164.138.22.123", + "164.138.22.124", + "164.138.22.129", + "164.138.22.130", + "164.138.22.131", + "164.138.22.132", + "164.138.22.133", + "164.138.22.134", + "164.138.22.137", + "164.138.22.138", + "164.138.22.139", + "164.138.22.140", + "164.138.22.141", + "164.138.22.169", + "164.138.22.177", + "164.138.22.179", + "164.138.22.180", + "164.138.22.181", + "164.138.22.186", + "164.138.22.187", + "164.138.23.50", + "164.138.23.51", + "164.138.23.52", + "164.138.23.173", + "164.138.23.233", + "164.138.23.237", + "164.138.206.100", + "164.215.57.55", + "164.215.57.56", + "164.215.59.37", + "164.215.59.117", + "171.22.24.5", + "171.22.24.13", + "171.22.24.15", + "171.22.24.16", + "171.22.24.24", + "171.22.24.34", + "171.22.24.38", + "171.22.24.42", + "171.22.24.50", + "171.22.24.54", + "171.22.24.80", + "171.22.24.88", + "171.22.24.91", + "171.22.24.103", + "171.22.24.105", + "171.22.24.112", + "171.22.24.115", + "171.22.24.130", + "171.22.24.134", + "171.22.24.137", + "171.22.24.146", + "171.22.24.150", + "171.22.24.155", + "171.22.24.157", + "171.22.24.159", + "171.22.24.161", + "171.22.24.164", + "171.22.24.165", + "171.22.24.169", + "171.22.24.176", + "171.22.24.182", + "171.22.24.187", + "171.22.24.188", + "171.22.24.211", + "171.22.24.221", + "171.22.24.231", + "171.22.24.236", + "171.22.24.246", + "171.22.25.10", + "171.22.25.18", + "171.22.25.19", + "171.22.25.20", + "171.22.25.24", + "171.22.25.25", + "171.22.25.26", + "171.22.25.31", + "171.22.25.32", + "171.22.25.41", + "171.22.25.44", + "171.22.25.49", + "171.22.25.50", + "171.22.25.54", + "171.22.25.63", + "171.22.25.64", + "171.22.25.65", + "171.22.25.69", + "171.22.25.71", + "171.22.25.73", + "171.22.25.75", + "171.22.25.79", + "171.22.25.80", + "171.22.25.83", + "171.22.25.84", + "171.22.25.86", + "171.22.25.90", + "171.22.25.91", + "171.22.25.96", + "171.22.25.99", + "171.22.25.100", + "171.22.25.102", + "171.22.25.104", + "171.22.25.105", + "171.22.25.107", + "171.22.25.110", + "171.22.25.113", + "171.22.25.128", + "171.22.25.133", + "171.22.25.140", + "171.22.25.148", + "171.22.25.149", + "171.22.25.150", + "171.22.25.155", + "171.22.25.156", + "171.22.25.161", + "171.22.25.174", + "171.22.25.185", + "171.22.25.190", + "171.22.25.191", + "171.22.25.198", + "171.22.25.222", + "171.22.25.234", + "171.22.25.253", + "171.22.26.13", + "171.22.26.31", + "171.22.26.35", + "171.22.26.41", + "171.22.26.44", + "171.22.26.46", + "171.22.26.50", + "171.22.26.69", + "171.22.26.73", + "171.22.26.75", + "171.22.26.76", + "171.22.26.82", + "171.22.26.88", + "171.22.26.90", + "171.22.26.92", + "171.22.26.95", + "171.22.26.99", + "171.22.26.104", + "171.22.26.106", + "171.22.26.108", + "171.22.26.116", + "171.22.26.120", + "171.22.26.121", + "171.22.26.127", + "171.22.26.129", + "171.22.26.131", + "171.22.26.135", + "171.22.26.140", + "171.22.26.143", + "171.22.26.145", + "171.22.26.146", + "171.22.26.151", + "171.22.26.157", + "171.22.26.165", + "171.22.26.172", + "171.22.26.176", + "171.22.26.180", + "171.22.26.193", + "171.22.26.194", + "171.22.26.196", + "171.22.26.199", + "171.22.26.202", + "171.22.26.204", + "171.22.26.205", + "171.22.26.206", + "171.22.26.207", + "171.22.26.210", + "171.22.26.212", + "171.22.26.225", + "171.22.26.233", + "171.22.26.234", + "171.22.26.238", + "171.22.26.250", + "171.22.27.13", + "171.22.27.16", + "171.22.27.29", + "171.22.27.45", + "171.22.27.49", + "171.22.27.57", + "171.22.27.75", + "171.22.27.76", + "171.22.27.83", + "171.22.27.93", + "171.22.27.98", + "171.22.27.106", + "171.22.27.108", + "171.22.27.109", + "171.22.27.110", + "171.22.27.115", + "171.22.27.119", + "171.22.27.122", + "171.22.27.123", + "171.22.27.125", + "171.22.27.130", + "171.22.27.132", + "171.22.27.133", + "171.22.27.137", + "171.22.27.143", + "171.22.27.145", + "171.22.27.146", + "171.22.27.151", + "171.22.27.152", + "171.22.27.155", + "171.22.27.158", + "171.22.27.159", + "171.22.27.160", + "171.22.27.162", + "171.22.27.164", + "171.22.27.170", + "171.22.27.172", + "171.22.27.173", + "171.22.27.175", + "171.22.27.184", + "171.22.27.189", + "171.22.27.194", + "171.22.27.197", + "171.22.27.200", + "171.22.27.201", + "171.22.27.207", + "171.22.27.225", + "171.22.27.240", + "171.22.27.244", + "171.22.27.247", + "171.22.27.251", + "176.10.95.10", + "176.56.156.4", + "176.56.157.4", + "176.62.144.36", + "176.62.144.43", + "176.62.144.45", + "176.62.144.46", + "176.65.191.39", + "176.65.224.205", + "176.65.225.161", + "176.65.240.45", + "176.65.240.46", + "176.65.240.50", + "176.65.240.51", + "176.65.240.87", + "176.65.240.89", + "176.65.240.162", + "176.65.240.163", + "176.65.241.162", + "176.65.241.164", + "176.65.241.165", + "176.65.242.131", + "176.65.242.204", + "176.65.242.205", + "176.65.243.44", + "176.65.243.51", + "176.65.243.52", + "176.65.243.53", + "176.65.243.57", + "176.65.243.61", + "176.65.243.196", + "176.65.243.230", + "176.65.252.158", + "176.97.218.7", + "176.97.218.9", + "176.97.218.30", + "176.97.218.31", + "176.97.218.35", + "176.97.218.36", + "176.97.218.37", + "176.97.218.42", + "176.97.218.46", + "176.97.218.49", + "176.97.218.50", + "176.97.218.65", + "176.97.218.66", + "176.97.218.70", + "176.97.218.73", + "176.97.218.74", + "176.97.218.75", + "176.97.218.86", + "176.97.218.98", + "176.97.218.99", + "176.97.218.105", + "176.97.218.142", + "176.97.218.144", + "176.97.218.150", + "176.97.218.152", + "176.97.218.156", + "176.97.218.159", + "176.97.218.161", + "176.97.218.166", + "176.97.218.189", + "176.97.218.215", + "176.97.218.217", + "176.97.218.226", + "176.97.218.237", + "176.97.218.246", + "176.97.218.248", + "176.97.218.252", + "176.97.220.34", + "176.97.220.69", + "176.97.220.70", + "176.97.220.162", + "176.97.220.235", + "176.97.220.236", + "176.97.220.237", + "176.97.220.238", + "176.101.33.161", + "176.101.48.167", + "176.101.48.192", + "176.101.48.195", + "176.102.224.6", + "176.102.224.7", + "176.120.16.4", + "176.120.16.21", + "176.120.16.25", + "176.120.16.55", + "176.120.16.56", + "176.120.16.60", + "176.120.16.85", + "176.120.16.88", + "176.120.16.96", + "176.120.16.104", + "176.120.16.122", + "176.120.16.136", + "176.120.16.163", + "176.120.16.181", + "176.120.16.186", + "176.120.16.205", + "176.120.16.212", + "176.120.16.217", + "176.120.16.224", + "176.120.16.228", + "176.120.16.229", + "176.120.16.232", + "176.120.16.252", + "176.120.16.254", + "176.122.210.2", + "176.122.210.6", + "176.122.210.10", + "176.122.210.78", + "176.122.210.98", + "176.126.120.3", + "176.126.120.5", + "176.126.120.8", + "176.126.120.19", + "176.126.120.33", + "176.126.120.101", + "176.126.120.108", + "176.126.120.199", + "176.126.120.207", + "176.126.120.208", + "176.126.120.209", + "176.126.120.217", + "176.126.120.218", + "176.126.120.234", + "176.126.120.235", + "176.126.120.236", + "176.126.120.237", + "176.126.120.238", + "176.126.120.243", + "178.21.40.17", + "178.21.40.20", + "178.21.40.21", + "178.21.41.20", + "178.21.45.10", + "178.21.45.11", + "178.21.46.10", + "178.21.46.11", + "178.21.161.12", + "178.21.161.13", + "178.21.161.53", + "178.21.161.137", + "178.21.162.38", + "178.21.162.44", + "178.21.162.47", + "178.21.162.49", + "178.21.162.56", + "178.21.163.51", + "178.21.163.60", + "178.21.163.81", + "178.21.163.97", + "178.21.164.113", + "178.21.164.247", + "178.22.120.85", + "178.22.120.100", + "178.22.120.103", + "178.22.120.110", + "178.22.120.113", + "178.22.120.140", + "178.22.120.238", + "178.22.121.70", + "178.22.121.72", + "178.22.121.139", + "178.22.121.148", + "178.22.121.149", + "178.22.121.212", + "178.22.121.214", + "178.22.122.72", + "178.22.122.75", + "178.22.122.101", + "178.22.122.103", + "178.22.122.122", + "178.22.122.232", + "178.22.123.10", + "178.22.123.42", + "178.22.123.43", + "178.22.123.44", + "178.22.123.45", + "178.22.123.114", + "178.22.123.198", + "178.22.124.7", + "178.22.124.17", + "178.22.124.20", + "178.22.124.21", + "178.22.124.25", + "178.22.124.29", + "178.22.124.230", + "178.22.124.231", + "178.22.124.237", + "178.22.126.2", + "178.22.126.18", + "178.22.126.26", + "178.22.126.30", + "178.22.126.42", + "178.22.127.57", + "178.22.127.58", + "178.22.127.179", + "178.22.127.180", + "178.131.8.229", + "178.131.78.211", + "178.131.88.93", + "178.131.89.118", + "178.131.109.152", + "178.131.110.148", + "178.131.119.93", + "178.131.120.31", + "178.131.120.32", + "178.131.120.33", + "178.131.120.34", + "178.131.123.31", + "178.131.123.32", + "178.131.124.31", + "178.131.124.32", + "178.131.125.31", + "178.131.125.32", + "178.131.127.31", + "178.173.128.7", + "178.173.128.16", + "178.173.128.28", + "178.173.128.29", + "178.173.140.108", + "178.173.141.28", + "178.173.141.47", + "178.173.143.43", + "178.173.144.93", + "178.173.145.26", + "178.173.145.137", + "178.173.149.25", + "178.173.151.16", + "178.173.151.23", + "178.173.161.190", + "178.173.170.83", + "178.173.171.82", + "178.173.190.9", + "178.173.190.58", + "178.173.212.160", + "178.173.214.4", + "178.173.219.7", + "178.215.8.18", + "178.215.8.54", + "178.216.248.4", + "178.216.248.30", + "178.216.248.100", + "178.216.248.106", + "178.216.248.108", + "178.216.248.110", + "178.216.248.251", + "178.216.249.55", + "178.216.249.194", + "178.216.249.202", + "178.216.249.206", + "178.216.250.162", + "178.216.250.163", + "178.216.250.167", + "178.216.250.173", + "178.216.250.181", + "178.216.250.182", + "178.216.250.183", + "178.216.250.184", + "178.216.250.186", + "178.216.250.188", + "178.216.250.189", + "178.216.251.14", + "178.216.251.34", + "178.216.251.106", + "178.216.251.219", + "178.216.251.220", + "178.216.251.226", + "178.216.251.229", + "178.216.251.231", + "178.216.251.232", + "178.216.251.233", + "178.216.251.234", + "178.216.251.235", + "178.216.251.240", + "178.216.251.241", + "178.216.251.242", + "178.216.251.243", + "178.216.251.245", + "178.216.251.246", + "178.216.251.248", + "178.216.251.249", + "178.216.251.252", + "178.216.251.253", + "178.216.251.254", + "178.219.238.55", + "178.236.105.45", + "178.236.108.24", + "178.236.108.72", + "178.238.193.14", + "178.238.195.158", + "178.238.197.244", + "178.239.145.30", + "178.239.147.62", + "178.239.147.71", + "178.239.147.86", + "178.239.147.91", + "178.239.147.98", + "178.239.147.99", + "178.239.147.100", + "178.239.147.101", + "178.239.147.102", + "178.239.147.106", + "178.239.147.107", + "178.239.147.108", + "178.239.147.109", + "178.239.147.114", + "178.239.147.115", + "178.239.147.116", + "178.239.147.117", + "178.239.147.118", + "178.239.147.119", + "178.239.147.120", + "178.239.147.121", + "178.239.147.122", + "178.239.147.123", + "178.239.147.137", + "178.239.147.139", + "178.239.147.146", + "178.239.148.110", + "178.239.148.129", + "178.239.148.133", + "178.239.151.13", + "178.239.151.16", + "178.239.151.20", + "178.239.151.26", + "178.239.151.28", + "178.239.151.42", + "178.239.151.60", + "178.239.151.64", + "178.239.151.84", + "178.239.151.86", + "178.239.151.87", + "178.239.151.89", + "178.239.151.97", + "178.239.151.100", + "178.239.151.112", + "178.239.151.123", + "178.239.151.130", + "178.239.151.136", + "178.239.151.137", + "178.239.151.139", + "178.239.151.160", + "178.239.151.179", + "178.239.151.185", + "178.239.151.186", + "178.239.151.193", + "178.239.151.196", + "178.239.151.200", + "178.239.151.207", + "178.239.151.225", + "178.239.151.226", + "178.239.151.228", + "178.239.151.239", + "178.239.151.240", + "178.239.158.14", + "178.239.158.56", + "178.239.158.60", + "178.239.158.61", + "178.239.158.64", + "178.239.158.65", + "178.239.158.67", + "178.239.158.76", + "178.239.158.80", + "178.239.158.90", + "178.239.158.91", + "178.239.158.95", + "178.239.158.96", + "178.239.158.103", + "178.239.158.117", + "178.239.158.118", + "178.239.158.128", + "178.239.158.140", + "178.239.158.141", + "178.239.158.145", + "178.252.128.66", + "178.252.128.67", + "178.252.130.200", + "178.252.132.131", + "178.252.132.202", + "178.252.134.6", + "178.252.134.106", + "178.252.137.98", + "178.252.137.99", + "178.252.137.100", + "178.252.138.62", + "178.252.140.186", + "178.252.140.198", + "178.252.140.210", + "178.252.140.211", + "178.252.140.212", + "178.252.141.66", + "178.252.141.72", + "178.252.141.73", + "178.252.141.74", + "178.252.141.75", + "178.252.141.76", + "178.252.141.77", + "178.252.141.78", + "178.252.141.228", + "178.252.143.122", + "178.252.143.123", + "178.252.143.124", + "178.252.143.125", + "178.252.143.126", + "178.252.143.132", + "178.252.143.133", + "178.252.144.227", + "178.252.144.228", + "178.252.146.186", + "178.252.147.66", + "178.252.147.69", + "178.252.147.219", + "178.252.148.244", + "178.252.149.134", + "178.252.149.237", + "178.252.153.60", + "178.252.153.197", + "178.252.166.35", + "178.252.166.147", + "178.252.166.148", + "178.252.166.150", + "178.252.166.151", + "178.252.166.152", + "178.252.167.151", + "178.252.178.34", + "178.252.178.35", + "178.252.178.40", + "178.252.178.44", + "178.252.180.195", + "178.252.181.45", + "178.252.181.52", + "178.252.181.73", + "178.252.181.246", + "178.252.181.247", + "178.252.182.180", + "178.252.183.11", + "178.252.190.15", + "185.2.13.98", + "185.2.13.193", + "185.2.13.207", + "185.2.13.208", + "185.2.13.211", + "185.2.13.212", + "185.2.13.213", + "185.2.13.214", + "185.2.13.217", + "185.2.13.218", + "185.2.13.219", + "185.2.13.220", + "185.2.13.221", + "185.2.13.222", + "185.2.14.3", + "185.2.14.4", + "185.2.14.5", + "185.2.14.7", + "185.2.14.12", + "185.2.14.37", + "185.2.14.39", + "185.2.14.42", + "185.2.14.59", + "185.2.14.61", + "185.2.14.62", + "185.2.14.67", + "185.2.14.70", + "185.2.14.75", + "185.2.14.76", + "185.2.14.77", + "185.2.14.86", + "185.2.14.91", + "185.2.14.93", + "185.2.14.126", + "185.2.14.131", + "185.2.14.132", + "185.2.14.156", + "185.2.14.157", + "185.2.14.164", + "185.2.14.179", + "185.2.14.180", + "185.2.14.181", + "185.2.14.197", + "185.2.14.198", + "185.2.14.201", + "185.2.14.214", + "185.2.14.219", + "185.2.14.220", + "185.2.14.227", + "185.2.14.236", + "185.2.14.237", + "185.2.14.238", + "185.2.14.243", + "185.2.14.252", + "185.2.14.254", + "185.3.124.35", + "185.3.124.36", + "185.3.124.42", + "185.3.124.51", + "185.3.124.52", + "185.3.124.53", + "185.3.124.54", + "185.3.124.58", + "185.3.124.68", + "185.3.124.94", + "185.3.124.98", + "185.3.124.99", + "185.3.124.106", + "185.3.124.131", + "185.3.124.150", + "185.3.124.206", + "185.3.124.226", + "185.3.124.247", + "185.3.124.253", + "185.3.126.3", + "185.3.126.4", + "185.3.201.195", + "185.3.201.240", + "185.3.201.241", + "185.3.201.242", + "185.3.201.243", + "185.3.201.244", + "185.3.201.245", + "185.3.201.246", + "185.3.201.247", + "185.3.201.248", + "185.3.201.249", + "185.3.201.250", + "185.3.201.251", + "185.3.201.252", + "185.3.201.253", + "185.3.201.254", + "185.3.201.255", + "185.3.202.148", + "185.3.203.32", + "185.3.203.33", + "185.3.203.35", + "185.3.203.203", + "185.4.1.14", + "185.4.1.15", + "185.4.1.20", + "185.4.1.21", + "185.4.1.23", + "185.4.1.26", + "185.4.1.30", + "185.4.1.37", + "185.4.1.38", + "185.4.1.39", + "185.4.1.41", + "185.4.1.44", + "185.4.1.47", + "185.4.1.62", + "185.4.1.64", + "185.4.1.67", + "185.4.1.70", + "185.4.1.72", + "185.4.1.76", + "185.4.1.87", + "185.4.1.90", + "185.4.1.98", + "185.4.1.100", + "185.4.1.154", + "185.4.1.170", + "185.4.2.50", + "185.4.3.12", + "185.4.3.14", + "185.4.3.15", + "185.4.3.16", + "185.4.3.17", + "185.4.3.26", + "185.4.3.27", + "185.4.3.28", + "185.4.3.29", + "185.4.3.30", + "185.4.3.31", + "185.4.3.32", + "185.4.3.33", + "185.4.3.34", + "185.4.3.37", + "185.4.3.42", + "185.4.3.43", + "185.4.3.44", + "185.4.3.45", + "185.4.3.54", + "185.4.3.64", + "185.4.3.65", + "185.4.3.66", + "185.4.3.74", + "185.4.3.75", + "185.4.3.78", + "185.4.28.184", + "185.4.29.181", + "185.4.29.196", + "185.4.30.4", + "185.4.30.13", + "185.4.30.24", + "185.4.30.31", + "185.4.30.32", + "185.4.30.33", + "185.4.30.41", + "185.4.30.42", + "185.4.30.44", + "185.4.30.54", + "185.4.30.62", + "185.4.30.64", + "185.4.30.66", + "185.4.30.69", + "185.4.30.73", + "185.4.30.74", + "185.4.30.82", + "185.4.30.87", + "185.4.30.88", + "185.4.30.89", + "185.4.30.90", + "185.4.30.91", + "185.4.30.111", + "185.4.30.120", + "185.4.30.130", + "185.4.30.173", + "185.4.30.174", + "185.4.30.175", + "185.4.30.176", + "185.4.30.177", + "185.4.30.178", + "185.4.30.179", + "185.4.30.180", + "185.4.30.181", + "185.4.30.182", + "185.4.30.234", + "185.4.31.6", + "185.7.172.4", + "185.7.172.5", + "185.7.172.11", + "185.7.172.12", + "185.7.172.34", + "185.7.172.53", + "185.7.172.56", + "185.7.172.58", + "185.7.172.59", + "185.7.172.66", + "185.7.172.67", + "185.7.172.68", + "185.7.172.74", + "185.7.172.82", + "185.7.172.122", + "185.7.172.130", + "185.7.172.133", + "185.7.172.134", + "185.7.172.136", + "185.7.172.137", + "185.7.172.150", + "185.7.212.4", + "185.7.212.5", + "185.7.212.16", + "185.7.212.29", + "185.7.212.32", + "185.7.212.37", + "185.7.212.53", + "185.7.212.60", + "185.7.212.63", + "185.7.212.72", + "185.7.212.83", + "185.7.212.87", + "185.7.212.95", + "185.7.212.101", + "185.7.212.109", + "185.7.212.111", + "185.7.212.112", + "185.7.212.120", + "185.7.212.135", + "185.7.212.139", + "185.7.212.145", + "185.7.212.154", + "185.7.212.155", + "185.7.212.166", + "185.7.212.168", + "185.7.212.169", + "185.7.212.170", + "185.7.212.171", + "185.7.212.172", + "185.7.212.181", + "185.7.212.183", + "185.7.212.192", + "185.7.212.195", + "185.7.212.201", + "185.7.212.204", + "185.7.212.217", + "185.7.212.218", + "185.7.212.219", + "185.7.212.221", + "185.7.212.232", + "185.7.212.236", + "185.7.212.245", + "185.7.212.246", + "185.7.212.252", + "185.8.172.6", + "185.8.172.12", + "185.8.172.14", + "185.8.172.15", + "185.8.172.19", + "185.8.172.22", + "185.8.172.25", + "185.8.172.35", + "185.8.172.56", + "185.8.172.58", + "185.8.172.67", + "185.8.172.68", + "185.8.172.69", + "185.8.172.81", + "185.8.172.83", + "185.8.172.101", + "185.8.172.104", + "185.8.172.105", + "185.8.172.107", + "185.8.172.117", + "185.8.172.118", + "185.8.172.119", + "185.8.172.120", + "185.8.172.125", + "185.8.172.137", + "185.8.172.139", + "185.8.172.145", + "185.8.172.146", + "185.8.172.147", + "185.8.172.151", + "185.8.172.155", + "185.8.172.156", + "185.8.172.159", + "185.8.172.161", + "185.8.172.165", + "185.8.172.168", + "185.8.172.170", + "185.8.172.172", + "185.8.172.180", + "185.8.172.196", + "185.8.172.197", + "185.8.172.198", + "185.8.172.205", + "185.8.172.208", + "185.8.172.211", + "185.8.172.216", + "185.8.172.217", + "185.8.172.218", + "185.8.172.227", + "185.8.172.233", + "185.8.172.243", + "185.8.172.249", + "185.8.173.13", + "185.8.173.16", + "185.8.173.36", + "185.8.173.39", + "185.8.173.42", + "185.8.173.44", + "185.8.173.45", + "185.8.173.53", + "185.8.173.59", + "185.8.173.71", + "185.8.173.72", + "185.8.173.79", + "185.8.173.83", + "185.8.173.102", + "185.8.173.104", + "185.8.173.107", + "185.8.173.110", + "185.8.173.122", + "185.8.173.126", + "185.8.173.127", + "185.8.173.128", + "185.8.173.129", + "185.8.173.131", + "185.8.173.136", + "185.8.173.137", + "185.8.173.140", + "185.8.173.142", + "185.8.173.146", + "185.8.173.151", + "185.8.173.153", + "185.8.173.154", + "185.8.173.159", + "185.8.173.163", + "185.8.173.168", + "185.8.173.170", + "185.8.173.172", + "185.8.173.181", + "185.8.173.182", + "185.8.173.193", + "185.8.173.196", + "185.8.173.198", + "185.8.173.201", + "185.8.173.206", + "185.8.173.212", + "185.8.173.213", + "185.8.173.217", + "185.8.173.218", + "185.8.173.226", + "185.8.173.229", + "185.8.173.248", + "185.8.173.249", + "185.8.174.6", + "185.8.174.7", + "185.8.174.16", + "185.8.174.18", + "185.8.174.37", + "185.8.174.39", + "185.8.174.40", + "185.8.174.43", + "185.8.174.49", + "185.8.174.55", + "185.8.174.62", + "185.8.174.65", + "185.8.174.70", + "185.8.174.82", + "185.8.174.86", + "185.8.174.87", + "185.8.174.88", + "185.8.174.92", + "185.8.174.98", + "185.8.174.100", + "185.8.174.105", + "185.8.174.110", + "185.8.174.111", + "185.8.174.112", + "185.8.174.116", + "185.8.174.117", + "185.8.174.121", + "185.8.174.123", + "185.8.174.133", + "185.8.174.139", + "185.8.174.140", + "185.8.174.143", + "185.8.174.144", + "185.8.174.145", + "185.8.174.148", + "185.8.174.149", + "185.8.174.150", + "185.8.174.151", + "185.8.174.155", + "185.8.174.159", + "185.8.174.160", + "185.8.174.162", + "185.8.174.167", + "185.8.174.168", + "185.8.174.171", + "185.8.174.178", + "185.8.174.179", + "185.8.174.181", + "185.8.174.182", + "185.8.174.189", + "185.8.174.190", + "185.8.174.193", + "185.8.174.196", + "185.8.174.198", + "185.8.174.199", + "185.8.174.200", + "185.8.174.211", + "185.8.174.218", + "185.8.174.219", + "185.8.174.222", + "185.8.174.224", + "185.8.174.231", + "185.8.174.232", + "185.8.174.234", + "185.8.174.244", + "185.8.174.245", + "185.8.174.247", + "185.8.174.251", + "185.8.175.13", + "185.8.175.27", + "185.8.175.29", + "185.8.175.31", + "185.8.175.37", + "185.8.175.40", + "185.8.175.45", + "185.8.175.46", + "185.8.175.49", + "185.8.175.51", + "185.8.175.52", + "185.8.175.58", + "185.8.175.61", + "185.8.175.64", + "185.8.175.92", + "185.8.175.96", + "185.8.175.101", + "185.8.175.102", + "185.8.175.104", + "185.8.175.107", + "185.8.175.109", + "185.8.175.117", + "185.8.175.118", + "185.8.175.126", + "185.8.175.132", + "185.8.175.134", + "185.8.175.137", + "185.8.175.144", + "185.8.175.145", + "185.8.175.146", + "185.8.175.159", + "185.8.175.162", + "185.8.175.163", + "185.8.175.169", + "185.8.175.170", + "185.8.175.171", + "185.8.175.173", + "185.8.175.198", + "185.8.175.199", + "185.8.175.203", + "185.8.175.206", + "185.8.175.208", + "185.8.175.213", + "185.8.175.223", + "185.8.175.224", + "185.8.175.228", + "185.8.175.230", + "185.8.175.234", + "185.8.175.238", + "185.10.71.138", + "185.10.72.35", + "185.10.72.41", + "185.10.72.49", + "185.10.72.64", + "185.10.72.65", + "185.10.72.66", + "185.10.72.67", + "185.10.72.70", + "185.10.72.76", + "185.10.72.83", + "185.10.72.90", + "185.10.72.121", + "185.10.72.123", + "185.10.72.136", + "185.10.72.141", + "185.10.72.150", + "185.10.72.156", + "185.10.72.165", + "185.10.72.168", + "185.10.72.182", + "185.10.72.184", + "185.10.72.216", + "185.10.73.10", + "185.10.73.11", + "185.10.73.12", + "185.10.73.15", + "185.10.73.17", + "185.10.73.19", + "185.10.73.53", + "185.10.73.72", + "185.10.73.83", + "185.10.73.84", + "185.10.73.88", + "185.10.73.96", + "185.10.73.114", + "185.10.73.116", + "185.10.73.145", + "185.10.73.152", + "185.10.73.190", + "185.10.73.192", + "185.10.73.200", + "185.10.73.227", + "185.10.73.230", + "185.10.74.2", + "185.10.74.33", + "185.10.74.37", + "185.10.74.44", + "185.10.74.47", + "185.10.74.55", + "185.10.74.56", + "185.10.74.64", + "185.10.74.72", + "185.10.74.91", + "185.10.74.105", + "185.10.74.110", + "185.10.74.123", + "185.10.74.137", + "185.10.74.149", + "185.10.74.154", + "185.10.74.156", + "185.10.74.159", + "185.10.74.184", + "185.10.74.207", + "185.10.74.240", + "185.10.74.244", + "185.10.74.245", + "185.10.74.251", + "185.10.74.253", + "185.10.75.10", + "185.10.75.51", + "185.10.75.67", + "185.10.75.69", + "185.10.75.71", + "185.10.75.73", + "185.10.75.79", + "185.10.75.85", + "185.10.75.88", + "185.10.75.89", + "185.10.75.93", + "185.10.75.94", + "185.10.75.100", + "185.10.75.102", + "185.10.75.103", + "185.10.75.104", + "185.10.75.106", + "185.10.75.109", + "185.10.75.110", + "185.10.75.112", + "185.10.75.114", + "185.10.75.116", + "185.10.75.117", + "185.10.75.122", + "185.10.75.125", + "185.10.75.126", + "185.10.75.132", + "185.10.75.136", + "185.10.75.139", + "185.10.75.142", + "185.10.75.147", + "185.10.75.154", + "185.10.75.155", + "185.10.75.160", + "185.10.75.163", + "185.10.75.165", + "185.10.75.172", + "185.10.75.174", + "185.10.75.178", + "185.10.75.179", + "185.10.75.180", + "185.10.75.181", + "185.10.75.182", + "185.10.75.183", + "185.10.75.185", + "185.10.75.186", + "185.10.75.188", + "185.10.75.189", + "185.10.75.195", + "185.10.75.216", + "185.11.68.7", + "185.11.68.8", + "185.11.68.14", + "185.11.68.29", + "185.11.68.30", + "185.11.69.99", + "185.11.89.100", + "185.12.101.11", + "185.12.101.18", + "185.13.228.45", + "185.13.228.50", + "185.13.228.59", + "185.13.228.130", + "185.13.228.135", + "185.13.228.136", + "185.13.228.137", + "185.13.228.139", + "185.13.228.150", + "185.13.228.153", + "185.13.228.154", + "185.13.228.155", + "185.13.228.157", + "185.13.228.158", + "185.13.228.162", + "185.13.228.218", + "185.13.228.244", + "185.13.229.3", + "185.13.229.4", + "185.13.229.53", + "185.13.229.228", + "185.13.230.75", + "185.13.230.115", + "185.13.230.117", + "185.13.230.118", + "185.13.230.156", + "185.13.231.11", + "185.14.162.68", + "185.14.162.80", + "185.14.163.6", + "185.14.163.7", + "185.14.163.75", + "185.18.212.19", + "185.18.212.65", + "185.18.212.71", + "185.18.212.83", + "185.18.212.85", + "185.18.212.96", + "185.18.212.100", + "185.18.212.102", + "185.18.212.113", + "185.18.212.132", + "185.18.212.136", + "185.18.212.137", + "185.18.212.176", + "185.18.212.177", + "185.18.212.185", + "185.18.212.188", + "185.18.212.200", + "185.18.212.216", + "185.18.213.17", + "185.18.213.18", + "185.18.213.20", + "185.18.213.22", + "185.18.213.43", + "185.18.213.52", + "185.18.213.56", + "185.18.213.59", + "185.18.213.78", + "185.18.213.82", + "185.18.213.86", + "185.18.213.164", + "185.18.213.191", + "185.18.213.211", + "185.18.213.218", + "185.18.213.224", + "185.18.213.225", + "185.18.213.226", + "185.18.213.227", + "185.18.214.23", + "185.18.214.26", + "185.18.214.28", + "185.18.214.76", + "185.18.214.82", + "185.18.214.92", + "185.18.214.130", + "185.18.214.154", + "185.18.214.243", + "185.18.214.249", + "185.18.214.250", + "185.18.215.6", + "185.18.215.18", + "185.18.215.35", + "185.18.215.42", + "185.18.215.43", + "185.18.215.109", + "185.18.215.110", + "185.18.215.120", + "185.18.215.134", + "185.18.215.136", + "185.18.215.138", + "185.18.215.209", + "185.18.215.213", + "185.18.215.214", + "185.18.215.241", + "185.19.201.19", + "185.19.201.21", + "185.19.201.25", + "185.19.201.39", + "185.19.201.41", + "185.19.201.43", + "185.19.201.49", + "185.19.201.59", + "185.19.201.60", + "185.19.201.65", + "185.19.201.78", + "185.19.201.89", + "185.19.201.106", + "185.19.201.110", + "185.19.201.116", + "185.19.201.125", + "185.19.201.142", + "185.19.201.144", + "185.19.201.148", + "185.19.201.162", + "185.19.201.167", + "185.19.201.183", + "185.19.201.190", + "185.19.201.200", + "185.20.162.8", + "185.20.162.9", + "185.21.70.234", + "185.24.136.90", + "185.24.139.71", + "185.24.139.91", + "185.24.252.133", + "185.24.252.166", + "185.24.252.195", + "185.24.252.199", + "185.24.255.71", + "185.24.255.75", + "185.24.255.195", + "185.24.255.200", + "185.26.32.33", + "185.26.32.137", + "185.31.124.3", + "185.31.124.7", + "185.31.125.3", + "185.31.125.7", + "185.31.126.3", + "185.31.127.3", + "185.36.228.117", + "185.36.228.160", + "185.36.228.161", + "185.36.228.174", + "185.36.231.11", + "185.36.231.47", + "185.36.231.88", + "185.36.231.91", + "185.36.231.93", + "185.36.231.157", + "185.36.231.231", + "185.37.52.36", + "185.37.52.37", + "185.37.52.126", + "185.37.53.209", + "185.37.54.19", + "185.37.54.20", + "185.37.54.30", + "185.37.54.112", + "185.37.54.113", + "185.37.54.133", + "185.37.54.152", + "185.37.54.191", + "185.37.54.253", + "185.37.54.254", + "185.37.55.30", + "185.37.55.127", + "185.37.55.169", + "185.37.55.203", + "185.37.55.243", + "185.37.55.247", + "185.39.180.119", + "185.39.180.120", + "185.39.180.181", + "185.39.182.228", + "185.39.182.230", + "185.39.182.232", + "185.39.182.233", + "185.39.182.234", + "185.40.16.42", + "185.40.16.194", + "185.40.16.198", + "185.41.1.130", + "185.41.1.134", + "185.41.1.141", + "185.41.3.2", + "185.41.3.3", + "185.41.3.4", + "185.41.3.6", + "185.41.3.7", + "185.41.3.8", + "185.41.3.9", + "185.41.3.10", + "185.41.3.11", + "185.41.3.12", + "185.41.3.13", + "185.41.3.14", + "185.41.3.15", + "185.41.3.16", + "185.41.3.17", + "185.41.3.18", + "185.41.3.19", + "185.41.3.20", + "185.41.3.21", + "185.41.3.22", + "185.41.3.23", + "185.41.3.24", + "185.41.3.25", + "185.41.3.26", + "185.41.3.27", + "185.41.3.28", + "185.41.3.29", + "185.41.3.30", + "185.41.3.34", + "185.41.3.98", + "185.41.3.99", + "185.41.3.100", + "185.41.3.101", + "185.41.3.102", + "185.41.3.103", + "185.41.3.104", + "185.41.3.105", + "185.41.3.107", + "185.41.3.108", + "185.41.3.109", + "185.41.3.110", + "185.41.3.111", + "185.41.3.112", + "185.41.3.113", + "185.41.3.114", + "185.41.3.115", + "185.41.3.116", + "185.41.3.117", + "185.41.3.118", + "185.41.3.119", + "185.41.3.120", + "185.41.3.121", + "185.41.3.122", + "185.41.3.123", + "185.41.3.124", + "185.41.3.125", + "185.41.3.126", + "185.42.224.5", + "185.42.224.7", + "185.42.225.31", + "185.42.226.18", + "185.44.36.36", + "185.44.36.216", + "185.44.36.248", + "185.46.217.234", + "185.47.48.26", + "185.47.50.162", + "185.47.50.166", + "185.49.84.18", + "185.49.84.34", + "185.49.84.51", + "185.49.84.68", + "185.49.84.83", + "185.49.84.84", + "185.49.84.90", + "185.49.84.91", + "185.49.84.92", + "185.49.84.93", + "185.49.84.134", + "185.49.84.143", + "185.49.84.157", + "185.49.84.166", + "185.49.84.209", + "185.49.84.226", + "185.49.84.228", + "185.49.84.229", + "185.49.84.230", + "185.49.84.231", + "185.49.84.232", + "185.49.84.233", + "185.49.84.234", + "185.49.84.235", + "185.49.84.236", + "185.49.84.237", + "185.49.84.238", + "185.49.84.239", + "185.49.84.240", + "185.49.84.241", + "185.49.84.242", + "185.49.84.243", + "185.49.84.244", + "185.49.84.245", + "185.49.84.246", + "185.49.84.248", + "185.49.84.252", + "185.49.85.2", + "185.49.85.5", + "185.49.85.19", + "185.49.85.27", + "185.49.85.28", + "185.49.85.34", + "185.49.85.35", + "185.49.85.36", + "185.49.85.37", + "185.49.85.38", + "185.49.85.39", + "185.49.85.40", + "185.49.85.41", + "185.49.85.42", + "185.49.85.43", + "185.49.85.44", + "185.49.85.45", + "185.49.85.46", + "185.49.85.130", + "185.49.85.131", + "185.49.85.133", + "185.49.85.135", + "185.49.85.138", + "185.49.85.142", + "185.49.85.143", + "185.49.85.146", + "185.49.85.149", + "185.49.85.152", + "185.49.85.154", + "185.49.85.157", + "185.49.85.163", + "185.49.85.168", + "185.49.85.172", + "185.49.85.173", + "185.49.85.204", + "185.49.85.206", + "185.49.85.212", + "185.49.85.244", + "185.49.86.13", + "185.49.86.42", + "185.49.86.56", + "185.49.86.57", + "185.49.86.111", + "185.49.86.194", + "185.49.86.202", + "185.49.86.213", + "185.49.87.94", + "185.49.87.180", + "185.49.87.181", + "185.49.87.182", + "185.49.87.218", + "185.49.87.219", + "185.49.87.240", + "185.49.96.252", + "185.49.96.253", + "185.50.37.15", + "185.50.37.46", + "185.50.37.48", + "185.50.37.52", + "185.50.37.56", + "185.50.37.66", + "185.50.37.91", + "185.50.37.166", + "185.50.37.168", + "185.50.37.171", + "185.50.37.174", + "185.50.37.175", + "185.50.38.29", + "185.50.38.33", + "185.50.38.38", + "185.50.38.72", + "185.50.38.128", + "185.50.38.135", + "185.50.38.145", + "185.50.38.148", + "185.50.38.168", + "185.50.38.171", + "185.50.38.191", + "185.50.38.201", + "185.50.38.216", + "185.50.38.220", + "185.50.38.225", + "185.50.38.236", + "185.50.39.3", + "185.50.39.32", + "185.50.39.49", + "185.50.39.50", + "185.50.39.53", + "185.50.39.94", + "185.50.39.96", + "185.50.39.97", + "185.50.39.159", + "185.51.43.2", + "185.51.200.1", + "185.51.200.5", + "185.51.200.58", + "185.51.200.182", + "185.51.201.58", + "185.51.201.77", + "185.51.201.195", + "185.51.201.255", + "185.51.202.251", + "185.53.140.6", + "185.53.140.43", + "185.53.141.45", + "185.53.141.54", + "185.53.141.120", + "185.53.141.182", + "185.53.141.187", + "185.53.141.194", + "185.53.141.195", + "185.53.141.218", + "185.53.141.229", + "185.53.142.2", + "185.53.142.3", + "185.53.142.4", + "185.53.142.5", + "185.53.142.6", + "185.53.142.35", + "185.53.142.37", + "185.53.142.123", + "185.53.142.174", + "185.53.142.180", + "185.53.142.211", + "185.53.142.212", + "185.53.142.213", + "185.53.142.214", + "185.53.143.3", + "185.53.143.74", + "185.53.143.133", + "185.53.143.134", + "185.53.143.135", + "185.53.143.136", + "185.53.143.137", + "185.53.143.139", + "185.53.143.141", + "185.53.143.142", + "185.53.143.143", + "185.53.143.146", + "185.53.143.156", + "185.53.143.157", + "185.53.143.186", + "185.53.143.244", + "185.55.224.3", + "185.55.224.4", + "185.55.224.8", + "185.55.224.10", + "185.55.224.11", + "185.55.224.12", + "185.55.224.13", + "185.55.224.14", + "185.55.224.15", + "185.55.224.16", + "185.55.224.17", + "185.55.224.18", + "185.55.224.20", + "185.55.224.21", + "185.55.224.22", + "185.55.224.23", + "185.55.224.27", + "185.55.224.28", + "185.55.224.29", + "185.55.224.30", + "185.55.224.31", + "185.55.224.32", + "185.55.224.33", + "185.55.224.34", + "185.55.224.35", + "185.55.224.36", + "185.55.224.38", + "185.55.224.39", + "185.55.224.45", + "185.55.224.46", + "185.55.224.48", + "185.55.224.49", + "185.55.224.50", + "185.55.224.51", + "185.55.224.52", + "185.55.224.53", + "185.55.224.54", + "185.55.224.55", + "185.55.224.56", + "185.55.224.57", + "185.55.224.59", + "185.55.224.60", + "185.55.224.61", + "185.55.224.62", + "185.55.224.63", + "185.55.224.64", + "185.55.224.65", + "185.55.224.66", + "185.55.224.67", + "185.55.224.68", + "185.55.224.69", + "185.55.224.70", + "185.55.224.71", + "185.55.224.72", + "185.55.224.73", + "185.55.224.74", + "185.55.224.75", + "185.55.224.76", + "185.55.224.77", + "185.55.224.78", + "185.55.224.80", + "185.55.224.81", + "185.55.224.82", + "185.55.224.83", + "185.55.224.84", + "185.55.224.85", + "185.55.224.86", + "185.55.224.87", + "185.55.224.88", + "185.55.224.93", + "185.55.224.94", + "185.55.224.96", + "185.55.224.97", + "185.55.224.98", + "185.55.224.99", + "185.55.224.101", + "185.55.224.102", + "185.55.224.104", + "185.55.224.105", + "185.55.224.106", + "185.55.224.108", + "185.55.224.110", + "185.55.224.111", + "185.55.224.112", + "185.55.224.113", + "185.55.224.114", + "185.55.224.117", + "185.55.224.118", + "185.55.224.120", + "185.55.224.122", + "185.55.224.123", + "185.55.224.124", + "185.55.224.125", + "185.55.224.126", + "185.55.224.127", + "185.55.224.128", + "185.55.224.129", + "185.55.224.132", + "185.55.224.134", + "185.55.224.135", + "185.55.224.136", + "185.55.224.137", + "185.55.224.138", + "185.55.224.139", + "185.55.224.140", + "185.55.224.141", + "185.55.224.142", + "185.55.224.143", + "185.55.224.144", + "185.55.224.145", + "185.55.224.146", + "185.55.224.148", + "185.55.224.149", + "185.55.224.150", + "185.55.224.152", + "185.55.224.153", + "185.55.224.154", + "185.55.224.155", + "185.55.224.156", + "185.55.224.157", + "185.55.224.158", + "185.55.224.159", + "185.55.224.160", + "185.55.224.161", + "185.55.224.162", + "185.55.224.165", + "185.55.224.166", + "185.55.224.172", + "185.55.224.179", + "185.55.224.180", + "185.55.224.181", + "185.55.224.182", + "185.55.224.183", + "185.55.224.184", + "185.55.224.185", + "185.55.224.188", + "185.55.224.191", + "185.55.224.192", + "185.55.224.194", + "185.55.224.195", + "185.55.224.196", + "185.55.224.197", + "185.55.224.198", + "185.55.224.199", + "185.55.224.201", + "185.55.224.203", + "185.55.224.204", + "185.55.224.206", + "185.55.224.207", + "185.55.224.208", + "185.55.224.209", + "185.55.224.211", + "185.55.224.212", + "185.55.224.213", + "185.55.224.215", + "185.55.224.216", + "185.55.224.217", + "185.55.224.218", + "185.55.224.219", + "185.55.224.220", + "185.55.224.221", + "185.55.224.227", + "185.55.224.229", + "185.55.224.230", + "185.55.224.231", + "185.55.224.232", + "185.55.224.233", + "185.55.224.234", + "185.55.224.235", + "185.55.224.236", + "185.55.224.237", + "185.55.224.245", + "185.55.224.246", + "185.55.224.247", + "185.55.224.251", + "185.55.225.2", + "185.55.225.3", + "185.55.225.4", + "185.55.225.5", + "185.55.225.6", + "185.55.225.7", + "185.55.225.8", + "185.55.225.9", + "185.55.225.10", + "185.55.225.11", + "185.55.225.15", + "185.55.225.16", + "185.55.225.17", + "185.55.225.18", + "185.55.225.19", + "185.55.225.20", + "185.55.225.21", + "185.55.225.22", + "185.55.225.23", + "185.55.225.24", + "185.55.225.26", + "185.55.225.27", + "185.55.225.28", + "185.55.225.29", + "185.55.225.34", + "185.55.225.35", + "185.55.225.36", + "185.55.225.37", + "185.55.225.38", + "185.55.225.39", + "185.55.225.41", + "185.55.225.42", + "185.55.225.43", + "185.55.225.44", + "185.55.225.45", + "185.55.225.46", + "185.55.225.47", + "185.55.225.48", + "185.55.225.49", + "185.55.225.50", + "185.55.225.51", + "185.55.225.52", + "185.55.225.53", + "185.55.225.54", + "185.55.225.55", + "185.55.225.56", + "185.55.225.57", + "185.55.225.59", + "185.55.225.60", + "185.55.225.61", + "185.55.225.62", + "185.55.225.63", + "185.55.225.65", + "185.55.225.66", + "185.55.225.67", + "185.55.225.69", + "185.55.225.72", + "185.55.225.73", + "185.55.225.77", + "185.55.225.78", + "185.55.225.79", + "185.55.225.80", + "185.55.225.81", + "185.55.225.82", + "185.55.225.83", + "185.55.225.84", + "185.55.225.85", + "185.55.225.86", + "185.55.225.87", + "185.55.225.88", + "185.55.225.89", + "185.55.225.90", + "185.55.225.91", + "185.55.225.92", + "185.55.225.93", + "185.55.225.94", + "185.55.225.95", + "185.55.225.96", + "185.55.225.98", + "185.55.225.99", + "185.55.225.100", + "185.55.225.102", + "185.55.225.103", + "185.55.225.104", + "185.55.225.105", + "185.55.225.106", + "185.55.225.107", + "185.55.225.108", + "185.55.225.110", + "185.55.225.111", + "185.55.225.112", + "185.55.225.113", + "185.55.225.114", + "185.55.225.115", + "185.55.225.116", + "185.55.225.117", + "185.55.225.118", + "185.55.225.119", + "185.55.225.120", + "185.55.225.121", + "185.55.225.122", + "185.55.225.123", + "185.55.225.124", + "185.55.225.125", + "185.55.225.126", + "185.55.225.127", + "185.55.225.128", + "185.55.225.129", + "185.55.225.130", + "185.55.225.131", + "185.55.225.132", + "185.55.225.133", + "185.55.225.134", + "185.55.225.135", + "185.55.225.136", + "185.55.225.137", + "185.55.225.138", + "185.55.225.139", + "185.55.225.140", + "185.55.225.141", + "185.55.225.142", + "185.55.225.143", + "185.55.225.144", + "185.55.225.145", + "185.55.225.146", + "185.55.225.147", + "185.55.225.148", + "185.55.225.149", + "185.55.225.152", + "185.55.225.153", + "185.55.225.154", + "185.55.225.155", + "185.55.225.156", + "185.55.225.157", + "185.55.225.159", + "185.55.225.160", + "185.55.225.161", + "185.55.225.162", + "185.55.225.164", + "185.55.225.165", + "185.55.225.166", + "185.55.225.167", + "185.55.225.168", + "185.55.225.169", + "185.55.225.170", + "185.55.225.172", + "185.55.225.173", + "185.55.225.174", + "185.55.225.175", + "185.55.225.177", + "185.55.225.178", + "185.55.225.179", + "185.55.225.180", + "185.55.225.181", + "185.55.225.183", + "185.55.225.184", + "185.55.225.185", + "185.55.225.186", + "185.55.225.187", + "185.55.225.188", + "185.55.225.191", + "185.55.225.192", + "185.55.225.193", + "185.55.225.194", + "185.55.225.195", + "185.55.225.196", + "185.55.225.197", + "185.55.225.198", + "185.55.225.199", + "185.55.225.200", + "185.55.225.201", + "185.55.225.202", + "185.55.225.203", + "185.55.225.204", + "185.55.225.205", + "185.55.225.206", + "185.55.225.207", + "185.55.225.208", + "185.55.225.209", + "185.55.225.211", + "185.55.225.212", + "185.55.225.213", + "185.55.225.214", + "185.55.225.215", + "185.55.225.216", + "185.55.225.217", + "185.55.225.218", + "185.55.225.219", + "185.55.225.220", + "185.55.225.221", + "185.55.225.222", + "185.55.225.223", + "185.55.225.224", + "185.55.225.225", + "185.55.225.227", + "185.55.225.228", + "185.55.225.229", + "185.55.225.230", + "185.55.225.231", + "185.55.225.232", + "185.55.225.233", + "185.55.225.234", + "185.55.225.235", + "185.55.225.236", + "185.55.225.237", + "185.55.225.240", + "185.55.225.241", + "185.55.225.242", + "185.55.225.243", + "185.55.225.244", + "185.55.225.245", + "185.55.225.246", + "185.55.225.247", + "185.55.225.248", + "185.55.225.251", + "185.55.226.20", + "185.55.226.39", + "185.55.226.100", + "185.55.226.116", + "185.55.226.121", + "185.55.226.124", + "185.55.226.145", + "185.55.226.171", + "185.55.226.173", + "185.55.226.181", + "185.55.226.194", + "185.55.226.211", + "185.55.226.226", + "185.55.226.231", + "185.55.226.239", + "185.55.227.2", + "185.55.227.7", + "185.55.227.9", + "185.55.227.12", + "185.55.227.15", + "185.55.227.20", + "185.55.227.21", + "185.55.227.22", + "185.55.227.23", + "185.55.227.24", + "185.55.227.26", + "185.55.227.29", + "185.55.227.31", + "185.55.227.32", + "185.55.227.33", + "185.55.227.35", + "185.55.227.44", + "185.55.227.49", + "185.55.227.50", + "185.55.227.51", + "185.55.227.53", + "185.55.227.54", + "185.55.227.55", + "185.55.227.56", + "185.55.227.57", + "185.55.227.58", + "185.55.227.59", + "185.55.227.60", + "185.55.227.61", + "185.55.227.62", + "185.55.227.63", + "185.55.227.64", + "185.55.227.65", + "185.55.227.66", + "185.55.227.67", + "185.55.227.68", + "185.55.227.69", + "185.55.227.70", + "185.55.227.71", + "185.55.227.72", + "185.55.227.73", + "185.55.227.76", + "185.55.227.77", + "185.55.227.78", + "185.55.227.79", + "185.55.227.80", + "185.55.227.81", + "185.55.227.82", + "185.55.227.83", + "185.55.227.84", + "185.55.227.85", + "185.55.227.86", + "185.55.227.87", + "185.55.227.88", + "185.55.227.89", + "185.55.227.91", + "185.55.227.92", + "185.55.227.93", + "185.55.227.94", + "185.55.227.95", + "185.55.227.96", + "185.55.227.97", + "185.55.227.98", + "185.55.227.99", + "185.55.227.100", + "185.55.227.102", + "185.55.227.103", + "185.55.227.104", + "185.55.227.106", + "185.55.227.107", + "185.55.227.108", + "185.55.227.109", + "185.55.227.110", + "185.55.227.111", + "185.55.227.112", + "185.55.227.113", + "185.55.227.114", + "185.55.227.115", + "185.55.227.116", + "185.55.227.117", + "185.55.227.118", + "185.55.227.119", + "185.55.227.120", + "185.55.227.122", + "185.55.227.123", + "185.55.227.124", + "185.55.227.125", + "185.55.227.126", + "185.55.227.127", + "185.55.227.128", + "185.55.227.129", + "185.55.227.130", + "185.55.227.131", + "185.55.227.132", + "185.55.227.133", + "185.55.227.134", + "185.55.227.139", + "185.55.227.140", + "185.55.227.144", + "185.55.227.145", + "185.55.227.146", + "185.55.227.148", + "185.55.227.149", + "185.55.227.150", + "185.55.227.151", + "185.55.227.152", + "185.55.227.153", + "185.55.227.154", + "185.55.227.155", + "185.55.227.156", + "185.55.227.157", + "185.55.227.158", + "185.55.227.159", + "185.55.227.160", + "185.55.227.161", + "185.55.227.162", + "185.55.227.163", + "185.55.227.164", + "185.55.227.165", + "185.55.227.166", + "185.55.227.168", + "185.55.227.169", + "185.55.227.171", + "185.55.227.172", + "185.55.227.174", + "185.55.227.176", + "185.55.227.177", + "185.55.227.178", + "185.55.227.179", + "185.55.227.180", + "185.55.227.181", + "185.55.227.182", + "185.55.227.183", + "185.55.227.184", + "185.55.227.185", + "185.55.227.189", + "185.55.227.190", + "185.55.227.191", + "185.55.227.194", + "185.55.227.196", + "185.55.227.197", + "185.55.227.198", + "185.55.227.199", + "185.55.227.201", + "185.55.227.202", + "185.55.227.203", + "185.55.227.204", + "185.55.227.206", + "185.55.227.207", + "185.55.227.208", + "185.55.227.210", + "185.55.227.211", + "185.55.227.213", + "185.55.227.215", + "185.55.227.216", + "185.55.227.218", + "185.55.227.220", + "185.55.227.222", + "185.55.227.223", + "185.55.227.224", + "185.55.227.225", + "185.55.227.226", + "185.55.227.228", + "185.55.227.229", + "185.55.227.231", + "185.55.227.233", + "185.55.227.235", + "185.55.227.236", + "185.55.227.239", + "185.55.227.240", + "185.55.227.241", + "185.55.227.243", + "185.55.227.245", + "185.56.93.100", + "185.56.93.101", + "185.57.164.4", + "185.57.164.5", + "185.57.164.177", + "185.57.165.93", + "185.58.240.15", + "185.58.240.80", + "185.58.240.81", + "185.58.240.105", + "185.58.240.114", + "185.58.240.115", + "185.58.240.120", + "185.58.240.167", + "185.58.240.216", + "185.58.240.251", + "185.58.241.5", + "185.58.241.12", + "185.58.241.27", + "185.58.241.44", + "185.58.241.54", + "185.58.241.62", + "185.58.241.139", + "185.58.241.148", + "185.58.241.169", + "185.58.241.225", + "185.58.241.226", + "185.58.241.227", + "185.58.241.235", + "185.58.242.19", + "185.58.242.37", + "185.58.242.129", + "185.58.242.215", + "185.58.242.230", + "185.58.242.236", + "185.58.242.253", + "185.58.243.65", + "185.59.112.218", + "185.60.136.90", + "185.60.136.128", + "185.60.136.140", + "185.60.137.149", + "185.63.113.16", + "185.63.113.17", + "185.63.113.18", + "185.63.113.20", + "185.63.113.25", + "185.63.113.30", + "185.63.113.35", + "185.63.113.37", + "185.63.113.131", + "185.63.113.134", + "185.63.113.137", + "185.63.113.141", + "185.63.113.145", + "185.63.113.148", + "185.63.113.156", + "185.63.113.158", + "185.63.113.173", + "185.63.113.194", + "185.63.113.196", + "185.63.113.211", + "185.63.113.212", + "185.63.113.219", + "185.63.113.236", + "185.63.113.242", + "185.63.113.245", + "185.63.114.119", + "185.63.237.12", + "185.66.224.4", + "185.66.224.24", + "185.66.226.83", + "185.66.230.194", + "185.66.230.195", + "185.66.230.253", + "185.67.12.11", + "185.67.12.12", + "185.67.14.11", + "185.67.156.5", + "185.67.156.7", + "185.67.213.50", + "185.67.214.50", + "185.71.193.84", + "185.71.194.171", + "185.72.25.204", + "185.72.25.205", + "185.72.25.226", + "185.73.78.10", + "185.73.112.39", + "185.73.112.41", + "185.73.112.62", + "185.73.112.163", + "185.73.112.180", + "185.73.112.227", + "185.73.112.228", + "185.73.114.28", + "185.73.114.67", + "185.73.114.82", + "185.73.114.86", + "185.73.114.123", + "185.73.114.207", + "185.73.114.232", + "185.73.114.247", + "185.73.114.254", + "185.73.226.237", + "185.73.226.241", + "185.73.226.242", + "185.73.226.243", + "185.74.221.2", + "185.74.221.51", + "185.74.221.155", + "185.74.221.180", + "185.74.221.197", + "185.74.221.238", + "185.78.21.33", + "185.78.21.35", + "185.78.21.40", + "185.78.21.42", + "185.78.21.43", + "185.78.21.49", + "185.78.21.50", + "185.78.21.51", + "185.78.22.2", + "185.78.22.174", + "185.78.22.175", + "185.78.23.202", + "185.79.62.4", + "185.79.62.5", + "185.79.96.67", + "185.79.156.78", + "185.79.156.88", + "185.79.156.92", + "185.79.156.105", + "185.79.156.107", + "185.79.156.122", + "185.79.156.132", + "185.79.156.148", + "185.79.156.149", + "185.79.156.151", + "185.79.156.155", + "185.79.156.160", + "185.79.156.165", + "185.79.156.172", + "185.79.156.173", + "185.79.156.174", + "185.79.156.199", + "185.79.156.214", + "185.79.156.228", + "185.79.156.229", + "185.79.156.234", + "185.79.157.2", + "185.79.157.3", + "185.79.157.5", + "185.79.157.7", + "185.79.157.16", + "185.79.157.23", + "185.79.157.60", + "185.79.157.153", + "185.79.157.161", + "185.79.157.188", + "185.79.157.192", + "185.79.157.195", + "185.79.157.199", + "185.79.157.205", + "185.79.157.208", + "185.79.157.232", + "185.79.157.247", + "185.79.157.254", + "185.79.158.8", + "185.79.158.22", + "185.79.158.23", + "185.79.158.33", + "185.79.158.75", + "185.79.158.165", + "185.79.158.166", + "185.79.158.169", + "185.79.158.212", + "185.79.159.136", + "185.81.96.5", + "185.81.96.10", + "185.81.96.15", + "185.81.96.16", + "185.81.96.20", + "185.81.96.27", + "185.81.96.28", + "185.81.96.31", + "185.81.96.35", + "185.81.96.38", + "185.81.96.42", + "185.81.96.103", + "185.81.96.114", + "185.81.96.129", + "185.81.96.195", + "185.81.96.211", + "185.81.96.221", + "185.81.96.240", + "185.81.97.5", + "185.81.97.17", + "185.81.97.25", + "185.81.97.26", + "185.81.97.28", + "185.81.97.55", + "185.81.97.56", + "185.81.97.59", + "185.81.97.69", + "185.81.97.77", + "185.81.97.89", + "185.81.97.148", + "185.81.97.155", + "185.81.97.163", + "185.81.97.165", + "185.81.97.175", + "185.81.97.181", + "185.81.97.191", + "185.81.97.193", + "185.81.97.222", + "185.81.97.235", + "185.81.97.250", + "185.81.99.46", + "185.81.99.76", + "185.81.99.119", + "185.81.99.120", + "185.81.99.123", + "185.81.99.135", + "185.81.99.140", + "185.81.99.151", + "185.81.99.156", + "185.81.99.171", + "185.81.99.185", + "185.81.99.222", + "185.81.99.223", + "185.81.99.228", + "185.81.99.239", + "185.82.136.19", + "185.83.88.19", + "185.83.88.23", + "185.83.88.24", + "185.83.88.27", + "185.83.91.3", + "185.83.112.67", + "185.83.112.99", + "185.83.112.110", + "185.83.112.212", + "185.83.113.28", + "185.83.113.39", + "185.83.113.46", + "185.83.113.57", + "185.83.113.59", + "185.83.113.77", + "185.83.113.78", + "185.83.113.80", + "185.83.113.92", + "185.83.113.104", + "185.83.113.123", + "185.83.113.124", + "185.83.113.142", + "185.83.113.172", + "185.83.113.176", + "185.83.113.178", + "185.83.113.180", + "185.83.113.184", + "185.83.113.192", + "185.83.113.198", + "185.83.113.210", + "185.83.113.213", + "185.83.113.214", + "185.83.113.216", + "185.83.113.217", + "185.83.113.218", + "185.83.113.219", + "185.83.113.220", + "185.83.113.225", + "185.83.113.226", + "185.83.113.227", + "185.83.113.228", + "185.83.113.230", + "185.83.113.231", + "185.83.113.234", + "185.83.113.236", + "185.83.113.241", + "185.83.113.244", + "185.83.113.246", + "185.83.113.247", + "185.83.113.251", + "185.83.113.252", + "185.83.114.57", + "185.83.114.67", + "185.83.114.69", + "185.83.114.75", + "185.83.114.110", + "185.83.114.116", + "185.83.114.118", + "185.83.114.122", + "185.83.114.128", + "185.83.114.142", + "185.83.114.148", + "185.83.114.153", + "185.83.114.159", + "185.83.114.162", + "185.83.114.172", + "185.83.114.186", + "185.83.114.244", + "185.83.114.251", + "185.83.115.42", + "185.83.115.43", + "185.83.115.44", + "185.83.115.46", + "185.83.115.182", + "185.83.181.58", + "185.83.181.100", + "185.83.181.101", + "185.83.181.120", + "185.83.181.121", + "185.83.181.190", + "185.83.182.19", + "185.83.182.22", + "185.83.182.29", + "185.83.182.85", + "185.83.182.107", + "185.83.182.120", + "185.83.182.172", + "185.83.183.3", + "185.83.183.4", + "185.83.183.5", + "185.83.183.8", + "185.83.183.14", + "185.83.183.17", + "185.83.183.19", + "185.83.183.23", + "185.83.183.24", + "185.83.183.27", + "185.83.183.30", + "185.83.183.37", + "185.83.183.38", + "185.83.183.39", + "185.83.183.41", + "185.83.183.44", + "185.83.183.45", + "185.83.183.46", + "185.83.183.49", + "185.83.183.56", + "185.83.183.57", + "185.83.183.59", + "185.83.183.92", + "185.83.183.94", + "185.83.183.97", + "185.83.183.99", + "185.83.183.100", + "185.83.183.102", + "185.83.183.107", + "185.83.183.114", + "185.83.183.115", + "185.83.183.116", + "185.83.183.128", + "185.83.183.130", + "185.83.183.131", + "185.83.183.132", + "185.83.183.134", + "185.83.183.135", + "185.83.183.136", + "185.83.183.139", + "185.83.183.147", + "185.83.183.152", + "185.83.183.153", + "185.83.183.154", + "185.83.183.159", + "185.83.183.161", + "185.83.183.163", + "185.83.183.164", + "185.83.183.184", + "185.83.183.187", + "185.83.183.195", + "185.83.183.197", + "185.83.183.199", + "185.83.183.200", + "185.83.183.202", + "185.83.183.203", + "185.83.183.205", + "185.83.183.207", + "185.83.183.210", + "185.83.183.215", + "185.83.183.216", + "185.83.183.217", + "185.83.183.218", + "185.83.183.220", + "185.83.183.221", + "185.83.183.223", + "185.83.183.225", + "185.83.183.228", + "185.83.183.229", + "185.83.183.234", + "185.83.183.236", + "185.83.183.240", + "185.83.183.241", + "185.83.183.242", + "185.83.183.244", + "185.83.183.245", + "185.83.183.246", + "185.83.183.247", + "185.83.183.248", + "185.83.183.249", + "185.83.183.254", + "185.83.197.81", + "185.83.197.83", + "185.83.197.84", + "185.83.197.87", + "185.83.198.46", + "185.83.199.97", + "185.83.208.2", + "185.83.208.3", + "185.83.208.10", + "185.83.208.13", + "185.83.208.14", + "185.83.208.15", + "185.83.208.23", + "185.83.208.24", + "185.83.208.25", + "185.83.208.26", + "185.83.208.35", + "185.83.208.39", + "185.83.208.43", + "185.83.208.44", + "185.83.208.45", + "185.83.208.106", + "185.83.208.109", + "185.83.208.111", + "185.83.208.112", + "185.83.208.114", + "185.83.208.115", + "185.83.208.116", + "185.83.208.118", + "185.83.208.119", + "185.83.208.136", + "185.83.208.140", + "185.83.208.150", + "185.83.208.158", + "185.83.208.160", + "185.83.208.162", + "185.83.208.167", + "185.83.208.169", + "185.83.208.172", + "185.83.208.173", + "185.83.208.174", + "185.83.208.175", + "185.83.208.177", + "185.83.208.182", + "185.83.208.184", + "185.83.208.188", + "185.83.208.189", + "185.83.208.201", + "185.83.208.204", + "185.83.208.205", + "185.83.208.207", + "185.83.208.208", + "185.83.208.209", + "185.83.208.226", + "185.83.208.234", + "185.83.208.249", + "185.83.209.9", + "185.83.209.10", + "185.83.209.34", + "185.83.209.35", + "185.83.209.36", + "185.83.209.37", + "185.83.209.38", + "185.83.209.39", + "185.83.210.2", + "185.83.210.3", + "185.83.210.5", + "185.83.210.6", + "185.83.210.7", + "185.83.210.9", + "185.83.210.14", + "185.83.210.34", + "185.83.210.66", + "185.83.210.67", + "185.83.210.68", + "185.83.210.70", + "185.83.210.82", + "185.83.210.98", + "185.83.210.136", + "185.83.210.138", + "185.83.210.143", + "185.83.210.184", + "185.84.158.6", + "185.84.158.79", + "185.84.220.5", + "185.84.222.22", + "185.86.36.97", + "185.86.36.98", + "185.86.36.99", + "185.86.36.100", + "185.86.36.101", + "185.86.36.102", + "185.86.36.103", + "185.86.36.104", + "185.86.36.105", + "185.86.36.106", + "185.86.36.107", + "185.86.36.108", + "185.86.36.109", + "185.86.36.161", + "185.86.36.185", + "185.86.36.201", + "185.86.36.202", + "185.86.180.71", + "185.86.180.218", + "185.86.181.21", + "185.86.181.28", + "185.86.181.29", + "185.86.181.30", + "185.86.181.102", + "185.86.181.106", + "185.86.181.108", + "185.86.181.117", + "185.86.181.146", + "185.86.181.182", + "185.86.181.183", + "185.86.181.184", + "185.86.181.214", + "185.86.181.235", + "185.86.181.237", + "185.86.181.238", + "185.88.152.3", + "185.88.152.4", + "185.88.152.12", + "185.88.152.16", + "185.88.152.22", + "185.88.152.26", + "185.88.152.27", + "185.88.152.31", + "185.88.152.40", + "185.88.152.41", + "185.88.152.42", + "185.88.152.44", + "185.88.152.45", + "185.88.152.46", + "185.88.152.63", + "185.88.152.64", + "185.88.152.66", + "185.88.152.68", + "185.88.152.70", + "185.88.152.76", + "185.88.152.77", + "185.88.152.78", + "185.88.152.81", + "185.88.152.85", + "185.88.152.86", + "185.88.152.87", + "185.88.152.88", + "185.88.152.92", + "185.88.152.93", + "185.88.152.94", + "185.88.152.97", + "185.88.152.98", + "185.88.152.103", + "185.88.152.109", + "185.88.152.110", + "185.88.152.113", + "185.88.152.115", + "185.88.152.117", + "185.88.152.123", + "185.88.152.124", + "185.88.152.126", + "185.88.152.127", + "185.88.152.129", + "185.88.152.130", + "185.88.152.134", + "185.88.152.138", + "185.88.152.140", + "185.88.152.142", + "185.88.152.154", + "185.88.152.156", + "185.88.152.161", + "185.88.152.162", + "185.88.152.163", + "185.88.152.173", + "185.88.152.178", + "185.88.152.186", + "185.88.152.187", + "185.88.152.192", + "185.88.152.193", + "185.88.152.197", + "185.88.152.199", + "185.88.152.201", + "185.88.152.202", + "185.88.152.206", + "185.88.152.213", + "185.88.152.215", + "185.88.152.218", + "185.88.152.222", + "185.88.152.223", + "185.88.152.228", + "185.88.152.232", + "185.88.152.236", + "185.88.152.238", + "185.88.152.242", + "185.88.152.248", + "185.88.152.250", + "185.88.152.254", + "185.88.153.10", + "185.88.153.15", + "185.88.153.18", + "185.88.153.20", + "185.88.153.29", + "185.88.153.31", + "185.88.153.33", + "185.88.153.34", + "185.88.153.39", + "185.88.153.42", + "185.88.153.43", + "185.88.153.50", + "185.88.153.52", + "185.88.153.64", + "185.88.153.66", + "185.88.153.67", + "185.88.153.70", + "185.88.153.71", + "185.88.153.72", + "185.88.153.75", + "185.88.153.76", + "185.88.153.80", + "185.88.153.82", + "185.88.153.83", + "185.88.153.85", + "185.88.153.88", + "185.88.153.90", + "185.88.153.98", + "185.88.153.99", + "185.88.153.103", + "185.88.153.104", + "185.88.153.108", + "185.88.153.111", + "185.88.153.112", + "185.88.153.115", + "185.88.153.116", + "185.88.153.117", + "185.88.153.118", + "185.88.153.119", + "185.88.153.122", + "185.88.153.123", + "185.88.153.124", + "185.88.153.128", + "185.88.153.130", + "185.88.153.146", + "185.88.153.147", + "185.88.153.150", + "185.88.153.153", + "185.88.153.162", + "185.88.153.166", + "185.88.153.169", + "185.88.153.170", + "185.88.153.172", + "185.88.153.173", + "185.88.153.179", + "185.88.153.180", + "185.88.153.186", + "185.88.153.189", + "185.88.153.191", + "185.88.153.196", + "185.88.153.198", + "185.88.153.199", + "185.88.153.214", + "185.88.153.218", + "185.88.153.219", + "185.88.153.222", + "185.88.153.227", + "185.88.153.228", + "185.88.153.233", + "185.88.153.234", + "185.88.153.237", + "185.88.153.240", + "185.88.153.241", + "185.88.153.243", + "185.88.153.249", + "185.88.153.254", + "185.88.154.2", + "185.88.154.22", + "185.88.154.23", + "185.88.154.26", + "185.88.154.27", + "185.88.154.29", + "185.88.154.32", + "185.88.154.35", + "185.88.154.37", + "185.88.154.38", + "185.88.154.40", + "185.88.154.41", + "185.88.154.47", + "185.88.154.55", + "185.88.154.56", + "185.88.154.62", + "185.88.154.71", + "185.88.154.76", + "185.88.154.78", + "185.88.154.85", + "185.88.154.101", + "185.88.154.108", + "185.88.154.111", + "185.88.154.116", + "185.88.154.121", + "185.88.154.123", + "185.88.154.124", + "185.88.154.125", + "185.88.154.126", + "185.88.154.128", + "185.88.154.129", + "185.88.154.130", + "185.88.154.133", + "185.88.154.134", + "185.88.154.138", + "185.88.154.151", + "185.88.154.153", + "185.88.154.160", + "185.88.154.167", + "185.88.154.174", + "185.88.154.175", + "185.88.154.181", + "185.88.154.183", + "185.88.154.187", + "185.88.154.189", + "185.88.154.192", + "185.88.154.195", + "185.88.154.197", + "185.88.154.198", + "185.88.154.215", + "185.88.154.221", + "185.88.154.224", + "185.88.154.225", + "185.88.154.233", + "185.88.154.235", + "185.88.154.240", + "185.88.154.242", + "185.88.154.248", + "185.88.155.7", + "185.88.155.53", + "185.88.155.98", + "185.88.155.122", + "185.88.155.125", + "185.88.155.150", + "185.88.155.166", + "185.88.155.167", + "185.88.155.172", + "185.88.155.189", + "185.88.155.192", + "185.88.176.138", + "185.88.176.152", + "185.88.176.210", + "185.88.176.211", + "185.88.176.212", + "185.88.176.213", + "185.88.176.218", + "185.88.176.219", + "185.88.176.221", + "185.88.176.222", + "185.88.177.2", + "185.88.177.3", + "185.88.177.5", + "185.88.177.6", + "185.88.177.7", + "185.88.177.8", + "185.88.177.9", + "185.88.177.17", + "185.88.177.18", + "185.88.177.19", + "185.88.177.20", + "185.88.177.22", + "185.88.177.25", + "185.88.177.26", + "185.88.177.27", + "185.88.177.33", + "185.88.177.34", + "185.88.177.35", + "185.88.177.50", + "185.88.177.55", + "185.88.177.56", + "185.88.177.57", + "185.88.177.60", + "185.88.177.64", + "185.88.177.67", + "185.88.177.68", + "185.88.177.69", + "185.88.177.70", + "185.88.177.72", + "185.88.177.79", + "185.88.177.81", + "185.88.177.88", + "185.88.177.90", + "185.88.177.92", + "185.88.177.94", + "185.88.177.103", + "185.88.177.104", + "185.88.177.105", + "185.88.177.110", + "185.88.177.111", + "185.88.177.112", + "185.88.177.113", + "185.88.177.117", + "185.88.177.118", + "185.88.177.120", + "185.88.177.122", + "185.88.177.123", + "185.88.177.124", + "185.88.177.135", + "185.88.177.138", + "185.88.177.139", + "185.88.177.140", + "185.88.177.141", + "185.88.177.142", + "185.88.177.143", + "185.88.177.144", + "185.88.177.145", + "185.88.177.146", + "185.88.177.147", + "185.88.177.148", + "185.88.177.149", + "185.88.177.150", + "185.88.177.151", + "185.88.177.152", + "185.88.177.153", + "185.88.177.154", + "185.88.177.155", + "185.88.177.156", + "185.88.177.157", + "185.88.177.159", + "185.88.177.162", + "185.88.177.163", + "185.88.177.164", + "185.88.177.165", + "185.88.177.167", + "185.88.177.173", + "185.88.177.175", + "185.88.177.181", + "185.88.177.182", + "185.88.177.183", + "185.88.177.184", + "185.88.177.185", + "185.88.177.188", + "185.88.177.199", + "185.88.177.206", + "185.88.177.214", + "185.88.177.222", + "185.88.177.223", + "185.88.177.226", + "185.88.177.227", + "185.88.177.228", + "185.88.177.231", + "185.88.177.232", + "185.88.177.233", + "185.88.177.236", + "185.88.177.242", + "185.88.177.243", + "185.88.177.244", + "185.88.177.246", + "185.88.177.247", + "185.88.177.248", + "185.88.177.250", + "185.88.177.252", + "185.88.178.5", + "185.88.178.6", + "185.88.178.7", + "185.88.178.9", + "185.88.178.11", + "185.88.178.15", + "185.88.178.16", + "185.88.178.17", + "185.88.178.18", + "185.88.178.20", + "185.88.178.27", + "185.88.178.28", + "185.88.178.30", + "185.88.178.35", + "185.88.178.38", + "185.88.178.39", + "185.88.178.40", + "185.88.178.42", + "185.88.178.44", + "185.88.178.45", + "185.88.178.47", + "185.88.178.49", + "185.88.178.52", + "185.88.178.55", + "185.88.178.56", + "185.88.178.58", + "185.88.178.66", + "185.88.178.67", + "185.88.178.68", + "185.88.178.69", + "185.88.178.71", + "185.88.178.72", + "185.88.178.73", + "185.88.178.75", + "185.88.178.76", + "185.88.178.77", + "185.88.178.81", + "185.88.178.89", + "185.88.178.90", + "185.88.178.91", + "185.88.178.98", + "185.88.178.101", + "185.88.178.102", + "185.88.178.103", + "185.88.178.106", + "185.88.178.107", + "185.88.178.108", + "185.88.178.109", + "185.88.178.110", + "185.88.178.113", + "185.88.178.115", + "185.88.178.117", + "185.88.178.118", + "185.88.178.119", + "185.88.178.122", + "185.88.178.123", + "185.88.178.125", + "185.88.178.132", + "185.88.178.158", + "185.88.178.177", + "185.88.178.179", + "185.88.178.196", + "185.88.178.197", + "185.88.178.199", + "185.88.178.202", + "185.88.178.206", + "185.88.179.68", + "185.88.179.71", + "185.88.179.72", + "185.88.179.73", + "185.88.179.74", + "185.88.179.76", + "185.88.179.78", + "185.88.179.83", + "185.88.179.85", + "185.88.179.87", + "185.88.179.88", + "185.88.179.90", + "185.88.179.91", + "185.88.179.92", + "185.88.179.146", + "185.88.179.148", + "185.88.179.149", + "185.88.179.196", + "185.88.179.197", + "185.88.179.198", + "185.88.179.199", + "185.88.179.200", + "185.88.179.201", + "185.88.179.202", + "185.88.179.203", + "185.88.179.204", + "185.88.179.206", + "185.88.179.211", + "185.88.179.212", + "185.88.179.213", + "185.88.179.217", + "185.88.179.218", + "185.88.179.219", + "185.88.179.226", + "185.88.179.230", + "185.88.179.231", + "185.88.179.236", + "185.88.179.237", + "185.88.179.238", + "185.88.179.252", + "185.88.179.253", + "185.88.255.10", + "185.89.22.2", + "185.89.22.6", + "185.89.22.14", + "185.89.22.100", + "185.89.22.138", + "185.89.22.186", + "185.89.22.194", + "185.89.22.210", + "185.89.22.234", + "185.89.112.20", + "185.89.112.26", + "185.89.113.121", + "185.89.115.195", + "185.94.96.2", + "185.94.96.6", + "185.94.96.10", + "185.94.96.22", + "185.94.96.34", + "185.94.96.38", + "185.94.96.42", + "185.94.96.46", + "185.94.96.56", + "185.94.96.58", + "185.94.96.76", + "185.94.96.87", + "185.94.96.89", + "185.94.96.98", + "185.94.96.100", + "185.94.96.101", + "185.94.96.102", + "185.94.96.106", + "185.94.96.110", + "185.94.96.118", + "185.94.96.130", + "185.94.96.134", + "185.94.96.138", + "185.94.96.146", + "185.94.96.154", + "185.94.96.158", + "185.94.96.162", + "185.94.96.174", + "185.94.96.178", + "185.94.96.186", + "185.94.96.190", + "185.94.96.194", + "185.94.96.196", + "185.94.96.197", + "185.94.96.204", + "185.94.96.207", + "185.94.96.210", + "185.94.96.214", + "185.94.96.216", + "185.94.96.217", + "185.94.96.218", + "185.94.96.226", + "185.94.96.229", + "185.94.96.231", + "185.94.96.233", + "185.94.96.237", + "185.94.96.242", + "185.94.97.2", + "185.94.97.5", + "185.94.97.6", + "185.94.97.7", + "185.94.97.8", + "185.94.97.9", + "185.94.97.10", + "185.94.97.13", + "185.94.97.14", + "185.94.97.18", + "185.94.97.30", + "185.94.97.34", + "185.94.97.37", + "185.94.97.46", + "185.94.97.50", + "185.94.97.54", + "185.94.97.62", + "185.94.97.74", + "185.94.97.85", + "185.94.97.87", + "185.94.97.90", + "185.94.97.92", + "185.94.97.98", + "185.94.97.102", + "185.94.97.150", + "185.94.97.198", + "185.94.97.206", + "185.94.97.210", + "185.94.97.222", + "185.94.97.227", + "185.94.97.229", + "185.94.97.230", + "185.94.97.231", + "185.94.97.233", + "185.94.97.235", + "185.94.97.236", + "185.94.97.254", + "185.94.98.3", + "185.94.98.4", + "185.94.98.6", + "185.94.98.11", + "185.94.98.17", + "185.94.98.34", + "185.94.98.42", + "185.94.98.44", + "185.94.98.45", + "185.94.98.54", + "185.94.98.69", + "185.94.98.74", + "185.94.98.78", + "185.94.98.90", + "185.94.98.92", + "185.94.98.93", + "185.94.98.94", + "185.94.98.110", + "185.94.98.114", + "185.94.98.117", + "185.94.98.118", + "185.94.98.119", + "185.94.98.121", + "185.94.98.122", + "185.94.98.123", + "185.94.98.126", + "185.94.98.130", + "185.94.98.133", + "185.94.98.134", + "185.94.98.146", + "185.94.98.151", + "185.94.98.156", + "185.94.98.171", + "185.94.98.172", + "185.94.98.194", + "185.94.98.197", + "185.94.98.198", + "185.94.98.199", + "185.94.98.201", + "185.94.98.202", + "185.94.98.203", + "185.94.98.205", + "185.94.98.206", + "185.94.98.209", + "185.94.98.216", + "185.94.98.218", + "185.94.98.227", + "185.94.98.229", + "185.94.98.230", + "185.94.98.234", + "185.94.98.236", + "185.94.98.237", + "185.94.98.242", + "185.94.98.250", + "185.94.98.251", + "185.94.98.252", + "185.94.98.253", + "185.94.99.11", + "185.94.99.13", + "185.94.99.15", + "185.94.99.18", + "185.94.99.27", + "185.94.99.36", + "185.94.99.45", + "185.94.99.50", + "185.94.99.70", + "185.94.99.82", + "185.94.99.93", + "185.94.99.100", + "185.94.99.103", + "185.94.99.105", + "185.94.99.106", + "185.94.99.130", + "185.94.99.138", + "185.94.99.142", + "185.94.99.162", + "185.94.99.166", + "185.94.99.170", + "185.94.99.171", + "185.94.99.172", + "185.94.99.174", + "185.94.99.178", + "185.94.99.181", + "185.94.99.195", + "185.94.99.201", + "185.94.99.202", + "185.94.99.203", + "185.94.99.204", + "185.94.99.209", + "185.94.99.211", + "185.94.99.215", + "185.94.99.219", + "185.94.99.220", + "185.94.99.226", + "185.94.99.228", + "185.94.99.229", + "185.94.99.230", + "185.94.99.231", + "185.94.99.232", + "185.94.99.233", + "185.94.99.234", + "185.94.99.235", + "185.94.99.236", + "185.94.99.237", + "185.94.99.238", + "185.94.99.239", + "185.94.99.240", + "185.94.99.241", + "185.94.99.243", + "185.94.99.244", + "185.94.99.246", + "185.94.99.247", + "185.94.99.248", + "185.94.99.249", + "185.94.99.250", + "185.94.99.251", + "185.94.99.253", + "185.94.99.254", + "185.95.152.11", + "185.95.152.12", + "185.95.152.155", + "185.95.154.130", + "185.95.155.164", + "185.95.155.167", + "185.95.180.131", + "185.95.180.230", + "185.97.116.107", + "185.97.116.131", + "185.97.116.166", + "185.97.116.180", + "185.97.116.204", + "185.97.116.205", + "185.97.117.54", + "185.97.117.66", + "185.97.117.159", + "185.97.118.139", + "185.97.119.4", + "185.97.119.111", + "185.98.113.34", + "185.98.113.113", + "185.98.113.115", + "185.98.113.141", + "185.98.113.142", + "185.98.114.114", + "185.98.114.116", + "185.100.45.85", + "185.100.47.7", + "185.100.47.8", + "185.100.47.11", + "185.100.47.16", + "185.100.47.82", + "185.103.129.109", + "185.103.129.113", + "185.103.130.201", + "185.103.130.202", + "185.103.201.147", + "185.103.250.250", + "185.104.243.9", + "185.105.101.58", + "185.105.121.10", + "185.105.121.11", + "185.105.184.40", + "185.105.184.41", + "185.105.184.42", + "185.105.184.52", + "185.105.184.53", + "185.105.184.70", + "185.105.184.76", + "185.105.184.89", + "185.105.184.91", + "185.105.184.108", + "185.105.184.121", + "185.105.184.205", + "185.105.184.206", + "185.105.184.227", + "185.105.185.25", + "185.105.185.28", + "185.105.185.36", + "185.105.185.41", + "185.105.185.149", + "185.105.185.151", + "185.105.185.155", + "185.105.187.14", + "185.105.187.160", + "185.105.187.186", + "185.105.187.189", + "185.105.187.226", + "185.105.187.242", + "185.105.187.243", + "185.105.237.96", + "185.105.237.252", + "185.105.237.253", + "185.105.238.196", + "185.105.238.200", + "185.105.238.210", + "185.105.238.253", + "185.105.239.10", + "185.105.239.11", + "185.105.239.29", + "185.105.239.50", + "185.105.239.68", + "185.105.239.78", + "185.105.239.79", + "185.105.239.97", + "185.105.239.102", + "185.105.239.104", + "185.105.239.107", + "185.105.239.115", + "185.105.239.119", + "185.105.239.123", + "185.105.239.129", + "185.105.239.132", + "185.105.239.139", + "185.105.239.141", + "185.105.239.169", + "185.105.239.172", + "185.105.239.175", + "185.105.239.178", + "185.105.239.187", + "185.105.239.189", + "185.105.239.191", + "185.105.239.194", + "185.105.239.198", + "185.105.239.201", + "185.105.239.208", + "185.105.239.211", + "185.105.239.214", + "185.105.239.216", + "185.105.239.217", + "185.105.239.220", + "185.105.239.231", + "185.105.239.232", + "185.105.239.236", + "185.106.144.149", + "185.106.146.179", + "185.106.146.180", + "185.107.32.115", + "185.107.32.117", + "185.107.32.120", + "185.107.32.122", + "185.109.61.27", + "185.109.75.241", + "185.109.75.242", + "185.109.80.137", + "185.109.82.15", + "185.109.244.91", + "185.109.244.94", + "185.109.250.245", + "185.110.29.251", + "185.110.219.23", + "185.110.219.139", + "185.110.236.10", + "185.110.236.120", + "185.110.236.122", + "185.110.244.7", + "185.110.244.23", + "185.110.244.98", + "185.110.244.105", + "185.110.244.112", + "185.110.244.120", + "185.110.244.150", + "185.110.244.151", + "185.110.245.16", + "185.110.245.19", + "185.110.245.26", + "185.110.245.30", + "185.110.245.83", + "185.110.245.94", + "185.110.245.100", + "185.110.245.140", + "185.110.245.178", + "185.110.245.187", + "185.110.245.197", + "185.110.245.213", + "185.110.247.9", + "185.110.247.11", + "185.110.247.16", + "185.110.247.63", + "185.110.247.72", + "185.110.247.93", + "185.110.247.101", + "185.110.247.110", + "185.110.247.118", + "185.110.247.154", + "185.110.247.179", + "185.110.247.195", + "185.110.247.213", + "185.110.253.2", + "185.111.83.58", + "185.112.32.43", + "185.112.32.45", + "185.112.32.52", + "185.112.32.56", + "185.112.32.62", + "185.112.32.212", + "185.112.33.51", + "185.112.33.54", + "185.112.33.60", + "185.112.33.107", + "185.112.33.110", + "185.112.33.219", + "185.112.33.220", + "185.112.33.221", + "185.112.33.222", + "185.112.34.83", + "185.112.34.92", + "185.112.34.139", + "185.112.34.181", + "185.112.34.235", + "185.112.34.236", + "185.112.35.2", + "185.112.35.20", + "185.112.35.38", + "185.112.35.194", + "185.112.35.195", + "185.112.35.196", + "185.112.35.197", + "185.112.39.18", + "185.112.39.33", + "185.112.39.112", + "185.112.149.98", + "185.112.149.116", + "185.112.149.117", + "185.112.149.119", + "185.112.150.7", + "185.112.150.13", + "185.112.150.54", + "185.112.150.55", + "185.112.150.56", + "185.112.150.92", + "185.112.150.134", + "185.112.150.149", + "185.112.150.204", + "185.112.150.242", + "185.112.150.246", + "185.112.151.97", + "185.112.151.198", + "185.112.151.213", + "185.112.151.214", + "185.112.151.231", + "185.112.151.232", + "185.112.151.249", + "185.112.151.254", + "185.113.9.3", + "185.113.9.5", + "185.113.9.6", + "185.113.9.7", + "185.113.9.9", + "185.113.9.10", + "185.113.9.13", + "185.113.9.16", + "185.113.9.18", + "185.113.9.20", + "185.113.9.22", + "185.113.9.24", + "185.113.9.33", + "185.113.9.34", + "185.113.9.35", + "185.113.9.37", + "185.113.9.38", + "185.113.9.43", + "185.113.9.44", + "185.113.9.45", + "185.113.9.46", + "185.113.9.47", + "185.113.9.48", + "185.113.9.49", + "185.113.9.50", + "185.113.9.52", + "185.113.9.53", + "185.113.9.54", + "185.113.9.55", + "185.113.9.57", + "185.113.9.61", + "185.113.9.65", + "185.113.9.66", + "185.113.9.68", + "185.113.9.71", + "185.113.9.73", + "185.113.9.74", + "185.113.9.75", + "185.113.9.76", + "185.113.9.77", + "185.113.9.80", + "185.113.9.82", + "185.113.9.86", + "185.113.9.87", + "185.113.9.88", + "185.113.9.90", + "185.113.9.91", + "185.113.9.92", + "185.113.9.94", + "185.113.9.97", + "185.113.9.102", + "185.113.9.106", + "185.113.9.108", + "185.113.9.110", + "185.113.9.111", + "185.113.9.118", + "185.113.9.120", + "185.113.9.122", + "185.113.9.123", + "185.113.9.125", + "185.113.9.133", + "185.113.9.135", + "185.113.9.144", + "185.113.9.151", + "185.113.9.157", + "185.113.9.162", + "185.113.9.163", + "185.113.9.165", + "185.113.9.170", + "185.113.9.171", + "185.113.9.172", + "185.113.9.173", + "185.113.9.174", + "185.113.9.176", + "185.113.9.178", + "185.113.9.181", + "185.113.9.186", + "185.113.9.189", + "185.113.9.190", + "185.113.9.194", + "185.113.9.196", + "185.113.9.197", + "185.113.9.205", + "185.113.9.210", + "185.113.9.213", + "185.113.9.214", + "185.113.9.215", + "185.113.9.216", + "185.113.9.220", + "185.113.9.222", + "185.113.9.225", + "185.113.9.226", + "185.113.9.229", + "185.113.9.231", + "185.113.9.236", + "185.113.9.237", + "185.113.9.238", + "185.113.9.239", + "185.113.9.241", + "185.113.9.242", + "185.113.9.245", + "185.113.9.251", + "185.113.56.68", + "185.113.56.123", + "185.113.56.124", + "185.113.56.126", + "185.113.59.131", + "185.113.59.181", + "185.113.115.23", + "185.113.115.33", + "185.113.115.34", + "185.114.188.20", + "185.114.188.34", + "185.115.168.163", + "185.116.20.54", + "185.116.23.23", + "185.116.24.2", + "185.116.24.100", + "185.116.112.12", + "185.116.160.10", + "185.116.160.18", + "185.116.160.19", + "185.116.160.22", + "185.116.160.26", + "185.116.160.27", + "185.116.160.28", + "185.116.160.32", + "185.116.160.38", + "185.116.160.39", + "185.116.160.41", + "185.116.160.44", + "185.116.160.51", + "185.116.160.55", + "185.116.160.68", + "185.116.160.83", + "185.116.160.91", + "185.116.160.107", + "185.116.160.110", + "185.116.160.111", + "185.116.160.113", + "185.116.160.121", + "185.116.160.130", + "185.116.160.132", + "185.116.160.139", + "185.116.160.140", + "185.116.160.141", + "185.116.160.142", + "185.116.160.154", + "185.116.160.233", + "185.116.161.5", + "185.116.161.7", + "185.116.161.15", + "185.116.161.24", + "185.116.161.32", + "185.116.161.34", + "185.116.161.39", + "185.116.161.42", + "185.116.161.49", + "185.116.161.59", + "185.116.161.60", + "185.116.161.61", + "185.116.161.67", + "185.116.161.80", + "185.116.161.87", + "185.116.161.89", + "185.116.161.90", + "185.116.161.93", + "185.116.161.94", + "185.116.161.95", + "185.116.161.96", + "185.116.161.98", + "185.116.161.99", + "185.116.161.103", + "185.116.161.105", + "185.116.161.106", + "185.116.161.109", + "185.116.161.116", + "185.116.161.125", + "185.116.161.126", + "185.116.161.135", + "185.116.161.156", + "185.116.161.157", + "185.116.161.167", + "185.116.161.184", + "185.116.161.230", + "185.116.161.231", + "185.116.161.232", + "185.116.161.248", + "185.116.162.11", + "185.116.162.16", + "185.116.162.20", + "185.116.162.22", + "185.116.162.23", + "185.116.162.24", + "185.116.163.1", + "185.116.163.2", + "185.116.163.4", + "185.116.163.6", + "185.116.163.12", + "185.116.163.14", + "185.116.163.16", + "185.116.163.18", + "185.116.163.19", + "185.116.163.20", + "185.116.163.21", + "185.116.163.24", + "185.116.163.27", + "185.116.163.31", + "185.116.163.43", + "185.116.163.46", + "185.116.163.49", + "185.116.163.56", + "185.116.163.61", + "185.116.163.63", + "185.116.163.64", + "185.116.163.69", + "185.116.163.73", + "185.116.163.78", + "185.116.163.101", + "185.116.163.102", + "185.116.163.103", + "185.116.163.115", + "185.116.163.117", + "185.116.163.123", + "185.116.163.144", + "185.116.163.149", + "185.116.163.150", + "185.116.163.161", + "185.116.163.180", + "185.116.163.184", + "185.116.163.186", + "185.116.163.204", + "185.116.163.215", + "185.116.163.225", + "185.117.139.47", + "185.117.139.77", + "185.118.14.244", + "185.118.152.195", + "185.118.152.197", + "185.119.4.216", + "185.119.5.15", + "185.119.5.16", + "185.119.241.241", + "185.120.160.3", + "185.120.160.4", + "185.120.160.5", + "185.120.160.6", + "185.120.162.76", + "185.120.168.30", + "185.120.201.133", + "185.120.201.141", + "185.120.217.242", + "185.120.220.29", + "185.120.220.75", + "185.120.220.76", + "185.120.220.77", + "185.120.220.110", + "185.120.220.114", + "185.120.220.139", + "185.120.220.164", + "185.120.220.166", + "185.120.220.179", + "185.120.220.180", + "185.120.220.181", + "185.120.220.182", + "185.120.220.188", + "185.120.220.200", + "185.120.221.44", + "185.120.221.46", + "185.120.221.106", + "185.120.221.108", + "185.120.221.110", + "185.120.221.195", + "185.120.221.244", + "185.120.221.247", + "185.120.221.248", + "185.120.221.250", + "185.120.221.252", + "185.120.221.253", + "185.120.221.254", + "185.120.222.106", + "185.120.222.107", + "185.120.222.108", + "185.120.222.109", + "185.120.222.110", + "185.120.222.132", + "185.120.222.133", + "185.120.222.135", + "185.120.222.137", + "185.120.222.140", + "185.120.222.141", + "185.120.222.142", + "185.120.222.144", + "185.120.222.146", + "185.120.222.147", + "185.120.222.148", + "185.120.222.149", + "185.120.222.151", + "185.120.222.152", + "185.120.222.153", + "185.120.222.154", + "185.120.222.155", + "185.120.222.156", + "185.120.222.157", + "185.120.222.158", + "185.120.222.159", + "185.120.222.160", + "185.120.222.162", + "185.120.222.163", + "185.120.222.164", + "185.120.222.167", + "185.120.222.168", + "185.120.222.169", + "185.120.222.171", + "185.120.222.172", + "185.120.222.173", + "185.120.222.175", + "185.120.222.177", + "185.120.222.178", + "185.120.222.179", + "185.120.222.180", + "185.120.222.181", + "185.120.222.182", + "185.120.222.183", + "185.120.222.186", + "185.120.222.187", + "185.120.222.188", + "185.120.222.190", + "185.120.222.191", + "185.120.222.192", + "185.120.222.193", + "185.120.222.194", + "185.120.222.195", + "185.120.222.196", + "185.120.222.197", + "185.120.222.199", + "185.120.222.200", + "185.120.222.201", + "185.120.222.202", + "185.120.222.203", + "185.120.222.205", + "185.120.222.207", + "185.120.222.208", + "185.120.222.210", + "185.120.222.212", + "185.120.222.213", + "185.120.222.214", + "185.120.222.215", + "185.120.222.216", + "185.120.222.220", + "185.120.222.221", + "185.120.222.222", + "185.120.222.223", + "185.120.222.224", + "185.120.222.225", + "185.120.222.226", + "185.120.222.227", + "185.120.222.228", + "185.120.222.236", + "185.120.222.237", + "185.120.222.238", + "185.120.222.240", + "185.120.222.241", + "185.120.222.243", + "185.120.222.244", + "185.120.222.246", + "185.120.222.249", + "185.120.222.250", + "185.120.222.252", + "185.120.239.45", + "185.120.248.116", + "185.120.250.9", + "185.121.130.187", + "185.122.80.68", + "185.122.81.4", + "185.123.68.2", + "185.123.68.36", + "185.124.112.19", + "185.124.112.25", + "185.124.115.18", + "185.124.157.91", + "185.124.172.8", + "185.124.172.9", + "185.124.174.19", + "185.126.2.239", + "185.126.3.244", + "185.126.4.18", + "185.126.4.52", + "185.126.4.53", + "185.126.4.54", + "185.126.4.55", + "185.126.4.59", + "185.126.4.61", + "185.126.4.70", + "185.126.4.104", + "185.126.4.130", + "185.126.4.133", + "185.126.4.134", + "185.126.4.135", + "185.126.4.222", + "185.126.5.50", + "185.126.5.62", + "185.126.6.204", + "185.126.6.206", + "185.126.6.219", + "185.126.6.220", + "185.126.6.234", + "185.126.6.235", + "185.126.7.6", + "185.126.7.7", + "185.126.7.8", + "185.126.7.18", + "185.126.7.20", + "185.126.8.35", + "185.126.8.52", + "185.126.8.58", + "185.126.8.77", + "185.126.8.158", + "185.126.8.234", + "185.126.9.181", + "185.126.9.188", + "185.126.9.189", + "185.126.10.58", + "185.126.10.100", + "185.126.10.130", + "185.126.10.131", + "185.126.10.133", + "185.126.10.135", + "185.126.10.139", + "185.126.10.141", + "185.126.10.182", + "185.126.10.220", + "185.126.10.221", + "185.126.10.222", + "185.126.11.84", + "185.126.11.130", + "185.126.11.131", + "185.126.11.132", + "185.126.11.133", + "185.126.11.135", + "185.126.11.142", + "185.126.11.211", + "185.126.11.212", + "185.126.11.213", + "185.126.11.219", + "185.126.11.221", + "185.126.14.11", + "185.126.14.12", + "185.126.14.20", + "185.126.14.21", + "185.126.14.99", + "185.126.14.100", + "185.126.14.139", + "185.126.14.210", + "185.126.14.243", + "185.126.14.246", + "185.126.14.248", + "185.126.15.215", + "185.126.16.27", + "185.126.16.55", + "185.126.40.242", + "185.126.133.37", + "185.126.200.14", + "185.126.200.21", + "185.126.200.32", + "185.126.200.39", + "185.126.200.45", + "185.126.200.65", + "185.126.200.127", + "185.126.200.159", + "185.126.200.200", + "185.126.200.212", + "185.126.201.29", + "185.126.201.183", + "185.126.201.198", + "185.126.201.246", + "185.126.202.44", + "185.126.202.52", + "185.126.202.62", + "185.126.202.99", + "185.126.202.100", + "185.126.202.110", + "185.126.202.120", + "185.126.202.122", + "185.126.202.124", + "185.126.202.156", + "185.126.202.175", + "185.126.202.235", + "185.126.202.236", + "185.126.202.247", + "185.126.203.22", + "185.126.203.23", + "185.126.203.24", + "185.126.203.61", + "185.126.203.103", + "185.126.203.147", + "185.126.203.148", + "185.126.203.150", + "185.126.203.178", + "185.126.203.210", + "185.126.203.211", + "185.126.203.252", + "185.128.80.27", + "185.128.80.28", + "185.128.80.30", + "185.128.80.33", + "185.128.80.34", + "185.128.80.35", + "185.128.80.39", + "185.128.80.40", + "185.128.80.41", + "185.128.80.42", + "185.128.80.46", + "185.128.80.48", + "185.128.80.50", + "185.128.80.53", + "185.128.80.65", + "185.128.80.79", + "185.128.80.81", + "185.128.81.35", + "185.128.81.37", + "185.128.81.45", + "185.128.81.60", + "185.128.81.70", + "185.128.81.78", + "185.128.81.80", + "185.128.81.90", + "185.128.81.95", + "185.128.81.100", + "185.128.81.101", + "185.128.81.120", + "185.128.81.125", + "185.128.81.130", + "185.128.81.135", + "185.128.81.140", + "185.128.81.155", + "185.128.81.160", + "185.128.81.193", + "185.128.81.220", + "185.128.82.5", + "185.128.82.90", + "185.128.83.10", + "185.128.136.7", + "185.128.136.48", + "185.128.136.49", + "185.128.136.60", + "185.128.136.163", + "185.128.136.167", + "185.128.136.169", + "185.128.136.185", + "185.128.136.186", + "185.128.136.188", + "185.128.137.154", + "185.128.137.187", + "185.128.137.192", + "185.128.137.193", + "185.128.137.212", + "185.128.138.1", + "185.128.138.13", + "185.128.138.14", + "185.128.138.129", + "185.128.138.130", + "185.128.138.132", + "185.128.138.142", + "185.128.138.172", + "185.128.138.174", + "185.128.138.196", + "185.128.138.199", + "185.128.138.206", + "185.128.138.214", + "185.128.138.218", + "185.128.138.249", + "185.128.138.250", + "185.128.138.251", + "185.128.139.26", + "185.128.139.29", + "185.128.139.45", + "185.128.139.48", + "185.128.139.57", + "185.128.139.61", + "185.128.139.65", + "185.128.139.84", + "185.128.139.116", + "185.128.139.119", + "185.128.139.149", + "185.128.139.168", + "185.128.139.188", + "185.128.139.194", + "185.128.139.216", + "185.128.139.222", + "185.129.83.20", + "185.129.83.32", + "185.129.116.109", + "185.129.116.115", + "185.129.116.147", + "185.129.116.242", + "185.129.117.204", + "185.129.118.51", + "185.129.118.62", + "185.129.118.72", + "185.129.118.125", + "185.129.118.207", + "185.129.119.12", + "185.129.119.36", + "185.129.119.55", + "185.129.119.59", + "185.129.119.105", + "185.129.119.202", + "185.129.119.210", + "185.129.168.15", + "185.129.168.16", + "185.129.168.27", + "185.129.168.28", + "185.129.168.31", + "185.129.168.35", + "185.129.168.50", + "185.129.168.51", + "185.129.168.53", + "185.129.168.60", + "185.129.168.62", + "185.129.168.65", + "185.129.168.68", + "185.129.168.72", + "185.129.168.75", + "185.129.168.81", + "185.129.168.85", + "185.129.168.86", + "185.129.168.90", + "185.129.168.97", + "185.129.168.99", + "185.129.168.104", + "185.129.168.114", + "185.129.168.120", + "185.129.168.125", + "185.129.168.128", + "185.129.168.132", + "185.129.168.133", + "185.129.168.139", + "185.129.168.143", + "185.129.168.144", + "185.129.168.148", + "185.129.168.188", + "185.129.168.191", + "185.129.168.192", + "185.129.168.194", + "185.129.168.217", + "185.129.168.220", + "185.129.168.229", + "185.129.168.232", + "185.129.168.233", + "185.129.168.241", + "185.129.168.249", + "185.129.168.250", + "185.129.168.251", + "185.129.168.253", + "185.129.169.21", + "185.129.169.22", + "185.129.169.25", + "185.129.169.26", + "185.129.169.27", + "185.129.169.35", + "185.129.169.37", + "185.129.169.40", + "185.129.169.44", + "185.129.169.49", + "185.129.169.53", + "185.129.169.65", + "185.129.169.71", + "185.129.169.76", + "185.129.169.79", + "185.129.169.84", + "185.129.169.88", + "185.129.169.89", + "185.129.169.107", + "185.129.169.109", + "185.129.169.110", + "185.129.169.111", + "185.129.169.112", + "185.129.169.121", + "185.129.169.125", + "185.129.169.130", + "185.129.169.136", + "185.129.169.138", + "185.129.169.145", + "185.129.169.147", + "185.129.169.149", + "185.129.169.156", + "185.129.169.163", + "185.129.169.175", + "185.129.169.187", + "185.129.169.196", + "185.129.169.198", + "185.129.169.199", + "185.129.169.206", + "185.129.169.208", + "185.129.169.209", + "185.129.169.210", + "185.129.169.212", + "185.129.169.231", + "185.129.169.238", + "185.129.169.240", + "185.129.169.241", + "185.129.169.243", + "185.129.169.244", + "185.129.169.245", + "185.129.169.247", + "185.129.169.248", + "185.129.169.250", + "185.129.169.251", + "185.129.169.253", + "185.129.170.31", + "185.129.170.46", + "185.129.170.75", + "185.129.170.77", + "185.129.170.89", + "185.129.170.90", + "185.129.170.100", + "185.129.170.116", + "185.129.170.138", + "185.129.170.150", + "185.129.171.10", + "185.129.171.15", + "185.129.171.22", + "185.129.171.56", + "185.129.171.57", + "185.129.171.61", + "185.129.171.75", + "185.129.171.80", + "185.129.171.83", + "185.129.171.89", + "185.129.171.93", + "185.129.171.102", + "185.129.171.117", + "185.129.171.127", + "185.129.197.4", + "185.129.203.240", + "185.129.230.35", + "185.130.50.5", + "185.130.50.6", + "185.130.50.7", + "185.130.50.8", + "185.130.50.9", + "185.130.50.10", + "185.130.50.15", + "185.130.50.19", + "185.130.50.37", + "185.130.50.38", + "185.130.50.39", + "185.130.50.40", + "185.130.50.41", + "185.130.50.42", + "185.130.50.43", + "185.130.50.44", + "185.130.50.45", + "185.130.50.46", + "185.130.50.47", + "185.130.50.50", + "185.130.50.51", + "185.130.50.63", + "185.130.50.64", + "185.130.50.65", + "185.130.50.66", + "185.130.50.68", + "185.130.50.69", + "185.130.50.70", + "185.130.50.71", + "185.130.50.72", + "185.130.50.77", + "185.130.50.78", + "185.130.50.80", + "185.130.50.81", + "185.130.50.83", + "185.130.50.84", + "185.130.50.85", + "185.130.50.89", + "185.130.50.91", + "185.130.50.93", + "185.130.50.97", + "185.130.50.99", + "185.130.50.100", + "185.130.50.101", + "185.130.50.109", + "185.130.50.110", + "185.130.50.112", + "185.130.50.115", + "185.130.50.116", + "185.130.50.117", + "185.130.50.118", + "185.130.50.119", + "185.130.50.133", + "185.130.50.139", + "185.130.50.140", + "185.130.50.143", + "185.130.50.144", + "185.130.50.146", + "185.130.50.149", + "185.130.50.150", + "185.130.50.160", + "185.130.50.166", + "185.130.50.169", + "185.130.50.171", + "185.130.50.180", + "185.130.50.184", + "185.130.50.188", + "185.130.50.192", + "185.130.50.201", + "185.130.50.222", + "185.130.50.224", + "185.130.50.233", + "185.130.50.239", + "185.130.50.242", + "185.130.50.251", + "185.130.76.2", + "185.130.76.4", + "185.130.76.72", + "185.130.76.74", + "185.130.78.123", + "185.130.78.142", + "185.130.79.21", + "185.130.79.253", + "185.131.28.254", + "185.131.92.31", + "185.131.116.150", + "185.131.116.173", + "185.132.124.53", + "185.132.124.198", + "185.132.124.199", + "185.132.212.29", + "185.132.212.129", + "185.132.212.130", + "185.132.212.135", + "185.132.212.136", + "185.132.213.19", + "185.132.213.61", + "185.132.213.62", + "185.132.213.63", + "185.132.213.64", + "185.134.97.210", + "185.135.47.10", + "185.135.47.11", + "185.135.47.12", + "185.135.47.13", + "185.135.47.14", + "185.135.47.15", + "185.135.47.16", + "185.135.47.17", + "185.135.47.18", + "185.135.47.21", + "185.135.47.22", + "185.135.47.23", + "185.135.47.24", + "185.135.47.25", + "185.135.47.30", + "185.135.47.52", + "185.135.47.57", + "185.135.47.60", + "185.135.47.61", + "185.135.47.64", + "185.135.47.74", + "185.135.47.107", + "185.135.47.108", + "185.135.47.120", + "185.136.133.72", + "185.136.180.2", + "185.136.181.99", + "185.136.181.105", + "185.136.181.197", + "185.137.24.10", + "185.137.24.18", + "185.137.24.83", + "185.137.24.100", + "185.137.24.165", + "185.137.24.202", + "185.137.24.206", + "185.137.24.245", + "185.137.25.41", + "185.137.25.72", + "185.137.25.74", + "185.137.25.78", + "185.137.25.79", + "185.137.25.102", + "185.137.25.146", + "185.137.25.213", + "185.137.25.214", + "185.137.25.215", + "185.137.25.216", + "185.137.25.223", + "185.137.25.234", + "185.137.26.14", + "185.137.26.50", + "185.137.26.58", + "185.137.26.165", + "185.137.26.184", + "185.137.26.185", + "185.137.26.186", + "185.137.26.189", + "185.137.26.190", + "185.137.26.197", + "185.137.26.227", + "185.137.26.237", + "185.137.26.243", + "185.137.26.244", + "185.137.27.3", + "185.137.27.4", + "185.137.27.22", + "185.137.27.27", + "185.137.27.40", + "185.137.27.42", + "185.137.27.43", + "185.137.27.44", + "185.137.27.45", + "185.137.27.46", + "185.137.27.86", + "185.137.27.138", + "185.137.27.164", + "185.137.27.179", + "185.137.27.188", + "185.137.27.217", + "185.137.27.227", + "185.139.64.9", + "185.139.64.21", + "185.140.4.4", + "185.140.4.5", + "185.140.5.127", + "185.140.5.232", + "185.140.5.244", + "185.140.240.238", + "185.141.36.54", + "185.141.36.82", + "185.141.48.53", + "185.141.48.54", + "185.141.104.69", + "185.141.104.107", + "185.141.104.118", + "185.141.104.126", + "185.141.104.136", + "185.141.104.141", + "185.141.104.142", + "185.141.104.211", + "185.141.104.242", + "185.141.105.27", + "185.141.105.100", + "185.141.105.105", + "185.141.105.107", + "185.141.105.109", + "185.141.105.113", + "185.141.105.119", + "185.141.105.138", + "185.141.105.139", + "185.141.105.217", + "185.141.105.230", + "185.141.105.232", + "185.141.105.233", + "185.141.106.39", + "185.141.106.64", + "185.141.106.68", + "185.141.106.71", + "185.141.106.82", + "185.141.106.84", + "185.141.106.117", + "185.141.106.121", + "185.141.106.126", + "185.141.106.149", + "185.141.106.150", + "185.141.106.153", + "185.141.106.154", + "185.141.106.169", + "185.141.106.225", + "185.141.106.228", + "185.141.106.230", + "185.141.106.231", + "185.141.106.232", + "185.141.106.238", + "185.141.106.240", + "185.141.106.243", + "185.141.107.17", + "185.141.107.27", + "185.141.107.38", + "185.141.107.50", + "185.141.107.51", + "185.141.107.163", + "185.141.107.197", + "185.141.107.228", + "185.141.107.244", + "185.141.132.11", + "185.141.132.23", + "185.141.132.44", + "185.141.132.45", + "185.141.132.46", + "185.141.132.47", + "185.141.132.60", + "185.141.132.89", + "185.141.132.92", + "185.141.132.95", + "185.141.132.127", + "185.141.132.216", + "185.141.132.218", + "185.141.133.3", + "185.141.133.5", + "185.141.133.6", + "185.141.133.18", + "185.141.133.19", + "185.141.133.28", + "185.141.133.50", + "185.141.133.66", + "185.141.133.67", + "185.141.133.76", + "185.141.133.120", + "185.141.133.145", + "185.141.133.216", + "185.141.133.217", + "185.141.133.218", + "185.141.134.49", + "185.141.134.64", + "185.141.134.68", + "185.141.134.74", + "185.141.134.77", + "185.141.134.88", + "185.141.134.93", + "185.141.134.114", + "185.141.134.151", + "185.141.134.191", + "185.141.134.206", + "185.141.134.221", + "185.141.135.113", + "185.141.135.115", + "185.141.168.117", + "185.141.168.122", + "185.141.168.123", + "185.141.168.124", + "185.141.168.130", + "185.141.168.133", + "185.141.168.134", + "185.141.168.135", + "185.141.168.136", + "185.141.168.137", + "185.141.168.138", + "185.141.168.139", + "185.141.168.140", + "185.141.168.141", + "185.141.169.42", + "185.141.169.155", + "185.141.169.220", + "185.141.169.226", + "185.141.169.227", + "185.141.169.228", + "185.141.169.229", + "185.141.169.230", + "185.141.169.231", + "185.141.169.232", + "185.141.169.234", + "185.141.169.235", + "185.141.169.236", + "185.141.169.237", + "185.141.169.238", + "185.141.169.239", + "185.141.169.241", + "185.141.169.242", + "185.141.169.243", + "185.141.169.244", + "185.141.169.245", + "185.141.169.246", + "185.141.169.247", + "185.141.170.2", + "185.141.170.74", + "185.141.170.99", + "185.141.170.132", + "185.141.170.195", + "185.141.171.30", + "185.141.212.11", + "185.141.212.139", + "185.141.212.140", + "185.141.212.141", + "185.141.212.142", + "185.141.212.170", + "185.141.212.171", + "185.141.212.172", + "185.141.212.173", + "185.141.212.174", + "185.141.213.4", + "185.141.213.5", + "185.141.213.12", + "185.141.213.61", + "185.141.213.133", + "185.141.213.166", + "185.141.213.167", + "185.141.213.172", + "185.141.213.180", + "185.141.213.181", + "185.141.213.244", + "185.141.213.248", + "185.141.213.249", + "185.141.213.250", + "185.141.213.251", + "185.141.214.71", + "185.142.95.68", + "185.142.95.69", + "185.142.156.11", + "185.142.156.12", + "185.142.156.85", + "185.142.156.214", + "185.142.156.227", + "185.142.156.235", + "185.142.157.5", + "185.142.157.12", + "185.142.157.29", + "185.142.157.37", + "185.142.157.39", + "185.142.157.53", + "185.142.157.75", + "185.142.157.81", + "185.142.157.95", + "185.142.157.96", + "185.142.157.119", + "185.142.157.120", + "185.142.157.121", + "185.142.157.123", + "185.142.157.124", + "185.142.157.151", + "185.142.157.168", + "185.142.157.231", + "185.142.157.246", + "185.142.157.251", + "185.142.158.28", + "185.142.158.98", + "185.142.158.99", + "185.142.158.140", + "185.142.158.141", + "185.142.158.162", + "185.142.159.172", + "185.142.159.195", + "185.142.159.210", + "185.142.159.212", + "185.142.159.229", + "185.142.159.230", + "185.142.159.232", + "185.142.159.234", + "185.142.159.235", + "185.142.159.243", + "185.142.159.251", + "185.143.207.131", + "185.143.207.246", + "185.143.232.253", + "185.143.233.253", + "185.143.234.253", + "185.143.235.253", + "185.144.65.6", + "185.145.11.246", + "185.145.186.32", + "185.145.186.130", + "185.147.40.115", + "185.147.40.160", + "185.147.84.105", + "185.147.85.105", + "185.147.160.21", + "185.147.160.22", + "185.147.160.107", + "185.147.160.109", + "185.147.161.170", + "185.147.162.26", + "185.147.162.68", + "185.147.162.130", + "185.147.162.132", + "185.147.162.195", + "185.147.163.46", + "185.147.163.62", + "185.147.163.168", + "185.147.163.169", + "185.147.163.196", + "185.147.163.202", + "185.147.176.189", + "185.147.176.221", + "185.147.177.111", + "185.147.177.112", + "185.147.177.113", + "185.147.177.224", + "185.147.177.232", + "185.149.192.18", + "185.149.192.28", + "185.149.192.30", + "185.149.192.32", + "185.149.192.33", + "185.149.192.47", + "185.149.192.54", + "185.149.192.132", + "185.149.192.133", + "185.149.192.134", + "185.149.192.150", + "185.149.192.155", + "185.149.192.218", + "185.153.208.77", + "185.155.9.40", + "185.155.14.130", + "185.155.15.140", + "185.155.236.2", + "185.155.236.3", + "185.155.236.4", + "185.158.172.3", + "185.158.173.8", + "185.158.173.165", + "185.159.153.2", + "185.159.153.3", + "185.159.153.5", + "185.159.153.6", + "185.159.153.10", + "185.159.153.12", + "185.159.153.13", + "185.159.153.15", + "185.159.153.16", + "185.159.153.17", + "185.159.153.19", + "185.159.153.20", + "185.159.153.21", + "185.159.153.23", + "185.159.153.24", + "185.159.153.25", + "185.159.153.26", + "185.159.153.27", + "185.159.153.29", + "185.159.153.30", + "185.159.153.31", + "185.159.153.32", + "185.159.153.33", + "185.159.153.34", + "185.159.153.35", + "185.159.153.36", + "185.159.153.38", + "185.159.153.39", + "185.159.153.40", + "185.159.153.42", + "185.159.153.44", + "185.159.153.45", + "185.159.153.46", + "185.159.153.47", + "185.159.153.48", + "185.159.153.50", + "185.159.153.51", + "185.159.153.52", + "185.159.153.53", + "185.159.153.54", + "185.159.153.55", + "185.159.153.56", + "185.159.153.57", + "185.159.153.58", + "185.159.153.59", + "185.159.153.60", + "185.159.153.61", + "185.159.153.64", + "185.159.153.66", + "185.159.153.67", + "185.159.153.68", + "185.159.153.69", + "185.159.153.70", + "185.159.153.71", + "185.159.153.72", + "185.159.153.73", + "185.159.153.74", + "185.159.153.75", + "185.159.153.77", + "185.159.153.78", + "185.159.153.79", + "185.159.153.80", + "185.159.153.81", + "185.159.153.82", + "185.159.153.83", + "185.159.153.84", + "185.159.153.85", + "185.159.153.86", + "185.159.153.87", + "185.159.153.88", + "185.159.153.89", + "185.159.153.90", + "185.159.153.91", + "185.159.153.92", + "185.159.153.93", + "185.159.153.98", + "185.159.153.99", + "185.159.153.100", + "185.159.153.102", + "185.159.153.103", + "185.159.153.104", + "185.159.153.105", + "185.159.153.106", + "185.159.153.108", + "185.159.153.110", + "185.159.153.111", + "185.159.153.112", + "185.159.153.113", + "185.159.153.114", + "185.159.153.115", + "185.159.153.116", + "185.159.153.119", + "185.159.153.120", + "185.159.153.121", + "185.159.153.122", + "185.159.153.123", + "185.159.153.124", + "185.159.153.125", + "185.159.153.126", + "185.159.153.127", + "185.159.153.128", + "185.159.153.129", + "185.159.153.131", + "185.159.153.132", + "185.159.153.133", + "185.159.153.134", + "185.159.153.135", + "185.159.153.136", + "185.159.153.137", + "185.159.153.140", + "185.159.153.141", + "185.159.153.142", + "185.159.153.145", + "185.159.153.147", + "185.159.153.148", + "185.159.153.149", + "185.159.153.150", + "185.159.153.151", + "185.159.153.152", + "185.159.153.153", + "185.159.153.154", + "185.159.153.156", + "185.159.153.157", + "185.159.153.158", + "185.159.153.159", + "185.159.153.160", + "185.159.153.161", + "185.159.153.162", + "185.159.153.163", + "185.159.153.165", + "185.159.153.166", + "185.159.153.171", + "185.159.153.172", + "185.159.153.174", + "185.159.153.175", + "185.159.153.176", + "185.159.153.177", + "185.159.153.186", + "185.159.153.187", + "185.159.153.189", + "185.159.153.191", + "185.159.153.193", + "185.159.153.194", + "185.159.153.196", + "185.159.153.199", + "185.159.153.200", + "185.159.153.201", + "185.159.153.202", + "185.159.153.203", + "185.159.153.204", + "185.159.153.205", + "185.159.153.207", + "185.159.153.208", + "185.159.153.211", + "185.159.153.214", + "185.159.153.216", + "185.159.153.218", + "185.159.153.219", + "185.159.153.220", + "185.159.153.226", + "185.159.153.227", + "185.159.153.228", + "185.159.153.235", + "185.159.153.253", + "185.159.154.2", + "185.159.154.3", + "185.159.154.4", + "185.159.154.5", + "185.159.154.7", + "185.159.154.9", + "185.159.154.10", + "185.159.154.14", + "185.159.154.16", + "185.159.154.17", + "185.159.154.19", + "185.159.154.20", + "185.159.154.21", + "185.159.154.22", + "185.159.154.23", + "185.159.154.25", + "185.159.154.26", + "185.159.154.27", + "185.159.154.28", + "185.159.154.29", + "185.159.154.30", + "185.159.154.31", + "185.159.154.32", + "185.159.154.33", + "185.159.154.34", + "185.159.154.35", + "185.159.154.36", + "185.159.154.37", + "185.159.154.38", + "185.159.154.39", + "185.159.154.40", + "185.159.154.41", + "185.159.154.42", + "185.159.154.43", + "185.159.154.44", + "185.159.154.45", + "185.159.154.46", + "185.159.154.47", + "185.159.154.48", + "185.159.154.49", + "185.159.154.50", + "185.159.154.51", + "185.159.154.52", + "185.159.154.53", + "185.159.154.54", + "185.159.154.55", + "185.159.154.56", + "185.159.154.57", + "185.159.154.61", + "185.159.154.63", + "185.159.154.64", + "185.159.154.65", + "185.159.154.66", + "185.159.154.67", + "185.159.154.68", + "185.159.154.69", + "185.159.154.70", + "185.159.154.71", + "185.159.154.72", + "185.159.154.73", + "185.159.154.74", + "185.159.154.75", + "185.159.154.79", + "185.159.154.81", + "185.159.154.82", + "185.159.154.84", + "185.159.154.85", + "185.159.154.86", + "185.159.154.87", + "185.159.154.88", + "185.159.154.89", + "185.159.154.90", + "185.159.154.91", + "185.159.154.92", + "185.159.154.93", + "185.159.154.94", + "185.159.154.98", + "185.159.154.99", + "185.159.154.100", + "185.159.154.102", + "185.159.154.104", + "185.159.154.105", + "185.159.154.106", + "185.159.154.107", + "185.159.154.108", + "185.159.154.109", + "185.159.154.111", + "185.159.154.114", + "185.159.154.115", + "185.159.154.118", + "185.159.154.120", + "185.159.154.124", + "185.159.154.125", + "185.159.154.127", + "185.159.154.128", + "185.159.154.130", + "185.159.154.132", + "185.159.154.133", + "185.159.154.134", + "185.159.154.137", + "185.159.154.143", + "185.159.154.147", + "185.159.154.149", + "185.159.154.150", + "185.159.154.151", + "185.159.154.152", + "185.159.154.153", + "185.159.154.154", + "185.159.154.157", + "185.159.154.159", + "185.159.154.161", + "185.159.154.162", + "185.159.154.163", + "185.159.154.164", + "185.159.154.165", + "185.159.154.167", + "185.159.154.168", + "185.159.154.169", + "185.159.154.170", + "185.159.154.171", + "185.159.154.172", + "185.159.154.173", + "185.159.154.177", + "185.159.154.178", + "185.159.154.179", + "185.159.154.180", + "185.159.154.181", + "185.159.154.188", + "185.159.154.189", + "185.159.154.190", + "185.159.154.191", + "185.159.154.192", + "185.159.154.197", + "185.159.154.198", + "185.159.154.199", + "185.159.154.200", + "185.159.154.201", + "185.159.154.202", + "185.159.154.204", + "185.159.154.207", + "185.159.154.208", + "185.159.154.209", + "185.159.154.210", + "185.159.154.211", + "185.159.154.212", + "185.159.154.213", + "185.159.154.214", + "185.159.154.215", + "185.159.154.219", + "185.159.154.221", + "185.159.154.222", + "185.159.154.224", + "185.159.154.225", + "185.159.154.226", + "185.159.154.227", + "185.159.154.228", + "185.159.154.242", + "185.159.154.245", + "185.159.154.249", + "185.159.154.250", + "185.159.154.251", + "185.159.154.252", + "185.159.154.253", + "185.159.155.2", + "185.159.155.3", + "185.159.179.1", + "185.159.179.2", + "185.159.179.71", + "185.159.179.72", + "185.159.179.74", + "185.159.179.75", + "185.159.179.80", + "185.160.104.84", + "185.160.105.54", + "185.160.205.18", + "185.161.39.242", + "185.161.112.33", + "185.161.112.34", + "185.161.112.35", + "185.161.112.247", + "185.161.113.253", + "185.161.121.152", + "185.164.72.4", + "185.164.72.5", + "185.164.72.7", + "185.164.72.10", + "185.164.72.14", + "185.164.72.18", + "185.164.72.23", + "185.164.72.24", + "185.164.72.25", + "185.164.72.27", + "185.164.72.36", + "185.164.72.44", + "185.164.72.46", + "185.164.72.53", + "185.164.72.56", + "185.164.72.62", + "185.164.72.70", + "185.164.72.71", + "185.164.72.72", + "185.164.72.77", + "185.164.72.82", + "185.164.72.83", + "185.164.72.85", + "185.164.72.89", + "185.164.72.92", + "185.164.72.97", + "185.164.72.99", + "185.164.72.107", + "185.164.72.108", + "185.164.72.118", + "185.164.72.119", + "185.164.72.128", + "185.164.72.131", + "185.164.72.135", + "185.164.72.140", + "185.164.72.142", + "185.164.72.149", + "185.164.72.157", + "185.164.72.160", + "185.164.72.170", + "185.164.72.173", + "185.164.72.174", + "185.164.72.179", + "185.164.72.185", + "185.164.72.194", + "185.164.72.195", + "185.164.72.199", + "185.164.72.215", + "185.164.72.219", + "185.164.72.225", + "185.164.72.227", + "185.164.72.237", + "185.164.72.249", + "185.164.72.252", + "185.164.73.9", + "185.164.73.14", + "185.164.73.18", + "185.164.73.21", + "185.164.73.22", + "185.164.73.30", + "185.164.73.34", + "185.164.73.38", + "185.164.73.47", + "185.164.73.49", + "185.164.73.53", + "185.164.73.57", + "185.164.73.64", + "185.164.73.65", + "185.164.73.73", + "185.164.73.75", + "185.164.73.76", + "185.164.73.81", + "185.164.73.87", + "185.164.73.94", + "185.164.73.103", + "185.164.73.104", + "185.164.73.108", + "185.164.73.109", + "185.164.73.115", + "185.164.73.119", + "185.164.73.121", + "185.164.73.123", + "185.164.73.125", + "185.164.73.133", + "185.164.73.135", + "185.164.73.140", + "185.164.73.141", + "185.164.73.152", + "185.164.73.158", + "185.164.73.160", + "185.164.73.164", + "185.164.73.168", + "185.164.73.171", + "185.164.73.174", + "185.164.73.177", + "185.164.73.180", + "185.164.73.187", + "185.164.73.190", + "185.164.73.191", + "185.164.73.205", + "185.164.73.224", + "185.164.73.229", + "185.164.73.243", + "185.164.253.26", + "185.165.28.12", + "185.165.28.33", + "185.165.28.34", + "185.165.28.251", + "185.165.28.252", + "185.165.29.17", + "185.165.29.28", + "185.165.29.33", + "185.165.29.50", + "185.165.29.65", + "185.165.29.128", + "185.165.29.136", + "185.165.29.175", + "185.165.29.176", + "185.165.29.177", + "185.165.29.179", + "185.165.29.182", + "185.165.29.186", + "185.165.29.189", + "185.165.29.194", + "185.165.29.206", + "185.165.29.211", + "185.165.29.220", + "185.165.29.223", + "185.165.29.240", + "185.165.29.242", + "185.165.29.247", + "185.165.30.93", + "185.165.30.94", + "185.165.116.1", + "185.165.116.13", + "185.165.116.19", + "185.165.116.20", + "185.165.116.21", + "185.165.116.22", + "185.165.116.23", + "185.165.116.24", + "185.165.116.26", + "185.165.116.30", + "185.165.116.31", + "185.165.116.32", + "185.165.116.34", + "185.165.116.36", + "185.165.116.41", + "185.165.116.43", + "185.165.116.47", + "185.165.116.49", + "185.165.116.52", + "185.165.116.57", + "185.165.116.58", + "185.165.116.60", + "185.165.116.62", + "185.165.116.63", + "185.165.116.66", + "185.165.116.72", + "185.165.116.75", + "185.165.116.79", + "185.165.116.80", + "185.165.116.85", + "185.165.116.90", + "185.165.116.93", + "185.165.116.102", + "185.165.116.116", + "185.165.116.133", + "185.165.116.134", + "185.165.116.135", + "185.165.116.136", + "185.165.116.139", + "185.165.116.140", + "185.165.116.144", + "185.165.116.145", + "185.165.116.149", + "185.165.116.152", + "185.165.116.155", + "185.165.116.156", + "185.165.116.158", + "185.165.116.159", + "185.165.116.161", + "185.165.116.164", + "185.165.116.168", + "185.165.116.173", + "185.165.116.182", + "185.165.116.195", + "185.165.116.196", + "185.165.116.198", + "185.165.116.202", + "185.165.116.204", + "185.165.116.210", + "185.165.116.213", + "185.165.116.214", + "185.165.116.219", + "185.165.116.235", + "185.165.116.253", + "185.165.117.1", + "185.165.117.11", + "185.165.117.120", + "185.165.117.244", + "185.165.118.1", + "185.165.118.35", + "185.165.118.49", + "185.165.118.51", + "185.165.118.58", + "185.165.118.59", + "185.165.118.72", + "185.165.118.74", + "185.165.118.80", + "185.165.118.92", + "185.165.118.98", + "185.165.118.138", + "185.165.118.193", + "185.165.118.195", + "185.165.118.197", + "185.165.118.201", + "185.165.118.205", + "185.165.118.210", + "185.165.118.211", + "185.165.118.215", + "185.165.118.216", + "185.165.118.224", + "185.165.118.227", + "185.165.118.228", + "185.165.118.235", + "185.165.118.237", + "185.165.119.1", + "185.165.119.59", + "185.165.119.60", + "185.165.119.63", + "185.166.63.43", + "185.166.104.53", + "185.166.104.54", + "185.167.73.25", + "185.167.75.25", + "185.167.124.244", + "185.169.6.18", + "185.169.6.44", + "185.169.6.58", + "185.169.6.94", + "185.169.6.106", + "185.169.6.146", + "185.169.6.154", + "185.169.6.166", + "185.169.6.174", + "185.169.6.194", + "185.169.6.196", + "185.169.6.211", + "185.169.6.226", + "185.169.20.106", + "185.169.20.107", + "185.169.21.62", + "185.170.8.5", + "185.170.8.11", + "185.170.8.195", + "185.170.8.236", + "185.170.8.241", + "185.170.8.248", + "185.170.8.254", + "185.170.236.26", + "185.170.236.27", + "185.170.236.28", + "185.170.236.30", + "185.171.53.3", + "185.171.53.4", + "185.171.53.57", + "185.171.53.58", + "185.171.53.60", + "185.171.53.61", + "185.171.53.62", + "185.171.53.66", + "185.171.53.70", + "185.171.53.75", + "185.171.53.149", + "185.171.53.158", + "185.171.53.163", + "185.171.53.164", + "185.171.53.171", + "185.171.53.174", + "185.171.53.186", + "185.172.212.26", + "185.172.212.210", + "185.172.213.2", + "185.172.213.3", + "185.172.213.4", + "185.172.213.5", + "185.172.213.6", + "185.172.213.7", + "185.172.213.51", + "185.173.104.18", + "185.173.104.41", + "185.173.104.44", + "185.173.104.46", + "185.173.104.48", + "185.173.104.50", + "185.173.104.59", + "185.173.104.68", + "185.173.104.72", + "185.173.104.74", + "185.173.104.78", + "185.173.104.81", + "185.173.104.83", + "185.173.104.87", + "185.173.104.88", + "185.173.104.90", + "185.173.104.95", + "185.173.104.102", + "185.173.104.107", + "185.173.104.119", + "185.173.104.129", + "185.173.104.134", + "185.173.104.141", + "185.173.104.146", + "185.173.104.148", + "185.173.104.151", + "185.173.104.152", + "185.173.104.167", + "185.173.104.177", + "185.173.104.179", + "185.173.104.181", + "185.173.104.202", + "185.173.104.206", + "185.173.104.207", + "185.173.104.211", + "185.173.104.228", + "185.173.104.229", + "185.173.104.230", + "185.173.104.235", + "185.173.104.238", + "185.173.104.246", + "185.173.105.6", + "185.173.105.22", + "185.173.105.26", + "185.173.105.122", + "185.173.105.126", + "185.173.105.134", + "185.173.105.154", + "185.173.105.170", + "185.173.105.210", + "185.173.105.211", + "185.173.105.218", + "185.173.105.219", + "185.173.105.221", + "185.173.106.22", + "185.173.106.32", + "185.173.106.54", + "185.173.106.56", + "185.173.106.90", + "185.173.106.105", + "185.173.106.109", + "185.173.106.155", + "185.173.106.164", + "185.173.106.171", + "185.173.106.172", + "185.173.106.177", + "185.173.106.188", + "185.173.106.196", + "185.173.106.199", + "185.173.106.200", + "185.173.106.207", + "185.173.106.238", + "185.173.106.239", + "185.173.107.40", + "185.173.107.68", + "185.173.107.125", + "185.173.107.154", + "185.173.107.158", + "185.173.107.171", + "185.173.107.179", + "185.173.107.207", + "185.173.107.212", + "185.173.129.48", + "185.173.129.137", + "185.173.129.143", + "185.173.129.200", + "185.173.129.208", + "185.173.130.100", + "185.173.130.149", + "185.173.130.160", + "185.173.130.249", + "185.173.168.3", + "185.173.168.14", + "185.173.168.18", + "185.173.168.57", + "185.173.168.59", + "185.173.168.107", + "185.173.168.175", + "185.173.168.183", + "185.173.168.185", + "185.173.168.209", + "185.173.168.210", + "185.173.168.244", + "185.173.168.245", + "185.173.168.246", + "185.173.168.247", + "185.173.168.248", + "185.173.168.249", + "185.173.168.252", + "185.173.168.254", + "185.173.169.169", + "185.173.169.254", + "185.173.170.11", + "185.173.171.251", + "185.173.171.252", + "185.173.171.253", + "185.173.171.254", + "185.174.250.20", + "185.174.250.51", + "185.174.250.52", + "185.174.251.22", + "185.175.76.140", + "185.176.58.58", + "185.177.158.4", + "185.178.220.15", + "185.178.220.55", + "185.179.90.10", + "185.179.90.54", + "185.179.90.61", + "185.179.90.66", + "185.179.90.75", + "185.179.90.125", + "185.179.90.130", + "185.179.90.150", + "185.179.90.179", + "185.179.90.227", + "185.179.168.9", + "185.179.168.12", + "185.179.168.24", + "185.179.168.30", + "185.179.168.50", + "185.179.168.51", + "185.179.168.56", + "185.179.168.168", + "185.179.169.13", + "185.179.169.90", + "185.179.171.127", + "185.179.220.9", + "185.180.128.92", + "185.180.129.219", + "185.181.180.14", + "185.181.180.54", + "185.181.180.62", + "185.181.180.116", + "185.181.180.119", + "185.181.180.120", + "185.181.180.122", + "185.181.180.123", + "185.181.180.130", + "185.181.180.156", + "185.181.180.205", + "185.181.180.206", + "185.181.180.208", + "185.181.180.209", + "185.181.180.222", + "185.181.180.237", + "185.181.180.244", + "185.181.181.2", + "185.181.181.5", + "185.181.181.9", + "185.181.181.30", + "185.181.181.85", + "185.181.181.87", + "185.181.181.88", + "185.181.181.166", + "185.181.181.183", + "185.181.181.187", + "185.181.181.188", + "185.181.182.15", + "185.181.182.17", + "185.181.182.52", + "185.181.182.64", + "185.181.182.75", + "185.181.182.84", + "185.181.182.85", + "185.181.182.88", + "185.181.182.91", + "185.181.182.95", + "185.181.182.102", + "185.181.182.107", + "185.181.182.128", + "185.181.182.133", + "185.181.182.157", + "185.181.182.161", + "185.181.182.173", + "185.181.182.177", + "185.181.182.178", + "185.181.182.190", + "185.181.182.202", + "185.181.182.206", + "185.181.182.224", + "185.181.182.230", + "185.181.183.25", + "185.181.183.38", + "185.181.183.47", + "185.181.183.56", + "185.181.183.58", + "185.181.183.87", + "185.181.183.94", + "185.181.183.109", + "185.181.183.111", + "185.181.183.112", + "185.181.183.114", + "185.181.183.125", + "185.181.183.144", + "185.181.183.145", + "185.181.183.157", + "185.181.183.166", + "185.182.222.85", + "185.182.222.219", + "185.185.16.116", + "185.185.240.4", + "185.186.50.44", + "185.186.50.67", + "185.186.50.108", + "185.186.50.124", + "185.186.50.130", + "185.186.50.243", + "185.186.50.245", + "185.186.50.247", + "185.186.51.10", + "185.186.51.115", + "185.186.51.243", + "185.186.240.240", + "185.186.241.3", + "185.186.241.8", + "185.186.241.10", + "185.186.241.133", + "185.186.242.21", + "185.186.242.135", + "185.186.242.141", + "185.186.242.157", + "185.186.243.250", + "185.187.48.3", + "185.187.50.4", + "185.187.50.37", + "185.187.50.40", + "185.187.51.250", + "185.187.86.5", + "185.187.86.10", + "185.188.104.10", + "185.188.104.11", + "185.188.104.12", + "185.188.104.13", + "185.188.105.10", + "185.188.105.11", + "185.188.105.12", + "185.188.106.10", + "185.188.106.11", + "185.190.39.7", + "185.190.39.18", + "185.190.39.27", + "185.190.39.31", + "185.190.39.34", + "185.190.39.40", + "185.190.39.46", + "185.190.39.63", + "185.190.39.69", + "185.190.39.75", + "185.190.39.85", + "185.190.39.91", + "185.190.39.93", + "185.190.39.103", + "185.190.39.113", + "185.190.39.120", + "185.190.39.133", + "185.190.39.137", + "185.190.39.143", + "185.190.39.145", + "185.190.39.148", + "185.190.39.154", + "185.190.39.159", + "185.190.39.166", + "185.190.39.172", + "185.190.39.176", + "185.190.39.179", + "185.190.39.189", + "185.190.39.193", + "185.190.39.195", + "185.190.39.213", + "185.190.39.222", + "185.190.39.225", + "185.190.39.227", + "185.190.39.231", + "185.190.39.232", + "185.190.39.235", + "185.190.39.241", + "185.190.39.251", + "185.191.76.3", + "185.191.76.5", + "185.191.76.8", + "185.191.76.9", + "185.191.76.10", + "185.191.76.11", + "185.191.76.12", + "185.191.76.13", + "185.191.76.19", + "185.191.76.27", + "185.191.76.30", + "185.191.76.84", + "185.191.76.142", + "185.191.76.147", + "185.191.76.148", + "185.191.76.149", + "185.191.76.150", + "185.191.76.163", + "185.191.76.210", + "185.191.77.50", + "185.191.77.54", + "185.191.77.122", + "185.191.77.138", + "185.191.77.142", + "185.191.77.197", + "185.191.77.210", + "185.191.77.221", + "185.191.77.229", + "185.191.77.235", + "185.191.77.236", + "185.191.77.237", + "185.191.77.241", + "185.191.77.244", + "185.191.77.245", + "185.191.77.249", + "185.191.77.250", + "185.191.77.251", + "185.191.77.252", + "185.191.78.22", + "185.191.78.74", + "185.191.78.187", + "185.191.78.214", + "185.191.78.226", + "185.191.78.227", + "185.191.78.228", + "185.191.78.231", + "185.191.79.8", + "185.191.79.10", + "185.191.79.13", + "185.191.79.14", + "185.191.79.17", + "185.191.79.22", + "185.191.79.23", + "185.191.79.55", + "185.191.79.102", + "185.191.79.138", + "185.191.79.208", + "185.191.79.211", + "185.192.10.3", + "185.192.112.2", + "185.192.112.3", + "185.192.112.5", + "185.192.112.7", + "185.192.112.8", + "185.192.112.9", + "185.192.112.13", + "185.192.112.14", + "185.192.112.15", + "185.192.112.19", + "185.192.112.20", + "185.192.112.21", + "185.192.112.22", + "185.192.112.24", + "185.192.112.26", + "185.192.112.29", + "185.192.112.30", + "185.192.112.31", + "185.192.112.38", + "185.192.112.41", + "185.192.112.44", + "185.192.112.49", + "185.192.112.53", + "185.192.112.55", + "185.192.112.56", + "185.192.112.57", + "185.192.112.58", + "185.192.112.64", + "185.192.112.68", + "185.192.112.70", + "185.192.112.75", + "185.192.112.79", + "185.192.112.81", + "185.192.112.82", + "185.192.112.83", + "185.192.112.84", + "185.192.112.87", + "185.192.112.88", + "185.192.112.93", + "185.192.112.96", + "185.192.112.97", + "185.192.112.98", + "185.192.112.99", + "185.192.112.101", + "185.192.112.103", + "185.192.112.104", + "185.192.112.108", + "185.192.112.109", + "185.192.112.110", + "185.192.112.114", + "185.192.112.117", + "185.192.112.118", + "185.192.112.119", + "185.192.112.125", + "185.192.112.126", + "185.192.112.130", + "185.192.112.131", + "185.192.112.132", + "185.192.112.133", + "185.192.112.137", + "185.192.112.138", + "185.192.112.139", + "185.192.112.144", + "185.192.112.145", + "185.192.112.146", + "185.192.112.152", + "185.192.112.153", + "185.192.112.157", + "185.192.112.161", + "185.192.112.162", + "185.192.112.163", + "185.192.112.164", + "185.192.112.201", + "185.192.112.202", + "185.192.112.203", + "185.192.112.205", + "185.192.112.207", + "185.192.112.208", + "185.192.112.209", + "185.192.112.211", + "185.192.112.212", + "185.192.112.213", + "185.192.112.214", + "185.192.112.215", + "185.192.112.216", + "185.192.112.217", + "185.192.112.218", + "185.192.112.219", + "185.192.112.221", + "185.192.112.223", + "185.192.112.224", + "185.192.112.225", + "185.192.112.227", + "185.192.112.229", + "185.192.112.230", + "185.192.112.231", + "185.192.112.241", + "185.192.112.243", + "185.192.112.244", + "185.192.112.245", + "185.192.112.246", + "185.192.112.247", + "185.192.112.248", + "185.192.112.251", + "185.192.112.253", + "185.192.112.254", + "185.192.113.6", + "185.192.113.11", + "185.192.113.12", + "185.192.113.14", + "185.192.113.15", + "185.192.113.16", + "185.192.113.17", + "185.192.113.18", + "185.192.113.20", + "185.192.113.23", + "185.192.113.25", + "185.192.113.26", + "185.192.113.30", + "185.192.113.31", + "185.192.113.32", + "185.192.113.34", + "185.192.113.35", + "185.192.113.43", + "185.192.113.44", + "185.192.113.47", + "185.192.113.49", + "185.192.113.82", + "185.192.113.86", + "185.192.113.90", + "185.192.113.92", + "185.192.113.102", + "185.192.113.103", + "185.192.113.110", + "185.192.113.111", + "185.192.113.112", + "185.192.113.114", + "185.192.113.115", + "185.192.113.117", + "185.192.113.118", + "185.192.113.119", + "185.192.113.122", + "185.192.113.123", + "185.192.113.124", + "185.192.113.126", + "185.192.113.127", + "185.192.113.128", + "185.192.113.129", + "185.192.113.130", + "185.192.113.133", + "185.192.113.140", + "185.192.113.141", + "185.192.113.143", + "185.192.113.145", + "185.192.113.150", + "185.192.113.151", + "185.192.113.152", + "185.192.113.153", + "185.192.113.157", + "185.192.113.159", + "185.192.113.163", + "185.192.113.164", + "185.192.113.174", + "185.192.113.175", + "185.192.113.177", + "185.192.113.183", + "185.192.113.185", + "185.192.113.186", + "185.192.113.193", + "185.192.113.201", + "185.192.113.203", + "185.192.113.206", + "185.192.113.209", + "185.192.113.242", + "185.192.113.251", + "185.192.113.252", + "185.192.114.12", + "185.192.114.13", + "185.192.114.15", + "185.192.114.16", + "185.192.114.17", + "185.192.114.20", + "185.192.114.23", + "185.192.114.24", + "185.192.114.25", + "185.192.114.26", + "185.192.114.28", + "185.192.114.30", + "185.192.114.39", + "185.192.114.40", + "185.192.114.41", + "185.192.114.47", + "185.192.114.48", + "185.192.114.49", + "185.192.114.53", + "185.192.114.54", + "185.192.114.65", + "185.192.114.83", + "185.192.114.101", + "185.192.114.102", + "185.192.114.103", + "185.192.114.104", + "185.192.114.105", + "185.192.114.108", + "185.192.114.112", + "185.192.114.114", + "185.192.114.116", + "185.192.114.117", + "185.192.114.118", + "185.192.114.123", + "185.192.114.126", + "185.192.114.127", + "185.192.114.129", + "185.192.114.130", + "185.192.114.133", + "185.192.114.135", + "185.192.114.140", + "185.192.114.148", + "185.192.114.153", + "185.192.114.154", + "185.192.114.158", + "185.192.114.159", + "185.192.114.162", + "185.192.114.165", + "185.192.114.172", + "185.192.114.201", + "185.192.114.203", + "185.192.114.204", + "185.192.114.205", + "185.192.114.209", + "185.192.114.211", + "185.192.114.254", + "185.193.47.15", + "185.193.47.16", + "185.194.79.2", + "185.194.79.3", + "185.194.79.4", + "185.196.148.112", + "185.198.160.2", + "185.198.160.3", + "185.201.49.149", + "185.201.49.228", + "185.201.50.101", + "185.201.50.104", + "185.201.50.105", + "185.201.50.220", + "185.202.59.66", + "185.204.197.12", + "185.204.197.13", + "185.204.197.15", + "185.204.197.16", + "185.204.197.19", + "185.204.197.21", + "185.204.197.24", + "185.204.197.28", + "185.204.197.33", + "185.204.197.41", + "185.204.197.42", + "185.204.197.60", + "185.204.197.71", + "185.204.197.85", + "185.204.197.96", + "185.204.197.97", + "185.204.197.98", + "185.204.197.99", + "185.204.197.106", + "185.204.197.110", + "185.204.197.116", + "185.204.197.120", + "185.204.197.123", + "185.204.197.129", + "185.204.197.132", + "185.204.197.133", + "185.204.197.140", + "185.204.197.145", + "185.204.197.155", + "185.204.197.160", + "185.204.197.164", + "185.204.197.169", + "185.204.197.171", + "185.204.197.172", + "185.204.197.178", + "185.204.197.182", + "185.204.197.186", + "185.204.197.187", + "185.204.197.188", + "185.204.197.195", + "185.204.197.198", + "185.204.197.199", + "185.204.197.203", + "185.204.197.204", + "185.204.197.210", + "185.204.197.215", + "185.204.197.216", + "185.204.197.225", + "185.204.197.232", + "185.204.197.235", + "185.204.197.239", + "185.204.197.248", + "185.205.203.85", + "185.205.203.153", + "185.205.203.189", + "185.205.203.230", + "185.205.203.253", + "185.206.92.90", + "185.206.92.250", + "185.206.92.253", + "185.206.93.158", + "185.206.93.186", + "185.206.93.230", + "185.206.94.60", + "185.206.94.193", + "185.206.95.17", + "185.206.95.33", + "185.206.95.87", + "185.206.95.198", + "185.206.229.76", + "185.206.229.79", + "185.206.229.81", + "185.206.229.82", + "185.206.229.99", + "185.206.229.100", + "185.206.229.109", + "185.206.229.134", + "185.206.229.137", + "185.206.229.142", + "185.206.229.210", + "185.206.231.5", + "185.206.238.18", + "185.206.238.20", + "185.206.238.23", + "185.206.238.42", + "185.206.238.146", + "185.206.238.147", + "185.206.238.170", + "185.207.72.6", + "185.207.73.6", + "185.208.77.86", + "185.208.78.38", + "185.208.78.214", + "185.208.79.6", + "185.208.79.48", + "185.208.79.49", + "185.208.79.65", + "185.208.79.68", + "185.208.79.72", + "185.208.79.82", + "185.208.79.99", + "185.208.79.104", + "185.208.79.106", + "185.208.79.128", + "185.208.79.129", + "185.208.79.191", + "185.208.79.199", + "185.208.79.206", + "185.208.79.209", + "185.208.79.210", + "185.208.79.221", + "185.208.79.230", + "185.208.79.243", + "185.208.149.56", + "185.208.149.142", + "185.208.174.5", + "185.208.174.7", + "185.208.174.8", + "185.208.174.16", + "185.208.174.22", + "185.208.174.23", + "185.208.174.28", + "185.208.174.33", + "185.208.174.35", + "185.208.174.37", + "185.208.174.42", + "185.208.174.45", + "185.208.174.48", + "185.208.174.51", + "185.208.174.59", + "185.208.174.60", + "185.208.174.61", + "185.208.174.64", + "185.208.174.65", + "185.208.174.67", + "185.208.174.69", + "185.208.174.76", + "185.208.174.79", + "185.208.174.85", + "185.208.174.88", + "185.208.174.95", + "185.208.174.97", + "185.208.174.99", + "185.208.174.100", + "185.208.174.103", + "185.208.174.113", + "185.208.174.115", + "185.208.174.116", + "185.208.174.124", + "185.208.174.128", + "185.208.174.129", + "185.208.174.130", + "185.208.174.134", + "185.208.174.135", + "185.208.174.136", + "185.208.174.139", + "185.208.174.141", + "185.208.174.144", + "185.208.174.147", + "185.208.174.152", + "185.208.174.155", + "185.208.174.156", + "185.208.174.159", + "185.208.174.164", + "185.208.174.165", + "185.208.174.166", + "185.208.174.167", + "185.208.174.173", + "185.208.174.176", + "185.208.174.180", + "185.208.174.181", + "185.208.174.182", + "185.208.174.183", + "185.208.174.190", + "185.208.174.198", + "185.208.174.200", + "185.208.174.201", + "185.208.174.205", + "185.208.174.206", + "185.208.174.214", + "185.208.174.219", + "185.208.174.220", + "185.208.174.222", + "185.208.174.225", + "185.208.174.231", + "185.208.174.242", + "185.208.174.249", + "185.208.174.251", + "185.208.175.4", + "185.208.175.15", + "185.208.175.17", + "185.208.175.18", + "185.208.175.21", + "185.208.175.24", + "185.208.175.31", + "185.208.175.41", + "185.208.175.43", + "185.208.175.53", + "185.208.175.59", + "185.208.175.60", + "185.208.175.64", + "185.208.175.67", + "185.208.175.69", + "185.208.175.70", + "185.208.175.71", + "185.208.175.75", + "185.208.175.82", + "185.208.175.84", + "185.208.175.92", + "185.208.175.93", + "185.208.175.94", + "185.208.175.97", + "185.208.175.98", + "185.208.175.104", + "185.208.175.108", + "185.208.175.109", + "185.208.175.115", + "185.208.175.118", + "185.208.175.123", + "185.208.175.125", + "185.208.175.126", + "185.208.175.131", + "185.208.175.132", + "185.208.175.139", + "185.208.175.140", + "185.208.175.144", + "185.208.175.149", + "185.208.175.151", + "185.208.175.158", + "185.208.175.163", + "185.208.175.170", + "185.208.175.172", + "185.208.175.174", + "185.208.175.176", + "185.208.175.182", + "185.208.175.186", + "185.208.175.189", + "185.208.175.191", + "185.208.175.192", + "185.208.175.193", + "185.208.175.195", + "185.208.175.197", + "185.208.175.205", + "185.208.175.206", + "185.208.175.210", + "185.208.175.214", + "185.208.175.217", + "185.208.175.220", + "185.208.175.223", + "185.208.175.228", + "185.208.175.239", + "185.208.175.246", + "185.208.175.250", + "185.208.175.251", + "185.208.180.23", + "185.208.180.35", + "185.208.180.44", + "185.208.180.52", + "185.208.180.60", + "185.208.180.91", + "185.208.180.92", + "185.208.180.94", + "185.208.180.132", + "185.208.180.133", + "185.208.180.134", + "185.208.180.135", + "185.208.180.136", + "185.208.180.137", + "185.208.180.139", + "185.208.180.141", + "185.208.180.149", + "185.208.180.151", + "185.208.180.153", + "185.208.180.155", + "185.208.180.157", + "185.208.180.166", + "185.208.180.169", + "185.208.180.171", + "185.208.180.244", + "185.208.180.249", + "185.208.180.251", + "185.208.180.253", + "185.208.181.34", + "185.208.181.130", + "185.208.181.211", + "185.208.181.252", + "185.208.181.253", + "185.208.181.254", + "185.208.182.20", + "185.208.182.24", + "185.208.182.28", + "185.208.182.42", + "185.208.182.53", + "185.208.182.60", + "185.208.182.78", + "185.208.182.139", + "185.208.182.140", + "185.208.182.142", + "185.208.182.152", + "185.208.182.153", + "185.208.182.154", + "185.208.182.159", + "185.208.182.178", + "185.208.182.212", + "185.208.182.214", + "185.208.182.251", + "185.208.182.252", + "185.208.183.21", + "185.208.183.29", + "185.208.183.35", + "185.208.183.126", + "185.208.183.133", + "185.208.183.245", + "185.209.42.88", + "185.209.42.151", + "185.209.42.219", + "185.209.42.224", + "185.210.200.253", + "185.210.200.254", + "185.211.56.3", + "185.211.56.4", + "185.211.56.14", + "185.211.56.22", + "185.211.56.24", + "185.211.56.25", + "185.211.56.33", + "185.211.56.40", + "185.211.56.43", + "185.211.56.44", + "185.211.56.59", + "185.211.56.63", + "185.211.56.64", + "185.211.56.76", + "185.211.56.82", + "185.211.56.84", + "185.211.56.96", + "185.211.56.99", + "185.211.56.112", + "185.211.56.130", + "185.211.56.132", + "185.211.56.134", + "185.211.56.147", + "185.211.56.163", + "185.211.56.165", + "185.211.56.168", + "185.211.56.169", + "185.211.56.183", + "185.211.56.188", + "185.211.56.210", + "185.211.56.233", + "185.211.56.254", + "185.211.57.7", + "185.211.57.18", + "185.211.57.24", + "185.211.57.34", + "185.211.57.53", + "185.211.57.54", + "185.211.57.69", + "185.211.57.76", + "185.211.57.78", + "185.211.57.84", + "185.211.57.89", + "185.211.57.98", + "185.211.57.106", + "185.211.57.107", + "185.211.57.129", + "185.211.57.140", + "185.211.57.163", + "185.211.57.172", + "185.211.57.176", + "185.211.57.181", + "185.211.57.191", + "185.211.57.193", + "185.211.57.197", + "185.211.57.217", + "185.211.57.222", + "185.211.58.8", + "185.211.58.37", + "185.211.58.41", + "185.211.58.44", + "185.211.58.83", + "185.211.58.84", + "185.211.58.91", + "185.211.58.192", + "185.211.58.198", + "185.211.58.214", + "185.211.58.216", + "185.211.58.217", + "185.211.58.235", + "185.211.58.242", + "185.211.58.243", + "185.211.59.7", + "185.211.59.27", + "185.211.59.41", + "185.211.59.56", + "185.211.59.84", + "185.211.59.103", + "185.211.59.111", + "185.211.59.121", + "185.211.59.127", + "185.211.59.128", + "185.211.59.131", + "185.211.59.141", + "185.211.59.142", + "185.211.59.143", + "185.211.59.144", + "185.211.59.151", + "185.211.59.163", + "185.211.59.168", + "185.211.59.175", + "185.211.59.184", + "185.211.59.196", + "185.211.59.198", + "185.211.59.202", + "185.211.59.204", + "185.211.59.211", + "185.211.59.216", + "185.211.59.232", + "185.211.88.6", + "185.211.89.14", + "185.211.90.9", + "185.212.51.67", + "185.212.51.69", + "185.212.51.70", + "185.212.51.72", + "185.212.51.75", + "185.212.51.76", + "185.212.51.78", + "185.212.51.79", + "185.212.51.81", + "185.212.51.110", + "185.212.51.114", + "185.212.51.211", + "185.212.51.242", + "185.212.194.28", + "185.212.194.53", + "185.212.194.66", + "185.212.194.141", + "185.212.194.146", + "185.212.194.161", + "185.212.195.160", + "185.213.8.40", + "185.213.9.40", + "185.213.10.18", + "185.213.10.22", + "185.213.10.29", + "185.213.10.35", + "185.213.10.37", + "185.213.10.38", + "185.213.10.39", + "185.213.10.40", + "185.213.10.41", + "185.213.10.42", + "185.213.10.43", + "185.213.10.44", + "185.213.10.45", + "185.213.10.46", + "185.213.10.70", + "185.213.10.100", + "185.213.10.116", + "185.213.10.167", + "185.213.10.172", + "185.213.10.227", + "185.213.10.230", + "185.213.10.234", + "185.213.10.237", + "185.213.11.19", + "185.213.11.20", + "185.213.11.21", + "185.213.11.22", + "185.213.11.34", + "185.213.11.60", + "185.213.11.82", + "185.213.11.84", + "185.213.11.85", + "185.213.11.201", + "185.213.11.202", + "185.213.11.217", + "185.213.164.7", + "185.213.164.12", + "185.213.164.15", + "185.213.164.24", + "185.213.164.26", + "185.213.164.28", + "185.213.164.29", + "185.213.164.31", + "185.213.164.38", + "185.213.164.39", + "185.213.164.40", + "185.213.164.41", + "185.213.164.42", + "185.213.164.43", + "185.213.164.50", + "185.213.164.53", + "185.213.164.56", + "185.213.164.59", + "185.213.164.62", + "185.213.164.65", + "185.213.164.68", + "185.213.164.69", + "185.213.164.71", + "185.213.164.78", + "185.213.164.84", + "185.213.164.96", + "185.213.164.112", + "185.213.164.113", + "185.213.164.120", + "185.213.164.121", + "185.213.164.124", + "185.213.164.128", + "185.213.164.130", + "185.213.164.131", + "185.213.164.137", + "185.213.164.138", + "185.213.164.144", + "185.213.164.149", + "185.213.164.155", + "185.213.164.160", + "185.213.164.167", + "185.213.164.168", + "185.213.164.185", + "185.213.164.187", + "185.213.164.188", + "185.213.164.189", + "185.213.164.192", + "185.213.164.195", + "185.213.164.220", + "185.213.164.221", + "185.213.164.230", + "185.213.164.231", + "185.213.164.232", + "185.213.164.235", + "185.213.164.239", + "185.213.164.245", + "185.213.164.252", + "185.213.165.10", + "185.213.165.12", + "185.213.165.16", + "185.213.165.18", + "185.213.165.24", + "185.213.165.39", + "185.213.165.44", + "185.213.165.47", + "185.213.165.51", + "185.213.165.59", + "185.213.165.63", + "185.213.165.68", + "185.213.165.74", + "185.213.165.76", + "185.213.165.79", + "185.213.165.81", + "185.213.165.94", + "185.213.165.100", + "185.213.165.119", + "185.213.165.127", + "185.213.165.134", + "185.213.165.143", + "185.213.165.148", + "185.213.165.162", + "185.213.165.165", + "185.213.165.173", + "185.213.165.179", + "185.213.165.181", + "185.213.165.185", + "185.213.165.188", + "185.213.165.191", + "185.213.165.197", + "185.213.165.205", + "185.213.165.208", + "185.213.165.209", + "185.213.165.217", + "185.213.165.229", + "185.213.165.231", + "185.213.165.234", + "185.213.165.235", + "185.213.165.236", + "185.213.165.237", + "185.213.195.127", + "185.213.195.190", + "185.215.125.6", + "185.215.126.9", + "185.215.126.220", + "185.215.154.123", + "185.215.229.2", + "185.217.6.82", + "185.217.6.87", + "185.217.6.89", + "185.217.6.118", + "185.218.139.28", + "185.218.139.30", + "185.218.139.36", + "185.218.139.45", + "185.218.139.74", + "185.218.139.103", + "185.218.139.138", + "185.218.139.150", + "185.218.139.162", + "185.218.139.196", + "185.218.139.201", + "185.218.139.205", + "185.218.139.223", + "185.218.139.225", + "185.218.139.229", + "185.218.139.248", + "185.218.139.252", + "185.218.139.253", + "185.221.239.27", + "185.221.239.85", + "185.221.239.97", + "185.221.239.105", + "185.221.239.108", + "185.221.239.112", + "185.221.239.131", + "185.221.239.137", + "185.221.239.144", + "185.221.239.150", + "185.221.239.161", + "185.221.239.192", + "185.221.239.198", + "185.221.239.219", + "185.221.239.235", + "185.221.239.245", + "185.222.163.16", + "185.222.163.28", + "185.222.163.82", + "185.222.163.90", + "185.222.163.91", + "185.222.163.92", + "185.222.163.94", + "185.222.163.107", + "185.222.163.112", + "185.222.163.117", + "185.222.163.123", + "185.222.163.124", + "185.222.163.125", + "185.222.163.131", + "185.222.163.141", + "185.222.163.144", + "185.222.163.145", + "185.222.163.146", + "185.222.163.147", + "185.222.163.148", + "185.222.163.149", + "185.222.163.150", + "185.222.163.153", + "185.222.163.174", + "185.222.163.181", + "185.222.163.186", + "185.222.163.202", + "185.222.163.210", + "185.222.182.90", + "185.222.183.90", + "185.225.80.15", + "185.225.80.17", + "185.225.80.40", + "185.225.80.41", + "185.225.80.42", + "185.225.80.46", + "185.225.80.50", + "185.225.80.55", + "185.225.80.57", + "185.225.80.60", + "185.225.80.112", + "185.225.80.132", + "185.225.80.133", + "185.225.80.134", + "185.225.80.136", + "185.225.80.137", + "185.225.82.115", + "185.225.83.236", + "185.226.117.3", + "185.226.117.66", + "185.226.117.240", + "185.226.118.54", + "185.226.118.108", + "185.226.118.201", + "185.226.119.17", + "185.226.119.48", + "185.226.134.182", + "185.226.141.41", + "185.226.141.141", + "185.226.143.43", + "185.226.143.143", + "185.229.28.6", + "185.229.28.28", + "185.229.28.29", + "185.229.28.135", + "185.229.28.136", + "185.229.28.162", + "185.229.28.163", + "185.229.29.147", + "185.229.29.148", + "185.229.29.149", + "185.229.29.150", + "185.229.133.6", + "185.229.133.44", + "185.229.204.52", + "185.229.204.107", + "185.229.204.108", + "185.229.204.120", + "185.229.204.123", + "185.229.204.124", + "185.229.204.125", + "185.229.204.141", + "185.229.204.151", + "185.229.204.155", + "185.229.204.231", + "185.231.65.27", + "185.231.112.9", + "185.231.112.10", + "185.231.112.26", + "185.231.112.34", + "185.231.112.45", + "185.231.112.46", + "185.231.112.53", + "185.231.112.56", + "185.231.112.61", + "185.231.112.68", + "185.231.112.88", + "185.231.112.103", + "185.231.112.110", + "185.231.112.115", + "185.231.112.116", + "185.231.112.117", + "185.231.112.131", + "185.231.112.134", + "185.231.112.138", + "185.231.112.143", + "185.231.112.158", + "185.231.112.159", + "185.231.112.164", + "185.231.112.167", + "185.231.112.178", + "185.231.112.182", + "185.231.112.190", + "185.231.112.191", + "185.231.112.192", + "185.231.112.198", + "185.231.112.203", + "185.231.112.208", + "185.231.112.210", + "185.231.112.213", + "185.231.112.215", + "185.231.112.221", + "185.231.112.231", + "185.231.112.239", + "185.231.112.241", + "185.231.112.244", + "185.231.113.22", + "185.231.114.218", + "185.231.115.15", + "185.231.115.16", + "185.231.115.18", + "185.231.115.23", + "185.231.115.31", + "185.231.115.32", + "185.231.115.34", + "185.231.115.47", + "185.231.115.50", + "185.231.115.54", + "185.231.115.58", + "185.231.115.67", + "185.231.115.73", + "185.231.115.91", + "185.231.115.103", + "185.231.115.112", + "185.231.115.125", + "185.231.115.127", + "185.231.115.130", + "185.231.115.135", + "185.231.115.143", + "185.231.115.147", + "185.231.115.170", + "185.231.115.174", + "185.231.115.175", + "185.231.115.177", + "185.231.115.178", + "185.231.115.183", + "185.231.115.187", + "185.231.115.193", + "185.231.115.197", + "185.231.115.202", + "185.231.115.203", + "185.231.115.206", + "185.231.115.216", + "185.231.115.226", + "185.231.115.235", + "185.231.115.238", + "185.231.115.247", + "185.231.115.252", + "185.231.180.29", + "185.231.180.181", + "185.231.180.203", + "185.231.180.228", + "185.231.181.14", + "185.231.181.143", + "185.231.181.159", + "185.231.181.206", + "185.231.181.221", + "185.231.182.207", + "185.231.182.208", + "185.231.183.19", + "185.231.183.238", + "185.232.154.69", + "185.234.192.17", + "185.234.195.13", + "185.235.136.20", + "185.235.196.9", + "185.235.196.10", + "185.235.196.11", + "185.235.196.12", + "185.235.196.13", + "185.235.196.14", + "185.235.196.16", + "185.235.196.17", + "185.235.196.18", + "185.235.196.19", + "185.235.196.20", + "185.235.196.22", + "185.235.196.25", + "185.235.196.38", + "185.235.196.39", + "185.235.196.48", + "185.235.196.49", + "185.235.196.52", + "185.235.196.53", + "185.235.196.136", + "185.235.196.242", + "185.235.196.243", + "185.235.196.244", + "185.235.196.245", + "185.235.197.6", + "185.235.197.8", + "185.235.197.31", + "185.235.197.216", + "185.235.198.200", + "185.235.245.12", + "185.235.245.30", + "185.235.245.49", + "185.235.245.51", + "185.235.245.91", + "185.235.245.239", + "185.235.245.246", + "185.235.245.247", + "185.236.36.21", + "185.236.37.15", + "185.236.37.179", + "185.236.37.198", + "185.236.38.92", + "185.237.84.10", + "185.237.84.11", + "185.237.85.4", + "185.237.85.5", + "185.237.85.9", + "185.237.85.10", + "185.237.85.16", + "185.237.85.21", + "185.237.85.26", + "185.237.85.40", + "185.237.85.42", + "185.237.85.47", + "185.237.85.54", + "185.237.85.55", + "185.237.85.56", + "185.237.85.95", + "185.237.85.96", + "185.237.85.97", + "185.237.85.114", + "185.237.85.117", + "185.237.85.118", + "185.237.85.252", + "185.237.87.84", + "185.237.87.85", + "185.237.87.139", + "185.238.140.195", + "185.238.143.229", + "185.239.0.12", + "185.239.0.22", + "185.239.0.53", + "185.239.0.86", + "185.239.0.127", + "185.239.0.190", + "185.239.0.232", + "185.239.0.239", + "185.239.0.246", + "185.239.1.179", + "185.239.2.31", + "185.239.3.47", + "185.239.3.86", + "185.239.3.112", + "185.239.3.117", + "185.239.3.124", + "185.239.3.151", + "185.239.3.152", + "185.239.3.187", + "185.239.3.199", + "185.239.3.208", + "185.239.3.220", + "185.239.3.222", + "185.239.3.224", + "185.239.3.236", + "185.239.104.106", + "185.239.105.144", + "185.239.106.86", + "185.243.48.4", + "185.243.48.13", + "185.243.48.15", + "185.243.48.28", + "185.243.48.31", + "185.243.48.45", + "185.243.48.52", + "185.243.48.57", + "185.243.48.58", + "185.243.48.70", + "185.243.48.78", + "185.243.48.102", + "185.243.48.109", + "185.243.48.126", + "185.243.48.127", + "185.243.48.131", + "185.243.48.132", + "185.243.48.134", + "185.243.48.135", + "185.243.48.140", + "185.243.48.145", + "185.243.48.159", + "185.243.48.170", + "185.243.48.173", + "185.243.48.176", + "185.243.48.181", + "185.243.48.189", + "185.243.48.196", + "185.243.48.198", + "185.243.48.199", + "185.243.48.200", + "185.243.48.202", + "185.243.48.204", + "185.243.48.211", + "185.243.48.215", + "185.243.48.226", + "185.243.48.227", + "185.243.48.231", + "185.243.48.233", + "185.243.48.237", + "185.243.48.242", + "185.243.48.243", + "185.243.48.251", + "185.243.51.50", + "185.243.51.58", + "185.246.6.178", + "185.246.6.179", + "185.252.28.13", + "185.252.28.16", + "185.252.28.19", + "185.252.28.20", + "185.252.28.21", + "185.252.28.30", + "185.252.28.31", + "185.252.28.32", + "185.252.28.34", + "185.252.28.42", + "185.252.28.44", + "185.252.28.46", + "185.252.28.47", + "185.252.28.56", + "185.252.28.63", + "185.252.28.68", + "185.252.28.77", + "185.252.28.86", + "185.252.28.105", + "185.252.28.106", + "185.252.28.116", + "185.252.28.117", + "185.252.28.120", + "185.252.28.123", + "185.252.28.124", + "185.252.28.127", + "185.252.28.128", + "185.252.28.129", + "185.252.28.130", + "185.252.28.136", + "185.252.28.137", + "185.252.28.138", + "185.252.28.140", + "185.252.28.150", + "185.252.28.165", + "185.252.28.166", + "185.252.28.170", + "185.252.28.178", + "185.252.28.179", + "185.252.28.180", + "185.252.28.182", + "185.252.28.186", + "185.252.28.188", + "185.252.28.192", + "185.252.28.194", + "185.252.28.195", + "185.252.28.200", + "185.252.28.207", + "185.252.28.210", + "185.252.28.211", + "185.252.28.228", + "185.252.28.229", + "185.252.28.230", + "185.252.29.11", + "185.252.29.14", + "185.252.29.17", + "185.252.29.18", + "185.252.29.27", + "185.252.29.29", + "185.252.29.31", + "185.252.29.32", + "185.252.29.35", + "185.252.29.47", + "185.252.29.50", + "185.252.29.53", + "185.252.29.54", + "185.252.29.60", + "185.252.29.62", + "185.252.29.68", + "185.252.29.72", + "185.252.29.74", + "185.252.29.99", + "185.252.29.102", + "185.252.29.121", + "185.252.29.133", + "185.252.29.141", + "185.252.29.143", + "185.252.29.145", + "185.252.29.151", + "185.252.29.154", + "185.252.29.160", + "185.252.29.161", + "185.252.29.162", + "185.252.29.234", + "185.252.29.235", + "185.252.29.239", + "185.252.30.39", + "185.252.30.40", + "185.252.30.41", + "185.252.30.42", + "185.252.30.43", + "185.252.30.175", + "185.252.30.188", + "185.252.30.200", + "185.252.30.206", + "185.252.31.4", + "185.252.31.5", + "185.252.31.8", + "185.252.31.10", + "185.252.31.19", + "185.252.31.21", + "185.252.31.22", + "185.252.31.27", + "185.252.31.28", + "185.252.31.32", + "185.252.31.33", + "185.252.31.40", + "185.252.31.42", + "185.252.31.43", + "185.252.31.62", + "185.252.31.63", + "185.252.31.65", + "185.252.31.99", + "185.252.31.103", + "185.252.31.105", + "185.252.31.116", + "185.252.31.119", + "185.252.31.120", + "185.252.31.138", + "185.252.31.140", + "185.252.31.142", + "185.252.31.146", + "185.252.31.161", + "185.252.31.164", + "185.252.31.165", + "185.252.31.166", + "185.252.31.201", + "185.252.31.203", + "185.252.31.213", + "185.252.31.223", + "185.252.86.15", + "185.252.86.17", + "185.252.86.20", + "185.252.86.30", + "185.252.86.31", + "185.252.86.34", + "185.252.86.36", + "185.252.86.38", + "185.252.86.43", + "185.252.86.49", + "185.252.86.56", + "185.252.86.75", + "185.252.86.77", + "185.252.86.79", + "185.252.86.81", + "185.252.86.102", + "185.252.86.107", + "185.252.86.110", + "185.252.86.120", + "185.252.86.121", + "185.252.86.125", + "185.252.86.126", + "185.252.86.145", + "185.252.86.164", + "185.252.86.165", + "185.252.86.168", + "185.252.86.171", + "185.252.86.181", + "185.252.86.183", + "185.252.86.185", + "185.252.200.157", + "185.254.165.5", + "185.255.88.3", + "185.255.88.11", + "185.255.88.12", + "185.255.88.13", + "185.255.88.15", + "185.255.88.18", + "185.255.88.19", + "185.255.88.24", + "185.255.88.36", + "185.255.88.42", + "185.255.88.59", + "185.255.88.61", + "185.255.88.63", + "185.255.88.64", + "185.255.88.66", + "185.255.88.68", + "185.255.88.74", + "185.255.88.79", + "185.255.88.85", + "185.255.88.102", + "185.255.88.106", + "185.255.88.107", + "185.255.88.109", + "185.255.88.110", + "185.255.88.112", + "185.255.88.113", + "185.255.88.115", + "185.255.88.117", + "185.255.88.118", + "185.255.88.119", + "185.255.88.123", + "185.255.88.124", + "185.255.88.126", + "185.255.88.130", + "185.255.88.131", + "185.255.88.132", + "185.255.88.134", + "185.255.88.135", + "185.255.88.136", + "185.255.88.137", + "185.255.88.139", + "185.255.88.140", + "185.255.88.142", + "185.255.88.145", + "185.255.88.148", + "185.255.88.149", + "185.255.88.150", + "185.255.88.152", + "185.255.88.156", + "185.255.88.157", + "185.255.88.160", + "185.255.88.161", + "185.255.88.164", + "185.255.88.167", + "185.255.88.169", + "185.255.88.171", + "185.255.88.172", + "185.255.88.173", + "185.255.88.174", + "185.255.88.175", + "185.255.88.176", + "185.255.88.177", + "185.255.88.182", + "185.255.88.188", + "185.255.88.190", + "185.255.88.191", + "185.255.88.194", + "185.255.88.198", + "185.255.88.200", + "185.255.88.203", + "185.255.88.207", + "185.255.88.212", + "185.255.88.214", + "185.255.88.216", + "185.255.88.217", + "185.255.88.218", + "185.255.88.219", + "185.255.89.6", + "185.255.89.7", + "185.255.89.8", + "185.255.89.10", + "185.255.89.12", + "185.255.89.13", + "185.255.89.14", + "185.255.89.15", + "185.255.89.18", + "185.255.89.26", + "185.255.89.37", + "185.255.89.38", + "185.255.89.40", + "185.255.89.46", + "185.255.89.57", + "185.255.89.60", + "185.255.89.66", + "185.255.89.86", + "185.255.89.90", + "185.255.89.94", + "185.255.89.102", + "185.255.89.103", + "185.255.89.104", + "185.255.89.106", + "185.255.89.110", + "185.255.89.112", + "185.255.89.116", + "185.255.89.118", + "185.255.89.119", + "185.255.89.120", + "185.255.89.122", + "185.255.89.123", + "185.255.89.126", + "185.255.89.128", + "185.255.89.131", + "185.255.89.132", + "185.255.89.134", + "185.255.89.139", + "185.255.89.142", + "185.255.89.143", + "185.255.89.147", + "185.255.89.149", + "185.255.89.150", + "185.255.89.151", + "185.255.89.152", + "185.255.89.153", + "185.255.89.157", + "185.255.89.160", + "185.255.89.166", + "185.255.89.168", + "185.255.89.171", + "185.255.89.173", + "185.255.89.181", + "185.255.89.191", + "185.255.89.199", + "185.255.89.200", + "185.255.89.205", + "185.255.89.207", + "185.255.89.210", + "185.255.89.216", + "185.255.89.229", + "185.255.89.233", + "185.255.89.237", + "185.255.89.243", + "185.255.89.244", + "185.255.90.15", + "185.255.90.17", + "185.255.90.41", + "185.255.90.48", + "185.255.90.82", + "185.255.90.104", + "185.255.90.105", + "185.255.90.109", + "185.255.90.123", + "185.255.90.125", + "185.255.90.129", + "185.255.90.137", + "185.255.90.148", + "185.255.90.152", + "185.255.90.159", + "185.255.90.162", + "185.255.90.163", + "185.255.90.202", + "185.255.90.205", + "185.255.91.15", + "185.255.91.20", + "185.255.91.32", + "185.255.91.36", + "185.255.91.48", + "185.255.91.58", + "185.255.91.60", + "185.255.91.80", + "185.255.91.116", + "185.255.91.120", + "185.255.91.132", + "185.255.91.140", + "185.255.91.144", + "185.255.91.147", + "185.255.91.160", + "185.255.91.173", + "185.255.91.182", + "185.255.91.190", + "185.255.91.208", + "185.255.91.212", + "185.255.91.213", + "185.255.91.215", + "185.255.91.219", + "185.255.91.227", + "185.255.91.229", + "185.255.91.232", + "185.255.91.234", + "185.255.91.235", + "185.255.91.237", + "185.255.91.242", + "185.255.91.243", + "185.255.91.244", + "185.255.208.144", + "185.255.208.221", + "185.255.208.222", + "185.255.208.243", + "185.255.209.48", + "188.0.240.12", + "188.0.240.13", + "188.0.240.42", + "188.0.240.43", + "188.0.240.44", + "188.0.240.85", + "188.0.240.130", + "188.0.240.131", + "188.0.240.134", + "188.0.240.186", + "188.0.240.187", + "188.0.240.188", + "188.0.240.189", + "188.0.240.190", + "188.0.240.194", + "188.0.240.195", + "188.0.240.217", + "188.0.241.19", + "188.0.241.76", + "188.0.241.77", + "188.0.241.78", + "188.0.241.101", + "188.0.241.248", + "188.0.248.66", + "188.0.248.233", + "188.0.253.168", + "188.75.65.222", + "188.75.78.130", + "188.75.78.136", + "188.75.78.138", + "188.75.78.162", + "188.75.78.164", + "188.75.78.172", + "188.75.78.174", + "188.75.78.184", + "188.75.78.187", + "188.75.78.189", + "188.75.80.176", + "188.75.82.172", + "188.75.82.173", + "188.75.95.22", + "188.75.95.66", + "188.75.106.66", + "188.75.106.69", + "188.75.106.70", + "188.75.106.178", + "188.75.106.179", + "188.75.106.184", + "188.75.106.185", + "188.121.96.94", + "188.121.97.2", + "188.121.99.119", + "188.121.99.121", + "188.121.99.199", + "188.121.100.85", + "188.121.100.96", + "188.121.100.104", + "188.121.100.200", + "188.121.100.251", + "188.121.101.238", + "188.121.102.172", + "188.121.102.175", + "188.121.102.244", + "188.121.103.36", + "188.121.103.186", + "188.121.103.193", + "188.121.104.41", + "188.121.105.164", + "188.121.105.234", + "188.121.107.156", + "188.121.109.173", + "188.121.110.50", + "188.121.110.163", + "188.121.110.254", + "188.121.112.58", + "188.121.112.78", + "188.121.112.92", + "188.121.112.127", + "188.121.112.198", + "188.121.113.10", + "188.121.113.74", + "188.121.113.102", + "188.121.113.191", + "188.121.113.210", + "188.121.113.213", + "188.121.114.188", + "188.121.114.240", + "188.121.114.247", + "188.121.115.45", + "188.121.115.61", + "188.121.116.50", + "188.121.116.80", + "188.121.116.139", + "188.121.116.237", + "188.121.116.252", + "188.121.117.27", + "188.121.117.64", + "188.121.117.146", + "188.121.117.170", + "188.121.118.14", + "188.121.118.52", + "188.121.118.134", + "188.121.118.179", + "188.121.119.37", + "188.121.119.58", + "188.121.119.90", + "188.121.119.103", + "188.121.119.202", + "188.121.120.171", + "188.121.122.33", + "188.121.123.26", + "188.121.123.159", + "188.121.124.11", + "188.121.125.89", + "188.121.132.140", + "188.121.157.234", + "188.121.158.211", + "188.136.136.9", + "188.136.136.18", + "188.136.136.44", + "188.136.137.137", + "188.136.137.138", + "188.136.138.16", + "188.136.138.17", + "188.136.138.18", + "188.136.138.19", + "188.136.138.21", + "188.136.138.23", + "188.136.138.30", + "188.136.138.31", + "188.136.138.32", + "188.136.138.33", + "188.136.138.35", + "188.136.138.36", + "188.136.138.37", + "188.136.138.38", + "188.136.138.39", + "188.136.138.40", + "188.136.138.41", + "188.136.138.42", + "188.136.138.43", + "188.136.138.44", + "188.136.138.100", + "188.136.142.225", + "188.136.154.151", + "188.136.172.3", + "188.136.172.170", + "188.136.194.102", + "188.136.208.245", + "188.191.176.47", + "188.191.176.48", + "188.208.144.158", + "188.208.149.251", + "188.209.7.172", + "188.209.8.200", + "188.209.9.1", + "188.209.67.214", + "188.209.68.97", + "188.209.72.9", + "188.209.79.76", + "188.209.152.106", + "188.210.112.2", + "188.210.115.164", + "188.210.115.245", + "188.210.120.63", + "188.210.121.66", + "188.211.36.201", + "188.211.36.224", + "188.211.66.248", + "188.211.69.120", + "188.211.72.197", + "188.211.74.26", + "188.211.79.18", + "188.211.79.68", + "188.211.81.31", + "188.211.83.163", + "188.211.85.185", + "188.211.86.127", + "188.211.91.220", + "188.211.98.243", + "188.211.100.56", + "188.211.100.115", + "188.211.102.37", + "188.211.102.179", + "188.211.102.212", + "188.211.108.253", + "188.211.111.51", + "188.211.113.70", + "188.211.119.60", + "188.211.124.229", + "188.212.22.5", + "188.212.22.7", + "188.212.22.10", + "188.212.22.17", + "188.212.22.19", + "188.212.22.20", + "188.212.22.22", + "188.212.22.23", + "188.212.22.36", + "188.212.22.38", + "188.212.22.39", + "188.212.22.40", + "188.212.22.41", + "188.212.22.46", + "188.212.22.53", + "188.212.22.54", + "188.212.22.59", + "188.212.22.81", + "188.212.22.82", + "188.212.22.99", + "188.212.22.106", + "188.212.22.116", + "188.212.22.122", + "188.212.22.130", + "188.212.22.131", + "188.212.22.133", + "188.212.22.134", + "188.212.22.152", + "188.212.22.158", + "188.212.22.163", + "188.212.22.164", + "188.212.22.167", + "188.212.22.171", + "188.212.22.178", + "188.212.22.179", + "188.212.22.180", + "188.212.22.187", + "188.212.22.190", + "188.212.22.195", + "188.212.22.198", + "188.212.22.199", + "188.212.22.200", + "188.212.22.201", + "188.212.22.205", + "188.212.22.213", + "188.212.22.214", + "188.212.22.216", + "188.212.22.226", + "188.212.22.227", + "188.212.22.228", + "188.212.22.229", + "188.212.22.230", + "188.212.22.234", + "188.212.22.235", + "188.212.22.236", + "188.212.22.237", + "188.212.22.238", + "188.212.22.242", + "188.212.22.243", + "188.212.22.248", + "188.212.22.249", + "188.212.22.251", + "188.212.22.252", + "188.212.22.253", + "188.212.22.254", + "188.212.86.72", + "188.212.148.101", + "188.212.163.97", + "188.213.65.54", + "188.213.65.140", + "188.213.65.141", + "188.213.65.156", + "188.213.66.6", + "188.213.66.9", + "188.213.66.12", + "188.213.66.13", + "188.213.66.14", + "188.213.66.16", + "188.213.66.17", + "188.213.66.18", + "188.213.66.21", + "188.213.66.23", + "188.213.66.24", + "188.213.66.26", + "188.213.66.30", + "188.213.66.91", + "188.213.66.139", + "188.213.66.141", + "188.213.66.156", + "188.213.66.157", + "188.213.66.158", + "188.213.66.172", + "188.213.68.3", + "188.213.68.4", + "188.213.68.5", + "188.213.68.7", + "188.213.68.12", + "188.213.68.13", + "188.213.68.99", + "188.213.72.13", + "188.213.72.71", + "188.213.72.84", + "188.213.72.85", + "188.213.75.5", + "188.213.75.51", + "188.213.154.188", + "188.213.187.132", + "188.213.188.41", + "188.213.188.123", + "188.213.196.191", + "188.213.196.202", + "188.213.198.4", + "188.213.198.90", + "188.213.198.236", + "188.213.199.7", + "188.213.199.80", + "188.213.199.141", + "188.213.209.31", + "188.213.209.49", + "188.215.160.93", + "188.215.174.178", + "188.240.212.208", + "188.240.212.209", + "188.240.212.210", + "188.240.212.211", + "188.240.212.212", + "188.240.212.213", + "188.240.212.214", + "188.240.212.215", + "193.3.31.11", + "193.3.31.12", + "193.3.31.20", + "193.3.182.25", + "193.3.182.59", + "193.3.231.146", + "193.3.231.150", + "193.3.231.158", + "193.3.231.194", + "193.3.231.210", + "193.3.231.218", + "193.3.231.222", + "193.3.231.230", + "193.3.231.254", + "193.5.44.11", + "193.5.44.12", + "193.5.44.14", + "193.5.44.26", + "193.5.44.30", + "193.5.44.58", + "193.5.44.62", + "193.5.44.67", + "193.5.44.80", + "193.5.44.86", + "193.5.44.88", + "193.5.44.92", + "193.5.44.94", + "193.5.44.105", + "193.5.44.107", + "193.5.44.110", + "193.5.44.115", + "193.5.44.152", + "193.5.44.158", + "193.5.44.159", + "193.5.44.161", + "193.5.44.192", + "193.5.44.237", + "193.8.139.55", + "193.8.139.56", + "193.19.147.244", + "193.19.147.245", + "193.35.62.1", + "193.35.230.4", + "193.35.230.5", + "193.35.230.6", + "193.35.230.7", + "193.35.230.8", + "193.35.230.10", + "193.35.230.13", + "193.35.230.14", + "193.35.230.15", + "193.35.230.16", + "193.35.230.17", + "193.35.230.18", + "193.35.230.19", + "193.35.230.20", + "193.35.230.21", + "193.35.230.22", + "193.35.230.23", + "193.35.230.24", + "193.35.230.25", + "193.35.230.26", + "193.35.230.27", + "193.35.230.28", + "193.35.230.29", + "193.35.230.30", + "193.35.230.31", + "193.35.230.32", + "193.37.37.38", + "193.37.37.243", + "193.39.9.3", + "193.39.9.32", + "193.39.9.54", + "193.39.9.92", + "193.39.9.105", + "193.39.9.107", + "193.39.9.117", + "193.39.9.162", + "193.39.9.166", + "193.39.9.188", + "193.39.9.191", + "193.39.9.214", + "193.39.9.236", + "193.39.9.250", + "193.56.61.66", + "193.56.61.67", + "193.56.118.1", + "193.58.119.50", + "193.58.119.130", + "193.58.119.198", + "193.58.119.234", + "193.58.119.247", + "193.84.255.2", + "193.84.255.20", + "193.84.255.66", + "193.84.255.95", + "193.105.2.90", + "193.105.2.91", + "193.105.6.15", + "193.105.6.30", + "193.105.6.118", + "193.105.6.120", + "193.105.6.121", + "193.105.6.131", + "193.105.6.133", + "193.105.6.235", + "193.105.234.13", + "193.105.234.17", + "193.105.234.19", + "193.105.234.20", + "193.105.234.29", + "193.105.234.38", + "193.105.234.58", + "193.105.234.68", + "193.105.234.81", + "193.105.234.85", + "193.105.234.87", + "193.105.234.88", + "193.105.234.90", + "193.105.234.93", + "193.105.234.96", + "193.105.234.100", + "193.105.234.104", + "193.105.234.105", + "193.105.234.111", + "193.105.234.115", + "193.105.234.119", + "193.105.234.131", + "193.105.234.143", + "193.105.234.144", + "193.105.234.151", + "193.105.234.164", + "193.105.234.165", + "193.105.234.167", + "193.105.234.180", + "193.105.234.191", + "193.105.234.207", + "193.105.234.216", + "193.105.234.223", + "193.105.234.229", + "193.105.234.230", + "193.105.234.231", + "193.105.234.234", + "193.105.234.237", + "193.105.234.243", + "193.107.48.72", + "193.107.48.203", + "193.107.48.222", + "193.111.234.35", + "193.111.234.40", + "193.111.234.61", + "193.111.234.72", + "193.111.234.114", + "193.111.234.147", + "193.111.234.152", + "193.111.234.153", + "193.111.234.196", + "193.111.235.7", + "193.111.235.77", + "193.111.235.103", + "193.111.235.115", + "193.111.235.160", + "193.111.235.179", + "193.111.236.99", + "193.111.236.101", + "193.111.236.103", + "193.134.100.12", + "193.134.100.60", + "193.134.100.148", + "193.134.100.155", + "193.134.101.20", + "193.134.101.172", + "193.134.101.173", + "193.141.64.5", + "193.141.64.7", + "193.141.64.10", + "193.141.64.11", + "193.141.64.23", + "193.141.64.32", + "193.141.64.35", + "193.141.64.38", + "193.141.64.44", + "193.141.64.47", + "193.141.64.48", + "193.141.64.55", + "193.141.64.61", + "193.141.64.66", + "193.141.64.68", + "193.141.64.88", + "193.141.64.90", + "193.141.64.103", + "193.141.64.107", + "193.141.64.109", + "193.141.64.110", + "193.141.64.111", + "193.141.64.115", + "193.141.64.117", + "193.141.64.118", + "193.141.64.119", + "193.141.64.120", + "193.141.64.121", + "193.141.64.122", + "193.141.64.123", + "193.141.64.125", + "193.141.64.126", + "193.141.64.128", + "193.141.64.129", + "193.141.64.130", + "193.141.64.134", + "193.141.64.140", + "193.141.64.142", + "193.141.64.143", + "193.141.64.146", + "193.141.64.149", + "193.141.64.152", + "193.141.64.153", + "193.141.64.154", + "193.141.64.157", + "193.141.64.158", + "193.141.64.160", + "193.141.64.174", + "193.141.64.182", + "193.141.64.191", + "193.141.64.192", + "193.141.64.212", + "193.141.64.213", + "193.141.64.215", + "193.141.64.217", + "193.141.64.221", + "193.141.64.231", + "193.141.64.239", + "193.141.64.241", + "193.141.64.242", + "193.141.64.247", + "193.141.64.248", + "193.141.65.17", + "193.141.65.24", + "193.141.65.26", + "193.141.65.28", + "193.141.65.34", + "193.141.65.42", + "193.141.65.45", + "193.141.65.56", + "193.141.65.67", + "193.141.65.68", + "193.141.65.73", + "193.141.65.78", + "193.141.65.81", + "193.141.65.82", + "193.141.65.92", + "193.141.65.94", + "193.141.65.97", + "193.141.65.99", + "193.141.65.100", + "193.141.65.101", + "193.141.65.106", + "193.141.65.107", + "193.141.65.112", + "193.141.65.115", + "193.141.65.125", + "193.141.65.129", + "193.141.65.131", + "193.141.65.142", + "193.141.65.146", + "193.141.65.155", + "193.141.65.160", + "193.141.65.167", + "193.141.65.168", + "193.141.65.169", + "193.141.65.178", + "193.141.65.185", + "193.141.65.186", + "193.141.65.188", + "193.141.65.221", + "193.141.65.222", + "193.141.65.224", + "193.141.65.228", + "193.141.65.234", + "193.141.65.244", + "193.141.65.253", + "193.148.64.119", + "193.148.65.65", + "193.148.66.1", + "193.148.66.11", + "193.148.66.66", + "193.148.66.190", + "193.151.128.225", + "193.151.130.4", + "193.151.131.113", + "193.151.131.151", + "193.151.134.65", + "193.151.134.90", + "193.151.134.104", + "193.151.134.156", + "193.151.134.206", + "193.151.135.112", + "193.151.135.182", + "193.151.136.80", + "193.151.137.194", + "193.151.140.11", + "193.151.144.140", + "193.151.144.201", + "193.151.145.107", + "193.151.145.125", + "193.151.146.234", + "193.151.149.64", + "193.151.149.160", + "193.151.153.95", + "193.151.153.225", + "193.151.154.24", + "193.151.155.149", + "193.151.157.12", + "193.151.157.13", + "193.151.157.14", + "193.151.157.15", + "193.151.157.16", + "193.151.157.17", + "193.151.157.19", + "193.151.157.20", + "193.151.157.29", + "193.151.157.30", + "193.151.159.58", + "193.151.159.59", + "193.151.159.60", + "193.151.159.61", + "193.151.159.62", + "193.151.159.226", + "193.151.159.227", + "193.151.159.228", + "193.151.159.245", + "193.151.159.246", + "193.151.159.247", + "193.151.159.253", + "193.151.159.254", + "193.162.129.10", + "193.162.129.11", + "193.162.129.14", + "193.162.129.35", + "193.162.129.59", + "193.162.129.63", + "193.162.129.77", + "193.162.129.78", + "193.162.129.156", + "193.162.129.170", + "193.162.129.212", + "193.162.129.216", + "193.162.129.217", + "193.162.129.223", + "193.162.129.224", + "193.162.129.226", + "193.162.129.233", + "193.162.129.242", + "193.162.129.248", + "193.176.97.94", + "193.178.200.3", + "193.178.200.5", + "193.186.32.5", + "193.186.32.7", + "193.186.32.32", + "193.186.32.45", + "193.186.32.56", + "193.186.32.58", + "193.186.32.59", + "193.186.32.81", + "193.186.32.87", + "193.186.32.105", + "193.186.32.106", + "193.186.32.108", + "193.186.32.109", + "193.186.32.111", + "193.186.32.113", + "193.186.32.123", + "193.186.32.139", + "193.186.32.141", + "193.186.32.145", + "193.186.32.148", + "193.186.32.152", + "193.186.32.155", + "193.186.32.156", + "193.186.32.162", + "193.186.32.170", + "193.186.32.178", + "193.186.32.184", + "193.186.32.197", + "193.186.32.199", + "193.186.32.201", + "193.186.32.203", + "193.186.32.216", + "193.186.32.222", + "193.186.32.227", + "193.186.32.239", + "193.186.32.242", + "193.186.32.246", + "193.186.32.247", + "193.186.32.248", + "193.186.32.249", + "193.186.32.251", + "193.186.32.252", + "193.200.148.9", + "193.200.148.66", + "193.222.51.1", + "193.228.90.2", + "193.228.90.3", + "193.228.90.4", + "193.228.90.5", + "193.228.90.6", + "193.228.90.7", + "193.228.90.8", + "193.228.90.13", + "193.228.90.19", + "193.228.90.20", + "193.228.90.21", + "193.228.90.23", + "193.228.90.25", + "193.228.90.27", + "193.228.90.28", + "193.228.90.35", + "193.228.90.37", + "193.228.90.39", + "193.228.90.40", + "193.228.90.42", + "193.228.90.43", + "193.228.90.46", + "193.228.90.48", + "193.228.90.57", + "193.228.90.63", + "193.228.90.64", + "193.228.90.70", + "193.228.90.72", + "193.228.90.76", + "193.228.90.78", + "193.228.90.82", + "193.228.90.87", + "193.228.90.90", + "193.228.90.91", + "193.228.90.92", + "193.228.90.93", + "193.228.90.99", + "193.228.90.101", + "193.228.90.102", + "193.228.90.111", + "193.228.90.112", + "193.228.90.113", + "193.228.90.119", + "193.228.90.120", + "193.228.90.130", + "193.228.90.131", + "193.228.90.132", + "193.228.90.136", + "193.228.90.139", + "193.228.90.141", + "193.228.90.144", + "193.228.90.145", + "193.228.90.147", + "193.228.90.148", + "193.228.90.156", + "193.228.90.161", + "193.228.90.180", + "193.228.90.182", + "193.228.90.185", + "193.228.90.186", + "193.228.90.188", + "193.228.90.189", + "193.228.90.191", + "193.228.90.198", + "193.228.90.199", + "193.228.90.238", + "193.228.90.244", + "193.228.90.245", + "193.228.90.253", + "193.228.90.254", + "193.228.169.51", + "193.228.169.234", + "193.242.125.12", + "193.242.125.15", + "193.242.125.25", + "193.242.125.28", + "193.242.125.29", + "193.242.125.35", + "193.242.125.38", + "193.242.125.47", + "193.242.125.51", + "193.242.125.61", + "193.242.125.63", + "193.242.125.65", + "193.242.125.69", + "193.242.125.73", + "193.242.125.83", + "193.242.125.93", + "193.242.125.112", + "193.242.125.124", + "193.242.125.135", + "193.242.125.141", + "193.242.125.156", + "193.242.125.186", + "193.242.125.189", + "193.242.125.197", + "193.242.125.198", + "193.242.125.217", + "193.242.125.221", + "193.242.125.223", + "193.242.125.232", + "193.242.125.241", + "193.242.125.251", + "193.242.125.254", + "193.242.208.10", + "193.242.208.21", + "193.242.208.94", + "193.242.208.110", + "193.242.208.124", + "193.242.208.128", + "193.242.208.166", + "193.242.208.185", + "193.242.208.245", + "193.246.200.118", + "193.246.200.121", + "194.1.155.45", + "194.5.175.15", + "194.5.175.22", + "194.5.175.24", + "194.5.175.29", + "194.5.175.33", + "194.5.175.35", + "194.5.175.37", + "194.5.175.38", + "194.5.175.42", + "194.5.175.44", + "194.5.175.46", + "194.5.175.52", + "194.5.175.61", + "194.5.175.73", + "194.5.175.74", + "194.5.175.75", + "194.5.175.76", + "194.5.175.81", + "194.5.175.89", + "194.5.175.90", + "194.5.175.104", + "194.5.175.113", + "194.5.175.114", + "194.5.175.115", + "194.5.175.117", + "194.5.175.119", + "194.5.175.132", + "194.5.175.139", + "194.5.175.144", + "194.5.175.145", + "194.5.175.146", + "194.5.175.154", + "194.5.175.157", + "194.5.175.158", + "194.5.175.164", + "194.5.175.169", + "194.5.175.170", + "194.5.175.178", + "194.5.175.181", + "194.5.175.183", + "194.5.175.186", + "194.5.175.190", + "194.5.175.200", + "194.5.175.202", + "194.5.175.216", + "194.5.175.217", + "194.5.175.236", + "194.5.175.242", + "194.5.175.244", + "194.5.175.246", + "194.5.175.247", + "194.5.175.249", + "194.5.175.251", + "194.5.176.46", + "194.5.176.203", + "194.5.176.206", + "194.5.177.120", + "194.5.178.48", + "194.5.178.81", + "194.5.179.79", + "194.5.179.181", + "194.5.188.18", + "194.5.188.40", + "194.5.188.41", + "194.5.188.42", + "194.5.188.53", + "194.5.188.56", + "194.5.188.59", + "194.5.188.63", + "194.5.188.66", + "194.5.188.76", + "194.5.188.79", + "194.5.188.83", + "194.5.188.93", + "194.5.188.112", + "194.5.188.124", + "194.5.188.126", + "194.5.188.128", + "194.5.188.132", + "194.5.188.136", + "194.5.188.147", + "194.5.188.148", + "194.5.188.159", + "194.5.188.161", + "194.5.188.169", + "194.5.188.183", + "194.5.188.193", + "194.5.188.194", + "194.5.188.198", + "194.5.188.208", + "194.5.188.214", + "194.5.188.216", + "194.5.188.222", + "194.5.188.225", + "194.5.188.226", + "194.5.188.230", + "194.5.188.231", + "194.5.188.236", + "194.5.188.237", + "194.5.188.240", + "194.5.188.241", + "194.5.188.242", + "194.5.188.243", + "194.5.188.246", + "194.5.188.247", + "194.5.188.251", + "194.5.188.252", + "194.5.195.14", + "194.5.195.20", + "194.5.195.23", + "194.5.195.24", + "194.5.195.33", + "194.5.195.37", + "194.5.195.50", + "194.5.195.53", + "194.5.195.62", + "194.5.195.66", + "194.5.195.69", + "194.5.195.76", + "194.5.195.79", + "194.5.195.80", + "194.5.195.89", + "194.5.195.91", + "194.5.195.92", + "194.5.195.93", + "194.5.195.96", + "194.5.195.100", + "194.5.195.101", + "194.5.195.104", + "194.5.195.117", + "194.5.195.120", + "194.5.195.126", + "194.5.195.129", + "194.5.195.133", + "194.5.195.134", + "194.5.195.143", + "194.5.195.146", + "194.5.195.152", + "194.5.195.153", + "194.5.195.154", + "194.5.195.155", + "194.5.195.156", + "194.5.195.158", + "194.5.195.161", + "194.5.195.162", + "194.5.195.163", + "194.5.195.164", + "194.5.195.167", + "194.5.195.168", + "194.5.195.172", + "194.5.195.175", + "194.5.195.176", + "194.5.195.185", + "194.5.195.188", + "194.5.195.191", + "194.5.195.192", + "194.5.195.200", + "194.5.195.205", + "194.5.195.215", + "194.5.195.219", + "194.5.195.221", + "194.5.195.226", + "194.5.195.227", + "194.5.195.234", + "194.5.195.244", + "194.5.195.247", + "194.5.195.248", + "194.5.195.249", + "194.5.205.10", + "194.5.205.11", + "194.5.205.12", + "194.5.205.16", + "194.5.205.31", + "194.5.205.32", + "194.5.205.34", + "194.5.205.36", + "194.5.205.37", + "194.5.205.39", + "194.5.205.40", + "194.5.205.64", + "194.5.205.70", + "194.5.205.85", + "194.5.205.86", + "194.5.205.88", + "194.5.205.89", + "194.5.205.91", + "194.5.205.92", + "194.5.205.93", + "194.5.205.97", + "194.5.205.110", + "194.5.205.117", + "194.5.205.121", + "194.5.205.128", + "194.5.205.149", + "194.5.205.150", + "194.5.205.158", + "194.5.205.161", + "194.5.205.163", + "194.5.205.166", + "194.5.205.168", + "194.5.205.169", + "194.5.205.170", + "194.5.205.172", + "194.5.205.176", + "194.5.205.178", + "194.5.205.179", + "194.5.205.181", + "194.5.205.191", + "194.5.205.192", + "194.5.205.193", + "194.5.205.200", + "194.5.205.203", + "194.5.205.213", + "194.5.205.214", + "194.5.205.231", + "194.5.205.237", + "194.5.205.239", + "194.5.205.247", + "194.5.205.249", + "194.5.205.250", + "194.9.56.58", + "194.9.56.62", + "194.9.56.102", + "194.9.56.124", + "194.9.56.138", + "194.9.57.212", + "194.9.80.10", + "194.9.80.19", + "194.9.80.45", + "194.9.80.58", + "194.9.80.75", + "194.9.80.153", + "194.9.80.154", + "194.9.80.155", + "194.9.80.156", + "194.9.80.157", + "194.9.80.158", + "194.9.80.160", + "194.9.80.193", + "194.9.80.202", + "194.9.81.51", + "194.9.81.131", + "194.31.108.2", + "194.31.108.3", + "194.31.108.4", + "194.31.108.7", + "194.31.108.8", + "194.31.108.9", + "194.31.108.14", + "194.31.108.15", + "194.31.108.16", + "194.31.108.24", + "194.31.108.27", + "194.31.108.28", + "194.31.108.83", + "194.31.108.88", + "194.31.194.2", + "194.31.194.36", + "194.31.194.37", + "194.31.194.42", + "194.31.194.100", + "194.31.194.105", + "194.31.194.114", + "194.31.194.125", + "194.31.194.130", + "194.31.194.143", + "194.31.194.145", + "194.31.194.146", + "194.31.194.148", + "194.31.194.158", + "194.31.194.163", + "194.31.194.178", + "194.31.194.194", + "194.31.194.195", + "194.31.194.205", + "194.31.194.217", + "194.31.194.221", + "194.33.105.28", + "194.33.105.69", + "194.33.105.71", + "194.33.105.72", + "194.33.105.134", + "194.33.105.139", + "194.33.105.152", + "194.33.105.168", + "194.33.105.169", + "194.33.105.206", + "194.33.105.209", + "194.33.105.222", + "194.33.105.232", + "194.33.105.234", + "194.33.106.103", + "194.33.107.107", + "194.33.107.108", + "194.33.107.240", + "194.34.162.150", + "194.34.163.53", + "194.34.163.54", + "194.36.174.2", + "194.36.174.31", + "194.36.174.56", + "194.36.174.57", + "194.36.174.64", + "194.36.174.78", + "194.36.174.80", + "194.36.174.108", + "194.36.174.110", + "194.36.174.111", + "194.36.174.151", + "194.36.174.152", + "194.36.174.153", + "194.36.174.161", + "194.36.174.162", + "194.36.174.167", + "194.36.174.172", + "194.36.174.175", + "194.36.174.190", + "194.36.174.200", + "194.36.174.209", + "194.36.174.231", + "194.39.254.42", + "194.39.254.43", + "194.39.254.50", + "194.39.254.51", + "194.39.254.52", + "194.39.254.53", + "194.39.254.59", + "194.41.48.20", + "194.41.51.34", + "194.48.198.15", + "194.48.198.16", + "194.48.198.18", + "194.48.198.29", + "194.48.198.37", + "194.48.198.38", + "194.48.198.40", + "194.48.198.65", + "194.48.198.70", + "194.48.198.74", + "194.48.198.75", + "194.48.198.85", + "194.48.198.92", + "194.48.198.93", + "194.48.198.95", + "194.48.198.100", + "194.48.198.105", + "194.48.198.108", + "194.48.198.121", + "194.48.198.133", + "194.48.198.145", + "194.48.198.147", + "194.48.198.164", + "194.48.198.178", + "194.48.198.193", + "194.48.198.206", + "194.48.198.208", + "194.48.198.216", + "194.48.198.226", + "194.48.198.232", + "194.48.198.234", + "194.48.198.239", + "194.48.198.244", + "194.48.198.248", + "194.53.122.10", + "194.53.122.11", + "194.53.122.13", + "194.53.122.14", + "194.53.122.15", + "194.53.122.16", + "194.53.122.17", + "194.53.122.18", + "194.53.122.19", + "194.53.122.20", + "194.53.122.21", + "194.53.122.25", + "194.53.122.26", + "194.53.122.27", + "194.53.122.28", + "194.53.122.29", + "194.53.122.30", + "194.53.122.31", + "194.53.122.32", + "194.53.122.33", + "194.53.122.34", + "194.53.122.35", + "194.53.122.36", + "194.53.122.37", + "194.53.122.38", + "194.53.122.39", + "194.53.122.40", + "194.53.122.42", + "194.53.122.43", + "194.53.122.44", + "194.53.122.45", + "194.53.122.46", + "194.53.122.47", + "194.53.122.48", + "194.53.122.49", + "194.53.122.50", + "194.53.122.51", + "194.53.122.52", + "194.53.122.53", + "194.53.122.54", + "194.53.122.55", + "194.53.122.56", + "194.53.122.57", + "194.53.122.58", + "194.53.122.59", + "194.53.122.60", + "194.53.122.62", + "194.53.122.63", + "194.53.122.64", + "194.53.122.65", + "194.53.122.66", + "194.53.122.67", + "194.53.122.68", + "194.53.122.69", + "194.53.122.71", + "194.53.122.72", + "194.53.122.73", + "194.53.122.74", + "194.53.122.77", + "194.53.122.78", + "194.53.122.79", + "194.53.122.80", + "194.53.122.81", + "194.53.122.82", + "194.53.122.84", + "194.53.122.85", + "194.53.122.86", + "194.53.122.87", + "194.53.122.88", + "194.53.122.89", + "194.53.122.90", + "194.53.122.91", + "194.53.122.92", + "194.53.122.93", + "194.53.122.94", + "194.53.122.95", + "194.53.122.96", + "194.53.122.97", + "194.53.122.98", + "194.53.122.99", + "194.53.122.100", + "194.53.122.102", + "194.53.122.103", + "194.53.122.104", + "194.53.122.105", + "194.53.122.106", + "194.53.122.107", + "194.53.122.108", + "194.53.122.109", + "194.53.122.110", + "194.53.122.111", + "194.53.122.112", + "194.53.122.113", + "194.53.122.114", + "194.53.122.115", + "194.53.122.116", + "194.53.122.117", + "194.53.122.118", + "194.53.122.120", + "194.53.122.121", + "194.53.122.122", + "194.53.122.124", + "194.53.122.125", + "194.53.122.127", + "194.53.122.128", + "194.53.122.129", + "194.53.122.130", + "194.53.122.131", + "194.53.122.132", + "194.53.122.133", + "194.53.122.134", + "194.53.122.135", + "194.53.122.136", + "194.53.122.137", + "194.53.122.138", + "194.53.122.139", + "194.53.122.140", + "194.53.122.142", + "194.53.122.144", + "194.53.122.145", + "194.53.122.146", + "194.53.122.147", + "194.53.122.148", + "194.53.122.149", + "194.53.122.151", + "194.53.122.152", + "194.53.122.153", + "194.53.122.154", + "194.53.122.155", + "194.53.122.156", + "194.53.122.157", + "194.53.122.158", + "194.53.122.159", + "194.53.122.160", + "194.53.122.161", + "194.53.122.162", + "194.53.122.163", + "194.53.122.164", + "194.53.122.165", + "194.53.122.166", + "194.53.122.167", + "194.53.122.168", + "194.53.122.169", + "194.53.122.170", + "194.53.122.171", + "194.53.122.172", + "194.53.122.173", + "194.53.122.174", + "194.53.122.175", + "194.53.122.176", + "194.53.122.177", + "194.53.122.179", + "194.53.122.180", + "194.53.122.181", + "194.53.122.182", + "194.53.122.183", + "194.53.122.184", + "194.53.122.185", + "194.53.122.186", + "194.53.122.187", + "194.53.122.188", + "194.53.122.189", + "194.53.122.190", + "194.53.122.191", + "194.53.122.192", + "194.53.122.193", + "194.53.122.194", + "194.53.122.195", + "194.53.122.196", + "194.53.122.197", + "194.53.122.198", + "194.53.122.199", + "194.53.122.200", + "194.53.122.201", + "194.53.122.202", + "194.53.122.203", + "194.53.122.204", + "194.53.122.205", + "194.53.122.206", + "194.53.122.207", + "194.53.122.208", + "194.53.122.209", + "194.53.122.210", + "194.53.122.211", + "194.53.122.212", + "194.53.122.213", + "194.53.122.214", + "194.53.122.215", + "194.53.122.216", + "194.53.122.217", + "194.53.122.218", + "194.53.122.219", + "194.53.122.220", + "194.53.122.221", + "194.53.122.222", + "194.53.122.223", + "194.53.122.225", + "194.53.122.226", + "194.53.122.227", + "194.53.122.228", + "194.53.122.229", + "194.53.122.230", + "194.53.122.231", + "194.53.122.232", + "194.53.122.233", + "194.53.122.234", + "194.53.122.235", + "194.53.122.236", + "194.53.122.237", + "194.53.122.238", + "194.53.122.239", + "194.53.122.240", + "194.53.122.241", + "194.53.122.242", + "194.53.122.243", + "194.53.122.244", + "194.53.122.245", + "194.53.122.246", + "194.53.122.247", + "194.53.122.249", + "194.53.122.250", + "194.56.148.22", + "194.56.148.76", + "194.56.148.179", + "194.56.148.222", + "194.59.170.5", + "194.59.170.6", + "194.59.170.15", + "194.59.170.19", + "194.59.170.21", + "194.59.170.22", + "194.59.170.23", + "194.59.170.24", + "194.59.170.25", + "194.59.170.26", + "194.59.170.27", + "194.59.170.28", + "194.59.170.29", + "194.59.170.30", + "194.59.170.49", + "194.59.170.60", + "194.59.170.61", + "194.59.170.75", + "194.59.170.82", + "194.59.170.83", + "194.59.170.90", + "194.59.170.100", + "194.59.170.115", + "194.59.170.117", + "194.59.170.119", + "194.59.170.120", + "194.59.170.132", + "194.59.170.138", + "194.59.170.144", + "194.59.170.146", + "194.59.170.154", + "194.59.170.166", + "194.59.170.174", + "194.59.170.192", + "194.59.170.207", + "194.59.170.228", + "194.59.170.232", + "194.59.170.243", + "194.59.171.11", + "194.59.171.53", + "194.59.171.153", + "194.59.171.154", + "194.59.171.185", + "194.59.214.2", + "194.59.214.3", + "194.59.214.4", + "194.59.214.7", + "194.59.214.10", + "194.59.214.30", + "194.59.214.31", + "194.59.214.35", + "194.59.214.36", + "194.59.214.37", + "194.59.214.38", + "194.59.214.55", + "194.59.214.71", + "194.59.214.74", + "194.59.214.75", + "194.59.214.76", + "194.59.214.85", + "194.59.214.91", + "194.59.214.92", + "194.59.214.93", + "194.59.214.99", + "194.59.214.100", + "194.59.214.102", + "194.59.214.103", + "194.59.214.104", + "194.59.214.105", + "194.59.214.106", + "194.59.214.108", + "194.59.214.110", + "194.59.214.116", + "194.59.214.120", + "194.59.214.121", + "194.59.214.123", + "194.59.214.124", + "194.59.214.125", + "194.59.214.129", + "194.59.214.148", + "194.59.214.160", + "194.59.214.161", + "194.59.214.162", + "194.59.214.163", + "194.59.214.164", + "194.59.214.165", + "194.59.214.166", + "194.59.214.174", + "194.59.214.176", + "194.59.214.195", + "194.59.214.200", + "194.59.214.202", + "194.59.214.203", + "194.59.214.205", + "194.59.214.209", + "194.59.214.210", + "194.59.215.11", + "194.59.215.36", + "194.59.215.37", + "194.59.215.49", + "194.59.215.51", + "194.59.215.242", + "194.59.215.246", + "194.60.209.91", + "194.60.210.66", + "194.60.210.67", + "194.60.230.25", + "194.60.230.28", + "194.60.230.50", + "194.60.230.53", + "194.60.230.55", + "194.60.230.58", + "194.60.230.75", + "194.60.230.78", + "194.60.230.84", + "194.60.230.88", + "194.60.230.92", + "194.60.230.148", + "194.60.230.161", + "194.60.230.174", + "194.60.230.194", + "194.60.230.195", + "194.60.230.203", + "194.60.230.208", + "194.60.231.12", + "194.60.231.30", + "194.60.231.34", + "194.60.231.47", + "194.60.231.77", + "194.60.231.84", + "194.60.231.112", + "194.60.231.122", + "194.60.231.149", + "194.60.231.156", + "194.60.231.157", + "194.60.231.182", + "194.60.231.196", + "194.60.231.234", + "194.62.17.18", + "194.62.17.22", + "194.62.17.26", + "194.62.17.43", + "194.62.17.50", + "194.62.17.59", + "194.62.17.60", + "194.62.17.70", + "194.62.17.146", + "194.62.17.155", + "194.62.17.174", + "194.62.17.178", + "194.62.17.190", + "194.62.17.203", + "194.62.17.206", + "194.62.17.212", + "194.62.17.234", + "194.62.17.254", + "194.62.43.5", + "194.62.43.6", + "194.62.43.14", + "194.62.43.15", + "194.62.43.44", + "194.62.43.45", + "194.62.43.55", + "194.62.43.58", + "194.62.43.66", + "194.62.43.67", + "194.62.43.80", + "194.62.43.83", + "194.62.43.91", + "194.62.43.92", + "194.62.43.94", + "194.62.43.99", + "194.62.43.100", + "194.62.43.101", + "194.62.43.102", + "194.62.43.104", + "194.62.43.108", + "194.62.43.123", + "194.62.43.126", + "194.62.43.129", + "194.62.43.139", + "194.62.43.140", + "194.62.43.141", + "194.62.43.146", + "194.62.43.155", + "194.62.43.175", + "194.62.43.180", + "194.62.43.183", + "194.62.43.184", + "194.62.43.194", + "194.62.43.207", + "194.62.43.208", + "194.62.43.216", + "194.62.43.218", + "194.62.43.227", + "194.62.43.233", + "194.62.43.236", + "194.62.43.237", + "194.62.43.238", + "194.62.43.240", + "194.62.43.241", + "194.143.140.9", + "194.143.141.9", + "194.146.151.18", + "194.146.151.19", + "194.146.151.20", + "194.146.151.22", + "194.147.142.10", + "194.147.142.29", + "194.147.142.35", + "194.147.142.39", + "194.147.142.41", + "194.147.142.44", + "194.147.142.57", + "194.147.142.64", + "194.147.142.67", + "194.147.142.71", + "194.147.142.72", + "194.147.142.78", + "194.147.142.79", + "194.147.142.82", + "194.147.142.94", + "194.147.142.100", + "194.147.142.104", + "194.147.142.122", + "194.147.142.123", + "194.147.142.125", + "194.147.142.131", + "194.147.142.136", + "194.147.142.153", + "194.147.142.174", + "194.147.142.180", + "194.147.142.186", + "194.147.142.192", + "194.147.142.195", + "194.147.142.200", + "194.147.142.213", + "194.147.142.218", + "194.147.142.233", + "194.147.142.239", + "194.147.142.242", + "194.147.142.243", + "194.147.142.250", + "194.147.164.49", + "194.147.222.37", + "194.147.222.53", + "194.147.222.68", + "194.147.222.76", + "194.147.222.82", + "194.147.222.84", + "194.147.222.85", + "194.147.222.86", + "194.147.222.87", + "194.147.222.94", + "194.147.222.96", + "194.147.222.131", + "194.147.222.137", + "194.147.222.143", + "194.147.222.144", + "194.147.222.146", + "194.147.222.150", + "194.147.222.153", + "194.147.222.162", + "194.147.222.176", + "194.147.222.228", + "194.147.222.229", + "194.147.222.230", + "194.150.68.28", + "194.150.68.29", + "194.150.68.44", + "194.150.68.45", + "194.150.68.51", + "194.150.69.119", + "194.150.69.164", + "194.150.70.11", + "194.150.70.12", + "194.150.71.198", + "194.150.71.230", + "194.180.11.12", + "194.180.11.14", + "194.180.11.17", + "194.180.11.26", + "194.180.11.28", + "194.180.11.37", + "194.180.11.43", + "194.180.11.56", + "194.180.11.64", + "194.180.11.79", + "194.180.11.85", + "194.180.11.94", + "194.180.11.110", + "194.180.11.117", + "194.180.11.118", + "194.180.11.128", + "194.180.11.134", + "194.180.11.142", + "194.180.11.148", + "194.180.11.153", + "194.180.11.177", + "194.180.11.185", + "194.180.11.204", + "194.180.11.206", + "194.180.11.212", + "194.180.11.213", + "194.180.11.216", + "194.180.11.217", + "194.180.11.219", + "194.180.11.225", + "194.180.11.226", + "194.180.11.229", + "194.180.11.233", + "194.180.11.236", + "194.180.11.243", + "194.180.11.254", + "194.225.16.3", + "194.225.24.20", + "194.225.25.15", + "194.225.25.16", + "194.225.40.49", + "194.225.40.114", + "194.225.50.6", + "194.225.62.66", + "194.225.62.80", + "194.225.62.98", + "194.225.62.107", + "194.225.73.141", + "194.225.73.151", + "194.225.90.4", + "194.225.90.5", + "194.225.92.15", + "194.225.92.17", + "194.225.100.42", + "194.225.100.43", + "194.225.106.21", + "194.225.106.22", + "194.225.115.9", + "194.225.115.10", + "194.225.117.10", + "194.225.131.64", + "194.225.133.40", + "194.225.136.3", + "194.225.136.4", + "194.225.144.2", + "194.225.144.3", + "194.225.152.10", + "194.225.152.12", + "194.225.166.7", + "194.225.166.8", + "194.225.183.61", + "194.225.183.62", + "194.225.228.4", + "194.225.228.5", + "195.24.233.75", + "195.28.11.3", + "195.28.11.27", + "195.28.11.35", + "195.28.11.37", + "195.28.11.47", + "195.28.11.58", + "195.28.11.60", + "195.28.11.63", + "195.28.11.64", + "195.28.11.67", + "195.28.11.68", + "195.28.11.70", + "195.28.11.99", + "195.28.11.104", + "195.28.11.109", + "195.28.11.156", + "195.28.11.179", + "195.28.11.220", + "195.28.11.236", + "195.28.11.249", + "195.28.11.250", + "195.28.11.251", + "195.28.11.253", + "195.28.169.201", + "195.28.169.248", + "195.28.169.253", + "195.62.4.20", + "195.62.4.21", + "195.62.4.22", + "195.62.4.24", + "195.62.4.25", + "195.62.4.26", + "195.88.189.70", + "195.88.189.71", + "195.88.189.74", + "195.88.189.76", + "195.88.189.77", + "195.88.189.79", + "195.88.189.81", + "195.88.189.84", + "195.88.189.86", + "195.88.189.91", + "195.88.189.98", + "195.88.189.101", + "195.88.189.110", + "195.88.189.111", + "195.88.189.112", + "195.88.208.7", + "195.88.208.9", + "195.88.208.15", + "195.88.208.17", + "195.88.208.28", + "195.88.208.49", + "195.88.208.50", + "195.88.208.53", + "195.88.208.67", + "195.88.208.82", + "195.88.208.107", + "195.88.208.111", + "195.88.208.112", + "195.88.208.115", + "195.88.208.123", + "195.88.208.125", + "195.88.208.130", + "195.88.208.134", + "195.88.208.139", + "195.88.208.141", + "195.88.208.143", + "195.88.208.147", + "195.88.208.149", + "195.88.208.157", + "195.88.208.164", + "195.88.208.167", + "195.88.208.180", + "195.88.208.181", + "195.88.208.198", + "195.88.208.204", + "195.88.208.207", + "195.88.208.210", + "195.88.208.216", + "195.88.208.236", + "195.88.208.237", + "195.88.208.241", + "195.96.128.24", + "195.96.128.130", + "195.96.128.144", + "195.96.128.148", + "195.110.38.129", + "195.110.38.141", + "195.110.38.156", + "195.110.38.190", + "195.110.38.203", + "195.110.38.205", + "195.110.38.214", + "195.110.38.217", + "195.110.38.223", + "195.110.38.230", + "195.110.38.232", + "195.110.38.234", + "195.110.38.244", + "195.110.38.245", + "195.110.38.246", + "195.110.38.248", + "195.110.39.192", + "195.110.39.194", + "195.110.39.195", + "195.110.39.198", + "195.110.39.199", + "195.110.39.220", + "195.110.39.221", + "195.110.39.222", + "195.110.39.223", + "195.114.4.16", + "195.114.4.21", + "195.114.4.23", + "195.114.4.25", + "195.114.4.117", + "195.114.4.180", + "195.114.4.182", + "195.114.5.243", + "195.114.8.10", + "195.146.59.62", + "195.146.59.249", + "195.146.59.253", + "195.146.60.2", + "195.177.255.4", + "195.177.255.15", + "195.177.255.30", + "195.177.255.47", + "195.177.255.54", + "195.177.255.60", + "195.177.255.64", + "195.177.255.74", + "195.177.255.89", + "195.177.255.90", + "195.177.255.95", + "195.177.255.103", + "195.177.255.120", + "195.177.255.125", + "195.177.255.126", + "195.177.255.132", + "195.177.255.136", + "195.177.255.141", + "195.177.255.142", + "195.177.255.164", + "195.177.255.169", + "195.177.255.170", + "195.177.255.180", + "195.177.255.184", + "195.177.255.187", + "195.177.255.201", + "195.177.255.205", + "195.177.255.208", + "195.177.255.217", + "195.177.255.226", + "195.177.255.253", + "195.181.12.184", + "195.181.41.225", + "195.181.41.229", + "195.181.81.185", + "195.181.84.14", + "195.181.91.99", + "195.191.74.141", + "195.200.77.125", + "195.200.77.134", + "195.200.77.135", + "195.200.77.136", + "195.200.77.194", + "195.200.77.227", + "195.200.77.230", + "195.200.77.242", + "195.200.77.243", + "195.200.77.244", + "195.200.77.245", + "195.200.77.251", + "195.211.44.16", + "195.211.44.17", + "195.211.44.21", + "195.211.44.24", + "195.211.44.41", + "195.211.44.69", + "195.211.44.76", + "195.211.44.96", + "195.211.44.104", + "195.211.44.107", + "195.211.44.120", + "195.211.44.136", + "195.211.44.138", + "195.211.44.160", + "195.211.44.161", + "195.211.44.162", + "195.211.44.173", + "195.211.44.211", + "195.211.44.216", + "195.211.44.218", + "195.211.44.223", + "195.211.44.227", + "195.211.44.232", + "195.211.44.235", + "195.211.45.169", + "195.211.45.184", + "195.211.45.190", + "195.211.45.230", + "195.211.45.239", + "195.211.45.240", + "195.211.45.255", + "195.211.46.64", + "195.211.46.65", + "195.211.46.111", + "195.211.46.117", + "195.211.46.211", + "195.211.46.212", + "195.211.46.242", + "195.211.47.24", + "195.211.47.27", + "195.211.47.65", + "195.211.47.199", + "195.211.47.213", + "195.211.47.220", + "195.214.235.3", + "195.214.235.4", + "195.214.235.5", + "195.214.235.11", + "195.214.235.12", + "195.214.235.13", + "195.214.235.14", + "195.214.235.19", + "195.214.235.20", + "195.214.235.21", + "195.214.235.22", + "195.214.235.46", + "195.214.235.56", + "195.214.235.57", + "195.214.235.62", + "195.214.235.92", + "195.214.235.99", + "195.214.235.100", + "195.214.235.101", + "195.214.235.102", + "195.214.235.114", + "195.214.235.116", + "195.214.235.117", + "195.214.235.118", + "195.214.235.119", + "195.214.235.121", + "195.214.235.122", + "195.214.235.123", + "195.214.235.124", + "195.214.235.141", + "195.214.235.142", + "195.214.235.147", + "195.214.235.148", + "195.214.235.163", + "195.214.235.164", + "195.214.235.171", + "195.214.235.172", + "195.214.235.179", + "195.214.235.180", + "195.214.235.181", + "195.214.235.182", + "195.214.235.206", + "195.214.235.216", + "195.226.223.3", + "195.226.223.6", + "195.226.223.11", + "195.226.223.13", + "195.226.223.14", + "195.226.223.44", + "195.226.223.70", + "195.226.223.100", + "195.226.223.131", + "195.226.223.134", + "195.226.223.135", + "195.226.223.140", + "195.226.223.147", + "195.226.223.155", + "195.226.223.157", + "195.226.223.211", + "195.226.223.213", + "195.230.107.2", + "195.230.107.49", + "195.230.107.130", + "195.234.191.130", + "195.234.191.133", + "195.234.191.137", + "195.234.191.138", + "195.234.191.141", + "195.234.191.144", + "195.234.191.197", + "195.234.191.200", + "195.238.231.8", + "195.238.231.55", + "195.238.231.56", + "195.238.231.132", + "195.238.231.133", + "195.238.231.134", + "195.238.231.146", + "195.238.231.187", + "195.238.231.234", + "195.238.231.245", + "195.238.240.2", + "195.238.240.5", + "195.238.240.19", + "195.238.240.22", + "195.238.240.91", + "195.238.240.215", + "195.245.70.210", + "212.16.64.74", + "212.16.68.4", + "212.16.70.4", + "212.16.70.8", + "212.16.70.19", + "212.16.76.19", + "212.16.76.72", + "212.16.76.117", + "212.16.76.119", + "212.16.76.120", + "212.16.76.250", + "212.16.82.16", + "212.16.84.147", + "212.16.84.148", + "212.16.86.4", + "212.16.86.15", + "212.16.88.21", + "212.16.88.22", + "212.16.89.20", + "212.16.89.25", + "212.16.95.143", + "212.23.201.8", + "212.23.201.10", + "212.23.201.13", + "212.23.201.15", + "212.23.201.27", + "212.23.201.29", + "212.23.201.30", + "212.23.201.36", + "212.23.201.41", + "212.23.201.44", + "212.23.201.50", + "212.23.201.53", + "212.23.201.57", + "212.23.201.58", + "212.23.201.70", + "212.23.201.72", + "212.23.201.73", + "212.23.201.74", + "212.23.201.75", + "212.23.201.76", + "212.23.201.86", + "212.23.201.91", + "212.23.201.92", + "212.23.201.93", + "212.23.201.95", + "212.23.201.98", + "212.23.201.101", + "212.23.201.103", + "212.23.201.104", + "212.23.201.107", + "212.23.201.114", + "212.23.201.116", + "212.23.201.118", + "212.23.201.122", + "212.23.201.123", + "212.23.201.133", + "212.23.201.135", + "212.23.201.143", + "212.23.201.147", + "212.23.201.151", + "212.23.201.154", + "212.23.201.158", + "212.23.201.159", + "212.23.201.162", + "212.23.201.165", + "212.23.201.168", + "212.23.201.170", + "212.23.201.172", + "212.23.201.174", + "212.23.201.176", + "212.23.201.177", + "212.23.201.182", + "212.23.201.186", + "212.23.201.192", + "212.23.201.196", + "212.23.201.203", + "212.23.201.205", + "212.23.201.208", + "212.23.201.216", + "212.23.201.218", + "212.23.201.220", + "212.23.201.225", + "212.23.201.232", + "212.23.201.248", + "212.23.201.251", + "212.23.216.25", + "212.23.216.55", + "212.23.216.56", + "212.23.216.60", + "212.23.216.100", + "212.23.216.117", + "212.23.216.121", + "212.23.216.125", + "212.23.216.150", + "212.23.216.152", + "212.33.194.26", + "212.33.194.27", + "212.33.194.28", + "212.33.194.29", + "212.33.194.30", + "212.33.194.58", + "212.33.194.60", + "212.33.194.108", + "212.33.194.148", + "212.33.194.149", + "212.33.195.66", + "212.33.195.80", + "212.33.195.85", + "212.33.195.86", + "212.33.195.88", + "212.33.195.91", + "212.33.195.92", + "212.33.195.93", + "212.33.195.94", + "212.33.195.165", + "212.33.195.166", + "212.33.195.168", + "212.33.195.252", + "212.33.197.69", + "212.33.197.71", + "212.33.197.82", + "212.33.197.85", + "212.33.197.92", + "212.33.197.94", + "212.33.197.100", + "212.33.197.124", + "212.33.197.131", + "212.33.198.15", + "212.33.198.57", + "212.33.198.60", + "212.33.198.90", + "212.33.198.227", + "212.33.201.26", + "212.33.201.27", + "212.33.201.55", + "212.33.201.133", + "212.33.201.143", + "212.33.201.167", + "212.33.201.215", + "212.33.201.220", + "212.33.202.21", + "212.33.202.24", + "212.33.202.76", + "212.33.202.97", + "212.33.202.107", + "212.33.202.113", + "212.33.202.122", + "212.33.202.153", + "212.33.202.167", + "212.33.202.192", + "212.33.202.213", + "212.33.202.214", + "212.33.202.215", + "212.33.202.216", + "212.33.202.227", + "212.33.203.10", + "212.33.203.92", + "212.33.203.186", + "212.33.203.187", + "212.33.203.188", + "212.33.203.189", + "212.33.203.190", + "212.33.203.226", + "212.33.204.93", + "212.33.205.21", + "212.33.205.55", + "212.33.205.81", + "212.33.205.82", + "212.33.205.95", + "212.33.205.96", + "212.33.205.99", + "212.33.205.110", + "212.33.205.186", + "212.33.205.223", + "212.33.205.254", + "212.33.206.11", + "212.33.206.20", + "212.33.206.46", + "212.33.206.55", + "212.33.206.93", + "212.33.206.138", + "212.33.206.140", + "212.33.206.156", + "212.33.206.168", + "212.33.206.199", + "212.33.206.205", + "212.33.206.239", + "212.33.217.224", + "212.33.222.179", + "212.80.5.205", + "212.80.8.70", + "212.80.8.84", + "212.80.8.103", + "212.80.8.105", + "212.80.8.115", + "212.80.8.212", + "212.80.9.228", + "212.80.9.229", + "212.80.9.230", + "212.80.9.231", + "212.80.9.235", + "212.80.10.252", + "212.80.10.253", + "212.80.16.72", + "212.80.18.83", + "212.80.18.182", + "212.80.18.213", + "212.80.19.2", + "212.80.19.12", + "212.80.19.244", + "212.80.20.92", + "212.80.20.93", + "212.80.20.101", + "212.80.20.132", + "212.80.20.134", + "212.80.20.165", + "212.80.21.10", + "212.80.21.68", + "212.80.21.157", + "212.80.21.179", + "212.80.21.184", + "212.80.22.18", + "212.80.22.22", + "212.80.25.138", + "212.80.25.217", + "212.80.27.250", + "212.80.30.189", + "212.80.30.190", + "212.86.64.99", + "212.86.64.100", + "212.86.64.193", + "212.86.66.11", + "212.86.66.50", + "212.86.66.90", + "212.86.66.96", + "212.86.67.138", + "212.86.67.147", + "212.86.67.149", + "212.86.74.64", + "212.86.74.148", + "212.86.76.69", + "212.86.77.54", + "212.86.79.180", + "212.86.79.184", + "212.86.79.188", + "212.86.79.189", + "212.120.192.7", + "212.120.192.10", + "212.120.192.13", + "212.120.192.21", + "212.120.198.165", + "212.120.200.58", + "212.120.201.176", + "212.120.205.150", + "212.120.208.80", + "212.120.211.206", + "212.120.214.42", + "212.120.214.61", + "212.120.214.230", + "212.120.223.248", + "213.108.240.52", + "213.108.240.53", + "213.108.240.54", + "213.108.240.67", + "213.108.240.82", + "213.108.240.86", + "213.108.240.91", + "213.108.240.131", + "213.108.240.132", + "213.108.240.134", + "213.108.240.136", + "213.108.240.141", + "213.108.240.143", + "213.108.240.144", + "213.108.240.145", + "213.108.240.151", + "213.108.240.152", + "213.108.240.155", + "213.108.240.157", + "213.108.240.158", + "213.108.240.162", + "213.108.240.167", + "213.108.240.169", + "213.108.240.170", + "213.108.240.173", + "213.108.240.180", + "213.108.240.194", + "213.108.240.204", + "213.108.240.205", + "213.108.241.73", + "213.108.241.106", + "213.108.241.110", + "213.108.241.132", + "213.108.241.133", + "213.108.241.135", + "213.108.241.140", + "213.108.241.145", + "213.108.241.146", + "213.108.241.155", + "213.108.241.196", + "213.108.241.199", + "213.108.241.200", + "213.108.241.216", + "213.108.241.235", + "213.108.241.237", + "213.108.241.243", + "213.108.242.211", + "213.108.242.215", + "213.108.242.216", + "213.108.242.217", + "213.108.242.219", + "213.108.242.250", + "213.109.199.133", + "213.176.1.165", + "213.176.1.194", + "213.176.5.20", + "213.176.5.21", + "213.176.6.146", + "213.176.6.153", + "213.176.6.198", + "213.176.7.126", + "213.176.7.151", + "213.176.28.37", + "213.176.28.45", + "213.176.28.91", + "213.176.28.143", + "213.176.28.149", + "213.176.28.151", + "213.176.28.170", + "213.176.28.199", + "213.176.28.231", + "213.176.28.245", + "213.176.28.247", + "213.176.29.6", + "213.176.29.7", + "213.176.29.8", + "213.176.29.9", + "213.176.29.26", + "213.176.29.31", + "213.176.29.32", + "213.176.29.33", + "213.176.29.34", + "213.176.29.35", + "213.176.29.58", + "213.176.29.63", + "213.176.29.66", + "213.176.29.75", + "213.176.29.78", + "213.176.29.85", + "213.176.29.87", + "213.176.29.88", + "213.176.29.89", + "213.176.29.107", + "213.176.29.108", + "213.176.29.115", + "213.176.29.121", + "213.176.29.124", + "213.176.29.125", + "213.176.29.126", + "213.176.29.128", + "213.176.29.132", + "213.176.29.133", + "213.176.29.141", + "213.176.29.142", + "213.176.29.145", + "213.176.29.151", + "213.176.29.158", + "213.176.29.159", + "213.176.29.161", + "213.176.29.162", + "213.176.29.163", + "213.176.29.189", + "213.176.29.192", + "213.176.29.201", + "213.176.29.207", + "213.176.29.208", + "213.176.29.209", + "213.176.29.212", + "213.176.29.214", + "213.176.29.219", + "213.176.29.229", + "213.176.29.236", + "213.176.29.243", + "213.176.29.244", + "213.176.29.246", + "213.176.31.7", + "213.176.31.66", + "213.176.31.74", + "213.176.31.98", + "213.176.31.99", + "213.176.31.110", + "213.176.31.122", + "213.176.31.154", + "213.176.31.214", + "213.176.120.9", + "213.176.120.34", + "213.176.120.40", + "213.176.120.114", + "213.176.120.130", + "213.176.121.78", + "213.176.123.5", + "213.195.22.3", + "213.195.22.4", + "213.195.22.106", + "213.195.22.238", + "213.195.56.113", + "213.195.58.167", + "213.207.192.19", + "213.207.198.66", + "213.207.203.90", + "213.207.203.211", + "213.207.204.10", + "213.207.204.66", + "213.207.209.21", + "213.207.250.50", + "213.207.250.59", + "213.207.250.90", + "213.232.124.4", + "213.232.124.10", + "213.232.124.34", + "213.232.124.78", + "213.232.124.154", + "213.232.124.222", + "213.232.125.115", + "213.232.125.186", + "213.232.126.49", + "213.232.126.180", + "213.232.126.235", + "213.232.126.236", + "213.232.126.237", + "213.233.161.81", + "213.233.161.190", + "213.233.173.10", + "213.233.176.155", + "213.233.176.156", + "213.233.177.172", + "213.233.177.219", + "213.233.177.221", + "213.233.178.82", + "213.233.178.102", + "213.233.178.133", + "213.233.178.226", + "213.233.182.147", + "213.233.182.236", + "213.233.184.221", + "217.11.18.100", + "217.11.18.190", + "217.11.19.10", + "217.11.20.217", + "217.11.20.227", + "217.11.21.11", + "217.11.23.6", + "217.11.24.166", + "217.11.27.116", + "217.11.27.117", + "217.11.28.51", + "217.11.28.53", + "217.11.29.178", + "217.11.29.184", + "217.11.29.186", + "217.11.29.187", + "217.11.29.188", + "217.11.29.190", + "217.11.29.191", + "217.20.252.9", + "217.20.252.12", + "217.20.252.20", + "217.20.252.24", + "217.20.252.42", + "217.20.252.68", + "217.20.252.249", + "217.24.147.254", + "217.26.222.2", + "217.26.222.234", + "217.60.243.221", + "217.60.243.244", + "217.66.200.240", + "217.66.215.114", + "217.114.40.18", + "217.114.40.19", + "217.114.40.32", + "217.114.40.34", + "217.114.40.43", + "217.114.40.49", + "217.114.40.54", + "217.114.40.75", + "217.114.40.104", + "217.114.40.107", + "217.114.40.112", + "217.114.40.203", + "217.114.40.206", + "217.114.40.215", + "217.114.40.216", + "217.114.40.246", + "217.114.46.156", + "217.114.46.157", + "217.144.104.6", + "217.144.104.7", + "217.144.104.9", + "217.144.104.11", + "217.144.104.13", + "217.144.104.14", + "217.144.104.15", + "217.144.104.16", + "217.144.104.18", + "217.144.104.19", + "217.144.104.21", + "217.144.104.22", + "217.144.104.23", + "217.144.104.25", + "217.144.104.26", + "217.144.104.27", + "217.144.104.35", + "217.144.104.37", + "217.144.104.42", + "217.144.104.44", + "217.144.104.47", + "217.144.104.49", + "217.144.104.50", + "217.144.104.54", + "217.144.104.56", + "217.144.104.58", + "217.144.104.64", + "217.144.104.65", + "217.144.104.68", + "217.144.104.69", + "217.144.104.70", + "217.144.104.74", + "217.144.104.78", + "217.144.104.80", + "217.144.104.82", + "217.144.104.83", + "217.144.104.84", + "217.144.104.87", + "217.144.104.88", + "217.144.104.92", + "217.144.104.95", + "217.144.104.97", + "217.144.104.100", + "217.144.104.101", + "217.144.104.102", + "217.144.104.106", + "217.144.104.111", + "217.144.104.112", + "217.144.104.113", + "217.144.104.119", + "217.144.104.120", + "217.144.104.122", + "217.144.104.123", + "217.144.104.127", + "217.144.104.128", + "217.144.104.129", + "217.144.104.130", + "217.144.104.131", + "217.144.104.133", + "217.144.104.134", + "217.144.104.136", + "217.144.104.139", + "217.144.104.141", + "217.144.104.142", + "217.144.104.146", + "217.144.104.155", + "217.144.104.159", + "217.144.104.160", + "217.144.104.161", + "217.144.104.162", + "217.144.104.163", + "217.144.104.166", + "217.144.104.168", + "217.144.104.170", + "217.144.104.173", + "217.144.104.174", + "217.144.104.177", + "217.144.104.178", + "217.144.104.179", + "217.144.104.180", + "217.144.104.182", + "217.144.104.184", + "217.144.104.185", + "217.144.104.189", + "217.144.104.190", + "217.144.104.191", + "217.144.104.193", + "217.144.104.197", + "217.144.104.203", + "217.144.104.205", + "217.144.104.210", + "217.144.104.212", + "217.144.104.214", + "217.144.104.217", + "217.144.104.218", + "217.144.104.222", + "217.144.104.225", + "217.144.104.226", + "217.144.104.227", + "217.144.104.234", + "217.144.104.235", + "217.144.104.242", + "217.144.104.243", + "217.144.104.249", + "217.144.104.250", + "217.144.104.254", + "217.144.105.39", + "217.144.105.42", + "217.144.105.49", + "217.144.105.52", + "217.144.105.57", + "217.144.105.58", + "217.144.105.61", + "217.144.105.63", + "217.144.105.73", + "217.144.105.77", + "217.144.105.79", + "217.144.105.80", + "217.144.105.84", + "217.144.105.90", + "217.144.105.94", + "217.144.105.101", + "217.144.105.156", + "217.144.105.163", + "217.144.105.168", + "217.144.105.169", + "217.144.105.175", + "217.144.105.176", + "217.144.105.181", + "217.144.105.195", + "217.144.105.197", + "217.144.105.198", + "217.144.105.200", + "217.144.105.205", + "217.144.105.206", + "217.144.105.207", + "217.144.105.209", + "217.144.105.210", + "217.144.105.212", + "217.144.105.217", + "217.144.105.218", + "217.144.105.221", + "217.144.105.226", + "217.144.105.230", + "217.144.105.233", + "217.144.105.252", + "217.144.105.254", + "217.144.106.7", + "217.144.106.11", + "217.144.106.16", + "217.144.106.17", + "217.144.106.18", + "217.144.106.20", + "217.144.106.21", + "217.144.106.22", + "217.144.106.26", + "217.144.106.30", + "217.144.106.31", + "217.144.106.32", + "217.144.106.36", + "217.144.106.39", + "217.144.106.40", + "217.144.106.44", + "217.144.106.48", + "217.144.106.50", + "217.144.106.54", + "217.144.106.55", + "217.144.106.59", + "217.144.106.61", + "217.144.106.62", + "217.144.106.68", + "217.144.106.93", + "217.144.106.94", + "217.144.106.100", + "217.144.106.104", + "217.144.106.110", + "217.144.106.111", + "217.144.106.113", + "217.144.106.115", + "217.144.106.116", + "217.144.106.117", + "217.144.106.118", + "217.144.106.125", + "217.144.106.126", + "217.144.106.132", + "217.144.106.138", + "217.144.106.141", + "217.144.106.143", + "217.144.106.153", + "217.144.106.155", + "217.144.106.157", + "217.144.106.201", + "217.144.106.202", + "217.144.106.203", + "217.144.106.204", + "217.144.106.205", + "217.144.106.208", + "217.144.106.212", + "217.144.106.218", + "217.144.106.222", + "217.144.106.227", + "217.144.106.236", + "217.144.106.242", + "217.144.106.252", + "217.144.107.2", + "217.144.107.4", + "217.144.107.5", + "217.144.107.6", + "217.144.107.15", + "217.144.107.20", + "217.144.107.22", + "217.144.107.52", + "217.144.107.55", + "217.144.107.61", + "217.144.107.70", + "217.144.107.83", + "217.144.107.85", + "217.144.107.94", + "217.144.107.96", + "217.144.107.98", + "217.144.107.104", + "217.144.107.111", + "217.144.107.113", + "217.144.107.119", + "217.144.107.126", + "217.144.107.127", + "217.144.107.130", + "217.144.107.132", + "217.144.107.139", + "217.144.107.147", + "217.144.107.148", + "217.144.107.149", + "217.144.107.159", + "217.144.107.160", + "217.144.107.161", + "217.144.107.162", + "217.144.107.163", + "217.144.107.166", + "217.144.107.169", + "217.144.107.171", + "217.144.107.174", + "217.144.107.176", + "217.144.107.181", + "217.144.107.182", + "217.144.107.183", + "217.144.107.184", + "217.144.107.186", + "217.144.107.194", + "217.144.107.195", + "217.144.107.197", + "217.144.107.200", + "217.144.107.203", + "217.144.107.205", + "217.144.107.208", + "217.144.107.226", + "217.144.107.228", + "217.144.107.230", + "217.144.107.235", + "217.144.107.239", + "217.144.107.241", + "217.144.107.242", + "217.144.107.246", + "217.144.107.247", + "217.146.209.50", + "217.170.251.26", + "217.170.252.10", + "217.170.252.47", + "217.170.252.155", + "217.170.253.10", + "217.172.98.71", + "217.172.98.72", + "217.172.98.74", + "217.172.98.75", + "217.172.98.77", + "217.172.98.78", + "217.172.98.79", + "217.172.98.87", + "217.172.98.90", + "217.172.98.91", + "217.172.98.92", + "217.172.98.93", + "217.172.98.95", + "217.172.98.96", + "217.172.98.97", + "217.172.98.98", + "217.172.98.102", + "217.172.98.108", + "217.172.98.109", + "217.172.98.111", + "217.172.98.116", + "217.172.98.124", + "217.172.98.132", + "217.172.98.138", + "217.172.98.147", + "217.172.98.148", + "217.172.98.149", + "217.172.98.158", + "217.172.98.160", + "217.172.98.161", + "217.172.98.162", + "217.172.98.163", + "217.172.98.169", + "217.172.98.172", + "217.172.98.175", + "217.172.98.228", + "217.172.98.231", + "217.172.98.233", + "217.172.98.234", + "217.172.98.237", + "217.172.98.241", + "217.172.98.251", + "217.172.126.251", + "217.198.190.5", + "217.198.190.56", + "217.198.190.66", + "217.198.190.100", + "217.198.190.125", + "217.198.190.132", + "217.198.190.145", + "217.198.190.146", + "217.198.190.147", + "217.198.190.150", + "217.198.190.151", + "217.198.190.153", + "217.198.190.156", + "217.198.190.162", + "217.198.190.178", + "217.198.190.209", + "217.198.190.212", + "217.198.190.214", + "217.198.190.215", + "217.198.190.225", + "217.218.2.100", + "217.218.6.5", + "217.218.7.4", + "217.218.7.5", + "217.218.9.140", + "217.218.9.141", + "217.218.11.11", + "217.218.11.12", + "217.218.12.58", + "217.218.13.168", + "217.218.21.104", + "217.218.21.122", + "217.218.21.124", + "217.218.26.220", + "217.218.32.74", + "217.218.40.5", + "217.218.40.6", + "217.218.46.11", + "217.218.53.153", + "217.218.54.16", + "217.218.56.21", + "217.218.56.210", + "217.218.56.249", + "217.218.60.101", + "217.218.62.65", + "217.218.62.66", + "217.218.70.10", + "217.218.71.153", + "217.218.75.202", + "217.218.82.85", + "217.218.82.140", + "217.218.85.234", + "217.218.86.50", + "217.218.98.56", + "217.218.98.157", + "217.218.102.174", + "217.218.106.140", + "217.218.106.141", + "217.218.113.50", + "217.218.114.8", + "217.218.124.10", + "217.218.126.162", + "217.218.127.126", + "217.218.127.127", + "217.218.155.154", + "217.218.155.155", + "217.218.168.3", + "217.218.171.210", + "217.218.179.31", + "217.218.179.39", + "217.218.200.218", + "217.218.204.123", + "217.218.204.124", + "217.218.204.125", + "217.218.209.221", + "217.218.212.140", + "217.218.214.4", + "217.218.214.16", + "217.218.215.25", + "217.218.215.78", + "217.218.215.140", + "217.218.227.241", + "217.218.227.242", + "217.218.227.243", + "217.218.227.244", + "217.218.227.247", + "217.218.227.248", + "217.218.227.249", + "217.218.227.250", + "217.218.227.251", + "217.218.227.252", + "217.218.227.253", + "217.218.227.254", + "217.218.230.194", + "217.218.234.252", + "217.218.242.66", + "217.218.242.67", + "217.218.251.178", + "217.219.29.73", + "217.219.29.107", + "217.219.31.147", + "217.219.33.33", + "217.219.33.34", + "217.219.33.35", + "217.219.33.69", + "217.219.34.30", + "217.219.39.116", + "217.219.42.18", + "217.219.42.134", + "217.219.66.2", + "217.219.66.3", + "217.219.66.8", + "217.219.67.130", + "217.219.68.45", + "217.219.70.92", + "217.219.70.197", + "217.219.78.138", + "217.219.78.144", + "217.219.78.152", + "217.219.78.153", + "217.219.78.154", + "217.219.78.156", + "217.219.78.200", + "217.219.79.82", + "217.219.85.3", + "217.219.85.4", + "217.219.85.5", + "217.219.87.100", + "217.219.91.132", + "217.219.91.135", + "217.219.93.2", + "217.219.120.82", + "217.219.121.35", + "217.219.121.36", + "217.219.124.203", + "217.219.129.246", + "217.219.135.200", + "217.219.136.95", + "217.219.137.210", + "217.219.138.140", + "217.219.138.205", + "217.219.139.219", + "217.219.147.197", + "217.219.148.251", + "217.219.157.69", + "217.219.157.78", + "217.219.157.82", + "217.219.157.112", + "217.219.157.115", + "217.219.157.122", + "217.219.157.123", + "217.219.157.124", + "217.219.157.125", + "217.219.157.126", + "217.219.158.99", + "217.219.162.163", + "217.219.162.165", + "217.219.165.2", + "217.219.165.3", + "217.219.168.164", + "217.219.169.170", + "217.219.173.3", + "217.219.182.42", + "217.219.187.3", + "217.219.192.197", + "217.219.199.52", + "217.219.202.3", + "217.219.204.10", + "217.219.217.179", + "217.219.223.3", + "217.219.223.4", + "217.219.223.195", + "217.219.226.98", + "217.219.226.103", + "217.219.227.30", + "217.219.244.3", + "217.219.244.33", + "217.219.251.131", + "217.219.254.5", + "193.134.100.123", + "2.179.113.235", + "2.179.114.71", + "2.187.34.221", + "2.179.162.163", + "2.182.52.30", + "2.179.162.72", + "2.190.249.146", + "2.176.245.137", + "2.176.254.42", + "2.177.48.37", + "2.177.52.221", + "2.177.95.39", + "2.177.104.14", + "2.177.105.80", + "2.177.150.72", + "2.177.168.52", + "2.178.31.22", + "2.182.152.219", + "2.182.154.12", + "2.190.33.197", + "2.190.45.194", + "2.190.87.30", + "2.190.90.36", + "2.190.227.52", + "2.190.240.232", + "2.191.7.159", + "2.191.74.29", + "2.191.78.44", + "2.191.83.211", + "2.191.181.170", + "2.177.164.134", + "2.180.26.106", + "2.190.155.142", + "2.176.238.212", + "2.177.45.68", + "2.177.54.23", + "2.177.114.60", + "2.178.137.218", + "2.190.158.13", + "2.190.176.218", + "2.176.234.128", + "2.176.247.54", + "2.176.250.245", + "2.177.45.25", + "2.177.92.255", + "2.177.109.58", + "2.177.158.21", + "2.177.219.23", + "2.178.142.224", + "2.178.215.98", + "2.178.245.98", + "2.178.253.51", + "2.179.105.155", + "2.179.164.199", + "2.179.164.237", + "2.184.156.151", + "2.190.25.70", + "2.190.96.211", + "2.190.160.70", + "2.190.188.156", + "2.190.195.246", + "2.191.5.173", + "2.191.19.31", + "2.191.116.58", + "2.176.176.154", + "2.176.244.30", + "2.177.211.193", + "2.180.33.238", + "2.182.226.202", + "2.183.9.23", + "2.183.29.240", + "2.190.201.38", + "2.190.202.101", + "2.191.1.229", + "2.176.242.22", + "2.177.51.166", + "2.177.106.45", + "2.177.150.38", + "2.178.83.133", + "2.180.43.33", + "2.182.223.152", + "2.186.115.37", + "2.189.139.181", + "2.189.139.227", + "2.189.139.226", + "2.189.164.30", + "2.190.201.62", + "2.191.79.18", + "2.191.105.26", + "2.191.115.143", + "2.191.126.83", + "2.191.187.230", + "2.177.115.69", + "2.178.245.83", + "2.182.54.48", + "2.191.65.217", + "2.189.139.212", + "2.191.13.49", + "2.191.67.102", + "2.176.235.174", + "2.176.237.127", + "2.176.242.7", + "2.177.41.82", + "2.177.98.70", + "2.177.152.179", + "2.178.16.146", + "2.178.71.207", + "2.179.64.79", + "2.179.67.90", + "2.179.229.254", + "2.180.29.142", + "2.180.30.167", + "2.182.153.70", + "2.190.194.227", + "2.191.16.108", + "2.176.198.213", + "2.176.232.208", + "2.176.238.75", + "2.176.242.124", + "2.177.92.52", + "2.177.108.109", + "2.177.153.8", + "2.177.172.249", + "2.177.175.40", + "2.177.219.183", + "2.177.223.28", + "2.178.23.43", + "2.178.80.11", + "2.178.189.61", + "2.179.109.19", + "2.179.117.179", + "2.182.154.9", + "2.182.225.237", + "2.189.139.231", + "2.190.42.94", + "2.190.138.115", + "2.190.216.245", + "2.191.5.137", + "2.191.9.130", + "2.177.106.125", + "2.177.110.187", + "2.177.158.155", + "2.177.216.60", + "2.178.13.244", + "2.178.142.152", + "2.178.186.226", + "2.180.29.17", + "2.189.148.130", + "2.177.151.229", + "2.177.152.199", + "2.177.176.61", + "2.178.74.99", + "2.183.29.242", + "2.184.237.51", + "2.189.139.228", + "2.191.73.34", + "2.177.227.61", + "2.178.30.208", + "2.178.129.235", + "2.178.140.0", + "2.178.204.102", + "2.180.26.19", + "2.190.100.38", + "2.190.131.48", + "2.190.218.64", + "2.190.225.30", + "2.191.79.57", + "2.191.114.208", + "2.191.119.217", + "2.191.123.60", + "2.176.128.105", + "2.176.174.251", + "2.176.246.7", + "2.177.41.184", + "2.177.42.153", + "2.177.47.149", + "2.177.68.208", + "2.177.111.71", + "2.177.221.131", + "2.178.17.165", + "2.178.133.2", + "2.179.124.171", + "2.182.222.226", + "2.189.139.146", + "2.189.148.93", + "2.189.148.149", + "2.190.38.43", + "2.190.38.168", + "2.191.21.72", + "2.191.66.80", + "2.177.37.77", + "2.177.108.126", + "2.177.211.148", + "2.177.231.29", + "2.189.148.122", + "2.189.148.121", + "2.189.148.127", + "2.189.148.128", + "2.191.2.86", + "2.191.127.125", + "2.177.228.72", + "2.179.113.178", + "2.179.161.147", + "2.177.169.30", + "2.191.16.201", + "2.191.73.58", + "2.176.179.89", + "2.177.43.209", + "2.177.115.49", + "2.177.159.38", + "2.177.231.47", + "2.182.214.198", + "2.189.148.91", + "2.189.148.89", + "2.189.148.90", + "2.189.148.132", + "2.190.73.186", + "2.190.93.129", + "2.178.78.5", + "2.190.38.244", + "2.177.38.182", + "2.177.165.101", + "2.178.145.58", + "2.180.202.254", + "2.182.88.204", + "2.186.122.108", + "2.190.93.101", + "2.190.151.62", + "2.177.110.46", + "2.182.152.182", + "2.187.128.167", + "2.180.218.207", + "2.180.42.227", + "2.181.236.27", + "2.181.250.52", + "2.183.201.25", + "2.184.16.254", + "2.185.102.157", + "2.185.147.195", + "2.186.12.123", + "2.187.39.139", + "2.187.147.65", + "2.190.159.73", + "2.191.63.230", + "2.191.125.110", + "2.182.215.189", + "2.189.148.118", + "2.189.148.254", + "2.176.244.117", + "2.189.122.179", + "2.189.139.162", + "2.189.139.184", + "2.190.207.58", + "2.191.47.177", + "2.185.108.199", + "2.190.93.162", + "2.177.157.104", + "2.181.85.90", + "2.189.148.92", + "2.189.148.119", + "2.191.187.56", + "2.188.226.244", + "2.180.5.196", + "2.189.10.143", + "2.177.99.255", + "2.180.19.106", + "2.189.148.129", + "2.190.153.37", + "2.180.33.248", + "2.182.214.99", + "2.189.139.20", + "2.180.165.148", + "2.189.139.22", + "2.190.203.58", + "2.179.165.76", + "2.191.81.12", + "2.181.1.163", + "2.178.10.185", + "2.179.113.3", + "2.190.153.144", + "2.191.118.46", + "2.179.108.17", + "2.188.73.132", + "2.184.60.90", + "2.178.194.150", + "2.191.69.61", + "2.144.243.102", + "2.176.77.239", + "2.176.139.231", + "2.176.155.226", + "2.176.198.253", + "2.176.199.97", + "2.176.199.228", + "2.176.202.231", + "2.176.207.137", + "2.176.207.237", + "2.176.208.185", + "2.176.212.136", + "2.176.212.148", + "2.176.213.17", + "2.176.214.75", + "2.176.214.67", + "2.176.214.140", + "2.176.215.74", + "2.176.215.104", + "2.176.215.116", + "2.176.226.183", + "2.176.255.109", + "2.176.255.225", + "2.177.18.194", + "2.177.21.242", + "2.177.29.172", + "2.177.42.65", + "2.177.64.236", + "2.177.65.11", + "2.177.73.173", + "2.177.74.143", + "2.177.78.163", + "2.177.82.240", + "2.177.84.65", + "2.177.84.84", + "2.177.86.86", + "2.177.87.89", + "2.177.88.150", + "2.177.88.129", + "2.177.88.183", + "2.177.89.83", + "2.177.94.139", + "2.177.97.172", + "2.177.118.29", + "2.177.130.106", + "2.177.140.113", + "2.177.149.223", + "2.177.151.13", + "2.177.151.42", + "2.177.151.74", + "2.177.151.137", + "2.177.154.57", + "2.177.219.36", + "2.177.220.103", + "2.177.221.181", + "2.177.229.250", + "2.177.232.104", + "2.177.233.99", + "2.177.243.139", + "2.177.244.82", + "2.178.32.142", + "2.178.35.0", + "2.178.43.119", + "2.178.56.53", + "2.178.56.149", + "2.178.79.195", + "2.178.110.158", + "2.178.110.133", + "2.178.111.164", + "2.178.111.252", + "2.178.114.114", + "2.178.115.36", + "2.178.115.143", + "2.178.122.163", + "2.178.129.61", + "2.178.156.168", + "2.178.166.203", + "2.178.169.72", + "2.178.170.193", + "2.178.176.212", + "2.178.178.16", + "2.178.178.217", + "2.178.224.25", + "2.179.43.185", + "2.179.44.186", + "2.179.46.75", + "2.179.91.125", + "2.179.100.200", + "2.179.100.165", + "2.179.102.161", + "2.179.102.166", + "2.179.110.20", + "2.179.111.182", + "2.179.124.77", + "2.179.124.180", + "2.180.155.152", + "2.180.168.204", + "2.181.34.56", + "2.181.89.209", + "2.181.90.137", + "2.181.224.89", + "2.181.231.135", + "2.182.48.18", + "2.182.48.64", + "2.182.49.108", + "2.182.50.207", + "2.182.51.240", + "2.182.109.74", + "2.182.127.113", + "2.182.181.171", + "85.185.41.64", + "85.185.62.143", + "85.185.75.75", + "85.185.166.18", + "85.185.166.170", + "85.185.210.194", + "85.185.235.44", + "85.185.241.82", + "85.185.242.34", + "85.185.247.208", + "85.204.82.68", + "86.104.111.7", + "86.104.111.126", + "86.105.44.162", + "86.107.148.106", + "86.107.149.130", + "86.107.149.136", + "87.107.48.137", + "87.107.49.52", + "87.107.55.204", + "87.107.73.83", + "87.107.73.82", + "87.107.82.198", + "87.107.139.210", + "87.107.185.6", + "87.251.147.166", + "185.206.231.51", + "188.121.146.34", + "185.8.175.222", + "185.192.113.13", + "185.213.165.177", + "188.136.174.82", + "188.136.174.210", + "185.110.231.1", + "185.121.131.178", + "188.0.240.4", + "188.211.98.218", + "46.100.12.247", + "46.100.60.230", + "46.100.134.33", + "185.46.218.250", + "185.46.219.53", + "31.7.90.122", + "188.75.65.182", + "188.75.95.21", + "188.211.55.158", + "46.245.72.221", + "46.245.118.148", + "178.252.191.18", + "185.111.83.55", + "185.112.39.5", + "185.112.36.217", + "37.114.251.107", + "185.172.0.242", + "185.169.6.245", + "185.206.95.135", + "178.236.106.230", + "185.46.217.124", + "185.46.218.130", + "185.44.36.5", + "31.7.73.126", + "37.255.149.138", + "46.100.94.121", + "46.245.122.21", + "185.79.158.138", + "185.112.38.182", + "185.112.36.185", + "46.34.166.162", + "46.100.1.109", + "185.140.4.30", + "46.245.118.164", + "46.245.118.163", + "185.193.209.13", + "185.175.79.173", + "185.193.209.44", + "185.214.37.82", + "185.193.209.38", + "188.211.125.25", + "178.239.149.193", + "37.153.128.137", + "185.110.28.118", + "37.255.222.132", + "37.255.203.116", + "185.162.41.202", + "185.193.209.25", + "185.186.240.233", + "185.193.209.12", + "185.175.79.140", + "185.193.209.33", + "188.210.154.132", + "188.211.101.233", + "188.211.68.59", + "188.209.31.92", + "46.245.118.162", + "46.245.122.28", + "178.131.30.4", + "178.131.89.13", + "178.131.95.154", + "178.131.158.148", + "178.131.165.9", + "178.131.155.93", + "178.173.144.210", + "178.173.196.71", + "178.239.149.225", + "178.239.149.1", + "178.239.156.19", + "185.83.88.105", + "185.110.30.64", + "185.112.36.181", + "185.112.38.56", + "5.201.132.213", + "5.201.133.136", + "5.201.141.56", + "5.201.145.207", + "37.153.128.140", + "37.255.198.6", + "37.202.153.179", + "46.38.132.238", + "185.106.137.92", + "188.75.65.179", + "188.75.65.181", + "188.211.73.133", + "188.210.156.33", + "188.213.154.236", + "188.211.104.243", + "188.213.147.34", + "188.211.103.49", + "46.245.78.85", + "178.131.79.145", + "178.131.31.79", + "178.173.140.145", + "178.173.217.221", + "185.193.209.37", + "185.193.209.24", + "188.211.113.172", + "178.131.95.2", + "178.131.94.35", + "45.142.191.36", + "188.75.65.180", + "188.75.65.178", + "188.211.101.239", + "188.211.73.123", + "188.211.99.131", + "188.209.66.119", + "178.131.27.231", + "178.131.165.97", + "178.131.101.41", + "178.131.147.208", + "178.131.191.141", + "178.173.138.218", + "178.173.184.189", + "178.173.215.59", + "185.193.209.15", + "185.193.209.3", + "185.193.209.23", + "185.175.78.199", + "188.208.144.51", + "188.209.79.123", + "188.211.127.238", + "46.235.76.161", + "178.131.75.107", + "178.131.91.5", + "178.131.89.197", + "178.131.93.191", + "178.236.104.186", + "37.153.130.202", + "46.100.60.44", + "46.245.4.4", + "178.131.100.139", + "178.131.153.91", + "185.120.214.240", + "178.173.185.216", + "185.162.41.59", + "188.211.101.127", + "178.131.81.219", + "178.131.76.141", + "178.131.183.169", + "178.173.215.53", + "5.201.128.211", + "5.201.129.159", + "5.201.132.11", + "37.143.146.56", + "37.148.2.225", + "188.211.104.216", + "178.131.93.225", + "45.8.160.217", + "178.131.21.179", + "178.131.111.182", + "178.131.147.249", + "178.131.111.183", + "46.34.164.62", + "185.175.78.97", + "188.211.115.34", + "37.202.188.182", + "188.210.124.67", + "178.131.132.168", + "185.3.212.58", + "188.209.65.12", + "188.211.80.86", + "188.211.122.188", + "178.131.85.186", + "178.131.181.76", + "188.211.93.212", + "188.211.113.28", + "37.148.10.14", + "178.131.91.185", + "185.193.209.45", + "37.148.5.188", + "185.193.209.28", + "178.131.89.60", + "5.201.137.183", + "185.162.40.190", + "5.201.136.198", + "185.176.34.216", + "178.131.93.104", + "178.131.190.233", + "188.121.147.14", + "2.144.6.75", + "2.144.234.8", + "2.176.0.149", + "2.176.1.2", + "2.176.3.199", + "2.176.4.253", + "2.176.5.147", + "2.176.10.31", + "2.176.13.185", + "2.176.14.103", + "2.176.14.183", + "2.176.19.229", + "2.176.21.116", + "2.176.22.40", + "2.176.23.196", + "2.176.26.161", + "2.176.28.124", + "2.176.32.50", + "2.176.32.152", + "2.176.33.3", + "2.176.33.49", + "2.176.33.252", + "2.176.37.116", + "2.176.38.115", + "2.176.40.17", + "2.176.46.159", + "2.176.48.151", + "2.176.49.16", + "2.176.51.21", + "2.176.54.114", + "2.176.57.203", + "2.176.59.199", + "2.176.63.26", + "2.176.63.164", + "2.176.64.13", + "2.176.64.62", + "2.176.67.195", + "2.176.68.200", + "2.176.69.30", + "2.176.70.163", + "2.176.70.194", + "2.176.70.255", + "2.176.72.122", + "2.176.73.187", + "2.176.75.51", + "2.176.79.19", + "2.176.79.205", + "2.176.82.84", + "2.176.89.185", + "2.176.91.175", + "2.176.96.244", + "2.176.97.95", + "2.176.110.198", + "2.176.111.242", + "2.176.112.190", + "2.176.114.152", + "2.176.115.171", + "2.176.116.29", + "2.176.118.141", + "2.176.125.63", + "2.176.125.180", + "2.176.127.4", + "2.176.128.63", + "2.176.128.77", + "2.176.128.112", + "2.176.128.228", + "2.176.129.91", + "2.176.129.231", + "2.176.130.105", + "2.176.131.65", + "2.176.131.77", + "2.176.132.230", + "2.176.137.87", + "2.176.138.199", + "2.176.139.170", + "2.176.142.146", + "2.176.144.136", + "2.176.144.166", + "2.176.144.243", + "2.176.145.7", + "2.176.146.150", + "2.176.146.196", + "2.176.148.38", + "2.176.148.44", + "2.176.150.91", + "2.176.152.223", + "2.176.153.246", + "2.176.155.87", + "2.176.157.202", + "2.176.158.62", + "2.176.158.249", + "2.176.159.236", + "2.176.164.204", + "2.176.170.96", + "2.176.172.196", + "2.176.174.221", + "2.176.180.253", + "2.176.182.199", + "2.176.186.237", + "2.176.192.190", + "2.176.194.95", + "2.176.194.219", + "2.176.194.252", + "2.176.195.55", + "2.176.195.166", + "2.176.196.6", + "2.176.198.119", + "2.176.198.194", + "2.176.199.32", + "2.176.200.173", + "2.176.200.224", + "2.176.203.56", + "2.176.203.132", + "2.176.204.68", + "2.176.207.44", + "2.176.208.140", + "2.176.210.13", + "2.176.214.194", + "2.176.217.128", + "2.176.218.15", + "2.176.218.38", + "2.176.218.157", + "2.176.219.106", + "2.176.220.0", + "2.176.220.41", + "2.176.220.74", + "2.176.220.159", + "2.176.220.232", + "2.176.223.22", + "2.176.225.123", + "2.176.228.243", + "2.176.235.178", + "2.176.236.18", + "2.176.236.73", + "2.176.237.101", + "2.176.239.112", + "2.176.239.166", + "2.176.240.214", + "2.176.241.29", + "2.176.241.192", + "2.176.243.199", + "2.176.246.103", + "2.176.246.118", + "2.176.249.144", + "2.176.250.140", + "2.176.251.152", + "2.176.252.239", + "2.176.253.59", + "2.177.0.4", + "2.177.0.102", + "2.177.1.27", + "2.177.2.24", + "2.177.2.191", + "2.177.4.142", + "2.177.4.190", + "2.177.5.6", + "2.177.5.226", + "2.177.7.132", + "2.177.8.24", + "2.177.9.2", + "2.177.9.92", + "2.177.11.190", + "2.177.13.184", + "2.177.14.233", + "2.177.16.78", + "2.177.17.153", + "2.177.20.69", + "2.177.21.120", + "2.177.21.132", + "2.177.21.177", + "2.177.22.81", + "2.177.22.101", + "2.177.23.57", + "2.177.24.65", + "2.177.24.180", + "2.177.26.202", + "2.177.28.36", + "2.177.35.28", + "2.177.35.203", + "2.177.35.241", + "2.177.37.57", + "2.177.44.216", + "2.177.45.132", + "2.177.46.215", + "2.177.48.88", + "2.177.51.76", + "2.177.51.229", + "2.177.55.110", + "2.177.56.122", + "2.177.60.52", + "2.177.60.86", + "2.177.60.149", + "2.177.65.54", + "2.177.67.107", + "2.177.67.109", + "2.177.68.140", + "2.177.69.213", + "2.177.72.120", + "2.177.72.160", + "2.177.73.27", + "2.177.73.194", + "2.177.75.6", + "2.177.77.139", + "2.177.78.50", + "2.177.79.209", + "2.177.80.105", + "2.177.81.171", + "2.177.85.139", + "2.177.86.104", + "2.177.88.168", + "2.177.89.205", + "2.177.97.64", + "2.177.100.34", + "2.177.102.82", + "2.177.104.97", + "2.177.105.74", + "2.177.109.182", + "2.177.110.188", + "2.177.112.34", + "2.177.113.111", + "2.177.115.30", + "2.177.115.139", + "2.177.116.83", + "2.177.116.163", + "2.177.117.151", + "2.177.120.18", + "2.177.121.15", + "2.177.124.17", + "2.177.125.26", + "2.177.125.210", + "2.177.126.89", + "2.177.129.25", + "2.177.130.99", + "2.177.131.124", + "2.177.132.3", + "2.177.132.150", + "2.177.133.146", + "2.177.133.241", + "2.177.137.23", + "2.177.138.130", + "2.177.138.241", + "2.177.141.203", + "2.177.142.59", + "2.177.143.254", + "2.177.144.14", + "2.177.144.176", + "2.177.145.91", + "2.177.145.107", + "2.177.146.124", + "2.177.146.253", + "2.177.147.104", + "2.177.147.146", + "2.177.148.14", + "2.177.148.18", + "2.177.150.204", + "2.177.150.232", + "2.177.153.97", + "2.177.153.117", + "2.177.154.117", + "2.177.154.163", + "2.177.155.114", + "2.177.157.64", + "2.177.160.50", + "2.177.160.134", + "2.177.162.103", + "2.177.162.117", + "2.177.163.247", + "2.177.165.197", + "2.177.166.204", + "2.177.166.244", + "2.177.171.61", + "2.177.171.185", + "2.177.172.140", + "2.177.172.168", + "2.177.174.32", + "2.177.174.65", + "2.177.174.80", + "2.177.175.61", + "2.177.176.87", + "2.177.179.220", + "2.177.180.217", + "2.177.181.160", + "2.177.181.248", + "2.177.181.252", + "2.177.182.225", + "2.177.182.249", + "2.177.185.220", + "2.177.187.117", + "2.177.187.218", + "2.177.188.154", + "2.177.190.97", + "2.177.194.12", + "2.177.194.227", + "2.177.195.220", + "2.177.196.18", + "2.177.197.5", + "2.177.198.140", + "2.177.199.1", + "2.177.199.219", + "2.177.200.176", + "2.177.202.44", + "2.177.202.188", + "2.177.205.239", + "2.177.206.194", + "2.177.208.38", + "2.177.208.191", + "2.177.209.17", + "2.177.212.13", + "2.177.212.85", + "2.177.214.26", + "2.177.214.42", + "2.177.214.238", + "2.177.217.0", + "2.177.217.81", + "2.177.218.70", + "2.177.221.170", + "2.177.221.178", + "2.177.224.225", + "2.177.225.4", + "2.177.225.205", + "2.177.226.0", + "2.177.226.178", + "2.177.230.72", + "2.177.231.76", + "2.177.231.200", + "2.177.233.62", + "2.177.236.2", + "2.177.236.30", + "2.177.236.221", + "2.177.237.95", + "2.177.240.71", + "2.177.243.171", + "2.177.244.185", + "2.177.245.168", + "2.177.245.206", + "2.177.245.207", + "2.177.246.141", + "2.177.250.215", + "2.177.250.239", + "2.177.251.74", + "2.178.0.88", + "2.178.1.123", + "2.178.2.120", + "2.178.2.138", + "2.178.12.135", + "2.178.15.73", + "2.178.17.73", + "2.178.18.219", + "2.178.20.173", + "2.178.21.53", + "2.178.25.0", + "2.178.28.209", + "2.178.28.241", + "2.178.29.173", + "2.178.34.223", + "2.178.37.140", + "2.178.41.120", + "2.178.43.178", + "2.178.44.137", + "2.178.48.99", + "2.178.48.207", + "2.178.50.81", + "2.178.53.39", + "2.178.54.58", + "2.178.58.202", + "2.178.59.38", + "2.178.67.118", + "2.178.68.83", + "2.178.70.36", + "2.178.71.48", + "2.178.72.192", + "2.178.75.5", + "2.178.79.202", + "2.178.80.60", + "2.178.80.166", + "2.178.81.3", + "2.178.87.134", + "2.178.89.141", + "2.178.92.217", + "2.178.95.100", + "2.178.98.24", + "2.178.99.137", + "2.178.99.171", + "2.178.100.51", + "2.178.101.158", + "2.178.103.59", + "2.178.104.72", + "2.178.106.47", + "2.178.111.162", + "2.178.114.185", + "2.178.116.7", + "2.178.118.23", + "2.178.118.113", + "2.178.120.176", + "2.178.121.69", + "2.178.123.81", + "2.178.125.192", + "2.178.127.86", + "2.178.131.81", + "2.178.131.208", + "2.178.134.227", + "2.178.141.17", + "2.178.147.116", + "2.178.149.161", + "2.178.150.176", + "2.178.152.155", + "2.178.162.109", + "2.178.165.47", + "2.178.167.203", + "2.178.168.166", + "2.178.176.44", + "2.178.180.145", + "2.178.183.88", + "2.178.185.208", + "2.178.188.32", + "2.178.195.84", + "2.178.195.119", + "2.178.201.117", + "2.178.203.141", + "2.178.208.230", + "2.178.214.202", + "2.178.220.103", + "2.178.229.49", + "2.178.230.103", + "2.178.232.133", + "2.178.232.202", + "2.178.232.209", + "2.178.233.77", + "2.178.234.139", + "2.178.236.87", + "2.178.241.103", + "2.178.247.85", + "2.179.20.77", + "2.179.21.89", + "2.179.21.253", + "2.179.22.24", + "2.179.46.173", + "2.179.47.241", + "2.179.67.96", + "2.179.70.156", + "2.179.71.6", + "2.179.71.183", + "2.179.78.139", + "2.179.80.86", + "2.179.83.31", + "2.179.83.40", + "2.179.84.26", + "2.179.84.223", + "2.179.86.198", + "2.179.96.57", + "2.179.96.164", + "2.179.96.196", + "2.179.100.5", + "2.179.100.90", + "2.179.101.193", + "2.179.103.136", + "2.179.103.247", + "2.179.105.36", + "2.179.106.42", + "2.179.115.152", + "2.179.115.171", + "2.179.117.25", + "2.179.117.157", + "2.179.117.227", + "2.179.118.254", + "2.179.121.31", + "2.179.124.214", + "2.179.125.87", + "2.179.126.163", + "2.179.127.65", + "2.179.162.144", + "2.179.163.232", + "2.179.167.231", + "2.179.178.115", + "2.179.186.238", + "2.179.193.146", + "2.179.194.45", + "2.179.194.138", + "2.179.211.167", + "2.179.212.14", + "2.179.217.132", + "2.179.232.21", + "2.179.252.135", + "2.180.0.177", + "2.180.1.91", + "2.180.1.95", + "2.180.3.207", + "2.180.6.135", + "2.180.8.54", + "2.180.8.97", + "2.180.15.75", + "2.180.21.43", + "2.180.21.218", + "2.180.22.62", + "2.180.22.233", + "2.180.24.112", + "2.180.25.192", + "2.180.29.94", + "2.180.31.174", + "2.180.32.48", + "2.180.32.211", + "2.180.33.137", + "2.180.35.195", + "2.180.35.227", + "2.180.36.43", + "2.180.39.39", + "2.180.41.242", + "2.180.42.127", + "2.180.43.95", + "2.180.47.53", + "2.181.1.61", + "2.181.30.184", + "2.181.31.165", + "2.181.35.20", + "2.181.35.134", + "2.181.60.180", + "2.181.80.158", + "2.181.81.16", + "2.181.85.245", + "2.181.89.248", + "2.181.93.51", + "2.181.101.208", + "2.181.102.4", + "2.181.109.155", + "2.181.177.81", + "2.181.224.175", + "2.181.224.244", + "2.181.232.2", + "2.181.232.233", + "2.181.237.83", + "2.181.238.104", + "2.181.245.168", + "2.181.247.131", + "2.181.249.200", + "2.181.251.228", + "2.181.255.52", + "2.182.51.83", + "2.182.51.102", + "2.182.51.211", + "2.182.52.8", + "2.182.53.4", + "2.182.53.28", + "2.182.54.16", + "2.182.55.126", + "2.182.59.207", + "2.182.64.250", + "2.182.65.3", + "2.182.67.56", + "2.182.67.151", + "2.182.67.168", + "2.182.67.197", + "2.182.68.82", + "2.182.68.142", + "2.182.70.153", + "2.182.73.35", + "2.182.75.49", + "2.182.75.133", + "2.182.75.162", + "2.182.75.254", + "2.182.77.238", + "2.182.84.145", + "2.182.85.146", + "2.182.86.49", + "2.182.88.229", + "2.182.90.64", + "2.182.90.230", + "2.182.93.10", + "2.182.93.122", + "2.182.93.241", + "2.182.95.158", + "2.182.95.239", + "2.182.116.229", + "2.182.118.163", + "2.182.119.115", + "2.182.127.44", + "2.182.127.71", + "2.182.166.5", + "2.182.184.150", + "2.182.190.134", + "2.182.191.10", + "2.182.192.22", + "2.182.192.106", + "2.182.196.132", + "2.182.197.46", + "2.182.201.6", + "2.182.208.44", + "2.182.215.172", + "2.182.217.172", + "2.182.217.217", + "2.182.218.73", + "2.182.221.90", + "2.182.222.208", + "2.182.226.35", + "2.182.226.205", + "2.182.227.83", + "2.182.228.6", + "2.182.228.77", + "2.182.228.197", + "2.182.229.125", + "2.182.231.26", + "2.182.234.126", + "2.182.234.153", + "2.182.234.201", + "2.182.235.5", + "2.182.235.216", + "2.182.241.138", + "2.182.249.72", + "2.182.251.205", + "2.183.8.116", + "2.183.88.79", + "2.183.89.135", + "2.183.99.60", + "2.183.99.80", + "2.183.115.168", + "2.183.123.1", + "2.183.142.201", + "2.183.143.93", + "2.183.195.164", + "2.183.198.32", + "2.183.200.141", + "2.183.202.66", + "2.183.202.70", + "2.183.202.71", + "2.183.203.130", + "2.183.203.186", + "2.184.2.165", + "2.184.2.184", + "2.184.3.191", + "2.184.15.56", + "2.184.16.149", + "2.184.16.208", + "2.184.18.54", + "2.184.18.130", + "2.184.22.102", + "2.184.29.224", + "2.184.30.140", + "2.184.31.224", + "2.184.31.226", + "2.184.53.203", + "2.184.55.72", + "2.184.57.76", + "2.184.70.111", + "2.184.156.10", + "2.184.156.166", + "2.184.157.37", + "2.184.158.58", + "2.184.158.225", + "2.184.159.1", + "2.184.159.172", + "2.184.182.83", + "2.184.204.101", + "2.184.216.242", + "2.184.238.105", + "2.184.238.193", + "2.184.239.43", + "2.185.17.16", + "2.185.17.227", + "2.185.66.102", + "2.185.97.114", + "2.185.100.126", + "2.185.102.55", + "2.185.104.61", + "2.185.109.51", + "2.185.109.210", + "2.185.110.156", + "2.185.113.54", + "2.185.116.135", + "2.185.122.111", + "2.185.141.99", + "2.185.143.19", + "2.185.143.54", + "2.185.144.8", + "2.185.144.53", + "2.185.144.86", + "2.185.146.24", + "2.185.148.251", + "2.185.175.77", + "2.185.230.179", + "2.185.232.154", + "2.185.232.162", + "2.185.233.7", + "2.185.233.26", + "2.185.237.103", + "2.185.243.230", + "2.185.244.248", + "2.185.247.84", + "2.185.251.200", + "2.185.252.143", + "2.186.13.114", + "2.186.15.84", + "2.186.112.159", + "2.186.116.88", + "2.186.116.124", + "2.186.118.250", + "2.186.120.77", + "2.186.121.189", + "2.186.122.128", + "2.186.229.13", + "2.187.2.155", + "2.187.9.112", + "2.187.20.133", + "2.187.32.176", + "2.187.34.93", + "2.187.36.108", + "2.187.56.151", + "2.187.57.2", + "2.187.59.40", + "2.187.129.147", + "2.187.138.186", + "2.187.146.168", + "2.187.148.114", + "2.187.151.18", + "2.187.188.92", + "2.187.189.189", + "2.187.249.102", + "2.187.250.158", + "2.188.72.139", + "2.188.162.210", + "2.188.167.197", + "2.188.168.191", + "2.188.174.129", + "2.188.174.149", + "2.188.174.154", + "2.188.210.1", + "2.188.210.9", + "2.188.210.11", + "2.188.210.60", + "2.188.212.190", + "2.188.218.178", + "2.188.226.186", + "2.188.226.249", + "2.188.230.202", + "2.188.236.66", + "2.188.236.68", + "2.188.236.69", + "2.189.20.134", + "2.189.20.221", + "2.189.33.59", + "2.189.89.26", + "2.189.103.0", + "2.189.103.1", + "2.189.103.2", + "2.189.103.3", + "2.189.103.5", + "2.189.103.6", + "2.189.103.7", + "2.189.103.8", + "2.189.103.10", + "2.189.103.11", + "2.189.103.13", + "2.189.103.16", + "2.189.103.17", + "2.189.103.18", + "2.189.103.19", + "2.189.103.20", + "2.189.103.21", + "2.189.103.22", + "2.189.103.23", + "2.189.103.24", + "2.189.103.25", + "2.189.103.26", + "2.189.103.27", + "2.189.103.28", + "2.189.103.30", + "2.189.103.32", + "2.189.103.33", + "2.189.103.34", + "2.189.103.35", + "2.189.103.36", + "2.189.103.37", + "2.189.103.39", + "2.189.103.40", + "2.189.103.41", + "2.189.103.42", + "2.189.103.43", + "2.189.103.44", + "2.189.103.45", + "2.189.103.46", + "2.189.103.47", + "2.189.103.48", + "2.189.103.49", + "2.189.103.50", + "2.189.103.51", + "2.189.103.52", + "2.189.103.53", + "2.189.103.55", + "2.189.103.56", + "2.189.103.57", + "2.189.103.59", + "2.189.103.60", + "2.189.103.61", + "2.189.103.63", + "2.189.103.64", + "2.189.103.65", + "2.189.103.67", + "2.189.103.68", + "2.189.103.69", + "2.189.103.72", + "2.189.103.73", + "2.189.103.74", + "2.189.103.75", + "2.189.103.76", + "2.189.103.78", + "2.189.103.79", + "2.189.103.80", + "2.189.103.81", + "2.189.103.82", + "2.189.103.84", + "2.189.103.85", + "2.189.103.86", + "2.189.103.87", + "2.189.103.88", + "2.189.103.89", + "2.189.103.90", + "2.189.103.91", + "2.189.103.92", + "2.189.103.93", + "2.189.103.95", + "2.189.103.96", + "2.189.103.97", + "2.189.103.98", + "2.189.103.99", + "2.189.103.100", + "2.189.103.101", + "2.189.103.102", + "2.189.103.105", + "2.189.103.106", + "2.189.103.107", + "2.189.103.108", + "2.189.103.109", + "2.189.103.110", + "2.189.103.111", + "2.189.103.112", + "2.189.103.113", + "2.189.103.114", + "2.189.103.115", + "2.189.103.116", + "2.189.103.117", + "2.189.103.118", + "2.189.103.119", + "2.189.103.120", + "2.189.103.121", + "2.189.103.122", + "2.189.103.123", + "2.189.103.124", + "2.189.103.125", + "2.189.103.128", + "2.189.103.129", + "2.189.103.130", + "2.189.103.131", + "2.189.103.132", + "2.189.103.134", + "2.189.103.135", + "2.189.103.136", + "2.189.103.138", + "2.189.103.139", + "2.189.103.140", + "2.189.103.141", + "2.189.103.142", + "2.189.103.143", + "2.189.103.144", + "2.189.103.145", + "2.189.103.146", + "2.189.103.147", + "2.189.103.148", + "2.189.103.149", + "2.189.103.150", + "2.189.103.152", + "2.189.103.153", + "2.189.103.154", + "2.189.103.155", + "2.189.103.156", + "2.189.103.158", + "2.189.103.159", + "2.189.103.160", + "2.189.103.161", + "2.189.103.162", + "2.189.103.163", + "2.189.103.164", + "2.189.103.166", + "2.189.103.167", + "2.189.103.168", + "2.189.103.169", + "2.189.103.170", + "2.189.103.171", + "2.189.103.172", + "2.189.103.173", + "2.189.103.174", + "2.189.103.176", + "2.189.103.177", + "2.189.103.178", + "2.189.103.179", + "2.189.103.180", + "2.189.103.181", + "2.189.103.182", + "2.189.103.183", + "2.189.103.184", + "2.189.103.186", + "2.189.103.187", + "2.189.103.188", + "2.189.103.189", + "2.189.103.190", + "2.189.103.192", + "2.189.103.193", + "2.189.103.194", + "2.189.103.195", + "2.189.103.196", + "2.189.103.197", + "2.189.103.198", + "2.189.103.199", + "2.189.103.200", + "2.189.103.202", + "2.189.103.203", + "2.189.103.204", + "2.189.103.205", + "2.189.103.206", + "2.189.103.208", + "2.189.103.209", + "2.189.103.210", + "2.189.103.212", + "2.189.103.213", + "2.189.103.214", + "2.189.103.215", + "2.189.103.216", + "2.189.103.217", + "2.189.103.219", + "2.189.103.220", + "2.189.103.222", + "2.189.103.223", + "2.189.103.224", + "2.189.103.225", + "2.189.103.226", + "2.189.103.227", + "2.189.103.228", + "2.189.103.229", + "2.189.103.230", + "2.189.103.232", + "2.189.103.233", + "2.189.103.234", + "2.189.103.235", + "2.189.103.236", + "2.189.103.238", + "2.189.103.239", + "2.189.103.240", + "2.189.103.241", + "2.189.103.242", + "2.189.103.244", + "2.189.103.245", + "2.189.103.246", + "2.189.103.247", + "2.189.103.248", + "2.189.103.249", + "2.189.103.250", + "2.189.103.251", + "2.189.103.252", + "2.189.103.253", + "2.189.103.254", + "2.189.103.255", + "2.189.142.24", + "2.189.144.51", + "2.189.146.0", + "2.189.146.1", + "2.189.146.2", + "2.189.146.3", + "2.189.146.4", + "2.189.146.5", + "2.189.146.6", + "2.189.146.7", + "2.189.146.8", + "2.189.146.9", + "2.189.146.10", + "2.189.146.11", + "2.189.146.12", + "2.189.146.13", + "2.189.146.14", + "2.189.146.15", + "2.189.146.16", + "2.189.146.17", + "2.189.146.19", + "2.189.146.20", + "2.189.146.21", + "2.189.146.22", + "2.189.146.24", + "2.189.146.25", + "2.189.146.26", + "2.189.146.27", + "2.189.146.28", + "2.189.146.30", + "2.189.146.31", + "2.189.146.32", + "2.189.146.33", + "2.189.146.34", + "2.189.146.35", + "2.189.146.36", + "2.189.146.37", + "2.189.146.38", + "2.189.146.39", + "2.189.146.40", + "2.189.146.41", + "2.189.146.42", + "2.189.146.43", + "2.189.146.44", + "2.189.146.45", + "2.189.146.46", + "2.189.146.47", + "2.189.146.49", + "2.189.146.50", + "2.189.146.51", + "2.189.146.52", + "2.189.146.53", + "2.189.146.54", + "2.189.146.55", + "2.189.146.57", + "2.189.146.58", + "2.189.146.59", + "2.189.146.60", + "2.189.146.61", + "2.189.146.62", + "2.189.146.63", + "2.189.146.64", + "2.189.146.65", + "2.189.146.66", + "2.189.146.68", + "2.189.146.69", + "2.189.146.70", + "2.189.146.71", + "2.189.146.72", + "2.189.146.73", + "2.189.146.74", + "2.189.146.76", + "2.189.146.77", + "2.189.146.79", + "2.189.146.80", + "2.189.146.81", + "2.189.146.82", + "2.189.146.83", + "2.189.146.84", + "2.189.146.85", + "2.189.146.86", + "2.189.146.87", + "2.189.146.88", + "2.189.146.89", + "2.189.146.90", + "2.189.146.91", + "2.189.146.92", + "2.189.146.93", + "2.189.146.95", + "2.189.146.96", + "2.189.146.98", + "2.189.146.99", + "2.189.146.100", + "2.189.146.101", + "2.189.146.103", + "2.189.146.104", + "2.189.146.105", + "2.189.146.106", + "2.189.146.109", + "2.189.146.110", + "2.189.146.111", + "2.189.146.112", + "2.189.146.114", + "2.189.146.115", + "2.189.146.116", + "2.189.146.117", + "2.189.146.119", + "2.189.146.120", + "2.189.146.121", + "2.189.146.122", + "2.189.146.123", + "2.189.146.124", + "2.189.146.125", + "2.189.146.126", + "2.189.146.127", + "2.189.146.128", + "2.189.146.130", + "2.189.146.131", + "2.189.146.134", + "2.189.146.135", + "2.189.146.136", + "2.189.146.137", + "2.189.146.138", + "2.189.146.141", + "2.189.146.142", + "2.189.146.143", + "2.189.146.144", + "2.189.146.145", + "2.189.146.146", + "2.189.146.148", + "2.189.146.150", + "2.189.146.151", + "2.189.146.153", + "2.189.146.154", + "2.189.146.155", + "2.189.146.156", + "2.189.146.157", + "2.189.146.158", + "2.189.146.159", + "2.189.146.160", + "2.189.146.161", + "2.189.146.162", + "2.189.146.163", + "2.189.146.164", + "2.189.146.165", + "2.189.146.166", + "2.189.146.169", + "2.189.146.170", + "2.189.146.171", + "2.189.146.172", + "2.189.146.173", + "2.189.146.174", + "2.189.146.176", + "2.189.146.177", + "2.189.146.178", + "2.189.146.179", + "2.189.146.180", + "2.189.146.181", + "2.189.146.183", + "2.189.146.184", + "2.189.146.185", + "2.189.146.186", + "2.189.146.188", + "2.189.146.189", + "2.189.146.190", + "2.189.146.191", + "2.189.146.192", + "2.189.146.193", + "2.189.146.194", + "2.189.146.195", + "2.189.146.196", + "2.189.146.197", + "2.189.146.198", + "2.189.146.200", + "2.189.146.201", + "2.189.146.202", + "2.189.146.203", + "2.189.146.204", + "2.189.146.205", + "2.189.146.206", + "2.189.146.207", + "2.189.146.208", + "2.189.146.209", + "2.189.146.210", + "2.189.146.211", + "2.189.146.212", + "2.189.146.213", + "2.189.146.214", + "2.189.146.215", + "2.189.146.216", + "2.189.146.217", + "2.189.146.218", + "2.189.146.219", + "2.189.146.220", + "2.189.146.221", + "2.189.146.222", + "2.189.146.223", + "2.189.146.224", + "2.189.146.225", + "2.189.146.226", + "2.189.146.227", + "2.189.146.228", + "2.189.146.229", + "2.189.146.230", + "2.189.146.231", + "2.189.146.232", + "2.189.146.233", + "2.189.146.234", + "2.189.146.235", + "2.189.146.236", + "2.189.146.238", + "2.189.146.239", + "2.189.146.240", + "2.189.146.241", + "2.189.146.243", + "2.189.146.244", + "2.189.146.245", + "2.189.146.246", + "2.189.146.247", + "2.189.146.248", + "2.189.146.249", + "2.189.146.250", + "2.189.146.251", + "2.189.146.252", + "2.189.146.254", + "2.189.146.255", + "2.189.147.203", + "2.189.167.182", + "2.189.248.85", + "2.189.253.227", + "2.190.0.219", + "2.190.3.109", + "2.190.4.52", + "2.190.6.196", + "2.190.7.228", + "2.190.9.118", + "2.190.11.141", + "2.190.15.136", + "2.190.19.245", + "2.190.20.60", + "2.190.23.236", + "2.190.26.141", + "2.190.27.169", + "2.190.27.197", + "2.190.37.39", + "2.190.38.55", + "2.190.38.87", + "2.190.38.142", + "2.190.38.211", + "2.190.39.77", + "2.190.39.130", + "2.190.40.156", + "2.190.42.21", + "2.190.42.183", + "2.190.45.166", + "2.190.46.116", + "2.190.51.254", + "2.190.52.74", + "2.190.53.198", + "2.190.59.26", + "2.190.59.204", + "2.190.60.248", + "2.190.63.200", + "2.190.64.4", + "2.190.64.131", + "2.190.65.190", + "2.190.76.222", + "2.190.78.34", + "2.190.78.187", + "2.190.78.200", + "2.190.80.240", + "2.190.85.139", + "2.190.87.233", + "2.190.98.198", + "2.190.99.4", + "2.190.100.201", + "2.190.101.82", + "2.190.102.90", + "2.190.102.135", + "2.190.103.7", + "2.190.104.114", + "2.190.109.119", + "2.190.110.193", + "2.190.111.127", + "2.190.113.4", + "2.190.113.163", + "2.190.119.48", + "2.190.122.249", + "2.190.124.53", + "2.190.124.161", + "2.190.125.194", + "2.190.127.59", + "2.190.128.73", + "2.190.129.19", + "2.190.130.21", + "2.190.130.101", + "2.190.131.105", + "2.190.131.202", + "2.190.136.56", + "2.190.136.169", + "2.190.137.45", + "2.190.138.190", + "2.190.139.44", + "2.190.140.121", + "2.190.141.59", + "2.190.142.75", + "2.190.144.94", + "2.190.146.240", + "2.190.148.66", + "2.190.148.218", + "2.190.149.244", + "2.190.152.106", + "2.190.154.170", + "2.190.155.54", + "2.190.155.103", + "2.190.158.27", + "2.190.160.83", + "2.190.160.127", + "2.190.162.121", + "2.190.162.212", + "2.190.163.198", + "2.190.163.219", + "2.190.164.173", + "2.190.166.177", + "2.190.168.116", + "2.190.170.245", + "2.190.172.131", + "2.190.173.242", + "2.190.175.7", + "2.190.176.19", + "2.190.176.114", + "2.190.176.170", + "2.190.177.8", + "2.190.177.128", + "2.190.178.171", + "2.190.179.93", + "2.190.180.3", + "2.190.180.123", + "2.190.183.211", + "2.190.184.51", + "2.190.184.94", + "2.190.184.245", + "2.190.187.101", + "2.190.188.246", + "2.190.190.162", + "2.190.197.84", + "2.190.200.30", + "2.190.201.140", + "2.190.202.90", + "2.190.207.43", + "2.190.211.126", + "2.190.213.109", + "2.190.221.31", + "2.190.223.151", + "2.190.224.23", + "2.190.225.129", + "2.190.227.220", + "2.190.230.241", + "2.190.233.13", + "2.190.235.238", + "2.190.238.96", + "2.190.239.255", + "2.190.240.70", + "2.190.240.227", + "2.190.245.242", + "2.190.247.236", + "2.190.249.207", + "2.190.250.172", + "2.190.251.210", + "2.190.254.120", + "2.190.254.154", + "2.190.255.53", + "2.191.0.6", + "2.191.0.224", + "2.191.1.50", + "2.191.1.113", + "2.191.1.242", + "2.191.4.59", + "2.191.5.65", + "2.191.7.180", + "2.191.10.52", + "2.191.12.81", + "2.191.16.31", + "2.191.17.99", + "2.191.18.29", + "2.191.18.142", + "2.191.18.180", + "2.191.19.78", + "2.191.20.194", + "2.191.21.17", + "2.191.22.179", + "2.191.23.246", + "2.191.24.148", + "2.191.24.199", + "2.191.24.212", + "2.191.25.48", + "2.191.25.243", + "2.191.26.247", + "2.191.31.63", + "2.191.31.199", + "2.191.31.237", + "2.191.33.102", + "2.191.33.218", + "2.191.36.95", + "2.191.38.0", + "2.191.38.215", + "2.191.39.12", + "2.191.39.33", + "2.191.44.159", + "2.191.46.156", + "2.191.47.193", + "2.191.47.212", + "2.191.48.80", + "2.191.50.18", + "2.191.50.221", + "2.191.51.19", + "2.191.51.147", + "2.191.52.111", + "2.191.53.176", + "2.191.53.235", + "2.191.55.88", + "2.191.55.143", + "2.191.57.193", + "2.191.60.111", + "2.191.60.174", + "2.191.64.245", + "2.191.65.114", + "2.191.67.13", + "2.191.67.122", + "2.191.67.132", + "2.191.68.132", + "2.191.72.96", + "2.191.75.140", + "2.191.77.110", + "2.191.79.203", + "2.191.81.104", + "2.191.83.222", + "2.191.85.229", + "2.191.88.158", + "2.191.88.227", + "2.191.89.5", + "2.191.89.181", + "2.191.90.111", + "2.191.92.21", + "2.191.93.51", + "2.191.96.43", + "2.191.98.55", + "2.191.98.216", + "2.191.102.103", + "2.191.102.147", + "2.191.102.197", + "2.191.104.0", + "2.191.109.200", + "2.191.110.107", + "2.191.111.70", + "2.191.111.155", + "2.191.111.206", + "2.191.112.240", + "2.191.117.110", + "2.191.122.151", + "2.191.123.43", + "2.191.123.207", + "2.191.123.233", + "2.191.125.122", + "2.191.126.113", + "2.191.127.237", + "2.191.128.179", + "2.191.129.228", + "2.191.130.41", + "2.191.130.94", + "2.191.136.130", + "2.191.142.36", + "2.191.147.12", + "2.191.150.45", + "2.191.152.160", + "2.191.152.233", + "2.191.153.212", + "2.191.153.213", + "2.191.155.105", + "2.191.156.224", + "2.191.158.7", + "2.191.159.18", + "2.191.161.238", + "2.191.162.18", + "2.191.165.91", + "2.191.166.250", + "2.191.167.59", + "2.191.167.133", + "2.191.167.183", + "2.191.169.113", + "2.191.173.112", + "2.191.173.135", + "2.191.174.192", + "2.191.177.52", + "2.191.180.74", + "2.191.181.20", + "2.191.181.136", + "2.191.190.126", + "2.191.196.245", + "2.191.199.52", + "2.191.203.66", + "2.191.207.176", + "2.191.212.121", + "2.191.215.7", + "2.191.221.159", + "2.191.230.3", + "2.191.231.150", + "5.22.198.2", + "5.22.199.225", + "5.22.199.227", + "5.22.199.233", + "5.61.24.140", + "5.61.25.140", + "5.62.161.12", + "5.62.164.234", + "5.62.167.252", + "5.62.168.205", + "5.62.168.209", + "5.62.169.32", + "5.62.170.171", + "5.62.171.239", + "5.62.172.230", + "5.62.177.52", + "5.62.180.232", + "5.62.184.217", + "5.62.185.88", + "5.62.185.142", + "5.62.187.24", + "5.62.190.208", + "5.74.1.46", + "5.74.2.235", + "5.74.4.38", + "5.74.4.133", + "5.74.5.163", + "5.74.8.73", + "5.74.10.109", + "5.74.10.150", + "5.74.10.215", + "5.74.11.204", + "5.74.13.185", + "5.74.14.152", + "5.74.14.155", + "5.74.15.104", + "5.74.15.181", + "5.74.15.246", + "5.74.16.146", + "5.74.17.89", + "5.74.18.0", + "5.74.21.244", + "5.74.23.161", + "5.74.25.63", + "5.74.25.64", + "5.74.26.88", + "5.74.26.94", + "5.74.31.226", + "5.74.32.237", + "5.74.33.49", + "5.74.42.117", + "5.74.44.120", + "5.74.45.152", + "5.74.46.132", + "5.74.47.40", + "5.74.48.79", + "5.74.48.107", + "5.74.48.243", + "5.74.49.7", + "5.74.49.68", + "5.74.51.101", + "5.74.51.243", + "5.74.53.164", + "5.74.56.24", + "5.74.68.122", + "5.74.69.14", + "5.74.70.57", + "5.74.70.206", + "5.74.72.139", + "5.74.72.153", + "5.74.73.211", + "5.74.81.76", + "5.74.81.109", + "5.74.81.245", + "5.74.82.14", + "5.74.83.74", + "5.74.84.91", + "5.74.85.195", + "5.74.86.234", + "5.74.87.26", + "5.74.89.115", + "5.74.90.185", + "5.74.93.95", + "5.74.94.194", + "5.74.96.192", + "5.74.101.232", + "5.74.102.212", + "5.74.103.201", + "5.74.104.46", + "5.74.104.251", + "5.74.105.6", + "5.74.105.160", + "5.74.106.186", + "5.74.107.255", + "5.74.108.196", + "5.74.111.225", + "5.74.112.152", + "5.74.114.57", + "5.74.114.157", + "5.74.119.110", + "5.74.122.30", + "5.74.122.185", + "5.74.124.110", + "5.74.124.128", + "5.74.129.51", + "5.74.137.139", + "5.74.140.172", + "5.74.141.173", + "5.74.143.207", + "5.74.144.104", + "5.74.145.28", + "5.74.146.231", + "5.74.150.112", + "5.74.151.182", + "5.74.152.5", + "5.74.153.178", + "5.74.159.57", + "5.74.167.27", + "5.74.172.121", + "5.74.172.152", + "5.74.174.105", + "5.74.176.41", + "5.74.180.83", + "5.74.180.115", + "5.74.184.161", + "5.74.184.196", + "5.74.185.175", + "5.74.185.189", + "5.74.186.120", + "5.74.187.88", + "5.74.190.109", + "5.74.191.121", + "5.74.192.203", + "5.74.197.119", + "5.74.200.185", + "5.74.210.56", + "5.74.212.34", + "5.74.213.219", + "5.74.217.255", + "5.74.218.230", + "5.74.224.78", + "5.74.227.206", + "5.74.227.218", + "5.74.229.87", + "5.74.230.51", + "5.74.232.30", + "5.74.233.67", + "5.74.234.125", + "5.74.234.204", + "5.74.239.116", + "5.74.245.238", + "5.74.249.28", + "5.74.249.161", + "5.74.250.248", + "5.74.251.44", + "5.74.255.5", + "5.74.255.195", + "5.106.18.195", + "5.106.19.94", + "5.106.19.219", + "5.134.193.5", + "5.134.196.186", + "5.159.48.64", + "5.159.48.65", + "5.159.48.66", + "5.159.48.67", + "5.159.48.68", + "5.159.48.69", + "5.159.48.70", + "5.159.48.71", + "5.159.50.80", + "5.159.50.81", + "5.159.50.82", + "5.159.50.83", + "5.159.50.84", + "5.159.50.85", + "5.159.50.86", + "5.159.50.87", + "5.159.51.208", + "5.159.51.209", + "5.159.51.210", + "5.159.51.211", + "5.159.51.212", + "5.159.51.213", + "5.159.51.214", + "5.159.52.128", + "5.159.52.130", + "5.159.52.131", + "5.159.52.133", + "5.159.52.134", + "5.159.52.135", + "5.159.52.240", + "5.159.52.242", + "5.159.52.243", + "5.159.53.57", + "5.159.53.59", + "5.159.53.60", + "5.159.53.61", + "5.159.53.63", + "5.159.55.32", + "5.159.55.33", + "5.159.55.34", + "5.159.55.35", + "5.159.55.36", + "5.159.55.37", + "5.159.55.38", + "5.159.55.39", + "5.160.4.2", + "5.160.4.3", + "5.160.4.4", + "5.160.4.5", + "5.160.4.7", + "5.160.4.8", + "5.160.4.9", + "5.160.4.10", + "5.160.4.11", + "5.160.4.12", + "5.160.12.122", + "5.160.13.202", + "5.160.24.22", + "5.160.25.130", + "5.160.36.9", + "5.160.45.254", + "5.160.56.246", + "5.160.62.1", + "5.160.65.254", + "5.160.92.121", + "5.160.92.226", + "5.160.92.227", + "5.160.92.228", + "5.160.92.229", + "5.160.92.230", + "5.160.94.34", + "5.160.96.225", + "5.160.109.184", + "5.160.123.194", + "5.160.126.138", + "5.160.136.10", + "5.160.148.66", + "5.160.148.67", + "5.160.148.68", + "5.160.148.69", + "5.160.148.70", + "5.160.148.71", + "5.160.148.72", + "5.160.148.74", + "5.160.148.77", + "5.160.163.134", + "5.160.171.205", + "5.160.178.11", + "5.160.186.158", + "5.160.224.230", + "5.160.236.206", + "5.190.6.209", + "5.190.15.14", + "5.190.15.24", + "5.190.19.169", + "5.190.31.223", + "5.190.35.66", + "5.190.35.97", + "5.190.38.2", + "5.190.38.49", + "5.190.69.7", + "5.190.112.72", + "5.190.113.6", + "5.190.113.140", + "5.190.113.183", + "5.190.128.8", + "5.190.128.129", + "5.190.160.53", + "5.190.160.57", + "5.190.220.34", + "5.198.177.232", + "5.200.65.62", + "5.200.72.170", + "5.200.72.171", + "5.200.72.172", + "5.200.72.173", + "5.200.74.42", + "5.200.74.50", + "5.200.92.86", + "5.200.92.244", + "5.200.128.20", + "5.200.132.3", + "5.200.134.101", + "5.200.146.99", + "5.200.163.136", + "5.200.164.247", + "5.200.170.71", + "5.200.171.36", + "5.200.176.48", + "5.200.184.32", + "5.200.187.76", + "5.200.194.128", + "5.200.195.164", + "5.200.197.185", + "5.200.198.18", + "5.200.201.89", + "5.200.210.162", + "5.200.215.15", + "5.200.240.130", + "5.200.242.159", + "5.200.244.61", + "5.201.137.96", + "5.201.179.171", + "5.201.188.29", + "5.201.190.178", + "5.202.7.227", + "5.202.8.189", + "5.202.18.102", + "5.202.31.165", + "5.202.31.223", + "5.202.33.71", + "5.202.37.55", + "5.202.44.204", + "5.202.45.176", + "5.202.46.130", + "5.202.50.117", + "5.202.53.129", + "5.202.56.141", + "5.202.60.116", + "5.202.67.98", + "5.202.68.103", + "5.202.84.189", + "5.202.101.56", + "5.202.101.146", + "5.202.104.46", + "5.202.105.105", + "5.202.130.8", + "5.202.130.15", + "5.202.131.150", + "5.202.133.117", + "5.202.134.189", + "5.202.134.199", + "5.202.168.111", + "5.202.168.119", + "5.202.168.152", + "5.202.169.223", + "5.202.170.19", + "5.202.171.163", + "5.202.171.191", + "5.202.172.103", + "5.202.172.114", + "5.202.172.183", + "5.202.174.52", + "5.202.176.249", + "5.202.180.114", + "5.202.192.33", + "5.202.193.131", + "5.202.195.45", + "5.202.195.51", + "5.202.195.180", + "5.202.197.82", + "5.202.198.47", + "5.202.243.73", + "5.202.244.49", + "5.202.244.217", + "5.202.248.100", + "5.202.248.115", + "5.202.251.74", + "5.202.252.251", + "5.232.193.40", + "5.232.195.40", + "5.232.199.145", + "5.232.201.225", + "5.232.208.59", + "5.232.208.178", + "5.232.210.50", + "5.232.210.132", + "5.232.210.222", + "5.232.214.197", + "5.233.44.62", + "5.233.46.93", + "5.233.48.136", + "5.233.48.194", + "5.233.52.86", + "5.233.53.56", + "5.233.55.148", + "5.233.58.136", + "5.233.58.144", + "5.233.59.44", + "5.233.59.230", + "5.233.61.98", + "5.233.64.200", + "5.233.67.248", + "5.233.69.125", + "5.233.74.76", + "5.233.75.240", + "5.233.75.250", + "5.233.77.37", + "5.233.81.193", + "5.233.85.66", + "5.233.91.53", + "5.233.93.115", + "5.233.93.131", + "5.233.94.60", + "5.233.97.234", + "5.233.106.189", + "5.233.181.73", + "5.233.250.11", + "5.233.250.69", + "5.234.0.79", + "5.234.33.50", + "5.234.34.57", + "5.234.196.166", + "5.234.199.241", + "5.234.200.92", + "5.234.200.137", + "5.234.202.94", + "5.234.202.190", + "5.234.204.177", + "5.234.204.253", + "5.234.207.165", + "5.234.208.89", + "5.234.210.1", + "5.234.219.151", + "5.235.156.18", + "5.235.158.237", + "5.235.160.167", + "5.235.163.89", + "5.235.163.98", + "5.235.165.39", + "5.235.168.186", + "5.235.183.5", + "5.235.183.159", + "5.235.184.199", + "5.235.186.254", + "5.235.204.78", + "5.235.208.203", + "5.235.225.63", + "5.235.237.139", + "5.236.27.202", + "5.236.36.77", + "5.236.37.103", + "5.236.37.159", + "5.236.52.142", + "5.236.68.129", + "5.236.73.237", + "5.236.74.210", + "5.236.84.190", + "5.236.84.192", + "5.236.85.62", + "5.236.107.158", + "5.236.118.91", + "5.236.118.105", + "5.236.119.175", + "5.236.119.229", + "5.236.120.190", + "5.236.120.193", + "5.236.120.235", + "5.236.123.214", + "5.236.124.167", + "5.236.126.76", + "5.236.126.227", + "5.236.194.234", + "5.236.195.148", + "5.236.209.26", + "5.236.229.2", + "5.236.230.82", + "5.236.231.10", + "5.236.232.140", + "5.236.232.215", + "5.236.232.238", + "5.236.234.18", + "5.236.235.10", + "5.236.235.199", + "5.236.237.236", + "5.236.239.22", + "5.236.241.109", + "5.236.241.133", + "5.236.245.47", + "5.236.245.86", + "5.236.245.163", + "5.236.246.226", + "5.236.249.2", + "5.236.250.105", + "5.236.250.176", + "5.236.251.135", + "5.237.48.134", + "5.237.51.190", + "5.237.172.191", + "5.237.174.161", + "5.237.190.133", + "5.237.206.248", + "5.237.213.90", + "5.237.245.132", + "5.237.255.27", + "5.238.0.89", + "5.238.7.139", + "5.238.24.206", + "5.238.30.209", + "5.238.31.104", + "5.238.65.35", + "5.238.78.40", + "5.238.88.24", + "5.238.89.91", + "5.238.97.239", + "5.238.110.3", + "5.238.110.167", + "5.238.110.213", + "5.238.130.4", + "5.238.133.19", + "5.238.134.79", + "5.238.135.96", + "5.238.141.127", + "5.238.143.185", + "5.238.150.47", + "5.238.150.53", + "5.238.150.56", + "5.238.151.220", + "5.238.152.177", + "5.238.152.198", + "5.238.152.219", + "5.238.154.229", + "5.238.156.79", + "5.238.156.165", + "5.238.157.70", + "5.238.157.199", + "5.238.158.245", + "5.238.159.131", + "5.238.160.36", + "5.238.165.192", + "5.238.169.30", + "5.238.171.5", + "5.238.172.60", + "5.238.173.41", + "5.238.175.197", + "5.238.177.107", + "5.238.178.33", + "5.238.179.239", + "5.238.181.231", + "5.238.181.255", + "5.238.182.247", + "5.238.185.124", + "5.238.187.1", + "5.238.187.140", + "5.238.193.30", + "5.238.193.244", + "5.238.200.210", + "5.238.201.203", + "5.238.203.199", + "5.238.204.198", + "5.238.207.195", + "5.238.212.93", + "5.238.212.230", + "5.238.215.18", + "5.238.217.118", + "5.238.219.142", + "5.238.220.91", + "5.238.220.102", + "5.238.220.140", + "5.238.220.186", + "5.238.220.220", + "5.238.220.248", + "5.238.221.65", + "5.238.223.141", + "5.238.223.191", + "5.238.224.243", + "5.238.225.34", + "5.238.229.124", + "5.238.232.103", + "5.238.232.177", + "5.238.236.69", + "5.238.238.240", + "5.238.240.163", + "5.238.241.173", + "5.238.242.42", + "5.238.242.208", + "5.238.243.13", + "5.238.245.212", + "5.238.246.10", + "5.238.246.172", + "5.238.247.166", + "5.238.248.237", + "5.238.251.105", + "5.238.253.179", + "5.238.255.209", + "5.239.1.48", + "5.239.2.189", + "5.239.7.66", + "5.239.8.61", + "5.239.8.91", + "5.239.9.27", + "5.239.9.95", + "5.239.15.238", + "5.239.16.146", + "5.239.18.169", + "5.239.19.59", + "5.239.21.123", + "5.239.21.243", + "5.239.23.168", + "5.239.28.79", + "5.239.28.187", + "5.239.28.190", + "5.239.32.187", + "5.239.33.153", + "5.239.39.141", + "5.239.43.186", + "5.239.44.114", + "5.239.45.55", + "5.239.45.119", + "5.239.46.122", + "5.239.50.227", + "5.239.52.92", + "5.239.53.98", + "5.239.56.200", + "5.239.57.135", + "5.239.58.62", + "5.239.59.64", + "5.239.59.107", + "5.239.60.67", + "5.239.62.104", + "5.239.63.8", + "5.239.64.243", + "5.239.67.160", + "5.239.67.218", + "5.239.68.12", + "5.239.72.145", + "5.239.75.70", + "5.239.77.124", + "5.239.77.235", + "5.239.78.153", + "5.239.81.155", + "5.239.82.24", + "5.239.82.141", + "5.239.84.50", + "5.239.86.80", + "5.239.86.114", + "5.239.86.213", + "5.239.86.250", + "5.239.87.163", + "5.239.88.27", + "5.239.88.97", + "5.239.89.181", + "5.239.95.160", + "5.239.97.128", + "5.239.97.243", + "5.239.106.96", + "5.239.107.197", + "5.239.114.119", + "5.239.114.191", + "5.239.115.200", + "5.239.118.70", + "5.239.120.171", + "5.239.121.241", + "5.239.122.71", + "5.239.123.122", + "5.239.146.31", + "5.239.156.62", + "5.239.160.78", + "5.239.229.90", + "5.239.229.93", + "5.239.229.141", + "5.239.230.163", + "5.239.231.128", + "5.239.232.233", + "5.239.234.104", + "5.239.234.118", + "5.239.234.251", + "5.239.237.197", + "5.239.240.243", + "5.239.241.88", + "31.7.65.42", + "31.7.65.43", + "31.7.65.44", + "31.7.65.46", + "31.7.89.106", + "31.7.89.126", + "31.7.90.115", + "31.24.201.82", + "31.25.92.29", + "31.25.92.100", + "31.25.92.161", + "31.25.129.114", + "31.25.132.114", + "31.25.135.146", + "31.47.51.23", + "31.47.51.149", + "31.171.222.82", + "31.184.141.30", + "31.184.188.88", + "37.32.47.176", + "37.32.121.210", + "37.32.121.225", + "37.32.125.194", + "37.75.244.6", + "37.75.244.51", + "37.75.245.24", + "37.75.245.59", + "37.75.245.130", + "37.75.245.132", + "37.75.245.133", + "37.75.246.1", + "37.75.246.50", + "37.75.246.52", + "37.75.246.53", + "37.143.147.31", + "37.148.0.71", + "37.148.17.66", + "37.148.25.167", + "37.148.58.95", + "37.148.81.211", + "37.148.86.227", + "37.152.164.50", + "37.153.128.176", + "37.153.130.55", + "37.156.8.33", + "37.156.8.85", + "37.156.9.190", + "37.156.11.70", + "37.156.12.28", + "37.156.13.215", + "37.156.15.184", + "37.156.15.246", + "37.156.30.206", + "37.156.144.81", + "37.191.78.213", + "37.191.78.214", + "37.202.156.75", + "37.202.156.98", + "37.202.157.22", + "37.202.170.112", + "37.202.171.204", + "37.202.189.21", + "37.228.139.163", + "37.228.139.164", + "37.228.139.165", + "37.228.139.166", + "37.255.128.218", + "37.255.128.220", + "37.255.129.56", + "37.255.132.26", + "37.255.134.87", + "37.255.134.143", + "37.255.149.128", + "37.255.149.221", + "37.255.173.50", + "37.255.176.18", + "37.255.192.204", + "37.255.195.143", + "37.255.196.74", + "37.255.196.175", + "37.255.208.116", + "37.255.210.1", + "37.255.211.212", + "37.255.213.72", + "37.255.213.226", + "37.255.217.2", + "37.255.220.2", + "37.255.220.101", + "37.255.222.28", + "37.255.222.121", + "37.255.223.74", + "37.255.226.13", + "37.255.231.150", + "37.255.235.76", + "37.255.236.74", + "37.255.238.0", + "37.255.238.113", + "37.255.238.136", + "37.255.238.156", + "37.255.240.167", + "37.255.241.74", + "37.255.242.142", + "37.255.242.252", + "37.255.255.243", + "45.9.253.179", + "45.87.4.91", + "45.87.4.130", + "45.94.212.5", + "45.132.175.121", + "45.135.241.251", + "45.138.135.75", + "45.142.191.27", + "45.142.191.196", + "45.142.191.197", + "45.142.191.202", + "45.142.191.203", + "45.159.150.79", + "46.32.31.195", + "46.34.161.193", + "46.34.166.142", + "46.34.166.236", + "46.51.37.142", + "46.100.2.195", + "46.100.3.123", + "46.100.8.118", + "46.100.10.122", + "46.100.10.123", + "46.100.10.237", + "46.100.11.36", + "46.100.11.103", + "46.100.11.168", + "46.100.32.107", + "46.100.32.206", + "46.100.33.20", + "46.100.40.43", + "46.100.41.93", + "46.100.42.58", + "46.100.45.65", + "46.100.45.228", + "46.100.45.234", + "46.100.47.174", + "46.100.49.96", + "46.100.50.129", + "46.100.52.76", + "46.100.53.30", + "46.100.53.68", + "46.100.55.77", + "46.100.56.65", + "46.100.57.133", + "46.100.58.82", + "46.100.58.238", + "46.100.59.62", + "46.100.59.121", + "46.100.60.8", + "46.100.60.29", + "46.100.60.125", + "46.100.60.151", + "46.100.61.40", + "46.100.61.74", + "46.100.61.171", + "46.100.61.247", + "46.100.62.89", + "46.100.75.205", + "46.100.92.38", + "46.100.92.64", + "46.100.93.209", + "46.100.94.175", + "46.100.99.218", + "46.100.106.113", + "46.100.116.237", + "46.100.132.82", + "46.100.132.250", + "46.100.146.123", + "46.100.147.227", + "46.100.148.51", + "46.100.149.144", + "46.100.164.204", + "46.100.165.3", + "46.100.165.41", + "46.100.165.154", + "46.100.167.151", + "46.100.167.203", + "46.100.168.233", + "46.100.169.22", + "46.100.169.38", + "46.100.169.142", + "46.100.171.14", + "46.100.171.105", + "46.100.187.239", + "46.100.196.108", + "46.100.216.23", + "46.100.219.91", + "46.100.220.219", + "46.100.220.225", + "46.100.221.40", + "46.100.221.85", + "46.100.231.142", + "46.100.243.137", + "46.100.248.35", + "46.102.130.37", + "46.143.245.4", + "46.148.34.220", + "46.148.38.10", + "46.148.38.19", + "46.148.38.43", + "46.148.39.101", + "46.148.40.49", + "46.148.43.190", + "46.148.44.204", + "46.167.133.214", + "46.167.136.35", + "46.167.140.202", + "46.167.144.122", + "46.167.144.253", + "46.167.145.195", + "46.167.147.150", + "46.167.151.122", + "46.167.151.238", + "46.167.159.23", + "46.209.22.231", + "46.209.30.46", + "46.209.44.2", + "46.209.51.158", + "46.209.106.204", + "46.209.223.243", + "46.209.255.243", + "46.235.76.19", + "46.235.77.78", + "46.245.2.181", + "46.245.12.52", + "46.245.13.243", + "46.245.14.45", + "46.245.14.119", + "46.245.15.57", + "46.245.23.49", + "46.245.26.167", + "46.245.27.5", + "46.245.31.147", + "46.245.40.26", + "46.245.43.146", + "46.245.43.156", + "46.245.45.95", + "46.245.45.132", + "46.245.46.94", + "46.245.76.247", + "46.245.76.249", + "46.245.78.84", + "46.245.78.86", + "46.245.87.3", + "46.245.89.48", + "46.245.89.51", + "46.245.89.52", + "46.245.89.53", + "46.245.89.55", + "46.245.89.56", + "46.245.89.57", + "46.245.91.72", + "46.245.91.73", + "46.245.91.74", + "46.245.91.75", + "46.245.91.78", + "46.245.91.80", + "46.245.94.190", + "46.245.114.114", + "46.249.120.224", + "46.249.120.227", + "46.249.120.228", + "46.249.120.244", + "46.249.121.42", + "46.249.121.227", + "46.249.122.28", + "46.249.122.86", + "46.249.122.87", + "46.249.122.90", + "46.249.122.91", + "46.249.124.1", + "46.249.126.177", + "62.60.162.236", + "62.60.163.244", + "62.60.207.217", + "62.60.223.196", + "62.220.112.249", + "66.79.99.200", + "66.79.112.89", + "66.79.113.200", + "77.36.146.37", + "77.36.243.5", + "77.81.34.180", + "77.81.34.221", + "77.81.35.43", + "77.81.37.184", + "77.81.40.10", + "77.81.41.69", + "77.81.46.254", + "77.81.146.230", + "77.81.147.29", + "77.81.148.90", + "77.81.148.172", + "77.81.148.187", + "77.81.150.212", + "77.81.153.245", + "77.81.154.53", + "77.81.156.52", + "77.81.158.150", + "77.81.158.173", + "77.81.158.250", + "77.104.90.154", + "77.104.93.43", + "77.104.115.152", + "77.104.115.161", + "77.104.126.13", + "77.104.126.136", + "77.238.106.88", + "77.238.106.108", + "77.238.106.154", + "77.238.107.79", + "77.238.109.90", + "78.38.26.128", + "78.38.26.132", + "78.38.26.134", + "78.38.26.135", + "78.38.26.151", + "78.38.26.155", + "78.38.26.159", + "78.38.26.164", + "78.38.26.181", + "78.38.26.183", + "78.38.26.188", + "78.38.26.210", + "78.38.26.226", + "78.38.27.3", + "78.38.29.31", + "78.38.30.118", + "78.38.34.90", + "78.38.88.75", + "78.38.89.48", + "78.38.90.169", + "78.38.91.170", + "78.38.105.11", + "78.38.110.82", + "78.38.123.58", + "78.38.126.189", + "78.38.152.39", + "78.38.153.189", + "78.38.166.50", + "78.38.166.161", + "78.38.168.165", + "78.38.169.230", + "78.38.183.35", + "78.38.194.203", + "78.38.195.162", + "78.38.195.168", + "78.38.210.89", + "78.38.210.119", + "78.38.210.206", + "78.38.217.110", + "78.38.243.235", + "78.39.11.74", + "78.39.26.103", + "78.39.28.149", + "78.39.29.203", + "78.39.29.208", + "78.39.31.90", + "78.39.57.167", + "78.39.81.36", + "78.39.83.6", + "78.39.83.21", + "78.39.83.133", + "78.39.88.109", + "78.39.88.157", + "78.39.88.243", + "78.39.89.85", + "78.39.89.162", + "78.39.90.151", + "78.39.91.177", + "78.39.91.192", + "78.39.92.139", + "78.39.93.201", + "78.39.94.33", + "78.39.94.34", + "78.39.95.51", + "78.39.97.3", + "78.39.99.16", + "78.39.99.18", + "78.39.99.19", + "78.39.99.20", + "78.39.99.21", + "78.39.99.22", + "78.39.99.23", + "78.39.99.114", + "78.39.99.118", + "78.39.112.154", + "78.39.119.115", + "78.39.119.205", + "78.39.120.28", + "78.39.120.135", + "78.39.120.208", + "78.39.122.39", + "78.39.123.99", + "78.39.123.105", + "78.39.126.216", + "78.39.134.132", + "78.39.139.32", + "78.39.140.106", + "78.39.140.166", + "78.39.142.2", + "78.39.145.18", + "78.39.173.132", + "78.39.201.210", + "78.39.228.64", + "78.39.228.77", + "78.39.231.68", + "78.39.233.249", + "78.39.234.58", + "78.39.235.85", + "78.39.250.120", + "78.110.122.102", + "78.157.34.223", + "78.157.35.16", + "78.157.35.31", + "78.157.35.40", + "78.157.37.240", + "78.157.38.175", + "78.157.39.84", + "78.157.39.112", + "78.157.39.127", + "78.157.41.112", + "78.157.41.120", + "78.157.43.176", + "78.157.43.183", + "78.157.45.48", + "78.157.45.136", + "78.157.45.224", + "78.157.45.231", + "78.157.45.240", + "78.157.45.247", + "78.157.46.80", + "78.157.47.115", + "78.157.48.23", + "78.157.48.127", + "78.157.48.128", + "78.157.50.23", + "78.157.57.32", + "78.157.57.60", + "78.157.57.67", + "78.157.57.194", + "78.157.57.227", + "78.157.57.237", + "78.157.58.31", + "78.157.58.36", + "78.157.58.40", + "78.157.58.168", + "78.157.61.40", + "78.158.191.228", + "79.127.2.107", + "79.127.3.107", + "79.127.3.122", + "79.127.4.41", + "79.127.4.154", + "79.127.5.25", + "79.127.5.209", + "79.127.6.78", + "79.127.6.134", + "79.127.6.165", + "79.127.7.120", + "79.127.13.189", + "79.127.14.88", + "79.127.69.121", + "79.127.69.125", + "79.127.75.51", + "79.127.75.52", + "79.127.75.54", + "79.127.77.51", + "79.127.77.52", + "79.127.77.53", + "79.127.77.54", + "79.127.101.13", + "79.127.125.18", + "79.127.126.91", + "79.175.182.42", + "79.175.186.60", + "79.175.186.61", + "80.75.5.26", + "80.75.8.70", + "80.191.16.186", + "80.191.17.246", + "80.191.38.128", + "80.191.39.11", + "80.191.39.185", + "80.191.40.137", + "80.191.40.152", + "80.191.51.51", + "80.191.52.19", + "80.191.64.104", + "80.191.71.89", + "80.191.71.136", + "80.191.77.163", + "80.191.86.234", + "80.191.87.143", + "80.191.100.159", + "80.191.102.86", + "80.191.107.5", + "80.191.189.166", + "80.191.192.249", + "80.191.194.187", + "80.191.195.227", + "80.191.203.4", + "80.191.220.134", + "80.191.221.10", + "80.191.222.119", + "80.191.230.86", + "80.191.235.130", + "80.191.240.70", + "80.191.250.61", + "80.191.253.225", + "80.210.6.100", + "80.210.7.254", + "80.210.8.120", + "80.210.9.170", + "80.210.9.226", + "80.210.12.253", + "80.210.17.204", + "80.210.18.80", + "80.210.18.226", + "80.210.21.43", + "80.210.22.3", + "80.210.22.27", + "80.210.22.73", + "80.210.22.115", + "80.210.22.181", + "80.210.22.219", + "80.210.24.197", + "80.210.25.137", + "80.210.26.129", + "80.210.27.23", + "80.210.30.51", + "80.210.30.117", + "80.210.32.0", + "80.210.32.240", + "80.210.35.177", + "80.210.35.231", + "80.210.37.24", + "80.210.41.3", + "80.210.41.114", + "80.210.42.127", + "80.210.42.186", + "80.210.44.69", + "80.210.45.21", + "80.210.45.71", + "80.210.48.164", + "80.210.48.185", + "80.210.50.88", + "80.210.50.152", + "80.210.50.199", + "80.210.51.52", + "80.210.52.182", + "80.210.54.72", + "80.210.58.171", + "80.210.59.165", + "80.210.60.166", + "80.210.63.101", + "80.210.139.23", + "80.210.141.56", + "80.210.160.201", + "80.210.164.153", + "80.210.183.27", + "80.210.193.176", + "80.210.196.185", + "80.210.242.220", + "80.210.245.84", + "80.210.246.86", + "80.253.152.178", + "80.253.152.180", + "81.12.1.28", + "81.12.5.227", + "81.12.47.52", + "81.12.70.146", + "81.12.72.234", + "81.12.74.5", + "81.12.76.64", + "81.12.76.65", + "81.12.76.66", + "81.12.76.67", + "81.12.76.68", + "81.12.76.69", + "81.12.76.70", + "81.12.76.71", + "81.12.76.73", + "81.12.76.74", + "81.12.76.75", + "81.12.76.77", + "81.12.76.79", + "81.12.84.247", + "81.12.87.101", + "81.12.99.218", + "81.12.118.214", + "81.12.124.117", + "81.12.124.158", + "81.12.127.50", + "81.16.112.49", + "81.16.112.111", + "81.16.113.186", + "81.16.114.44", + "81.16.114.199", + "81.16.117.49", + "81.16.121.150", + "81.16.121.241", + "81.16.124.53", + "81.16.124.73", + "81.16.124.165", + "81.16.125.185", + "81.16.125.235", + "81.16.126.254", + "81.28.32.40", + "81.28.33.58", + "81.28.52.31", + "81.28.61.73", + "81.29.248.99", + "81.29.248.100", + "81.29.248.101", + "81.29.248.102", + "81.29.248.103", + "81.29.248.104", + "81.29.248.105", + "81.29.248.107", + "81.90.146.46", + "81.91.136.210", + "81.91.144.18", + "81.91.146.22", + "81.91.151.30", + "81.91.152.222", + "81.91.153.9", + "81.91.155.78", + "81.91.156.186", + "81.91.157.178", + "81.91.159.58", + "81.163.6.45", + "82.99.232.78", + "82.99.253.49", + "84.47.236.218", + "84.47.236.242", + "84.241.1.127", + "84.241.2.181", + "84.241.2.187", + "84.241.3.56", + "84.241.3.94", + "84.241.3.202", + "84.241.4.78", + "84.241.5.178", + "84.241.6.65", + "84.241.6.132", + "84.241.6.191", + "84.241.6.244", + "84.241.8.128", + "84.241.8.189", + "84.241.9.10", + "84.241.12.44", + "84.241.12.173", + "84.241.12.174", + "84.241.14.53", + "84.241.14.215", + "84.241.14.220", + "84.241.14.228", + "84.241.15.29", + "84.241.15.191", + "84.241.16.37", + "84.241.16.46", + "84.241.16.203", + "84.241.16.229", + "84.241.18.174", + "84.241.18.236", + "84.241.18.254", + "84.241.19.114", + "84.241.22.145", + "84.241.23.155", + "84.241.24.107", + "84.241.24.201", + "84.241.24.246", + "84.241.25.93", + "84.241.25.250", + "84.241.26.234", + "84.241.27.32", + "84.241.27.173", + "84.241.28.126", + "84.241.28.170", + "84.241.28.209", + "84.241.29.33", + "84.241.29.55", + "84.241.29.213", + "84.241.30.49", + "84.241.30.140", + "84.241.36.194", + "84.241.38.77", + "84.241.38.166", + "84.241.41.13", + "84.241.42.191", + "84.241.44.145", + "84.241.44.198", + "84.241.45.83", + "84.241.45.218", + "84.241.49.40", + "84.241.52.13", + "84.241.52.15", + "84.241.56.122", + "84.241.60.91", + "85.9.87.50", + "85.9.87.51", + "85.9.94.3", + "85.9.94.229", + "85.9.105.38", + "85.9.105.41", + "85.9.115.105", + "85.9.120.237", + "85.9.121.104", + "85.9.122.69", + "85.133.133.206", + "85.133.136.202", + "85.133.137.98", + "85.133.173.138", + "85.133.173.139", + "85.133.173.141", + "85.133.173.142", + "85.133.176.48", + "85.133.184.233", + "85.133.186.106", + "85.133.187.69", + "85.133.190.170", + "85.133.221.190", + "85.133.221.191", + "85.133.228.178", + "85.133.229.121", + "85.133.229.202", + "85.185.3.100", + "85.185.12.173", + "85.185.13.60", + "85.185.13.148", + "85.185.14.43", + "85.185.43.176", + "85.185.43.224", + "85.185.49.27", + "85.185.54.100", + "85.185.62.32", + "85.185.75.218", + "85.185.78.181", + "85.185.81.226", + "85.185.81.227", + "85.185.85.6", + "85.185.87.105", + "85.185.88.134", + "85.185.98.57", + "85.185.111.80", + "85.185.123.177", + "85.185.133.193", + "85.185.138.250", + "85.185.139.34", + "85.185.139.42", + "85.185.185.172", + "85.185.187.2", + "85.185.190.109", + "85.185.201.197", + "85.185.201.198", + "85.185.201.227", + "85.185.201.230", + "85.185.206.161", + "85.185.208.82", + "85.185.212.150", + "85.185.235.18", + "85.185.238.111", + "85.185.239.154", + "85.185.239.155", + "85.185.239.157", + "85.198.26.3", + "85.198.28.2", + "85.198.29.80", + "85.198.30.54", + "85.198.30.223", + "85.198.31.15", + "85.198.31.107", + "85.198.31.113", + "85.204.88.255", + "85.204.208.159", + "86.104.38.94", + "86.104.83.86", + "86.104.88.202", + "86.104.96.231", + "86.104.98.191", + "86.104.98.195", + "86.104.98.239", + "86.104.100.245", + "86.104.101.72", + "86.104.105.166", + "86.104.106.34", + "86.104.107.4", + "86.104.109.105", + "86.104.111.197", + "86.105.43.13", + "86.105.44.251", + "86.105.45.105", + "86.105.46.122", + "86.105.47.176", + "86.105.134.249", + "86.105.138.65", + "86.107.93.69", + "86.107.145.246", + "86.107.148.66", + "86.107.149.221", + "86.107.152.218", + "86.107.155.195", + "86.107.155.228", + "86.107.156.242", + "86.107.157.82", + "87.107.17.22", + "87.107.18.26", + "87.107.18.178", + "87.107.18.180", + "87.107.48.10", + "87.107.55.66", + "87.107.87.8", + "87.107.87.9", + "87.107.87.10", + "87.107.87.11", + "87.107.105.43", + "87.107.105.145", + "87.107.136.130", + "87.107.141.35", + "87.107.141.136", + "87.236.38.97", + "87.236.38.99", + "87.236.38.226", + "87.236.214.214", + "87.236.215.179", + "87.248.145.213", + "87.248.150.229", + "87.248.151.36", + "87.251.141.125", + "88.218.16.4", + "89.32.197.10", + "89.33.129.156", + "89.37.152.90", + "89.37.153.30", + "89.37.153.57", + "89.37.155.10", + "89.37.155.119", + "89.37.219.25", + "89.38.103.224", + "89.38.244.110", + "89.38.246.130", + "89.39.9.15", + "89.39.9.200", + "89.39.11.125", + "89.39.11.190", + "89.40.78.167", + "89.40.79.39", + "89.40.79.93", + "89.40.79.94", + "89.40.246.181", + "89.40.247.32", + "89.41.40.219", + "89.41.41.95", + "89.42.69.188", + "89.42.98.141", + "89.43.7.182", + "89.43.144.209", + "89.43.220.123", + "89.43.222.29", + "89.43.224.58", + "89.43.224.123", + "89.43.226.208", + "89.43.227.25", + "89.43.227.182", + "89.43.230.205", + "89.43.231.153", + "89.44.132.254", + "89.44.176.90", + "89.46.95.145", + "89.46.216.48", + "89.46.216.49", + "89.46.216.50", + "89.46.216.51", + "89.46.216.53", + "89.46.218.208", + "89.46.218.209", + "89.46.218.210", + "89.46.218.211", + "89.46.218.212", + "89.46.218.213", + "89.46.218.214", + "89.46.218.215", + "89.46.219.39", + "89.46.219.200", + "89.46.219.201", + "89.46.219.202", + "89.46.219.203", + "89.46.219.204", + "89.46.219.205", + "89.46.219.206", + "89.46.219.207", + "89.144.136.152", + "89.144.148.155", + "89.144.157.144", + "89.144.157.219", + "89.144.167.45", + "89.219.64.165", + "89.219.66.125", + "89.219.68.58", + "89.219.69.122", + "89.219.69.188", + "89.219.71.91", + "89.219.71.206", + "89.219.74.95", + "89.219.74.246", + "89.219.76.55", + "89.219.76.168", + "89.219.76.250", + "89.219.76.253", + "89.219.77.179", + "89.219.78.137", + "89.219.78.248", + "89.219.79.28", + "89.219.79.34", + "89.219.81.20", + "89.219.82.36", + "89.219.82.192", + "89.219.84.16", + "89.219.88.172", + "89.219.92.79", + "89.219.95.236", + "89.219.98.186", + "89.219.102.81", + "89.219.102.145", + "89.219.102.228", + "89.219.104.103", + "89.219.105.36", + "89.219.105.147", + "89.219.105.208", + "89.219.106.180", + "89.219.106.245", + "89.219.107.242", + "89.219.108.30", + "89.219.108.87", + "89.219.108.119", + "89.219.108.162", + "89.219.114.19", + "89.219.114.67", + "89.219.116.81", + "89.219.117.55", + "89.219.118.124", + "89.219.120.196", + "89.219.121.97", + "89.219.121.202", + "89.219.122.163", + "89.219.124.77", + "89.219.124.182", + "89.219.124.190", + "89.219.125.219", + "89.219.125.241", + "89.219.195.28", + "89.219.197.140", + "89.219.198.174", + "89.219.200.215", + "89.219.202.170", + "89.219.204.225", + "89.219.205.242", + "89.219.206.64", + "89.219.206.217", + "89.219.208.93", + "89.219.209.192", + "89.219.210.23", + "89.219.210.59", + "89.219.210.101", + "89.219.211.41", + "89.219.211.114", + "89.219.211.122", + "89.219.213.9", + "89.219.215.18", + "89.219.216.120", + "89.219.219.5", + "89.219.220.133", + "89.219.224.139", + "89.219.224.145", + "89.219.225.30", + "89.219.226.171", + "89.219.227.18", + "89.219.227.30", + "89.219.229.128", + "89.219.230.93", + "89.219.235.240", + "89.219.238.107", + "89.219.240.85", + "89.219.240.218", + "89.219.241.130", + "89.219.241.141", + "89.219.244.22", + "89.219.244.236", + "89.219.246.68", + "89.219.246.152", + "89.219.246.183", + "89.219.249.214", + "89.219.251.90", + "89.219.251.219", + "89.219.251.248", + "89.219.253.157", + "89.219.254.24", + "89.219.254.58", + "89.221.84.53", + "89.221.84.111", + "89.221.85.46", + "89.221.85.61", + "89.221.85.149", + "89.221.85.194", + "89.221.85.206", + "89.221.85.225", + "89.221.85.235", + "89.221.85.241", + "89.221.85.242", + "89.221.85.253", + "89.221.86.62", + "89.221.86.159", + "89.221.86.186", + "89.235.70.70", + "89.235.102.11", + "89.235.109.20", + "91.92.121.161", + "91.92.121.180", + "91.92.121.214", + "91.92.122.157", + "91.92.125.104", + "91.92.125.118", + "91.92.125.195", + "91.92.129.240", + "91.92.130.175", + "91.92.131.18", + "91.92.131.52", + "91.92.131.133", + "91.92.133.152", + "91.92.149.40", + "91.92.150.170", + "91.92.157.114", + "91.92.157.168", + "91.92.164.143", + "91.92.180.196", + "91.92.181.171", + "91.92.181.193", + "91.92.182.158", + "91.92.185.37", + "91.92.186.76", + "91.92.186.135", + "91.92.188.251", + "91.92.191.47", + "91.92.191.125", + "91.92.205.45", + "91.92.207.143", + "91.92.207.179", + "91.92.209.91", + "91.92.212.107", + "91.92.212.182", + "91.92.214.181", + "91.92.215.243", + "91.106.67.30", + "91.106.80.65", + "91.106.80.127", + "91.106.83.24", + "91.106.83.29", + "91.106.90.177", + "91.106.94.136", + "91.108.138.179", + "91.108.150.42", + "91.185.129.168", + "91.185.130.76", + "91.185.140.53", + "91.185.141.74", + "91.185.144.212", + "91.185.147.40", + "91.186.193.26", + "91.186.193.57", + "91.199.18.60", + "91.222.206.170", + "91.222.206.206", + "91.232.72.92", + "91.232.72.108", + "91.232.72.116", + "91.232.72.118", + "91.232.72.126", + "91.232.74.194", + "91.234.39.83", + "91.242.44.34", + "91.243.165.178", + "91.243.172.186", + "92.114.17.4", + "92.119.58.85", + "92.119.58.126", + "92.242.193.66", + "92.242.206.6", + "92.242.219.186", + "92.246.144.29", + "93.114.111.108", + "93.114.111.175", + "93.115.121.162", + "93.115.125.255", + "93.115.127.45", + "93.115.144.17", + "93.115.144.80", + "93.115.145.41", + "93.115.145.221", + "93.115.145.237", + "93.115.146.70", + "93.115.146.140", + "93.115.146.168", + "93.115.146.217", + "93.115.147.35", + "93.115.147.60", + "93.115.147.79", + "93.115.147.92", + "93.115.147.105", + "93.115.147.149", + "93.115.147.162", + "93.115.148.33", + "93.115.148.245", + "93.115.149.187", + "93.115.149.243", + "93.115.219.124", + "93.117.1.197", + "93.117.32.98", + "93.117.38.186", + "93.117.41.59", + "93.117.42.205", + "93.117.127.64", + "93.117.127.230", + "93.118.96.209", + "93.118.107.27", + "93.118.107.167", + "93.118.108.1", + "93.118.108.20", + "93.118.108.97", + "93.118.108.201", + "93.118.110.78", + "93.118.114.252", + "93.118.115.17", + "93.118.116.160", + "93.118.119.64", + "93.118.119.238", + "93.118.120.51", + "93.118.120.135", + "93.118.123.87", + "93.118.124.218", + "93.118.126.1", + "93.118.127.126", + "93.118.127.147", + "93.118.127.234", + "93.118.128.93", + "93.118.128.173", + "93.118.132.228", + "93.118.136.52", + "93.118.139.177", + "93.118.139.247", + "93.118.140.25", + "93.118.140.163", + "93.118.141.13", + "93.118.141.183", + "93.118.142.99", + "93.118.142.181", + "93.118.143.242", + "93.118.145.14", + "93.118.145.65", + "93.118.146.80", + "93.118.146.143", + "93.118.146.178", + "93.118.147.1", + "93.118.148.211", + "93.118.148.214", + "93.118.150.2", + "93.118.151.69", + "93.118.151.170", + "93.118.152.54", + "93.118.152.147", + "93.118.152.220", + "93.118.153.212", + "93.118.154.127", + "93.118.156.8", + "93.118.159.179", + "93.118.160.4", + "93.118.160.31", + "93.118.161.97", + "93.118.161.126", + "93.118.162.200", + "93.118.163.83", + "93.118.163.99", + "93.118.163.126", + "93.118.163.166", + "93.118.164.241", + "93.118.166.232", + "93.118.180.65", + "93.126.7.16", + "93.126.9.38", + "93.126.10.1", + "93.126.11.129", + "93.126.12.160", + "93.126.18.28", + "93.126.18.95", + "93.126.18.133", + "93.126.19.141", + "93.126.19.239", + "93.126.25.34", + "93.126.56.30", + "94.101.133.19", + "94.139.183.164", + "94.182.2.70", + "94.182.2.132", + "94.182.2.222", + "94.182.4.172", + "94.182.5.199", + "94.182.18.14", + "94.182.18.209", + "94.182.18.226", + "94.182.23.57", + "94.182.24.206", + "94.182.27.105", + "94.182.31.70", + "94.182.31.129", + "94.182.34.88", + "94.182.35.165", + "94.182.35.190", + "94.182.35.249", + "94.182.39.166", + "94.182.48.5", + "94.182.50.48", + "94.182.53.166", + "94.182.53.245", + "94.182.56.16", + "94.182.63.24", + "94.182.94.190", + "94.182.110.250", + "94.182.189.29", + "94.182.189.246", + "94.182.192.210", + "94.182.192.239", + "94.182.193.14", + "94.182.193.62", + "94.182.193.89", + "94.182.193.142", + "94.182.193.197", + "94.182.193.211", + "94.182.194.132", + "94.182.196.113", + "94.182.196.165", + "94.182.197.49", + "94.182.197.50", + "94.182.200.10", + "94.182.202.193", + "94.182.211.97", + "94.182.211.247", + "94.182.214.205", + "94.182.216.18", + "94.182.216.205", + "94.182.225.225", + "94.183.0.137", + "94.183.0.195", + "94.183.1.160", + "94.183.6.134", + "94.183.7.152", + "94.183.7.159", + "94.183.19.200", + "94.183.21.110", + "94.183.23.251", + "94.183.24.50", + "94.183.27.70", + "94.183.27.128", + "94.183.27.173", + "94.183.27.209", + "94.183.28.216", + "94.183.30.24", + "94.183.30.72", + "94.183.30.131", + "94.183.30.132", + "94.183.30.162", + "94.183.30.197", + "94.183.31.117", + "94.183.32.24", + "94.183.32.107", + "94.183.46.99", + "94.183.60.235", + "94.183.62.94", + "94.183.62.120", + "94.183.67.63", + "94.183.67.107", + "94.183.71.93", + "94.183.72.37", + "94.183.72.133", + "94.183.75.164", + "94.183.83.164", + "94.183.85.12", + "94.183.89.83", + "94.183.90.176", + "94.183.91.88", + "94.183.91.145", + "94.183.92.218", + "94.183.93.40", + "94.183.97.3", + "94.183.97.61", + "94.183.99.183", + "94.183.102.135", + "94.183.103.185", + "94.183.109.61", + "94.183.116.21", + "94.183.118.4", + "94.183.118.45", + "94.183.121.12", + "94.183.123.138", + "94.183.124.229", + "94.183.126.175", + "94.183.127.101", + "94.183.127.111", + "94.183.127.124", + "94.183.131.36", + "94.183.133.129", + "94.183.145.252", + "94.183.149.144", + "94.183.160.229", + "94.184.21.255", + "94.184.178.254", + "94.232.173.145", + "94.232.175.245", + "95.38.11.5", + "95.38.33.72", + "95.38.33.190", + "95.38.34.61", + "95.38.46.178", + "95.38.48.195", + "95.38.48.247", + "95.38.49.44", + "95.38.49.132", + "95.38.49.150", + "95.38.49.158", + "95.38.52.10", + "95.38.52.44", + "95.38.52.224", + "95.38.66.32", + "95.38.66.42", + "95.38.67.74", + "95.38.67.163", + "95.38.78.14", + "95.38.132.238", + "95.38.135.134", + "95.38.143.22", + "95.38.148.64", + "95.38.149.46", + "95.38.149.51", + "95.38.149.96", + "95.38.169.186", + "95.38.192.58", + "95.38.192.133", + "95.38.192.135", + "95.38.192.178", + "95.38.196.157", + "95.38.196.169", + "95.38.196.202", + "95.38.196.243", + "95.38.199.230", + "95.80.141.50", + "95.80.168.237", + "95.80.171.57", + "95.80.175.41", + "95.130.56.177", + "95.130.59.34", + "95.130.225.119", + "95.130.225.124", + "95.130.225.186", + "95.130.225.229", + "95.130.225.239", + "95.130.225.241", + "95.156.252.19", + "95.215.160.179", + "103.215.220.90", + "109.72.197.58", + "109.95.71.36", + "109.109.44.37", + "109.109.44.172", + "109.109.52.175", + "109.122.235.120", + "109.122.237.78", + "109.125.129.234", + "109.125.130.85", + "109.125.132.133", + "109.125.132.208", + "109.125.132.231", + "109.125.134.92", + "109.125.135.16", + "109.125.140.186", + "109.125.147.46", + "109.125.160.46", + "109.125.168.20", + "109.201.13.188", + "109.201.15.9", + "109.230.82.114", + "109.230.83.233", + "109.230.89.157", + "109.230.90.106", + "109.230.90.174", + "109.230.93.162", + "109.230.94.222", + "109.230.94.246", + "109.230.204.66", + "109.230.204.91", + "109.230.204.154", + "109.230.204.174", + "109.230.204.175", + "109.232.2.63", + "109.238.189.4", + "109.238.189.50", + "128.65.176.22", + "128.65.179.91", + "128.65.180.193", + "128.65.183.63", + "128.65.184.21", + "128.65.184.26", + "128.65.184.184", + "128.65.188.19", + "128.65.188.30", + "128.65.189.79", + "151.232.3.161", + "151.232.48.49", + "151.232.60.225", + "151.232.101.57", + "151.232.207.122", + "151.233.2.157", + "151.233.18.251", + "151.233.47.165", + "151.233.50.228", + "151.233.53.38", + "151.233.58.9", + "151.234.0.199", + "151.234.1.48", + "151.234.2.254", + "151.234.3.120", + "151.234.23.25", + "151.234.28.19", + "151.234.31.125", + "151.234.31.243", + "151.234.72.243", + "151.234.87.99", + "151.234.125.227", + "151.234.187.125", + "151.234.211.218", + "151.234.245.230", + "151.235.1.157", + "151.235.6.95", + "151.235.7.132", + "151.235.8.27", + "151.235.8.47", + "151.235.23.185", + "151.235.32.61", + "151.235.35.236", + "151.235.38.166", + "151.235.91.218", + "151.235.95.23", + "151.235.98.215", + "151.235.99.142", + "151.235.99.169", + "151.235.101.1", + "151.235.101.23", + "151.235.102.98", + "151.235.106.149", + "151.235.106.162", + "151.235.107.85", + "151.235.108.124", + "151.235.109.61", + "151.235.109.246", + "151.235.110.118", + "151.235.110.157", + "151.235.111.147", + "151.235.112.216", + "151.235.114.9", + "151.235.115.208", + "151.235.118.241", + "151.235.120.110", + "151.235.121.214", + "151.235.123.9", + "151.235.126.249", + "151.235.129.206", + "151.235.134.175", + "151.235.185.215", + "151.235.187.44", + "151.235.190.76", + "151.235.217.72", + "151.235.240.128", + "151.235.246.224", + "151.235.250.125", + "159.20.96.249", + "159.20.103.83", + "159.20.111.37", + "171.22.25.51", + "176.65.184.254", + "176.65.224.62", + "176.65.226.105", + "176.65.226.111", + "176.65.227.214", + "176.65.228.94", + "176.65.231.45", + "176.65.241.43", + "176.65.241.44", + "176.65.241.45", + "176.65.241.46", + "176.65.252.69", + "176.65.252.81", + "176.65.252.227", + "176.65.253.46", + "176.65.253.55", + "176.65.253.106", + "176.65.253.200", + "176.65.254.61", + "176.65.254.112", + "176.65.254.160", + "176.122.210.113", + "176.223.80.171", + "176.223.83.46", + "178.131.66.247", + "178.131.72.214", + "178.131.101.153", + "178.131.113.81", + "178.131.134.93", + "178.131.135.230", + "178.173.131.62", + "178.173.131.163", + "178.173.131.188", + "178.173.137.44", + "178.173.137.132", + "178.173.137.249", + "178.173.137.251", + "178.173.138.33", + "178.173.138.59", + "178.173.138.157", + "178.173.138.171", + "178.173.138.255", + "178.173.139.95", + "178.173.140.171", + "178.173.141.90", + "178.173.143.103", + "178.173.143.116", + "178.173.143.161", + "178.173.146.129", + "178.173.147.73", + "178.173.147.227", + "178.173.161.10", + "178.173.163.7", + "178.173.163.158", + "178.173.163.202", + "178.173.169.16", + "178.173.169.199", + "178.173.169.228", + "178.173.170.142", + "178.173.173.88", + "178.173.173.255", + "178.173.174.48", + "178.173.174.136", + "178.173.174.229", + "178.173.175.6", + "178.173.175.56", + "178.173.175.110", + "178.173.175.152", + "178.173.175.209", + "178.173.182.45", + "178.173.182.72", + "178.173.182.87", + "178.173.182.148", + "178.173.182.177", + "178.173.184.37", + "178.173.184.112", + "178.173.184.122", + "178.173.185.11", + "178.173.185.49", + "178.173.185.86", + "178.173.185.222", + "178.173.185.241", + "178.173.192.30", + "178.173.193.32", + "178.173.193.54", + "178.173.193.77", + "178.173.193.82", + "178.173.195.75", + "178.173.195.203", + "178.173.196.39", + "178.173.196.161", + "178.173.197.84", + "178.173.197.192", + "178.173.197.228", + "178.173.198.82", + "178.173.198.112", + "178.173.198.238", + "178.173.199.32", + "178.173.199.53", + "178.173.199.105", + "178.173.199.109", + "178.173.199.113", + "178.173.199.200", + "178.173.200.68", + "178.173.210.19", + "178.173.210.29", + "178.173.210.152", + "178.173.210.176", + "178.173.211.216", + "178.173.212.43", + "178.173.212.126", + "178.173.212.209", + "178.173.213.152", + "178.173.213.211", + "178.173.213.226", + "178.173.214.151", + "178.173.214.164", + "178.173.215.117", + "178.173.215.158", + "178.173.215.208", + "178.173.215.210", + "178.173.216.5", + "178.173.216.75", + "178.173.216.112", + "178.173.217.59", + "178.173.218.46", + "178.173.219.53", + "178.173.219.114", + "178.173.220.21", + "178.173.220.47", + "178.173.220.206", + "178.173.221.227", + "178.173.222.165", + "178.236.98.110", + "178.236.99.12", + "178.236.99.239", + "178.236.100.37", + "178.236.103.15", + "178.236.103.18", + "178.236.103.127", + "178.236.104.163", + "178.236.105.85", + "178.236.105.154", + "178.236.106.237", + "178.236.107.152", + "178.236.108.124", + "178.236.111.3", + "178.236.111.114", + "178.238.195.238", + "178.239.144.40", + "178.239.156.36", + "178.239.156.113", + "178.239.156.130", + "178.239.156.133", + "178.239.157.98", + "178.239.157.100", + "178.239.157.101", + "178.239.157.102", + "178.239.157.103", + "178.239.157.104", + "178.252.141.229", + "178.252.147.125", + "178.252.151.158", + "178.252.152.72", + "178.252.153.250", + "178.252.171.227", + "178.252.171.230", + "178.252.177.32", + "178.252.177.33", + "178.252.177.35", + "178.252.177.36", + "178.252.177.37", + "178.252.177.38", + "178.252.177.39", + "178.252.177.40", + "178.252.177.42", + "178.252.177.43", + "178.252.177.44", + "178.252.177.45", + "185.3.201.0", + "185.3.201.1", + "185.3.201.2", + "185.3.201.3", + "185.3.201.4", + "185.3.201.5", + "185.3.201.6", + "185.3.201.7", + "185.3.201.8", + "185.3.201.9", + "185.3.201.10", + "185.3.201.11", + "185.3.201.12", + "185.3.201.13", + "185.3.201.14", + "185.3.201.15", + "185.3.201.16", + "185.3.201.17", + "185.3.201.18", + "185.3.201.19", + "185.3.201.20", + "185.3.201.21", + "185.3.201.22", + "185.3.201.23", + "185.3.201.24", + "185.3.201.25", + "185.3.201.26", + "185.3.201.27", + "185.3.201.28", + "185.3.201.29", + "185.3.201.31", + "185.3.201.32", + "185.3.201.33", + "185.3.201.35", + "185.3.201.36", + "185.3.201.38", + "185.3.201.40", + "185.3.201.41", + "185.3.201.42", + "185.3.201.43", + "185.3.201.44", + "185.3.201.45", + "185.3.201.46", + "185.3.201.47", + "185.3.201.48", + "185.3.201.49", + "185.3.201.51", + "185.3.201.52", + "185.3.201.53", + "185.3.201.55", + "185.3.201.56", + "185.3.201.57", + "185.3.201.58", + "185.3.201.59", + "185.3.201.60", + "185.3.201.61", + "185.3.201.62", + "185.3.201.63", + "185.3.201.64", + "185.3.201.65", + "185.3.201.66", + "185.3.201.67", + "185.3.201.68", + "185.3.201.69", + "185.3.201.70", + "185.3.201.71", + "185.3.201.72", + "185.3.201.73", + "185.3.201.74", + "185.3.201.75", + "185.3.201.76", + "185.3.201.77", + "185.3.201.78", + "185.3.201.79", + "185.3.201.80", + "185.3.201.81", + "185.3.201.82", + "185.3.201.83", + "185.3.201.84", + "185.3.201.85", + "185.3.201.86", + "185.3.201.87", + "185.3.201.88", + "185.3.201.89", + "185.3.201.90", + "185.3.201.91", + "185.3.201.92", + "185.3.201.93", + "185.3.201.94", + "185.3.201.95", + "185.3.201.96", + "185.3.201.97", + "185.3.201.98", + "185.3.201.99", + "185.3.201.100", + "185.3.201.101", + "185.3.201.102", + "185.3.201.103", + "185.3.201.104", + "185.3.201.105", + "185.3.201.106", + "185.3.201.107", + "185.3.201.109", + "185.3.201.112", + "185.3.201.114", + "185.3.201.115", + "185.3.201.116", + "185.3.201.117", + "185.3.201.118", + "185.3.201.120", + "185.3.201.121", + "185.3.201.122", + "185.3.201.123", + "185.3.201.124", + "185.3.201.125", + "185.3.201.126", + "185.3.201.127", + "185.3.201.129", + "185.3.201.130", + "185.3.201.131", + "185.3.201.132", + "185.3.201.133", + "185.3.201.134", + "185.3.201.135", + "185.3.202.0", + "185.3.202.1", + "185.3.202.2", + "185.3.202.8", + "185.3.202.10", + "185.3.202.11", + "185.3.202.12", + "185.3.202.13", + "185.3.202.15", + "185.3.202.16", + "185.3.202.17", + "185.3.202.18", + "185.3.202.19", + "185.3.202.20", + "185.3.202.21", + "185.3.202.22", + "185.3.202.23", + "185.3.202.24", + "185.3.202.27", + "185.3.202.28", + "185.3.202.29", + "185.3.202.30", + "185.3.202.31", + "185.3.202.32", + "185.3.202.33", + "185.3.202.34", + "185.3.202.35", + "185.3.202.37", + "185.3.202.38", + "185.3.202.39", + "185.3.202.40", + "185.3.202.41", + "185.3.202.43", + "185.3.202.44", + "185.3.202.45", + "185.3.202.47", + "185.3.202.48", + "185.3.202.49", + "185.3.202.50", + "185.3.202.51", + "185.3.202.52", + "185.3.202.53", + "185.3.202.54", + "185.3.202.55", + "185.3.202.56", + "185.3.202.58", + "185.3.202.60", + "185.3.202.61", + "185.3.202.62", + "185.3.202.64", + "185.3.202.65", + "185.3.202.66", + "185.3.202.67", + "185.3.202.68", + "185.3.202.69", + "185.3.202.70", + "185.3.202.71", + "185.3.202.73", + "185.3.202.75", + "185.3.202.76", + "185.3.202.77", + "185.3.202.78", + "185.3.202.79", + "185.3.202.81", + "185.3.202.82", + "185.3.202.83", + "185.3.202.84", + "185.3.202.85", + "185.3.202.86", + "185.3.202.88", + "185.3.202.89", + "185.3.202.90", + "185.3.202.91", + "185.3.202.92", + "185.3.202.93", + "185.3.202.94", + "185.3.202.95", + "185.3.202.97", + "185.3.202.98", + "185.3.202.101", + "185.3.202.102", + "185.3.202.103", + "185.3.202.104", + "185.3.202.105", + "185.3.202.106", + "185.3.202.107", + "185.3.202.109", + "185.3.202.110", + "185.3.202.111", + "185.3.202.112", + "185.3.202.113", + "185.3.202.114", + "185.3.202.117", + "185.3.202.118", + "185.3.202.119", + "185.3.202.120", + "185.3.202.121", + "185.3.202.122", + "185.3.202.123", + "185.3.202.124", + "185.3.202.126", + "185.3.202.127", + "185.3.202.128", + "185.3.202.130", + "185.3.202.131", + "185.3.202.132", + "185.3.202.133", + "185.3.202.134", + "185.3.202.135", + "185.3.202.136", + "185.3.202.137", + "185.3.202.138", + "185.3.202.139", + "185.3.202.140", + "185.3.202.141", + "185.3.202.142", + "185.3.202.145", + "185.3.202.146", + "185.3.202.147", + "185.3.202.149", + "185.3.202.150", + "185.3.202.151", + "185.3.202.152", + "185.3.202.153", + "185.3.202.155", + "185.3.202.156", + "185.3.202.157", + "185.3.202.161", + "185.3.202.163", + "185.3.202.164", + "185.3.202.165", + "185.3.202.166", + "185.3.202.167", + "185.3.202.168", + "185.3.202.169", + "185.3.202.170", + "185.3.202.171", + "185.3.202.173", + "185.3.202.175", + "185.3.202.176", + "185.3.202.177", + "185.3.202.178", + "185.3.202.179", + "185.3.202.181", + "185.3.202.182", + "185.3.202.183", + "185.3.202.185", + "185.3.202.186", + "185.3.202.189", + "185.3.202.190", + "185.3.202.191", + "185.3.202.192", + "185.3.202.194", + "185.3.202.195", + "185.3.202.196", + "185.3.202.197", + "185.3.202.198", + "185.3.202.199", + "185.3.202.200", + "185.3.202.201", + "185.3.202.202", + "185.3.202.203", + "185.3.202.204", + "185.3.202.205", + "185.3.202.206", + "185.3.202.207", + "185.3.202.208", + "185.3.202.209", + "185.3.202.210", + "185.3.202.211", + "185.3.202.212", + "185.3.202.213", + "185.3.202.214", + "185.3.202.215", + "185.3.202.217", + "185.3.202.218", + "185.3.202.219", + "185.3.202.220", + "185.3.202.222", + "185.3.202.224", + "185.3.202.225", + "185.3.202.226", + "185.3.202.227", + "185.3.202.228", + "185.3.202.229", + "185.3.202.230", + "185.3.202.231", + "185.3.202.232", + "185.3.202.233", + "185.3.202.234", + "185.3.202.235", + "185.3.202.236", + "185.3.202.237", + "185.3.202.238", + "185.3.202.239", + "185.3.202.240", + "185.3.202.241", + "185.3.202.242", + "185.3.202.243", + "185.3.202.244", + "185.3.202.245", + "185.3.202.246", + "185.3.202.247", + "185.3.202.248", + "185.3.202.249", + "185.3.202.250", + "185.3.202.252", + "185.3.202.253", + "185.3.202.254", + "185.3.203.8", + "185.3.203.9", + "185.3.203.11", + "185.3.203.13", + "185.3.203.15", + "185.3.203.36", + "185.3.203.37", + "185.3.203.38", + "185.3.203.39", + "185.3.203.40", + "185.3.203.41", + "185.3.203.42", + "185.3.203.43", + "185.3.203.44", + "185.3.203.45", + "185.3.203.46", + "185.3.203.48", + "185.3.203.50", + "185.3.203.51", + "185.3.203.52", + "185.3.203.53", + "185.3.203.54", + "185.3.203.57", + "185.3.203.60", + "185.3.203.61", + "185.3.203.62", + "185.3.203.64", + "185.3.203.65", + "185.3.203.66", + "185.3.203.67", + "185.3.203.69", + "185.3.203.70", + "185.3.203.71", + "185.3.203.72", + "185.3.203.73", + "185.3.203.74", + "185.3.203.75", + "185.3.203.76", + "185.3.203.78", + "185.3.203.79", + "185.3.203.80", + "185.3.203.81", + "185.3.203.82", + "185.3.203.83", + "185.3.203.86", + "185.3.203.87", + "185.3.203.88", + "185.3.203.89", + "185.3.203.90", + "185.3.203.94", + "185.3.203.95", + "185.3.203.96", + "185.3.203.97", + "185.3.203.98", + "185.3.203.99", + "185.3.203.101", + "185.3.203.103", + "185.3.203.104", + "185.3.203.105", + "185.3.203.106", + "185.3.203.107", + "185.3.203.108", + "185.3.203.109", + "185.3.203.110", + "185.3.203.111", + "185.3.203.113", + "185.3.203.114", + "185.3.203.115", + "185.3.203.116", + "185.3.203.119", + "185.3.203.120", + "185.3.203.121", + "185.3.203.122", + "185.3.203.123", + "185.3.203.124", + "185.3.203.125", + "185.3.203.127", + "185.14.80.238", + "185.18.213.184", + "185.20.163.2", + "185.21.68.25", + "185.22.31.186", + "185.42.225.202", + "185.42.225.203", + "185.42.226.217", + "185.44.113.194", + "185.46.108.90", + "185.46.108.100", + "185.46.110.117", + "185.46.218.30", + "185.46.218.233", + "185.49.96.145", + "185.49.97.109", + "185.49.97.238", + "185.53.141.35", + "185.53.143.185", + "185.56.92.177", + "185.56.93.73", + "185.56.93.121", + "185.66.226.12", + "185.66.228.17", + "185.71.195.160", + "185.72.27.113", + "185.72.27.114", + "185.72.27.182", + "185.72.80.60", + "185.72.81.119", + "185.83.88.119", + "185.83.88.208", + "185.83.182.89", + "185.83.196.247", + "185.86.183.33", + "185.86.183.217", + "185.92.41.251", + "185.100.45.101", + "185.100.47.205", + "185.103.87.182", + "185.103.129.198", + "185.103.131.129", + "185.106.137.91", + "185.106.138.219", + "185.106.146.146", + "185.109.60.222", + "185.109.63.58", + "185.109.63.59", + "185.109.63.60", + "185.109.63.61", + "185.109.63.146", + "185.109.80.243", + "185.109.251.165", + "185.110.219.78", + "185.110.219.88", + "185.110.253.9", + "185.111.81.2", + "185.112.36.123", + "185.112.36.172", + "185.112.36.255", + "185.112.37.174", + "185.112.38.213", + "185.112.39.14", + "185.112.39.30", + "185.112.39.181", + "185.115.150.52", + "185.115.169.71", + "185.115.169.95", + "185.115.169.101", + "185.115.169.104", + "185.115.169.193", + "185.115.169.220", + "185.117.139.165", + "185.117.139.168", + "185.117.139.169", + "185.117.139.173", + "185.119.164.1", + "185.120.219.38", + "185.120.230.33", + "185.120.237.47", + "185.120.238.5", + "185.121.129.166", + "185.126.2.134", + "185.126.4.33", + "185.126.6.3", + "185.126.10.9", + "185.126.10.33", + "185.126.11.241", + "185.128.138.138", + "185.129.199.5", + "185.129.199.129", + "185.129.199.201", + "185.129.216.1", + "185.129.216.115", + "185.129.216.151", + "185.129.217.142", + "185.131.30.5", + "185.131.30.145", + "185.131.30.163", + "185.131.30.246", + "185.131.103.238", + "185.132.80.141", + "185.132.80.188", + "185.134.99.46", + "185.136.174.247", + "185.136.192.65", + "185.136.194.35", + "185.136.194.37", + "185.136.194.40", + "185.136.194.41", + "185.136.194.234", + "185.137.25.75", + "185.137.25.208", + "185.137.108.87", + "185.141.36.130", + "185.141.39.45", + "185.142.95.10", + "185.145.184.159", + "185.147.40.12", + "185.147.41.75", + "185.147.41.88", + "185.147.161.27", + "185.147.161.30", + "185.155.10.129", + "185.155.10.178", + "185.155.10.202", + "185.155.11.23", + "185.155.14.246", + "185.155.15.164", + "185.161.39.174", + "185.161.112.1", + "185.161.112.5", + "185.161.112.8", + "185.161.112.38", + "185.161.112.41", + "185.161.114.190", + "185.162.42.113", + "185.164.73.155", + "185.169.20.113", + "185.170.239.185", + "185.172.2.162", + "185.172.68.18", + "185.172.68.66", + "185.172.68.98", + "185.172.68.100", + "185.172.68.101", + "185.172.68.103", + "185.172.68.246", + "185.172.71.115", + "185.172.213.123", + "185.174.132.180", + "185.174.132.243", + "185.176.59.227", + "185.177.158.84", + "185.177.159.231", + "185.179.168.242", + "185.179.171.216", + "185.180.130.229", + "185.180.131.223", + "185.191.76.116", + "185.191.76.119", + "185.191.78.184", + "185.193.209.34", + "185.193.209.35", + "185.193.209.36", + "185.193.209.39", + "185.193.209.40", + "185.193.209.41", + "185.193.209.42", + "185.193.209.43", + "185.193.209.46", + "185.208.148.19", + "185.213.165.116", + "185.221.194.141", + "185.221.194.214", + "185.224.177.117", + "185.224.179.59", + "185.225.80.200", + "185.225.83.200", + "185.225.243.82", + "185.225.243.85", + "185.225.243.92", + "185.225.243.93", + "185.229.31.60", + "185.235.197.122", + "185.235.197.210", + "185.235.197.218", + "185.237.84.18", + "185.237.87.97", + "185.237.87.228", + "185.255.208.31", + "185.255.210.156", + "185.255.210.160", + "185.255.210.168", + "185.255.210.194", + "188.0.252.93", + "188.75.64.118", + "188.75.65.218", + "188.75.65.219", + "188.75.65.220", + "188.75.65.221", + "188.121.128.138", + "188.121.129.219", + "188.121.144.154", + "188.121.145.94", + "188.121.147.246", + "188.121.150.39", + "188.121.157.50", + "188.121.159.178", + "188.136.133.81", + "188.136.133.82", + "188.136.133.83", + "188.136.142.185", + "188.136.143.166", + "188.136.144.98", + "188.136.144.109", + "188.136.152.9", + "188.136.152.15", + "188.136.152.100", + "188.136.156.1", + "188.136.156.2", + "188.136.156.3", + "188.136.156.4", + "188.136.156.6", + "188.136.156.9", + "188.136.156.15", + "188.136.156.18", + "188.136.156.20", + "188.136.156.21", + "188.136.156.22", + "188.136.156.23", + "188.136.156.24", + "188.136.156.26", + "188.136.156.27", + "188.136.156.28", + "188.136.156.29", + "188.136.162.118", + "188.136.172.48", + "188.136.174.223", + "188.136.194.198", + "188.136.208.249", + "188.208.57.96", + "188.208.57.189", + "188.208.62.218", + "188.209.9.21", + "188.209.9.46", + "188.209.14.56", + "188.209.14.143", + "188.209.14.176", + "188.209.24.18", + "188.209.24.179", + "188.209.36.183", + "188.209.39.46", + "188.209.41.118", + "188.209.43.141", + "188.209.44.22", + "188.209.44.25", + "188.209.64.53", + "188.209.64.97", + "188.209.65.5", + "188.209.65.16", + "188.209.66.78", + "188.209.70.28", + "188.209.71.132", + "188.209.75.85", + "188.209.77.106", + "188.209.79.35", + "188.209.79.214", + "188.210.80.12", + "188.210.80.133", + "188.210.82.71", + "188.210.83.33", + "188.210.85.91", + "188.210.85.158", + "188.210.85.198", + "188.210.85.235", + "188.210.86.7", + "188.210.86.167", + "188.210.87.171", + "188.210.87.228", + "188.210.97.73", + "188.210.102.225", + "188.210.103.74", + "188.210.104.175", + "188.210.110.19", + "188.210.112.48", + "188.210.112.62", + "188.210.112.148", + "188.210.113.85", + "188.210.118.27", + "188.210.120.214", + "188.210.121.198", + "188.210.123.34", + "188.210.153.243", + "188.210.160.92", + "188.210.173.224", + "188.211.32.154", + "188.211.36.157", + "188.211.48.173", + "188.211.53.136", + "188.211.53.150", + "188.211.57.184", + "188.211.59.63", + "188.211.61.93", + "188.211.61.110", + "188.211.61.148", + "188.211.62.19", + "188.211.63.80", + "188.211.66.239", + "188.211.69.229", + "188.211.74.122", + "188.211.74.173", + "188.211.74.230", + "188.211.77.214", + "188.211.78.196", + "188.211.80.11", + "188.211.81.41", + "188.211.81.72", + "188.211.82.229", + "188.211.84.82", + "188.211.84.110", + "188.211.86.57", + "188.211.86.140", + "188.211.86.142", + "188.211.87.47", + "188.211.92.127", + "188.211.95.111", + "188.211.100.21", + "188.211.100.255", + "188.211.104.251", + "188.211.106.164", + "188.211.108.44", + "188.211.109.83", + "188.211.111.253", + "188.211.114.45", + "188.211.118.152", + "188.211.119.147", + "188.211.121.217", + "188.211.123.8", + "188.211.123.252", + "188.211.124.104", + "188.211.124.169", + "188.211.124.189", + "188.211.125.201", + "188.211.127.59", + "188.212.73.15", + "188.212.81.13", + "188.212.82.127", + "188.212.85.27", + "188.212.90.17", + "188.212.144.128", + "188.212.144.224", + "188.212.145.97", + "188.212.145.143", + "188.212.148.5", + "188.212.149.44", + "188.212.245.103", + "188.213.147.209", + "188.213.148.89", + "188.213.150.39", + "188.213.155.38", + "188.213.157.159", + "188.213.158.155", + "188.213.159.229", + "188.213.177.216", + "188.213.181.60", + "188.213.183.209", + "188.213.185.56", + "188.213.188.237", + "188.213.209.185", + "188.214.187.38", + "188.240.212.0", + "188.240.212.1", + "188.240.212.2", + "188.240.212.3", + "188.240.212.48", + "188.240.212.49", + "188.240.212.50", + "188.240.212.51", + "188.240.212.52", + "188.240.212.53", + "188.240.212.54", + "188.240.212.55", + "188.240.254.107", + "193.24.120.212", + "193.56.61.180", + "193.134.100.5", + "193.134.100.6", + "193.134.100.7", + "193.134.100.8", + "193.134.100.9", + "193.134.100.10", + "193.134.100.11", + "193.134.100.26", + "193.134.100.27", + "193.134.100.116", + "193.134.100.117", + "193.134.100.118", + "193.151.157.201", + "193.200.148.28", + "193.200.148.79", + "193.228.91.116", + "193.228.168.209", + "193.228.169.115", + "193.228.169.116", + "193.228.169.165", + "194.9.56.64", + "194.9.57.12", + "194.9.57.139", + "194.33.105.89", + "194.36.174.129", + "194.60.231.237", + "194.147.164.139", + "194.147.165.188", + "194.150.68.122", + "194.150.70.218", + "194.150.70.219", + "194.150.70.221", + "194.150.71.157", + "194.225.101.4", + "194.225.101.8", + "194.225.101.10", + "194.225.101.23", + "194.225.101.25", + "194.225.101.28", + "194.225.101.29", + "194.225.101.33", + "194.225.136.233", + "194.225.149.241", + "195.8.114.144", + "195.88.189.60", + "195.146.59.174", + "195.146.59.175", + "195.181.1.32", + "195.181.3.10", + "195.181.11.166", + "195.181.11.227", + "195.181.13.168", + "195.181.26.210", + "195.181.27.26", + "195.181.27.67", + "195.181.30.106", + "195.181.30.206", + "195.181.37.54", + "195.181.39.210", + "195.181.40.4", + "195.181.40.5", + "195.181.58.162", + "195.181.86.242", + "195.181.114.229", + "212.16.64.129", + "212.16.86.176", + "212.33.210.119", + "212.33.218.42", + "212.80.19.122", + "212.80.19.137", + "212.86.73.128", + "212.86.75.15", + "212.86.94.173", + "212.120.194.186", + "212.120.196.162", + "212.120.198.180", + "212.120.199.78", + "212.120.200.252", + "212.120.215.52", + "212.120.216.120", + "212.120.220.62", + "212.120.222.90", + "212.120.223.134", + "213.109.199.99", + "213.109.248.89", + "213.109.248.198", + "213.109.250.70", + "213.109.251.121", + "213.109.251.128", + "213.176.122.52", + "213.176.122.93", + "213.195.56.65", + "213.207.204.105", + "213.207.204.106", + "217.11.27.141", + "217.11.27.142", + "217.11.29.254", + "217.24.144.199", + "217.24.148.89", + "217.24.152.78", + "217.24.155.243", + "217.24.157.45", + "217.60.241.77", + "217.66.213.130", + "217.146.222.230", + "217.170.246.241", + "217.170.251.124", + "217.170.254.224", + "217.170.254.225", + "217.170.254.239", + "217.170.255.2", + "217.172.104.107", + "217.172.104.155", + "217.172.105.44", + "217.172.105.112", + "217.172.107.99", + "217.172.110.58", + "217.172.124.7", + "217.218.33.138", + "217.218.33.139", + "217.218.44.62", + "217.218.56.114", + "217.218.60.89", + "217.218.60.95", + "217.218.65.22", + "217.218.68.93", + "217.218.72.159", + "217.218.77.64", + "217.218.87.32", + "217.218.88.43", + "217.218.118.210", + "217.218.121.141", + "217.218.121.183", + "217.218.125.178", + "217.218.126.239", + "217.218.129.118", + "217.218.137.200", + "217.218.138.96", + "217.218.164.81", + "217.218.194.163", + "217.218.195.27", + "217.218.219.157", + "217.218.223.149", + "217.218.224.38", + "217.218.224.212", + "217.218.229.234", + "217.218.229.236", + "217.218.231.95", + "217.218.235.107", + "217.218.242.10", + "217.218.248.213", + "217.218.253.81", + "217.219.27.59", + "217.219.31.73", + "217.219.34.42", + "217.219.34.49", + "217.219.35.99", + "217.219.39.119", + "217.219.45.4", + "217.219.45.18", + "217.219.45.19", + "217.219.66.192", + "217.219.66.194", + "217.219.66.195", + "217.219.66.196", + "217.219.66.231", + "217.219.66.233", + "217.219.66.234", + "217.219.66.235", + "217.219.66.236", + "217.219.66.237", + "217.219.66.238", + "217.219.66.239", + "217.219.71.186", + "217.219.71.190", + "217.219.72.11", + "217.219.75.225", + "217.219.81.66", + "217.219.82.195", + "217.219.113.141", + "217.219.131.139", + "217.219.146.65", + "217.219.146.71", + "217.219.146.72", + "217.219.146.74", + "217.219.146.117", + "217.219.156.146", + "217.219.163.2", + "217.219.163.12", + "217.219.163.20", + "217.219.163.22", + "217.219.163.29", + "217.219.163.58", + "217.219.163.151", + "217.219.163.162", + "217.219.163.173", + "217.219.173.237", + "217.219.199.51", + "217.219.213.28", + "217.219.213.125", + "217.219.221.147", + "217.219.222.194", + "217.219.224.130", + "217.219.236.215", + "217.219.237.17", + "217.219.238.116", + "217.219.239.69", + "217.219.242.142", + "217.219.242.195", + "217.219.245.124", + "217.219.245.171" + ], + "IT": [ + "95.141.39.218" + ], + "KE": [ + "102.22.108.2", + "102.22.108.13", + "102.22.108.14", + "102.22.108.20", + "102.22.108.21", + "102.22.108.22", + "102.22.108.23", + "102.22.108.25", + "102.22.109.1", + "102.22.109.225", + "102.23.136.215" + ], + "NL": [ + "80.80.80.80", + "80.80.81.81", + "146.70.86.142" + ], + "RU": [ + "77.88.8.8", + "77.88.8.1", + "185.234.14.114", + "185.234.14.34", + "185.234.14.43", + "185.234.14.65", + "185.234.14.94", + "185.234.14.101", + "185.234.14.102", + "185.234.14.111", + "185.234.14.129", + "185.234.14.137", + "194.50.169.217" + ], + "SE": [ + "192.36.148.17" + ], + "SL": [ + "102.23.140.2", + "102.23.140.42" + ], + "SZ": [ + "102.23.132.42", + "102.23.133.10", + "102.23.133.12" + ], + "TR": [ + "103.130.146.35", + "103.130.146.36", + "103.130.146.37", + "103.130.146.43", + "103.130.146.195", + "103.130.146.198", + "103.130.146.206", + "103.130.146.235", + "103.130.146.238", + "185.220.226.0", + "185.220.226.1", + "185.220.226.2", + "185.220.226.3", + "185.220.226.4", + "185.220.226.5", + "185.220.226.6", + "185.220.226.7", + "185.220.226.8", + "185.220.226.10", + "185.220.226.11", + "185.220.226.12", + "185.220.226.13", + "185.220.226.15", + "185.220.226.17", + "185.220.226.18", + "185.220.226.19", + "185.220.226.21", + "185.220.226.24", + "185.220.226.25", + "185.220.226.26", + "185.220.226.27", + "185.220.226.29", + "185.220.226.30", + "185.220.226.31", + "185.220.226.33", + "185.220.226.34", + "185.220.226.35", + "185.220.226.36", + "185.220.226.37", + "185.220.226.38", + "185.220.226.39", + "185.220.226.40", + "185.220.226.42", + "185.220.226.43", + "185.220.226.44", + "185.220.226.45", + "185.220.226.46", + "185.220.226.47", + "185.220.226.48", + "185.220.226.49", + "185.220.226.50", + "185.220.226.51", + "185.220.226.52", + "185.220.226.53", + "185.220.226.54", + "185.220.226.55", + "185.220.226.56", + "185.220.226.57", + "185.220.226.58", + "185.220.226.59", + "185.220.226.60", + "185.220.226.61", + "185.220.226.62", + "185.220.226.63", + "185.220.226.64", + "185.220.226.65", + "185.220.226.66", + "185.220.226.68", + "185.220.226.69", + "185.220.226.70", + "185.220.226.71", + "185.220.226.72", + "185.220.226.73", + "185.220.226.75", + "185.220.226.76", + "185.220.226.77", + "185.220.226.78", + "185.220.226.79", + "185.220.226.80", + "185.220.226.81", + "185.220.226.83", + "185.220.226.84", + "185.220.226.85", + "185.220.226.86", + "185.220.226.87", + "185.220.226.88", + "185.220.226.89", + "185.220.226.90", + "185.220.226.92", + "185.220.226.93", + "185.220.226.94", + "185.220.226.95", + "185.220.226.96", + "185.220.226.97", + "185.220.226.98", + "185.220.226.99", + "185.220.226.100", + "185.220.226.101", + "185.220.226.103", + "185.220.226.104", + "185.220.226.105", + "185.220.226.106", + "185.220.226.108", + "185.220.226.110", + "185.220.226.111", + "185.220.226.112", + "185.220.226.114", + "185.220.226.115", + "185.220.226.116", + "185.220.226.117", + "185.220.226.118", + "185.220.226.121", + "185.220.226.122", + "185.220.226.123", + "185.220.226.124", + "185.220.226.125", + "185.220.226.126", + "185.220.226.127", + "185.220.226.128", + "185.220.226.130", + "185.220.226.131", + "185.220.226.132", + "185.220.226.133", + "185.220.226.134", + "185.220.226.135", + "185.220.226.136", + "185.220.226.137", + "185.220.226.138", + "185.220.226.139", + "185.220.226.140", + "185.220.226.141", + "185.220.226.142", + "185.220.226.143", + "185.220.226.144", + "185.220.226.145", + "185.220.226.146", + "185.220.226.147", + "185.220.226.148", + "185.220.226.150", + "185.220.226.151", + "185.220.226.152", + "185.220.226.153", + "185.220.226.154", + "185.220.226.155", + "185.220.226.156", + "185.220.226.157", + "185.220.226.158", + "185.220.226.159", + "185.220.226.160", + "185.220.226.161", + "185.220.226.162", + "185.220.226.163", + "185.220.226.164", + "185.220.226.165", + "185.220.226.166", + "185.220.226.167", + "185.220.226.168", + "185.220.226.169", + "185.220.226.170", + "185.220.226.171", + "185.220.226.172", + "185.220.226.173", + "185.220.226.174", + "185.220.226.175", + "185.220.226.176", + "185.220.226.177", + "185.220.226.178", + "185.220.226.179", + "185.220.226.181", + "185.220.226.182", + "185.220.226.183", + "185.220.226.184", + "185.220.226.185", + "185.220.226.186", + "185.220.226.187", + "185.220.226.188", + "185.220.226.189", + "185.220.226.190", + "185.220.226.191", + "185.220.226.192", + "185.220.226.193", + "185.220.226.195", + "185.220.226.198", + "185.220.226.199", + "185.220.226.200", + "185.220.226.201", + "185.220.226.202", + "185.220.226.203", + "185.220.226.204", + "185.220.226.205", + "185.220.226.206", + "185.220.226.207", + "185.220.226.208", + "185.220.226.209", + "185.220.226.211", + "185.220.226.212", + "185.220.226.213", + "185.220.226.214", + "185.220.226.215", + "185.220.226.216", + "185.220.226.217", + "185.220.226.218", + "185.220.226.219", + "185.220.226.220", + "185.220.226.221", + "185.220.226.222", + "185.220.226.223", + "185.220.226.224", + "185.220.226.225", + "185.220.226.226", + "185.220.226.227", + "185.220.226.228", + "185.220.226.230", + "185.220.226.231", + "185.220.226.232", + "185.220.226.233", + "185.220.226.234", + "185.220.226.236", + "185.220.226.237", + "185.220.226.239", + "185.220.226.240", + "185.220.226.241", + "185.220.226.242", + "185.220.226.244", + "185.220.226.247", + "185.220.226.248", + "185.220.226.249", + "185.220.226.250", + "185.220.226.251", + "185.220.226.252", + "185.220.226.253", + "185.220.226.255", + "212.18.108.187", + "212.18.108.235", + "193.111.76.3", + "193.111.76.10", + "212.18.108.115", + "212.18.108.131", + "212.18.108.132", + "212.18.108.173", + "212.18.108.174", + "212.18.108.188", + "212.18.108.189", + "212.18.108.190", + "212.18.108.203", + "212.18.108.205", + "212.18.108.211", + "212.18.108.212", + "212.18.108.214", + "212.18.108.228", + "212.18.108.237" + ], + "TW": [ + "101.101.101.101", + "101.102.103.104" + ], + "US": [ + "8.8.8.8", + "8.8.4.4", + "208.67.220.220", + "4.2.2.1", + "4.2.2.2", + "4.4.4.4", + "156.154.70.1", + "156.154.71.1", + "149.112.112.112", + "9.9.9.10", + "149.112.112.10", + "9.9.9.11", + "149.112.112.11", + "194.242.2.2", + "194.242.2.3", + "76.76.19.19", + "76.223.122.150", + "76.76.26.0", + "8.26.56.26", + "8.20.247.20", + "185.228.168.9", + "185.228.169.9", + "185.228.168.168", + "185.228.169.168", + "185.228.168.10", + "185.228.169.11", + "64.6.64.6", + "64.6.65.6", + "195.46.39.39", + "195.46.39.40", + "45.81.18.141", + "207.246.121.77", + "45.81.16.103", + "45.81.17.18", + "45.81.17.19", + "45.81.17.20", + "45.81.17.23", + "45.81.17.24", + "45.81.17.27", + "45.81.17.28", + "45.81.17.29", + "45.81.17.30", + "45.81.17.133", + "45.81.17.135", + "45.81.17.190", + "45.81.18.107", + "45.81.18.123", + "87.248.131.23", + "87.248.131.45", + "87.248.131.67", + "87.248.131.125", + "87.248.131.164", + "87.248.131.207", + "87.248.131.112", + "87.248.131.176", + "12.15.19.238", + "12.15.19.241", + "45.81.16.36", + "45.81.16.155", + "45.81.16.217", + "45.81.17.109", + "45.81.17.159", + "45.81.17.160", + "45.81.17.177", + "45.81.17.182", + "45.81.17.183", + "45.81.17.184", + "45.81.17.196", + "45.81.17.197", + "45.81.17.199", + "45.81.18.104", + "45.81.18.109", + "87.248.131.2", + "87.248.131.33", + "87.248.131.148", + "87.248.131.158", + "87.248.131.161", + "87.248.131.223", + "87.248.131.244" + ], + "ZA": [ + "102.22.81.183", + "102.22.81.133", + "102.133.234.155", + "102.215.240.26", + "102.215.240.45", + "102.215.240.66", + "102.215.240.72", + "102.215.240.74", + "102.215.240.81", + "102.215.240.120", + "102.215.240.121", + "102.215.240.216", + "102.215.242.125", + "102.215.242.149", + "102.215.242.182", + "102.215.243.18", + "102.215.243.98", + "102.22.72.46", + "102.22.80.182", + "102.22.80.183", + "102.22.80.250", + "102.22.81.11", + "102.22.81.13", + "102.22.81.19", + "102.22.81.20", + "102.22.81.34", + "102.22.81.37", + "102.22.81.41", + "102.22.81.42", + "102.22.81.45", + "102.22.81.52", + "102.22.81.74", + "102.22.81.75", + "102.22.81.85", + "102.22.81.99", + "102.22.81.110", + "102.22.81.122", + "102.22.81.140", + "102.22.81.141", + "102.22.81.151", + "102.22.81.171", + "102.22.81.175", + "102.22.81.193", + "102.22.81.195", + "102.22.81.201", + "102.22.81.211", + "102.22.81.212", + "102.22.81.213", + "102.22.81.225", + "102.22.81.249", + "102.22.81.254", + "102.22.82.4", + "102.22.82.5", + "102.22.82.17", + "102.22.82.45", + "102.22.82.53", + "102.22.82.56", + "102.22.82.59", + "102.22.82.70", + "102.22.82.71", + "102.22.82.94", + "102.22.82.95", + "102.22.82.129", + "102.22.82.214", + "102.22.82.221", + "102.22.82.227", + "102.22.82.233", + "102.22.82.242", + "102.22.83.12", + "102.22.83.14", + "102.22.83.46", + "102.22.83.71", + "102.22.83.72", + "102.22.83.78", + "102.22.83.102", + "102.22.83.103", + "102.22.83.113", + "102.22.83.114", + "102.22.83.139", + "102.22.83.142", + "102.22.83.149", + "102.22.83.155", + "102.22.83.207", + "102.22.124.69", + "102.22.124.70", + "102.22.127.147", + "102.22.195.106", + "102.22.195.246", + "102.22.207.4", + "102.22.207.42", + "102.22.237.8", + "102.22.237.9", + "102.22.247.162", + "102.22.254.116", + "102.22.254.232", + "102.23.163.244", + "102.23.168.24", + "102.23.196.253", + "102.23.198.210", + "102.23.208.4", + "102.23.208.30", + "102.23.226.27", + "102.23.226.70", + "102.23.226.71", + "102.23.230.23", + "102.23.231.69", + "102.23.231.82", + "102.23.231.84", + "102.23.231.87", + "102.23.231.172", + "102.23.231.253", + "102.23.237.76", + "102.23.252.83", + "102.37.16.226", + "102.37.21.224", + "102.37.109.39", + "102.37.113.11", + "102.37.113.89", + "102.37.116.18", + "102.37.118.18", + "102.37.142.149", + "102.37.144.57", + "102.37.145.202", + "102.37.158.250", + "102.37.176.63", + "102.37.213.24", + "102.37.214.202", + "102.37.216.211", + "102.37.217.70", + "102.37.217.174", + "102.37.218.157", + "102.37.220.188", + "102.133.128.244", + "102.133.129.203", + "102.133.132.86", + "102.133.132.90", + "102.133.136.51", + "102.133.136.53", + "102.133.136.58", + "102.133.136.65", + "102.133.138.26", + "102.133.138.30", + "102.133.138.66", + "102.133.139.5", + "102.133.139.31", + "102.133.139.152", + "102.133.139.153", + "102.133.139.154", + "102.133.146.143", + "102.133.160.77", + "102.133.171.26", + "102.133.227.168", + "102.133.235.79", + "102.215.242.87" + ], + "ZM": [ + "102.23.121.15", + "102.23.121.16", + "102.23.122.120", + "102.23.122.251", + "102.23.123.90", + "102.23.123.91" + ] +} \ No newline at end of file diff --git a/protocol/hiddify/dnstt/tester.go b/protocol/hiddify/dnstt/tester.go new file mode 100644 index 0000000000..2c399444ff --- /dev/null +++ b/protocol/hiddify/dnstt/tester.go @@ -0,0 +1,375 @@ +package dnstt + +import ( + "context" + "crypto/tls" + "encoding/json" + "fmt" + "math/rand/v2" + "net" + "net/http" + "sort" + "strconv" + "sync" + "time" + + dnstt "github.com/net2share/vaydns/client" + "github.com/sagernet/sing-box/adapter" + + "github.com/sagernet/sing-box/dns/transport" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/batch" + + "github.com/miekg/dns" +) + +type History struct { + ResolverRate map[string]int `json:"resolver_rate"` +} + +func (h *Outbound) loadHistory() *History { + history := &History{ResolverRate: make(map[string]int)} + + if h.cache == nil { + return history + } + + savedBinary := h.cache.LoadBinary("dnstt_resolvers" + h.options.RecordType) + if savedBinary == nil { + return history + } + if err := json.Unmarshal(savedBinary.Content, history); err != nil { + return history + } + + return history +} + +func (h *Outbound) saveHistory(his *History) { + if h.cache == nil { + return + } + + content, err := json.Marshal(his) + if err != nil { + h.logger.Error("failed to marshal outbound monitoring history: ", err) + return + } + h.cache.SaveBinary("dnstt_resolvers"+h.options.RecordType, &adapter.SavedBinary{ + LastUpdated: time.Now(), + Content: content, + }) +} + +func (h *Outbound) startTestResolver() error { + history := h.loadHistory() + defer h.saveHistory(history) + + b, _ := batch.New(h.ctx, batch.WithConcurrencyNum[any](100)) + + candidates := h.candidateResolvers + rand.Shuffle(len(candidates), func(i, j int) { + candidates[i], candidates[j] = candidates[j], candidates[i] + }) + + sort.Slice(candidates, func(i, j int) bool { + a := candidates[i] + b := candidates[j] + + // Auto first + if a.Auto != b.Auto { + return a.Auto + } + + // Then by rate descending + rateA := history.ResolverRate[a.Resolver.ResolverAddr] + rateB := history.ResolverRate[b.Resolver.ResolverAddr] + + return rateA > rateB + }) + historyMutex := sync.Mutex{} + + for _, r := range candidates { + resolver := r.Resolver + + select { + case <-h.ctx.Done(): + return h.ctx.Err() + default: + } + + b.Go(resolver.ResolverAddr, func() (any, error) { + h.mu.Lock() + resCount := len(h.resolvers) + h.mu.Unlock() + if resCount > 10 { + return nil, nil + } + + // stop if context cancelled + select { + case <-h.ctx.Done(): + return nil, h.ctx.Err() + default: + } + + rate, err := h.testTunnelResolver(resolver) + + // update history safely + historyMutex.Lock() + defer historyMutex.Unlock() + + if err == nil { + h.logger.InfoContext(h.ctx, "resolver ", resolver.ResolverAddr, " is working and added to the pool") + + if history.ResolverRate[resolver.ResolverAddr] < 0 { + history.ResolverRate[resolver.ResolverAddr] = 0 + } + history.ResolverRate[resolver.ResolverAddr] += rate + if history.ResolverRate[resolver.ResolverAddr] > 10 { + history.ResolverRate[resolver.ResolverAddr] = 10 + } + + h.addResolver(resolver) + if h.IsReady() { + h.saveHistory(history) + } + + } else { + h.logger.WarnContext(h.ctx, "resolver ", resolver.ResolverAddr, " failed: ", err) + + history.ResolverRate[resolver.ResolverAddr] += rate + if history.ResolverRate[resolver.ResolverAddr] < -10 { + history.ResolverRate[resolver.ResolverAddr] = -10 + } + } + + return nil, nil + }) + } + + return b.Wait() +} +func getDnsRecordType(record string) uint16 { + switch record { + case "a": + return dns.TypeA + case "aaaa": + return dns.TypeAAAA + case "cname": + return dns.TypeCNAME + case "mx": + return dns.TypeMX + case "ns": + return dns.TypeNS + case "ptr": + return dns.TypePTR + case "soa": + return dns.TypeSOA + case "srv": + return dns.TypeSRV + case "txt": + return dns.TypeTXT + default: + return dns.TypeA + } +} + +func (h *Outbound) testTunnelResolver(resolver dnstt.Resolver) (rate int, err error) { + domain := h.options.PreTestDomain + record := h.options.PreTestRecordType + h.logger.DebugContext(h.ctx, "testing resolver ", resolver.ResolverAddr, " with domain ", domain, " and record type ", record) + resp, err := h.Resolve(resolver, domain, getDnsRecordType(record)) + h.logger.DebugContext(h.ctx, "resolver ", resolver.ResolverAddr, " response ", fmt.Sprint(resp), " error ", err) + if err != nil { + return -4, err + } + // ips := make([]string, 0) + if resp == nil || len(resp.Answer) == 0 { + return -3, fmt.Errorf("no record found in response from resolver %s", resolver.ResolverAddr) + } + // for _, ans := range resp.Answer { + // if a, ok := ans.(*dns.A); ok { + // if a.A.IsLoopback() || a.A.IsPrivate() { + // continue + // } + // ips = append(ips, a.A.String()) + // } + // } + // } + // if len(ips) == 0 { + // return fmt.Errorf("no A record found in response from resolver %s", resolver.ResolverAddr) + // } + + ctx, cancel := context.WithTimeout(h.ctx, 5*time.Second) + defer cancel() + tunnel, err := h.createDnsttTunnel(ctx, []dnstt.Resolver{resolver}) + if err != nil { + h.logger.WarnContext(h.ctx, "failed to establish tunnel to resolver ", resolver.ResolverAddr, ": ", err) + return -2, err + } + defer tunnel.Close() + h.logger.InfoContext(h.ctx, "successfully established tunnel to resolver ", resolver.ResolverAddr) + conn, err := tunnel.OpenStream() + if err != nil { + h.logger.WarnContext(h.ctx, "failed to open stream to resolver ", resolver.ResolverAddr, ": ", err) + return -1, err + + } + h.logger.InfoContext(h.ctx, "successfully opened stream to resolver ", resolver.ResolverAddr) + conn.Close() + return 1, nil + +} + +func buildDNSQuery(name string, qtype uint16) *dns.Msg { + m := new(dns.Msg) + m.SetQuestion(dns.Fqdn(name), qtype) + m.RecursionDesired = true + + return m +} + +func (h *Outbound) ResolveUDP(resolver dnstt.Resolver, name string, qtype uint16) (*dns.Msg, error) { + host, portStr, err := net.SplitHostPort(resolver.ResolverAddr) + if err != nil { + return nil, err + } + + portInt, err := strconv.Atoi(portStr) + if err != nil { + return nil, err + } + trans, err := transport.NewUDP( + h.ctx, + h.logger, + "resolver"+resolver.ResolverAddr, + option.RemoteDNSServerOptions{ + DNSServerAddressOptions: option.DNSServerAddressOptions{ + Server: host, + ServerPort: uint16(portInt), + }, + }, + ) + if err != nil { + return nil, err + } + // h.logger.InfoContext(h.ctx, "resolving name ", name, " of type ", qtype, " using resolver ", resolver.ResolverAddr) + + msg := buildDNSQuery(name, qtype) + trans.Start(adapter.StartStateStart) + ctx, cancel := context.WithTimeout(h.ctx, 5*time.Second) + defer cancel() + resp, err := trans.Exchange(ctx, msg) + if err != nil { + return nil, err + } + return resp, nil +} + +func (h *Outbound) Resolve(resolver dnstt.Resolver, name string, qtype uint16) (*dns.Msg, error) { + if resolver.ResolverType == dnstt.ResolverTypeUDP { + return h.ResolveUDP(resolver, name, qtype) + } + conn, r, err := h.getTCPBasedResolverConnection(resolver, 5*time.Second) + msg := buildDNSQuery(name, qtype) + + data, err := msg.Pack() + if err != nil { + return nil, err + } + + _, err = conn.WriteTo(data, r) + if err != nil { + return nil, err + } + buf := make([]byte, 4096) + + n, _, err := conn.ReadFrom(buf) + if err != nil { + return nil, err + } + + resp := new(dns.Msg) + err = resp.Unpack(buf[:n]) + if err != nil { + return nil, err + } + return resp, nil +} + +func (h *Outbound) getTCPBasedResolverConnection(r dnstt.Resolver, timeout time.Duration) (net.PacketConn, *net.UDPAddr, error) { + + h.logger.InfoContext(h.ctx, "getting resolver connection ", r.ResolverAddr) + switch r.ResolverType { + case dnstt.ResolverTypeUDP: + addr, err := net.ResolveUDPAddr("udp", r.ResolverAddr) + if err != nil { + return nil, nil, err + } + // t.remoteAddr = addr + if r.UDPSharedSocket { + lc := net.ListenConfig{Control: r.DialerControl} + conn, err := lc.ListenPacket(context.Background(), "udp", ":0") + if err != nil { + return nil, nil, err + } + h.logger.InfoContext(h.ctx, "resolver connection established ", r.ResolverAddr) + return conn, addr, nil + } else { + workers := r.UDPWorkers + if workers <= 0 { + workers = dnstt.DefaultUDPWorkers + } + + if timeout <= 0 { + timeout = dnstt.DefaultUDPResponseTimeout + } + conn, _, err := dnstt.NewUDPPacketConn(addr, r.DialerControl, workers, timeout, !r.UDPAcceptErrors, 512, "block") + if err != nil { + return nil, nil, err + } + h.logger.InfoContext(h.ctx, "resolver connection established ", r.ResolverAddr) + // t.forgedStats = forgedStats + return conn, addr, nil + } + + case dnstt.ResolverTypeDOH: + // t.remoteAddr = turbotunnel.DummyAddr{} + var rt http.RoundTripper + if r.RoundTripper != nil { + rt = r.RoundTripper + } else if r.UTLSClientHelloID != nil { + rt = dnstt.NewUTLSRoundTripper(nil, r.UTLSClientHelloID) + } else { + rt = http.DefaultTransport + } + conn, err := dnstt.NewHTTPPacketConn(rt, r.ResolverAddr, 8, 512, "block") + if err != nil { + return nil, nil, err + } + return conn, nil, nil + + case dnstt.ResolverTypeDOT: + // t.remoteAddr = turbotunnel.DummyAddr{} + var dialTLSContext func(ctx context.Context, network, addr string) (net.Conn, error) + if r.UTLSClientHelloID != nil { + id := r.UTLSClientHelloID + dialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) { + return dnstt.UTLSDialContext(ctx, network, addr, nil, id) + } + } else { + dialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) { + return tls.DialWithDialer(&net.Dialer{}, network, addr, nil) + } + } + conn, err := dnstt.NewTLSPacketConn(r.ResolverAddr, dialTLSContext, 512, "block") + if err != nil { + return nil, nil, err + } + return conn, nil, nil + + default: + return nil, nil, fmt.Errorf("unsupported resolver type: %s", r.ResolverType) + } +} diff --git a/protocol/hiddify/dnstt/tools.go b/protocol/hiddify/dnstt/tools.go new file mode 100644 index 0000000000..88c6fda7eb --- /dev/null +++ b/protocol/hiddify/dnstt/tools.go @@ -0,0 +1,90 @@ +package dnstt + +import ( + "encoding/json" + "fmt" + "strings" + + _ "embed" + + dnstt "github.com/net2share/vaydns/client" + "github.com/sagernet/sing-box/option" +) + +var ( + //go:embed resolvers_by_country.json + resolvers_bytes []byte + countryResolvers map[string][]string + resolverCountry map[string]string +) + +func loadResolvers() { + json.Unmarshal(resolvers_bytes, &countryResolvers) + resolverCountry = make(map[string]string) + for country, resolvers := range countryResolvers { + for _, resolver := range resolvers { + resolverCountry[resolver] = country + } + } +} + +type ResolverS struct { + Resolver dnstt.Resolver + Auto bool +} + +func getConfigResolvers(options option.DnsttOptions) ([]ResolverS, error) { + resolvers := []ResolverS{} + for _, resolverAddr := range options.Resolvers { + if resolverAddr == "" || resolverAddr == "auto" { + for ip, _ := range resolverCountry { + resolver, err := getResolver(options, fmt.Sprint(ip, ":53")) + + if err != nil { + return nil, fmt.Errorf("invalid resolver address %s: %w", ip, err) + } + resolvers = append(resolvers, ResolverS{ + Resolver: resolver, + Auto: true, + }) + } + continue + } + resolver, err := getResolver(options, resolverAddr) + if err != nil { + return nil, fmt.Errorf("invalid resolver address %s: %w", resolverAddr, err) + } + resolvers = append(resolvers, ResolverS{ + Resolver: resolver, + Auto: false, + }) + + } + return resolvers, nil +} +func getResolver(options option.DnsttOptions, s string) (dnstt.Resolver, error) { + var resolver dnstt.Resolver + var err error + if _, ok := strings.CutPrefix(s, "https://"); ok { + resolver, err = dnstt.NewResolver(dnstt.ResolverTypeDOH, s) + } else if dot, ok := strings.CutPrefix(s, "dot://"); ok { + resolver, err = dnstt.NewResolver(dnstt.ResolverTypeDOT, dot) + } else { + resolver, err = dnstt.NewResolver(dnstt.ResolverTypeUDP, s) + } + if err != nil { + return resolver, err + } + resolver.UDPAcceptErrors = options.UdpAcceptErrors + resolver.UDPSharedSocket = options.UdpSharedSocket + if options.UdpTimeout != nil { + resolver.UDPTimeout = options.UdpTimeout.Build() + } + if options.UdpWorkers != nil { + resolver.UDPWorkers = *options.UdpWorkers + } + if options.UTLSClientHelloID != "" { + resolver.UTLSClientHelloID = dnstt.UTLSLookup(options.UTLSClientHelloID) + } + return resolver, nil +} diff --git a/protocol/hiddify/gooserelay/outbound.go b/protocol/hiddify/gooserelay/outbound.go new file mode 100644 index 0000000000..91922d3ab5 --- /dev/null +++ b/protocol/hiddify/gooserelay/outbound.go @@ -0,0 +1,275 @@ +// Package gooserelay wraps the GooseRelayVPN carrier as a sing-box outbound. +// +// Traffic is multiplexed over a domain-fronted HTTPS connection to a Google +// Apps Script endpoint, which forwards encrypted frames to the user's VPS. +// The carrier handles per-endpoint health and round-robin internally; this +// outbound only manages the lifecycle and wraps each session as a net.Conn. +package gooserelay + +import ( + "context" + "encoding/hex" + "fmt" + "net" + "strings" + "sync" + "time" + + "github.com/kianmhz/GooseRelayVPN/goose" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/uot" +) + +const ( + defaultGoogleHost = "216.239.38.120:443" + defaultDiagnoseTimeout = 10 * time.Second +) + +var defaultSNIHosts = []string{"www.google.com"} + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.GooseRelayOptions](registry, C.TypeGooseRelay, New) +} + +var _ adapter.Outbound = (*Outbound)(nil) + +type Outbound struct { + outbound.Adapter + ctx context.Context + logger logger.ContextLogger + options option.GooseRelayOptions + client *goose.Client + uotClient *uot.Client + + mu sync.Mutex + runCancel context.CancelFunc + started int +} + +func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.GooseRelayOptions) (adapter.Outbound, error) { + if len(options.ScriptKeys) == 0 { + return nil, E.New("script_keys is required") + } + if options.TunnelKey == "" { + return nil, E.New("tunnel_key is required") + } + if len(options.TunnelKey) != 64 { + return nil, E.New("tunnel_key must be 64 hex characters (AES-256)") + } + if _, err := hex.DecodeString(options.TunnelKey); err != nil { + return nil, E.Cause(err, "tunnel_key not valid hex") + } + + googleHost := options.GoogleHost + if googleHost == "" { + googleHost = defaultGoogleHost + } + sniHosts := options.SNI + if len(sniHosts) == 0 { + sniHosts = defaultSNIHosts + } + + scriptURLs := make([]string, 0, len(options.ScriptKeys)) + for i, key := range options.ScriptKeys { + key = strings.TrimSpace(key) + if key == "" { + return nil, E.New("script_keys[", i, "] is empty") + } + if strings.ContainsAny(key, "/?#") { + return nil, E.New("script_keys[", i, "] contains a URL separator (/?#); paste the deployment ID only") + } + scriptURLs = append(scriptURLs, fmt.Sprintf("https://script.google.com/macros/s/%s/exec", key)) + } + + client, err := goose.New(goose.Config{ + ScriptURLs: scriptURLs, + Fronting: goose.FrontingConfig{GoogleIP: googleHost, SNIHosts: sniHosts}, + AESKeyHex: options.TunnelKey, + DebugTiming: options.DebugTiming, + }) + if err != nil { + return nil, E.Cause(err, "construct goose client") + } + + out := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeGooseRelay, tag, []string{N.NetworkTCP}, options.DialerOptions), + ctx: ctx, + logger: logger, + options: options, + client: client, + } + if options.UDPOverTCP != nil && options.UDPOverTCP.Enabled { + out.uotClient = &uot.Client{ + Dialer: singDialerAdapter{out: out}, + Version: options.UDPOverTCP.Version, + } + } + return out, nil +} + +func (h *Outbound) PostStart() error { + runCtx, cancel := context.WithCancel(h.ctx) + h.mu.Lock() + h.runCancel = cancel + h.mu.Unlock() + + go func() { + if err := h.client.Run(runCtx); err != nil && runCtx.Err() == nil { + h.logger.ErrorContext(runCtx, "carrier run exited: ", err) + } + }() + go h.diagnoseAndMarkReady(runCtx) + return nil +} + +// diagnoseAndMarkReady probes every configured script_key concurrently using a +// throwaway one-endpoint carrier per probe, and flips h.started: +// - +1 (ready) as soon as ANY endpoint passes Diagnose, +// - -1 (failed) only if ALL endpoints fail. +// +// Throwaway probes are cheap: carrier.New only allocates HTTP clients and +// returns; no goroutines are launched until Run is called, which we never do +// for probes. Cancelling probeCtx on first success aborts the remaining +// in-flight HTTP requests. +func (h *Outbound) diagnoseAndMarkReady(runCtx context.Context) { + budget := defaultDiagnoseTimeout + if h.options.HandshakeTimeout != nil { + if d := h.options.HandshakeTimeout.Build(); d > 0 { + budget = d + } + } + probeCtx, cancel := context.WithTimeout(runCtx, budget) + defer cancel() + + googleHost := h.options.GoogleHost + if googleHost == "" { + googleHost = defaultGoogleHost + } + sniHosts := h.options.SNI + if len(sniHosts) == 0 { + sniHosts = defaultSNIHosts + } + fronting := goose.FrontingConfig{GoogleIP: googleHost, SNIHosts: sniHosts} + + type probeResult struct { + key string + err error + } + keys := h.options.ScriptKeys + results := make(chan probeResult, len(keys)) + for _, key := range keys { + go func(k string) { + trimmed := strings.TrimSpace(k) + probe, err := goose.New(goose.Config{ + ScriptURLs: []string{fmt.Sprintf("https://script.google.com/macros/s/%s/exec", trimmed)}, + Fronting: fronting, + AESKeyHex: h.options.TunnelKey, + }) + if err != nil { + results <- probeResult{trimmed, err} + return + } + results <- probeResult{trimmed, probe.Diagnose(probeCtx)} + }(key) + } + + for i := 0; i < len(keys); i++ { + select { + case r := <-results: + if r.err == nil { + h.mu.Lock() + h.started = 1 + h.mu.Unlock() + h.logger.InfoContext(runCtx, "goose-relay ready (first healthy endpoint: ", r.key, ")") + return + } + h.logger.WarnContext(probeCtx, "goose-relay endpoint ", r.key, " diagnose failed: ", r.err) + case <-runCtx.Done(): + return + } + } + + h.mu.Lock() + h.started = -1 + h.mu.Unlock() + h.logger.ErrorContext(runCtx, "goose-relay: all ", len(keys), " endpoints failed diagnose") +} + +func (h *Outbound) IsReady() bool { + h.mu.Lock() + defer h.mu.Unlock() + return h.started > 0 +} + +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + if !h.IsReady() { + return nil, E.New("outbound is not started") + } + switch N.NetworkName(network) { + case N.NetworkTCP: + default: + return nil, E.New("network ", network, " not supported by goose-relay") + } + return h.client.Dial(destination.String()), nil +} + +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + if h.uotClient == nil { + return nil, E.New("UDP over TCP is not enabled for this outbound") + } + if !h.IsReady() { + return nil, E.New("outbound is not started") + } + return h.uotClient.ListenPacket(ctx, destination) +} + +func (h *Outbound) DisplayType() string { + str := C.ProxyDisplayName(h.Type()) + h.mu.Lock() + state := h.started + h.mu.Unlock() + switch { + case state == 0: + return str + " ⚠️ Connecting..." + case state < 0: + return str + " ❌ Failed!" + default: + return fmt.Sprint(str, " ✔️ ", len(h.options.ScriptKeys), " endpoints") + } +} + +func (h *Outbound) Close() error { + h.mu.Lock() + cancel := h.runCancel + h.runCancel = nil + h.mu.Unlock() + if cancel != nil { + shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 2*time.Second) + h.client.Shutdown(shutdownCtx) + shutdownCancel() + cancel() + } + return nil +} + +// singDialerAdapter bridges Outbound back to N.Dialer so uot.Client can dial +// its underlying TCP carrier session via DialContext. +type singDialerAdapter struct { + out *Outbound +} + +func (a singDialerAdapter) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + return a.out.DialContext(ctx, network, destination) +} + +func (a singDialerAdapter) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return nil, E.New("not supported") +} diff --git a/protocol/hiddify/gooserelay/outbound_test.go b/protocol/hiddify/gooserelay/outbound_test.go new file mode 100644 index 0000000000..411c14dd70 --- /dev/null +++ b/protocol/hiddify/gooserelay/outbound_test.go @@ -0,0 +1,127 @@ +package gooserelay_test + +import ( + "context" + "strings" + "testing" + + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/protocol/hiddify/gooserelay" +) + +const validHexKey = "0000000000000000000000000000000000000000000000000000000000000000" + +func TestNew_OptionValidation(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + opts option.GooseRelayOptions + wantErr string // substring expected in the error; "" = expect success + wantSkip bool + }{ + { + name: "missing script_keys", + opts: option.GooseRelayOptions{TunnelKey: validHexKey}, + wantErr: "script_keys is required", + }, + { + name: "missing tunnel_key", + opts: option.GooseRelayOptions{ScriptKeys: []string{"abc"}}, + wantErr: "tunnel_key is required", + }, + { + name: "tunnel_key wrong length", + opts: option.GooseRelayOptions{ + ScriptKeys: []string{"abc"}, + TunnelKey: "deadbeef", + }, + wantErr: "64 hex characters", + }, + { + name: "tunnel_key not hex", + opts: option.GooseRelayOptions{ + ScriptKeys: []string{"abc"}, + TunnelKey: strings.Repeat("Z", 64), + }, + wantErr: "not valid hex", + }, + { + name: "empty entry in script_keys", + opts: option.GooseRelayOptions{ + ScriptKeys: []string{"abc", " "}, + TunnelKey: validHexKey, + }, + wantErr: "script_keys[1] is empty", + }, + { + name: "script_keys contains URL separator /", + opts: option.GooseRelayOptions{ + ScriptKeys: []string{"abc/def"}, + TunnelKey: validHexKey, + }, + wantErr: "URL separator", + }, + { + name: "script_keys contains URL separator ?", + opts: option.GooseRelayOptions{ + ScriptKeys: []string{"abc?evil=1"}, + TunnelKey: validHexKey, + }, + wantErr: "URL separator", + }, + { + name: "script_keys contains URL separator #", + opts: option.GooseRelayOptions{ + ScriptKeys: []string{"abc#frag"}, + TunnelKey: validHexKey, + }, + wantErr: "URL separator", + }, + { + name: "valid minimal options", + opts: option.GooseRelayOptions{ + ScriptKeys: []string{"AKfycbxFakeDeploymentID"}, + TunnelKey: validHexKey, + }, + wantErr: "", + }, + { + name: "valid with custom GoogleHost and SNI defaults applied", + opts: option.GooseRelayOptions{ + ScriptKeys: []string{"AKfycbxFakeDeploymentID"}, + TunnelKey: validHexKey, + GoogleHost: "1.2.3.4:443", + }, + wantErr: "", + }, + } + + logger := log.NewNOPFactory().Logger() + for _, tc := range cases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + if tc.wantSkip { + t.Skip() + } + out, err := gooserelay.New(context.Background(), nil, logger, "goose-test", tc.opts) + if tc.wantErr == "" { + if err != nil { + t.Fatalf("expected success, got error: %v", err) + } + if out == nil { + t.Fatal("expected non-nil outbound on success") + } + return + } + if err == nil { + t.Fatalf("expected error containing %q, got nil", tc.wantErr) + } + if !strings.Contains(err.Error(), tc.wantErr) { + t.Fatalf("expected error containing %q, got: %v", tc.wantErr, err) + } + }) + } +} diff --git a/protocol/hiddify/hinvalid/outbound.go b/protocol/hiddify/hinvalid/outbound.go new file mode 100644 index 0000000000..11d38b35f2 --- /dev/null +++ b/protocol/hiddify/hinvalid/outbound.go @@ -0,0 +1,50 @@ +package hinvalid + +import ( + "context" + "net" + "syscall" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.HInvalidOptions](registry, C.TypeHInvalidConfig, New) +} + +var _ adapter.Outbound = (*Outbound)(nil) + +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger + InvalidOptions option.HInvalidOptions +} + +func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, invalidOptions option.HInvalidOptions) (adapter.Outbound, error) { + return &Outbound{ + Adapter: outbound.NewAdapter(C.TypeHInvalidConfig, tag, []string{N.NetworkTCP, N.NetworkUDP}, nil), + logger: logger, + InvalidOptions: invalidOptions, + }, nil +} + +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + h.logger.InfoContext(ctx, "blocked connection to ", destination) + return nil, syscall.EPERM +} + +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + h.logger.InfoContext(ctx, "blocked packet connection to ", destination) + return nil, syscall.EPERM +} + +func (h *Outbound) DisplayType() string { + return C.ProxyDisplayName(h.Tag()) + " " + h.InvalidOptions.Err.Error() +} diff --git a/protocol/hiddify/xray/outbound.go b/protocol/hiddify/xray/outbound.go new file mode 100644 index 0000000000..f60526d56d --- /dev/null +++ b/protocol/hiddify/xray/outbound.go @@ -0,0 +1,43 @@ +package xray + +import ( + "context" + "net" + "syscall" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" +) + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.XrayOutboundOptions](registry, C.TypeXray, New) +} + +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger +} + +func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.XrayOutboundOptions) (adapter.Outbound, error) { + // return &Outbound{ + // Adapter: outbound.NewAdapter(C.TypeBlock, tag, []string{N.NetworkTCP, N.NetworkUDP}, nil), + // logger: logger, + // }, nil + return nil, E.New("Xray is not implemented yet") +} + +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + h.logger.InfoContext(ctx, "blocked connection to ", destination) + return nil, syscall.EPERM +} + +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + h.logger.InfoContext(ctx, "blocked packet connection to ", destination) + return nil, syscall.EPERM +} diff --git a/protocol/http/inbound.go b/protocol/http/inbound.go new file mode 100644 index 0000000000..fe573ea1e0 --- /dev/null +++ b/protocol/http/inbound.go @@ -0,0 +1,132 @@ +package http + +import ( + std_bufio "bufio" + "context" + "net" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/common/uot" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/auth" + E "github.com/sagernet/sing/common/exceptions" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/protocol/http" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.HTTPMixedInboundOptions](registry, C.TypeHTTP, NewInbound) +} + +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) + +type Inbound struct { + inbound.Adapter + router adapter.ConnectionRouterEx + logger log.ContextLogger + listener *listener.Listener + authenticator *auth.Authenticator + tlsConfig tls.ServerConfig +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeHTTP, tag), + router: uot.NewRouter(router, logger), + logger: logger, + authenticator: auth.NewAuthenticator(options.Users), + } + if options.TLS != nil { + tlsConfig, err := tls.NewServerWithOptions(tls.ServerOptions{ + Context: ctx, + Logger: logger, + Options: common.PtrValueOrDefault(options.TLS), + KTLSCompatible: true, + }) + if err != nil { + return nil, err + } + inbound.tlsConfig = tlsConfig + } + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + SetSystemProxy: options.SetSystemProxy, + SystemProxySOCKS: false, + }) + return inbound, nil +} + +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + if h.tlsConfig != nil { + err := h.tlsConfig.Start() + if err != nil { + return E.Cause(err, "create TLS config") + } + } + return h.listener.Start() +} + +func (h *Inbound) Close() error { + return common.Close( + h.listener, + h.tlsConfig, + ) +} + +func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + if h.tlsConfig != nil { + tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig) + if err != nil { + N.CloseOnHandshakeFailure(conn, onClose, err) + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source, ": TLS handshake")) + return + } + conn = tlsConn + } + err := http.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, adapter.NewUpstreamHandler(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose) + if err != nil { + N.CloseOnHandshakeFailure(conn, onClose, err) + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} + +func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + user, loaded := auth.UserFromContext[string](ctx) + if !loaded { + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) + return + } + metadata.User = user + h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + user, loaded := auth.UserFromContext[string](ctx) + if !loaded { + h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) + return + } + metadata.User = user + h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/protocol/http/outbound.go b/protocol/http/outbound.go new file mode 100644 index 0000000000..48c4be6b62 --- /dev/null +++ b/protocol/http/outbound.go @@ -0,0 +1,65 @@ +package http + +import ( + "context" + "net" + "os" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + sHTTP "github.com/sagernet/sing/protocol/http" +) + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.HTTPOutboundOptions](registry, C.TypeHTTP, NewOutbound) +} + +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger + client *sHTTP.Client +} + +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPOutboundOptions) (adapter.Outbound, error) { + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) + if err != nil { + return nil, err + } + detour, err := tls.NewDialerFromOptions(ctx, logger, outboundDialer, options.Server, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + return &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeHTTP, tag, []string{N.NetworkTCP}, options.DialerOptions), + logger: logger, + client: sHTTP.NewClient(sHTTP.Options{ + Dialer: detour, + Server: options.ServerOptions.Build(), + Username: options.Username, + Password: options.Password, + Path: options.Path, + Headers: options.Headers.Build(), + }), + }, nil +} + +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() + metadata.Destination = destination + h.logger.InfoContext(ctx, "outbound connection to ", destination) + return h.client.DialContext(ctx, network, destination) +} + +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return nil, os.ErrInvalid +} diff --git a/inbound/hysteria.go b/protocol/hysteria/inbound.go similarity index 57% rename from inbound/hysteria.go rename to protocol/hysteria/inbound.go index e415d57019..6fd4fe9716 100644 --- a/inbound/hysteria.go +++ b/protocol/hysteria/inbound.go @@ -1,6 +1,4 @@ -//go:build with_quic - -package inbound +package hysteria import ( "context" @@ -8,7 +6,8 @@ import ( "time" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/humanize" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -16,20 +15,25 @@ import ( "github.com/sagernet/sing-quic/hysteria" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" - E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) -var _ adapter.Inbound = (*Hysteria)(nil) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.HysteriaInboundOptions](registry, C.TypeHysteria, NewInbound) +} -type Hysteria struct { - myInboundAdapter +type Inbound struct { + inbound.Adapter + router adapter.Router + logger log.ContextLogger + listener *listener.Listener tlsConfig tls.ServerConfig service *hysteria.Service[int] userNameList []string } -func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (*Hysteria, error) { +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (adapter.Inbound, error) { options.UDPFragmentDefault = true if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired @@ -38,32 +42,25 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } - inbound := &Hysteria{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeHysteria, - network: []string{N.NetworkUDP}, - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeHysteria, tag), + router: router, + logger: logger, + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Listen: options.ListenOptions, + }), tlsConfig: tlsConfig, } var sendBps, receiveBps uint64 - if len(options.Up) > 0 { - sendBps, err = humanize.ParseBytes(options.Up) - if err != nil { - return nil, E.Cause(err, "invalid up speed format: ", options.Up) - } + if options.Up.Value() > 0 { + sendBps = options.Up.Value() } else { sendBps = uint64(options.UpMbps) * hysteria.MbpsToBps } - if len(options.Down) > 0 { - receiveBps, err = humanize.ParseBytes(options.Down) - if receiveBps == 0 { - return nil, E.New("invalid down speed format: ", options.Down) - } + if options.Down.Value() > 0 { + receiveBps = options.Down.Value() } else { receiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps } @@ -80,15 +77,9 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL ReceiveBPS: receiveBps, XPlusPassword: options.Obfs, TLSConfig: tlsConfig, + QUICOptions: buildInboundQUICOptions(options), UDPTimeout: udpTimeout, - Handler: adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, nil), - - // Legacy options - - ConnReceiveWindow: options.ReceiveWindowConn, - StreamReceiveWindow: options.ReceiveWindowClient, - MaxIncomingStreams: int64(options.MaxConnClient), - DisableMTUDiscovery: options.DisableMTUDiscovery, + Handler: inbound, }) if err != nil { return nil, err @@ -113,9 +104,17 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL return inbound, nil } -func (h *Hysteria) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ctx = log.ContextWithNewID(ctx) - metadata = h.createMetadata(conn, metadata) + var metadata adapter.InboundContext + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + //nolint:staticcheck + metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck + metadata.OriginDestination = h.listener.UDPAddr() + metadata.Source = source + metadata.Destination = destination h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -124,12 +123,20 @@ func (h *Hysteria) newConnection(ctx context.Context, conn net.Conn, metadata ad } else { h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) } - return h.router.RouteConnection(ctx, conn, metadata) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) } -func (h *Hysteria) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ctx = log.ContextWithNewID(ctx) - metadata = h.createPacketMetadata(conn, metadata) + var metadata adapter.InboundContext + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + //nolint:staticcheck + metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck + metadata.OriginDestination = h.listener.UDPAddr() + metadata.Source = source + metadata.Destination = destination h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -138,26 +145,29 @@ func (h *Hysteria) newPacketConnection(ctx context.Context, conn N.PacketConn, m } else { h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) } - return h.router.RoutePacketConnection(ctx, conn, metadata) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } -func (h *Hysteria) Start() error { +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } if h.tlsConfig != nil { err := h.tlsConfig.Start() if err != nil { return err } } - packetConn, err := h.myInboundAdapter.ListenUDP() + packetConn, err := h.listener.ListenUDP() if err != nil { return err } return h.service.Start(packetConn) } -func (h *Hysteria) Close() error { +func (h *Inbound) Close() error { return common.Close( - &h.myInboundAdapter, + h.listener, h.tlsConfig, common.PtrOrNil(h.service), ) diff --git a/outbound/hysteria.go b/protocol/hysteria/outbound.go similarity index 56% rename from outbound/hysteria.go rename to protocol/hysteria/outbound.go index cf7f7fed11..bd6c5e4a37 100644 --- a/outbound/hysteria.go +++ b/protocol/hysteria/outbound.go @@ -1,47 +1,53 @@ -//go:build with_quic - -package outbound +package hysteria import ( "context" "net" "os" + "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" - "github.com/sagernet/sing-box/common/humanize" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/protocol/tuic" "github.com/sagernet/sing-quic/hysteria" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.HysteriaOutboundOptions](registry, C.TypeHysteria, NewOutbound) +} + var ( - _ adapter.Outbound = (*TUIC)(nil) - _ adapter.InterfaceUpdateListener = (*TUIC)(nil) + _ adapter.Outbound = (*tuic.Outbound)(nil) + _ adapter.InterfaceUpdateListener = (*tuic.Outbound)(nil) ) -type Hysteria struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger client *hysteria.Client } -func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (*Hysteria, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (adapter.Outbound, error) { options.UDPFragmentDefault = true if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired } - tlsConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS)) + tlsConfig, err := tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS)) if err != nil { return nil, err } - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } @@ -53,19 +59,13 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL password = string(options.Auth) } var sendBps, receiveBps uint64 - if len(options.Up) > 0 { - sendBps, err = humanize.ParseBytes(options.Up) - if err != nil { - return nil, E.Cause(err, "invalid up speed format: ", options.Up) - } + if options.Up.Value() > 0 { + sendBps = options.Up.Value() } else { sendBps = uint64(options.UpMbps) * hysteria.MbpsToBps } - if len(options.Down) > 0 { - receiveBps, err = humanize.ParseBytes(options.Down) - if receiveBps == 0 { - return nil, E.New("invalid down speed format: ", options.Down) - } + if options.Down.Value() > 0 { + receiveBps = options.Down.Value() } else { receiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps } @@ -74,34 +74,27 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL Dialer: outboundDialer, Logger: logger, ServerAddress: options.ServerOptions.Build(), + ServerPorts: options.ServerPorts, + HopInterval: time.Duration(options.HopInterval), SendBPS: sendBps, ReceiveBPS: receiveBps, XPlusPassword: options.Obfs, Password: password, TLSConfig: tlsConfig, + QUICOptions: buildOutboundQUICOptions(options), UDPDisabled: !common.Contains(networkList, N.NetworkUDP), - - ConnReceiveWindow: options.ReceiveWindowConn, - StreamReceiveWindow: options.ReceiveWindow, - DisableMTUDiscovery: options.DisableMTUDiscovery, }) if err != nil { return nil, err } - return &Hysteria{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeHysteria, - network: networkList, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, - client: client, + return &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeHysteria, tag, networkList, options.DialerOptions), + logger: logger, + client: client, }, nil } -func (h *Hysteria) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { switch N.NetworkName(network) { case N.NetworkTCP: h.logger.InfoContext(ctx, "outbound connection to ", destination) @@ -117,23 +110,15 @@ func (h *Hysteria) DialContext(ctx context.Context, network string, destination } } -func (h *Hysteria) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return h.client.ListenPacket(ctx, destination) } -func (h *Hysteria) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *Hysteria) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewPacketConnection(ctx, h, conn, metadata) -} - -func (h *Hysteria) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { h.client.CloseWithError(E.New("network changed")) } -func (h *Hysteria) Close() error { +func (h *Outbound) Close() error { return h.client.CloseWithError(os.ErrClosed) } diff --git a/protocol/hysteria/quic.go b/protocol/hysteria/quic.go new file mode 100644 index 0000000000..5d6e039c3f --- /dev/null +++ b/protocol/hysteria/quic.go @@ -0,0 +1,49 @@ +package hysteria + +import ( + "github.com/sagernet/sing-box/option" + qtls "github.com/sagernet/sing-quic" +) + +func buildBaseQUICOptions(options option.QUICOptions) qtls.QUICOptions { + return qtls.QUICOptions{ + IdleTimeout: options.IdleTimeout.Build(), + KeepAlivePeriod: options.KeepAlivePeriod.Build(), + StreamReceiveWindow: options.StreamReceiveWindow.Value(), + ConnectionReceiveWindow: options.ConnectionReceiveWindow.Value(), + MaxConcurrentStreams: options.MaxConcurrentStreams, + InitialPacketSize: options.InitialPacketSize, + DisablePathMTUDiscovery: options.DisablePathMTUDiscovery, + } +} + +func buildInboundQUICOptions(options option.HysteriaInboundOptions) qtls.QUICOptions { + quicOptions := buildBaseQUICOptions(options.QUICOptions) + if quicOptions.ConnectionReceiveWindow == 0 { + quicOptions.ConnectionReceiveWindow = options.ReceiveWindowConn //nolint:staticcheck + } + if quicOptions.StreamReceiveWindow == 0 { + quicOptions.StreamReceiveWindow = options.ReceiveWindowClient //nolint:staticcheck + } + if quicOptions.MaxConcurrentStreams == 0 { + quicOptions.MaxConcurrentStreams = options.MaxConnClient //nolint:staticcheck + } + if !quicOptions.DisablePathMTUDiscovery { + quicOptions.DisablePathMTUDiscovery = options.DisableMTUDiscovery //nolint:staticcheck + } + return quicOptions +} + +func buildOutboundQUICOptions(options option.HysteriaOutboundOptions) qtls.QUICOptions { + quicOptions := buildBaseQUICOptions(options.QUICOptions) + if quicOptions.ConnectionReceiveWindow == 0 { + quicOptions.ConnectionReceiveWindow = options.ReceiveWindowConn //nolint:staticcheck + } + if quicOptions.StreamReceiveWindow == 0 { + quicOptions.StreamReceiveWindow = options.ReceiveWindow //nolint:staticcheck + } + if !quicOptions.DisablePathMTUDiscovery { + quicOptions.DisablePathMTUDiscovery = options.DisableMTUDiscovery //nolint:staticcheck + } + return quicOptions +} diff --git a/protocol/hysteria2/inbound.go b/protocol/hysteria2/inbound.go new file mode 100644 index 0000000000..b706b49202 --- /dev/null +++ b/protocol/hysteria2/inbound.go @@ -0,0 +1,271 @@ +package hysteria2 + +import ( + "context" + "net" + "net/http" + "net/http/httputil" + "net/netip" + "net/url" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + qtls "github.com/sagernet/sing-quic" + "github.com/sagernet/sing-quic/hysteria" + "github.com/sagernet/sing-quic/hysteria2" + "github.com/sagernet/sing-quic/hysteria2/realm" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/auth" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.Hysteria2InboundOptions](registry, C.TypeHysteria2, NewInbound) +} + +type Inbound struct { + inbound.Adapter + router adapter.Router + logger log.ContextLogger + listener *listener.Listener + tlsConfig tls.ServerConfig + service *hysteria2.Service[int] + userNameList []string +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (adapter.Inbound, error) { + options.UDPFragmentDefault = true + if options.TLS == nil || !options.TLS.Enabled { + return nil, C.ErrTLSRequired + } + tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + var salamanderPassword string + var geckoPassword string + var geckoMinPacketSize, geckoMaxPacketSize int + if options.Obfs != nil { + if options.Obfs.Password == "" { + return nil, E.New("missing obfs password") + } + switch options.Obfs.Type { + case hysteria2.ObfsTypeSalamander: + salamanderPassword = options.Obfs.Password + case hysteria2.ObfsTypeGecko: + geckoPassword = options.Obfs.Password + geckoMinPacketSize = options.Obfs.GeckoOptions.MinPacketSize + geckoMaxPacketSize = options.Obfs.GeckoOptions.MaxPacketSize + default: + return nil, E.New("unknown obfs type: ", options.Obfs.Type) + } + } + var masqueradeHandler http.Handler + if options.Masquerade != nil && options.Masquerade.Type != "" { + switch options.Masquerade.Type { + case C.Hysterai2MasqueradeTypeFile: + masqueradeHandler = http.FileServer(http.Dir(options.Masquerade.FileOptions.Directory)) + case C.Hysterai2MasqueradeTypeProxy: + masqueradeURL, err := url.Parse(options.Masquerade.ProxyOptions.URL) + if err != nil { + return nil, E.Cause(err, "parse masquerade URL") + } + masqueradeHandler = &httputil.ReverseProxy{ + Rewrite: func(r *httputil.ProxyRequest) { + r.SetURL(masqueradeURL) + if !options.Masquerade.ProxyOptions.RewriteHost { + r.Out.Host = r.In.Host + } + }, + ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) { + w.WriteHeader(http.StatusBadGateway) + }, + } + case C.Hysterai2MasqueradeTypeString: + masqueradeHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if options.Masquerade.StringOptions.StatusCode != 0 { + w.WriteHeader(options.Masquerade.StringOptions.StatusCode) + } + for key, values := range options.Masquerade.StringOptions.Headers { + for _, value := range values { + w.Header().Add(key, value) + } + } + w.Write([]byte(options.Masquerade.StringOptions.Content)) + }) + default: + return nil, E.New("unknown masquerade type: ", options.Masquerade.Type) + } + } + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeHysteria2, tag), + router: router, + logger: logger, + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Listen: options.ListenOptions, + }), + tlsConfig: tlsConfig, + } + var udpTimeout time.Duration + if options.UDPTimeout != 0 { + udpTimeout = time.Duration(options.UDPTimeout) + } else { + udpTimeout = C.UDPTimeout + } + var realmOptions *realm.Options + if options.Realm != nil { + queryOptions, err := adapter.DNSQueryOptionsFrom(ctx, options.Realm.STUNDomainResolver) + if err != nil { + return nil, err + } + httpClientTransport, err := service.FromContext[adapter.HTTPClientManager](ctx).ResolveTransport(ctx, logger, common.PtrValueOrDefault(options.Realm.HTTPClient)) + if err != nil { + return nil, E.Cause(err, "create realm http client") + } + dnsRouter := service.FromContext[adapter.DNSRouter](ctx) + realmOptions = &realm.Options{ + ServerURL: options.Realm.ServerURL, + Token: options.Realm.Token, + RealmID: options.Realm.RealmID, + STUNServers: options.Realm.STUNServers, + HTTPClient: &http.Client{Transport: httpClientTransport}, + Resolver: func(ctx context.Context, host string, ipv4, ipv6 bool) ([]netip.Addr, error) { + dnsOptions := queryOptions + switch { + case ipv4 && !ipv6: + dnsOptions.Strategy = C.DomainStrategyIPv4Only + case !ipv4 && ipv6: + dnsOptions.Strategy = C.DomainStrategyIPv6Only + } + return dnsRouter.Lookup(ctx, host, dnsOptions) + }, + Logger: logger, + } + } + hysteriaService, err := hysteria2.NewService[int](hysteria2.ServiceOptions{ + Context: ctx, + Logger: logger, + BrutalDebug: options.BrutalDebug, + SendBPS: uint64(options.UpMbps * hysteria.MbpsToBps), + ReceiveBPS: uint64(options.DownMbps * hysteria.MbpsToBps), + SalamanderPassword: salamanderPassword, + GeckoPassword: geckoPassword, + GeckoMinPacketSize: geckoMinPacketSize, + GeckoMaxPacketSize: geckoMaxPacketSize, + TLSConfig: tlsConfig, + QUICOptions: qtls.QUICOptions{ + IdleTimeout: options.IdleTimeout.Build(), + KeepAlivePeriod: options.KeepAlivePeriod.Build(), + StreamReceiveWindow: options.StreamReceiveWindow.Value(), + ConnectionReceiveWindow: options.ConnectionReceiveWindow.Value(), + MaxConcurrentStreams: options.MaxConcurrentStreams, + InitialPacketSize: options.InitialPacketSize, + DisablePathMTUDiscovery: options.DisablePathMTUDiscovery, + }, + IgnoreClientBandwidth: options.IgnoreClientBandwidth, + UDPTimeout: udpTimeout, + Handler: inbound, + MasqueradeHandler: masqueradeHandler, + BBRProfile: options.BBRProfile, + RealmOptions: realmOptions, + }) + if err != nil { + return nil, err + } + userList := make([]int, 0, len(options.Users)) + userNameList := make([]string, 0, len(options.Users)) + userPasswordList := make([]string, 0, len(options.Users)) + for index, user := range options.Users { + userList = append(userList, index) + userNameList = append(userNameList, user.Name) + userPasswordList = append(userPasswordList, user.Password) + } + hysteriaService.UpdateUsers(userList, userPasswordList) + inbound.service = hysteriaService + inbound.userNameList = userNameList + return inbound, nil +} + +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + ctx = log.ContextWithNewID(ctx) + var metadata adapter.InboundContext + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + //nolint:staticcheck + metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck + metadata.OriginDestination = h.listener.UDPAddr() + metadata.Source = source + metadata.Destination = destination + h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) + userID, _ := auth.UserFromContext[int](ctx) + if userName := h.userNameList[userID]; userName != "" { + metadata.User = userName + h.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", metadata.Destination) + } else { + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + } + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + ctx = log.ContextWithNewID(ctx) + var metadata adapter.InboundContext + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + //nolint:staticcheck + metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck + metadata.OriginDestination = h.listener.UDPAddr() + metadata.Source = source + metadata.Destination = destination + h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) + userID, _ := auth.UserFromContext[int](ctx) + if userName := h.userNameList[userID]; userName != "" { + metadata.User = userName + h.logger.InfoContext(ctx, "[", userName, "] inbound packet connection to ", metadata.Destination) + } else { + h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + } + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} + +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + if h.tlsConfig != nil { + err := h.tlsConfig.Start() + if err != nil { + return err + } + } + packetConn, err := h.listener.ListenUDP() + if err != nil { + return err + } + return h.service.Start(packetConn) +} + +func (h *Inbound) InterfaceUpdated() { + h.service.Reset() +} + +func (h *Inbound) Close() error { + return common.Close( + h.listener, + h.tlsConfig, + common.PtrOrNil(h.service), + ) +} diff --git a/protocol/hysteria2/outbound.go b/protocol/hysteria2/outbound.go new file mode 100644 index 0000000000..7651a0c653 --- /dev/null +++ b/protocol/hysteria2/outbound.go @@ -0,0 +1,204 @@ +package hysteria2 + +import ( + "context" + "net" + "net/http" + "net/netip" + "net/url" + "os" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/protocol/tuic" + qtls "github.com/sagernet/sing-quic" + "github.com/sagernet/sing-quic/hysteria" + "github.com/sagernet/sing-quic/hysteria2" + "github.com/sagernet/sing-quic/hysteria2/realm" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" +) + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.Hysteria2OutboundOptions](registry, C.TypeHysteria2, NewOutbound) +} + +var ( + _ adapter.Outbound = (*tuic.Outbound)(nil) + _ adapter.InterfaceUpdateListener = (*tuic.Outbound)(nil) +) + +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger + client *hysteria2.Client +} + +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2OutboundOptions) (adapter.Outbound, error) { + options.UDPFragmentDefault = true + if options.TLS == nil || !options.TLS.Enabled { + return nil, C.ErrTLSRequired + } + tlsServerAddress, tlsOptions, err := outboundTLSOptions(options) + if err != nil { + return nil, err + } + tlsConfig, err := tls.NewClient(ctx, logger, tlsServerAddress, tlsOptions) + if err != nil { + return nil, err + } + var salamanderPassword string + var geckoPassword string + var geckoMinPacketSize, geckoMaxPacketSize int + if options.Obfs != nil { + if options.Obfs.Password == "" { + return nil, E.New("missing obfs password") + } + switch options.Obfs.Type { + case hysteria2.ObfsTypeSalamander: + salamanderPassword = options.Obfs.Password + case hysteria2.ObfsTypeGecko: + geckoPassword = options.Obfs.Password + geckoMinPacketSize = options.Obfs.GeckoOptions.MinPacketSize + geckoMaxPacketSize = options.Obfs.GeckoOptions.MaxPacketSize + default: + return nil, E.New("unknown obfs type: ", options.Obfs.Type) + } + } + outboundDialer, err := dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: options.DialerOptions, + RemoteIsDomain: options.ServerIsDomain(), + }) + if err != nil { + return nil, err + } + var realmOptions *realm.Options + if options.Realm != nil { + queryOptions, err := adapter.DNSQueryOptionsFrom(ctx, options.DialerOptions.DomainResolver) + if err != nil { + return nil, err + } + httpClientTransport, err := service.FromContext[adapter.HTTPClientManager](ctx).ResolveTransport(ctx, logger, common.PtrValueOrDefault(options.Realm.HTTPClient)) + if err != nil { + return nil, E.Cause(err, "create realm http client") + } + dnsRouter := service.FromContext[adapter.DNSRouter](ctx) + realmOptions = &realm.Options{ + ServerURL: options.Realm.ServerURL, + Token: options.Realm.Token, + RealmID: options.Realm.RealmID, + STUNServers: options.Realm.STUNServers, + HTTPClient: &http.Client{Transport: httpClientTransport}, + Resolver: func(ctx context.Context, host string, ipv4, ipv6 bool) ([]netip.Addr, error) { + dnsOptions := queryOptions + switch { + case ipv4 && !ipv6: + dnsOptions.Strategy = C.DomainStrategyIPv4Only + case !ipv4 && ipv6: + dnsOptions.Strategy = C.DomainStrategyIPv6Only + } + return dnsRouter.Lookup(ctx, host, dnsOptions) + }, + Logger: logger, + } + } + networkList := options.Network.Build() + client, err := hysteria2.NewClient(hysteria2.ClientOptions{ + Context: ctx, + Dialer: outboundDialer, + Logger: logger, + BrutalDebug: options.BrutalDebug, + ServerAddress: options.ServerOptions.Build(), + ServerPorts: options.ServerPorts, + HopInterval: time.Duration(options.HopInterval), + HopIntervalMax: time.Duration(options.HopIntervalMax), + SendBPS: uint64(options.UpMbps * hysteria.MbpsToBps), + ReceiveBPS: uint64(options.DownMbps * hysteria.MbpsToBps), + SalamanderPassword: salamanderPassword, + GeckoPassword: geckoPassword, + GeckoMinPacketSize: geckoMinPacketSize, + GeckoMaxPacketSize: geckoMaxPacketSize, + Password: options.Password, + TLSConfig: tlsConfig, + QUICOptions: qtls.QUICOptions{ + IdleTimeout: options.IdleTimeout.Build(), + KeepAlivePeriod: options.KeepAlivePeriod.Build(), + StreamReceiveWindow: options.StreamReceiveWindow.Value(), + ConnectionReceiveWindow: options.ConnectionReceiveWindow.Value(), + MaxConcurrentStreams: options.MaxConcurrentStreams, + InitialPacketSize: options.InitialPacketSize, + DisablePathMTUDiscovery: options.DisablePathMTUDiscovery, + }, + UDPDisabled: !common.Contains(networkList, N.NetworkUDP), + BBRProfile: options.BBRProfile, + RealmOptions: realmOptions, + }) + if err != nil { + return nil, err + } + return &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeHysteria2, tag, networkList, options.DialerOptions), + logger: logger, + client: client, + }, nil +} + +func outboundTLSOptions(options option.Hysteria2OutboundOptions) (string, option.OutboundTLSOptions, error) { + tlsOptions := common.PtrValueOrDefault(options.TLS) + if options.Realm == nil { + return options.Server, tlsOptions, nil + } + if options.Server != "" || options.ServerPort != 0 || len(options.ServerPorts) > 0 { + return "", tlsOptions, E.New("realm conflicts with server, server_port, and server_ports") + } + serverURL, err := url.Parse(options.Realm.ServerURL) + if err != nil { + return "", tlsOptions, E.Cause(err, "parse realm server_url") + } + serverName := serverURL.Hostname() + if serverName == "" { + return "", tlsOptions, E.New("missing host in realm server_url") + } + return serverName, tlsOptions, nil +} + +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + switch N.NetworkName(network) { + case N.NetworkTCP: + h.logger.InfoContext(ctx, "outbound connection to ", destination) + return h.client.DialConn(ctx, destination) + case N.NetworkUDP: + conn, err := h.ListenPacket(ctx, destination) + if err != nil { + return nil, err + } + return bufio.NewBindPacketConn(conn, destination), nil + default: + return nil, E.New("unsupported network: ", network) + } +} + +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + h.logger.InfoContext(ctx, "outbound packet connection to ", destination) + return h.client.ListenPacket(ctx) +} + +func (h *Outbound) InterfaceUpdated() { + h.client.CloseWithError(E.New("network changed")) +} + +func (h *Outbound) Close() error { + return h.client.CloseWithError(os.ErrClosed) +} diff --git a/protocol/hysteria2/realm.go b/protocol/hysteria2/realm.go new file mode 100644 index 0000000000..5b7a82e84f --- /dev/null +++ b/protocol/hysteria2/realm.go @@ -0,0 +1,161 @@ +package hysteria2 + +import ( + "context" + "errors" + "net" + "net/http" + "time" + + "github.com/sagernet/sing-box/adapter" + boxService "github.com/sagernet/sing-box/adapter/service" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + N "github.com/sagernet/sing/common/network" + aTLS "github.com/sagernet/sing/common/tls" + sHTTP "github.com/sagernet/sing/protocol/http" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" + "github.com/go-chi/render" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" +) + +func RegisterRealmService(registry *boxService.Registry) { + boxService.Register[option.HysteriaRealmServiceOptions](registry, C.TypeHysteriaRealm, NewRealmService) +} + +type RealmService struct { + boxService.Adapter + ctx context.Context + cancel context.CancelFunc + logger log.ContextLogger + listener *listener.Listener + tlsConfig tls.ServerConfig + httpServer *http.Server + server *server +} + +func NewRealmService(ctx context.Context, logger log.ContextLogger, tag string, options option.HysteriaRealmServiceOptions) (adapter.Service, error) { + if len(options.Users) == 0 { + return nil, E.New("missing users") + } + tokenMap := make(map[string]*realmUser, len(options.Users)) + for i, user := range options.Users { + if user.Name == "" { + return nil, E.New("missing name for user[", i, "]") + } + if user.Token == "" { + return nil, E.New("missing token for user[", i, "]") + } + tokenMap[user.Token] = &realmUser{ + name: user.Name, + maxRealms: user.MaxRealms, + } + } + server := newServer(logger, tokenMap) + ctx, cancel := context.WithCancel(ctx) + chiRouter := chi.NewRouter() + chiRouter.Use(middleware.RequestSize(maxRequestBodyBytes)) + chiRouter.Use(func(handler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + logger.DebugContext(r.Context(), r.Method, " ", r.RequestURI, " ", sHTTP.SourceAddress(r)) + handler.ServeHTTP(w, r) + }) + }) + chiRouter.Route("/v1/{id}", func(r chi.Router) { + r.Use(validateRealmID) + r.With(server.authUser).Post("/", server.handleRegister) + r.With(server.authSession).Delete("/", server.handleDeregister) + r.With(server.authSession).Get("/events", server.handleEvents) + r.With(server.authSession).Post("/heartbeat", server.handleHeartbeat) + r.With(server.authUser).Post("/connect", server.handleConnect) + r.With(server.authSession).Post("/connects/{nonce}", server.handleConnectResponse) + }) + chiRouter.NotFound(func(w http.ResponseWriter, r *http.Request) { + render.Status(r, http.StatusNotFound) + render.JSON(w, r, render.M{"error": "not_found", "message": "unknown path"}) + }) + chiRouter.MethodNotAllowed(func(w http.ResponseWriter, r *http.Request) { + render.Status(r, http.StatusMethodNotAllowed) + render.JSON(w, r, render.M{"error": "bad_request", "message": "method not allowed"}) + }) + s := &RealmService{ + Adapter: boxService.NewAdapter(C.TypeHysteriaRealm, tag), + ctx: ctx, + cancel: cancel, + logger: logger, + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + }), + httpServer: &http.Server{ + Handler: h2c.NewHandler(chiRouter, &http2.Server{ + IdleTimeout: time.Duration(options.IdleTimeout), + ReadIdleTimeout: time.Duration(options.KeepAlivePeriod), + MaxUploadBufferPerStream: int32(options.StreamReceiveWindow.Value()), + MaxUploadBufferPerConnection: int32(options.ConnectionReceiveWindow.Value()), + MaxConcurrentStreams: uint32(options.MaxConcurrentStreams), + }), + ConnContext: func(ctx context.Context, _ net.Conn) context.Context { + return log.ContextWithNewID(ctx) + }, + }, + server: server, + } + if options.TLS != nil { + tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + s.tlsConfig = tlsConfig + } + return s, nil +} + +func (s *RealmService) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + if s.tlsConfig != nil { + err := s.tlsConfig.Start() + if err != nil { + return E.Cause(err, "create TLS config") + } + } + tcpListener, err := s.listener.ListenTCP() + if err != nil { + return err + } + if s.tlsConfig != nil { + if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) { + s.tlsConfig.SetNextProtos(append([]string{"h2"}, s.tlsConfig.NextProtos()...)) + } + tcpListener = aTLS.NewListener(tcpListener, s.tlsConfig) + } + go func() { + err = s.httpServer.Serve(tcpListener) + if err != nil && !errors.Is(err, http.ErrServerClosed) { + s.logger.Error("serve error: ", err) + } + }() + return nil +} + +func (s *RealmService) Close() error { + s.cancel() + err := common.Close(common.PtrOrNil(s.httpServer)) + s.server.closeAll() + return E.Errors(err, common.Close( + common.PtrOrNil(s.listener), + s.tlsConfig, + )) +} diff --git a/protocol/hysteria2/realm_server.go b/protocol/hysteria2/realm_server.go new file mode 100644 index 0000000000..5143b86b6e --- /dev/null +++ b/protocol/hysteria2/realm_server.go @@ -0,0 +1,536 @@ +package hysteria2 + +import ( + "context" + "crypto/rand" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/netip" + "regexp" + "strings" + "sync" + "time" + + "github.com/sagernet/sing-box/log" + E "github.com/sagernet/sing/common/exceptions" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" +) + +const ( + sessionTTL = time.Minute + realmNamePattern = `^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$` + maxRequestBodyBytes = 4 << 10 + maxAddresses = 8 + nonceHexLength = 32 + obfsHexLength = 64 + eventChannelSize = 16 + maxPendingAttempts = 16 + connectResponseTimeout = 10 * time.Second +) + +var realmPattern = regexp.MustCompile(realmNamePattern) + +type contextKey int + +const ( + contextKeyUser contextKey = iota + contextKeySession +) + +type realmUser struct { + name string + maxRealms int +} + +type realmSession struct { + id string + realmID string + username string + addresses []string + expires time.Time + events chan realmEvent + timer *time.Timer + done chan struct{} + closed bool + pending map[string]chan punchResponsePayload +} + +type realmEvent struct { + kind string + data any +} + +type punchEvent struct { + Addresses []string `json:"addresses"` + Nonce string `json:"nonce"` + Obfs string `json:"obfs"` +} + +type punchResponsePayload struct { + addresses []string +} + +type server struct { + access sync.Mutex + realms map[string]*realmSession + sessions map[string]*realmSession + userCounts map[string]int + logger log.ContextLogger + tokenMap map[string]*realmUser +} + +func newServer(logger log.ContextLogger, tokenMap map[string]*realmUser) *server { + return &server{ + realms: make(map[string]*realmSession), + sessions: make(map[string]*realmSession), + userCounts: make(map[string]int), + logger: logger, + tokenMap: tokenMap, + } +} + +func validateRealmID(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + id := chi.URLParam(r, "id") + if !realmPattern.MatchString(id) { + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, render.M{"error": "bad_request", "message": "invalid realm name"}) + return + } + next.ServeHTTP(w, r) + }) +} + +func (s *server) authBearer(name string, key contextKey, lookup func(r *http.Request, token string) (any, bool)) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + header := r.Header.Get("Authorization") + bearer, token, found := strings.Cut(header, " ") + if bearer != "Bearer" || !found { + render.Status(r, http.StatusUnauthorized) + render.JSON(w, r, render.M{"error": "invalid_token", "message": "invalid " + name + " token"}) + return + } + value, authenticated := lookup(r, token) + if !authenticated { + render.Status(r, http.StatusUnauthorized) + render.JSON(w, r, render.M{"error": "invalid_token", "message": "invalid " + name + " token"}) + return + } + next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), key, value))) + }) + } +} + +func (s *server) authUser(next http.Handler) http.Handler { + return s.authBearer("realm", contextKeyUser, func(_ *http.Request, token string) (any, bool) { + user, authenticated := s.tokenMap[token] + return user, authenticated + })(next) +} + +func (s *server) authSession(next http.Handler) http.Handler { + return s.authBearer("session", contextKeySession, func(r *http.Request, token string) (any, bool) { + sess := s.getSessionByToken(token) + if sess == nil || sess.realmID != chi.URLParam(r, "id") { + return nil, false + } + return sess, true + })(next) +} + +func (s *server) getSessionByToken(token string) *realmSession { + s.access.Lock() + defer s.access.Unlock() + sess := s.sessions[token] + if sess == nil || sess.closed || time.Now().After(sess.expires) { + return nil + } + return sess +} + +func (s *server) removeSessionLocked(sess *realmSession) { + if sess.closed { + return + } + sess.closed = true + close(sess.done) + if s.realms[sess.realmID] == sess { + delete(s.realms, sess.realmID) + } + if _, found := s.sessions[sess.id]; found { + s.userCounts[sess.username]-- + if s.userCounts[sess.username] <= 0 { + delete(s.userCounts, sess.username) + } + } + delete(s.sessions, sess.id) + sess.timer.Stop() + close(sess.events) + for nonce, ch := range sess.pending { + close(ch) + delete(sess.pending, nonce) + } +} + +func (s *server) removeSession(sess *realmSession) { + s.access.Lock() + defer s.access.Unlock() + s.removeSessionLocked(sess) +} + +func (s *server) removeExpiredSession(sess *realmSession) bool { + s.access.Lock() + defer s.access.Unlock() + if sess.closed || !time.Now().After(sess.expires) { + return false + } + s.removeSessionLocked(sess) + return true +} + +func (s *server) closeAll() { + s.access.Lock() + defer s.access.Unlock() + for _, sess := range s.sessions { + s.removeSessionLocked(sess) + } +} + +func (s *server) registerPending(sess *realmSession, nonce string) (chan punchResponsePayload, bool) { + s.access.Lock() + defer s.access.Unlock() + if sess.closed || len(sess.pending) >= maxPendingAttempts { + return nil, false + } + if _, exists := sess.pending[nonce]; exists { + return nil, false + } + ch := make(chan punchResponsePayload, 1) + sess.pending[nonce] = ch + return ch, true +} + +func (s *server) deliverPending(sess *realmSession, nonce string, payload punchResponsePayload) bool { + s.access.Lock() + defer s.access.Unlock() + if sess.closed { + return false + } + ch, found := sess.pending[nonce] + if !found { + return false + } + delete(sess.pending, nonce) + select { + case ch <- payload: + default: + } + return true +} + +func (s *server) cancelPending(sess *realmSession, nonce string) { + s.access.Lock() + defer s.access.Unlock() + delete(sess.pending, nonce) +} + +func (s *server) sendEvent(sess *realmSession, ev realmEvent) bool { + s.access.Lock() + defer s.access.Unlock() + if sess.closed { + return false + } + select { + case sess.events <- ev: + return true + default: + return false + } +} + +func (s *server) handleRegister(w http.ResponseWriter, r *http.Request) { + user := r.Context().Value(contextKeyUser).(*realmUser) + id := chi.URLParam(r, "id") + var req struct { + Addresses []string `json:"addresses"` + } + err := render.DecodeJSON(r.Body, &req) + if err != nil { + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, render.M{"error": "bad_request", "message": "invalid json"}) + return + } + err = validateAddresses(req.Addresses) + if err != nil { + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, render.M{"error": "bad_request", "message": err.Error()}) + return + } + s.access.Lock() + if _, exists := s.realms[id]; exists { + s.access.Unlock() + render.Status(r, http.StatusConflict) + render.JSON(w, r, render.M{"error": "realm_taken", "message": "realm already registered"}) + return + } + if user.maxRealms > 0 && s.userCounts[user.name] >= user.maxRealms { + s.access.Unlock() + render.Status(r, http.StatusTooManyRequests) + render.JSON(w, r, render.M{"error": "realm_limit_reached", "message": "per-user realm limit reached"}) + return + } + var b [16]byte + _, err = rand.Read(b[:]) + if err != nil { + s.access.Unlock() + render.Status(r, http.StatusInternalServerError) + render.JSON(w, r, render.M{"error": "internal", "message": "entropy failure"}) + return + } + sess := &realmSession{ + id: hex.EncodeToString(b[:]), + realmID: id, + username: user.name, + addresses: append([]string(nil), req.Addresses...), + expires: time.Now().Add(sessionTTL), + events: make(chan realmEvent, eventChannelSize), + done: make(chan struct{}), + pending: make(map[string]chan punchResponsePayload), + } + s.realms[id] = sess + s.sessions[sess.id] = sess + s.userCounts[user.name]++ + sess.timer = time.AfterFunc(sessionTTL, func() { + if s.removeExpiredSession(sess) { + s.logger.Debug("[", sess.username, "] session expired realm=", sess.realmID) + } + }) + s.access.Unlock() + s.logger.InfoContext(r.Context(), "[", user.name, "] registered realm=", id) + render.JSON(w, r, render.M{ + "session_id": sess.id, + "ttl": int(sessionTTL.Seconds()), + }) +} + +func (s *server) handleDeregister(w http.ResponseWriter, r *http.Request) { + sess := r.Context().Value(contextKeySession).(*realmSession) + s.logger.InfoContext(r.Context(), "[", sess.username, "] deregistered realm=", sess.realmID) + s.removeSession(sess) + render.NoContent(w, r) +} + +func (s *server) handleHeartbeat(w http.ResponseWriter, r *http.Request) { + sess := r.Context().Value(contextKeySession).(*realmSession) + var req struct { + Addresses []string `json:"addresses"` + } + err := render.DecodeJSON(r.Body, &req) + if err != nil && !errors.Is(err, io.EOF) { + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, render.M{"error": "bad_request", "message": "invalid json"}) + return + } + if req.Addresses != nil { + err = validateAddresses(req.Addresses) + if err != nil { + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, render.M{"error": "bad_request", "message": err.Error()}) + return + } + } + s.access.Lock() + sess.expires = time.Now().Add(sessionTTL) + if req.Addresses != nil { + sess.addresses = append([]string(nil), req.Addresses...) + } + sess.timer.Reset(sessionTTL) + s.access.Unlock() + s.logger.DebugContext(r.Context(), "[", sess.username, "] heartbeat realm=", sess.realmID) + s.sendEvent(sess, realmEvent{kind: "heartbeat_ack", data: render.M{"ttl": int(sessionTTL.Seconds())}}) + render.JSON(w, r, render.M{"ttl": int(sessionTTL.Seconds())}) +} + +func (s *server) handleEvents(w http.ResponseWriter, r *http.Request) { + sess := r.Context().Value(contextKeySession).(*realmSession) + flusher, supportsFlusher := w.(http.Flusher) + if !supportsFlusher { + render.Status(r, http.StatusInternalServerError) + render.JSON(w, r, render.M{"error": "internal", "message": "streaming unsupported"}) + return + } + w.Header().Set("Content-Type", "text/event-stream") + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("Connection", "keep-alive") + w.WriteHeader(http.StatusOK) + flusher.Flush() + ctx := r.Context() + for { + select { + case <-ctx.Done(): + return + case ev, open := <-sess.events: + if !open { + return + } + data, _ := json.Marshal(ev.data) + fmt.Fprintf(w, "event: %s\ndata: %s\n\n", ev.kind, data) + flusher.Flush() + } + } +} + +func (s *server) handleConnect(w http.ResponseWriter, r *http.Request) { + user := r.Context().Value(contextKeyUser).(*realmUser) + id := chi.URLParam(r, "id") + var req struct { + Addresses []string `json:"addresses"` + Nonce string `json:"nonce"` + Obfs string `json:"obfs"` + } + err := render.DecodeJSON(r.Body, &req) + if err != nil { + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, render.M{"error": "bad_request", "message": "invalid json"}) + return + } + err = validateAddresses(req.Addresses) + if err != nil { + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, render.M{"error": "bad_request", "message": err.Error()}) + return + } + err = validateHexField("nonce", req.Nonce, nonceHexLength) + if err != nil { + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, render.M{"error": "bad_request", "message": err.Error()}) + return + } + err = validateHexField("obfs", req.Obfs, obfsHexLength) + if err != nil { + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, render.M{"error": "bad_request", "message": err.Error()}) + return + } + s.access.Lock() + // Any authenticated realm user may connect to a registered realm. The user name + // is for logging and per-user registration quota, not an ownership boundary here. + sess := s.realms[id] + if sess == nil || sess.closed || time.Now().After(sess.expires) { + s.access.Unlock() + render.Status(r, http.StatusNotFound) + render.JSON(w, r, render.M{"error": "realm_not_found", "message": "realm not registered"}) + return + } + serverAddresses := append([]string(nil), sess.addresses...) + s.access.Unlock() + + respCh, ready := s.registerPending(sess, req.Nonce) + if !ready { + render.Status(r, http.StatusServiceUnavailable) + render.JSON(w, r, render.M{"error": "rate_limited", "message": "too many in-flight connect attempts"}) + return + } + defer s.cancelPending(sess, req.Nonce) + + if !s.sendEvent(sess, realmEvent{kind: "punch", data: punchEvent{Addresses: req.Addresses, Nonce: req.Nonce, Obfs: req.Obfs}}) { + render.Status(r, http.StatusServiceUnavailable) + render.JSON(w, r, render.M{"error": "rate_limited", "message": "server event buffer full"}) + return + } + s.logger.DebugContext(r.Context(), "[", user.name, "] connect realm=", id) + + timer := time.NewTimer(connectResponseTimeout) + defer timer.Stop() + select { + case payload, open := <-respCh: + if !open { + render.Status(r, http.StatusNotFound) + render.JSON(w, r, render.M{"error": "realm_not_found", "message": "realm not registered"}) + return + } + if len(payload.addresses) > 0 { + serverAddresses = payload.addresses + } + case <-timer.C: + case <-sess.done: + render.Status(r, http.StatusNotFound) + render.JSON(w, r, render.M{"error": "realm_not_found", "message": "realm not registered"}) + return + case <-r.Context().Done(): + return + } + render.JSON(w, r, render.M{ + "addresses": serverAddresses, + "nonce": req.Nonce, + "obfs": req.Obfs, + }) +} + +func (s *server) handleConnectResponse(w http.ResponseWriter, r *http.Request) { + sess := r.Context().Value(contextKeySession).(*realmSession) + nonce := chi.URLParam(r, "nonce") + err := validateHexField("nonce", nonce, nonceHexLength) + if err != nil { + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, render.M{"error": "bad_request", "message": err.Error()}) + return + } + var req struct { + Addresses []string `json:"addresses"` + } + err = render.DecodeJSON(r.Body, &req) + if err != nil { + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, render.M{"error": "bad_request", "message": "invalid json"}) + return + } + err = validateAddresses(req.Addresses) + if err != nil { + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, render.M{"error": "bad_request", "message": err.Error()}) + return + } + delivered := s.deliverPending(sess, nonce, punchResponsePayload{addresses: append([]string(nil), req.Addresses...)}) + if !delivered { + render.Status(r, http.StatusNotFound) + render.JSON(w, r, render.M{"error": "attempt_not_found", "message": "no pending attempt for nonce"}) + return + } + s.logger.DebugContext(r.Context(), "[", sess.username, "] connect-response realm=", sess.realmID) + render.NoContent(w, r) +} + +func validateAddresses(addresses []string) error { + if len(addresses) == 0 { + return E.New("at least one address required") + } + if len(addresses) > maxAddresses { + return E.New("too many addresses (max ", maxAddresses, ")") + } + for _, address := range addresses { + _, err := netip.ParseAddrPort(address) + if err != nil { + return E.New("invalid address: ", address) + } + } + return nil +} + +func validateHexField(name, value string, length int) error { + if len(value) != length { + return E.New(name, " must be ", length, " hex characters") + } + _, err := hex.DecodeString(value) + if err != nil { + return E.New(name, " must be valid hex") + } + return nil +} diff --git a/protocol/masque/config.go b/protocol/masque/config.go new file mode 100644 index 0000000000..11aa52daf7 --- /dev/null +++ b/protocol/masque/config.go @@ -0,0 +1,89 @@ +package masque + +import ( + "crypto/ecdsa" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "fmt" + "net" +) + +type Config struct { + PrivateKey string `json:"private_key"` // Base64-encoded ECDSA private key + EndpointV4 string `json:"endpoint_v4"` // IPv4 address of the endpoint + EndpointV6 string `json:"endpoint_v6"` // IPv6 address of the endpoint + EndpointH2V4 string `json:"endpoint_h2_v4"` // IPv4 address used in HTTP/2 mode + EndpointH2V6 string `json:"endpoint_h2_v6"` // IPv6 address used in HTTP/2 mode + EndpointPubKey string `json:"endpoint_pub_key"` // PEM-encoded ECDSA public key of the endpoint to verify against + License string `json:"license"` // Application license key + ID string `json:"id"` // Device unique identifier + AccessToken string `json:"access_token"` // Authentication token for API access + IPv4 string `json:"ipv4"` // Assigned IPv4 address + IPv6 string `json:"ipv6"` // Assigned IPv6 address +} + +func (c *Config) GetEcPrivateKey() (*ecdsa.PrivateKey, error) { + privKeyB64, err := base64.StdEncoding.DecodeString(c.PrivateKey) + if err != nil { + return nil, fmt.Errorf("failed to decode private key: %v", err) + } + privKey, err := x509.ParseECPrivateKey(privKeyB64) + if err != nil { + return nil, fmt.Errorf("failed to parse private key: %v", err) + } + return privKey, nil +} + +func (c *Config) GetEcEndpointPublicKey() (*ecdsa.PublicKey, error) { + endpointPubKeyB64, _ := pem.Decode([]byte(c.EndpointPubKey)) + if endpointPubKeyB64 == nil { + return nil, fmt.Errorf("failed to decode endpoint public key") + } + + pubKey, err := x509.ParsePKIXPublicKey(endpointPubKeyB64.Bytes) + if err != nil { + return nil, fmt.Errorf("failed to parse public key: %v", err) + } + + ecPubKey, ok := pubKey.(*ecdsa.PublicKey) + if !ok { + return nil, fmt.Errorf("failed to assert public key as ECDSA") + } + + return ecPubKey, nil +} + +func (c *Config) SelectEndpointFromConfig(useHTTP2 bool, useIPv6 bool, port int) (net.Addr, error) { + if useHTTP2 { + if useIPv6 { + if c.EndpointH2V6 == "" { + return nil, fmt.Errorf("--http2 with --ipv6 requires config endpoint_h2_v6 to be set") + } + ip := net.ParseIP(c.EndpointH2V6) + if ip == nil { + return nil, fmt.Errorf("invalid endpoint_h2_v6 value %q", c.EndpointH2V6) + } + + return &net.TCPAddr{IP: ip, Port: port}, nil + } + v4 := c.EndpointH2V4 + ip := net.ParseIP(v4) + if ip == nil { + return nil, fmt.Errorf("invalid endpoint_h2_v4 value %q") + } + return &net.TCPAddr{IP: ip, Port: port}, nil + } + if useIPv6 { + ip := net.ParseIP(c.EndpointV6) + if ip == nil { + return nil, fmt.Errorf("invalid endpoint_v6 value %q", c.EndpointV6) + } + return &net.UDPAddr{IP: ip, Port: port}, nil + } + ip := net.ParseIP(c.EndpointV4) + if ip == nil { + return nil, fmt.Errorf("invalid endpoint_v4 value %q", c.EndpointV4) + } + return &net.UDPAddr{IP: ip, Port: port}, nil +} diff --git a/protocol/masque/outbound.go b/protocol/masque/outbound.go new file mode 100644 index 0000000000..41e3a1008a --- /dev/null +++ b/protocol/masque/outbound.go @@ -0,0 +1,300 @@ +package masque + +import ( + "context" + "encoding/base64" + "encoding/json" + "net" + "net/netip" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/common/cloudflare" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/transport/masque" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.MASQUEOutboundOptions](registry, C.TypeMASQUE, NewOutbound) +} + +type Outbound struct { + outbound.Adapter + ctx context.Context + dnsRouter adapter.DNSRouter + logger logger.ContextLogger + options option.MASQUEOutboundOptions + tunnel *masque.Tunnel + startHandler func() + + await chan struct{} +} + +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.MASQUEOutboundOptions) (adapter.Outbound, error) { + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeMASQUE, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions), + ctx: ctx, + dnsRouter: service.FromContext[adapter.DNSRouter](ctx), + logger: logger, + options: options, + await: make(chan struct{}), + } + outbound.startHandler = func() { + defer close(outbound.await) + cacheFile := service.FromContext[adapter.CacheFile](ctx) + var appConfig *Config + var err error + if !options.Profile.Recreate && cacheFile != nil && cacheFile.StoreMASQUEConfig() { + savedProfile := cacheFile.LoadBinary(tag) + if savedProfile != nil { + if err = json.Unmarshal(savedProfile.Content, &appConfig); err != nil { + logger.ErrorContext(ctx, err) + return + } + } + } + if appConfig == nil { + appConfig, err = outbound.createConfig() + if err != nil { + logger.ErrorContext(ctx, err) + return + } + if cacheFile != nil && cacheFile.StoreMASQUEConfig() { + content, err := json.Marshal(appConfig) + if err != nil { + logger.ErrorContext(ctx, err) + return + } + cacheFile.SaveBinary(tag, &adapter.SavedBinary{ + LastUpdated: time.Now(), + Content: content, + LastEtag: "", + }) + } + } + privKey, err := appConfig.GetEcPrivateKey() + if err != nil { + logger.ErrorContext(ctx, E.New("failed to get private key: ", err)) + return + } + peerPubKey, err := appConfig.GetEcEndpointPublicKey() + if err != nil { + logger.ErrorContext(ctx, E.New("failed to get public key: ", err)) + return + } + cert, err := masque.GenerateCert(privKey, &privKey.PublicKey) + if err != nil { + logger.ErrorContext(ctx, E.New("failed to generate cert: ", err)) + return + } + tlsConfig, err := tls.NewMASQUEClient(ctx, logger, "consumer-masque.cloudflareclient.com", cert, privKey, peerPubKey, options.MASQUEOutboundTLSOptions) + if err != nil { + logger.ErrorContext(ctx, E.New("failed to prepare TLS config: ", err)) + return + } + endpoint, err := appConfig.SelectEndpointFromConfig(options.UseHTTP2, options.UseIPv6, 443) + if err != nil { + logger.ErrorContext(ctx, E.New("failed to select endpoint: ", err)) + return + } + var udpTimeout time.Duration + if options.UDPTimeout != 0 { + udpTimeout = time.Duration(options.UDPTimeout) + } else { + udpTimeout = C.UDPTimeout + } + var udpKeepalivePeriod time.Duration + if options.UDPKeepalivePeriod != 0 { + udpKeepalivePeriod = time.Duration(options.UDPKeepalivePeriod) + } else { + udpKeepalivePeriod = time.Second * 30 + } + outboundDialer, err := dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: options.DialerOptions, + RemoteIsDomain: false, + ResolverOnDetour: true, + }) + if err != nil { + logger.ErrorContext(ctx, err) + return + } + tunnel, err := masque.NewTunnel( + ctx, + logger, + masque.TunnelOptions{ + Dialer: outboundDialer, + Address: []netip.Prefix{ + netip.MustParsePrefix(appConfig.IPv4 + "/32"), + netip.MustParsePrefix(appConfig.IPv6 + "/128"), + }, + Endpoint: endpoint, + TLSConfig: tlsConfig, + UseHTTP2: options.UseHTTP2, + UDPTimeout: udpTimeout, + UDPKeepalivePeriod: udpKeepalivePeriod, + UDPInitialPacketSize: options.UDPInitialPacketSize, + ReconnectDelay: options.ReconnectDelay.Build(), + }) + if err != nil { + logger.ErrorContext(ctx, err) + return + } + outbound.tunnel = tunnel + if err = outbound.tunnel.Start(false); err != nil { + logger.ErrorContext(ctx, err) + return + } + if err = outbound.tunnel.Start(true); err != nil { + logger.ErrorContext(ctx, err) + return + } + } + return outbound, nil +} + +func (w *Outbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStatePostStart { + return nil + } + go w.startHandler() + return nil +} + +func (w *Outbound) Close() error { + if err := w.isTunnelInitialized(w.ctx); err != nil { + return err + } + return w.tunnel.Close() +} + +func (w *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + if err := w.isTunnelInitialized(ctx); err != nil { + return nil, err + } + switch network { + case N.NetworkTCP: + w.logger.InfoContext(ctx, "outbound connection to ", destination) + case N.NetworkUDP: + w.logger.InfoContext(ctx, "outbound packet connection to ", destination) + } + if destination.IsDomain() { + destinationAddresses, err := w.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) + if err != nil { + return nil, err + } + return N.DialSerial(ctx, w.tunnel, network, destination, destinationAddresses) + } else if !destination.Addr.IsValid() { + return nil, E.New("invalid destination: ", destination) + } + return w.tunnel.DialContext(ctx, network, destination) +} + +func (w *Outbound) ListenPacketWithDestination(ctx context.Context, destination M.Socksaddr) (net.PacketConn, netip.Addr, error) { + if err := w.isTunnelInitialized(ctx); err != nil { + return nil, netip.Addr{}, err + } + w.logger.InfoContext(ctx, "outbound packet connection to ", destination) + if destination.IsDomain() { + destinationAddresses, err := w.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) + if err != nil { + return nil, netip.Addr{}, err + } + return N.ListenSerial(ctx, w.tunnel, destination, destinationAddresses) + } + packetConn, err := w.tunnel.ListenPacket(ctx, destination) + if err != nil { + return nil, netip.Addr{}, err + } + if destination.IsIP() { + return packetConn, destination.Addr, nil + } + return packetConn, netip.Addr{}, nil +} + +func (w *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + packetConn, destinationAddress, err := w.ListenPacketWithDestination(ctx, destination) + if err != nil { + return nil, err + } + if destinationAddress.IsValid() && destination != M.SocksaddrFrom(destinationAddress, destination.Port) { + return bufio.NewNATPacketConn(bufio.NewPacketConn(packetConn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil + } + return packetConn, nil +} + +func (w *Outbound) isTunnelInitialized(ctx context.Context) error { + select { + case <-w.await: + case <-ctx.Done(): + return ctx.Err() + } + if w.tunnel == nil { + return E.New("tunnel not initialized") + } + return nil +} + +func (w *Outbound) createConfig() (*Config, error) { + opts := make([]cloudflare.CloudflareApiOption, 0, 1) + if w.options.Profile.Detour != "" { + detour, ok := service.FromContext[adapter.OutboundManager](w.ctx).Outbound(w.options.Profile.Detour) + if !ok { + return nil, E.New("outbound detour not found: ", w.options.Profile.Detour) + } + opts = append(opts, cloudflare.WithDialContext(func(ctx context.Context, network, addr string) (net.Conn, error) { + return detour.DialContext(ctx, network, M.ParseSocksaddr(addr)) + })) + } + api := cloudflare.NewCloudflareApi(opts...) + var profile *cloudflare.CloudflareProfile + var err error + if w.options.Profile.AuthToken != "" && w.options.Profile.ID != "" { + profile, err = api.GetProfile4471(w.ctx, w.options.Profile.AuthToken, w.options.Profile.ID) + if err != nil { + return nil, err + } + } else { + wgPrivateKey, err := wgtypes.GeneratePrivateKey() + if err != nil { + return nil, err + } + profile, err = api.CreateProfile(w.ctx, wgPrivateKey.PublicKey().String()) + if err != nil { + return nil, err + } + } + privateKey, publicKey, err := masque.GenerateEcKeyPair() + if err != nil { + return nil, E.New("failed to generate key pair: ", err) + } + updatedProfile, err := api.EnrollKey(w.ctx, profile.Token, profile.ID, cloudflare.KeyTypeMasque, cloudflare.TunTypeMasque, base64.StdEncoding.EncodeToString(publicKey)) + if err != nil { + return nil, err + } + return &Config{ + PrivateKey: base64.StdEncoding.EncodeToString(privateKey), + EndpointV4: updatedProfile.Config.Peers[0].Endpoint.V4[:len(updatedProfile.Config.Peers[0].Endpoint.V4)-2], + EndpointV6: updatedProfile.Config.Peers[0].Endpoint.V6[1 : len(updatedProfile.Config.Peers[0].Endpoint.V6)-3], + EndpointH2V4: cloudflare.DefaultEndpointH2V4, + EndpointH2V6: cloudflare.DefaultEndpointH2V6, + EndpointPubKey: updatedProfile.Config.Peers[0].PublicKey, + License: updatedProfile.Account.License, + ID: updatedProfile.ID, + AccessToken: profile.Token, + IPv4: updatedProfile.Config.Interface.Addresses.V4, + IPv6: updatedProfile.Config.Interface.Addresses.V6, + }, nil +} diff --git a/protocol/mieru/common.go b/protocol/mieru/common.go new file mode 100644 index 0000000000..2aaee81191 --- /dev/null +++ b/protocol/mieru/common.go @@ -0,0 +1,94 @@ +package mieru + +import ( + "fmt" + "strings" + + mierupb "github.com/enfein/mieru/v3/pkg/appctl/appctlpb" + "github.com/sagernet/sing-box/option" +) + +func validateMieruTransport(transport []option.MieruPortBinding) error { + + for _, pr := range transport { + if getTransportProtocol(pr.Protocol) == nil { + return fmt.Errorf("transport must be TCP or UDP") + } + if pr.Port != 0 && pr.PortRange != "" { + return fmt.Errorf("invalid format: both port and port_range should not be set") + } + if pr.Port == 0 && pr.PortRange == "" { + return fmt.Errorf("invalid format: either port or port_range must be set") + } + if pr.Port != 0 { + continue + } + begin, end, err := beginAndEndPortFromPortRange(pr.PortRange) + if err != nil { + return fmt.Errorf("invalid server_ports format") + } + + if begin < 1 || begin > 65535 { + return fmt.Errorf("begin port must be between 1 and 65535") + } + if end < 1 || end > 65535 { + return fmt.Errorf("end port must be between 1 and 65535") + } + if begin > end { + return fmt.Errorf("begin port must be less than or equal to end port") + } + + } + return nil +} + +func beginAndEndPortFromPortRange(portRange string) (int, int, error) { + var begin, end int + + _, err := fmt.Sscanf(portRange, "%d-%d", &begin, &end) + return begin, end, err + +} + +func getTransportProtocol(transport string) *mierupb.TransportProtocol { + switch strings.ToUpper(transport) { + case "TCP": + return mierupb.TransportProtocol_TCP.Enum() + case "UDP": + return mierupb.TransportProtocol_UDP.Enum() + default: + return nil + } +} + +func getHandshakeMode(handshakeMode string) *mierupb.HandshakeMode { + switch strings.ToUpper(handshakeMode) { + case "", "DEFAULT", "HANDSHAKE_DEFAULT": + return mierupb.HandshakeMode_HANDSHAKE_DEFAULT.Enum() + case "NO_WAIT", "NOWAIT", "HANDSHAKE_NO_WAIT": + return mierupb.HandshakeMode_HANDSHAKE_NO_WAIT.Enum() + case "STANDARD", "HANDSHAKE_STANDARD": + return mierupb.HandshakeMode_HANDSHAKE_STANDARD.Enum() + default: + return nil + } +} + +func getMultiplexingLevel(multiplexingLevel string) *mierupb.MultiplexingLevel { + switch strings.ToUpper(multiplexingLevel) { + case "", "DEFAULT", "MULTIPLEXING_DEFAULT": + return mierupb.MultiplexingLevel_MULTIPLEXING_DEFAULT.Enum() + + case "LOW", "MULTIPLEXING_LOW": + return mierupb.MultiplexingLevel_MULTIPLEXING_LOW.Enum() + + case "MEDIUM", "MULTIPLEXING_MEDIUM", "MIDDLE", "MULTIPLEXING_MIDDLE": + return mierupb.MultiplexingLevel_MULTIPLEXING_MIDDLE.Enum() + + case "HIGH", "MULTIPLEXING_HIGH": + return mierupb.MultiplexingLevel_MULTIPLEXING_HIGH.Enum() + + default: + return nil + } +} diff --git a/protocol/mieru/inbound.go b/protocol/mieru/inbound.go new file mode 100644 index 0000000000..b0f6590e10 --- /dev/null +++ b/protocol/mieru/inbound.go @@ -0,0 +1,319 @@ +package mieru + +import ( + "context" + "fmt" + "io" + "net" + "net/netip" + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/uot" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + mierucommon "github.com/enfein/mieru/v3/apis/common" + mieruconstant "github.com/enfein/mieru/v3/apis/constant" + mierumodel "github.com/enfein/mieru/v3/apis/model" + mieruserver "github.com/enfein/mieru/v3/apis/server" + mierupb "github.com/enfein/mieru/v3/pkg/appctl/appctlpb" + "google.golang.org/protobuf/proto" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.MieruInboundOptions](registry, C.TypeMieru, NewInbound) +} + +type Inbound struct { + inbound.Adapter + ctx context.Context + router adapter.ConnectionRouterEx + logger log.ContextLogger + listener *listener.Listener + server mieruserver.Server + userNames []string + + mu sync.Mutex +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.MieruInboundOptions) (adapter.Inbound, error) { + config, userNames, err := buildMieruServerConfig(ctx, options) + if err != nil { + return nil, fmt.Errorf("failed to build mieru server config: %w", err) + } + + s := mieruserver.NewServer() + if err := s.Store(config); err != nil { + return nil, fmt.Errorf("failed to store mieru server config: %w", err) + } + + inboundInstance := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeMieru, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, + server: s, + userNames: userNames, + } + inboundInstance.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: options.Network.Build(), + Listen: options.ListenOptions, + }) + + return inboundInstance, nil +} + +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + + h.mu.Lock() + defer h.mu.Unlock() + + if err := h.server.Start(); err != nil { + return fmt.Errorf("failed to start mieru server: %w", err) + } + + h.logger.Info("mieru server is started") + go h.acceptLoop() + return nil +} + +func (h *Inbound) Close() error { + h.mu.Lock() + defer h.mu.Unlock() + + if h.server.IsRunning() { + return h.server.Stop() + } + return nil +} + +func (h *Inbound) acceptLoop() { + for { + conn, request, err := h.server.Accept() + if err != nil { + if !h.server.IsRunning() { + return + } + h.logger.Debug("failed to accept mieru connection: ", err) + continue + } + go h.handleConnection(conn, request) + } +} + +func (h *Inbound) handleConnection(conn net.Conn, request *mierumodel.Request) { + ctx := log.ContextWithNewID(h.ctx) + + // Send fake SOCKS5 response back to proxy client. + resp := &mierumodel.Response{ + Reply: mieruconstant.Socks5ReplySuccess, + BindAddr: mierumodel.AddrSpec{ + IP: net.IPv4zero, + Port: 0, + }, + } + if err := resp.WriteToSocks5(conn); err != nil { + conn.Close() + h.logger.DebugContext(ctx, "failed to write mieru response: ", err) + return + } + + // Build metadata. + var metadata adapter.InboundContext + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + //nolint:staticcheck + metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + + // Parse source address. + if remoteAddr := conn.RemoteAddr(); remoteAddr != nil { + metadata.Source = M.SocksaddrFromNet(remoteAddr) + } + + // Parse destination from request. + if request.DstAddr.FQDN != "" { + metadata.Destination = M.Socksaddr{ + Fqdn: request.DstAddr.FQDN, + Port: uint16(request.DstAddr.Port), + } + } else if request.DstAddr.IP != nil { + addr, _ := netip.AddrFromSlice(request.DstAddr.IP) + metadata.Destination = M.Socksaddr{ + Addr: addr.Unmap(), + Port: uint16(request.DstAddr.Port), + } + } + + // Get username from connection. + if userCtx, ok := conn.(mierucommon.UserContext); ok { + metadata.User = userCtx.UserName() + } + + // Handle request. + switch request.Command { + case mieruconstant.Socks5ConnectCmd: + h.logger.InfoContext(ctx, "inbound TCP connection from ", metadata.Source, " to ", metadata.Destination) + if metadata.User != "" { + h.logger.InfoContext(ctx, "[", metadata.User, "] inbound TCP connection") + } + h.router.RouteConnectionEx(ctx, conn, metadata, nil) + case mieruconstant.Socks5UDPAssociateCmd: + h.logger.InfoContext(ctx, "inbound UDP connection from ", metadata.Source, " to ", metadata.Destination) + if metadata.User != "" { + h.logger.InfoContext(ctx, "[", metadata.User, "] inbound UDP connection") + } + h.handleUDP(ctx, conn, metadata) + default: + conn.Close() + h.logger.WarnContext(ctx, "unsupported mieru command: ", request.Command) + } +} + +func (h *Inbound) handleUDP(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) { + pc := mierucommon.NewPacketOverStreamTunnel(conn) + packetConn := &mieruPacketConn{ + PacketConn: pc, + destination: metadata.Destination, + } + h.router.RoutePacketConnectionEx(ctx, packetConn, metadata, nil) +} + +// mieruPacketConn wraps mieru's PacketConn to implement N.PacketConn +type mieruPacketConn struct { + net.PacketConn + destination M.Socksaddr +} + +var _ N.PacketConn = (*mieruPacketConn)(nil) + +// ReadPacket parses the SOCKS5 UDP header and returns the destination address. +func (c *mieruPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) { + n, _, err := c.PacketConn.ReadFrom(buffer.FreeBytes()) + if err != nil { + return M.Socksaddr{}, err + } + buffer.Truncate(n) + if buffer.Len() < 3 { + return M.Socksaddr{}, io.ErrShortBuffer + } + + // Skip RSV (2 bytes) and FRAG (1 byte). + buffer.Advance(3) + + var addr mierumodel.AddrSpec + if err := addr.ReadFromSocks5(buffer); err != nil { + return M.Socksaddr{}, err + } + if addr.FQDN != "" { + destination = M.Socksaddr{ + Fqdn: addr.FQDN, + Port: uint16(addr.Port), + } + } else if addr.IP != nil { + netAddr, _ := netip.AddrFromSlice(addr.IP) + destination = M.Socksaddr{ + Addr: netAddr.Unmap(), + Port: uint16(addr.Port), + } + } + return destination, nil +} + +// WritePacket writes the SOCKS5 UDP header and the payload. +func (c *mieruPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { + header := buf.NewSize(3 + M.MaxSocksaddrLength) + defer header.Release() + + // RSV (2 bytes) + FRAG (1 byte) + common.Must(header.WriteZeroN(3)) + + var addr mierumodel.AddrSpec + if destination.IsFqdn() { + addr.FQDN = destination.Fqdn + } else { + addr.IP = destination.Addr.AsSlice() + } + addr.Port = int(destination.Port) + if err := addr.WriteToSocks5(header); err != nil { + return err + } + + packet := buf.NewSize(header.Len() + buffer.Len()) + defer packet.Release() + common.Must1(packet.Write(header.Bytes())) + common.Must1(packet.Write(buffer.Bytes())) + _, err := c.PacketConn.WriteTo(packet.Bytes(), nil) + return err +} + +func buildMieruServerConfig(_ context.Context, options option.MieruInboundOptions) (*mieruserver.ServerConfig, []string, error) { + if err := validateMieruInboundOptions(options); err != nil { + return nil, nil, fmt.Errorf("failed to validate mieru options: %w", err) + } + + portBindings := []*mierupb.PortBinding{} + + for _, pr := range options.PortBindings { + intport := int32(pr.Port) + portBindings = append(portBindings, &mierupb.PortBinding{ + PortRange: &pr.PortRange, + Port: &intport, + Protocol: getTransportProtocol(pr.Protocol), + }) + } + + var users []*mierupb.User + var userNames []string + for _, user := range options.Users { + users = append(users, &mierupb.User{ + Name: proto.String(user.Name), + Password: proto.String(user.Password), + }) + userNames = append(userNames, user.Name) + } + + return &mieruserver.ServerConfig{ + Config: &mierupb.ServerConfig{ + PortBindings: portBindings, + Users: users, + }, + }, userNames, nil +} + +func validateMieruInboundOptions(options option.MieruInboundOptions) error { + if options.ListenPort == 0 && len(options.PortBindings) == 0 { + return fmt.Errorf("either server_port or transport must be set") + } + if options.ListenPort != 0 && (len(options.PortBindings) != 1 || options.PortBindings[0].Port != options.ListenPort) { + return fmt.Errorf("Transport of Server Port is not defined!") + } + if len(options.Users) == 0 { + return E.New("users is empty") + } + for _, user := range options.Users { + if user.Name == "" { + return E.New("username is empty") + } + if user.Password == "" { + return E.New("password is empty") + } + } + + return validateMieruTransport(options.PortBindings) +} diff --git a/protocol/mieru/outbound.go b/protocol/mieru/outbound.go new file mode 100644 index 0000000000..927a01ab0d --- /dev/null +++ b/protocol/mieru/outbound.go @@ -0,0 +1,236 @@ +package mieru + +import ( + "context" + "fmt" + "net" + "os" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/common/dialer" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + mieruclient "github.com/enfein/mieru/v3/apis/client" + mierucommon "github.com/enfein/mieru/v3/apis/common" + mierumodel "github.com/enfein/mieru/v3/apis/model" + mierupb "github.com/enfein/mieru/v3/pkg/appctl/appctlpb" + "google.golang.org/protobuf/proto" +) + +type Outbound struct { + outbound.Adapter + dialer N.Dialer + logger log.ContextLogger + client mieruclient.Client +} + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register(registry, C.TypeMieru, NewOutbound) +} + +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.MieruOutboundOptions) (adapter.Outbound, error) { + outboundDialer, err := dialer.New(ctx, options.DialerOptions, M.IsDomainName(options.Server)) + if err != nil { + return nil, err + } + + config, err := buildMieruClientConfig(options, mieruDialer{dialer: outboundDialer}) + if err != nil { + return nil, fmt.Errorf("failed to build mieru client config: %w", err) + } + c := mieruclient.NewClient() + if err := c.Store(config); err != nil { + return nil, fmt.Errorf("failed to store mieru client config: %w", err) + } + if err := c.Start(); err != nil { + return nil, fmt.Errorf("failed to start mieru client: %w", err) + } + logger.InfoContext(ctx, "mieru client is started") + + return &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeMieru, tag, options.Network.Build(), options.DialerOptions), + dialer: outboundDialer, + logger: logger, + client: c, + }, nil +} + +func (o *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = o.Tag() + metadata.Destination = destination + switch N.NetworkName(network) { + case N.NetworkTCP: + o.logger.InfoContext(ctx, "outbound connection to ", destination) + d, err := socksAddrToNetAddrSpec(destination, "tcp") + if err != nil { + return nil, E.Cause(err, "failed to convert destination address") + } + return o.client.DialContext(ctx, d) + case N.NetworkUDP: + o.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) + d, err := socksAddrToNetAddrSpec(destination, "udp") + if err != nil { + return nil, E.Cause(err, "failed to convert destination address") + } + streamConn, err := o.client.DialContext(ctx, d) + if err != nil { + return nil, err + } + return &streamer{ + PacketConn: mierucommon.NewUDPAssociateWrapper(mierucommon.NewPacketOverStreamTunnel(streamConn)), + Remote: destination, + }, nil + default: + return nil, os.ErrInvalid + } +} + +func (o *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = o.Tag() + metadata.Destination = destination + o.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) + d, err := socksAddrToNetAddrSpec(destination, "udp") + if err != nil { + return nil, E.Cause(err, "failed to convert destination address") + } + streamConn, err := o.client.DialContext(ctx, d) + if err != nil { + return nil, err + } + return mierucommon.NewUDPAssociateWrapper(mierucommon.NewPacketOverStreamTunnel(streamConn)), nil +} + +func (o *Outbound) Close() error { + return common.Close(o.client) +} + +// mieruDialer is an adapter to mieru dialer interface. +type mieruDialer struct { + dialer N.Dialer +} + +func (md mieruDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { + addr := M.ParseSocksaddr(address) + return md.dialer.DialContext(ctx, network, addr) +} + +func (md mieruDialer) ListenPacket(ctx context.Context, network, laddr, raddr string) (net.PacketConn, error) { + addr := M.ParseSocksaddr(raddr) + return md.dialer.ListenPacket(ctx, addr) +} + +var ( + _ mierucommon.Dialer = (*mieruDialer)(nil) + _ mierucommon.PacketDialer = (*mieruDialer)(nil) +) + +// streamer converts a net.PacketConn to a net.Conn. +type streamer struct { + net.PacketConn + Remote net.Addr +} + +var _ net.Conn = (*streamer)(nil) + +func (s *streamer) Read(b []byte) (n int, err error) { + n, _, err = s.PacketConn.ReadFrom(b) + return +} + +func (s *streamer) Write(b []byte) (n int, err error) { + return s.WriteTo(b, s.Remote) +} + +func (s *streamer) RemoteAddr() net.Addr { + return s.Remote +} + +// socksAddrToNetAddrSpec converts a Socksaddr object to NetAddrSpec, and overrides the network. +func socksAddrToNetAddrSpec(sa M.Socksaddr, network string) (mierumodel.NetAddrSpec, error) { + var nas mierumodel.NetAddrSpec + if err := nas.From(sa); err != nil { + return nas, err + } + nas.Net = network + return nas, nil +} + +func buildMieruClientConfig(options option.MieruOutboundOptions, dialer mieruDialer) (*mieruclient.ClientConfig, error) { + if err := validateMieruOptions(options); err != nil { + return nil, fmt.Errorf("failed to validate mieru options: %w", err) + } + + server := &mierupb.ServerEndpoint{} + for _, pr := range options.PortBindings { + intport := int32(pr.Port) + + server.PortBindings = append(server.PortBindings, &mierupb.PortBinding{ + PortRange: proto.String(pr.PortRange), + Port: &intport, + Protocol: getTransportProtocol(pr.Protocol), + }) + } + if M.IsDomainName(options.Server) { + server.DomainName = proto.String(options.Server) + } else { + server.IpAddress = proto.String(options.Server) + } + config := &mieruclient.ClientConfig{ + Profile: &mierupb.ClientProfile{ + ProfileName: proto.String("sing-box"), + User: &mierupb.User{ + Name: proto.String(options.UserName), + Password: proto.String(options.Password), + }, + Servers: []*mierupb.ServerEndpoint{server}, + HandshakeMode: getHandshakeMode(options.HandshakeMode), + Multiplexing: &mierupb.MultiplexingConfig{ + Level: getMultiplexingLevel(options.Multiplexing), + }, + }, + Dialer: dialer, + PacketDialer: dialer, + DNSConfig: &mierucommon.ClientDNSConfig{ + BypassDialerDNS: true, + }, + } + if multiplexing, ok := mierupb.MultiplexingLevel_value[options.Multiplexing]; ok { + config.Profile.Multiplexing = &mierupb.MultiplexingConfig{ + Level: mierupb.MultiplexingLevel(multiplexing).Enum(), + } + } + return config, nil +} +func validateMieruOptions(options option.MieruOutboundOptions) error { + if options.Server == "" { + return fmt.Errorf("server is empty") + } + if options.ServerPort == 0 && len(options.PortBindings) == 0 { + return fmt.Errorf("either server_port or transport must be set") + } + if options.ServerPort != 0 && (len(options.PortBindings) != 1 || options.PortBindings[0].Port != options.ServerPort) { + return fmt.Errorf("Transport of Server Port is not defined!") + } + if options.UserName == "" { + return fmt.Errorf("username is empty") + } + if options.Password == "" { + return fmt.Errorf("password is empty") + } + if getMultiplexingLevel(options.Multiplexing) == nil { + return fmt.Errorf("invalid multiplexing level: %s", options.Multiplexing) + } + if getHandshakeMode(options.HandshakeMode) == nil { + return fmt.Errorf("invalid handshake mode: %s", options.HandshakeMode) + } + return validateMieruTransport(options.PortBindings) +} diff --git a/protocol/mixed/inbound.go b/protocol/mixed/inbound.go new file mode 100644 index 0000000000..d35319473a --- /dev/null +++ b/protocol/mixed/inbound.go @@ -0,0 +1,168 @@ +package mixed + +import ( + std_bufio "bufio" + "context" + "net" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/common/uot" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/auth" + E "github.com/sagernet/sing/common/exceptions" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/protocol/http" + "github.com/sagernet/sing/protocol/socks" + "github.com/sagernet/sing/protocol/socks/socks4" + "github.com/sagernet/sing/protocol/socks/socks5" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.HTTPMixedInboundOptions](registry, C.TypeMixed, NewInbound) +} + +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) + +type Inbound struct { + inbound.Adapter + router adapter.ConnectionRouterEx + logger log.ContextLogger + listener *listener.Listener + authenticator *auth.Authenticator + tlsConfig tls.ServerConfig + udpTimeout time.Duration +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) (adapter.Inbound, error) { + var udpTimeout time.Duration + if options.UDPTimeout != 0 { + udpTimeout = time.Duration(options.UDPTimeout) + } else { + udpTimeout = C.UDPTimeout + } + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeMixed, tag), + router: uot.NewRouter(router, logger), + logger: logger, + authenticator: auth.NewAuthenticator(options.Users), + udpTimeout: udpTimeout, + } + if options.TLS != nil { + tlsConfig, err := tls.NewServerWithOptions(tls.ServerOptions{ + Context: ctx, + Logger: logger, + Options: common.PtrValueOrDefault(options.TLS), + KTLSCompatible: true, + }) + if err != nil { + return nil, err + } + inbound.tlsConfig = tlsConfig + } + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + SetSystemProxy: options.SetSystemProxy, + SystemProxySOCKS: true, + }) + return inbound, nil +} + +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + if h.tlsConfig != nil { + err := h.tlsConfig.Start() + if err != nil { + return E.Cause(err, "create TLS config") + } + } + return h.listener.Start() +} + +func (h *Inbound) Close() error { + return common.Close( + h.listener, + h.tlsConfig, + ) +} + +func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.newConnection(ctx, conn, metadata, onClose) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + if E.IsClosedOrCanceled(err) { + h.logger.DebugContext(ctx, "connection closed: ", err) + } else { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } + } +} + +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { + if h.tlsConfig != nil { + tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig) + if err != nil { + return E.Cause(err, "TLS handshake") + } + conn = tlsConn + } + reader := std_bufio.NewReader(conn) + headerBytes, err := reader.Peek(1) + if err != nil { + return E.Cause(err, "peek first byte") + } + switch headerBytes[0] { + case socks4.Version, socks5.Version: + return socks.HandleConnectionEx(ctx, conn, reader, h.authenticator, adapter.NewUpstreamHandler(metadata, h.newUserConnection, h.streamUserPacketConnection), h.listener, h.udpTimeout, metadata.Source, onClose) + default: + return http.HandleConnectionEx(ctx, conn, reader, h.authenticator, adapter.NewUpstreamHandler(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose) + } +} + +func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + user, loaded := auth.UserFromContext[string](ctx) + if !loaded { + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) + return + } + metadata.User = user + h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + user, loaded := auth.UserFromContext[string](ctx) + if !loaded { + if !metadata.Destination.IsValid() { + h.logger.InfoContext(ctx, "inbound packet connection") + } else { + h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + } + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) + return + } + metadata.User = user + if !metadata.Destination.IsValid() { + h.logger.InfoContext(ctx, "[", user, "] inbound packet connection") + } else { + h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) + } + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/protocol/naive/inbound.go b/protocol/naive/inbound.go new file mode 100644 index 0000000000..41f4179889 --- /dev/null +++ b/protocol/naive/inbound.go @@ -0,0 +1,251 @@ +package naive + +import ( + "context" + "errors" + "io" + "net" + "net/http" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/common/uot" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/transport/v2rayhttp" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/auth" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + aTLS "github.com/sagernet/sing/common/tls" + sHttp "github.com/sagernet/sing/protocol/http" + + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" +) + +var ( + ConfigureHTTP3ListenerFunc func(ctx context.Context, logger logger.Logger, listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, options option.NaiveInboundOptions) (io.Closer, error) + WrapError func(error) error +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.NaiveInboundOptions](registry, C.TypeNaive, NewInbound) +} + +type Inbound struct { + inbound.Adapter + ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + options option.NaiveInboundOptions + listener *listener.Listener + network []string + networkIsDefault bool + authenticator *auth.Authenticator + tlsConfig tls.ServerConfig + httpServer *http.Server + h3Server io.Closer +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NaiveInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeNaive, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Listen: options.ListenOptions, + }), + networkIsDefault: options.Network == "", + network: options.Network.Build(), + authenticator: auth.NewAuthenticator(options.Users), + } + if common.Contains(inbound.network, N.NetworkUDP) { + if options.TLS == nil || !options.TLS.Enabled { + return nil, E.New("TLS is required for QUIC server") + } + } + if len(options.Users) == 0 { + return nil, E.New("missing users") + } + if options.TLS != nil { + tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + inbound.tlsConfig = tlsConfig + } + return inbound, nil +} + +func (n *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + if n.tlsConfig != nil { + err := n.tlsConfig.Start() + if err != nil { + return E.Cause(err, "create TLS config") + } + } + if common.Contains(n.network, N.NetworkTCP) { + tcpListener, err := n.listener.ListenTCP() + if err != nil { + return err + } + n.httpServer = &http.Server{ + Handler: h2c.NewHandler(n, &http2.Server{}), + BaseContext: func(listener net.Listener) context.Context { + return n.ctx + }, + } + go func() { + listener := net.Listener(tcpListener) + if n.tlsConfig != nil { + if len(n.tlsConfig.NextProtos()) == 0 { + n.tlsConfig.SetNextProtos([]string{http2.NextProtoTLS, "http/1.1"}) + } else if !common.Contains(n.tlsConfig.NextProtos(), http2.NextProtoTLS) { + n.tlsConfig.SetNextProtos(append([]string{http2.NextProtoTLS}, n.tlsConfig.NextProtos()...)) + } + listener = aTLS.NewListener(tcpListener, n.tlsConfig) + } + sErr := n.httpServer.Serve(listener) + if sErr != nil && !errors.Is(sErr, http.ErrServerClosed) { + n.logger.Error("http server serve error: ", sErr) + } + }() + } + + if common.Contains(n.network, N.NetworkUDP) { + http3Server, err := ConfigureHTTP3ListenerFunc(n.ctx, n.logger, n.listener, n, n.tlsConfig, n.options) + if err == nil { + n.h3Server = http3Server + } else if len(n.network) > 1 { + n.logger.Warn(E.Cause(err, "naive http3 disabled")) + } else { + return err + } + } + + return nil +} + +func (n *Inbound) Close() error { + return common.Close( + n.listener, + common.PtrOrNil(n.httpServer), + n.h3Server, + n.tlsConfig, + ) +} + +func (n *Inbound) ServeHTTP(writer http.ResponseWriter, request *http.Request) { + ctx := log.ContextWithNewID(request.Context()) + if request.Method != "CONNECT" { + rejectHTTP(writer, http.StatusBadRequest) + n.badRequest(ctx, request, E.New("not CONNECT request")) + return + } else if request.Header.Get("Padding") == "" { + rejectHTTP(writer, http.StatusBadRequest) + n.badRequest(ctx, request, E.New("missing naive padding")) + return + } + userName, password, authOk := sHttp.ParseBasicAuth(request.Header.Get("Proxy-Authorization")) + if authOk { + authOk = n.authenticator.Verify(userName, password) + } + if !authOk { + rejectHTTP(writer, http.StatusProxyAuthRequired) + n.badRequest(ctx, request, E.New("authorization failed")) + return + } + writer.Header().Set("Padding", generatePaddingHeader()) + writer.WriteHeader(http.StatusOK) + writer.(http.Flusher).Flush() + + hostPort := request.Header.Get("-connect-authority") + if hostPort == "" { + hostPort = request.URL.Host + if hostPort == "" { + hostPort = request.Host + } + } + source := sHttp.SourceAddress(request) + destination := M.ParseSocksaddr(hostPort).Unwrap() + + if hijacker, isHijacker := writer.(http.Hijacker); isHijacker { + conn, _, err := hijacker.Hijack() + if err != nil { + n.badRequest(ctx, request, E.New("hijack failed")) + return + } + n.newConnection(ctx, false, &naiveConn{Conn: conn}, userName, source, destination) + } else { + n.newConnection(ctx, true, &naiveH2Conn{ + reader: request.Body, + writer: writer, + flusher: writer.(http.Flusher), + remoteAddress: source, + }, userName, source, destination) + } +} + +func (n *Inbound) newConnection(ctx context.Context, waitForClose bool, conn net.Conn, userName string, source M.Socksaddr, destination M.Socksaddr) { + if userName != "" { + n.logger.InfoContext(ctx, "[", userName, "] inbound connection from ", source) + n.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", destination) + } else { + n.logger.InfoContext(ctx, "inbound connection from ", source) + n.logger.InfoContext(ctx, "inbound connection to ", destination) + } + var metadata adapter.InboundContext + metadata.Inbound = n.Tag() + metadata.InboundType = n.Type() + //nolint:staticcheck + metadata.InboundDetour = n.listener.ListenOptions().Detour + //nolint:staticcheck + metadata.Source = source + metadata.Destination = destination + metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() + metadata.User = userName + if !waitForClose { + n.router.RouteConnectionEx(ctx, conn, metadata, nil) + } else { + done := make(chan struct{}) + wrapper := v2rayhttp.NewHTTP2Wrapper(conn) + n.router.RouteConnectionEx(ctx, conn, metadata, N.OnceClose(func(it error) { + close(done) + })) + <-done + wrapper.CloseWrapper() + } +} + +func (n *Inbound) badRequest(ctx context.Context, request *http.Request, err error) { + n.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", request.RemoteAddr)) +} + +func rejectHTTP(writer http.ResponseWriter, statusCode int) { + hijacker, ok := writer.(http.Hijacker) + if !ok { + writer.WriteHeader(statusCode) + return + } + conn, _, err := hijacker.Hijack() + if err != nil { + writer.WriteHeader(statusCode) + return + } + if tcpConn, isTCP := common.Cast[*net.TCPConn](conn); isTCP { + tcpConn.SetLinger(0) + } + conn.Close() +} diff --git a/protocol/naive/inbound_conn.go b/protocol/naive/inbound_conn.go new file mode 100644 index 0000000000..775004358d --- /dev/null +++ b/protocol/naive/inbound_conn.go @@ -0,0 +1,257 @@ +package naive + +import ( + "encoding/binary" + "io" + "math/rand" + "net" + "net/http" + "os" + "time" + + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/baderror" + "github.com/sagernet/sing/common/buf" + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/rw" +) + +const paddingCount = 8 + +func generatePaddingHeader() string { + paddingLen := rand.Intn(32) + 30 + padding := make([]byte, paddingLen) + bits := rand.Uint64() + for i := range 16 { + padding[i] = "!#$()+<>?@[]^`{}"[bits&15] + bits >>= 4 + } + for i := 16; i < paddingLen; i++ { + padding[i] = '~' + } + return string(padding) +} + +type paddingConn struct { + readPadding int + writePadding int + readRemaining int + paddingRemaining int +} + +func (p *paddingConn) readWithPadding(reader io.Reader, buffer []byte) (n int, err error) { + if p.readRemaining > 0 { + if len(buffer) > p.readRemaining { + buffer = buffer[:p.readRemaining] + } + n, err = reader.Read(buffer) + if err != nil { + return + } + p.readRemaining -= n + return + } + if p.paddingRemaining > 0 { + err = rw.SkipN(reader, p.paddingRemaining) + if err != nil { + return + } + p.paddingRemaining = 0 + } + if p.readPadding < paddingCount { + var paddingHeader []byte + if len(buffer) >= 3 { + paddingHeader = buffer[:3] + } else { + paddingHeader = make([]byte, 3) + } + _, err = io.ReadFull(reader, paddingHeader) + if err != nil { + return + } + originalDataSize := int(binary.BigEndian.Uint16(paddingHeader[:2])) + paddingSize := int(paddingHeader[2]) + if len(buffer) > originalDataSize { + buffer = buffer[:originalDataSize] + } + n, err = reader.Read(buffer) + if err != nil { + return + } + p.readPadding++ + p.readRemaining = originalDataSize - n + p.paddingRemaining = paddingSize + return + } + return reader.Read(buffer) +} + +func (p *paddingConn) writeWithPadding(writer io.Writer, data []byte) (n int, err error) { + if p.writePadding < paddingCount { + paddingSize := rand.Intn(256) + buffer := buf.NewSize(3 + len(data) + paddingSize) + defer buffer.Release() + header := buffer.Extend(3) + binary.BigEndian.PutUint16(header, uint16(len(data))) + header[2] = byte(paddingSize) + common.Must1(buffer.Write(data)) + common.Must(buffer.WriteZeroN(paddingSize)) + _, err = writer.Write(buffer.Bytes()) + if err == nil { + n = len(data) + } + p.writePadding++ + return + } + return writer.Write(data) +} + +func (p *paddingConn) writeBufferWithPadding(writer io.Writer, buffer *buf.Buffer) error { + if p.writePadding < paddingCount { + bufferLen := buffer.Len() + if bufferLen > 65535 { + _, err := p.writeChunked(writer, buffer.Bytes()) + return err + } + paddingSize := rand.Intn(256) + header := buffer.ExtendHeader(3) + binary.BigEndian.PutUint16(header, uint16(bufferLen)) + header[2] = byte(paddingSize) + common.Must(buffer.WriteZeroN(paddingSize)) + p.writePadding++ + } + return common.Error(writer.Write(buffer.Bytes())) +} + +func (p *paddingConn) writeChunked(writer io.Writer, data []byte) (n int, err error) { + for len(data) > 0 { + var chunk []byte + if len(data) > 65535 { + chunk = data[:65535] + data = data[65535:] + } else { + chunk = data + data = nil + } + var written int + written, err = p.writeWithPadding(writer, chunk) + n += written + if err != nil { + return + } + } + return +} + +func (p *paddingConn) frontHeadroom() int { + if p.writePadding < paddingCount { + return 3 + } + return 0 +} + +func (p *paddingConn) rearHeadroom() int { + if p.writePadding < paddingCount { + return 255 + } + return 0 +} + +func (p *paddingConn) writerMTU() int { + if p.writePadding < paddingCount { + return 65535 + } + return 0 +} + +func (p *paddingConn) readerReplaceable() bool { + return p.readPadding == paddingCount +} + +func (p *paddingConn) writerReplaceable() bool { + return p.writePadding == paddingCount +} + +type naiveConn struct { + net.Conn + paddingConn +} + +func (c *naiveConn) Read(p []byte) (n int, err error) { + n, err = c.readWithPadding(c.Conn, p) + return n, wrapError(err) +} + +func (c *naiveConn) Write(p []byte) (n int, err error) { + n, err = c.writeChunked(c.Conn, p) + return n, wrapError(err) +} + +func (c *naiveConn) WriteBuffer(buffer *buf.Buffer) error { + defer buffer.Release() + err := c.writeBufferWithPadding(c.Conn, buffer) + return wrapError(err) +} + +func (c *naiveConn) FrontHeadroom() int { return c.frontHeadroom() } +func (c *naiveConn) RearHeadroom() int { return c.rearHeadroom() } +func (c *naiveConn) WriterMTU() int { return c.writerMTU() } +func (c *naiveConn) Upstream() any { return c.Conn } +func (c *naiveConn) ReaderReplaceable() bool { return c.readerReplaceable() } +func (c *naiveConn) WriterReplaceable() bool { return c.writerReplaceable() } + +type naiveH2Conn struct { + reader io.Reader + writer io.Writer + flusher http.Flusher + remoteAddress net.Addr + paddingConn +} + +func (c *naiveH2Conn) Read(p []byte) (n int, err error) { + n, err = c.readWithPadding(c.reader, p) + return n, wrapError(err) +} + +func (c *naiveH2Conn) Write(p []byte) (n int, err error) { + n, err = c.writeChunked(c.writer, p) + if err == nil { + c.flusher.Flush() + } + return n, wrapError(err) +} + +func (c *naiveH2Conn) WriteBuffer(buffer *buf.Buffer) error { + defer buffer.Release() + err := c.writeBufferWithPadding(c.writer, buffer) + if err == nil { + c.flusher.Flush() + } + return wrapError(err) +} + +func wrapError(err error) error { + err = baderror.WrapH2(err) + if WrapError != nil { + err = WrapError(err) + } + return err +} + +func (c *naiveH2Conn) Close() error { + return common.Close(c.reader, c.writer) +} + +func (c *naiveH2Conn) LocalAddr() net.Addr { return M.Socksaddr{} } +func (c *naiveH2Conn) RemoteAddr() net.Addr { return c.remoteAddress } +func (c *naiveH2Conn) SetDeadline(t time.Time) error { return os.ErrInvalid } +func (c *naiveH2Conn) SetReadDeadline(t time.Time) error { return os.ErrInvalid } +func (c *naiveH2Conn) SetWriteDeadline(t time.Time) error { return os.ErrInvalid } +func (c *naiveH2Conn) NeedAdditionalReadDeadline() bool { return true } +func (c *naiveH2Conn) UpstreamReader() any { return c.reader } +func (c *naiveH2Conn) UpstreamWriter() any { return c.writer } +func (c *naiveH2Conn) FrontHeadroom() int { return c.frontHeadroom() } +func (c *naiveH2Conn) RearHeadroom() int { return c.rearHeadroom() } +func (c *naiveH2Conn) WriterMTU() int { return c.writerMTU() } +func (c *naiveH2Conn) ReaderReplaceable() bool { return c.readerReplaceable() } +func (c *naiveH2Conn) WriterReplaceable() bool { return c.writerReplaceable() } diff --git a/protocol/naive/outbound.go b/protocol/naive/outbound.go new file mode 100644 index 0000000000..8249a1fefe --- /dev/null +++ b/protocol/naive/outbound.go @@ -0,0 +1,275 @@ +//go:build with_naive_outbound + +package naive + +import ( + "context" + "encoding/pem" + "net" + "os" + "strings" + + "github.com/sagernet/cronet-go" + _ "github.com/sagernet/cronet-go/all" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/common/dialer" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/uot" + "github.com/sagernet/sing/service" + + mDNS "github.com/miekg/dns" +) + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.NaiveOutboundOptions](registry, C.TypeNaive, NewOutbound) +} + +type Outbound struct { + outbound.Adapter + ctx context.Context + logger logger.ContextLogger + client *cronet.NaiveClient + uotClient *uot.Client +} + +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NaiveOutboundOptions) (adapter.Outbound, error) { + if options.TLS == nil || !options.TLS.Enabled { + return nil, C.ErrTLSRequired + } + if options.TLS.DisableSNI { + return nil, E.New("disable_sni is not supported on naive outbound") + } + if options.TLS.Insecure { + return nil, E.New("insecure is not supported on naive outbound") + } + if len(options.TLS.ALPN) > 0 { + return nil, E.New("alpn is not supported on naive outbound") + } + if options.TLS.MinVersion != "" { + return nil, E.New("min_version is not supported on naive outbound") + } + if options.TLS.MaxVersion != "" { + return nil, E.New("max_version is not supported on naive outbound") + } + if len(options.TLS.CipherSuites) > 0 { + return nil, E.New("cipher_suites is not supported on naive outbound") + } + if len(options.TLS.CurvePreferences) > 0 { + return nil, E.New("curve_preferences is not supported on naive outbound") + } + if len(options.TLS.ClientCertificate) > 0 || options.TLS.ClientCertificatePath != "" { + return nil, E.New("client_certificate is not supported on naive outbound") + } + if len(options.TLS.ClientKey) > 0 || options.TLS.ClientKeyPath != "" { + return nil, E.New("client_key is not supported on naive outbound") + } + if options.TLS.Fragment || options.TLS.RecordFragment { + return nil, E.New("fragment is not supported on naive outbound") + } + if options.TLS.KernelTx || options.TLS.KernelRx { + return nil, E.New("kernel TLS is not supported on naive outbound") + } + if options.TLS.UTLS != nil && options.TLS.UTLS.Enabled { + return nil, E.New("uTLS is not supported on naive outbound") + } + if options.TLS.Reality != nil && options.TLS.Reality.Enabled { + return nil, E.New("reality is not supported on naive outbound") + } + + serverAddress := options.ServerOptions.Build() + + var serverName string + if options.TLS.ServerName != "" { + serverName = options.TLS.ServerName + } else { + serverName = serverAddress.AddrString() + } + + outboundDialer, err := dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: options.DialerOptions, + RemoteIsDomain: true, + ResolverOnDetour: true, + NewDialer: true, + }) + if err != nil { + return nil, err + } + + var trustedRootCertificates string + if len(options.TLS.Certificate) > 0 { + trustedRootCertificates = strings.Join(options.TLS.Certificate, "\n") + } else if options.TLS.CertificatePath != "" { + content, err := os.ReadFile(options.TLS.CertificatePath) + if err != nil { + return nil, E.Cause(err, "read certificate") + } + trustedRootCertificates = string(content) + } + + extraHeaders := make(map[string]string) + for key, values := range options.ExtraHeaders.Build() { + if len(values) > 0 { + extraHeaders[key] = values[0] + } + } + + dnsRouter := service.FromContext[adapter.DNSRouter](ctx) + var dnsResolver cronet.DNSResolverFunc + if dnsRouter != nil { + dnsResolver = func(dnsContext context.Context, request *mDNS.Msg) *mDNS.Msg { + response, err := dnsRouter.Exchange(dnsContext, request, outboundDialer.(dialer.ResolveDialer).QueryOptions()) + if err != nil { + logger.Error("DNS exchange failed: ", err) + return dns.FixedResponseStatus(request, mDNS.RcodeServerFailure) + } + return response + } + } + + var echEnabled bool + var echConfigList []byte + var echQueryServerName string + if options.TLS.ECH != nil && options.TLS.ECH.Enabled { + echEnabled = true + echQueryServerName = options.TLS.ECH.QueryServerName + var echConfig []byte + if len(options.TLS.ECH.Config) > 0 { + echConfig = []byte(strings.Join(options.TLS.ECH.Config, "\n")) + } else if options.TLS.ECH.ConfigPath != "" { + content, err := os.ReadFile(options.TLS.ECH.ConfigPath) + if err != nil { + return nil, E.Cause(err, "read ECH config") + } + echConfig = content + } + if len(echConfig) > 0 { + block, rest := pem.Decode(echConfig) + if block == nil || block.Type != "ECH CONFIGS" || len(rest) > 0 { + return nil, E.New("invalid ECH configs pem") + } + echConfigList = block.Bytes + } + } + var quicCongestionControl cronet.QUICCongestionControl + switch options.QUICCongestionControl { + case "": + quicCongestionControl = cronet.QUICCongestionControlDefault + case "bbr": + quicCongestionControl = cronet.QUICCongestionControlBBR + case "bbr2": + quicCongestionControl = cronet.QUICCongestionControlBBRv2 + case "cubic": + quicCongestionControl = cronet.QUICCongestionControlCubic + case "reno": + quicCongestionControl = cronet.QUICCongestionControlReno + default: + return nil, E.New("unknown quic congestion control: ", options.QUICCongestionControl) + } + client, err := cronet.NewNaiveClient(cronet.NaiveClientOptions{ + Context: ctx, + Logger: logger, + ServerAddress: serverAddress, + ServerName: serverName, + Username: options.Username, + Password: options.Password, + InsecureConcurrency: options.InsecureConcurrency, + ExtraHeaders: extraHeaders, + TrustedRootCertificates: trustedRootCertificates, + Dialer: outboundDialer, + DNSResolver: dnsResolver, + ECHEnabled: echEnabled, + ECHConfigList: echConfigList, + ECHQueryServerName: echQueryServerName, + QUIC: options.QUIC, + QUICCongestionControl: quicCongestionControl, + }) + if err != nil { + return nil, err + } + var uotClient *uot.Client + uotOptions := common.PtrValueOrDefault(options.UDPOverTCP) + if uotOptions.Enabled { + uotClient = &uot.Client{ + Dialer: &naiveDialer{client}, + Version: uotOptions.Version, + } + } + var networks []string + if uotClient != nil { + networks = []string{N.NetworkTCP, N.NetworkUDP} + } else { + networks = []string{N.NetworkTCP} + } + return &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeNaive, tag, networks, options.DialerOptions), + ctx: ctx, + logger: logger, + client: client, + uotClient: uotClient, + }, nil +} + +func (h *Outbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + err := h.client.Start() + if err != nil { + return err + } + h.logger.Info("NaiveProxy started, version: ", h.client.Engine().Version()) + return nil +} + +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + switch N.NetworkName(network) { + case N.NetworkTCP: + h.logger.InfoContext(ctx, "outbound connection to ", destination) + return h.client.DialEarly(ctx, destination) + case N.NetworkUDP: + if h.uotClient == nil { + return nil, E.New("UDP is not supported unless UDP over TCP is enabled") + } + h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) + return h.uotClient.DialContext(ctx, network, destination) + default: + return nil, E.Extend(N.ErrUnknownNetwork, network) + } +} + +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + if h.uotClient == nil { + return nil, E.New("UDP is not supported unless UDP over TCP is enabled") + } + return h.uotClient.ListenPacket(ctx, destination) +} + +func (h *Outbound) InterfaceUpdated() { + h.client.Engine().CloseAllConnections() +} + +func (h *Outbound) Close() error { + return h.client.Close() +} + +func (h *Outbound) Client() *cronet.NaiveClient { + return h.client +} + +type naiveDialer struct { + *cronet.NaiveClient +} + +func (d *naiveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + return d.NaiveClient.DialEarly(ctx, destination) +} diff --git a/protocol/naive/quic/inbound_init.go b/protocol/naive/quic/inbound_init.go new file mode 100644 index 0000000000..1f86826726 --- /dev/null +++ b/protocol/naive/quic/inbound_init.go @@ -0,0 +1,128 @@ +package quic + +import ( + "context" + "io" + "net/http" + "time" + + "github.com/sagernet/quic-go" + "github.com/sagernet/quic-go/congestion" + "github.com/sagernet/quic-go/http3" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/protocol/naive" + "github.com/sagernet/sing-quic" + "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" + "github.com/sagernet/sing/common/logger" + "github.com/sagernet/sing/common/ntp" +) + +func init() { + naive.ConfigureHTTP3ListenerFunc = func(ctx context.Context, logger logger.Logger, listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, options option.NaiveInboundOptions) (io.Closer, error) { + err := qtls.ConfigureHTTP3(tlsConfig) + if err != nil { + return nil, err + } + + udpConn, err := listener.ListenUDP() + if err != nil { + return nil, err + } + + var congestionControl func(conn *quic.Conn) congestion.CongestionControl + timeFunc := ntp.TimeFuncFromContext(ctx) + if timeFunc == nil { + timeFunc = time.Now + } + switch options.QUICCongestionControl { + case "", "bbr": + congestionControl = func(conn *quic.Conn) congestion.CongestionControl { + return congestion_meta2.NewBbrSender( + congestion_meta2.DefaultClock{TimeFunc: timeFunc}, + congestion.ByteCount(conn.Config().InitialPacketSize), + congestion.ByteCount(congestion_meta1.InitialCongestionWindow), + ) + } + case "bbr_standard": + congestionControl = 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, + ) + } + case "bbr2": + congestionControl = func(conn *quic.Conn) congestion.CongestionControl { + return congestion_bbr2.NewBBR2Sender( + congestion_bbr2.DefaultClock{TimeFunc: timeFunc}, + congestion.ByteCount(conn.Config().InitialPacketSize), + 0, + false, + ) + } + case "bbr2_variant": + congestionControl = 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, + ) + } + case "cubic": + congestionControl = func(conn *quic.Conn) congestion.CongestionControl { + return congestion_meta1.NewCubicSender( + congestion_meta1.DefaultClock{TimeFunc: timeFunc}, + congestion.ByteCount(conn.Config().InitialPacketSize), + false, + ) + } + case "reno": + congestionControl = func(conn *quic.Conn) congestion.CongestionControl { + return congestion_meta1.NewCubicSender( + congestion_meta1.DefaultClock{TimeFunc: timeFunc}, + congestion.ByteCount(conn.Config().InitialPacketSize), + true, + ) + } + default: + return nil, E.New("unknown quic congestion control: ", options.QUICCongestionControl) + } + + quicListener, err := qtls.ListenEarly(udpConn, tlsConfig, &quic.Config{ + MaxIncomingStreams: 1 << 60, + Allow0RTT: true, + }) + if err != nil { + udpConn.Close() + return nil, err + } + + h3Server := &http3.Server{ + Handler: handler, + ConnContext: func(ctx context.Context, conn *quic.Conn) context.Context { + conn.SetCongestionControl(congestionControl(conn)) + return log.ContextWithNewID(ctx) + }, + } + + go func() { + sErr := h3Server.ServeListener(quicListener) + udpConn.Close() + if sErr != nil && !E.IsClosedOrCanceled(sErr) { + logger.Error("http3 server closed: ", sErr) + } + }() + + return quicListener, nil + } + naive.WrapError = qtls.WrapError +} diff --git a/protocol/psiphon/outbound.go b/protocol/psiphon/outbound.go new file mode 100644 index 0000000000..04c860ab76 --- /dev/null +++ b/protocol/psiphon/outbound.go @@ -0,0 +1,325 @@ +package psiphon + +import ( + "context" + "net" + "path/filepath" + "sync" + "time" + + "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/common/dialer" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" +) + +const ( + defaultPropagationChannelID = "FFFFFFFFFFFFFFFF" + defaultSponsorID = "FFFFFFFFFFFFFFFF" + defaultNetworkID = "test" + defaultClientPlatform = "Android_4.0.4_com.example.exampleClientLibraryApp" + defaultRemoteServerListURL = "https://s3.amazonaws.com//psiphon/web/mjr4-p23r-puwl/server_list_compressed" + defaultRemoteServerListFilename = "remote_server_list" + defaultSignaturePublicKey = "MIICIDANBgkqhkiG9w0BAQEFAAOCAg0AMIICCAKCAgEAt7Ls+/39r+T6zNW7GiVpJfzq/xvL9SBH5rIFnk0RXYEYavax3WS6HOD35eTAqn8AniOwiH+DOkvgSKF2caqk/y1dfq47Pdymtwzp9ikpB1C5OfAysXzBiwVJlCdajBKvBZDerV1cMvRzCKvKwRmvDmHgphQQ7WfXIGbRbmmk6opMBh3roE42KcotLFtqp0RRwLtcBRNtCdsrVsjiI1Lqz/lH+T61sGjSjQ3CHMuZYSQJZo/KrvzgQXpkaCTdbObxHqb6/+i1qaVOfEsvjoiyzTxJADvSytVtcTjijhPEV6XskJVHE1Zgl+7rATr/pDQkw6DPCNBS1+Y6fy7GstZALQXwEDN/qhQI9kWkHijT8ns+i1vGg00Mk/6J75arLhqcodWsdeG/M/moWgqQAnlZAGVtJI1OgeF5fsPpXu4kctOfuZlGjVZXQNW34aOzm8r8S0eVZitPlbhcPiR4gT/aSMz/wd8lZlzZYsje/Jr8u/YtlwjjreZrGRmG8KMOzukV3lLmMppXFMvl4bxv6YFEmIuTsOhbLTwFgh7KYNjodLj/LsqRVfwz31PgWQFTEPICV7GCvgVlPRxnofqKSjgTWI4mxDhBpVcATvaoBl1L/6WLbFvBsoAUBItWwctO2xalKxF5szhGm8lccoc5MZr8kfE0uxMgsxz4er68iCID+rsCAQM=" + defaultDataDirectory = "data/psiphon" + defaultEstablishTunnelTimeout = time.Minute +) + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.PsiphonOutboundOptions](registry, C.TypePsiphon, NewOutbound) +} + +var _ adapter.Outbound = (*Outbound)(nil) +var _ adapter.InterfaceUpdateListener = (*Outbound)(nil) + +type Outbound struct { + outbound.Adapter + dnsRouter adapter.DNSRouter + logger logger.ContextLogger + dialer N.Dialer + + ctx context.Context + + mu sync.RWMutex + reconnectCh chan struct{} + psiphon *Psiphon +} + +type tunnelOwner struct{ notify func() } + +func (tunnelOwner) SignalSeededNewSLOK() {} +func (o tunnelOwner) SignalTunnelFailure(*psiphon.Tunnel) { + if o.notify != nil { + o.notify() + } +} + +func NewOutbound(ctx context.Context, _ adapter.Router, logger log.ContextLogger, tag string, options option.PsiphonOutboundOptions) (adapter.Outbound, error) { + outboundDialer, err := dialer.New(ctx, options.DialerOptions, false) + if err != nil { + return nil, err + } + + timeout := time.Duration(options.EstablishTunnelTimeout) + if timeout <= 0 { + timeout = defaultEstablishTunnelTimeout + } + config := buildConfig(options, timeout) + psiphon, err := NewPsiphon(ctx, logger, config, tag) + if err != nil { + + return nil, err + } + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypePsiphon, tag, []string{N.NetworkTCP}, options.DialerOptions), + dialer: outboundDialer, + psiphon: psiphon, + logger: logger, + ctx: ctx, + dnsRouter: service.FromContext[adapter.DNSRouter](ctx), + reconnectCh: make(chan struct{}, 1), + } + + return outbound, nil + +} +func (h *Outbound) PreStart() error { + return h.psiphon.PreStart() +} +func (h *Outbound) Start() error { + + go h.run() + return nil +} + +// func (h *Outbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { +// // return h.psiphon.Dial(dst, innerConn) +// } +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + // h.logger.Debug("diallign outboud ", h.Tag(), " to ", destination) + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() + metadata.Destination = destination + + switch N.NetworkName(network) { + case N.NetworkTCP: + h.logger.InfoContext(ctx, "outbound connection to ", destination) + // h.mu.RLock() + // current := h.tunnel + // h.mu.RUnlock() + // if current == nil || !current.IsActivated() { + // return nil, E.New("psiphon tunnel is not established") + // } + case N.NetworkUDP: + return nil, E.New("UDP is not supported by psiphon outbound") + default: + return nil, E.Extend(N.ErrUnknownNetwork, network) + } + dst := destination.String() + // if destination.IsFqdn() { + // destinationAddresses, err := h.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) + // if err != nil || len(destinationAddresses) == 0 { + // return nil, err + // } + // dst = fmt.Sprintf("%s:%d", destinationAddresses[0].String(), destination.Port) + // // return N.DialSerial(ctx, h.client, network, destination, destinationAddresses) + // } + h.logger.Debug("dialed destination ", destination, "(", dst, ") through outbound ", h.Tag(), ", now dialing through psiphon tunnel") + + // innerConn, err := h.dialer.DialContext(ctx, "tcp", destination) + // if err != nil { + // return nil, E.New(err, "failed to dial destination ", destination, " through outbound ", h.Tag()) + // } + return h.psiphon.Dial(dst, nil) + + // dialConf := psiphon.DialConfig{ + // // CustomDialer: *h.dialer, + // ResolveIP: func(context.Context, string) ([]net.IP, error) { + // if !destination.IsFqdn() { + // return []net.IP{netipAddrToIP(destination.Addr)}, nil + // } + // res, err := h.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) + // if err != nil { + // return nil, err + // } + + // ips := make([]net.IP, 0, len(res)) + // for _, r := range res { + // ips = append(ips, netipAddrToIP(r)) + // } + // return ips, nil + + // // return []net.IP{ + // // net.IPv4(1, 1, 1, 1), + // // }, nil + // // return nil, E.New("psiphon outbound does not support custom DNS resolution") + // }, + // } + // return psiphon.DialTCP(ctx, destination.String(), &dialConf) + +} +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return nil, E.New("UDP is not supported by psiphon outbound") +} + +func (h *Outbound) Close() error { + + h.psiphon.Close() + + return nil +} + +func buildConfig(options option.PsiphonOutboundOptions, timeout time.Duration) *psiphon.Config { + dataDir := options.DataDirectory + if dataDir == "" { + dataDir = defaultDataDirectory + } + allowDefaultDNS := true + if options.AllowDefaultDNSResolverWithBindToDevice != nil { + allowDefaultDNS = *options.AllowDefaultDNSResolverWithBindToDevice + } + remoteListFilename := options.RemoteServerListDownloadFilename + if remoteListFilename == "" { + remoteListFilename = defaultRemoteServerListFilename + } + remoteListURL := options.RemoteServerListURL + if remoteListURL == "" { + remoteListURL = defaultRemoteServerListURL + } + signatureKey := options.RemoteServerListSignaturePublicKey + if signatureKey == "" { + signatureKey = defaultSignaturePublicKey + } + propagationChannelID := options.PropagationChannelID + if propagationChannelID == "" { + propagationChannelID = defaultPropagationChannelID + } + sponsorID := options.SponsorID + if sponsorID == "" { + sponsorID = defaultSponsorID + } + networkID := options.NetworkID + if networkID == "" { + networkID = defaultNetworkID + } + clientPlatform := options.ClientPlatform + if clientPlatform == "" { + clientPlatform = defaultClientPlatform + } + establishTimeoutSeconds := durationToSecondsPtr(timeout) + config := &psiphon.Config{ + DataRootDirectory: dataDir, + EgressRegion: options.EgressRegion, + PropagationChannelId: propagationChannelID, + RemoteServerListDownloadFilename: remoteListFilename, + RemoteServerListSignaturePublicKey: signatureKey, + RemoteServerListUrl: remoteListURL, + SponsorId: sponsorID, + NetworkID: networkID, + ClientPlatform: clientPlatform, + ClientVersion: options.ClientVersion, + AllowDefaultDNSResolverWithBindToDevice: allowDefaultDNS, + EstablishTunnelTimeoutSeconds: establishTimeoutSeconds, + DisableLocalHTTPProxy: true, + DisableLocalSocksProxy: true, + MigrateDataStoreDirectory: dataDir, + MigrateObfuscatedServerListDownloadDirectory: dataDir, + MigrateRemoteServerListDownloadFilename: filepath.Join(dataDir, "server_list_compressed"), + } + if options.UpstreamProxyURL != "" { + config.UpstreamProxyURL = options.UpstreamProxyURL + } + return config +} + +func durationToSecondsPtr(duration time.Duration) *int { + seconds := int(duration / time.Second) + if seconds < 1 { + seconds = 1 + } + return &seconds +} + +func (h *Outbound) run() { + h.connectOnce() + // defer h.closeDataStore() + // for { + // if h.ctx.Err() != nil { + // return + // } + // if h.psiphon.IsConnected() { + // h.logger.Debug("psiphon tunnel is active") + // select { + // case <-h.ctx.Done(): + // return + // case <-h.reconnectCh: + // h.resetTunnel() + // } + // continue + // } + // h.logger.Debug("psiphon tunnel is not active, trying to connect") + // if err := h.connectOnce(); err != nil { + // h.logger.Warn("psiphon connect failed: ", err) + // select { + // case <-h.ctx.Done(): + // return + // case <-time.After(5 * time.Second): + // } + // continue + // } + // } +} + +func (p *Outbound) DisplayType() string { + str := "⚠️ Connecting..." + if p.psiphon.IsConnected() { + str = "✔️ Connected" + } + return C.ProxyDisplayName(p.Type()) + " " + str +} + +func (h *Outbound) connectOnce() error { + return h.psiphon.Start() +} + +// func (h *Outbound) connectOnce() error { +// ti := time.Now().Add(h.timeout) +// tunnel, err := psiphon.ConnectTunnel(h.ctx, h.config, ti, &psiphon.DialParameters{ + +// }) +// if err != nil { +// return err +// } +// owner := tunnelOwner{notify: h.requestReconnect} +// if err = tunnel.Activate(h.ctx, owner, false); err != nil { +// tunnel.Close(true) +// return err +// } +// h.mu.Lock() +// h.tunnel = tunnel +// h.mu.Unlock() +// return nil +// } + +func (h *Outbound) requestReconnect() { + select { + case h.reconnectCh <- struct{}{}: + default: + } +} +func (h *Outbound) IsReady() bool { + return h.psiphon.IsConnected() +} + +func (h *Outbound) InterfaceUpdated() { + h.logger.Info("Network Changed... Restarting Psiphon Tunnel") + h.psiphon.controller.NetworkChanged() +} diff --git a/protocol/psiphon/psiphon.go b/protocol/psiphon/psiphon.go new file mode 100644 index 0000000000..b1746b8587 --- /dev/null +++ b/protocol/psiphon/psiphon.go @@ -0,0 +1,245 @@ +package psiphon + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net" + "os" + + "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon" + "github.com/sagernet/sing-box/common/monitoring" + "github.com/sagernet/sing/common/logger" +) + +var Countries = []string{ + "AT", + "AU", + "BE", + "BG", + "CA", + "CH", + "CZ", + "DE", + "DK", + "EE", + "ES", + "FI", + "FR", + "GB", + "HR", + "HU", + "IE", + "IN", + "IT", + "JP", + "LV", + "NL", + "NO", + "PL", + "PT", + "RO", + "RS", + "SE", + "SG", + "SK", + "US", +} + +// NoticeEvent represents the notices emitted by tunnel core. It will be passed to +// noticeReceiver, if supplied. +// NOTE: Ordinary users of this library should never need this. +type NoticeEvent struct { + Data map[string]interface{} `json:"data"` + Type string `json:"noticeType"` + Timestamp string `json:"timestamp"` +} + +type Psiphon struct { + controller *psiphon.Controller + logger logger.ContextLogger + config *psiphon.Config + ctx context.Context + cancel context.CancelFunc + dataStoreOpened bool + connected bool + tag string +} + +func (p *Psiphon) Dial(address string, conn net.Conn) (net.Conn, error) { + if ctl := p.controller; ctl != nil { + return ctl.Dial(address, conn) + } + return nil, errors.New("controller not initialized") +} + +func (p *Psiphon) PreStart() error { + if err := os.MkdirAll(p.config.DataRootDirectory, 0o755); err != nil { + return err + } + if err := p.config.Commit(true); err != nil { + return err + } + if err := psiphon.OpenDataStore(p.config); err != nil { + return err + } + p.dataStoreOpened = true + if err := psiphon.ImportEmbeddedServerEntries(p.ctx, p.config, "", ""); err != nil { + p.closeDataStore() + return err + } + return nil +} +func (p *Psiphon) closeDataStore() { + if p.dataStoreOpened { + psiphon.CloseDataStore() + p.dataStoreOpened = false + } +} + +func (p *Psiphon) State() string { + if p.controller == nil || !p.connected { + return "connecting..." + } + + return "connected" +} +func (p *Psiphon) IsConnected() bool { + if p.controller == nil || !p.connected { + return false + } + return true +} + +func (p *Psiphon) Close() error { + p.connected = false + psiphon.ResetNoticeWriter() + p.cancel() + p.closeDataStore() + return nil +} +func NewPsiphon(ctx context.Context, l logger.ContextLogger, config *psiphon.Config, tag string) (*Psiphon, error) { + p := Psiphon{ + logger: l, + config: config, + ctx: ctx, + tag: tag, + } + return &p, nil +} +func (p *Psiphon) Start() error { + + ctx, cancel := context.WithCancel(p.ctx) + p.cancel = cancel + // config.Commit must be called before calling config.SetParameters + // or attempting to connect. + if err := p.config.Commit(true); err != nil { + return errors.New("config.Commit failed") + } + + connected := make(chan struct{}, 1) + errored := make(chan error, 1) + + psiphon.SetNoticeWriter(psiphon.NewNoticeReceiver(func(notice []byte) { + var event NoticeEvent + if err := json.Unmarshal(notice, &event); err != nil { + return + } + + go func(event NoticeEvent) { + p.logger.Debug(fmt.Sprint("Notic ", event.Type, " data ", event.Data)) + switch event.Type { + case "EstablishTunnelTimeout": + select { + case errored <- errors.New("clientlib: tunnel establishment timeout"): + default: + } + case "Tunnels": + if event.Data["count"].(float64) > 0 { + select { + case connected <- struct{}{}: + p.connected = true + monitoring.Get(p.ctx).TestNow(p.tag) + default: + } + } + } + }(event) + })) + + if err := psiphon.OpenDataStore(p.config); err != nil { + return errors.New("failed to open data store") + } + + if err := psiphon.ImportEmbeddedServerEntries(ctx, p.config, "", ""); err != nil { + return err + } + + controller, err := psiphon.NewController(p.config) + + if err != nil { + return errors.New("psiphon.NewController failed") + } + p.controller = controller + + go func() { + controller.Run(ctx) // Run will block until the controller is stopped + + select { + case errored <- errors.New("controller.Run exited unexpectedly"): + default: + } + }() + p.logger.Debug("Waiting for success or failure of tunnel connection...") + select { + case <-ctx.Done(): + p.logger.Debug("Context done while waiting for success or failure of tunnel connection") + p.Close() + return ctx.Err() + case <-connected: + p.logger.Debug("Tunnel connection established") + return nil + case err := <-errored: + p.logger.Debug("Tunnel connection failed: ", err) + p.Close() + return err + } + +} + +// func RunPsiphon(ctx context.Context, l logger.ContextLogger, wgBind netip.AddrPort, dir string, localSocksAddr netip.AddrPort, country string) error { +// host := "" +// if !netip.MustParsePrefix("127.0.0.0/8").Contains(localSocksAddr.Addr()) { +// host = "any" +// } + +// timeout := 60 +// config := psiphon.Config{ +// EgressRegion: country, +// ListenInterface: host, +// LocalSocksProxyPort: int(localSocksAddr.Port()), +// UpstreamProxyURL: fmt.Sprintf("socks5://%s", wgBind), +// DisableLocalHTTPProxy: true, +// PropagationChannelId: "FFFFFFFFFFFFFFFF", +// RemoteServerListDownloadFilename: "remote_server_list", +// RemoteServerListSignaturePublicKey: "MIICIDANBgkqhkiG9w0BAQEFAAOCAg0AMIICCAKCAgEAt7Ls+/39r+T6zNW7GiVpJfzq/xvL9SBH5rIFnk0RXYEYavax3WS6HOD35eTAqn8AniOwiH+DOkvgSKF2caqk/y1dfq47Pdymtwzp9ikpB1C5OfAysXzBiwVJlCdajBKvBZDerV1cMvRzCKvKwRmvDmHgphQQ7WfXIGbRbmmk6opMBh3roE42KcotLFtqp0RRwLtcBRNtCdsrVsjiI1Lqz/lH+T61sGjSjQ3CHMuZYSQJZo/KrvzgQXpkaCTdbObxHqb6/+i1qaVOfEsvjoiyzTxJADvSytVtcTjijhPEV6XskJVHE1Zgl+7rATr/pDQkw6DPCNBS1+Y6fy7GstZALQXwEDN/qhQI9kWkHijT8ns+i1vGg00Mk/6J75arLhqcodWsdeG/M/moWgqQAnlZAGVtJI1OgeF5fsPpXu4kctOfuZlGjVZXQNW34aOzm8r8S0eVZitPlbhcPiR4gT/aSMz/wd8lZlzZYsje/Jr8u/YtlwjjreZrGRmG8KMOzukV3lLmMppXFMvl4bxv6YFEmIuTsOhbLTwFgh7KYNjodLj/LsqRVfwz31PgWQFTEPICV7GCvgVlPRxnofqKSjgTWI4mxDhBpVcATvaoBl1L/6WLbFvBsoAUBItWwctO2xalKxF5szhGm8lccoc5MZr8kfE0uxMgsxz4er68iCID+rsCAQM=", +// RemoteServerListUrl: "https://s3.amazonaws.com//psiphon/web/mjr4-p23r-puwl/server_list_compressed", +// SponsorId: "FFFFFFFFFFFFFFFF", +// NetworkID: "test", +// ClientPlatform: "Android_4.0.4_com.example.exampleClientLibraryApp", +// AllowDefaultDNSResolverWithBindToDevice: true, +// EstablishTunnelTimeoutSeconds: &timeout, +// DataRootDirectory: dir, +// MigrateDataStoreDirectory: dir, +// MigrateObfuscatedServerListDownloadDirectory: dir, +// MigrateRemoteServerListDownloadFilename: filepath.Join(dir, "server_list_compressed"), +// } + +// l.Info("starting handshake") +// if _, err := StartTunnel(ctx, l, &config); err != nil { +// return fmt.Errorf("Unable to start psiphon: %w", err) +// } +// l.Info("psiphon started successfully") +// return nil +// } diff --git a/protocol/redirect/redirect.go b/protocol/redirect/redirect.go new file mode 100644 index 0000000000..ff52bd7b4b --- /dev/null +++ b/protocol/redirect/redirect.go @@ -0,0 +1,68 @@ +package redirect + +import ( + "context" + "net" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/redir" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func RegisterRedirect(registry *inbound.Registry) { + inbound.Register[option.RedirectInboundOptions](registry, C.TypeRedirect, NewRedirect) +} + +type Redirect struct { + inbound.Adapter + router adapter.Router + logger log.ContextLogger + listener *listener.Listener +} + +func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.RedirectInboundOptions) (adapter.Inbound, error) { + redirect := &Redirect{ + Adapter: inbound.NewAdapter(C.TypeRedirect, tag), + router: router, + logger: logger, + } + redirect.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: redirect, + }) + return redirect, nil +} + +func (h *Redirect) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + return h.listener.Start() +} + +func (h *Redirect) Close() error { + return h.listener.Close() +} + +func (h *Redirect) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + destination, err := redir.GetOriginalDestination(conn) + if err != nil { + conn.Close() + h.logger.ErrorContext(ctx, "process connection from ", conn.RemoteAddr(), ": get redirect destination: ", err) + return + } + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.Destination = M.SocksaddrFromNetIP(destination) + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/protocol/redirect/tproxy.go b/protocol/redirect/tproxy.go new file mode 100644 index 0000000000..5f24162629 --- /dev/null +++ b/protocol/redirect/tproxy.go @@ -0,0 +1,150 @@ +package redirect + +import ( + "context" + "net" + "net/netip" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/redir" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/control" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/udpnat2" +) + +func RegisterTProxy(registry *inbound.Registry) { + inbound.Register[option.TProxyInboundOptions](registry, C.TypeTProxy, NewTProxy) +} + +type TProxy struct { + inbound.Adapter + ctx context.Context + router adapter.Router + logger log.ContextLogger + listener *listener.Listener + udpNat *udpnat.Service +} + +func NewTProxy(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TProxyInboundOptions) (adapter.Inbound, error) { + tproxy := &TProxy{ + Adapter: inbound.NewAdapter(C.TypeTProxy, tag), + ctx: ctx, + router: router, + logger: logger, + } + var udpTimeout time.Duration + if options.UDPTimeout != 0 { + udpTimeout = time.Duration(options.UDPTimeout) + } else { + udpTimeout = C.UDPTimeout + } + tproxy.udpNat = udpnat.New(tproxy, tproxy.preparePacketConnection, udpTimeout, false) + tproxy.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: options.Network.Build(), + Listen: options.ListenOptions, + ConnectionHandler: tproxy, + OOBPacketHandler: tproxy, + TProxy: true, + }) + return tproxy, nil +} + +func (t *TProxy) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + return t.listener.Start() +} + +func (t *TProxy) Close() error { + return t.listener.Close() +} + +func (t *TProxy) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Inbound = t.Tag() + metadata.InboundType = t.Type() + metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() + t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + t.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (t *TProxy) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + t.logger.InfoContext(ctx, "inbound packet connection from ", source) + t.logger.InfoContext(ctx, "inbound packet connection to ", destination) + var metadata adapter.InboundContext + metadata.Inbound = t.Tag() + metadata.InboundType = t.Type() + metadata.Source = source + metadata.Destination = destination + metadata.OriginDestination = t.listener.UDPAddr() + t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} + +func (t *TProxy) NewPacket(buffer *buf.Buffer, oob []byte, source M.Socksaddr) { + destination, err := redir.GetOriginalDestinationFromOOB(oob) + if err != nil { + t.logger.Warn("process packet from ", source, ": get tproxy destination: ", err) + return + } + t.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, M.SocksaddrFromNetIP(destination), nil) +} + +func (t *TProxy) preparePacketConnection(source M.Socksaddr, destination M.Socksaddr, userData any) (bool, context.Context, N.PacketWriter, N.CloseHandlerFunc) { + ctx := log.ContextWithNewID(t.ctx) + writer := &tproxyPacketWriter{ + ctx: ctx, + listener: t.listener, + source: source.AddrPort(), + destination: destination, + } + return true, ctx, writer, func(it error) { + common.Close(common.PtrOrNil(writer.conn)) + } +} + +type tproxyPacketWriter struct { + ctx context.Context + listener *listener.Listener + source netip.AddrPort + destination M.Socksaddr + conn *net.UDPConn +} + +func (w *tproxyPacketWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { + defer buffer.Release() + if w.listener.ListenOptions().NetNs == "" { + conn := w.conn + if w.destination == destination && conn != nil { + _, err := conn.WriteToUDPAddrPort(buffer.Bytes(), w.source) + if err != nil { + w.conn = nil + } + return err + } + } + var listenConfig net.ListenConfig + listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr()) + listenConfig.Control = control.Append(listenConfig.Control, redir.TProxyWriteBack()) + packetConn, err := w.listener.ListenPacket(listenConfig, w.ctx, "udp", destination.String()) + if err != nil { + return err + } + udpConn := packetConn.(*net.UDPConn) + if w.listener.ListenOptions().NetNs == "" && w.destination == destination { + w.conn = udpConn + } else { + defer udpConn.Close() + } + return common.Error(udpConn.WriteToUDPAddrPort(buffer.Bytes(), w.source)) +} diff --git a/protocol/shadowsocks/inbound.go b/protocol/shadowsocks/inbound.go new file mode 100644 index 0000000000..fc77c423ca --- /dev/null +++ b/protocol/shadowsocks/inbound.go @@ -0,0 +1,202 @@ +package shadowsocks + +import ( + "context" + "net" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/mux" + "github.com/sagernet/sing-box/common/uot" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + obfs "github.com/sagernet/sing-box/transport/simple-obfs" + "github.com/sagernet/sing-shadowsocks" + "github.com/sagernet/sing-shadowsocks/shadowaead" + "github.com/sagernet/sing-shadowsocks/shadowaead_2022" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/ntp" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.ShadowsocksInboundOptions](registry, C.TypeShadowsocks, NewInbound) +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) { + if len(options.Users) > 0 && len(options.Destinations) > 0 { + return nil, E.New("users and destinations options must not be combined") + } else if options.Managed && (len(options.Users) > 0 || len(options.Destinations) > 0) { + return nil, E.New("users and destinations options are not supported in managed servers") + } + if len(options.Users) > 0 || options.Managed { + return newMultiInbound(ctx, router, logger, tag, options) + } else if len(options.Destinations) > 0 { + return newRelayInbound(ctx, router, logger, tag, options) + } else { + return newInbound(ctx, router, logger, tag, options) + } +} + +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) + +type Inbound struct { + inbound.Adapter + ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener + service shadowsocks.Service + obfsMode string +} + +func newInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*Inbound, error) { + switch options.ObfsMode { + case "", "http", "tls": + default: + return nil, E.New("shadowsocks: unsupported obfs mode: ", options.ObfsMode) + } + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeShadowsocks, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, + obfsMode: options.ObfsMode, + } + var err error + inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) + if err != nil { + return nil, err + } + var udpTimeout time.Duration + if options.UDPTimeout != 0 { + udpTimeout = time.Duration(options.UDPTimeout) + } else { + udpTimeout = C.UDPTimeout + } + switch { + case options.Method == shadowsocks.MethodNone: + inbound.service = shadowsocks.NewNoneService(int64(udpTimeout.Seconds()), adapter.NewLegacyUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound)) + case common.Contains(shadowaead.List, options.Method): + inbound.service, err = shadowaead.NewService(options.Method, nil, options.Password, int64(udpTimeout.Seconds()), adapter.NewLegacyUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound)) + case common.Contains(shadowaead_2022.List, options.Method): + inbound.service, err = shadowaead_2022.NewServiceWithPassword(options.Method, options.Password, int64(udpTimeout.Seconds()), adapter.NewLegacyUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound), ntp.TimeFuncFromContext(ctx)) + default: + err = E.New("unsupported method: ", options.Method) + } + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: options.Network.Build(), + Listen: options.ListenOptions, + ConnectionHandler: inbound, + PacketHandler: inbound, + ThreadUnsafePacketWriter: true, + }) + return inbound, err +} + +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + return h.listener.Start() +} + +func (h *Inbound) Close() error { + return h.listener.Close() +} + +//nolint:staticcheck +func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + switch h.obfsMode { + case "http": + conn = obfs.NewHTTPObfsServer(conn) + case "tls": + conn = obfs.NewTLSObfsServer(conn) + } + err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + if E.IsClosedOrCanceled(err) { + h.logger.DebugContext(ctx, "connection closed: ", err) + } else { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } + } +} + +//nolint:staticcheck +func (h *Inbound) NewPacket(buffer *buf.Buffer, source M.Socksaddr) { + err := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source}) + if err != nil { + h.logger.Error(E.Cause(err, "process packet from ", source)) + } +} + +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + return h.router.RouteConnection(ctx, conn, metadata) +} + +func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + ctx = log.ContextWithNewID(ctx) + h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) + h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + return h.router.RoutePacketConnection(ctx, conn, metadata) +} + +var _ N.PacketConn = (*stubPacketConn)(nil) + +type stubPacketConn struct { + N.PacketWriter +} + +func (c *stubPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) { + panic("stub!") +} + +func (c *stubPacketConn) Close() error { + return nil +} + +func (c *stubPacketConn) LocalAddr() net.Addr { + panic("stub!") +} + +func (c *stubPacketConn) SetDeadline(t time.Time) error { + panic("stub!") +} + +func (c *stubPacketConn) SetReadDeadline(t time.Time) error { + panic("stub!") +} + +func (c *stubPacketConn) SetWriteDeadline(t time.Time) error { + panic("stub!") +} + +func (h *Inbound) NewError(ctx context.Context, err error) { + NewError(h.logger, ctx, err) +} + +// Deprecated: remove +func NewError(logger logger.ContextLogger, ctx context.Context, err error) { + common.Close(err) + if E.IsClosedOrCanceled(err) { + logger.DebugContext(ctx, "connection closed: ", err) + return + } + logger.ErrorContext(ctx, err) +} diff --git a/protocol/shadowsocks/inbound_multi.go b/protocol/shadowsocks/inbound_multi.go new file mode 100644 index 0000000000..1da2bd9dc7 --- /dev/null +++ b/protocol/shadowsocks/inbound_multi.go @@ -0,0 +1,226 @@ +package shadowsocks + +import ( + "context" + "net" + "os" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/mux" + "github.com/sagernet/sing-box/common/uot" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + obfs "github.com/sagernet/sing-box/transport/simple-obfs" + "github.com/sagernet/sing-shadowsocks" + "github.com/sagernet/sing-shadowsocks/shadowaead" + "github.com/sagernet/sing-shadowsocks/shadowaead_2022" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/auth" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/ntp" +) + +var ( + _ adapter.TCPInjectableInbound = (*MultiInbound)(nil) + _ adapter.ManagedSSMServer = (*MultiInbound)(nil) +) + +type MultiInbound struct { + inbound.Adapter + ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener + service shadowsocks.MultiService[int] + users []option.ShadowsocksUser + tracker adapter.SSMTracker + obfsMode string +} + +func newMultiInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*MultiInbound, error) { + switch options.ObfsMode { + case "", "http", "tls": + default: + return nil, E.New("shadowsocks: unsupported obfs mode: ", options.ObfsMode) + } + inbound := &MultiInbound{ + Adapter: inbound.NewAdapter(C.TypeShadowsocks, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, + obfsMode: options.ObfsMode, + } + var err error + inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) + if err != nil { + return nil, err + } + var udpTimeout time.Duration + if options.UDPTimeout != 0 { + udpTimeout = time.Duration(options.UDPTimeout) + } else { + udpTimeout = C.UDPTimeout + } + var service shadowsocks.MultiService[int] + if common.Contains(shadowaead_2022.List, options.Method) { + service, err = shadowaead_2022.NewMultiServiceWithPassword[int]( + options.Method, + options.Password, + int64(udpTimeout.Seconds()), + adapter.NewLegacyUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound), + ntp.TimeFuncFromContext(ctx), + ) + } else if common.Contains(shadowaead.List, options.Method) { + service, err = shadowaead.NewMultiService[int]( + options.Method, + int64(udpTimeout.Seconds()), + adapter.NewLegacyUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound), + ) + } else { + return nil, E.New("unsupported method: " + options.Method) + } + if err != nil { + return nil, err + } + if len(options.Users) > 0 { + err = service.UpdateUsersWithPasswords(common.MapIndexed(options.Users, func(index int, user option.ShadowsocksUser) int { + return index + }), common.Map(options.Users, func(user option.ShadowsocksUser) string { + return user.Password + })) + if err != nil { + return nil, err + } + } + inbound.service = service + inbound.users = options.Users + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: options.Network.Build(), + Listen: options.ListenOptions, + ConnectionHandler: inbound, + PacketHandler: inbound, + ThreadUnsafePacketWriter: true, + }) + return inbound, err +} + +func (h *MultiInbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + return h.listener.Start() +} + +func (h *MultiInbound) Close() error { + return h.listener.Close() +} + +func (h *MultiInbound) SetTracker(tracker adapter.SSMTracker) { + h.tracker = tracker +} + +func (h *MultiInbound) UpdateUsers(users []string, uPSKs []string) error { + err := h.service.UpdateUsersWithPasswords(common.MapIndexed(users, func(index int, user string) int { + return index + }), uPSKs) + if err != nil { + return err + } + h.users = common.Map(users, func(user string) option.ShadowsocksUser { + return option.ShadowsocksUser{ + Name: user, + } + }) + return nil +} + +//nolint:staticcheck +func (h *MultiInbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + switch h.obfsMode { + case "http": + conn = obfs.NewHTTPObfsServer(conn) + case "tls": + conn = obfs.NewTLSObfsServer(conn) + } + err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + if E.IsClosedOrCanceled(err) { + h.logger.DebugContext(ctx, "connection closed: ", err) + } else { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } + } +} + +//nolint:staticcheck +func (h *MultiInbound) NewPacket(buffer *buf.Buffer, source M.Socksaddr) { + err := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source}) + if err != nil { + h.logger.Error(E.Cause(err, "process packet from ", source)) + } +} + +func (h *MultiInbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + userIndex, loaded := auth.UserFromContext[int](ctx) + if !loaded { + return os.ErrInvalid + } + user := h.users[userIndex].Name + if user == "" { + user = F.ToString(userIndex) + } else { + metadata.User = user + } + h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + //nolint:staticcheck + metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck + if h.tracker != nil { + conn = h.tracker.TrackConnection(conn, metadata) + } + return h.router.RouteConnection(ctx, conn, metadata) +} + +func (h *MultiInbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + userIndex, loaded := auth.UserFromContext[int](ctx) + if !loaded { + return os.ErrInvalid + } + user := h.users[userIndex].Name + if user == "" { + user = F.ToString(userIndex) + } else { + metadata.User = user + } + ctx = log.ContextWithNewID(ctx) + h.logger.InfoContext(ctx, "[", user, "] inbound packet connection from ", metadata.Source) + h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + //nolint:staticcheck + metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck + if h.tracker != nil { + conn = h.tracker.TrackPacketConnection(conn, metadata) + } + return h.router.RoutePacketConnection(ctx, conn, metadata) +} + +//nolint:staticcheck +func (h *MultiInbound) NewError(ctx context.Context, err error) { + NewError(h.logger, ctx, err) +} diff --git a/protocol/shadowsocks/inbound_relay.go b/protocol/shadowsocks/inbound_relay.go new file mode 100644 index 0000000000..7fe4534300 --- /dev/null +++ b/protocol/shadowsocks/inbound_relay.go @@ -0,0 +1,180 @@ +package shadowsocks + +import ( + "context" + "net" + "os" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/mux" + "github.com/sagernet/sing-box/common/uot" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + obfs "github.com/sagernet/sing-box/transport/simple-obfs" + "github.com/sagernet/sing-shadowsocks/shadowaead_2022" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/auth" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +var _ adapter.TCPInjectableInbound = (*RelayInbound)(nil) + +type RelayInbound struct { + inbound.Adapter + ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener + service *shadowaead_2022.RelayService[int] + destinations []option.ShadowsocksDestination + obfsMode string +} + +func newRelayInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*RelayInbound, error) { + switch options.ObfsMode { + case "", "http", "tls": + default: + return nil, E.New("shadowsocks: unsupported obfs mode: ", options.ObfsMode) + } + inbound := &RelayInbound{ + Adapter: inbound.NewAdapter(C.TypeShadowsocks, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, + destinations: options.Destinations, + obfsMode: options.ObfsMode, + } + var err error + inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) + if err != nil { + return nil, err + } + var udpTimeout time.Duration + if options.UDPTimeout != 0 { + udpTimeout = time.Duration(options.UDPTimeout) + } else { + udpTimeout = C.UDPTimeout + } + service, err := shadowaead_2022.NewRelayServiceWithPassword[int]( + options.Method, + options.Password, + int64(udpTimeout.Seconds()), + adapter.NewLegacyUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound), + ) + if err != nil { + return nil, err + } + err = service.UpdateUsersWithPasswords(common.MapIndexed(options.Destinations, func(index int, user option.ShadowsocksDestination) int { + return index + }), common.Map(options.Destinations, func(user option.ShadowsocksDestination) string { + return user.Password + }), common.Map(options.Destinations, option.ShadowsocksDestination.Build)) + if err != nil { + return nil, err + } + inbound.service = service + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: options.Network.Build(), + Listen: options.ListenOptions, + ConnectionHandler: inbound, + PacketHandler: inbound, + ThreadUnsafePacketWriter: true, + }) + return inbound, err +} + +func (h *RelayInbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + return h.listener.Start() +} + +func (h *RelayInbound) Close() error { + return h.listener.Close() +} + +//nolint:staticcheck +func (h *RelayInbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + switch h.obfsMode { + case "http": + conn = obfs.NewHTTPObfsServer(conn) + case "tls": + conn = obfs.NewTLSObfsServer(conn) + } + err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + if E.IsClosedOrCanceled(err) { + h.logger.DebugContext(ctx, "connection closed: ", err) + } else { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } + } +} + +//nolint:staticcheck +func (h *RelayInbound) NewPacket(buffer *buf.Buffer, source M.Socksaddr) { + err := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source}) + if err != nil { + h.logger.Error(E.Cause(err, "process packet from ", source)) + } +} + +func (h *RelayInbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + destinationIndex, loaded := auth.UserFromContext[int](ctx) + if !loaded { + return os.ErrInvalid + } + destination := h.destinations[destinationIndex].Name + if destination == "" { + destination = F.ToString(destinationIndex) + } else { + metadata.User = destination + } + h.logger.InfoContext(ctx, "[", destination, "] inbound connection to ", metadata.Destination) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + //nolint:staticcheck + metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck + return h.router.RouteConnection(ctx, conn, metadata) +} + +func (h *RelayInbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + destinationIndex, loaded := auth.UserFromContext[int](ctx) + if !loaded { + return os.ErrInvalid + } + destination := h.destinations[destinationIndex].Name + if destination == "" { + destination = F.ToString(destinationIndex) + } else { + metadata.User = destination + } + ctx = log.ContextWithNewID(ctx) + h.logger.InfoContext(ctx, "[", destination, "] inbound packet connection from ", metadata.Source) + h.logger.InfoContext(ctx, "[", destination, "] inbound packet connection to ", metadata.Destination) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + //nolint:staticcheck + metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck + return h.router.RoutePacketConnection(ctx, conn, metadata) +} + +//nolint:staticcheck +func (h *RelayInbound) NewError(ctx context.Context, err error) { + NewError(h.logger, ctx, err) +} diff --git a/outbound/shadowsocks.go b/protocol/shadowsocks/outbound.go similarity index 73% rename from outbound/shadowsocks.go rename to protocol/shadowsocks/outbound.go index addb6e576f..9b9d9252fe 100644 --- a/outbound/shadowsocks.go +++ b/protocol/shadowsocks/outbound.go @@ -1,10 +1,11 @@ -package outbound +package shadowsocks import ( "context" "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/mux" C "github.com/sagernet/sing-box/constant" @@ -15,15 +16,19 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/uot" ) -var _ adapter.Outbound = (*Shadowsocks)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.ShadowsocksOutboundOptions](registry, C.TypeShadowsocks, NewOutbound) +} -type Shadowsocks struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger dialer N.Dialer method shadowsocks.Method serverAddr M.Socksaddr @@ -32,26 +37,20 @@ type Shadowsocks struct { multiplexDialer *mux.Client } -func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksOutboundOptions) (*Shadowsocks, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksOutboundOptions) (adapter.Outbound, error) { method, err := shadowsocks.CreateMethod(ctx, options.Method, shadowsocks.MethodOptions{ Password: options.Password, }) if err != nil { return nil, err } - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } - outbound := &Shadowsocks{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeShadowsocks, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeShadowsocks, tag, options.Network.Build(), options.DialerOptions), + logger: logger, dialer: outboundDialer, method: method, serverAddr: options.ServerOptions.Build(), @@ -78,9 +77,9 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte return outbound, nil } -func (h *Shadowsocks) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - ctx, metadata := adapter.AppendContext(ctx) - metadata.Outbound = h.tag +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() metadata.Destination = destination if h.multiplexDialer == nil { switch N.NetworkName(network) { @@ -106,9 +105,9 @@ func (h *Shadowsocks) DialContext(ctx context.Context, network string, destinati } } -func (h *Shadowsocks) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - ctx, metadata := adapter.AppendContext(ctx) - metadata.Outbound = h.tag +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() metadata.Destination = destination if h.multiplexDialer == nil { if h.uotClient != nil { @@ -125,32 +124,23 @@ func (h *Shadowsocks) ListenPacket(ctx context.Context, destination M.Socksaddr) } } -func (h *Shadowsocks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *Shadowsocks) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewPacketConnection(ctx, h, conn, metadata) -} - -func (h *Shadowsocks) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { if h.multiplexDialer != nil { h.multiplexDialer.Reset() } - return } -func (h *Shadowsocks) Close() error { +func (h *Outbound) Close() error { return common.Close(common.PtrOrNil(h.multiplexDialer)) } var _ N.Dialer = (*shadowsocksDialer)(nil) -type shadowsocksDialer Shadowsocks +type shadowsocksDialer Outbound func (h *shadowsocksDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - ctx, metadata := adapter.AppendContext(ctx) - metadata.Outbound = h.tag + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() metadata.Destination = destination switch N.NetworkName(network) { case N.NetworkTCP: @@ -177,8 +167,8 @@ func (h *shadowsocksDialer) DialContext(ctx context.Context, network string, des } func (h *shadowsocksDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - ctx, metadata := adapter.AppendContext(ctx) - metadata.Outbound = h.tag + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() metadata.Destination = destination outConn, err := h.dialer.DialContext(ctx, N.NetworkUDP, h.serverAddr) if err != nil { diff --git a/protocol/shadowtls/inbound.go b/protocol/shadowtls/inbound.go new file mode 100644 index 0000000000..98d9ab0381 --- /dev/null +++ b/protocol/shadowtls/inbound.go @@ -0,0 +1,141 @@ +package shadowtls + +import ( + "context" + "net" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/listener" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-shadowtls" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/auth" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.ShadowTLSInboundOptions](registry, C.TypeShadowTLS, NewInbound) +} + +type Inbound struct { + inbound.Adapter + router adapter.Router + logger logger.ContextLogger + listener *listener.Listener + service *shadowtls.Service +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeShadowTLS, tag), + router: router, + logger: logger, + } + + if options.Version == 0 { + options.Version = 1 + } + + var handshakeForServerName map[string]shadowtls.HandshakeConfig + if options.Version > 1 { + handshakeForServerName = make(map[string]shadowtls.HandshakeConfig) + if options.HandshakeForServerName != nil { + for _, entry := range options.HandshakeForServerName.Entries() { + handshakeDialer, err := dialer.New(ctx, entry.Value.DialerOptions, entry.Value.ServerIsDomain()) + if err != nil { + return nil, err + } + handshakeForServerName[entry.Key] = shadowtls.HandshakeConfig{ + Server: entry.Value.ServerOptions.Build(), + Dialer: handshakeDialer, + } + } + } + } + serverIsDomain := options.Handshake.ServerIsDomain() + if options.WildcardSNI != option.ShadowTLSWildcardSNIOff { + serverIsDomain = true + } + handshakeDialer, err := dialer.New(ctx, options.Handshake.DialerOptions, serverIsDomain) + if err != nil { + return nil, err + } + service, err := shadowtls.NewService(shadowtls.ServiceConfig{ + Version: options.Version, + Password: options.Password, + Users: common.Map(options.Users, func(it option.ShadowTLSUser) shadowtls.User { + return (shadowtls.User)(it) + }), + Handshake: shadowtls.HandshakeConfig{ + Server: options.Handshake.ServerOptions.Build(), + Dialer: handshakeDialer, + }, + HandshakeForServerName: handshakeForServerName, + StrictMode: options.StrictMode, + WildcardSNI: shadowtls.WildcardSNI(options.WildcardSNI), + Handler: (*inboundHandler)(inbound), + Logger: logger, + }) + if err != nil { + return nil, err + } + inbound.service = service + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + }) + return inbound, nil +} + +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + return h.listener.Start() +} + +func (h *Inbound) Close() error { + return h.listener.Close() +} + +func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, metadata.Source, metadata.Destination, onClose) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + if E.IsClosedOrCanceled(err) { + h.logger.DebugContext(ctx, "connection closed: ", err) + } else { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } + } +} + +type inboundHandler Inbound + +func (h *inboundHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + //nolint:staticcheck + metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck + metadata.Source = source + metadata.Destination = destination + if userName, _ := auth.UserFromContext[string](ctx); userName != "" { + metadata.User = userName + h.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", metadata.Destination) + } else { + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + } + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/outbound/shadowtls.go b/protocol/shadowtls/outbound.go similarity index 61% rename from outbound/shadowtls.go rename to protocol/shadowtls/outbound.go index 4427301c40..41a4a6016d 100644 --- a/outbound/shadowtls.go +++ b/protocol/shadowtls/outbound.go @@ -1,4 +1,4 @@ -package outbound +package shadowtls import ( "context" @@ -6,6 +6,7 @@ import ( "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" @@ -17,23 +18,18 @@ import ( N "github.com/sagernet/sing/common/network" ) -var _ adapter.Outbound = (*ShadowTLS)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.ShadowTLSOutboundOptions](registry, C.TypeShadowTLS, NewOutbound) +} -type ShadowTLS struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter client *shadowtls.Client } -func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSOutboundOptions) (*ShadowTLS, error) { - outbound := &ShadowTLS{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeShadowTLS, - network: []string{N.NetworkTCP}, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSOutboundOptions) (adapter.Outbound, error) { + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeShadowTLS, tag, []string{N.NetworkTCP}, options.DialerOptions), } if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired @@ -47,7 +43,7 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context options.TLS.MinVersion = "1.2" options.TLS.MaxVersion = "1.2" } - tlsConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS)) + tlsConfig, err := tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS)) if err != nil { return nil, err } @@ -65,14 +61,14 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context return common.Error(tls.ClientHandshake(ctx, conn, tlsConfig)) } } else { - stdTLSConfig, err := tlsConfig.Config() + stdTLSConfig, err := tlsConfig.STDConfig() if err != nil { return nil, err } tlsHandshakeFunc = shadowtls.DefaultTLSHandshakeFunc(options.Password, stdTLSConfig) } } - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } @@ -91,9 +87,9 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context return outbound, nil } -func (h *ShadowTLS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - ctx, metadata := adapter.AppendContext(ctx) - metadata.Outbound = h.tag +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() metadata.Destination = destination switch N.NetworkName(network) { case N.NetworkTCP: @@ -103,14 +99,6 @@ func (h *ShadowTLS) DialContext(ctx context.Context, network string, destination } } -func (h *ShadowTLS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } - -func (h *ShadowTLS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *ShadowTLS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid -} diff --git a/protocol/snell/inbound.go b/protocol/snell/inbound.go new file mode 100644 index 0000000000..ee2e6efe83 --- /dev/null +++ b/protocol/snell/inbound.go @@ -0,0 +1,308 @@ +package snellprotocol + +import ( + "context" + "net" + "sync" + + snell "github.com/reF1nd/sing-snell" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/uot" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + obfs "github.com/sagernet/sing-box/transport/simple-obfs" + "github.com/sagernet/sing/common/auth" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + udpnat "github.com/sagernet/sing/common/udpnat2" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.SnellInboundOptions](registry, C.TypeSnell, NewInbound) +} + +type Inbound struct { + inbound.Adapter + ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener + service *snell.Service + obfsMode string + obfsHost string + // v5 QUIC proxy state + psk []byte + userPSKs [][]byte // non-nil in multi-user mode; used for QUIC proxy auth + version int + udpNat *udpnat.Service + quicSessions sync.Map // key: netip.AddrPort → *snell.QUICProxySession + // multi-user state + users []option.SnellUser +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SnellInboundOptions) (adapter.Inbound, error) { + if options.PSK == "" && len(options.Users) == 0 { + return nil, E.New("snell: psk or users is required") + } + if options.PSK != "" && len(options.Users) > 0 { + return nil, E.New("snell: psk and users are mutually exclusive") + } + + switch options.ObfsMode { + case "", "http": + case "tls": + ver := options.Version + if ver == 0 { + ver = snell.DefaultVersion + } + if ver >= snell.Version4 { + return nil, E.New("snell: obfs_mode TLS is insecure and not supported for v4/v5; use ShadowTLS instead") + } + default: + return nil, E.New("snell: unsupported obfs mode: ", options.ObfsMode) + } + + i := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeSnell, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, + obfsMode: options.ObfsMode, + obfsHost: options.ObfsHost, + psk: []byte(options.PSK), + version: options.Version, + users: options.Users, + } + + networks := []string{N.NetworkTCP} + if options.Version >= snell.Version5 { + networks = append(networks, N.NetworkUDP) + i.udpNat = udpnat.New((*inboundUDPHandler)(i), i.preparePacketConnection, snell.QUICProxySessionIdleTimeout, false) + } + var serviceConfig snell.ServiceConfig + if len(options.Users) > 0 { + users := make([]snell.User, len(options.Users)) + userPSKs := make([][]byte, len(options.Users)) + for j, u := range options.Users { + users[j] = snell.User{Name: u.Name, PSK: []byte(u.PSK)} + userPSKs[j] = []byte(u.PSK) + } + i.userPSKs = userPSKs + serviceConfig = snell.ServiceConfig{ + Users: users, + Version: options.Version, + UDPEnabled: true, + Handler: (*inboundHandler)(i), + UDPHandler: (*inboundUDPHandler)(i), + Logger: logger, + } + } else { + serviceConfig = snell.ServiceConfig{ + PSK: []byte(options.PSK), + Version: options.Version, + UDPEnabled: true, + Handler: (*inboundHandler)(i), + UDPHandler: (*inboundUDPHandler)(i), + Logger: logger, + } + } + service, err := snell.NewService(serviceConfig) + if err != nil { + return nil, err + } + i.service = service + + i.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: networks, + Listen: options.ListenOptions, + ConnectionHandler: i, + PacketHandler: (*inboundPacketHandler)(i), + }) + return i, nil +} + +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + return h.listener.Start() +} + +func (h *Inbound) Close() error { + return h.listener.Close() +} + +func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + switch h.obfsMode { + case "http": + conn = obfs.NewHTTPObfsServer(conn) + case "tls": + conn = obfs.NewTLSObfsServer(conn) + } + err := h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose) + if err != nil { + N.CloseOnHandshakeFailure(conn, onClose, err) + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} + +type inboundHandler Inbound + +func (h *inboundHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + //nolint:staticcheck + metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck + metadata.Source = source + metadata.Destination = destination.Unwrap() + if userIdx, loaded := auth.UserFromContext[int](ctx); loaded && userIdx < len(h.users) { + metadata.User = h.users[userIdx].Name + } + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +type inboundUDPHandler Inbound + +func (h *inboundUDPHandler) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + //nolint:staticcheck + metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck + metadata.Source = source + metadata.Destination = destination.Unwrap() + if userIdx, loaded := auth.UserFromContext[int](ctx); loaded && userIdx < len(h.users) { + metadata.User = h.users[userIdx].Name + } + h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} + +// preparePacketConnection is the udpnat PrepareFunc for QUIC proxy sessions. +// It returns a PacketWriter that sends raw UDP responses back to the client. +// In multi-user mode, userData is *snell.QUICProxySession and carries the +// matched UserIndex which is injected into the context for auth_user routing. +func (h *Inbound) preparePacketConnection(source M.Socksaddr, destination M.Socksaddr, userData any) (bool, context.Context, N.PacketWriter, N.CloseHandlerFunc) { + ctx := log.ContextWithNewID(h.ctx) + if qsess, ok := userData.(*snell.QUICProxySession); ok && qsess != nil && qsess.UserIndex >= 0 { + ctx = auth.ContextWithUser(ctx, qsess.UserIndex) + } + return true, ctx, &quicProxyResponseWriter{ + writer: h.listener.PacketWriter(), + clientAddr: source, + }, func(err error) { + h.quicSessions.Delete(source.AddrPort()) + } +} + +// quicProxyResponseWriter rewrites the destination to the original client +// address so that responses from the outbound go back to the right client. +type quicProxyResponseWriter struct { + writer N.PacketWriter + clientAddr M.Socksaddr +} + +func (w *quicProxyResponseWriter) WritePacket(buffer *buf.Buffer, _ M.Socksaddr) error { + return w.writer.WritePacket(buffer, w.clientAddr) +} + +// inboundPacketHandler handles raw UDP datagrams from the listener for +// Snell v5 QUIC proxy mode. It decrypts the Snell framing to recover the +// original UDP payload and target, then feeds it into udpnat so that +// routing rules, domain sniffing and detour all apply normally. +type inboundPacketHandler Inbound + +func (h *inboundPacketHandler) NewPacket(buffer *buf.Buffer, source M.Socksaddr) { + defer buffer.Release() + if h.udpNat == nil { + return + } + data := buffer.Bytes() + if len(data) == 0 { + return + } + + val, hasSession := h.quicSessions.Load(source.AddrPort()) + if hasSession { + qsess := val.(*snell.QUICProxySession) + qsess.Touch() + + var payload []byte + if snell.IsQUICLooking(data[0]) { + // Raw QUIC short-header — forward as-is + payload = data + } else { + // Duplicate init frame — decrypt to recover raw UDP payload + var err error + payload, err = qsess.DecodeDuplicateInit(data) + if err != nil { + h.logger.Error("quic proxy: decode duplicate init: ", err) + return + } + } + if len(payload) > 0 { + // Pass qsess as userData so that if the udpnat entry was evicted + // (timeout race between the two maps), PrepareFunc correctly + // re-injects the user context on re-creation. + h.udpNat.NewPacket([][]byte{payload}, source, qsess.Target(), qsess) + } + return + } + + // No existing session + if snell.IsQUICLooking(data[0]) { + // QUIC-looking without a session context — discard + h.logger.Debug("quic proxy: discarding QUIC-looking packet without session from ", source) + return + } + + // Parse Snell QUIC proxy init frame. In multi-user mode try every PSK; + // in single-user mode use h.psk directly. + var qsess *snell.QUICProxySession + var firstPayload []byte + if len(h.userPSKs) > 0 { + var err error + qsess, firstPayload, err = snell.ParseQUICProxyInitMulti(h.userPSKs, data) + if err != nil { + h.logger.Error("quic proxy: parse init from ", source, ": ", err) + return + } + } else { + var err error + qsess, firstPayload, err = snell.ParseQUICProxyInit(h.psk, data) + if err != nil { + h.logger.Error("quic proxy: parse init from ", source, ": ", err) + return + } + } + + // LoadOrStore so that if two concurrent goroutines both parse an init + // frame from the same source (very rare: same client port, same instant), + // only one session wins and both payloads are queued under the same + // crypto/user context. This avoids a benign but unnecessary duplicate + // firstPayload delivery that a plain Store+NewPacket would cause. + if actual, loaded := h.quicSessions.LoadOrStore(source.AddrPort(), qsess); loaded { + qsess = actual.(*snell.QUICProxySession) + } + + target := qsess.Target() + h.logger.Info("quic proxy: new session from ", source, " to ", target) + + if len(firstPayload) > 0 { + // Pass qsess as userData so preparePacketConnection can inject the + // matched user index into the context for auth_user rule matching. + h.udpNat.NewPacket([][]byte{firstPayload}, source, target, qsess) + } +} diff --git a/protocol/snell/outbound.go b/protocol/snell/outbound.go new file mode 100644 index 0000000000..4083853f89 --- /dev/null +++ b/protocol/snell/outbound.go @@ -0,0 +1,373 @@ +package snellprotocol + +import ( + "context" + "fmt" + "net" + "os" + "sync" + "time" + + snell "github.com/reF1nd/sing-snell" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/common/dialer" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + obfs "github.com/sagernet/sing-box/transport/simple-obfs" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.SnellOutboundOptions](registry, C.TypeSnell, NewOutbound) +} + +type Outbound struct { + outbound.Adapter + logger log.ContextLogger + dialer N.Dialer + client *snell.Client + pool *snell.Pool + serverAddr M.Socksaddr + obfsMode string + obfsHost string + serverPort string + psk []byte + version int +} + +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SnellOutboundOptions) (adapter.Outbound, error) { + if options.PSK == "" { + return nil, E.New("snell: psk is required") + } + + switch options.ObfsMode { + case "", "http": + case "tls": + ver := options.Version + if ver == 0 { + ver = snell.DefaultVersion + } + if ver >= snell.Version4 { + return nil, E.New("snell: obfs_mode TLS is insecure and not supported for v4/v5; use ShadowTLS instead") + } + default: + return nil, E.New("snell: unsupported obfs mode: ", options.ObfsMode) + } + + serverAddr := options.ServerOptions.Build() + + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) + if err != nil { + return nil, err + } + + version := options.Version + if version == 0 { + version = snell.DefaultVersion + } + + // Build network list. If the user specified an explicit `network` field use + // that; otherwise apply the protocol default: v3/v4/v5 enable TCP+UDP by + // default, while v1/v2 default to TCP-only (no UDP support). + var networks []string + if string(options.Network) != "" { + networks = options.Network.Build() + for _, net := range networks { + if net == N.NetworkUDP && version < snell.Version3 { + return nil, E.New("snell: UDP requires version 3 or above") + } + } + } else if version >= snell.Version3 { + networks = []string{N.NetworkTCP, N.NetworkUDP} + } else { + networks = []string{N.NetworkTCP} + } + + client, err := snell.NewClient([]byte(options.PSK), version) + if err != nil { + return nil, err + } + + o := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeSnell, tag, networks, options.DialerOptions), + logger: logger, + dialer: outboundDialer, + client: client, + serverAddr: serverAddr, + obfsMode: options.ObfsMode, + obfsHost: options.ObfsHost, + serverPort: fmt.Sprintf("%d", serverAddr.Port), + psk: []byte(options.PSK), + version: version, + } + + // Connection reuse (v4+): build a pool whose factory dials + encrypts a + // fresh stream. The pool is only active when reuse is explicitly enabled. + if options.Reuse && version >= snell.Version4 { + o.pool = snell.NewPool(func(ctx context.Context) (net.Conn, error) { + rawConn, err := outboundDialer.DialContext(ctx, N.NetworkTCP, serverAddr) + if err != nil { + return nil, err + } + return client.WrapStream(o.applyObfs(rawConn)), nil + }) + } + + return o, nil +} + +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() + metadata.Destination = destination + + switch N.NetworkName(network) { + case N.NetworkTCP: + h.logger.InfoContext(ctx, "outbound connection to ", destination) + if h.pool != nil { + return h.client.DialContextWithPool(ctx, h.pool, destination) + } + rawConn, err := h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr) + if err != nil { + return nil, err + } + conn := h.applyObfs(rawConn) + return h.client.DialContext(ctx, conn, destination) + case N.NetworkUDP: + h.logger.InfoContext(ctx, "outbound UDP connection to ", destination) + if h.version >= snell.Version5 { + pc := newV5LazyPacketConn(ctx, h, destination, metadata.Protocol == C.ProtocolQUIC) + return &packetConnWrapper{PacketConn: pc, destination: destination}, nil + } + rawConn, err := h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr) + if err != nil { + return nil, err + } + conn := h.applyObfs(rawConn) + udpStream, err := h.client.DialUDP(ctx, conn) + if err != nil { + return nil, err + } + pc := snell.NewClientPacketConn(udpStream) + return &packetConnWrapper{PacketConn: pc, destination: destination}, nil + } + return nil, os.ErrInvalid +} + +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() + metadata.Destination = destination + + h.logger.InfoContext(ctx, "outbound packet connection to ", destination) + + if h.version >= snell.Version5 { + return newV5LazyPacketConn(ctx, h, destination, metadata.Protocol == C.ProtocolQUIC), nil + } + + rawConn, err := h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr) + if err != nil { + return nil, err + } + conn := h.applyObfs(rawConn) + udpStream, err := h.client.DialUDP(ctx, conn) + if err != nil { + return nil, err + } + return snell.NewClientPacketConn(udpStream), nil +} + +// dialUDPOverTCP creates a UDP-over-TCP tunnel (v3/v4/v5 fallback for non-QUIC UDP). +func (h *Outbound) dialUDPOverTCP(ctx context.Context) (net.PacketConn, error) { + rawConn, err := h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr) + if err != nil { + return nil, err + } + conn := h.applyObfs(rawConn) + udpStream, err := h.client.DialUDP(ctx, conn) + if err != nil { + return nil, err + } + return snell.NewClientPacketConn(udpStream), nil +} + +// dialQUICProxy creates a QUIC proxy PacketConn (v5 only). +// initPayload is the first QUIC Initial packet; it is sent as part of the init frame. +func (h *Outbound) dialQUICProxy(ctx context.Context, destination M.Socksaddr, initPayload []byte) (net.PacketConn, error) { + rawConn, err := h.dialer.DialContext(ctx, N.NetworkUDP, h.serverAddr) + if err != nil { + return nil, err + } + return snell.NewQUICProxyPacketConn(rawConn, h.psk, destination, initPayload) +} + +// v5LazyPacketConn defers connection establishment to the first WriteTo call, +// allowing mode selection between QUIC proxy and UDP-over-TCP. +// +// Mode selection priority: +// 1. sniffQUIC == true (router sniff identified QUIC before reaching this outbound) +// 2. first payload byte >= 0xc0 (QUIC long-header, e.g. Initial / Handshake / 0-RTT) +// +// Using sniff as primary handles the 0-RTT resumption case where the first packet +// is a short-header (0x40-0x7f) and would otherwise be misclassified. +type v5LazyPacketConn struct { + outbound *Outbound + ctx context.Context + destination M.Socksaddr + // sniffQUIC is set when the router's sniff result identified this flow as QUIC. + // It takes priority over the first-byte heuristic. + sniffQUIC bool + + once sync.Once + initCh chan struct{} // closed once conn is ready + conn net.PacketConn + connErr error + firstWriteQUIC bool // true when dialQUICProxy consumed initPayload +} + +func newV5LazyPacketConn(ctx context.Context, ob *Outbound, dst M.Socksaddr, sniffQUIC bool) *v5LazyPacketConn { + return &v5LazyPacketConn{ + outbound: ob, + ctx: ctx, + destination: dst, + sniffQUIC: sniffQUIC, + initCh: make(chan struct{}), + } +} + +func (c *v5LazyPacketConn) initConn(p []byte, addr net.Addr) { + useQUIC := c.sniffQUIC || (len(p) > 0 && snell.IsQUICInitial(p[0])) + if useQUIC { + // Always use c.destination (resolved at ListenPacket/DialContext time, after + // FakeIP reverse-lookup and routing) as the QUIC proxy target. + // Do NOT use addr here, which carries the fake IP in TUN+FakeIP scenarios. + conn, err := c.outbound.dialQUICProxy(c.ctx, c.destination, p) + c.conn = conn + c.connErr = err + c.firstWriteQUIC = (err == nil) + } else { + conn, err := c.outbound.dialUDPOverTCP(c.ctx) + c.conn = conn + c.connErr = err + } + close(c.initCh) +} + +func (c *v5LazyPacketConn) WriteTo(p []byte, addr net.Addr) (int, error) { + var firstWriteQUIC bool + c.once.Do(func() { + c.initConn(p, addr) + firstWriteQUIC = c.firstWriteQUIC + }) + <-c.initCh + if c.connErr != nil { + return 0, c.connErr + } + // For QUIC proxy first write, the payload was already sent inside dialQUICProxy. + if firstWriteQUIC { + return len(p), nil + } + return c.conn.WriteTo(p, addr) +} + +func (c *v5LazyPacketConn) ReadFrom(p []byte) (int, net.Addr, error) { + <-c.initCh + if c.connErr != nil { + return 0, nil, c.connErr + } + return c.conn.ReadFrom(p) +} + +func (c *v5LazyPacketConn) Close() error { + select { + case <-c.initCh: + if c.conn != nil { + return c.conn.Close() + } + return nil + default: + // conn not yet initialized; nothing to close + return nil + } +} + +func (c *v5LazyPacketConn) LocalAddr() net.Addr { + select { + case <-c.initCh: + if c.conn != nil { + return c.conn.LocalAddr() + } + default: + } + return &net.UDPAddr{} +} + +func (c *v5LazyPacketConn) SetDeadline(t time.Time) error { + select { + case <-c.initCh: + if c.conn != nil { + return c.conn.SetDeadline(t) + } + default: + } + return nil +} + +func (c *v5LazyPacketConn) SetReadDeadline(t time.Time) error { + select { + case <-c.initCh: + if c.conn != nil { + return c.conn.SetReadDeadline(t) + } + default: + } + return nil +} + +func (c *v5LazyPacketConn) SetWriteDeadline(t time.Time) error { + select { + case <-c.initCh: + if c.conn != nil { + return c.conn.SetWriteDeadline(t) + } + default: + } + return nil +} + +func (h *Outbound) applyObfs(conn net.Conn) net.Conn { + obfsHost := h.obfsHost + if obfsHost == "" { + obfsHost = "bing.com" + } + switch h.obfsMode { + case "http": + return obfs.NewHTTPObfs(conn, obfsHost, h.serverPort) + case "tls": + return obfs.NewTLSObfs(conn, obfsHost) + } + return conn +} + +// packetConnWrapper wraps net.PacketConn as net.Conn so it can be used in DialContext for UDP networks. +type packetConnWrapper struct { + net.PacketConn + destination M.Socksaddr +} + +func (w *packetConnWrapper) Read(p []byte) (int, error) { + n, _, err := w.PacketConn.ReadFrom(p) + return n, err +} + +func (w *packetConnWrapper) Write(p []byte) (int, error) { + return w.PacketConn.WriteTo(p, w.destination) +} + +func (w *packetConnWrapper) RemoteAddr() net.Addr { + return w.destination +} diff --git a/protocol/socks/inbound.go b/protocol/socks/inbound.go new file mode 100644 index 0000000000..0f570b51f8 --- /dev/null +++ b/protocol/socks/inbound.go @@ -0,0 +1,119 @@ +package socks + +import ( + std_bufio "bufio" + "context" + "net" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/uot" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/auth" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/protocol/socks" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.SocksInboundOptions](registry, C.TypeSOCKS, NewInbound) +} + +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) + +type Inbound struct { + inbound.Adapter + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener + authenticator *auth.Authenticator + udpTimeout time.Duration +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SocksInboundOptions) (adapter.Inbound, error) { + var udpTimeout time.Duration + if options.UDPTimeout != 0 { + udpTimeout = time.Duration(options.UDPTimeout) + } else { + udpTimeout = C.UDPTimeout + } + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeSOCKS, tag), + router: uot.NewRouter(router, logger), + logger: logger, + authenticator: auth.NewAuthenticator(options.Users), + udpTimeout: udpTimeout, + } + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + }) + return inbound, nil +} + +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + return h.listener.Start() +} + +func (h *Inbound) Close() error { + return h.listener.Close() +} + +func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, adapter.NewUpstreamHandler(metadata, h.newUserConnection, h.streamUserPacketConnection), h.listener, h.udpTimeout, metadata.Source, onClose) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + if E.IsClosedOrCanceled(err) { + h.logger.DebugContext(ctx, "connection closed: ", err) + } else { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } + } +} + +func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + user, loaded := auth.UserFromContext[string](ctx) + if !loaded { + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) + return + } + metadata.User = user + h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + user, loaded := auth.UserFromContext[string](ctx) + if !loaded { + if !metadata.Destination.IsValid() { + h.logger.InfoContext(ctx, "inbound packet connection") + } else { + h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + } + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) + return + } + metadata.User = user + if !metadata.Destination.IsValid() { + h.logger.InfoContext(ctx, "[", user, "] inbound packet connection") + } else { + h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) + } + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/outbound/socks.go b/protocol/socks/outbound.go similarity index 53% rename from outbound/socks.go rename to protocol/socks/outbound.go index 063f7b952a..344c798881 100644 --- a/outbound/socks.go +++ b/protocol/socks/outbound.go @@ -1,33 +1,41 @@ -package outbound +package socks import ( "context" "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/uot" "github.com/sagernet/sing/protocol/socks" + "github.com/sagernet/sing/service" ) -var _ adapter.Outbound = (*Socks)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.SOCKSOutboundOptions](registry, C.TypeSOCKS, NewOutbound) +} + +var _ adapter.Outbound = (*Outbound)(nil) -type Socks struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + dnsRouter adapter.DNSRouter + logger logger.ContextLogger client *socks.Client resolve bool uotClient *uot.Client } -func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, options option.SocksOutboundOptions) (*Socks, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SOCKSOutboundOptions) (adapter.Outbound, error) { var version socks.Version var err error if options.Version != "" { @@ -38,21 +46,16 @@ func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, optio if err != nil { return nil, err } - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } - outbound := &Socks{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeSOCKS, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, - client: socks.NewClient(outboundDialer, options.ServerOptions.Build(), version, options.Username, options.Password), - resolve: version == socks.Version4, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeSOCKS, tag, options.Network.Build(), options.DialerOptions), + dnsRouter: service.FromContext[adapter.DNSRouter](ctx), + logger: logger, + client: socks.NewClient(outboundDialer, options.ServerOptions.Build(), version, options.Username, options.Password), + resolve: version == socks.Version4, } uotOptions := common.PtrValueOrDefault(options.UDPOverTCP) if uotOptions.Enabled { @@ -64,9 +67,9 @@ func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, optio return outbound, nil } -func (h *Socks) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - ctx, metadata := adapter.AppendContext(ctx) - metadata.Outbound = h.tag +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() metadata.Destination = destination switch N.NetworkName(network) { case N.NetworkTCP: @@ -80,8 +83,8 @@ func (h *Socks) DialContext(ctx context.Context, network string, destination M.S default: return nil, E.Extend(N.ErrUnknownNetwork, network) } - if h.resolve && destination.IsFqdn() { - destinationAddresses, err := h.router.LookupDefault(ctx, destination.Fqdn) + if h.resolve && destination.IsDomain() { + destinationAddresses, err := h.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) if err != nil { return nil, err } @@ -90,16 +93,16 @@ func (h *Socks) DialContext(ctx context.Context, network string, destination M.S return h.client.DialContext(ctx, network, destination) } -func (h *Socks) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - ctx, metadata := adapter.AppendContext(ctx) - metadata.Outbound = h.tag +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() metadata.Destination = destination if h.uotClient != nil { h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) return h.uotClient.ListenPacket(ctx, destination) } - if h.resolve && destination.IsFqdn() { - destinationAddresses, err := h.router.LookupDefault(ctx, destination.Fqdn) + if h.resolve && destination.IsDomain() { + destinationAddresses, err := h.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) if err != nil { return nil, err } @@ -112,19 +115,3 @@ func (h *Socks) ListenPacket(ctx context.Context, destination M.Socksaddr) (net. h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return h.client.ListenPacket(ctx, destination) } - -func (h *Socks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - if h.resolve { - return NewDirectConnection(ctx, h.router, h, conn, metadata, dns.DomainStrategyUseIPv4) - } else { - return NewConnection(ctx, h, conn, metadata) - } -} - -func (h *Socks) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - if h.resolve { - return NewDirectPacketConnection(ctx, h.router, h, conn, metadata, dns.DomainStrategyUseIPv4) - } else { - return NewPacketConnection(ctx, h, conn, metadata) - } -} diff --git a/protocol/ssh/inbound.go b/protocol/ssh/inbound.go new file mode 100644 index 0000000000..3db6661986 --- /dev/null +++ b/protocol/ssh/inbound.go @@ -0,0 +1,222 @@ +package ssh + +import ( + "context" + "fmt" + "net" + "net/netip" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/uot" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + + N "github.com/sagernet/sing/common/network" + "golang.org/x/crypto/ssh" +) + +// RegisterInbound registers SSH inbound type +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.SSHInboundOptions](registry, C.TypeSSH, NewInbound) +} + +// Inbound represents SSH inbound +type Inbound struct { + inbound.Adapter + ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener + users map[string]option.SSHUser + config *ssh.ServerConfig +} + +// NewInbound creates new SSH inbound +func NewInbound( + ctx context.Context, + router adapter.Router, + logger log.ContextLogger, + tag string, + options option.SSHInboundOptions, +) (adapter.Inbound, error) { + + users := map[string]option.SSHUser{} + for _, u := range options.Users { + users[u.User] = u + } + + serverConfig := &ssh.ServerConfig{} + + // Password auth + serverConfig.PasswordCallback = func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { + u, ok := users[c.User()] + logger.Debug("checking user pass ", c.User()) + if !ok || u.Password == "" { + return nil, fmt.Errorf("invalid user") + } + if u.Password != string(pass) { + return nil, fmt.Errorf("invalid password") + } + return &ssh.Permissions{}, nil + } + + // PublicKey auth + serverConfig.PublicKeyCallback = func(c ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) { + u, ok := users[c.User()] + logger.Debug("checking user pub ", c.User()) + if !ok || u.PublicKey == "" { + return nil, fmt.Errorf("invalid user") + } + + parsedKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(u.PublicKey)) + if err != nil { + return nil, err + } + + // Compare by Marshal (KeysEqual may not exist) + if string(parsedKey.Marshal()) == string(key.Marshal()) { + return &ssh.Permissions{}, nil + } + return nil, fmt.Errorf("invalid key") + } + + // HostKeys + for _, key := range options.HostKey { + privBytes := []byte(key) + priv, err := ssh.ParsePrivateKey(privBytes) + if err != nil { + return nil, err + } + serverConfig.AddHostKey(priv) + } + + in := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeSSH, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), // UDP over TCP + logger: logger, + + users: users, + config: serverConfig, + } + in.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Listen: options.ListenOptions, + Network: []string{"tcp"}, + ConnectionHandler: in, + }, + ) + + return in, nil +} + +// Start listener +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + + if err := h.listener.Start(); err != nil { + return err + } + + return nil +} + +//nolint:staticcheck +func (h *Inbound) NewConnection(ctx context.Context, rawConn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + ctx = log.ContextWithNewID(ctx) + h.logger.Debug("getting connection", fmt.Sprint(metadata)) + sshConn, chans, reqs, err := ssh.NewServerConn(rawConn, h.config) + if err != nil { + h.logger.Debug("error connection", err) + rawConn.Close() + return + } + defer sshConn.Close() + h.logger.Debug("user:", sshConn.User()) + + go ssh.DiscardRequests(reqs) + + for newChannel := range chans { + h.logger.Debug("channel connection", fmt.Sprintf("%+v", newChannel)) + if newChannel.ChannelType() != "direct-tcpip" { + h.logger.Debug("not direct-tcpip connection", fmt.Sprintf("%+v", newChannel.ChannelType())) + newChannel.Reject(ssh.UnknownChannelType, "unsupported") + continue + } + forward := forwardData{} + if err := ssh.Unmarshal(newChannel.ExtraData(), &forward); err != nil { + newChannel.Reject(ssh.ConnectionFailed, "error parsing forward data: "+err.Error()) + return + } + channel, requests, err := newChannel.Accept() + if err != nil { + continue + } + go ssh.DiscardRequests(requests) + + h.logger.Debug("Forwarding to ", forward.DestAddr, ":", forward.DestPort) + + go h.routeChannel(ctx, channel, forward, sshConn.User()) + } +} + +type forwardData struct { + DestAddr string + DestPort uint32 + + OriginAddr string + OriginPort uint32 +} + +// Close listener +func (h *Inbound) Close() error { + return h.listener.Close() +} + +// routeChannel routes channel to router +func (h *Inbound) routeChannel(ctx context.Context, channel ssh.Channel, d forwardData, user string) { + var metadata adapter.InboundContext + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.User = user + metadata.Destination = M.Socksaddr{ + Port: uint16(d.DestPort), + } + if ip, err := netip.ParseAddr(d.DestAddr); err == nil { + metadata.Destination.Addr = ip + } else { + metadata.Destination.Fqdn = d.DestAddr + } + + h.logger.InfoContext(ctx, "[", user, "] SSH inbound connection") + // Wrap channel to net.Conn + con := &channelConn{Channel: channel} // localAddr: &net.TCPAddr{IP: net.ParseIP(d.OriginAddr), Port: int(d.OriginPort)}, + // remoteAddr: &net.TCPAddr{IP: net.ParseIP(d.DestAddr), Port: int(d.DestPort)}, + + h.router.RouteConnectionEx(ctx, con, metadata, func(it error) { + channel.Close() + }) +} + +// ----------------- helper: wrap ssh.Channel to net.Conn ----------------- +type channelConn struct { + ssh.Channel + localAddr net.Addr + remoteAddr net.Addr +} + +func (c *channelConn) LocalAddr() net.Addr { return c.localAddr } +func (c *channelConn) RemoteAddr() net.Addr { return c.remoteAddr } +func (c *channelConn) SetDeadline(t time.Time) error { return nil } +func (c *channelConn) SetReadDeadline(t time.Time) error { return nil } +func (c *channelConn) SetWriteDeadline(t time.Time) error { return nil } diff --git a/outbound/ssh.go b/protocol/ssh/outbound.go similarity index 56% rename from outbound/ssh.go rename to protocol/ssh/outbound.go index ce62b00481..f93eaac536 100644 --- a/outbound/ssh.go +++ b/protocol/ssh/outbound.go @@ -1,4 +1,4 @@ -package outbound +package ssh import ( "bytes" @@ -10,28 +10,35 @@ import ( "strconv" "strings" "sync" + "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/monitoring" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/uot" "golang.org/x/crypto/ssh" ) -var ( - _ adapter.Outbound = (*SSH)(nil) - _ adapter.InterfaceUpdateListener = (*SSH)(nil) -) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.SSHOutboundOptions](registry, C.TypeSSH, NewOutbound) +} + +var _ adapter.InterfaceUpdateListener = (*Outbound)(nil) -type SSH struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter ctx context.Context + logger logger.ContextLogger dialer N.Dialer serverAddr M.Socksaddr user string @@ -42,23 +49,20 @@ type SSH struct { clientAccess sync.Mutex clientConn net.Conn client *ssh.Client + uotClient *uot.Client + connectionErr string } -func NewSSH(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SSHOutboundOptions) (*SSH, error) { - outboundDialer, err := dialer.New(router, options.DialerOptions) +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SSHOutboundOptions) (adapter.Outbound, error) { + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } - outbound := &SSH{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeSSH, - network: []string{N.NetworkTCP}, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeSSH, tag, options.Network.Build(), options.DialerOptions), ctx: ctx, + logger: logger, dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), user: options.User, @@ -109,6 +113,13 @@ func NewSSH(ctx context.Context, router adapter.Router, logger log.ContextLogger outbound.hostKey = append(outbound.hostKey, key) } } + uotOptions := common.PtrValueOrDefault(options.UDPOverTCP) + if uotOptions.Enabled { + outbound.uotClient = &uot.Client{ + Dialer: outbound, + Version: uotOptions.Version, + } + } return outbound, nil } @@ -122,7 +133,7 @@ func randomVersion() string { return version } -func (s *SSH) connect() (*ssh.Client, error) { +func (s *Outbound) connect() (*ssh.Client, error) { if s.client != nil { return s.client, nil } @@ -153,6 +164,7 @@ func (s *SSH) connect() (*ssh.Client, error) { return nil } } + return E.New("host key mismatch, server send ", key.Type(), " ", base64.StdEncoding.EncodeToString(serverKey)) }, } @@ -179,31 +191,94 @@ func (s *SSH) connect() (*ssh.Client, error) { return client, nil } -func (s *SSH) InterfaceUpdated() { +func (s *Outbound) PostStart() error { + s.connect() + if s.IsReady() { + monitoring.Get(s.ctx).TestNow(s.Tag()) + } + return nil +} + +func (s *Outbound) InterfaceUpdated() { common.Close(s.clientConn) - return } -func (s *SSH) Close() error { +func (s *Outbound) Close() error { return common.Close(s.clientConn) } -func (s *SSH) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (s *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = s.Tag() + metadata.Destination = destination client, err := s.connect() + if err != nil { + s.connectionErr = err.Error() + return nil, err + } + s.connectionErr = "" + + switch N.NetworkName(network) { + + case N.NetworkTCP: + s.logger.InfoContext(ctx, "outbound connection to ", destination) + case N.NetworkUDP: + if s.uotClient != nil { + s.logger.InfoContext(ctx, "outbound UoT connect packet connection to ", destination) + return s.uotClient.DialContext(ctx, network, destination) + } else { + s.logger.InfoContext(ctx, "outbound packet connection to ", destination) + } + } + conn, err := client.Dial(network, destination.String()) if err != nil { return nil, err } - return client.Dial(network, destination.String()) + return &chanConnWrapper{Conn: conn}, nil } -func (s *SSH) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (s *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = s.Tag() + metadata.Destination = destination + if s.uotClient != nil { + s.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) + return s.uotClient.ListenPacket(ctx, destination) + } else { + s.logger.InfoContext(ctx, "outbound packet connection to ", destination) + } + return nil, os.ErrInvalid } -func (s *SSH) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, s, conn, metadata) +type chanConnWrapper struct { + net.Conn } -func (s *SSH) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (c *chanConnWrapper) SetDeadline(t time.Time) error { return os.ErrInvalid } + +func (c *chanConnWrapper) SetReadDeadline(t time.Time) error { + return os.ErrInvalid +} + +func (c *chanConnWrapper) SetWriteDeadline(t time.Time) error { + return os.ErrInvalid +} + +func (s *Outbound) IsReady() bool { + return s.client != nil +} +func (s *Outbound) ProxyDisplayName() string { + str := C.ProxyDisplayName(s.Type()) + if !s.IsReady() { + if s.connectionErr != "" { + str += " ❌ " + str += s.connectionErr + } else { + str += " ⚠️ Connecting..." + } + } + return s.connectionErr +} diff --git a/protocol/tailscale/certificate_provider.go b/protocol/tailscale/certificate_provider.go new file mode 100644 index 0000000000..8170943b12 --- /dev/null +++ b/protocol/tailscale/certificate_provider.go @@ -0,0 +1,95 @@ +//go:build with_gvisor + +package tailscale + +import ( + "context" + "crypto/tls" + "net" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/certificate" + "github.com/sagernet/sing-box/common/dialer" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" + "github.com/sagernet/tailscale/client/local" +) + +func RegisterCertificateProvider(registry *certificate.Registry) { + certificate.Register[option.TailscaleCertificateProviderOptions](registry, C.TypeTailscale, NewCertificateProvider) +} + +var _ adapter.CertificateProviderService = (*CertificateProvider)(nil) + +type CertificateProvider struct { + certificate.Adapter + endpointTag string + endpoint *Endpoint + dialer N.Dialer + localClient *local.Client +} + +func NewCertificateProvider(ctx context.Context, _ log.ContextLogger, tag string, options option.TailscaleCertificateProviderOptions) (adapter.CertificateProviderService, error) { + if options.Endpoint == "" { + return nil, E.New("missing tailscale endpoint tag") + } + endpointManager := service.FromContext[adapter.EndpointManager](ctx) + rawEndpoint, loaded := endpointManager.Get(options.Endpoint) + if !loaded { + return nil, E.New("endpoint not found: ", options.Endpoint) + } + endpoint, isTailscale := rawEndpoint.(*Endpoint) + if !isTailscale { + return nil, E.New("endpoint is not Tailscale: ", options.Endpoint) + } + providerDialer, err := dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: option.DialerOptions{}, + RemoteIsDomain: true, + }) + if err != nil { + return nil, E.Cause(err, "create tailscale certificate provider dialer") + } + return &CertificateProvider{ + Adapter: certificate.NewAdapter(C.TypeTailscale, tag), + endpointTag: options.Endpoint, + endpoint: endpoint, + dialer: providerDialer, + }, nil +} + +func (p *CertificateProvider) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + localClient, err := p.endpoint.Server().LocalClient() + if err != nil { + return E.Cause(err, "initialize tailscale local client for endpoint ", p.endpointTag) + } + originalDial := localClient.Dial + localClient.Dial = func(ctx context.Context, network, addr string) (net.Conn, error) { + if originalDial != nil && addr == "local-tailscaled.sock:80" { + return originalDial(ctx, network, addr) + } + return p.dialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) + } + p.localClient = localClient + return nil +} + +func (p *CertificateProvider) Close() error { + return nil +} + +func (p *CertificateProvider) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { + localClient := p.localClient + if localClient == nil { + return nil, E.New("Tailscale is not ready yet") + } + return localClient.GetCertificate(clientHello) +} diff --git a/protocol/tailscale/dns_transport.go b/protocol/tailscale/dns_transport.go new file mode 100644 index 0000000000..25b5aabe07 --- /dev/null +++ b/protocol/tailscale/dns_transport.go @@ -0,0 +1,449 @@ +//go:build with_gvisor + +package tailscale + +import ( + "context" + "errors" + "net" + "net/http" + "net/netip" + "net/url" + "os" + "strings" + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/dns/transport" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" + nDNS "github.com/sagernet/tailscale/net/dns" + "github.com/sagernet/tailscale/types/dnstype" + "github.com/sagernet/tailscale/util/dnsname" + "github.com/sagernet/tailscale/wgengine/router" + "github.com/sagernet/tailscale/wgengine/wgcfg" + + mDNS "github.com/miekg/dns" + "go4.org/netipx" + "golang.org/x/net/http2" +) + +func RegistryTransport(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.TailscaleDNSServerOptions](registry, C.DNSTypeTailscale, NewDNSTransport) +} + +type DNSTransport struct { + dns.TransportAdapter + ctx context.Context + logger logger.ContextLogger + endpointTag string + acceptDefaultResolvers bool + acceptSearchDomain bool + dnsRouter adapter.DNSRouter + endpointManager adapter.EndpointManager + endpoint *Endpoint + access sync.RWMutex + routePrefixes []netip.Prefix + routes map[string][]adapter.DNSTransport + hosts map[string][]netip.Addr + searchDomains []string + defaultResolvers []adapter.DNSTransport +} + +func NewDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.TailscaleDNSServerOptions) (adapter.DNSTransport, error) { + if options.Endpoint == "" { + return nil, E.New("missing tailscale endpoint tag") + } + return &DNSTransport{ + TransportAdapter: dns.NewTransportAdapter(C.DNSTypeTailscale, tag, nil), + ctx: ctx, + logger: logger, + endpointTag: options.Endpoint, + acceptDefaultResolvers: options.AcceptDefaultResolvers, + acceptSearchDomain: options.AcceptSearchDomain, + dnsRouter: service.FromContext[adapter.DNSRouter](ctx), + endpointManager: service.FromContext[adapter.EndpointManager](ctx), + }, nil +} + +func (t *DNSTransport) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateInitialize { + return nil + } + rawOutbound, loaded := t.endpointManager.Get(t.endpointTag) + if !loaded { + return E.New("endpoint not found: ", t.endpointTag) + } + ep, isTailscale := rawOutbound.(*Endpoint) + if !isTailscale { + return E.New("endpoint is not Tailscale: ", t.endpointTag) + } + if ep.onReconfigHook != nil { + return E.New("only one Tailscale DNS server is allowed for single endpoint") + } + ep.onReconfigHook = t.onReconfig + t.endpoint = ep + return nil +} + +func (t *DNSTransport) Reset() { + t.access.RLock() + transports := t.collectResolversLocked() + t.access.RUnlock() + for _, transport := range transports { + transport.Reset() + } +} + +func (t *DNSTransport) onReconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCfg *nDNS.Config) { + err := t.updateDNSServers(routerCfg, dnsCfg) + if err != nil { + t.logger.Error(E.Cause(err, "update DNS servers")) + } +} + +func (t *DNSTransport) updateDNSServers(routeConfig *router.Config, dnsConfig *nDNS.Config) error { + routePrefixes := buildRoutePrefixes(routeConfig) + directDialerOnce := sync.OnceValue(func() N.Dialer { + directDialer := common.Must1(dialer.NewDefault(t.ctx, option.DialerOptions{})) + return &DNSDialer{transport: t, fallbackDialer: directDialer} + }) + routes := make(map[string][]adapter.DNSTransport) + for domain, resolvers := range dnsConfig.Routes { + var myResolvers []adapter.DNSTransport + for _, resolver := range resolvers { + myResolver, err := t.createResolver(directDialerOnce, resolver) + if err != nil { + return err + } + myResolvers = append(myResolvers, myResolver) + } + routes[domain.WithTrailingDot()] = myResolvers + } + hosts := make(map[string][]netip.Addr) + for domain, addresses := range dnsConfig.Hosts { + hosts[domain.WithTrailingDot()] = addresses + } + searchDomains := common.Map(dnsConfig.SearchDomains, func(it dnsname.FQDN) string { + return it.WithTrailingDot() + }) + var defaultResolvers []adapter.DNSTransport + for _, resolver := range dnsConfig.DefaultResolvers { + myResolver, err := t.createResolver(directDialerOnce, resolver) + if err != nil { + return err + } + defaultResolvers = append(defaultResolvers, myResolver) + } + + t.access.Lock() + oldResolvers := t.collectResolversLocked() + t.routePrefixes = routePrefixes + t.routes = routes + t.hosts = hosts + t.searchDomains = searchDomains + t.defaultResolvers = defaultResolvers + t.access.Unlock() + + for _, transport := range oldResolvers { + transport.Close() + } + + if len(defaultResolvers) > 0 { + t.logger.Info("updated ", len(routes), " routes, ", len(hosts), " hosts, ", len(searchDomains), " search domains, default resolvers: ", + strings.Join(common.Map(dnsConfig.DefaultResolvers, func(it *dnstype.Resolver) string { return it.Addr }), " ")) + } else { + t.logger.Info("updated ", len(routes), " routes, ", len(hosts), " hosts, ", len(searchDomains), " search domains") + } + return nil +} + +func (t *DNSTransport) createResolver(directDialer func() N.Dialer, resolver *dnstype.Resolver) (adapter.DNSTransport, error) { + serverURL, parseURLErr := url.Parse(resolver.Addr) + isHTTPScheme := parseURLErr == nil && (serverURL.Scheme == "http" || serverURL.Scheme == "https") + var myDialer N.Dialer + if isHTTPScheme && serverURL.Scheme == "http" { + myDialer = t.endpoint + } else { + myDialer = directDialer() + } + if len(resolver.BootstrapResolution) > 0 { + bootstrapTransport := transport.NewUDPRaw(t.logger, t.TransportAdapter, myDialer, M.SocksaddrFrom(resolver.BootstrapResolution[0], 53)) + myDialer = dialer.NewResolveDialer(t.ctx, myDialer, false, "", adapter.DNSQueryOptions{Transport: bootstrapTransport}, 0) + } else { + myDialer = dialer.NewResolveDialer(t.ctx, myDialer, false, "", t.endpoint.queryOptions, 0) + } + if isHTTPScheme { + serverAddr := M.ParseSocksaddrHostPortStr(serverURL.Hostname(), serverURL.Port()) + switch serverURL.Scheme { + case "https": + if serverAddr.Port == 0 { + serverAddr.Port = 443 + } + tlsConfig := common.Must1(tls.NewClient(t.ctx, t.logger, serverAddr.AddrString(), option.OutboundTLSOptions{ + Enabled: true, + ALPN: []string{http2.NextProtoTLS, "http/1.1"}, + })) + return transport.NewHTTPSRaw(t.TransportAdapter, t.logger, myDialer, serverURL, http.Header{}, serverAddr, tlsConfig), nil + case "http": + if serverAddr.Port == 0 { + serverAddr.Port = 80 + } + return transport.NewHTTPSRaw(t.TransportAdapter, t.logger, myDialer, serverURL, http.Header{}, serverAddr, nil), nil + } + } + serverAddr := M.ParseSocksaddr(resolver.Addr) + if !serverAddr.IsValid() { + if parseURLErr != nil { + return nil, E.Cause(parseURLErr, "parse resolver address") + } + return nil, E.New("invalid resolver address: ", resolver.Addr) + } + if serverAddr.Port == 0 { + serverAddr.Port = 53 + } + return transport.NewUDPRaw(t.logger, t.TransportAdapter, myDialer, serverAddr), nil +} + +func buildRoutePrefixes(routeConfig *router.Config) []netip.Prefix { + var builder netipx.IPSetBuilder + for _, localAddr := range routeConfig.LocalAddrs { + builder.AddPrefix(localAddr) + } + for _, route := range routeConfig.Routes { + builder.AddPrefix(route) + } + for _, route := range routeConfig.LocalRoutes { + builder.AddPrefix(route) + } + for _, route := range routeConfig.SubnetRoutes { + builder.AddPrefix(route) + } + ipSet, err := builder.IPSet() + if err != nil { + return nil + } + return ipSet.Prefixes() +} + +func (t *DNSTransport) Close() error { + t.access.Lock() + transports := t.collectResolversLocked() + t.routePrefixes = nil + t.routes = nil + t.hosts = nil + t.defaultResolvers = nil + t.access.Unlock() + + var err error + for _, transport := range transports { + name := "resolver/" + transport.Type() + "[" + transport.Tag() + "]" + err = E.Append(err, transport.Close(), func(err error) error { + return E.Cause(err, "close ", name) + }) + } + return err +} + +func (t *DNSTransport) Raw() bool { + return true +} + +func (t *DNSTransport) PreferredDomain(domain string) bool { + t.access.RLock() + hosts := t.hosts + routes := t.routes + t.access.RUnlock() + if _, loaded := hosts[domain]; loaded { + return true + } + for suffix := range routes { + if strings.HasSuffix(domain, suffix) { + return true + } + } + return false +} + +func (t *DNSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + if len(message.Question) != 1 { + return nil, os.ErrInvalid + } + if t.acceptSearchDomain && mDNS.CountLabel(message.Question[0].Name) == 1 { + return t.exchangeWithSearchDomains(ctx, message) + } + t.access.RLock() + acceptDefaultResolvers := t.acceptDefaultResolvers + t.access.RUnlock() + return t.exchangeOnce(ctx, message, acceptDefaultResolvers) +} + +func (t *DNSTransport) exchangeWithSearchDomains(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + t.access.RLock() + searchDomains := t.searchDomains + t.access.RUnlock() + originalQuestion := message.Question[0] + singleLabel := strings.TrimSuffix(originalQuestion.Name, ".") + var lastErr error + for _, searchDomain := range searchDomains { + expandedName := singleLabel + "." + searchDomain + question := originalQuestion + question.Name = expandedName + rewritten := *message + rewritten.Question = []mDNS.Question{question} + response, err := t.exchangeOnce(ctx, &rewritten, false) + if err == nil { + if response.Rcode == mDNS.RcodeNameError { + continue + } + restoreOriginalQuestion(response, expandedName, originalQuestion) + return response, nil + } + if errors.Is(err, dns.RcodeNameError) { + continue + } + lastErr = err + } + if lastErr != nil { + return nil, lastErr + } + return nil, dns.RcodeNameError +} + +// RFC 1035 §4.1.1 requires the response Question to match the request byte-for-byte, +// and stub resolvers discard Answer RRs whose owner name does not match the question. +func restoreOriginalQuestion(response *mDNS.Msg, expandedName string, originalQuestion mDNS.Question) { + response.Question = []mDNS.Question{originalQuestion} + for _, rr := range response.Answer { + if strings.EqualFold(rr.Header().Name, expandedName) { + rr.Header().Name = originalQuestion.Name + } + } +} + +func (t *DNSTransport) exchangeOnce(ctx context.Context, message *mDNS.Msg, allowDefaultResolvers bool) (*mDNS.Msg, error) { + question := message.Question[0] + + t.access.RLock() + hosts := t.hosts + routes := t.routes + defaultResolvers := t.defaultResolvers + t.access.RUnlock() + + addresses, hostsLoaded := hosts[question.Name] + if hostsLoaded { + switch question.Qtype { + case mDNS.TypeA: + addresses4 := common.Filter(addresses, func(addr netip.Addr) bool { + return addr.Is4() + }) + if len(addresses4) > 0 { + return dns.FixedResponse(message.Id, question, addresses4, C.DefaultDNSTTL), nil + } + case mDNS.TypeAAAA: + addresses6 := common.Filter(addresses, func(addr netip.Addr) bool { + return addr.Is6() + }) + if len(addresses6) > 0 { + return dns.FixedResponse(message.Id, question, addresses6, C.DefaultDNSTTL), nil + } + } + } + for domainSuffix, transports := range routes { + if strings.HasSuffix(question.Name, domainSuffix) { + if len(transports) == 0 { + return &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Id: message.Id, + Rcode: mDNS.RcodeNameError, + Response: true, + }, + Question: []mDNS.Question{question}, + }, nil + } + var lastErr error + for _, dnsTransport := range transports { + response, err := dnsTransport.Exchange(ctx, message) + if err != nil { + lastErr = err + continue + } + return response, nil + } + return nil, lastErr + } + } + if allowDefaultResolvers { + if len(defaultResolvers) > 0 { + var lastErr error + for _, resolver := range defaultResolvers { + response, err := resolver.Exchange(ctx, message) + if err != nil { + lastErr = err + continue + } + return response, nil + } + return nil, lastErr + } else { + return nil, E.New("missing default resolvers") + } + } + return nil, dns.RcodeNameError +} + +func (t *DNSTransport) collectResolversLocked() []adapter.DNSTransport { + var transports []adapter.DNSTransport + for _, resolvers := range t.routes { + transports = append(transports, resolvers...) + } + transports = append(transports, t.defaultResolvers...) + return transports +} + +type DNSDialer struct { + transport *DNSTransport + fallbackDialer N.Dialer +} + +func (d *DNSDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + if destination.IsDomain() { + panic("invalid request here") + } + routePrefixes := d.transport.routePrefixesSnapshot() + for _, prefix := range routePrefixes { + if prefix.Contains(destination.Addr) { + return d.transport.endpoint.DialContext(ctx, network, destination) + } + } + return d.fallbackDialer.DialContext(ctx, network, destination) +} + +func (d *DNSDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + if destination.IsDomain() { + panic("invalid request here") + } + routePrefixes := d.transport.routePrefixesSnapshot() + for _, prefix := range routePrefixes { + if prefix.Contains(destination.Addr) { + return d.transport.endpoint.ListenPacket(ctx, destination) + } + } + return d.fallbackDialer.ListenPacket(ctx, destination) +} + +func (t *DNSTransport) routePrefixesSnapshot() []netip.Prefix { + t.access.RLock() + defer t.access.RUnlock() + return append([]netip.Prefix(nil), t.routePrefixes...) +} diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go new file mode 100644 index 0000000000..3a742c165f --- /dev/null +++ b/protocol/tailscale/endpoint.go @@ -0,0 +1,953 @@ +//go:build with_gvisor + +package tailscale + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "net/http" + "net/netip" + "net/url" + "os" + "path/filepath" + "reflect" + "runtime" + "strings" + "sync/atomic" + "syscall" + "time" + + "github.com/sagernet/gvisor/pkg/tcpip" + "github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet" + "github.com/sagernet/gvisor/pkg/tcpip/header" + "github.com/sagernet/gvisor/pkg/tcpip/stack" + "github.com/sagernet/gvisor/pkg/tcpip/transport/icmp" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/endpoint" + "github.com/sagernet/sing-box/common/dialer" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + R "github.com/sagernet/sing-box/route/rule" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing-tun/ping" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/bufio" + "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/ntp" + "github.com/sagernet/sing/service" + "github.com/sagernet/sing/service/filemanager" + _ "github.com/sagernet/tailscale/feature/relayserver" + "github.com/sagernet/tailscale/ipn" + tsDNS "github.com/sagernet/tailscale/net/dns" + "github.com/sagernet/tailscale/net/netmon" + "github.com/sagernet/tailscale/net/netns" + "github.com/sagernet/tailscale/net/tsaddr" + tsTUN "github.com/sagernet/tailscale/net/tstun" + "github.com/sagernet/tailscale/tailcfg" + "github.com/sagernet/tailscale/tsnet" + "github.com/sagernet/tailscale/types/ipproto" + "github.com/sagernet/tailscale/types/nettype" + "github.com/sagernet/tailscale/version" + "github.com/sagernet/tailscale/wgengine" + "github.com/sagernet/tailscale/wgengine/filter" + "github.com/sagernet/tailscale/wgengine/router" + "github.com/sagernet/tailscale/wgengine/wgcfg" + + mDNS "github.com/miekg/dns" + "go4.org/netipx" +) + +var ( + _ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil) + _ adapter.DirectRouteOutbound = (*Endpoint)(nil) + _ dialer.PacketDialerWithDestination = (*Endpoint)(nil) +) + +func init() { + version.SetVersion("sing-box " + C.Version) +} + +func RegisterEndpoint(registry *endpoint.Registry) { + endpoint.Register[option.TailscaleEndpointOptions](registry, C.TypeTailscale, NewEndpoint) +} + +type Endpoint struct { + endpoint.Adapter + ctx context.Context + router adapter.Router + logger logger.ContextLogger + queryOptions adapter.DNSQueryOptions + dnsRouter adapter.DNSRouter + network adapter.NetworkManager + platformInterface adapter.PlatformInterface + server *tsnet.Server + stack *stack.Stack + icmpForwarder *tun.ICMPForwarder + filter *atomic.Pointer[filter.Filter] + onReconfigHook wgengine.ReconfigListener + + cfg *wgcfg.Config + dnsCfg *tsDNS.Config + routeDomains common.TypedValue[map[string]bool] + routePrefixes atomic.Pointer[netipx.IPSet] + + acceptRoutes bool + exitNode string + exitNodeAllowLANAccess bool + advertiseRoutes []netip.Prefix + advertiseExitNode bool + advertiseTags []string + relayServerPort *uint16 + relayServerStaticEndpoints []netip.AddrPort + + udpTimeout time.Duration + + systemInterface bool + systemInterfaceName string + systemInterfaceMTU uint32 + serverStarted bool + started atomic.Bool + systemTun tun.Tun + systemDialer *dialer.DefaultDialer + fallbackTCPCloser func() +} + +func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TailscaleEndpointOptions) (adapter.Endpoint, error) { + stateDirectory := options.StateDirectory + if stateDirectory == "" { + stateDirectory = "tailscale" + } + hostname := options.Hostname + if hostname == "" { + osHostname, _ := os.Hostname() + osHostname = strings.TrimSpace(osHostname) + hostname = osHostname + } + if hostname == "" { + hostname = "sing-box" + } + stateDirectory = filemanager.BasePath(ctx, os.ExpandEnv(stateDirectory)) + stateDirectory, _ = filepath.Abs(stateDirectory) + for _, advertiseRoute := range options.AdvertiseRoutes { + if advertiseRoute.Addr().IsUnspecified() && advertiseRoute.Bits() == 0 { + return nil, E.New("`advertise_routes` cannot be default, use `advertise_exit_node` instead.") + } + } + if options.AdvertiseExitNode && options.ExitNode != "" { + return nil, E.New("cannot advertise an exit node and use an exit node at the same time.") + } + var udpTimeout time.Duration + if options.UDPTimeout != 0 { + udpTimeout = time.Duration(options.UDPTimeout) + } else { + udpTimeout = C.UDPTimeout + } + var remoteIsDomain bool + if options.ControlURL != "" { + controlURL, err := url.Parse(options.ControlURL) + if err != nil { + return nil, E.Cause(err, "parse control URL") + } + remoteIsDomain = M.ParseSocksaddr(controlURL.Hostname()).IsDomain() + } else { + // controlplane.tailscale.com + remoteIsDomain = true + } + outboundDialer, err := dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: options.DialerOptions, + RemoteIsDomain: remoteIsDomain, + ResolverOnDetour: true, + NewDialer: true, + }) + if err != nil { + return nil, err + } + dialerQueryOptions := outboundDialer.(dialer.ResolveDialer).QueryOptions() + dnsRouter := service.FromContext[adapter.DNSRouter](ctx) + return &Endpoint{ + Adapter: endpoint.NewAdapter(C.TypeTailscale, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, nil), + ctx: ctx, + router: router, + logger: logger, + dnsRouter: dnsRouter, + queryOptions: dialerQueryOptions, + network: service.FromContext[adapter.NetworkManager](ctx), + platformInterface: service.FromContext[adapter.PlatformInterface](ctx), + server: &tsnet.Server{ + Dir: stateDirectory, + Hostname: hostname, + Logf: func(format string, args ...any) { + logger.Trace(fmt.Sprintf(format, args...)) + }, + UserLogf: func(format string, args ...any) { + logger.Debug(fmt.Sprintf(format, args...)) + }, + Ephemeral: options.Ephemeral, + AuthKey: options.AuthKey, + ControlURL: options.ControlURL, + AdvertiseTags: options.AdvertiseTags, + Dialer: &endpointDialer{Dialer: outboundDialer, logger: logger}, + LookupHook: func(ctx context.Context, host string) ([]netip.Addr, error) { + return dnsRouter.Lookup(ctx, host, dialerQueryOptions) + }, + DNS: &dnsConfigurtor{}, + HTTPClient: &http.Client{ + Transport: &http.Transport{ + ForceAttemptHTTP2: true, + DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { + return outboundDialer.DialContext(ctx, network, M.ParseSocksaddr(address)) + }, + TLSClientConfig: &tls.Config{ + RootCAs: adapter.RootPoolFromContext(ctx), + Time: ntp.TimeFuncFromContext(ctx), + }, + }, + }, + }, + acceptRoutes: options.AcceptRoutes, + exitNode: options.ExitNode, + exitNodeAllowLANAccess: options.ExitNodeAllowLANAccess, + advertiseRoutes: options.AdvertiseRoutes, + advertiseExitNode: options.AdvertiseExitNode, + advertiseTags: options.AdvertiseTags, + relayServerPort: options.RelayServerPort, + relayServerStaticEndpoints: options.RelayServerStaticEndpoints, + udpTimeout: udpTimeout, + systemInterface: options.SystemInterface, + systemInterfaceName: options.SystemInterfaceName, + systemInterfaceMTU: options.SystemInterfaceMTU, + }, nil +} + +func (t *Endpoint) Start(stage adapter.StartStage) error { + switch stage { + case adapter.StartStateInitialize: + t.server.PeerDNSQueryHandler = (*peerDNSQueryHandler)(t) + case adapter.StartStateStart: + return t.start() + case adapter.StartStatePostStart: + return t.postStart() + } + return nil +} + +func (t *Endpoint) start() error { + if t.platformInterface != nil { + err := t.network.UpdateInterfaces() + if err != nil { + return err + } + netmon.RegisterInterfaceGetter(func() ([]netmon.Interface, error) { + return common.Map(t.network.InterfaceFinder().Interfaces(), func(it control.Interface) netmon.Interface { + return netmon.Interface{ + Interface: &net.Interface{ + Index: it.Index, + MTU: it.MTU, + Name: it.Name, + HardwareAddr: it.HardwareAddr, + Flags: it.Flags, + }, + AltAddrs: common.Map(it.Addresses, func(it netip.Prefix) net.Addr { + return &net.IPNet{ + IP: it.Addr().AsSlice(), + Mask: net.CIDRMask(it.Bits(), it.Addr().BitLen()), + } + }), + } + }), nil + }) + } + if t.systemInterface { + mtu := t.systemInterfaceMTU + if mtu == 0 { + mtu = uint32(tsTUN.DefaultTUNMTU()) + } + tunName := t.systemInterfaceName + if tunName == "" { + tunName = tun.CalculateInterfaceName("tailscale") + } + tunOptions := tun.Options{ + Name: tunName, + MTU: mtu, + GSO: true, + InterfaceScope: true, + InterfaceMonitor: t.network.InterfaceMonitor(), + InterfaceFinder: t.network.InterfaceFinder(), + Logger: t.logger, + EXP_ExternalConfiguration: true, + } + systemTun, err := tun.New(tunOptions) + if err != nil { + return err + } + err = systemTun.Start() + if err != nil { + _ = systemTun.Close() + return err + } + wgTunDevice, err := newTunDeviceAdapter(systemTun, int(mtu), t.logger) + if err != nil { + _ = systemTun.Close() + return err + } + systemDialer, err := dialer.NewDefault(t.ctx, option.DialerOptions{ + BindInterface: tunName, + }) + if err != nil { + _ = systemTun.Close() + return err + } + t.systemTun = systemTun + t.systemDialer = systemDialer + t.server.TunDevice = wgTunDevice + } + if mark := t.network.AutoRedirectOutputMark(); mark > 0 { + controlFunc := t.network.AutoRedirectOutputMarkFunc() + if bindFunc := t.network.AutoDetectInterfaceFunc(); bindFunc != nil { + controlFunc = control.Append(controlFunc, bindFunc) + } + netns.SetControlFunc(controlFunc) + } else if runtime.GOOS == "android" && t.platformInterface != nil { + netns.SetControlFunc(func(network, address string, c syscall.RawConn) error { + return control.Raw(c, func(fd uintptr) error { + return t.platformInterface.AutoDetectInterfaceControl(int(fd)) + }) + }) + } + return nil +} + +func (t *Endpoint) postStart() error { + err := t.server.Start() + if err != nil { + if t.systemTun != nil { + _ = t.systemTun.Close() + } + return err + } + t.serverStarted = true + if t.fallbackTCPCloser == nil { + t.fallbackTCPCloser = t.server.RegisterFallbackTCPHandler(func(src, dst netip.AddrPort) (handler func(net.Conn), intercept bool) { + return func(conn net.Conn) { + ctx := log.ContextWithNewID(t.ctx) + source := M.SocksaddrFrom(src.Addr(), src.Port()) + destination := M.SocksaddrFrom(dst.Addr(), dst.Port()) + t.NewConnectionEx(ctx, conn, source, destination, nil) + }, true + }) + } + t.server.ExportLocalBackend().ExportEngine().(wgengine.ExportedUserspaceEngine).SetOnReconfigListener(t.onReconfig) + + ipStack := t.server.ExportNetstack().ExportIPStack() + gErr := ipStack.SetSpoofing(tun.DefaultNIC, true) + if gErr != nil { + return gonet.TranslateNetstackError(gErr) + } + gErr = ipStack.SetPromiscuousMode(tun.DefaultNIC, true) + if gErr != nil { + return gonet.TranslateNetstackError(gErr) + } + icmpForwarder := tun.NewICMPForwarder(t.ctx, ipStack, t, t.udpTimeout) + ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket) + ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket) + t.stack = ipStack + t.icmpForwarder = icmpForwarder + netstack := t.server.ExportNetstack() + if netstack != nil { + previousTCP := netstack.GetTCPHandlerForFlow + netstack.GetTCPHandlerForFlow = func(src, dst netip.AddrPort) (handler func(net.Conn), intercept bool) { + if previousTCP != nil { + handler, intercept = previousTCP(src, dst) + if handler != nil || !intercept { + return handler, intercept + } + } + return func(conn net.Conn) { + ctx := log.ContextWithNewID(t.ctx) + source := M.SocksaddrFrom(src.Addr(), src.Port()) + destination := M.SocksaddrFrom(dst.Addr(), dst.Port()) + t.NewConnectionEx(ctx, conn, source, destination, nil) + }, true + } + + previousUDP := netstack.GetUDPHandlerForFlow + netstack.GetUDPHandlerForFlow = func(src, dst netip.AddrPort) (handler func(nettype.ConnPacketConn), intercept bool) { + if previousUDP != nil { + handler, intercept = previousUDP(src, dst) + if handler != nil || !intercept { + return handler, intercept + } + } + return func(conn nettype.ConnPacketConn) { + ctx := log.ContextWithNewID(t.ctx) + source := M.SocksaddrFrom(src.Addr(), src.Port()) + destination := M.SocksaddrFrom(dst.Addr(), dst.Port()) + packetConn := bufio.NewUnbindPacketConnWithAddr(conn, destination) + t.NewPacketConnectionEx(ctx, packetConn, source, destination, nil) + }, true + } + } + + localBackend := t.server.ExportLocalBackend() + perfs := &ipn.MaskedPrefs{ + Prefs: ipn.Prefs{ + RouteAll: t.acceptRoutes, + AdvertiseRoutes: t.advertiseRoutes, + }, + RouteAllSet: true, + ExitNodeIPSet: true, + AdvertiseRoutesSet: true, + RelayServerPortSet: true, + RelayServerStaticEndpointsSet: true, + } + if t.advertiseExitNode { + perfs.AdvertiseRoutes = append(perfs.AdvertiseRoutes, tsaddr.ExitRoutes()...) + } + if t.relayServerPort != nil { + perfs.RelayServerPort = t.relayServerPort + } + if len(t.relayServerStaticEndpoints) > 0 { + perfs.RelayServerStaticEndpoints = t.relayServerStaticEndpoints + } + _, err = localBackend.EditPrefs(perfs) + if err != nil { + return E.Cause(err, "update prefs") + } + t.filter = localBackend.ExportFilter() + go t.watchState() + t.started.Store(true) + return nil +} + +func (t *Endpoint) watchState() { + localBackend := t.server.ExportLocalBackend() + localBackend.WatchNotifications(t.ctx, ipn.NotifyInitialState, nil, func(roNotify *ipn.Notify) (keepGoing bool) { + if roNotify.State != nil && *roNotify.State != ipn.NeedsLogin && *roNotify.State != ipn.NoState { + return false + } + authURL := localBackend.StatusWithoutPeers().AuthURL + if authURL != "" { + t.logger.Info("Waiting for authentication: ", authURL) + if t.platformInterface != nil { + err := t.platformInterface.SendNotification(&adapter.Notification{ + Identifier: "tailscale-authentication", + TypeName: "Tailscale Authentication Notifications", + TypeID: 10, + Title: "Tailscale Authentication", + Body: F.ToString("Tailscale outbound[", t.Tag(), "] is waiting for authentication."), + OpenURL: authURL, + }) + if err != nil { + t.logger.Error("send authentication notification: ", err) + } + } + return false + } + return true + }) + if t.exitNode != "" { + localBackend.WatchNotifications(t.ctx, ipn.NotifyInitialState, nil, func(roNotify *ipn.Notify) (keepGoing bool) { + if roNotify.State == nil || *roNotify.State != ipn.Running { + return true + } + status, err := common.Must1(t.server.LocalClient()).Status(t.ctx) + if err != nil { + t.logger.Error("set exit node: ", err) + return + } + perfs := &ipn.MaskedPrefs{ + Prefs: ipn.Prefs{ + ExitNodeAllowLANAccess: t.exitNodeAllowLANAccess, + }, + ExitNodeIPSet: true, + ExitNodeAllowLANAccessSet: true, + } + err = perfs.SetExitNodeIP(t.exitNode, status) + if err != nil { + t.logger.Error("set exit node: ", err) + return true + } + _, err = localBackend.EditPrefs(perfs) + if err != nil { + t.logger.Error("set exit node: ", err) + return true + } + return false + }) + } +} + +func (t *Endpoint) SetTailscaleExitNode(ctx context.Context, stableID string) error { + if !t.started.Load() { + return E.New("Tailscale is not ready yet") + } + if t.advertiseExitNode && stableID != "" { + return E.New("cannot advertise an exit node and use an exit node at the same time") + } + perfs := &ipn.MaskedPrefs{ + Prefs: ipn.Prefs{ + ExitNodeID: tailcfg.StableNodeID(stableID), + ExitNodeAllowLANAccess: t.exitNodeAllowLANAccess, + }, + ExitNodeIDSet: true, + ExitNodeIPSet: true, + ExitNodeAllowLANAccessSet: true, + } + if stableID != "" { + status, err := common.Must1(t.server.LocalClient()).Status(ctx) + if err != nil { + return E.Cause(err, "get tailscale status") + } + found := false + for _, peer := range status.Peer { + if peer.ID != tailcfg.StableNodeID(stableID) { + continue + } + if !peer.ExitNodeOption { + return E.New("peer does not offer exit node: ", stableID) + } + found = true + break + } + if !found { + return E.New("peer not found: ", stableID) + } + } + _, err := t.server.ExportLocalBackend().EditPrefs(perfs) + if err != nil { + return E.Cause(err, "update prefs") + } + return nil +} + +func (t *Endpoint) Close() error { + var err error + t.started.Store(false) + if t.serverStarted { + err = common.Close(common.PtrOrNil(t.server)) + t.serverStarted = false + } + netmon.RegisterInterfaceGetter(nil) + netns.SetControlFunc(nil) + if t.fallbackTCPCloser != nil { + t.fallbackTCPCloser() + t.fallbackTCPCloser = nil + } + if t.systemTun != nil { + t.systemTun.Close() + t.systemTun = nil + } + return err +} + +func (t *Endpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + switch network { + case N.NetworkTCP: + t.logger.InfoContext(ctx, "outbound connection to ", destination) + case N.NetworkUDP: + t.logger.InfoContext(ctx, "outbound packet connection to ", destination) + } + if !t.started.Load() { + return nil, E.New("Tailscale is not ready yet") + } + if destination.IsDomain() { + destinationAddresses, err := t.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) + if err != nil { + return nil, err + } + return N.DialSerial(ctx, t, network, destination, destinationAddresses) + } + if t.systemDialer != nil { + return t.systemDialer.DialContext(ctx, network, destination) + } + addr4, addr6 := t.server.TailscaleIPs() + remoteAddr := tcpip.FullAddress{ + NIC: 1, + Port: destination.Port, + Addr: addressFromAddr(destination.Addr), + } + var localAddr tcpip.FullAddress + var networkProtocol tcpip.NetworkProtocolNumber + if destination.IsIPv4() { + if !addr4.IsValid() { + return nil, E.New("missing Tailscale IPv4 address") + } + networkProtocol = header.IPv4ProtocolNumber + localAddr = tcpip.FullAddress{ + NIC: 1, + Addr: addressFromAddr(addr4), + } + } else { + if !addr6.IsValid() { + return nil, E.New("missing Tailscale IPv6 address") + } + networkProtocol = header.IPv6ProtocolNumber + localAddr = tcpip.FullAddress{ + NIC: 1, + Addr: addressFromAddr(addr6), + } + } + switch N.NetworkName(network) { + case N.NetworkTCP: + tcpConn, err := gonet.DialTCPWithBind(ctx, t.stack, localAddr, remoteAddr, networkProtocol) + if err != nil { + return nil, err + } + return tcpConn, nil + case N.NetworkUDP: + udpConn, err := gonet.DialUDP(t.stack, &localAddr, &remoteAddr, networkProtocol) + if err != nil { + return nil, err + } + return udpConn, nil + default: + return nil, E.Extend(N.ErrUnknownNetwork, network) + } +} + +func (t *Endpoint) listenPacketWithAddress(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + if !t.started.Load() { + return nil, E.New("Tailscale is not ready yet") + } + if t.systemDialer != nil { + return t.systemDialer.ListenPacket(ctx, destination) + } + addr4, addr6 := t.server.TailscaleIPs() + bind := tcpip.FullAddress{ + NIC: 1, + } + var networkProtocol tcpip.NetworkProtocolNumber + if destination.IsIPv4() { + if !addr4.IsValid() { + return nil, E.New("missing Tailscale IPv4 address") + } + networkProtocol = header.IPv4ProtocolNumber + bind.Addr = addressFromAddr(addr4) + } else { + if !addr6.IsValid() { + return nil, E.New("missing Tailscale IPv6 address") + } + networkProtocol = header.IPv6ProtocolNumber + bind.Addr = addressFromAddr(addr6) + } + udpConn, err := gonet.DialUDP(t.stack, &bind, nil, networkProtocol) + if err != nil { + return nil, err + } + return udpConn, nil +} + +func (t *Endpoint) ListenPacketWithDestination(ctx context.Context, destination M.Socksaddr) (net.PacketConn, netip.Addr, error) { + t.logger.InfoContext(ctx, "outbound packet connection to ", destination) + if destination.IsDomain() { + destinationAddresses, err := t.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) + if err != nil { + return nil, netip.Addr{}, err + } + var errors []error + for _, address := range destinationAddresses { + packetConn, packetErr := t.listenPacketWithAddress(ctx, M.SocksaddrFrom(address, destination.Port)) + if packetErr == nil { + return packetConn, address, nil + } + errors = append(errors, packetErr) + } + return nil, netip.Addr{}, E.Errors(errors...) + } + packetConn, err := t.listenPacketWithAddress(ctx, destination) + if err != nil { + return nil, netip.Addr{}, err + } + if destination.IsIP() { + return packetConn, destination.Addr, nil + } + return packetConn, netip.Addr{}, nil +} + +func (t *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + packetConn, destinationAddress, err := t.ListenPacketWithDestination(ctx, destination) + if err != nil { + return nil, err + } + if destinationAddress.IsValid() && destination != M.SocksaddrFrom(destinationAddress, destination.Port) { + return bufio.NewNATPacketConn(bufio.NewPacketConn(packetConn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil + } + return packetConn, nil +} + +func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + if !t.started.Load() { + return nil, E.New("Tailscale is not ready yet") + } + tsFilter := t.filter.Load() + if tsFilter != nil { + var ipProto ipproto.Proto + switch N.NetworkName(network) { + case N.NetworkTCP: + ipProto = ipproto.TCP + case N.NetworkUDP: + ipProto = ipproto.UDP + case N.NetworkICMP: + if !destination.IsIPv6() { + ipProto = ipproto.ICMPv4 + } else { + ipProto = ipproto.ICMPv6 + } + } + response := tsFilter.Check(source.Addr, destination.Addr, destination.Port, ipProto) + switch response { + case filter.Drop: + return nil, syscall.ECONNREFUSED + case filter.DropSilently: + return nil, tun.ErrDrop + } + } + var ipVersion uint8 + if !destination.IsIPv6() { + ipVersion = 4 + } else { + ipVersion = 6 + } + routeDestination, err := t.router.PreMatch(adapter.InboundContext{ + Inbound: t.Tag(), + InboundType: t.Type(), + IPVersion: ipVersion, + Network: network, + Source: source, + Destination: destination, + }, routeContext, timeout, false) + if err != nil { + switch { + case R.IsBypassed(err): + err = nil + case R.IsRejected(err): + t.logger.Trace("reject ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()) + default: + if network == N.NetworkICMP { + t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())) + } + } + } + return routeDestination, err +} + +func (t *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = t.Tag() + metadata.InboundType = t.Type() + metadata.Source = source + addr4, addr6 := t.server.TailscaleIPs() + switch destination.Addr { + case addr4: + destination.Addr = netip.AddrFrom4([4]uint8{127, 0, 0, 1}) + case addr6: + destination.Addr = netip.IPv6Loopback() + } + metadata.Destination = destination + t.logger.InfoContext(ctx, "inbound connection from ", source) + t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + t.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (t *Endpoint) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = t.Tag() + metadata.InboundType = t.Type() + metadata.Source = source + addr4, addr6 := t.server.TailscaleIPs() + switch destination.Addr { + case addr4: + metadata.OriginDestination = destination + destination.Addr = netip.AddrFrom4([4]uint8{127, 0, 0, 1}) + conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, destination) + case addr6: + metadata.OriginDestination = destination + destination.Addr = netip.IPv6Loopback() + conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, destination) + } + metadata.Destination = destination + t.logger.InfoContext(ctx, "inbound packet connection from ", source) + t.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} + +func (t *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + if !t.started.Load() { + return nil, E.New("Tailscale is not ready yet") + } + ctx := log.ContextWithNewID(t.ctx) + var destination tun.DirectRouteDestination + var err error + if t.systemDialer != nil { + destination, err = ping.ConnectDestination( + ctx, t.logger, + t.systemDialer.DialerForICMPDestination(metadata.Destination.Addr).Control, + metadata.Destination.Addr, routeContext, timeout, + ) + } else { + inet4Address, inet6Address := t.server.TailscaleIPs() + if metadata.Destination.Addr.Is4() && !inet4Address.IsValid() || metadata.Destination.Addr.Is6() && !inet6Address.IsValid() { + return nil, E.New("Tailscale is not ready yet") + } + destination, err = ping.ConnectGVisor( + ctx, t.logger, + metadata.Source.Addr, metadata.Destination.Addr, + routeContext, + t.stack, + inet4Address, inet6Address, + timeout, + ) + } + if err != nil { + return nil, err + } + t.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString()) + return destination, nil +} + +func (t *Endpoint) PreferredDomain(domain string) bool { + routeDomains := t.routeDomains.Load() + if routeDomains == nil { + return false + } + return routeDomains[strings.ToLower(domain)] +} + +func (t *Endpoint) PreferredAddress(address netip.Addr) bool { + routePrefixes := t.routePrefixes.Load() + if routePrefixes == nil { + return false + } + return routePrefixes.Contains(address) +} + +func (t *Endpoint) Server() *tsnet.Server { + return t.server +} + +func (t *Endpoint) onReconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCfg *tsDNS.Config) { + if cfg == nil || dnsCfg == nil { + return + } + if (t.cfg != nil && reflect.DeepEqual(t.cfg, cfg)) && (t.dnsCfg != nil && reflect.DeepEqual(t.dnsCfg, dnsCfg)) { + return + } + var inet4Address, inet6Address netip.Addr + for _, address := range cfg.Addresses { + if address.Addr().Is4() { + inet4Address = address.Addr() + } else if address.Addr().Is6() { + inet6Address = address.Addr() + } + } + t.icmpForwarder.SetLocalAddresses(inet4Address, inet6Address) + t.cfg = cfg + t.dnsCfg = dnsCfg + + routeDomains := make(map[string]bool) + for fqdn := range dnsCfg.Routes { + routeDomains[fqdn.WithoutTrailingDot()] = true + } + for _, fqdn := range dnsCfg.SearchDomains { + routeDomains[fqdn.WithoutTrailingDot()] = true + } + t.routeDomains.Store(routeDomains) + + var builder netipx.IPSetBuilder + for _, peer := range cfg.Peers { + for _, allowedIP := range peer.AllowedIPs { + builder.AddPrefix(allowedIP) + } + } + t.routePrefixes.Store(common.Must1(builder.IPSet())) + + if t.onReconfigHook != nil { + t.onReconfigHook(cfg, routerCfg, dnsCfg) + } +} + +func addressFromAddr(destination netip.Addr) tcpip.Address { + if destination.Is6() { + return tcpip.AddrFrom16(destination.As16()) + } else { + return tcpip.AddrFrom4(destination.As4()) + } +} + +type endpointDialer struct { + N.Dialer + logger logger.ContextLogger +} + +func (d *endpointDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + switch N.NetworkName(network) { + case N.NetworkTCP: + d.logger.InfoContext(ctx, "output connection to ", destination) + case N.NetworkUDP: + d.logger.InfoContext(ctx, "output packet connection to ", destination) + } + return d.Dialer.DialContext(ctx, network, destination) +} + +func (d *endpointDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + d.logger.InfoContext(ctx, "output packet connection") + return d.Dialer.ListenPacket(ctx, destination) +} + +type dnsConfigurtor struct { + baseConfig tsDNS.OSConfig +} + +func (c *dnsConfigurtor) SetDNS(cfg tsDNS.OSConfig) error { + c.baseConfig = cfg + return nil +} + +func (c *dnsConfigurtor) SupportsSplitDNS() bool { + return true +} + +func (c *dnsConfigurtor) GetBaseConfig() (tsDNS.OSConfig, error) { + return c.baseConfig, nil +} + +func (c *dnsConfigurtor) Close() error { + return nil +} + +type peerDNSQueryHandler Endpoint + +func (t *peerDNSQueryHandler) HandlePeerDNSQuery(ctx context.Context, query []byte, sourceAddress netip.AddrPort, allowName func(name string) bool) ([]byte, error) { + var message mDNS.Msg + err := message.Unpack(query) + if err != nil { + return nil, err + } + for _, question := range message.Question { + if allowName != nil && !allowName(question.Name) { + return dns.FixedResponseStatus(&message, mDNS.RcodeRefused).Pack() + } + } + var metadata adapter.InboundContext + metadata.Inbound = t.Tag() + metadata.InboundType = t.Type() + metadata.Source = M.SocksaddrFromNetIP(sourceAddress) + response, err := t.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), &message, adapter.DNSQueryOptions{}) + if err != nil { + if !R.IsRejected(err) && !E.IsClosedOrCanceled(err) { + t.logger.ErrorContext(ctx, E.Cause(err, "process peer DNS query")) + } + return nil, err + } + return response.Pack() +} diff --git a/protocol/tailscale/hostinfo_tvos.go b/protocol/tailscale/hostinfo_tvos.go new file mode 100644 index 0000000000..d8e391bb58 --- /dev/null +++ b/protocol/tailscale/hostinfo_tvos.go @@ -0,0 +1,16 @@ +//go:build with_gvisor && tvos + +package tailscale + +import ( + _ "unsafe" + + "github.com/sagernet/tailscale/types/lazy" +) + +//go:linkname isAppleTV github.com/sagernet/tailscale/version.isAppleTV +var isAppleTV lazy.SyncValue[bool] + +func init() { + isAppleTV.Set(true) +} diff --git a/protocol/tailscale/ping.go b/protocol/tailscale/ping.go new file mode 100644 index 0000000000..8bb0476b27 --- /dev/null +++ b/protocol/tailscale/ping.go @@ -0,0 +1,55 @@ +//go:build with_gvisor + +package tailscale + +import ( + "context" + "net/netip" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/tailscale/ipn/ipnstate" + "github.com/sagernet/tailscale/tailcfg" +) + +func (t *Endpoint) StartTailscalePing(ctx context.Context, peerIP string, fn func(*adapter.TailscalePingResult)) error { + ip, err := netip.ParseAddr(peerIP) + if err != nil { + return err + } + localClient, err := t.server.LocalClient() + if err != nil { + return err + } + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + for { + result, pingErr := localClient.Ping(ctx, ip, tailcfg.PingDisco) + if ctx.Err() != nil { + return ctx.Err() + } + if pingErr != nil { + fn(&adapter.TailscalePingResult{ + Error: pingErr.Error(), + }) + } else { + fn(convertPingResult(result)) + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-ticker.C: + } + } +} + +func convertPingResult(result *ipnstate.PingResult) *adapter.TailscalePingResult { + return &adapter.TailscalePingResult{ + LatencyMs: result.LatencySeconds * 1000, + IsDirect: result.Endpoint != "", + Endpoint: result.Endpoint, + DERPRegionID: int32(result.DERPRegionID), + DERPRegionCode: result.DERPRegionCode, + Error: result.Err, + } +} diff --git a/protocol/tailscale/status.go b/protocol/tailscale/status.go new file mode 100644 index 0000000000..7441d1761a --- /dev/null +++ b/protocol/tailscale/status.go @@ -0,0 +1,135 @@ +//go:build with_gvisor + +package tailscale + +import ( + "context" + "slices" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/tailscale/ipn" + "github.com/sagernet/tailscale/ipn/ipnstate" +) + +var _ adapter.TailscaleEndpoint = (*Endpoint)(nil) + +func (t *Endpoint) SubscribeTailscaleStatus(ctx context.Context, fn func(*adapter.TailscaleEndpointStatus)) error { + localBackend := t.server.ExportLocalBackend() + sendStatus := func() { + status := localBackend.Status() + fn(convertTailscaleStatus(status)) + } + sendStatus() + localBackend.WatchNotifications(ctx, ipn.NotifyInitialState|ipn.NotifyInitialNetMap|ipn.NotifyRateLimit, nil, func(roNotify *ipn.Notify) (keepGoing bool) { + select { + case <-ctx.Done(): + return false + default: + } + if roNotify.State != nil || roNotify.NetMap != nil || roNotify.BrowseToURL != nil || roNotify.Prefs != nil { + sendStatus() + } + return true + }) + return ctx.Err() +} + +func convertTailscaleStatus(status *ipnstate.Status) *adapter.TailscaleEndpointStatus { + result := &adapter.TailscaleEndpointStatus{ + BackendState: status.BackendState, + AuthURL: status.AuthURL, + } + if status.CurrentTailnet != nil { + result.NetworkName = status.CurrentTailnet.Name + result.MagicDNSSuffix = status.CurrentTailnet.MagicDNSSuffix + } + if status.Self != nil { + result.Self = convertTailscalePeer(status.Self) + } + groupIndex := make(map[int64]*adapter.TailscaleUserGroup) + for _, peerKey := range status.Peers() { + peer := status.Peer[peerKey] + userID := int64(peer.UserID) + group, loaded := groupIndex[userID] + if !loaded { + group = &adapter.TailscaleUserGroup{ + UserID: userID, + } + if profile, hasProfile := status.User[peer.UserID]; hasProfile { + group.LoginName = profile.LoginName + group.DisplayName = profile.DisplayName + group.ProfilePicURL = profile.ProfilePicURL + } + groupIndex[userID] = group + result.UserGroups = append(result.UserGroups, group) + } + group.Peers = append(group.Peers, convertTailscalePeer(peer)) + } + for _, group := range result.UserGroups { + slices.SortStableFunc(group.Peers, func(a, b *adapter.TailscalePeer) int { + if a.Online != b.Online { + if a.Online { + return -1 + } + return 1 + } + return 0 + }) + } + if status.ExitNodeStatus != nil { + for _, peerKey := range status.Peers() { + peer := status.Peer[peerKey] + if peer.ID == status.ExitNodeStatus.ID { + result.ExitNode = convertTailscalePeer(peer) + break + } + } + if result.ExitNode == nil { + ips := make([]string, 0, len(status.ExitNodeStatus.TailscaleIPs)) + for _, prefix := range status.ExitNodeStatus.TailscaleIPs { + ips = append(ips, prefix.Addr().String()) + } + result.ExitNode = &adapter.TailscalePeer{ + StableID: string(status.ExitNodeStatus.ID), + TailscaleIPs: ips, + Online: status.ExitNodeStatus.Online, + ExitNode: true, + } + } + } + return result +} + +func convertTailscalePeer(peer *ipnstate.PeerStatus) *adapter.TailscalePeer { + ips := make([]string, len(peer.TailscaleIPs)) + for i, ip := range peer.TailscaleIPs { + ips[i] = ip.String() + } + var keyExpiry int64 + if peer.KeyExpiry != nil { + keyExpiry = peer.KeyExpiry.Unix() + } + var lastSeen int64 + if !peer.LastSeen.IsZero() { + lastSeen = peer.LastSeen.Unix() + } + return &adapter.TailscalePeer{ + StableID: string(peer.ID), + HostName: peer.HostName, + DNSName: peer.DNSName, + OS: peer.OS, + TailscaleIPs: ips, + SSHHostKeys: peer.SSH_HostKeys, + Online: peer.Online, + ExitNode: peer.ExitNode, + ExitNodeOption: peer.ExitNodeOption, + ShareeNode: peer.ShareeNode, + Expired: peer.Expired, + Active: peer.Active, + RxBytes: peer.RxBytes, + TxBytes: peer.TxBytes, + UserID: int64(peer.UserID), + KeyExpiry: keyExpiry, + LastSeen: lastSeen, + } +} diff --git a/protocol/tailscale/tun_device_unix.go b/protocol/tailscale/tun_device_unix.go new file mode 100644 index 0000000000..d4bc7ced13 --- /dev/null +++ b/protocol/tailscale/tun_device_unix.go @@ -0,0 +1,155 @@ +//go:build with_gvisor && !windows + +package tailscale + +import ( + "encoding/hex" + "errors" + "io" + "os" + "sync" + "sync/atomic" + + singTun "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common/logger" + wgTun "github.com/sagernet/wireguard-go/tun" +) + +type tunDeviceAdapter struct { + tun singTun.Tun + linuxTUN singTun.LinuxTUN + events chan wgTun.Event + mtu int + logger logger.ContextLogger + debugTun bool + readCount atomic.Uint32 + writeCount atomic.Uint32 + closeOnce sync.Once +} + +func newTunDeviceAdapter(tun singTun.Tun, mtu int, logger logger.ContextLogger) (wgTun.Device, error) { + if tun == nil { + return nil, os.ErrInvalid + } + if mtu == 0 { + mtu = 1500 + } + adapter := &tunDeviceAdapter{ + tun: tun, + events: make(chan wgTun.Event, 1), + mtu: mtu, + logger: logger, + debugTun: os.Getenv("SINGBOX_TS_TUN_DEBUG") != "", + } + if linuxTUN, ok := tun.(singTun.LinuxTUN); ok { + adapter.linuxTUN = linuxTUN + } + adapter.events <- wgTun.EventUp + return adapter, nil +} + +func (a *tunDeviceAdapter) File() *os.File { + return nil +} + +func (a *tunDeviceAdapter) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) { + if a.linuxTUN != nil { + n, err := a.linuxTUN.BatchRead(bufs, offset-singTun.PacketOffset, sizes) + if err == nil { + for i := range n { + a.debugPacket("read", bufs[i][offset:offset+sizes[i]]) + } + } + return n, err + } + if offset < singTun.PacketOffset { + return 0, io.ErrShortBuffer + } + readBuf := bufs[0][offset-singTun.PacketOffset:] + n, err := a.tun.Read(readBuf) + if err == nil { + if n < singTun.PacketOffset { + return 0, io.ErrUnexpectedEOF + } + sizes[0] = n - singTun.PacketOffset + a.debugPacket("read", readBuf[singTun.PacketOffset:n]) + return 1, nil + } + if errors.Is(err, singTun.ErrTooManySegments) { + err = wgTun.ErrTooManySegments + } + return 0, err +} + +func (a *tunDeviceAdapter) Write(bufs [][]byte, offset int) (count int, err error) { + if a.linuxTUN != nil { + for i := range bufs { + a.debugPacket("write", bufs[i][offset:]) + } + return a.linuxTUN.BatchWrite(bufs, offset) + } + for _, packet := range bufs { + a.debugPacket("write", packet[offset:]) + if singTun.PacketOffset > 0 { + clear(packet[offset-singTun.PacketOffset : offset]) + singTun.PacketFillHeader(packet[offset-singTun.PacketOffset:], singTun.PacketIPVersion(packet[offset:])) + } + _, err = a.tun.Write(packet[offset-singTun.PacketOffset:]) + if err != nil { + return 0, err + } + } + // WireGuard will not read count. + return 0, nil +} + +func (a *tunDeviceAdapter) MTU() (int, error) { + return a.mtu, nil +} + +func (a *tunDeviceAdapter) Name() (string, error) { + return a.tun.Name() +} + +func (a *tunDeviceAdapter) Events() <-chan wgTun.Event { + return a.events +} + +func (a *tunDeviceAdapter) Close() error { + var err error + a.closeOnce.Do(func() { + close(a.events) + err = a.tun.Close() + }) + return err +} + +func (a *tunDeviceAdapter) BatchSize() int { + if a.linuxTUN != nil { + return a.linuxTUN.BatchSize() + } + return 1 +} + +func (a *tunDeviceAdapter) debugPacket(direction string, packet []byte) { + if !a.debugTun || a.logger == nil { + return + } + var counter *atomic.Uint32 + switch direction { + case "read": + counter = &a.readCount + case "write": + counter = &a.writeCount + default: + return + } + if counter.Add(1) > 8 { + return + } + sample := packet + if len(sample) > 64 { + sample = sample[:64] + } + a.logger.Trace("tailscale tun ", direction, " len=", len(packet), " head=", hex.EncodeToString(sample)) +} diff --git a/protocol/tailscale/tun_device_windows.go b/protocol/tailscale/tun_device_windows.go new file mode 100644 index 0000000000..8c9e87cee8 --- /dev/null +++ b/protocol/tailscale/tun_device_windows.go @@ -0,0 +1,117 @@ +//go:build with_gvisor && windows + +package tailscale + +import ( + "errors" + "os" + "sync" + "sync/atomic" + + singTun "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common/logger" + wgTun "github.com/sagernet/wireguard-go/tun" +) + +type tunDeviceAdapter struct { + tun singTun.WinTun + nativeTun *singTun.NativeTun + events chan wgTun.Event + mtu atomic.Int64 + closeOnce sync.Once +} + +func newTunDeviceAdapter(tun singTun.Tun, mtu int, _ logger.ContextLogger) (wgTun.Device, error) { + winTun, ok := tun.(singTun.WinTun) + if !ok { + return nil, errors.New("not a windows tun device") + } + nativeTun, ok := winTun.(*singTun.NativeTun) + if !ok { + return nil, errors.New("unsupported windows tun device") + } + if mtu == 0 { + mtu = 1500 + } + adapter := &tunDeviceAdapter{ + tun: winTun, + nativeTun: nativeTun, + events: make(chan wgTun.Event, 1), + } + adapter.mtu.Store(int64(mtu)) + adapter.events <- wgTun.EventUp + return adapter, nil +} + +func (a *tunDeviceAdapter) File() *os.File { + return nil +} + +func (a *tunDeviceAdapter) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) { + packet, release, err := a.tun.ReadPacket() + if err != nil { + return 0, err + } + defer release() + sizes[0] = copy(bufs[0][offset-singTun.PacketOffset:], packet) + return 1, nil +} + +func (a *tunDeviceAdapter) Write(bufs [][]byte, offset int) (count int, err error) { + for _, packet := range bufs { + if singTun.PacketOffset > 0 { + singTun.PacketFillHeader(packet[offset-singTun.PacketOffset:], singTun.PacketIPVersion(packet[offset:])) + } + _, err = a.tun.Write(packet[offset-singTun.PacketOffset:]) + if err != nil { + return 0, err + } + } + return 0, nil +} + +func (a *tunDeviceAdapter) MTU() (int, error) { + return int(a.mtu.Load()), nil +} + +func (a *tunDeviceAdapter) ForceMTU(mtu int) { + if mtu <= 0 { + return + } + update := int(a.mtu.Load()) != mtu + a.mtu.Store(int64(mtu)) + if update { + select { + case a.events <- wgTun.EventMTUUpdate: + default: + } + } +} + +func (a *tunDeviceAdapter) LUID() uint64 { + if a.nativeTun == nil { + return 0 + } + return a.nativeTun.LUID() +} + +func (a *tunDeviceAdapter) Name() (string, error) { + return a.tun.Name() +} + +func (a *tunDeviceAdapter) Events() <-chan wgTun.Event { + return a.events +} + +func (a *tunDeviceAdapter) Close() error { + var err error + a.closeOnce.Do(func() { + close(a.events) + err = a.tun.Close() + }) + return err +} + +func (a *tunDeviceAdapter) BatchSize() int { + return 1 +} diff --git a/outbound/tor.go b/protocol/tor/outbound.go similarity index 73% rename from outbound/tor.go rename to protocol/tor/outbound.go index 76c7955da7..6f0c3fd6d9 100644 --- a/outbound/tor.go +++ b/protocol/tor/outbound.go @@ -1,4 +1,4 @@ -package outbound +package tor import ( "context" @@ -8,13 +8,16 @@ import ( "strings" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/proxybridge" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "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" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/rw" @@ -24,12 +27,15 @@ import ( "github.com/cretz/bine/tor" ) -var _ adapter.Outbound = (*Tor)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.TorOutboundOptions](registry, C.TypeTor, NewOutbound) +} -type Tor struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter ctx context.Context - proxy *ProxyListener + logger logger.ContextLogger + proxy *proxybridge.Bridge startConf *tor.StartConf options map[string]string events chan control.Event @@ -37,17 +43,17 @@ type Tor struct { socksClient *socks.Client } -func NewTor(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TorOutboundOptions) (*Tor, error) { - startConf := newConfig() +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TorOutboundOptions) (adapter.Outbound, error) { + var startConf tor.StartConf startConf.DataDir = os.ExpandEnv(options.DataDirectory) startConf.TempDataDirBase = os.TempDir() startConf.ExtraArgs = options.ExtraArgs if options.DataDirectory != "" { dataDirAbs, _ := filepath.Abs(startConf.DataDir) - if geoIPPath := filepath.Join(dataDirAbs, "geoip"); rw.FileExists(geoIPPath) && !common.Contains(options.ExtraArgs, "--GeoIPFile") { + if geoIPPath := filepath.Join(dataDirAbs, "geoip"); rw.IsFile(geoIPPath) && !common.Contains(options.ExtraArgs, "--GeoIPFile") { options.ExtraArgs = append(options.ExtraArgs, "--GeoIPFile", geoIPPath) } - if geoIP6Path := filepath.Join(dataDirAbs, "geoip6"); rw.FileExists(geoIP6Path) && !common.Contains(options.ExtraArgs, "--GeoIPv6File") { + if geoIP6Path := filepath.Join(dataDirAbs, "geoip6"); rw.IsFile(geoIP6Path) && !common.Contains(options.ExtraArgs, "--GeoIPv6File") { options.ExtraArgs = append(options.ExtraArgs, "--GeoIPv6File", geoIP6Path) } } @@ -58,35 +64,37 @@ func NewTor(ctx context.Context, router adapter.Router, logger log.ContextLogger } if startConf.DataDir != "" { torrcFile := filepath.Join(startConf.DataDir, "torrc") - if !rw.FileExists(torrcFile) { - err := rw.WriteFile(torrcFile, []byte("")) + err := rw.MkdirParent(torrcFile) + if err != nil { + return nil, err + } + if !rw.IsFile(torrcFile) { + err := os.WriteFile(torrcFile, []byte(""), 0o600) if err != nil { return nil, err } } startConf.TorrcFile = torrcFile } - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, false) + if err != nil { + return nil, err + } + proxy, err := proxybridge.New(ctx, logger, "proxy", outboundDialer) if err != nil { return nil, err } - return &Tor{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeTor, - network: []string{N.NetworkTCP}, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + return &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTor, tag, []string{N.NetworkTCP}, options.DialerOptions), ctx: ctx, - proxy: NewProxyListener(ctx, logger, outboundDialer), + logger: logger, + proxy: proxy, startConf: &startConf, options: options.Options, }, nil } -func (t *Tor) Start() error { +func (t *Outbound) Start() error { err := t.start() if err != nil { t.Close() @@ -102,7 +110,7 @@ var torLogEvents = []control.EventCode{ control.EventCodeLogWarn, } -func (t *Tor) start() error { +func (t *Outbound) start() error { torInstance, err := tor.Start(t.ctx, t.startConf) if err != nil { return E.New(strings.ToLower(err.Error())) @@ -114,10 +122,6 @@ func (t *Tor) start() error { return err } go t.recvLoop() - err = t.proxy.Start() - if err != nil { - return err - } proxyPort := "127.0.0.1:" + F.ToString(t.proxy.Port()) proxyUsername := t.proxy.Username() proxyPassword := t.proxy.Password() @@ -164,7 +168,7 @@ func (t *Tor) start() error { return nil } -func (t *Tor) recvLoop() { +func (t *Outbound) recvLoop() { for rawEvent := range t.events { switch event := rawEvent.(type) { case *control.LogEvent: @@ -187,7 +191,7 @@ func (t *Tor) recvLoop() { } } -func (t *Tor) Close() error { +func (t *Outbound) Close() error { err := common.Close( common.PtrOrNil(t.proxy), common.PtrOrNil(t.instance), @@ -199,19 +203,11 @@ func (t *Tor) Close() error { return err } -func (t *Tor) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (t *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { t.logger.InfoContext(ctx, "outbound connection to ", destination) return t.socksClient.DialContext(ctx, network, destination) } -func (t *Tor) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (t *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } - -func (t *Tor) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, t, conn, metadata) -} - -func (t *Tor) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid -} diff --git a/inbound/trojan.go b/protocol/trojan/inbound.go similarity index 50% rename from inbound/trojan.go rename to protocol/trojan/inbound.go index 203b6d3927..920589b413 100644 --- a/inbound/trojan.go +++ b/protocol/trojan/inbound.go @@ -1,4 +1,4 @@ -package inbound +package trojan import ( "context" @@ -6,6 +6,8 @@ import ( "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" @@ -21,13 +23,17 @@ import ( N "github.com/sagernet/sing/common/network" ) -var ( - _ adapter.Inbound = (*Trojan)(nil) - _ adapter.InjectableInbound = (*Trojan)(nil) -) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.TrojanInboundOptions](registry, C.TypeTrojan, NewInbound) +} + +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) -type Trojan struct { - myInboundAdapter +type Inbound struct { + inbound.Adapter + router adapter.ConnectionRouterEx + logger log.ContextLogger + listener *listener.Listener service *trojan.Service[int] users []option.TrojanUser tlsConfig tls.ServerConfig @@ -36,27 +42,27 @@ type Trojan struct { transport adapter.V2RayServerTransport } -func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanInboundOptions) (*Trojan, error) { - inbound := &Trojan{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeTrojan, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - users: options.Users, +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeTrojan, tag), + router: router, + logger: logger, + users: options.Users, } if options.TLS != nil { - tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + tlsConfig, err := tls.NewServerWithOptions(tls.ServerOptions{ + Context: ctx, + Logger: logger, + Options: common.PtrValueOrDefault(options.TLS), + KTLSCompatible: common.PtrValueOrDefault(options.Transport).Type == "" && + !common.PtrValueOrDefault(options.Multiplex).Enabled, + }) if err != nil { return nil, err } inbound.tlsConfig = tlsConfig } - var fallbackHandler N.TCPConnectionHandler + var fallbackHandler N.TCPConnectionHandlerEx if options.Fallback != nil && options.Fallback.Server != "" || len(options.FallbackForALPN) > 0 { if options.Fallback != nil && options.Fallback.Server != "" { inbound.fallbackAddr = options.Fallback.Build() @@ -78,9 +84,9 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog } inbound.fallbackAddrTLSNextProto = fallbackAddrNextProto } - fallbackHandler = adapter.NewUpstreamContextHandler(inbound.fallbackConnection, nil, nil) + fallbackHandler = adapter.NewUpstreamContextHandler(inbound.fallbackConnection, nil) } - service := trojan.NewService[int](adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound), fallbackHandler) + service := trojan.NewService[int](adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection), fallbackHandler, logger) err := service.UpdateUsers(common.MapIndexed(options.Users, func(index int, it option.TrojanUser) int { return index }), common.Map(options.Users, func(it option.TrojanUser) string { @@ -90,7 +96,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog return nil, err } if options.Transport != nil { - inbound.transport, err = v2ray.NewServerTransport(ctx, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*trojanTransportHandler)(inbound)) + inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*inboundTransportHandler)(inbound)) if err != nil { return nil, E.Cause(err, "create server transport: ", options.Transport.Type) } @@ -100,11 +106,20 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog return nil, err } inbound.service = service - inbound.connHandler = inbound + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + }) return inbound, nil } -func (h *Trojan) Start() error { +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } if h.tlsConfig != nil { err := h.tlsConfig.Start() if err != nil { @@ -112,10 +127,10 @@ func (h *Trojan) Start() error { } } if h.transport == nil { - return h.myInboundAdapter.Start() + return h.listener.Start() } if common.Contains(h.transport.Network(), N.NetworkTCP) { - tcpListener, err := h.myInboundAdapter.ListenTCP() + tcpListener, err := h.listener.ListenTCP() if err != nil { return err } @@ -127,7 +142,7 @@ func (h *Trojan) Start() error { }() } if common.Contains(h.transport.Network(), N.NetworkUDP) { - udpConn, err := h.myInboundAdapter.ListenUDP() + udpConn, err := h.listener.ListenUDP() if err != nil { return err } @@ -141,38 +156,56 @@ func (h *Trojan) Start() error { return nil } -func (h *Trojan) Close() error { +func (h *Inbound) Close() error { return common.Close( - &h.myInboundAdapter, + h.listener, h.tlsConfig, h.transport, ) } -func (h *Trojan) newTransportConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - h.injectTCP(conn, metadata) - return nil -} - -func (h *Trojan) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - var err error +func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { if h.tlsConfig != nil && h.transport == nil { - conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) + tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig) if err != nil { - return err + N.CloseOnHandshakeFailure(conn, onClose, err) + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source, ": TLS handshake")) + return } + conn = tlsConn + } + err := h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose) + if err != nil { + N.CloseOnHandshakeFailure(conn, onClose, err) + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) } - return h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata)) } -func (h *Trojan) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + userIndex, loaded := auth.UserFromContext[int](ctx) + if !loaded { + N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid) + return + } + user := h.users[userIndex].Name + if user == "" { + user = F.ToString(userIndex) + } else { + metadata.User = user + } + h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) } -func (h *Trojan) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { - return os.ErrInvalid + N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid) + return } user := h.users[userIndex].Name if user == "" { @@ -180,55 +213,50 @@ func (h *Trojan) newConnection(ctx context.Context, conn net.Conn, metadata adap } else { metadata.User = user } - h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) - return h.router.RouteConnection(ctx, conn, metadata) + h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } -func (h *Trojan) fallbackConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) fallbackConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { var fallbackAddr M.Socksaddr if len(h.fallbackAddrTLSNextProto) > 0 { if tlsConn, loaded := common.Cast[tls.Conn](conn); loaded { connectionState := tlsConn.ConnectionState() if connectionState.NegotiatedProtocol != "" { if fallbackAddr, loaded = h.fallbackAddrTLSNextProto[connectionState.NegotiatedProtocol]; !loaded { - return E.New("fallback disabled for ALPN: ", connectionState.NegotiatedProtocol) + h.logger.DebugContext(ctx, "process connection from ", metadata.Source, ": fallback disabled for ALPN: ", connectionState.NegotiatedProtocol) + N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid) + return } } } } if !fallbackAddr.IsValid() { if !h.fallbackAddr.IsValid() { - return E.New("fallback disabled by default") + h.logger.DebugContext(ctx, "process connection from ", metadata.Source, ": fallback disabled by default") + N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid) + return } fallbackAddr = h.fallbackAddr } - h.logger.InfoContext(ctx, "fallback connection to ", fallbackAddr) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() metadata.Destination = fallbackAddr - return h.router.RouteConnection(ctx, conn, metadata) -} - -func (h *Trojan) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - userIndex, loaded := auth.UserFromContext[int](ctx) - if !loaded { - return os.ErrInvalid - } - user := h.users[userIndex].Name - if user == "" { - user = F.ToString(userIndex) - } else { - metadata.User = user - } - h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) - return h.router.RoutePacketConnection(ctx, conn, metadata) + h.logger.InfoContext(ctx, "fallback connection to ", fallbackAddr) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) } -var _ adapter.V2RayServerTransportHandler = (*trojanTransportHandler)(nil) +var _ adapter.V2RayServerTransportHandler = (*inboundTransportHandler)(nil) -type trojanTransportHandler Trojan +type inboundTransportHandler Inbound -func (t *trojanTransportHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { - return (*Trojan)(t).newTransportConnection(ctx, conn, adapter.InboundContext{ - Source: metadata.Source, - Destination: metadata.Destination, - }) +func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Source = source + metadata.Destination = destination + //nolint:staticcheck + metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck + h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) + (*Inbound)(h).NewConnection(ctx, conn, metadata, onClose) } diff --git a/outbound/trojan.go b/protocol/trojan/outbound.go similarity index 68% rename from outbound/trojan.go rename to protocol/trojan/outbound.go index 52d72757e1..26c7c81fe8 100644 --- a/outbound/trojan.go +++ b/protocol/trojan/outbound.go @@ -1,10 +1,11 @@ -package outbound +package trojan import ( "context" "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/tls" @@ -16,45 +17,52 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) -var _ adapter.Outbound = (*Trojan)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.TrojanOutboundOptions](registry, C.TypeTrojan, NewOutbound) +} -type Trojan struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger dialer N.Dialer serverAddr M.Socksaddr key [56]byte multiplexDialer *mux.Client tlsConfig tls.Config + tlsDialer tls.Dialer transport adapter.V2RayClientTransport } -func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanOutboundOptions) (*Trojan, error) { - outboundDialer, err := dialer.New(router, options.DialerOptions) +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanOutboundOptions) (adapter.Outbound, error) { + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } - outbound := &Trojan{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeTrojan, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTrojan, tag, options.Network.Build(), options.DialerOptions), + logger: logger, dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), key: trojan.Key(options.Password), } if options.TLS != nil { - outbound.tlsConfig, err = tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS)) + outbound.tlsConfig, err = tls.NewClientWithOptions(tls.ClientOptions{ + Context: ctx, + Logger: logger, + ServerAddress: options.Server, + Options: common.PtrValueOrDefault(options.TLS), + KTLSCompatible: common.PtrValueOrDefault(options.Transport).Type == "" && + !common.PtrValueOrDefault(options.Multiplex).Enabled, + }) if err != nil { return nil, err } + outbound.tlsDialer = tls.NewDialer(outboundDialer, outbound.tlsConfig) } if options.Transport != nil { outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig) @@ -69,7 +77,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog return outbound, nil } -func (h *Trojan) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { if h.multiplexDialer == nil { switch N.NetworkName(network) { case N.NetworkTCP: @@ -89,7 +97,7 @@ func (h *Trojan) DialContext(ctx context.Context, network string, destination M. } } -func (h *Trojan) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { if h.multiplexDialer == nil { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return (*trojanDialer)(h).ListenPacket(ctx, destination) @@ -99,43 +107,33 @@ func (h *Trojan) ListenPacket(ctx context.Context, destination M.Socksaddr) (net } } -func (h *Trojan) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *Trojan) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewPacketConnection(ctx, h, conn, metadata) -} - -func (h *Trojan) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { if h.transport != nil { h.transport.Close() } if h.multiplexDialer != nil { h.multiplexDialer.Reset() } - return } -func (h *Trojan) Close() error { +func (h *Outbound) Close() error { return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport) } -type trojanDialer Trojan +type trojanDialer Outbound func (h *trojanDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - ctx, metadata := adapter.AppendContext(ctx) - metadata.Outbound = h.tag + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() metadata.Destination = destination var conn net.Conn var err error if h.transport != nil { conn, err = h.transport.DialContext(ctx) + } else if h.tlsDialer != nil { + conn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr) } else { conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr) - if err == nil && h.tlsConfig != nil { - conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig) - } } if err != nil { common.Close(conn) diff --git a/inbound/tuic.go b/protocol/tuic/inbound.go similarity index 54% rename from inbound/tuic.go rename to protocol/tuic/inbound.go index b067c43cd9..531d426361 100644 --- a/inbound/tuic.go +++ b/protocol/tuic/inbound.go @@ -1,6 +1,4 @@ -//go:build with_quic - -package inbound +package tuic import ( "context" @@ -8,30 +6,39 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/uot" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + qtls "github.com/sagernet/sing-quic" "github.com/sagernet/sing-quic/tuic" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/gofrs/uuid/v5" ) -var _ adapter.Inbound = (*TUIC)(nil) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.TUICInboundOptions](registry, C.TypeTUIC, NewInbound) +} -type TUIC struct { - myInboundAdapter +type Inbound struct { + inbound.Adapter + router adapter.ConnectionRouterEx + logger log.ContextLogger + listener *listener.Listener tlsConfig tls.ServerConfig server *tuic.Service[int] userNameList []string } -func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICInboundOptions) (*TUIC, error) { +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICInboundOptions) (adapter.Inbound, error) { options.UDPFragmentDefault = true if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired @@ -40,16 +47,15 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge if err != nil { return nil, err } - inbound := &TUIC{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeTUIC, - network: []string{N.NetworkUDP}, - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeTUIC, tag), + router: uot.NewRouter(router, logger), + logger: logger, + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Listen: options.ListenOptions, + }), tlsConfig: tlsConfig, } var udpTimeout time.Duration @@ -59,15 +65,24 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge udpTimeout = C.UDPTimeout } service, err := tuic.NewService[int](tuic.ServiceOptions{ - Context: ctx, - Logger: logger, - TLSConfig: tlsConfig, + Context: ctx, + Logger: logger, + TLSConfig: tlsConfig, + QUICOptions: qtls.QUICOptions{ + IdleTimeout: options.IdleTimeout.Build(), + KeepAlivePeriod: options.KeepAlivePeriod.Build(), + StreamReceiveWindow: options.StreamReceiveWindow.Value(), + ConnectionReceiveWindow: options.ConnectionReceiveWindow.Value(), + MaxConcurrentStreams: options.MaxConcurrentStreams, + InitialPacketSize: options.InitialPacketSize, + DisablePathMTUDiscovery: options.DisablePathMTUDiscovery, + }, CongestionControl: options.CongestionControl, AuthTimeout: time.Duration(options.AuthTimeout), ZeroRTTHandshake: options.ZeroRTTHandshake, Heartbeat: time.Duration(options.Heartbeat), UDPTimeout: udpTimeout, - Handler: adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, nil), + Handler: inbound, }) if err != nil { return nil, err @@ -95,9 +110,17 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge return inbound, nil } -func (h *TUIC) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ctx = log.ContextWithNewID(ctx) - metadata = h.createMetadata(conn, metadata) + var metadata adapter.InboundContext + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + //nolint:staticcheck + metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck + metadata.OriginDestination = h.listener.UDPAddr() + metadata.Source = source + metadata.Destination = destination h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -106,12 +129,20 @@ func (h *TUIC) newConnection(ctx context.Context, conn net.Conn, metadata adapte } else { h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) } - return h.router.RouteConnection(ctx, conn, metadata) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) } -func (h *TUIC) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ctx = log.ContextWithNewID(ctx) - metadata = h.createPacketMetadata(conn, metadata) + var metadata adapter.InboundContext + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + //nolint:staticcheck + metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck + metadata.OriginDestination = h.listener.UDPAddr() + metadata.Source = source + metadata.Destination = destination h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -120,26 +151,29 @@ func (h *TUIC) newPacketConnection(ctx context.Context, conn N.PacketConn, metad } else { h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) } - return h.router.RoutePacketConnection(ctx, conn, metadata) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } -func (h *TUIC) Start() error { +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } if h.tlsConfig != nil { err := h.tlsConfig.Start() if err != nil { return err } } - packetConn, err := h.myInboundAdapter.ListenUDP() + packetConn, err := h.listener.ListenUDP() if err != nil { return err } return h.server.Start(packetConn) } -func (h *TUIC) Close() error { +func (h *Inbound) Close() error { return common.Close( - &h.myInboundAdapter, + h.listener, h.tlsConfig, common.PtrOrNil(h.server), ) diff --git a/outbound/tuic.go b/protocol/tuic/outbound.go similarity index 62% rename from outbound/tuic.go rename to protocol/tuic/outbound.go index c098332375..694c845152 100644 --- a/outbound/tuic.go +++ b/protocol/tuic/outbound.go @@ -1,6 +1,4 @@ -//go:build with_quic - -package outbound +package tuic import ( "context" @@ -9,15 +7,18 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + qtls "github.com/sagernet/sing-quic" "github.com/sagernet/sing-quic/tuic" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/uot" @@ -25,23 +26,25 @@ import ( "github.com/gofrs/uuid/v5" ) -var ( - _ adapter.Outbound = (*TUIC)(nil) - _ adapter.InterfaceUpdateListener = (*TUIC)(nil) -) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.TUICOutboundOptions](registry, C.TypeTUIC, NewOutbound) +} -type TUIC struct { - myOutboundAdapter +var _ adapter.InterfaceUpdateListener = (*Outbound)(nil) + +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger client *tuic.Client udpStream bool } -func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (*TUIC, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (adapter.Outbound, error) { options.UDPFragmentDefault = true if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired } - tlsConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS)) + tlsConfig, err := tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS)) if err != nil { return nil, err } @@ -58,15 +61,24 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge case "quic": tuicUDPStream = true } - outboundDialer, err := dialer.New(router, options.DialerOptions) + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } client, err := tuic.NewClient(tuic.ClientOptions{ - Context: ctx, - Dialer: outboundDialer, - ServerAddress: options.ServerOptions.Build(), - TLSConfig: tlsConfig, + Context: ctx, + Dialer: outboundDialer, + ServerAddress: options.ServerOptions.Build(), + TLSConfig: tlsConfig, + QUICOptions: qtls.QUICOptions{ + IdleTimeout: options.IdleTimeout.Build(), + KeepAlivePeriod: options.KeepAlivePeriod.Build(), + StreamReceiveWindow: options.StreamReceiveWindow.Value(), + ConnectionReceiveWindow: options.ConnectionReceiveWindow.Value(), + MaxConcurrentStreams: options.MaxConcurrentStreams, + InitialPacketSize: options.InitialPacketSize, + DisablePathMTUDiscovery: options.DisablePathMTUDiscovery, + }, UUID: userUUID, Password: options.Password, CongestionControl: options.CongestionControl, @@ -77,21 +89,15 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge if err != nil { return nil, err } - return &TUIC{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeTUIC, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + return &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTUIC, tag, options.Network.Build(), options.DialerOptions), + logger: logger, client: client, udpStream: options.UDPOverStream, }, nil } -func (h *TUIC) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { switch N.NetworkName(network) { case N.NetworkTCP: h.logger.InfoContext(ctx, "outbound connection to ", destination) @@ -119,7 +125,7 @@ func (h *TUIC) DialContext(ctx context.Context, network string, destination M.So } } -func (h *TUIC) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { if h.udpStream { h.logger.InfoContext(ctx, "outbound stream packet connection to ", destination) streamConn, err := h.client.DialConn(ctx, uot.RequestDestination(uot.Version)) @@ -136,18 +142,10 @@ func (h *TUIC) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.P } } -func (h *TUIC) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *TUIC) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewPacketConnection(ctx, h, conn, metadata) -} - -func (h *TUIC) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { _ = h.client.CloseWithError(E.New("network changed")) } -func (h *TUIC) Close() error { +func (h *Outbound) Close() error { return h.client.CloseWithError(os.ErrClosed) } diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go new file mode 100644 index 0000000000..65e87bfdc5 --- /dev/null +++ b/protocol/tun/inbound.go @@ -0,0 +1,589 @@ +package tun + +import ( + "context" + "net" + "net/netip" + "os" + "runtime" + "strconv" + "strings" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/taskmonitor" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/route/rule" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json/badoption" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/ranges" + "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service" + + "go4.org/netipx" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.TunInboundOptions](registry, C.TypeTun, NewInbound) +} + +type Inbound struct { + tag string + ctx context.Context + router adapter.Router + networkManager adapter.NetworkManager + logger log.ContextLogger + tunOptions tun.Options + udpTimeout time.Duration + dnsHijackAddress []netip.Addr + stack string + tunIf tun.Tun + tunStack tun.Stack + platformInterface adapter.PlatformInterface + platformOptions option.TunPlatformOptions + autoRedirect tun.AutoRedirect + routeRuleSet []adapter.RuleSet + routeRuleSetCallback []*list.Element[adapter.RuleSetUpdateCallback] + routeExcludeRuleSet []adapter.RuleSet + routeExcludeRuleSetCallback []*list.Element[adapter.RuleSetUpdateCallback] + routeAddressSet []*netipx.IPSet + routeExcludeAddressSet []*netipx.IPSet +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions) (adapter.Inbound, error) { + //nolint:staticcheck + if len(options.Inet4Address) > 0 || len(options.Inet6Address) > 0 || + len(options.Inet4RouteAddress) > 0 || len(options.Inet6RouteAddress) > 0 || + len(options.Inet4RouteExcludeAddress) > 0 || len(options.Inet6RouteExcludeAddress) > 0 { + return nil, E.New("legacy tun address fields are deprecated in sing-box 1.10.0 and removed in sing-box 1.12.0") + } + //nolint:staticcheck + if options.GSO { + return nil, E.New("GSO option in tun is deprecated in sing-box 1.11.0 and removed in sing-box 1.12.0") + } + //nolint:staticcheck + if options.InboundOptions != (option.InboundOptions{}) { + return nil, E.New("legacy inbound fields are deprecated in sing-box 1.11.0 and removed in sing-box 1.13.0, checkout migration: https://sing-box.sagernet.org/migration/#migrate-legacy-inbound-fields-to-rule-actions") + } + + address := options.Address + inet4Address := common.Filter(address, func(it netip.Prefix) bool { + return it.Addr().Is4() + }) + inet6Address := common.Filter(address, func(it netip.Prefix) bool { + return it.Addr().Is6() + }) + + routeAddress := options.RouteAddress + inet4RouteAddress := common.Filter(routeAddress, func(it netip.Prefix) bool { + return it.Addr().Is4() + }) + inet6RouteAddress := common.Filter(routeAddress, func(it netip.Prefix) bool { + return it.Addr().Is6() + }) + + routeExcludeAddress := options.RouteExcludeAddress + inet4RouteExcludeAddress := common.Filter(routeExcludeAddress, func(it netip.Prefix) bool { + return it.Addr().Is4() + }) + inet6RouteExcludeAddress := common.Filter(routeExcludeAddress, func(it netip.Prefix) bool { + return it.Addr().Is6() + }) + + platformInterface := service.FromContext[adapter.PlatformInterface](ctx) + tunMTU := options.MTU + enableGSO := C.IsLinux && options.Stack == "gvisor" && platformInterface == nil && tunMTU > 0 && tunMTU < 49152 + if tunMTU == 0 { + if platformInterface != nil && platformInterface.UnderNetworkExtension() { + // In Network Extension, when MTU exceeds 4064 (4096-UTUN_IF_HEADROOM_SIZE), the performance of tun will drop significantly, which may be a system bug. + tunMTU = 4064 + } else if C.IsAndroid { + // Some Android devices report ENOBUFS when using MTU 65535 + tunMTU = 9000 + } else { + tunMTU = 65535 + } + } + var udpTimeout time.Duration + if options.UDPTimeout != 0 { + udpTimeout = time.Duration(options.UDPTimeout) + } else { + udpTimeout = C.UDPTimeout + } + var err error + includeUID := uidToRange(options.IncludeUID) + if len(options.IncludeUIDRange) > 0 { + includeUID, err = parseRange(includeUID, options.IncludeUIDRange) + if err != nil { + return nil, E.Cause(err, "parse include_uid_range") + } + } + excludeUID := uidToRange(options.ExcludeUID) + if len(options.ExcludeUIDRange) > 0 { + excludeUID, err = parseRange(excludeUID, options.ExcludeUIDRange) + if err != nil { + return nil, E.Cause(err, "parse exclude_uid_range") + } + } + + tableIndex := options.IPRoute2TableIndex + if tableIndex == 0 { + tableIndex = tun.DefaultIPRoute2TableIndex + } + ruleIndex := options.IPRoute2RuleIndex + if ruleIndex == 0 { + ruleIndex = tun.DefaultIPRoute2RuleIndex + } + autoRedirectFallbackRuleIndex := options.AutoRedirectFallbackRuleIndex + if autoRedirectFallbackRuleIndex == 0 { + autoRedirectFallbackRuleIndex = tun.DefaultIPRoute2AutoRedirectFallbackRuleIndex + } + inputMark := uint32(options.AutoRedirectInputMark) + if inputMark == 0 { + inputMark = tun.DefaultAutoRedirectInputMark + } + outputMark := uint32(options.AutoRedirectOutputMark) + if outputMark == 0 { + outputMark = tun.DefaultAutoRedirectOutputMark + } + resetMark := uint32(options.AutoRedirectResetMark) + if resetMark == 0 { + resetMark = tun.DefaultAutoRedirectResetMark + } + nfQueue := options.AutoRedirectNFQueue + if nfQueue == 0 { + nfQueue = tun.DefaultAutoRedirectNFQueue + } + var includeMACAddress []net.HardwareAddr + for i, macString := range options.IncludeMACAddress { + mac, macErr := net.ParseMAC(macString) + if macErr != nil { + return nil, E.Cause(macErr, "parse include_mac_address[", i, "]") + } + includeMACAddress = append(includeMACAddress, mac) + } + var excludeMACAddress []net.HardwareAddr + for i, macString := range options.ExcludeMACAddress { + mac, macErr := net.ParseMAC(macString) + if macErr != nil { + return nil, E.Cause(macErr, "parse exclude_mac_address[", i, "]") + } + excludeMACAddress = append(excludeMACAddress, mac) + } + networkManager := service.FromContext[adapter.NetworkManager](ctx) + multiPendingPackets := C.IsDarwin && ((options.Stack == "gvisor" && tunMTU < 32768) || (options.Stack != "gvisor" && options.MTU <= 9000)) + inbound := &Inbound{ + tag: tag, + ctx: ctx, + router: router, + networkManager: networkManager, + logger: logger, + tunOptions: tun.Options{ + Name: options.InterfaceName, + MTU: tunMTU, + GSO: enableGSO, + Inet4Address: inet4Address, + Inet6Address: inet6Address, + DNSMode: options.DNSMode, + DNSAddress: options.DNSAddress, + AutoRoute: options.AutoRoute, + IPRoute2TableIndex: tableIndex, + IPRoute2RuleIndex: ruleIndex, + IPRoute2AutoRedirectFallbackRuleIndex: autoRedirectFallbackRuleIndex, + AutoRedirectInputMark: inputMark, + AutoRedirectOutputMark: outputMark, + AutoRedirectResetMark: resetMark, + AutoRedirectNFQueue: nfQueue, + ExcludeMPTCP: options.ExcludeMPTCP, + Inet4LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is4), + Inet6LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is6), + StrictRoute: options.StrictRoute, + IncludeInterface: options.IncludeInterface, + ExcludeInterface: options.ExcludeInterface, + Inet4RouteAddress: inet4RouteAddress, + Inet6RouteAddress: inet6RouteAddress, + Inet4RouteExcludeAddress: inet4RouteExcludeAddress, + Inet6RouteExcludeAddress: inet6RouteExcludeAddress, + IncludeUID: includeUID, + ExcludeUID: excludeUID, + IncludeAndroidUser: options.IncludeAndroidUser, + IncludePackage: options.IncludePackage, + ExcludePackage: options.ExcludePackage, + IncludeMACAddress: includeMACAddress, + ExcludeMACAddress: excludeMACAddress, + InterfaceMonitor: networkManager.InterfaceMonitor(), + EXP_MultiPendingPackets: multiPendingPackets, + }, + udpTimeout: udpTimeout, + stack: options.Stack, + platformInterface: platformInterface, + platformOptions: common.PtrValueOrDefault(options.Platform), + } + for _, routeAddressSet := range options.RouteAddressSet { + ruleSet, loaded := router.RuleSet(routeAddressSet) + if !loaded { + return nil, E.New("parse route_address_set: rule-set not found: ", routeAddressSet) + } + inbound.routeRuleSet = append(inbound.routeRuleSet, ruleSet) + } + for _, routeExcludeAddressSet := range options.RouteExcludeAddressSet { + ruleSet, loaded := router.RuleSet(routeExcludeAddressSet) + if !loaded { + return nil, E.New("parse route_exclude_address_set: rule-set not found: ", routeExcludeAddressSet) + } + inbound.routeExcludeRuleSet = append(inbound.routeExcludeRuleSet, ruleSet) + } + if options.AutoRedirect { + if !options.AutoRoute { + return nil, E.New("`auto_route` is required by `auto_redirect`") + } + disableNFTables, dErr := strconv.ParseBool(os.Getenv("DISABLE_NFTABLES")) + inbound.autoRedirect, err = tun.NewAutoRedirect(tun.AutoRedirectOptions{ + TunOptions: &inbound.tunOptions, + Context: ctx, + Handler: (*autoRedirectHandler)(inbound), + Logger: logger, + NetworkMonitor: networkManager.NetworkMonitor(), + InterfaceFinder: networkManager.InterfaceFinder(), + TableName: "sing-box", + DisableNFTables: dErr == nil && disableNFTables, + RouteAddressSet: &inbound.routeAddressSet, + RouteExcludeAddressSet: &inbound.routeExcludeAddressSet, + }) + if err != nil { + return nil, E.Cause(err, "initialize auto-redirect") + } + if !C.IsAndroid { + inbound.tunOptions.AutoRedirectMarkMode = true + err = networkManager.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark) + if err != nil { + return nil, err + } + } + } + return inbound, nil +} + +func uidToRange(uidList badoption.Listable[uint32]) []ranges.Range[uint32] { + return common.Map(uidList, func(uid uint32) ranges.Range[uint32] { + return ranges.NewSingle(uid) + }) +} + +func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges.Range[uint32], error) { + for _, uidRange := range rangeList { + if !strings.Contains(uidRange, ":") { + return nil, E.New("missing ':' in range: ", uidRange) + } + subIndex := strings.Index(uidRange, ":") + if subIndex == 0 { + return nil, E.New("missing range start: ", uidRange) + } else if subIndex == len(uidRange)-1 { + return nil, E.New("missing range end: ", uidRange) + } + var start, end uint64 + var err error + start, err = strconv.ParseUint(uidRange[:subIndex], 0, 32) + if err != nil { + return nil, E.Cause(err, "parse range start") + } + end, err = strconv.ParseUint(uidRange[subIndex+1:], 0, 32) + if err != nil { + return nil, E.Cause(err, "parse range end") + } + uidRanges = append(uidRanges, ranges.New(uint32(start), uint32(end))) + } + return uidRanges, nil +} + +func (t *Inbound) Type() string { + return C.TypeTun +} + +func (t *Inbound) Tag() string { + return t.tag +} + +func (t *Inbound) Start(stage adapter.StartStage) error { + switch stage { + case adapter.StartStateInitialize: + if t.tunOptions.DNSModeOrDefault() != tun.DNSModeDisabled && len(t.tunOptions.DNSAddress) == 0 { + inet4DNSAddress, _ := t.tunOptions.Inet4DNSAddress() + inet6DNSAddress, _ := t.tunOptions.Inet6DNSAddress() + t.dnsHijackAddress = append(inet4DNSAddress, inet6DNSAddress...) + } + case adapter.StartStateStart: + if C.IsAndroid && t.platformInterface == nil { + t.tunOptions.BuildAndroidRules(t.networkManager.PackageManager()) + } + if t.tunOptions.Name == "" { + t.tunOptions.Name = tun.CalculateInterfaceName("") + } + if t.platformInterface == nil { + t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) + for _, routeRuleSet := range t.routeRuleSet { + ipSets := routeRuleSet.ExtractIPSet() + if len(ipSets) == 0 { + t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeRuleSet.Name()) + } + routeRuleSet.IncRef() + t.routeAddressSet = append(t.routeAddressSet, ipSets...) + if t.autoRedirect != nil { + t.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet)) + } + } + t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet) + for _, routeExcludeRuleSet := range t.routeExcludeRuleSet { + ipSets := routeExcludeRuleSet.ExtractIPSet() + if len(ipSets) == 0 { + t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeExcludeRuleSet.Name()) + } + routeExcludeRuleSet.IncRef() + t.routeExcludeAddressSet = append(t.routeExcludeAddressSet, ipSets...) + if t.autoRedirect != nil { + t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet)) + } + } + } + var ( + tunInterface tun.Tun + err error + ) + monitor := taskmonitor.New(t.logger, C.StartTimeout) + tunOptions := t.tunOptions + if t.autoRedirect == nil && !(runtime.GOOS == "android" && t.platformInterface != nil) { + for _, ipSet := range t.routeAddressSet { + for _, prefix := range ipSet.Prefixes() { + if prefix.Addr().Is4() { + tunOptions.Inet4RouteAddress = append(tunOptions.Inet4RouteAddress, prefix) + } else { + tunOptions.Inet6RouteAddress = append(tunOptions.Inet6RouteAddress, prefix) + } + } + } + for _, ipSet := range t.routeExcludeAddressSet { + for _, prefix := range ipSet.Prefixes() { + if prefix.Addr().Is4() { + tunOptions.Inet4RouteExcludeAddress = append(tunOptions.Inet4RouteExcludeAddress, prefix) + } else { + tunOptions.Inet6RouteExcludeAddress = append(tunOptions.Inet6RouteExcludeAddress, prefix) + } + } + } + } + monitor.Start("open interface") + if t.platformInterface != nil && t.platformInterface.UsePlatformInterface() { + tunInterface, err = t.platformInterface.OpenInterface(&tunOptions, t.platformOptions) + } else { + tunInterface, err = tun.New(tunOptions) + } + monitor.Finish() + t.tunOptions.Name = tunOptions.Name + if err != nil { + return E.Cause(err, "configure tun interface") + } + t.logger.Trace("creating stack") + t.tunIf = tunInterface + var ( + forwarderBindInterface bool + includeAllNetworks bool + ) + if t.platformInterface != nil { + forwarderBindInterface = true + includeAllNetworks = t.platformInterface.NetworkExtensionIncludeAllNetworks() + } + tunStack, err := tun.NewStack(t.stack, tun.StackOptions{ + Context: t.ctx, + Tun: tunInterface, + TunOptions: t.tunOptions, + UDPTimeout: t.udpTimeout, + Handler: t, + Logger: t.logger, + ForwarderBindInterface: forwarderBindInterface, + InterfaceFinder: t.networkManager.InterfaceFinder(), + IncludeAllNetworks: includeAllNetworks, + }) + if err != nil { + return err + } + t.tunStack = tunStack + t.logger.Info("started at ", t.tunOptions.Name) + case adapter.StartStatePostStart: + monitor := taskmonitor.New(t.logger, C.StartTimeout) + monitor.Start("starting tun stack") + err := t.tunStack.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "starting tun stack") + } + monitor.Start("starting tun interface") + err = t.tunIf.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "starting TUN interface") + } + if t.autoRedirect != nil { + monitor.Start("initialize auto-redirect") + err := t.autoRedirect.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "auto-redirect") + } + } + t.routeAddressSet = nil + t.routeExcludeAddressSet = nil + } + return nil +} + +func (t *Inbound) updateRouteAddressSet(it adapter.RuleSet) { + t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) + t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet) + t.autoRedirect.UpdateRouteAddressSet() + t.routeAddressSet = nil + t.routeExcludeAddressSet = nil +} + +func (t *Inbound) Close() error { + return common.Close( + t.tunStack, + t.tunIf, + t.autoRedirect, + ) +} + +func (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + var ipVersion uint8 + if !destination.IsIPv6() { + ipVersion = 4 + } else { + ipVersion = 6 + } + routeDestination, err := t.router.PreMatch(adapter.InboundContext{ + Inbound: t.tag, + InboundType: C.TypeTun, + IPVersion: ipVersion, + Network: network, + Source: source, + Destination: destination, + }, routeContext, timeout, false) + if err != nil { + switch { + case rule.IsBypassed(err): + err = nil + case rule.IsRejected(err): + t.logger.Trace("reject ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()) + default: + if network == N.NetworkICMP { + t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())) + } + } + } + return routeDestination, err +} + +func (t *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + ctx = log.ContextWithNewID(ctx) + var metadata adapter.InboundContext + metadata.Inbound = t.tag + metadata.InboundType = C.TypeTun + metadata.Source = source + metadata.Destination = destination + for _, dnsHijackAddress := range t.dnsHijackAddress { + if destination.Addr == dnsHijackAddress { + metadata.Protocol = C.ProtocolDNS + } + } + if metadata.Protocol == C.ProtocolDNS { + t.logger.InfoContext(ctx, "inbound DNS connection from ", metadata.Source) + } else { + t.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) + t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + } + t.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (t *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + ctx = log.ContextWithNewID(ctx) + var metadata adapter.InboundContext + metadata.Inbound = t.tag + metadata.InboundType = C.TypeTun + metadata.Source = source + metadata.Destination = destination + for _, dnsHijackAddress := range t.dnsHijackAddress { + if destination.Addr == dnsHijackAddress { + metadata.Protocol = C.ProtocolDNS + } + } + if metadata.Protocol == C.ProtocolDNS { + t.logger.InfoContext(ctx, "inbound DNS packet connection from ", metadata.Source) + } else { + t.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) + t.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + } + t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} + +type autoRedirectHandler Inbound + +func (t *autoRedirectHandler) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + var ipVersion uint8 + if !destination.IsIPv6() { + ipVersion = 4 + } else { + ipVersion = 6 + } + routeDestination, err := t.router.PreMatch(adapter.InboundContext{ + Inbound: t.tag, + InboundType: C.TypeTun, + IPVersion: ipVersion, + Network: network, + Source: source, + Destination: destination, + }, routeContext, timeout, true) + if err != nil { + switch { + case rule.IsBypassed(err): + t.logger.Trace("bypass ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()) + case rule.IsRejected(err): + t.logger.Trace("reject ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()) + default: + if network == N.NetworkICMP { + t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())) + } + } + } + return routeDestination, err +} + +func (t *autoRedirectHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + ctx = log.ContextWithNewID(ctx) + var metadata adapter.InboundContext + metadata.Inbound = t.tag + metadata.InboundType = C.TypeTun + metadata.Source = source + metadata.Destination = destination + for _, dnsHijackAddress := range t.dnsHijackAddress { + if destination.Addr == dnsHijackAddress { + metadata.Protocol = C.ProtocolDNS + } + } + if metadata.Protocol == C.ProtocolDNS { + t.logger.InfoContext(ctx, "inbound redirect DNS connection from ", metadata.Source) + } else { + t.logger.InfoContext(ctx, "inbound redirect connection from ", metadata.Source) + t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + } + t.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (t *autoRedirectHandler) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + panic("unexcepted") +} diff --git a/protocol/tunnel/client.go b/protocol/tunnel/client.go new file mode 100644 index 0000000000..45255c1f81 --- /dev/null +++ b/protocol/tunnel/client.go @@ -0,0 +1,152 @@ +package tunnel + +import ( + "context" + "net" + "os" + "time" + + "github.com/gofrs/uuid/v5" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/endpoint" + "github.com/sagernet/sing-box/adapter/outbound" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" +) + +func RegisterClientEndpoint(registry *endpoint.Registry) { + endpoint.Register[option.TunnelClientEndpointOptions](registry, C.TypeTunnelClient, NewClientEndpoint) +} + +type ClientEndpoint struct { + outbound.Adapter + ctx context.Context + outbound adapter.Outbound + router adapter.ConnectionRouterEx + logger logger.ContextLogger + uuid uuid.UUID + key uuid.UUID +} + +func NewClientEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunnelClientEndpointOptions) (adapter.Endpoint, error) { + clientUUID, err := uuid.FromString(options.UUID) + if err != nil { + return nil, err + } + clientKey, err := uuid.FromString(options.Key) + if err != nil { + return nil, err + } + client := &ClientEndpoint{ + Adapter: outbound.NewAdapter(C.TypeTunnelClient, tag, []string{N.NetworkTCP}, []string{}), + ctx: ctx, + router: router, + logger: logger, + uuid: clientUUID, + key: clientKey, + } + outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx) + outbound, err := outboundRegistry.CreateOutbound(ctx, router, logger, options.Outbound.Tag, options.Outbound.Type, options.Outbound.Options) + if err != nil { + return nil, err + } + client.outbound = outbound + return client, nil +} + +func (c *ClientEndpoint) Start(stage adapter.StartStage) error { + if stage != adapter.StartStatePostStart { + return nil + } + for range 5 { + go func() { + for { + select { + case <-c.ctx.Done(): + return + default: + err := c.startInboundConn() + if err != nil { + c.logger.ErrorContext(c.ctx, err) + time.Sleep(time.Second * 5) + } + } + } + }() + } + return nil +} + +func (c *ClientEndpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + if network != N.NetworkTCP { + return nil, os.ErrInvalid + } + var destinationUUID *uuid.UUID + if metadata := adapter.ContextFrom(ctx); metadata != nil { + if metadata.TunnelDestination != "" { + uuid, err := uuid.FromString(metadata.TunnelDestination) + if err != nil { + return nil, err + } + destinationUUID = &uuid + } + } + if destinationUUID == nil { + return nil, E.New("tunnel destination not set") + } + if *destinationUUID == c.uuid { + return nil, E.New("routing loop") + } + conn, err := c.outbound.DialContext(ctx, N.NetworkTCP, Destination) + if err != nil { + return nil, err + } + err = WriteRequest(conn, &Request{UUID: c.key, Command: CommandTCP, DestinationUUID: *destinationUUID, Destination: destination}) + return conn, err +} + +func (c *ClientEndpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return nil, os.ErrInvalid +} + +func (c *ClientEndpoint) Close() error { + return common.Close(c.outbound) +} + +func (c *ClientEndpoint) startInboundConn() error { + conn, err := c.outbound.DialContext(c.ctx, N.NetworkTCP, Destination) + if err != nil { + return err + } + err = WriteRequest(conn, &Request{UUID: c.key, Command: CommandInbound, Destination: Destination}) + if err != nil { + return err + } + request, err := ReadRequest(conn) + if err != nil { + return err + } + go c.connHandler(conn, request) + return nil +} + +func (c *ClientEndpoint) connHandler(conn net.Conn, request *Request) { + metadata := adapter.InboundContext{ + Source: M.ParseSocksaddr(conn.RemoteAddr().String()), + Destination: request.Destination, + } + if request.UUID == c.uuid { + c.logger.ErrorContext(c.ctx, "routing loop") + conn.Close() + return + } + metadata.TunnelSource = request.UUID.String() + c.router.RouteConnectionEx(c.ctx, conn, metadata, func(it error) {}) +} diff --git a/protocol/tunnel/protocol.go b/protocol/tunnel/protocol.go new file mode 100644 index 0000000000..19e6f1cd62 --- /dev/null +++ b/protocol/tunnel/protocol.go @@ -0,0 +1,91 @@ +package tunnel + +import ( + "encoding/binary" + "io" + + "github.com/gofrs/uuid/v5" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" +) + +const ( + Version = 0 +) + +const ( + CommandInbound = 1 + CommandTCP = 2 +) + +var Destination = M.Socksaddr{ + Fqdn: "sp.tunnel.sing-box.arpa", + Port: 444, +} + +var AddressSerializer = M.NewSerializer( + M.AddressFamilyByte(0x01, M.AddressFamilyIPv4), + M.AddressFamilyByte(0x03, M.AddressFamilyIPv6), + M.AddressFamilyByte(0x02, M.AddressFamilyFqdn), + M.PortThenAddress(), +) + +type Request struct { + UUID uuid.UUID + Command byte + DestinationUUID uuid.UUID + Destination M.Socksaddr +} + +func ReadRequest(reader io.Reader) (*Request, error) { + var request Request + var version uint8 + err := binary.Read(reader, binary.BigEndian, &version) + if err != nil { + return nil, err + } + if version != Version { + return nil, E.New("unknown version: ", version) + } + _, err = io.ReadFull(reader, request.UUID[:]) + if err != nil { + return nil, err + } + err = binary.Read(reader, binary.BigEndian, &request.Command) + if err != nil { + return nil, err + } + _, err = io.ReadFull(reader, request.DestinationUUID[:]) + if err != nil { + return nil, err + } + request.Destination, err = AddressSerializer.ReadAddrPort(reader) + if err != nil { + return nil, err + } + return &request, nil +} + +func WriteRequest(writer io.Writer, request *Request) error { + var requestLen int + requestLen += 1 // version + requestLen += 16 // UUID + requestLen += 16 // destinationUUID + requestLen += 1 // command + requestLen += AddressSerializer.AddrPortLen(request.Destination) + buffer := buf.NewSize(requestLen) + defer buffer.Release() + common.Must( + buffer.WriteByte(Version), + common.Error(buffer.Write(request.UUID[:])), + buffer.WriteByte(request.Command), + common.Error(buffer.Write(request.DestinationUUID[:])), + ) + err := AddressSerializer.WriteAddrPort(buffer, request.Destination) + if err != nil { + return err + } + return common.Error(writer.Write(buffer.Bytes())) +} diff --git a/protocol/tunnel/router.go b/protocol/tunnel/router.go new file mode 100644 index 0000000000..e2e708b542 --- /dev/null +++ b/protocol/tunnel/router.go @@ -0,0 +1,41 @@ +package tunnel + +import ( + "context" + "net" + "os" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common/logger" + N "github.com/sagernet/sing/common/network" +) + +type Router struct { + adapter.Router + logger logger.ContextLogger + handler func(context.Context, net.Conn, adapter.InboundContext, N.CloseHandlerFunc) error +} + +func NewRouter(router adapter.Router, logger logger.ContextLogger, handler func(context.Context, net.Conn, adapter.InboundContext, N.CloseHandlerFunc) error) *Router { + return &Router{Router: router, logger: logger, handler: handler} +} + +func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + return r.handler(ctx, conn, metadata, func(error) {}) +} + +func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + return os.ErrInvalid +} + +func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + if err := r.handler(ctx, conn, metadata, onClose); err != nil { + r.logger.ErrorContext(ctx, err) + N.CloseOnHandshakeFailure(conn, onClose, err) + } +} + +func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + r.logger.ErrorContext(ctx, os.ErrInvalid) + N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid) +} diff --git a/protocol/tunnel/server.go b/protocol/tunnel/server.go new file mode 100644 index 0000000000..d447be8c65 --- /dev/null +++ b/protocol/tunnel/server.go @@ -0,0 +1,203 @@ +package tunnel + +import ( + "context" + "net" + "os" + "sync" + "time" + + "github.com/gofrs/uuid/v5" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/endpoint" + "github.com/sagernet/sing-box/adapter/outbound" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" +) + +func RegisterServerEndpoint(registry *endpoint.Registry) { + endpoint.Register[option.TunnelServerEndpointOptions](registry, C.TypeTunnelServer, NewServerEndpoint) +} + +type ServerEndpoint struct { + outbound.Adapter + logger logger.ContextLogger + inbound adapter.Inbound + router adapter.Router + uuid uuid.UUID + users map[uuid.UUID]uuid.UUID + keys map[uuid.UUID]uuid.UUID + conns map[uuid.UUID]chan net.Conn + timeout time.Duration + + mtx sync.Mutex +} + +func NewServerEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunnelServerEndpointOptions) (adapter.Endpoint, error) { + serverUUID, err := uuid.FromString(options.UUID) + if err != nil { + return nil, err + } + server := &ServerEndpoint{ + Adapter: outbound.NewAdapter(C.TypeTunnelServer, tag, []string{N.NetworkTCP}, []string{}), + logger: logger, + router: router, + uuid: serverUUID, + } + inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx) + inbound, err := inboundRegistry.Create(ctx, NewRouter(router, logger, server.connHandler), logger, options.Inbound.Tag, options.Inbound.Type, options.Inbound.Options) + if err != nil { + return nil, err + } + server.inbound = inbound + server.users = make(map[uuid.UUID]uuid.UUID, len(options.Users)) + server.keys = make(map[uuid.UUID]uuid.UUID, len(options.Users)) + server.conns = make(map[uuid.UUID]chan net.Conn) + for _, user := range options.Users { + key, err := uuid.FromString(user.Key) + if err != nil { + return nil, err + } + uuid, err := uuid.FromString(user.UUID) + if err != nil { + return nil, err + } + server.users[key] = uuid + server.keys[uuid] = key + server.conns[uuid] = make(chan net.Conn, 10) + } + if options.ConnectTimeout != 0 { + server.timeout = time.Duration(options.ConnectTimeout) + } else { + server.timeout = C.TCPConnectTimeout + } + return server, nil +} + +func (s *ServerEndpoint) Start(stage adapter.StartStage) error { + return s.inbound.Start(stage) +} + +func (s *ServerEndpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + if network != N.NetworkTCP { + return nil, os.ErrInvalid + } + var sourceUUID *uuid.UUID + var ch chan net.Conn + if metadata := adapter.ContextFrom(ctx); metadata != nil { + if metadata.TunnelDestination != "" { + tunnelDestination, err := uuid.FromString(metadata.TunnelDestination) + if err != nil { + return nil, err + } + s.mtx.Lock() + var ok bool + ch, ok = s.conns[tunnelDestination] + if !ok { + return nil, E.New("user ", metadata.TunnelDestination, " not found") + } + s.mtx.Unlock() + } + if metadata.TunnelSource != "" { + tunnelSource, err := uuid.FromString(metadata.TunnelSource) + if err != nil { + return nil, err + } + sourceUUID = &tunnelSource + } + } + if ch == nil { + return nil, E.New("tunnel destination not set") + } + if sourceUUID == nil { + sourceUUID = &s.uuid + } + ctx, cancel := context.WithTimeout(ctx, s.timeout) + defer cancel() + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + select { + case conn := <-ch: + err := WriteRequest(conn, &Request{UUID: *sourceUUID, Command: CommandTCP, Destination: destination}) + if err != nil { + s.logger.ErrorContext(ctx, err) + continue + } + return conn, nil + case <-ctx.Done(): + return nil, ctx.Err() + } + } +} + +func (s *ServerEndpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return nil, os.ErrInvalid +} + +func (s *ServerEndpoint) Close() error { + return common.Close(s.inbound) +} + +func (s *ServerEndpoint) connHandler(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { + if metadata.Destination != Destination { + s.router.RouteConnectionEx(ctx, conn, metadata, onClose) + return nil + } + request, err := ReadRequest(conn) + if err != nil { + return err + } + if request.Command == CommandInbound { + s.mtx.Lock() + defer s.mtx.Unlock() + uuid, ok := s.users[request.UUID] + if !ok { + return E.New("key ", request.UUID.String(), " not found") + } + ch := s.conns[uuid] + select { + case ch <- conn: + default: + oldConn := <-ch + oldConn.Close() + ch <- conn + } + return nil + } + if request.Command == CommandTCP { + sourceUUID, ok := s.users[request.UUID] + if !ok { + return E.New("key ", request.UUID, " not found") + } + if sourceUUID == request.DestinationUUID { + return E.New("routing loop on ", sourceUUID) + } + s.mtx.Lock() + if request.DestinationUUID != s.uuid { + _, ok = s.keys[request.DestinationUUID] + if !ok { + return E.New("user ", sourceUUID, " not found") + } + } + s.mtx.Unlock() + metadata.Inbound = s.Tag() + metadata.InboundType = C.TypeTunnelServer + metadata.Destination = request.Destination + metadata.TunnelSource = sourceUUID.String() + metadata.TunnelDestination = request.DestinationUUID.String() + s.router.RouteConnectionEx(ctx, conn, metadata, onClose) + return nil + } + return E.New("command ", request.Command, " not found") +} diff --git a/protocol/vless/encryption/client.go b/protocol/vless/encryption/client.go new file mode 100644 index 0000000000..947b279e33 --- /dev/null +++ b/protocol/vless/encryption/client.go @@ -0,0 +1,214 @@ +package encryption + +import ( + "crypto/cipher" + "crypto/ecdh" + "crypto/mlkem" + "crypto/rand" + "io" + "net" + "sync" + "time" + + "github.com/sagernet/sing-box/common/xray/cpuid" + E "github.com/sagernet/sing/common/exceptions" + "lukechampine.com/blake3" +) + +type ClientInstance struct { + NfsPKeys []any + NfsPKeysBytes [][]byte + Hash32s [][32]byte + RelaysLength int + XorMode uint32 + Seconds uint32 + PaddingLens [][3]int + PaddingGaps [][3]int + + RWLock sync.RWMutex + Expire time.Time + PfsKey []byte + Ticket []byte +} + +func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32, padding string) (err error) { + if i.NfsPKeys != nil { + return E.New("already initialized") + } + l := len(nfsPKeysBytes) + if l == 0 { + return E.New("empty nfsPKeysBytes") + } + i.NfsPKeys = make([]any, l) + i.NfsPKeysBytes = nfsPKeysBytes + i.Hash32s = make([][32]byte, l) + for j, k := range nfsPKeysBytes { + if len(k) == 32 { + if i.NfsPKeys[j], err = ecdh.X25519().NewPublicKey(k); err != nil { + return + } + i.RelaysLength += 32 + 32 + } else { + if i.NfsPKeys[j], err = mlkem.NewEncapsulationKey768(k); err != nil { + return + } + i.RelaysLength += 1088 + 32 + } + i.Hash32s[j] = blake3.Sum256(k) + } + i.RelaysLength -= 32 + i.XorMode = xorMode + i.Seconds = seconds + return ParsePadding(padding, &i.PaddingLens, &i.PaddingGaps) +} + +func (i *ClientInstance) IsFullRandomXorMode() bool { + return i.XorMode == 2 +} + +func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) { + if i.NfsPKeys == nil { + return nil, E.New("uninitialized") + } + c := NewCommonConn(conn, cpuid.HasAESGCM) + + ivAndRealysLength := 16 + i.RelaysLength + pfsKeyExchangeLength := 18 + 1184 + 32 + 16 + paddingLength, paddingLens, paddingGaps := CreatePadding(i.PaddingLens, i.PaddingGaps) + clientHello := make([]byte, ivAndRealysLength+pfsKeyExchangeLength+paddingLength) + + iv := clientHello[:16] + rand.Read(iv) + relays := clientHello[16:ivAndRealysLength] + var nfsKey []byte + var lastCTR cipher.Stream + for j, k := range i.NfsPKeys { + var index = 32 + if k, ok := k.(*ecdh.PublicKey); ok { + privateKey, _ := ecdh.X25519().GenerateKey(rand.Reader) + copy(relays, privateKey.PublicKey().Bytes()) + var err error + nfsKey, err = privateKey.ECDH(k) + if err != nil { + return nil, err + } + } + if k, ok := k.(*mlkem.EncapsulationKey768); ok { + var ciphertext []byte + nfsKey, ciphertext = k.Encapsulate() + copy(relays, ciphertext) + index = 1088 + } + if i.XorMode > 0 { // this xor can (others can't) be recovered by client's config, revealing an X25519 public key / ML-KEM-768 ciphertext, that's why "native" values + NewCTR(i.NfsPKeysBytes[j], iv).XORKeyStream(relays, relays[:index]) // make X25519 public key / ML-KEM-768 ciphertext distinguishable from random bytes + } + if lastCTR != nil { + lastCTR.XORKeyStream(relays, relays[:32]) // make this relay irreplaceable + } + if j == len(i.NfsPKeys)-1 { + break + } + lastCTR = NewCTR(nfsKey, iv) + lastCTR.XORKeyStream(relays[index:], i.Hash32s[j+1][:]) + relays = relays[index+32:] + } + nfsAEAD := NewAEAD(iv, nfsKey, c.UseAES) + + if i.Seconds > 0 { + i.RWLock.RLock() + if time.Now().Before(i.Expire) { + c.Client = i + c.UnitedKey = append(i.PfsKey, nfsKey...) // different unitedKey for each connection + nfsAEAD.Seal(clientHello[:ivAndRealysLength], nil, EncodeLength(32), nil) + nfsAEAD.Seal(clientHello[:ivAndRealysLength+18], nil, i.Ticket, nil) + i.RWLock.RUnlock() + c.PreWrite = clientHello[:ivAndRealysLength+18+32] + c.AEAD = NewAEAD(clientHello[ivAndRealysLength+18:ivAndRealysLength+18+32], c.UnitedKey, c.UseAES) + if i.XorMode == 2 { + c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, iv), nil, len(c.PreWrite), 16) + } + return c, nil + } + i.RWLock.RUnlock() + } + + pfsKeyExchange := clientHello[ivAndRealysLength : ivAndRealysLength+pfsKeyExchangeLength] + nfsAEAD.Seal(pfsKeyExchange[:0], nil, EncodeLength(pfsKeyExchangeLength-18), nil) + mlkem768DKey, _ := mlkem.GenerateKey768() + x25519SKey, _ := ecdh.X25519().GenerateKey(rand.Reader) + pfsPublicKey := append(mlkem768DKey.EncapsulationKey().Bytes(), x25519SKey.PublicKey().Bytes()...) + nfsAEAD.Seal(pfsKeyExchange[:18], nil, pfsPublicKey, nil) + + padding := clientHello[ivAndRealysLength+pfsKeyExchangeLength:] + nfsAEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil) + nfsAEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil) + + paddingLens[0] = ivAndRealysLength + pfsKeyExchangeLength + paddingLens[0] + for i, l := range paddingLens { // sends padding in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control + if l > 0 { + if _, err := conn.Write(clientHello[:l]); err != nil { + return nil, err + } + clientHello = clientHello[l:] + } + if len(paddingGaps) > i { + time.Sleep(paddingGaps[i]) + } + } + + encryptedPfsPublicKey := make([]byte, 1088+32+16) + if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil { + return nil, err + } + nfsAEAD.Open(encryptedPfsPublicKey[:0], MaxNonce, encryptedPfsPublicKey, nil) + mlkem768Key, err := mlkem768DKey.Decapsulate(encryptedPfsPublicKey[:1088]) + if err != nil { + return nil, err + } + peerX25519PKey, err := ecdh.X25519().NewPublicKey(encryptedPfsPublicKey[1088 : 1088+32]) + if err != nil { + return nil, err + } + x25519Key, err := x25519SKey.ECDH(peerX25519PKey) + if err != nil { + return nil, err + } + pfsKey := make([]byte, 32+32) // no more capacity + copy(pfsKey, mlkem768Key) + copy(pfsKey[32:], x25519Key) + c.UnitedKey = append(pfsKey, nfsKey...) + c.AEAD = NewAEAD(pfsPublicKey, c.UnitedKey, c.UseAES) + c.PeerAEAD = NewAEAD(encryptedPfsPublicKey[:1088+32], c.UnitedKey, c.UseAES) + + encryptedTicket := make([]byte, 32) + if _, err := io.ReadFull(conn, encryptedTicket); err != nil { + return nil, err + } + if _, err := c.PeerAEAD.Open(encryptedTicket[:0], nil, encryptedTicket, nil); err != nil { + return nil, err + } + seconds := DecodeLength(encryptedTicket) + + if i.Seconds > 0 && seconds > 0 { + i.RWLock.Lock() + i.Expire = time.Now().Add(time.Duration(seconds) * time.Second) + i.PfsKey = pfsKey + i.Ticket = encryptedTicket[:16] + i.RWLock.Unlock() + } + + encryptedLength := make([]byte, 18) + if _, err := io.ReadFull(conn, encryptedLength); err != nil { + return nil, err + } + if _, err := c.PeerAEAD.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil { + return nil, err + } + length := DecodeLength(encryptedLength[:2]) + c.PeerPadding = make([]byte, length) // important: allows server sends padding slowly, eliminating 1-RTT's traffic pattern + + if i.XorMode == 2 { + c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, iv), NewCTR(c.UnitedKey, encryptedTicket[:16]), 0, length) + } + return c, nil +} diff --git a/protocol/vless/encryption/common.go b/protocol/vless/encryption/common.go new file mode 100644 index 0000000000..84e3818dbb --- /dev/null +++ b/protocol/vless/encryption/common.go @@ -0,0 +1,297 @@ +package encryption + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "errors" + "fmt" + "io" + "net" + "strconv" + "strings" + "sync" + "time" + + "github.com/sagernet/sing-box/common/xray/crypto" + E "github.com/sagernet/sing/common/exceptions" + "golang.org/x/crypto/chacha20poly1305" + "lukechampine.com/blake3" +) + +var OutBytesPool = sync.Pool{ + New: func() any { + return make([]byte, 5+8192+16) + }, +} + +type EncryptionConn interface { + net.Conn + IsEncryptionLayer() bool +} + +type CommonConn struct { + net.Conn + UseAES bool + Client *ClientInstance + UnitedKey []byte + PreWrite []byte + AEAD *AEAD + PeerAEAD *AEAD + PeerPadding []byte + rawInput bytes.Buffer + input bytes.Reader +} + +func NewCommonConn(conn net.Conn, useAES bool) *CommonConn { + return &CommonConn{ + Conn: conn, + UseAES: useAES, + } +} + +func (c *CommonConn) Write(b []byte) (int, error) { + if len(b) == 0 { + return 0, nil + } + outBytes := OutBytesPool.Get().([]byte) + defer OutBytesPool.Put(outBytes) + for n := 0; n < len(b); { + b := b[n:] + if len(b) > 8192 { + b = b[:8192] // for avoiding another copy() in peer's Read() + } + n += len(b) + headerAndData := outBytes[:5+len(b)+16] + EncodeHeader(headerAndData, len(b)+16) + max := false + if bytes.Equal(c.AEAD.Nonce[:], MaxNonce) { + max = true + } + c.AEAD.Seal(headerAndData[:5], nil, b, headerAndData[:5]) + if max { + c.AEAD = NewAEAD(headerAndData, c.UnitedKey, c.UseAES) + } + if c.PreWrite != nil { + headerAndData = append(c.PreWrite, headerAndData...) + c.PreWrite = nil + } + if _, err := c.Conn.Write(headerAndData); err != nil { + return 0, err + } + } + return len(b), nil +} + +func (c *CommonConn) Read(b []byte) (int, error) { + if len(b) == 0 { + return 0, nil + } + if c.PeerAEAD == nil { // client's 0-RTT + serverRandom := make([]byte, 16) + if _, err := io.ReadFull(c.Conn, serverRandom); err != nil { + return 0, err + } + c.PeerAEAD = NewAEAD(serverRandom, c.UnitedKey, c.UseAES) + if xorConn, ok := c.Conn.(*XorConn); ok { + xorConn.PeerCTR = NewCTR(c.UnitedKey, serverRandom) + } + } + if c.PeerPadding != nil { // client's 1-RTT + if _, err := io.ReadFull(c.Conn, c.PeerPadding); err != nil { + return 0, err + } + if _, err := c.PeerAEAD.Open(c.PeerPadding[:0], nil, c.PeerPadding, nil); err != nil { + return 0, err + } + c.PeerPadding = nil + } + if c.input.Len() > 0 { + return c.input.Read(b) + } + peerHeader := [5]byte{} + if _, err := io.ReadFull(c.Conn, peerHeader[:]); err != nil { + return 0, err + } + l, err := DecodeHeader(peerHeader[:]) // l: 17~17000 + if err != nil { + if c.Client != nil && errors.Is(err, ErrInvalidHeader) { // client's 0-RTT + c.Client.RWLock.Lock() + if bytes.HasPrefix(c.UnitedKey, c.Client.PfsKey) { + c.Client.Expire = time.Now() // expired + } + c.Client.RWLock.Unlock() + return 0, E.New("new handshake needed") + } + return 0, err + } + c.Client = nil + if c.rawInput.Cap() < l { + c.rawInput.Grow(l) // no need to use sync.Pool, because we are always reading + } + peerData := c.rawInput.Bytes()[:l] + if _, err := io.ReadFull(c.Conn, peerData); err != nil { + return 0, err + } + dst := peerData[:l-16] + if len(dst) <= len(b) { + dst = b[:len(dst)] // avoids another copy() + } + var newAEAD *AEAD + if bytes.Equal(c.PeerAEAD.Nonce[:], MaxNonce) { + newAEAD = NewAEAD(append(peerHeader[:], peerData...), c.UnitedKey, c.UseAES) + } + _, err = c.PeerAEAD.Open(dst[:0], nil, peerData, peerHeader[:]) + if newAEAD != nil { + c.PeerAEAD = newAEAD + } + if err != nil { + return 0, err + } + if len(dst) > len(b) { + c.input.Reset(dst[copy(b, dst):]) + dst = b // for len(dst) + } + return len(dst), nil +} + +// Upstream returns the underlying connection, allowing Vision to unwrap and access the TLS connection +func (c *CommonConn) Upstream() any { + return c.Conn +} + +func (c *CommonConn) IsEncryptionLayer() bool { + return true +} + +type AEAD struct { + cipher.AEAD + Nonce [12]byte +} + +func NewAEAD(ctx, key []byte, useAES bool) *AEAD { + k := make([]byte, 32) + blake3.DeriveKey(k, string(ctx), key) + var aead cipher.AEAD + if useAES { + block, _ := aes.NewCipher(k) + aead, _ = cipher.NewGCM(block) + } else { + aead, _ = chacha20poly1305.New(k) + } + return &AEAD{AEAD: aead} +} + +func (a *AEAD) Seal(dst, nonce, plaintext, additionalData []byte) []byte { + if nonce == nil { + nonce = IncreaseNonce(a.Nonce[:]) + } + return a.AEAD.Seal(dst, nonce, plaintext, additionalData) +} + +func (a *AEAD) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { + if nonce == nil { + nonce = IncreaseNonce(a.Nonce[:]) + } + return a.AEAD.Open(dst, nonce, ciphertext, additionalData) +} + +func IncreaseNonce(nonce []byte) []byte { + for i := range 12 { + nonce[11-i]++ + if nonce[11-i] != 0 { + break + } + } + return nonce +} + +var MaxNonce = bytes.Repeat([]byte{255}, 12) + +func EncodeLength(l int) []byte { + return []byte{byte(l >> 8), byte(l)} +} + +func DecodeLength(b []byte) int { + return int(b[0])<<8 | int(b[1]) +} + +func EncodeHeader(h []byte, l int) { + h[0] = 23 + h[1] = 3 + h[2] = 3 + h[3] = byte(l >> 8) + h[4] = byte(l) +} + +var ErrInvalidHeader = errors.New("invalid header") + +func DecodeHeader(h []byte) (l int, err error) { + l = int(h[3])<<8 | int(h[4]) + if h[0] != 23 || h[1] != 3 || h[2] != 3 { + l = 0 + } + if l < 17 || l > 17000 { // TODO: TLSv1.3 max length + err = fmt.Errorf("%w: %v", ErrInvalidHeader, h[:5]) // DO NOT CHANGE: relied by client's Read() + } + return +} + +func ParsePadding(padding string, paddingLens, paddingGaps *[][3]int) (err error) { + if padding == "" { + return + } + maxLen := 0 + for i, s := range strings.Split(padding, ".") { + x := strings.Split(s, "-") + if len(x) < 3 || x[0] == "" || x[1] == "" || x[2] == "" { + return E.New("invalid padding lenth/gap parameter: " + s) + } + y := [3]int{} + if y[0], err = strconv.Atoi(x[0]); err != nil { + return + } + if y[1], err = strconv.Atoi(x[1]); err != nil { + return + } + if y[2], err = strconv.Atoi(x[2]); err != nil { + return + } + if i == 0 && (y[0] < 100 || y[1] < 18+17 || y[2] < 18+17) { + return E.New("first padding length must not be smaller than 35") + } + if i%2 == 0 { + *paddingLens = append(*paddingLens, y) + maxLen += max(y[1], y[2]) + } else { + *paddingGaps = append(*paddingGaps, y) + } + } + if maxLen > 18+65535 { + return E.New("total padding length must not be larger than 65553") + } + return +} + +func CreatePadding(paddingLens, paddingGaps [][3]int) (length int, lens []int, gaps []time.Duration) { + if len(paddingLens) == 0 { + paddingLens = [][3]int{{100, 111, 1111}, {50, 0, 3333}} + paddingGaps = [][3]int{{75, 0, 111}} + } + for _, y := range paddingLens { + l := 0 + if y[0] >= int(crypto.RandBetween(0, 100)) { + l = int(crypto.RandBetween(int64(y[1]), int64(y[2]))) + } + lens = append(lens, l) + length += l + } + for _, y := range paddingGaps { + g := 0 + if y[0] >= int(crypto.RandBetween(0, 100)) { + g = int(crypto.RandBetween(int64(y[1]), int64(y[2]))) + } + gaps = append(gaps, time.Duration(g)*time.Millisecond) + } + return +} diff --git a/protocol/vless/encryption/server.go b/protocol/vless/encryption/server.go new file mode 100644 index 0000000000..6e25f166c1 --- /dev/null +++ b/protocol/vless/encryption/server.go @@ -0,0 +1,336 @@ +package encryption + +import ( + "bytes" + "crypto/cipher" + "crypto/ecdh" + "crypto/mlkem" + "crypto/rand" + "fmt" + "io" + "net" + "sync" + "time" + + "github.com/sagernet/sing-box/common/xray/crypto" + E "github.com/sagernet/sing/common/exceptions" + "lukechampine.com/blake3" +) + +type ServerSession struct { + PfsKey []byte + NfsKeys sync.Map +} + +type ServerInstance struct { + NfsSKeys []any + NfsPKeysBytes [][]byte + Hash32s [][32]byte + RelaysLength int + XorMode uint32 + SecondsFrom int64 + SecondsTo int64 + PaddingLens [][3]int + PaddingGaps [][3]int + + RWLock sync.RWMutex + Closed bool + Lasts map[int64][16]byte + Tickets [][16]byte + Sessions map[[16]byte]*ServerSession +} + +func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode uint32, secondsFrom, secondsTo int64, padding string) (err error) { + if i.NfsSKeys != nil { + return E.New("already initialized") + } + l := len(nfsSKeysBytes) + if l == 0 { + return E.New("empty nfsSKeysBytes") + } + i.NfsSKeys = make([]any, l) + i.NfsPKeysBytes = make([][]byte, l) + i.Hash32s = make([][32]byte, l) + for j, k := range nfsSKeysBytes { + if len(k) == 32 { + if i.NfsSKeys[j], err = ecdh.X25519().NewPrivateKey(k); err != nil { + return + } + i.NfsPKeysBytes[j] = i.NfsSKeys[j].(*ecdh.PrivateKey).PublicKey().Bytes() + i.RelaysLength += 32 + 32 + } else { + if i.NfsSKeys[j], err = mlkem.NewDecapsulationKey768(k); err != nil { + return + } + i.NfsPKeysBytes[j] = i.NfsSKeys[j].(*mlkem.DecapsulationKey768).EncapsulationKey().Bytes() + i.RelaysLength += 1088 + 32 + } + i.Hash32s[j] = blake3.Sum256(i.NfsPKeysBytes[j]) + } + i.RelaysLength -= 32 + i.XorMode = xorMode + i.SecondsFrom = secondsFrom + i.SecondsTo = secondsTo + err = ParsePadding(padding, &i.PaddingLens, &i.PaddingGaps) + if err != nil { + return + } + if i.SecondsFrom > 0 || i.SecondsTo > 0 { + i.Lasts = make(map[int64][16]byte) + i.Tickets = make([][16]byte, 0, 1024) + i.Sessions = make(map[[16]byte]*ServerSession) + go func() { + for { + time.Sleep(time.Minute) + i.RWLock.Lock() + if i.Closed { + i.RWLock.Unlock() + return + } + minute := time.Now().Unix() / 60 + last := i.Lasts[minute] + delete(i.Lasts, minute) + delete(i.Lasts, minute-1) // for insurance + if last != [16]byte{} { + for j, ticket := range i.Tickets { + delete(i.Sessions, ticket) + if ticket == last { + i.Tickets = i.Tickets[j+1:] + break + } + } + } + i.RWLock.Unlock() + } + }() + } + return +} + +func (i *ServerInstance) Close() (err error) { + i.RWLock.Lock() + i.Closed = true + i.RWLock.Unlock() + return +} + +func (i *ServerInstance) IsXorMode() bool { + return i.XorMode > 0 +} + +func (i *ServerInstance) IsFullRandomXorMode() bool { + return i.XorMode == 2 +} + +func (i *ServerInstance) Handshake(conn net.Conn, fallback *[]byte) (*CommonConn, error) { + if i.NfsSKeys == nil { + return nil, E.New("uninitialized") + } + c := NewCommonConn(conn, true) + + ivAndRelays := make([]byte, 16+i.RelaysLength) + if _, err := io.ReadFull(conn, ivAndRelays); err != nil { + return nil, err + } + if fallback != nil { + *fallback = append(*fallback, ivAndRelays...) + } + iv := ivAndRelays[:16] + relays := ivAndRelays[16:] + var nfsKey []byte + var lastCTR cipher.Stream + for j, k := range i.NfsSKeys { + if lastCTR != nil { + lastCTR.XORKeyStream(relays, relays[:32]) // recover this relay + } + var index = 32 + if _, ok := k.(*mlkem.DecapsulationKey768); ok { + index = 1088 + } + if i.XorMode > 0 { + NewCTR(i.NfsPKeysBytes[j], iv).XORKeyStream(relays, relays[:index]) // we don't use buggy elligator2, because we have PSK :) + } + if k, ok := k.(*ecdh.PrivateKey); ok { + publicKey, err := ecdh.X25519().NewPublicKey(relays[:index]) + if err != nil { + return nil, err + } + if publicKey.Bytes()[31] > 127 { // we just don't want the observer can change even one bit without breaking the connection, though it has nothing to do with security + return nil, E.New("the highest bit of the last byte of the peer-sent X25519 public key is not 0") + } + nfsKey, err = k.ECDH(publicKey) + if err != nil { + return nil, err + } + } + if k, ok := k.(*mlkem.DecapsulationKey768); ok { + var err error + nfsKey, err = k.Decapsulate(relays[:index]) + if err != nil { + return nil, err + } + } + if j == len(i.NfsSKeys)-1 { + break + } + relays = relays[index:] + lastCTR = NewCTR(nfsKey, iv) + lastCTR.XORKeyStream(relays, relays[:32]) + if !bytes.Equal(relays[:32], i.Hash32s[j+1][:]) { + return nil, E.New("unexpected hash32: " + fmt.Sprintf("%v", relays[:32])) + } + relays = relays[32:] + } + nfsAEAD := NewAEAD(iv, nfsKey, c.UseAES) + + encryptedLength := make([]byte, 18) + if _, err := io.ReadFull(conn, encryptedLength); err != nil { + return nil, err + } + if fallback != nil { + *fallback = append(*fallback, encryptedLength...) + } + decryptedLength := make([]byte, 2) + if _, err := nfsAEAD.Open(decryptedLength[:0], nil, encryptedLength, nil); err != nil { + c.UseAES = !c.UseAES + nfsAEAD = NewAEAD(iv, nfsKey, c.UseAES) + if _, err := nfsAEAD.Open(decryptedLength[:0], nil, encryptedLength, nil); err != nil { + return nil, err + } + } + if fallback != nil { + *fallback = nil + } + length := DecodeLength(decryptedLength) + + if length == 32 { + if i.SecondsFrom == 0 && i.SecondsTo == 0 { + return nil, E.New("0-RTT is not allowed") + } + encryptedTicket := make([]byte, 32) + if _, err := io.ReadFull(conn, encryptedTicket); err != nil { + return nil, err + } + ticket, err := nfsAEAD.Open(nil, nil, encryptedTicket, nil) + if err != nil { + return nil, err + } + i.RWLock.RLock() + s := i.Sessions[[16]byte(ticket)] + i.RWLock.RUnlock() + if s == nil { + noises := make([]byte, crypto.RandBetween(1279, 2279)) // matches 1-RTT's server hello length for "random", though it is not important, just for example + var err error + for err == nil { + rand.Read(noises) + _, err = DecodeHeader(noises) + } + conn.Write(noises) // make client do new handshake + return nil, E.New("expired ticket") + } + if _, loaded := s.NfsKeys.LoadOrStore([32]byte(nfsKey), true); loaded { // prevents bad client also + return nil, E.New("replay detected") + } + c.UnitedKey = append(s.PfsKey, nfsKey...) // the same nfsKey links the upload & download (prevents server -> client's another request) + c.PreWrite = make([]byte, 16) + rand.Read(c.PreWrite) // always trust yourself, not the client (also prevents being parsed as TLS thus causing false interruption for "native" and "xorpub") + c.AEAD = NewAEAD(c.PreWrite, c.UnitedKey, c.UseAES) + c.PeerAEAD = NewAEAD(encryptedTicket, c.UnitedKey, c.UseAES) // unchangeable ctx (prevents server -> server), and different ctx length for upload / download (prevents client -> client) + if i.XorMode == 2 { + c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, c.PreWrite), NewCTR(c.UnitedKey, iv), 16, 0) // it doesn't matter if the attacker sends client's iv back to the client + } + return c, nil + } + + if length < 1184+32+16 { // client may send more public keys in the future's version + return nil, E.New("too short length") + } + encryptedPfsPublicKey := make([]byte, length) + if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil { + return nil, err + } + if _, err := nfsAEAD.Open(encryptedPfsPublicKey[:0], nil, encryptedPfsPublicKey, nil); err != nil { + return nil, err + } + mlkem768EKey, err := mlkem.NewEncapsulationKey768(encryptedPfsPublicKey[:1184]) + if err != nil { + return nil, err + } + mlkem768Key, encapsulatedPfsKey := mlkem768EKey.Encapsulate() + peerX25519PKey, err := ecdh.X25519().NewPublicKey(encryptedPfsPublicKey[1184 : 1184+32]) + if err != nil { + return nil, err + } + x25519SKey, _ := ecdh.X25519().GenerateKey(rand.Reader) + x25519Key, err := x25519SKey.ECDH(peerX25519PKey) + if err != nil { + return nil, err + } + pfsKey := make([]byte, 32+32) // no more capacity + copy(pfsKey, mlkem768Key) + copy(pfsKey[32:], x25519Key) + pfsPublicKey := append(encapsulatedPfsKey, x25519SKey.PublicKey().Bytes()...) + c.UnitedKey = append(pfsKey, nfsKey...) + c.AEAD = NewAEAD(pfsPublicKey, c.UnitedKey, c.UseAES) + c.PeerAEAD = NewAEAD(encryptedPfsPublicKey[:1184+32], c.UnitedKey, c.UseAES) + + ticket := [16]byte{} + rand.Read(ticket[:]) + var seconds int64 + if i.SecondsTo == 0 { + seconds = i.SecondsFrom * crypto.RandBetween(50, 100) / 100 + } else { + seconds = crypto.RandBetween(i.SecondsFrom, i.SecondsTo) + } + copy(ticket[:], EncodeLength(int(seconds))) + if seconds > 0 { + i.RWLock.Lock() + i.Lasts[(time.Now().Unix()+max(i.SecondsFrom, i.SecondsTo))/60+2] = ticket + i.Tickets = append(i.Tickets, ticket) + i.Sessions[ticket] = &ServerSession{PfsKey: pfsKey} + i.RWLock.Unlock() + } + + pfsKeyExchangeLength := 1088 + 32 + 16 + encryptedTicketLength := 32 + paddingLength, paddingLens, paddingGaps := CreatePadding(i.PaddingLens, i.PaddingGaps) + serverHello := make([]byte, pfsKeyExchangeLength+encryptedTicketLength+paddingLength) + nfsAEAD.Seal(serverHello[:0], MaxNonce, pfsPublicKey, nil) + c.AEAD.Seal(serverHello[:pfsKeyExchangeLength], nil, ticket[:], nil) + padding := serverHello[pfsKeyExchangeLength+encryptedTicketLength:] + c.AEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil) + c.AEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil) + + paddingLens[0] = pfsKeyExchangeLength + encryptedTicketLength + paddingLens[0] + for i, l := range paddingLens { // sends padding in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control + if l > 0 { + if _, err := conn.Write(serverHello[:l]); err != nil { + return nil, err + } + serverHello = serverHello[l:] + } + if len(paddingGaps) > i { + time.Sleep(paddingGaps[i]) + } + } + + // important: allows client sends padding slowly, eliminating 1-RTT's traffic pattern + if _, err := io.ReadFull(conn, encryptedLength); err != nil { + return nil, err + } + if _, err := nfsAEAD.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil { + return nil, err + } + encryptedPadding := make([]byte, DecodeLength(encryptedLength[:2])) + if _, err := io.ReadFull(conn, encryptedPadding); err != nil { + return nil, err + } + if _, err := nfsAEAD.Open(encryptedPadding[:0], nil, encryptedPadding, nil); err != nil { + return nil, err + } + + if i.XorMode == 2 { + c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, ticket[:]), NewCTR(c.UnitedKey, iv), 0, 0) + } + return c, nil +} diff --git a/protocol/vless/encryption/xor.go b/protocol/vless/encryption/xor.go new file mode 100644 index 0000000000..ac7bd58deb --- /dev/null +++ b/protocol/vless/encryption/xor.go @@ -0,0 +1,101 @@ +package encryption + +import ( + "crypto/aes" + "crypto/cipher" + "net" + + "lukechampine.com/blake3" +) + +func NewCTR(key, iv []byte) cipher.Stream { + k := make([]byte, 32) + blake3.DeriveKey(k, "VLESS", key) // avoids using key directly + block, _ := aes.NewCipher(k) + return cipher.NewCTR(block, iv) +} + +type XorConn struct { + net.Conn + CTR cipher.Stream + PeerCTR cipher.Stream + OutSkip int + OutHeader []byte + InSkip int + InHeader []byte +} + +func NewXorConn(conn net.Conn, ctr, peerCTR cipher.Stream, outSkip, inSkip int) *XorConn { + return &XorConn{ + Conn: conn, + CTR: ctr, + PeerCTR: peerCTR, + OutSkip: outSkip, + OutHeader: make([]byte, 0, 5), // important + InSkip: inSkip, + InHeader: make([]byte, 0, 5), // important + } +} + +func (c *XorConn) Write(b []byte) (int, error) { + if len(b) == 0 { + return 0, nil + } + for p := b; ; { + if len(p) <= c.OutSkip { + c.OutSkip -= len(p) + break + } + p = p[c.OutSkip:] + c.OutSkip = 0 + need := 5 - len(c.OutHeader) + if len(p) < need { + c.OutHeader = append(c.OutHeader, p...) + c.CTR.XORKeyStream(p, p) + break + } + c.OutSkip, _ = DecodeHeader(append(c.OutHeader, p[:need]...)) + c.OutHeader = c.OutHeader[:0] + c.CTR.XORKeyStream(p[:need], p[:need]) + p = p[need:] + } + if _, err := c.Conn.Write(b); err != nil { + return 0, err + } + return len(b), nil +} + +func (c *XorConn) Read(b []byte) (int, error) { + if len(b) == 0 { + return 0, nil + } + n, err := c.Conn.Read(b) + for p := b[:n]; ; { + if len(p) <= c.InSkip { + c.InSkip -= len(p) + break + } + p = p[c.InSkip:] + c.InSkip = 0 + need := 5 - len(c.InHeader) + if len(p) < need { + c.PeerCTR.XORKeyStream(p, p) + c.InHeader = append(c.InHeader, p...) + break + } + c.PeerCTR.XORKeyStream(p[:need], p[:need]) + c.InSkip, _ = DecodeHeader(append(c.InHeader, p[:need]...)) + c.InHeader = c.InHeader[:0] + p = p[need:] + } + return n, err +} + +// Upstream returns the underlying connection, allowing Vision to unwrap and access the TLS connection +func (c *XorConn) Upstream() any { + return c.Conn +} + +func (c *XorConn) IsEncryptionLayer() bool { + return true +} diff --git a/protocol/vless/inbound.go b/protocol/vless/inbound.go new file mode 100644 index 0000000000..62ae39840f --- /dev/null +++ b/protocol/vless/inbound.go @@ -0,0 +1,336 @@ +package vless + +import ( + "context" + "encoding/base64" + "net" + "os" + "strconv" + "strings" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/mux" + "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/common/uot" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/protocol/vless/encryption" + "github.com/sagernet/sing-box/transport/v2ray" + "github.com/sagernet/sing-vmess/packetaddr" + "github.com/sagernet/sing-vmess/vless" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/auth" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.VLESSInboundOptions](registry, C.TypeVLESS, NewInbound) +} + +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) + +type Inbound struct { + inbound.Adapter + ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener + users []option.VLESSUser + service *vless.Service[int] + tlsConfig tls.ServerConfig + transport adapter.V2RayServerTransport + decryption *encryption.ServerInstance +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeVLESS, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, + users: options.Users, + } + var err error + inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) + if err != nil { + return nil, err + } + service := vless.NewService[int](logger, adapter.NewUpstreamContextHandler(inbound.newConnectionEx, inbound.newPacketConnectionEx)) + service.UpdateUsers(common.MapIndexed(inbound.users, func(index int, _ option.VLESSUser) int { + return index + }), common.Map(inbound.users, func(it option.VLESSUser) string { + return it.UUID + }), common.Map(inbound.users, func(it option.VLESSUser) string { + return it.Flow + })) + inbound.service = service + if options.TLS != nil { + inbound.tlsConfig, err = tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + } + if options.Transport != nil { + inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*inboundTransportHandler)(inbound)) + if err != nil { + return nil, E.Cause(err, "create server transport: ", options.Transport.Type) + } + } + // Parse decryption configuration + if options.Decryption != "" && options.Decryption != "none" { + decryptionConfig, err := parseServerDecryption(options.Decryption) + if err != nil { + return nil, E.Cause(err, "parse decryption") + } + inbound.decryption = &encryption.ServerInstance{} + if err := inbound.decryption.Init(decryptionConfig.keys, decryptionConfig.xorMode, decryptionConfig.secondsFrom, decryptionConfig.secondsTo, decryptionConfig.padding); err != nil { + return nil, E.Cause(err, "initialize decryption") + } + logger.Debug("decryption initialized with ", len(decryptionConfig.keys), " keys xorMode=", decryptionConfig.xorMode, " secondsFrom=", decryptionConfig.secondsFrom, " secondsTo=", decryptionConfig.secondsTo, " padding=", decryptionConfig.padding) + } + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + }) + return inbound, nil +} + +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + if h.tlsConfig != nil { + err := h.tlsConfig.Start() + if err != nil { + return err + } + } + if h.transport == nil { + return h.listener.Start() + } + if common.Contains(h.transport.Network(), N.NetworkTCP) { + tcpListener, err := h.listener.ListenTCP() + if err != nil { + return err + } + go func() { + sErr := h.transport.Serve(tcpListener) + if sErr != nil && !E.IsClosed(sErr) { + h.logger.Error("transport serve error: ", sErr) + } + }() + } + if common.Contains(h.transport.Network(), N.NetworkUDP) { + udpConn, err := h.listener.ListenUDP() + if err != nil { + return err + } + go func() { + sErr := h.transport.ServePacket(udpConn) + if sErr != nil && !E.IsClosed(sErr) { + h.logger.Error("transport serve error: ", sErr) + } + }() + } + return nil +} + +func (h *Inbound) Close() error { + if h.decryption != nil { + h.decryption.Close() + } + return common.Close( + h.service, + h.listener, + h.tlsConfig, + h.transport, + ) +} + +func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + canSplice := h.transport == nil + if canSplice && h.decryption != nil && h.decryption.IsFullRandomXorMode() { + canSplice = false + } + h.newConnectionExInternal(ctx, conn, metadata, onClose, canSplice) +} + +func (h *Inbound) newConnectionExInternal(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc, canSplice bool) { + if h.tlsConfig != nil && h.transport == nil { + tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig) + if err != nil { + N.CloseOnHandshakeFailure(conn, onClose, err) + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source, ": TLS handshake")) + return + } + conn = tlsConn + } + // Apply decryption if configured + if h.decryption != nil { + encConn, err := h.decryption.Handshake(conn, nil) + if err != nil { + N.CloseOnHandshakeFailure(conn, onClose, err) + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source, ": encryption handshake")) + return + } + conn = encConn + } + err := h.service.NewConnectionWithOptions(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose, canSplice) + if err != nil { + N.CloseOnHandshakeFailure(conn, onClose, err) + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} + +func (h *Inbound) newConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + userIndex, loaded := auth.UserFromContext[int](ctx) + if !loaded { + N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid) + return + } + user := h.users[userIndex].Name + if user == "" { + user = F.ToString(userIndex) + } else { + metadata.User = user + } + h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (h *Inbound) newPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + userIndex, loaded := auth.UserFromContext[int](ctx) + if !loaded { + N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid) + return + } + user := h.users[userIndex].Name + if user == "" { + user = F.ToString(userIndex) + } else { + metadata.User = user + } + if metadata.Destination.Fqdn == packetaddr.SeqPacketMagicAddress { + metadata.Destination = M.Socksaddr{} + conn = packetaddr.NewConn(bufio.NewNetPacketConn(conn), metadata.Destination) + h.logger.InfoContext(ctx, "[", user, "] inbound packet addr connection") + } else { + h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) + } + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} + +type serverDecryptionConfig struct { + keys [][]byte + xorMode uint32 + secondsFrom int64 + secondsTo int64 + padding string +} + +func parseServerDecryption(raw string) (serverDecryptionConfig, error) { + var cfg serverDecryptionConfig + raw = strings.TrimSpace(raw) + if raw == "" { + return cfg, E.New("empty decryption string") + } + parts := strings.Split(raw, ".") + if len(parts) < 4 { + return cfg, E.New("invalid decryption string: missing components") + } + if parts[0] != "mlkem768x25519plus" { + return cfg, E.New("unsupported decryption prefix: ", parts[0]) + } + switch parts[1] { + case "native": + cfg.xorMode = 0 + case "xorpub": + cfg.xorMode = 1 + case "random": + cfg.xorMode = 2 + default: + return cfg, E.New("unknown decryption mode: ", parts[1]) + } + + secondsToken := strings.TrimSpace(parts[2]) + if secondsToken == "" { + return cfg, E.New("invalid decryption seconds segment") + } + trimmed := strings.TrimSuffix(secondsToken, "s") + if trimmed == "" { + return cfg, E.New("invalid decryption seconds segment") + } + values := strings.SplitN(trimmed, "-", 2) + secondsFrom, err := strconv.ParseInt(values[0], 10, 64) + if err != nil { + return cfg, E.Cause(err, "parse decryption seconds_from") + } + cfg.secondsFrom = secondsFrom + if len(values) == 2 && values[1] != "" { + secondsTo, err := strconv.ParseInt(values[1], 10, 64) + if err != nil { + return cfg, E.Cause(err, "parse decryption seconds_to") + } + cfg.secondsTo = secondsTo + } + + paddingPhase := true + var paddingParts []string + for _, segment := range parts[3:] { + segment = strings.TrimSpace(segment) + if segment == "" { + return cfg, E.New("invalid empty segment in decryption string") + } + if data, err := base64.RawURLEncoding.DecodeString(segment); err == nil { + if len(data) == 32 || len(data) == 64 { + cfg.keys = append(cfg.keys, data) + paddingPhase = false + continue + } + return cfg, E.New("invalid decryption key length: ", len(data)) + } + if paddingPhase { + paddingParts = append(paddingParts, segment) + continue + } + return cfg, E.New("invalid decryption key: ", segment) + } + if len(cfg.keys) == 0 { + return cfg, E.New("no valid decryption keys found in decryption string") + } + if len(paddingParts) > 0 { + cfg.padding = strings.Join(paddingParts, ".") + } + return cfg, nil +} + +var _ adapter.V2RayServerTransportHandler = (*inboundTransportHandler)(nil) + +type inboundTransportHandler Inbound + +func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Source = source + metadata.Destination = destination + //nolint:staticcheck + metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) + (*Inbound)(h).newConnectionExInternal(ctx, conn, metadata, onClose, false) +} diff --git a/protocol/vless/outbound.go b/protocol/vless/outbound.go new file mode 100644 index 0000000000..23656ca790 --- /dev/null +++ b/protocol/vless/outbound.go @@ -0,0 +1,465 @@ +package vless + +import ( + "context" + stdtls "crypto/tls" + "encoding/base64" + "net" + "reflect" + "strings" + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/mux" + "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/common/vision" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/protocol/vless/encryption" + "github.com/sagernet/sing-box/transport/v2ray" + "github.com/sagernet/sing-vmess/packetaddr" + "github.com/sagernet/sing-vmess/vless" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.VLESSOutboundOptions](registry, C.TypeVLESS, NewOutbound) +} + +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger + dialer N.Dialer + client *vless.Client + serverAddr M.Socksaddr + multiplexDialer *mux.Client + tlsConfig tls.Config + transport adapter.V2RayClientTransport + packetAddr bool + xudp bool + encryption *encryption.ClientInstance + vision bool +} + +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSOutboundOptions) (adapter.Outbound, error) { + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) + if err != nil { + return nil, err + } + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeVLESS, tag, options.Network.Build(), options.DialerOptions), + logger: logger, + dialer: outboundDialer, + serverAddr: options.ServerOptions.Build(), + vision: strings.HasPrefix(options.Flow, "xtls-rprx-vision"), + } + if options.TLS != nil { + outbound.tlsConfig, err = tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + } + if options.Transport != nil { + outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig) + if err != nil { + return nil, E.Cause(err, "create client transport: ", options.Transport.Type) + } + } + if options.PacketEncoding == nil { + outbound.xudp = true + } else { + switch *options.PacketEncoding { + case "": + case "packetaddr": + outbound.packetAddr = true + case "xudp": + outbound.xudp = true + default: + return nil, E.New("unknown packet encoding: ", options.PacketEncoding) + } + } + // Parse encryption configuration + if options.Encryption != "" && options.Encryption != "none" { + encryptionConfig, err := parseClientEncryption(options.Encryption) + if err != nil { + return nil, E.Cause(err, "parse encryption") + } + outbound.encryption = &encryption.ClientInstance{} + if err := outbound.encryption.Init(encryptionConfig.keys, encryptionConfig.xorMode, encryptionConfig.seconds, encryptionConfig.padding); err != nil { + return nil, E.Cause(err, "initialize encryption") + } + logger.Debug("encryption initialized: keys=", len(encryptionConfig.keys), " xorMode=", encryptionConfig.xorMode, " seconds=", encryptionConfig.seconds, " padding=", encryptionConfig.padding) + } + + muxOpts := common.PtrValueOrDefault(options.Multiplex) + if muxOpts.Enabled { + options.Flow = "" + } + outbound.client, err = vless.NewClient(options.UUID, options.Flow, logger) + if err != nil { + return nil, err + } + outbound.multiplexDialer, err = mux.NewClientWithOptions((*vlessDialer)(outbound), logger, muxOpts) + if err != nil { + return nil, err + } + return outbound, nil +} + +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + if h.multiplexDialer == nil { + switch N.NetworkName(network) { + case N.NetworkTCP: + h.logger.InfoContext(ctx, "outbound connection to ", destination) + case N.NetworkUDP: + h.logger.InfoContext(ctx, "outbound packet connection to ", destination) + } + return (*vlessDialer)(h).DialContext(ctx, network, destination) + } else { + switch N.NetworkName(network) { + case N.NetworkTCP: + h.logger.InfoContext(ctx, "outbound multiplex connection to ", destination) + case N.NetworkUDP: + h.logger.InfoContext(ctx, "outbound multiplex packet connection to ", destination) + } + return h.multiplexDialer.DialContext(ctx, network, destination) + } +} + +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + if h.multiplexDialer == nil { + h.logger.InfoContext(ctx, "outbound packet connection to ", destination) + return (*vlessDialer)(h).ListenPacket(ctx, destination) + } else { + h.logger.InfoContext(ctx, "outbound multiplex packet connection to ", destination) + return h.multiplexDialer.ListenPacket(ctx, destination) + } +} + +func (h *Outbound) InterfaceUpdated() { + if h.transport != nil { + h.transport.Close() + } + if h.multiplexDialer != nil { + h.multiplexDialer.Reset() + } +} + +func (h *Outbound) Close() error { + return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport) +} + +type vlessDialer Outbound + +func (h *vlessDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() + metadata.Destination = destination + var conn net.Conn + var baseConn net.Conn + var hookOnce sync.Once + if h.vision { + ctx = vision.WithHook(ctx, func(tlsConn net.Conn) { + if tlsConn == nil || !isVisionTLSConn(tlsConn) { + return + } + hookOnce.Do(func() { + baseConn = tlsConn + }) + }) + } + var err error + if h.transport != nil { + conn, err = h.transport.DialContext(ctx) + if err == nil && h.vision { + if baseConn == nil { + // Only set baseConn if the transport delivered a TLS-capable connection + if isVisionTLSConn(conn) { + h.logger.Warn("Vision enabled but hook was not called by transport, using fallback") + baseConn = conn + } + } + } + } else { + conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr) + if err == nil && h.tlsConfig != nil { + conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig) + if err == nil && h.vision && baseConn == nil { + baseConn = conn + } + } + } + if err != nil { + return nil, err + } + + // Apply encryption if configured + if h.encryption != nil { + conn, err = h.encryption.Handshake(conn) + if err != nil { + return nil, E.Cause(err, "encryption handshake") + } + } + + // For Vision: wrap the connection to expose the TLS/encryption connection for vless client + var visionBaseConn net.Conn // The connection to pass to Vision (TLS or encryption layer) + var visionCanSplice bool + if h.vision { + isRAWTransport := h.transport == nil + + if baseConn != nil && !isVisionTLSConn(baseConn) { + baseConn = nil + } + if baseConn != nil { + // Has TLS/Reality: use baseConn (TLS connection) + visionBaseConn = baseConn + visionCanSplice = isRAWTransport + conn = newVisionConnWrapper(conn, baseConn) + } else if h.encryption != nil { + // Only has encryption (no TLS/Reality): use encryption layer itself + encConn := findEncryptionLayer(conn) + if encConn != nil { + visionBaseConn = encConn + if h.encryption.IsFullRandomXorMode() { + visionCanSplice = false + } else { + visionCanSplice = isRAWTransport + } + conn = newVisionConnWrapper(conn, encConn) + } else { + return nil, E.New("Vision: failed to find encryption layer") + } + } else { + return nil, E.New("Vision requires either TLS/Reality or Encryption") + } + } + + switch N.NetworkName(network) { + case N.NetworkTCP: + h.logger.InfoContext(ctx, "outbound connection to ", destination) + if h.vision && visionBaseConn != nil { + // For Vision, we need to pass the base connection (TLS or encryption layer) + // to prepareConn so it can properly initialize VisionConn + return h.client.DialEarlyConnWithOptions(conn, visionBaseConn, destination, visionCanSplice) + } + return h.client.DialEarlyConn(conn, destination) + case N.NetworkUDP: + h.logger.InfoContext(ctx, "outbound packet connection to ", destination) + if h.xudp { + return h.client.DialEarlyXUDPPacketConn(conn, destination) + } else if h.packetAddr { + if destination.IsFqdn() { + return nil, E.New("packetaddr: domain destination is not supported") + } + packetConn, err := h.client.DialEarlyPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}) + if err != nil { + return nil, err + } + return bufio.NewBindPacketConn(packetaddr.NewConn(packetConn, destination), destination), nil + } else { + return h.client.DialEarlyPacketConn(conn, destination) + } + default: + return nil, E.Extend(N.ErrUnknownNetwork, network) + } +} + +func (h *vlessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + h.logger.InfoContext(ctx, "outbound packet connection to ", destination) + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() + metadata.Destination = destination + var conn net.Conn + var err error + if h.transport != nil { + conn, err = h.transport.DialContext(ctx) + } else { + conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr) + if err == nil && h.tlsConfig != nil { + conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig) + } + } + if err != nil { + common.Close(conn) + return nil, err + } + // Apply encryption if configured + if h.encryption != nil { + conn, err = h.encryption.Handshake(conn) + if err != nil { + common.Close(conn) + return nil, E.Cause(err, "encryption handshake") + } + } + if h.xudp { + return h.client.DialEarlyXUDPPacketConn(conn, destination) + } else if h.packetAddr { + if destination.IsFqdn() { + return nil, E.New("packetaddr: domain destination is not supported") + } + conn, err := h.client.DialEarlyPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}) + if err != nil { + return nil, err + } + return packetaddr.NewConn(conn, destination), nil + } else { + return h.client.DialEarlyPacketConn(conn, destination) + } +} + +type visionConnWrapper struct { + net.Conn + upstream net.Conn +} + +var ( + _ N.ReaderWithUpstream = (*visionConnWrapper)(nil) + _ N.WriterWithUpstream = (*visionConnWrapper)(nil) + _ common.WithUpstream = (*visionConnWrapper)(nil) +) + +func newVisionConnWrapper(conn net.Conn, upstream net.Conn) net.Conn { + if upstream == nil || conn == nil || conn == upstream { + return conn + } + return &visionConnWrapper{ + Conn: conn, + upstream: upstream, + } +} + +func (c *visionConnWrapper) Upstream() any { + return c.upstream +} + +func (c *visionConnWrapper) ReaderReplaceable() bool { + if replacer, ok := c.Conn.(N.ReaderWithUpstream); ok { + return replacer.ReaderReplaceable() + } + return true +} + +func (c *visionConnWrapper) WriterReplaceable() bool { + if replacer, ok := c.Conn.(N.WriterWithUpstream); ok { + return replacer.WriterReplaceable() + } + return true +} + +// isVisionTLSConn returns true when the provided connection exposes TLS semantics Vision expects. +func isVisionTLSConn(conn net.Conn) bool { + if conn == nil { + return false + } + if _, ok := conn.(interface{ ConnectionState() stdtls.ConnectionState }); ok { + return true + } + if _, ok := conn.(interface{ Handshake() error }); ok { + return true + } + connType := reflect.TypeOf(conn) + if connType == nil { + return false + } + if connType.Kind() == reflect.Ptr { + pkgPath := connType.Elem().PkgPath() + if pkgPath == "crypto/tls" || strings.Contains(pkgPath, "utls") || strings.Contains(pkgPath, "shadowtls") { + return true + } + } + return false +} + +func findEncryptionLayer(conn net.Conn) net.Conn { + for conn != nil { + if enc, ok := conn.(encryption.EncryptionConn); ok && enc.IsEncryptionLayer() { + return conn + } + if upstream, ok := conn.(common.WithUpstream); ok { + if next := upstream.Upstream(); next != nil { + if nextConn, ok := next.(net.Conn); ok { + conn = nextConn + continue + } + } + } + break + } + return nil +} + +type clientEncryptionConfig struct { + keys [][]byte + xorMode uint32 + seconds uint32 + padding string +} + +func parseClientEncryption(raw string) (clientEncryptionConfig, error) { + var cfg clientEncryptionConfig + raw = strings.TrimSpace(raw) + if raw == "" { + return cfg, E.New("empty encryption string") + } + parts := strings.Split(raw, ".") + if len(parts) < 4 { + return cfg, E.New("invalid encryption string: missing components") + } + if parts[0] != "mlkem768x25519plus" { + return cfg, E.New("unsupported encryption prefix: ", parts[0]) + } + switch parts[1] { + case "native": + cfg.xorMode = 0 + case "xorpub": + cfg.xorMode = 1 + case "random": + cfg.xorMode = 2 + default: + return cfg, E.New("unknown encryption mode: ", parts[1]) + } + switch parts[2] { + case "0rtt": + cfg.seconds = 1 + case "1rtt": + cfg.seconds = 0 + default: + return cfg, E.New("unsupported encryption RTT value: ", parts[2]) + } + paddingPhase := true + var paddingParts []string + for _, segment := range parts[3:] { + segment = strings.TrimSpace(segment) + if segment == "" { + return cfg, E.New("invalid empty segment in encryption string") + } + if data, err := base64.RawURLEncoding.DecodeString(segment); err == nil { + if len(data) == 32 || len(data) == 1184 { + cfg.keys = append(cfg.keys, data) + paddingPhase = false + continue + } + return cfg, E.New("invalid encryption key length: ", len(data)) + } + if paddingPhase { + paddingParts = append(paddingParts, segment) + continue + } + return cfg, E.New("invalid encryption key: ", segment) + } + if len(cfg.keys) == 0 { + return cfg, E.New("no valid encryption keys found in encryption string") + } + if len(paddingParts) > 0 { + cfg.padding = strings.Join(paddingParts, ".") + } + return cfg, nil +} diff --git a/inbound/vmess.go b/protocol/vmess/inbound.go similarity index 50% rename from inbound/vmess.go rename to protocol/vmess/inbound.go index 70676bbd8c..8783d3e377 100644 --- a/inbound/vmess.go +++ b/protocol/vmess/inbound.go @@ -1,4 +1,4 @@ -package inbound +package vmess import ( "context" @@ -6,6 +6,8 @@ import ( "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/uot" @@ -17,40 +19,40 @@ import ( "github.com/sagernet/sing-vmess/packetaddr" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" + "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ntp" ) -var ( - _ adapter.Inbound = (*VMess)(nil) - _ adapter.InjectableInbound = (*VMess)(nil) -) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.VMessInboundOptions](registry, C.TypeVMess, NewInbound) +} + +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) -type VMess struct { - myInboundAdapter +type Inbound struct { + inbound.Adapter ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener service *vmess.Service[int] users []option.VMessUser tlsConfig tls.ServerConfig transport adapter.V2RayServerTransport } -func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessInboundOptions) (*VMess, error) { - inbound := &VMess{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeVMess, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - ctx: ctx, - users: options.Users, +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeVMess, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, + users: options.Users, } var err error inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) @@ -64,7 +66,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg if options.Transport != nil && options.Transport.Type != "" { serviceOptions = append(serviceOptions, vmess.ServiceWithDisableHeaderProtection()) } - service := vmess.NewService[int](adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound), serviceOptions...) + service := vmess.NewService[int](adapter.NewUpstreamContextHandler(inbound.newConnectionEx, inbound.newPacketConnectionEx), serviceOptions...) inbound.service = service err = service.UpdateUsers(common.MapIndexed(options.Users, func(index int, it option.VMessUser) int { return index @@ -83,28 +85,40 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg } } if options.Transport != nil { - inbound.transport, err = v2ray.NewServerTransport(ctx, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*vmessTransportHandler)(inbound)) + inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*inboundTransportHandler)(inbound)) if err != nil { return nil, E.Cause(err, "create server transport: ", options.Transport.Type) } } - inbound.connHandler = inbound + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + }) return inbound, nil } -func (h *VMess) Start() error { - err := common.Start( - h.service, - h.tlsConfig, - ) +func (h *Inbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + err := h.service.Start() if err != nil { return err } + if h.tlsConfig != nil { + err = h.tlsConfig.Start() + if err != nil { + return err + } + } if h.transport == nil { - return h.myInboundAdapter.Start() + return h.listener.Start() } if common.Contains(h.transport.Network(), N.NetworkTCP) { - tcpListener, err := h.myInboundAdapter.ListenTCP() + tcpListener, err := h.listener.ListenTCP() if err != nil { return err } @@ -116,7 +130,7 @@ func (h *VMess) Start() error { }() } if common.Contains(h.transport.Network(), N.NetworkUDP) { - udpConn, err := h.myInboundAdapter.ListenUDP() + udpConn, err := h.listener.ListenUDP() if err != nil { return err } @@ -130,39 +144,39 @@ func (h *VMess) Start() error { return nil } -func (h *VMess) Close() error { +func (h *Inbound) Close() error { return common.Close( h.service, - &h.myInboundAdapter, + h.listener, h.tlsConfig, h.transport, ) } -func (h *VMess) newTransportConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - h.injectTCP(conn, metadata) - return nil -} - -func (h *VMess) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - var err error +func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { if h.tlsConfig != nil && h.transport == nil { - conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) + tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig) if err != nil { - return err + N.CloseOnHandshakeFailure(conn, onClose, err) + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source, ": TLS handshake")) + return } + conn = tlsConn + } + err := h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose) + if err != nil { + N.CloseOnHandshakeFailure(conn, onClose, err) + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) } - return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) -} - -func (h *VMess) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return os.ErrInvalid } -func (h *VMess) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { - return os.ErrInvalid + N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid) + return } user := h.users[userIndex].Name if user == "" { @@ -171,13 +185,16 @@ func (h *VMess) newConnection(ctx context.Context, conn net.Conn, metadata adapt metadata.User = user } h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) - return h.router.RouteConnection(ctx, conn, metadata) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) } -func (h *VMess) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) newPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { - return os.ErrInvalid + N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid) + return } user := h.users[userIndex].Name if user == "" { @@ -187,21 +204,25 @@ func (h *VMess) newPacketConnection(ctx context.Context, conn N.PacketConn, meta } if metadata.Destination.Fqdn == packetaddr.SeqPacketMagicAddress { metadata.Destination = M.Socksaddr{} - conn = packetaddr.NewConn(conn.(vmess.PacketConn), metadata.Destination) + conn = packetaddr.NewConn(bufio.NewNetPacketConn(conn), metadata.Destination) h.logger.InfoContext(ctx, "[", user, "] inbound packet addr connection") } else { h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) } - return h.router.RoutePacketConnection(ctx, conn, metadata) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } -var _ adapter.V2RayServerTransportHandler = (*vmessTransportHandler)(nil) +var _ adapter.V2RayServerTransportHandler = (*inboundTransportHandler)(nil) -type vmessTransportHandler VMess +type inboundTransportHandler Inbound -func (t *vmessTransportHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { - return (*VMess)(t).newTransportConnection(ctx, conn, adapter.InboundContext{ - Source: metadata.Source, - Destination: metadata.Destination, - }) +func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Source = source + metadata.Destination = destination + //nolint:staticcheck + metadata.InboundDetour = h.listener.ListenOptions().Detour + //nolint:staticcheck + h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) + (*Inbound)(h).NewConnection(ctx, conn, metadata, onClose) } diff --git a/outbound/vmess.go b/protocol/vmess/outbound.go similarity index 74% rename from outbound/vmess.go rename to protocol/vmess/outbound.go index c7d88b9040..703f06b1bb 100644 --- a/outbound/vmess.go +++ b/protocol/vmess/outbound.go @@ -1,10 +1,11 @@ -package outbound +package vmess import ( "context" "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/tls" @@ -16,47 +17,49 @@ import ( "github.com/sagernet/sing-vmess/packetaddr" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ntp" ) -var _ adapter.Outbound = (*VMess)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.VMessOutboundOptions](registry, C.TypeVMess, NewOutbound) +} -type VMess struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger dialer N.Dialer client *vmess.Client serverAddr M.Socksaddr multiplexDialer *mux.Client tlsConfig tls.Config + tlsDialer tls.Dialer transport adapter.V2RayClientTransport packetAddr bool xudp bool } -func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (*VMess, error) { - outboundDialer, err := dialer.New(router, options.DialerOptions) +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (adapter.Outbound, error) { + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } - outbound := &VMess{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeVMess, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeVMess, tag, options.Network.Build(), options.DialerOptions), + logger: logger, dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), } if options.TLS != nil { - outbound.tlsConfig, err = tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS)) + outbound.tlsConfig, err = tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS)) if err != nil { return nil, err } + if outbound.tlsConfig != nil { + outbound.tlsDialer = tls.NewDialer(outboundDialer, outbound.tlsConfig) + } } if options.Transport != nil { outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig) @@ -102,21 +105,20 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg return outbound, nil } -func (h *VMess) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { if h.transport != nil { h.transport.Close() } if h.multiplexDialer != nil { h.multiplexDialer.Reset() } - return } -func (h *VMess) Close() error { +func (h *Outbound) Close() error { return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport) } -func (h *VMess) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { if h.multiplexDialer == nil { switch N.NetworkName(network) { case N.NetworkTCP: @@ -136,7 +138,7 @@ func (h *VMess) DialContext(ctx context.Context, network string, destination M.S } } -func (h *VMess) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { if h.multiplexDialer == nil { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return (*vmessDialer)(h).ListenPacket(ctx, destination) @@ -146,29 +148,20 @@ func (h *VMess) ListenPacket(ctx context.Context, destination M.Socksaddr) (net. } } -func (h *VMess) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *VMess) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewPacketConnection(ctx, h, conn, metadata) -} - -type vmessDialer VMess +type vmessDialer Outbound func (h *vmessDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - ctx, metadata := adapter.AppendContext(ctx) - metadata.Outbound = h.tag + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() metadata.Destination = destination var conn net.Conn var err error if h.transport != nil { conn, err = h.transport.DialContext(ctx) + } else if h.tlsDialer != nil { + conn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr) } else { conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr) - if err == nil && h.tlsConfig != nil { - conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig) - } } if err != nil { common.Close(conn) @@ -185,24 +178,23 @@ func (h *vmessDialer) DialContext(ctx context.Context, network string, destinati } func (h *vmessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - ctx, metadata := adapter.AppendContext(ctx) - metadata.Outbound = h.tag + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() metadata.Destination = destination var conn net.Conn var err error if h.transport != nil { conn, err = h.transport.DialContext(ctx) + } else if h.tlsDialer != nil { + conn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr) } else { conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr) - if err == nil && h.tlsConfig != nil { - conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig) - } } if err != nil { return nil, err } if h.packetAddr { - if destination.IsFqdn() { + if destination.IsDomain() { return nil, E.New("packetaddr: domain destination is not supported") } return packetaddr.NewConn(h.client.DialEarlyPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}), destination), nil diff --git a/protocol/warp/endpoint_warp.go b/protocol/warp/endpoint_warp.go new file mode 100644 index 0000000000..6e9c375e16 --- /dev/null +++ b/protocol/warp/endpoint_warp.go @@ -0,0 +1,203 @@ +package warp + +import ( + "context" + "encoding/json" + "math/rand" + "net" + "strings" + "sync" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/endpoint" + "github.com/sagernet/sing-box/common/cloudflare" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" +) + +func RegisterWARPEndpoint(registry *endpoint.Registry) { + endpoint.Register[option.WARPEndpointOptions](registry, C.TypeWARP, NewWARPEndpoint) +} + +type WARPEndpoint struct { + endpoint.Adapter + endpoint adapter.Endpoint + startHandler func() + + mtx sync.Mutex +} + +func NewWARPEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WARPEndpointOptions) (adapter.Endpoint, error) { + var dependencies []string + if options.Detour != "" { + dependencies = append(dependencies, options.Detour) + } + if options.Profile.Detour != "" { + dependencies = append(dependencies, options.Profile.Detour) + } + warpEndpoint := &WARPEndpoint{ + Adapter: endpoint.NewAdapter(C.TypeWARP, tag, []string{N.NetworkTCP, N.NetworkUDP}, dependencies), + } + uniqueId := options.UniqueIdentifier + if uniqueId == "" { + uniqueId = tag + } + warpEndpoint.mtx.Lock() + warpEndpoint.startHandler = func() { + defer warpEndpoint.mtx.Unlock() + cacheFile := service.FromContext[adapter.CacheFile](ctx) + var config *C.WARPConfig + var err error + if !options.Profile.Recreate && cacheFile != nil && cacheFile.StoreWARPConfig() { + savedProfile := cacheFile.LoadBinary(uniqueId) + if savedProfile != nil { + if err = json.Unmarshal(savedProfile.Content, &config); err != nil { + logger.ErrorContext(ctx, err) + return + } + } + } + if config == nil && options.WARPConfig != nil { + config = options.WARPConfig + } + if config == nil || config.PrivateKey == "" { + profile, err := GetWarpProfile(ctx, &options.Profile) + if err != nil { + logger.ErrorContext(ctx, err) + return + } + config = &profile.Config + + if cacheFile != nil && cacheFile.StoreWARPConfig() { + content, err := json.Marshal(config) + if err != nil { + logger.ErrorContext(ctx, err) + return + } + cacheFile.SaveBinary(uniqueId, &adapter.SavedBinary{ + LastUpdated: time.Now(), + Content: content, + LastEtag: "", + }) + } + } + peer := config.Peers[0] + hostParts := strings.Split(peer.Endpoint.Host, ":") + peerAddr := hostParts[0] + perrPort := uint16(peer.Endpoint.Ports[rand.Intn(len(peer.Endpoint.Ports))]) + if options.ServerOptions.Server != "" { + peerAddr = options.ServerOptions.Server + } + if options.ServerOptions.ServerPort != 0 { + perrPort = options.ServerOptions.ServerPort + } + if options.AWG != nil && options.AWG.IsAvailble() { + warpEndpoint.endpoint, err = createWARPAwgEndpoint(ctx, router, logger, tag, options, config, peerAddr, perrPort, peer.PublicKey) + } else { + warpEndpoint.endpoint, err = createWARPWireGuardEndpoint(ctx, router, logger, tag, options, config, peerAddr, perrPort, peer.PublicKey) + } + if err != nil { + logger.ErrorContext(ctx, err) + return + } + if err = warpEndpoint.endpoint.Start(adapter.StartStateStart); err != nil { + logger.ErrorContext(ctx, err) + return + } + if err = warpEndpoint.endpoint.Start(adapter.StartStatePostStart); err != nil { + logger.ErrorContext(ctx, err) + return + } + } + return warpEndpoint, nil +} +func GetWarpProfile(ctx context.Context, profile *option.WARPProfile) (*cloudflare.CloudflareProfile, error) { + var dialer N.Dialer + outmanager := service.FromContext[adapter.OutboundManager](ctx) + if profile.Detour != "" && outmanager != nil { + var ok bool + dialer, ok = outmanager.Outbound(profile.Detour) + if !ok { + return nil, E.New("outbound detour not found: ", profile.Detour) + } + + } + cf, err := GetWarpProfileDialer(ctx, dialer, profile) + if err == nil || outmanager == nil { + return cf, nil + } + + for _, dialer := range outmanager.Outbounds() { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + if cf, err := GetWarpProfileDialer(ctx, dialer, profile); err == nil { + return cf, nil + } + } + return nil, err + +} +func GetWarpProfileDialer(ctx context.Context, dialer N.Dialer, profile *option.WARPProfile) (*cloudflare.CloudflareProfile, error) { + api := cloudflare.NewCloudflareApiDetour(dialer) + if profile.AuthToken != "" && profile.ID != "" { + return api.GetProfile(ctx, profile.AuthToken, profile.ID) + + } else { + return api.CreateProfileLicense(ctx, profile.PrivateKey, profile.License) + } +} +func (w *WARPEndpoint) IsReady() bool { + if ok := w.isEndpointInitialized(); !ok { + return false + } + return w.endpoint.IsReady() +} +func (w *WARPEndpoint) Start(stage adapter.StartStage) error { + if stage != adapter.StartStatePostStart { + return nil + } + go w.startHandler() + return nil +} + +func (w *WARPEndpoint) Close() error { + return common.Close(w.endpoint) +} + +func (w *WARPEndpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + if ok := w.isEndpointInitialized(); !ok { + return nil, E.New("endpoint not initialized") + } + return w.endpoint.DialContext(ctx, network, destination) +} + +func (w *WARPEndpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + if ok := w.isEndpointInitialized(); !ok { + return nil, E.New("endpoint not initialized") + } + return w.endpoint.ListenPacket(ctx, destination) +} + +func (w *WARPEndpoint) isEndpointInitialized() bool { + w.mtx.Lock() + defer w.mtx.Unlock() + return w.endpoint != nil +} + +func (w *WARPEndpoint) DisplayType() string { + str := C.ProxyDisplayName(w.Type()) + if !w.IsReady() { + str += " ⚠️ Connecting..." + } + return str +} diff --git a/protocol/warp/endpoint_warp_awg.go b/protocol/warp/endpoint_warp_awg.go new file mode 100644 index 0000000000..27f5a21945 --- /dev/null +++ b/protocol/warp/endpoint_warp_awg.go @@ -0,0 +1,51 @@ +//go:build with_awg + +package warp + +import ( + "context" + "net/netip" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/protocol/awg" + "github.com/sagernet/sing/common/json/badoption" +) + +func createWARPAwgEndpoint( + ctx context.Context, + router adapter.Router, + logger log.ContextLogger, + tag string, + options option.WARPEndpointOptions, + config *C.WARPConfig, + peerAddr string, + peerPort uint16, + peerPublicKey string, +) (adapter.Endpoint, error) { + return awg.NewEndpoint(ctx, router, logger, tag, option.AwgEndpointOptions{ + UseIntegratedTun: options.System, + PrivateKey: config.PrivateKey, + Address: badoption.Listable[netip.Prefix]{ + netip.MustParsePrefix(config.Interface.Addresses.V4 + "/32"), + netip.MustParsePrefix(config.Interface.Addresses.V6 + "/128"), + }, + MTU: options.MTU, + ListenPort: options.ListenPort, + Awg: *options.AWG, + Peers: []option.AwgPeerOptions{ + { + Address: peerAddr, + Port: peerPort, + PublicKey: peerPublicKey, + AllowedIPs: badoption.Listable[netip.Prefix]{ + netip.MustParsePrefix("0.0.0.0/0"), + netip.MustParsePrefix("::/0"), + }, + }, + }, + DialerOptions: options.DialerOptions, + }) +} diff --git a/protocol/warp/endpoint_warp_awg_stub.go b/protocol/warp/endpoint_warp_awg_stub.go new file mode 100644 index 0000000000..f8c134ae1c --- /dev/null +++ b/protocol/warp/endpoint_warp_awg_stub.go @@ -0,0 +1,27 @@ +//go:build !with_awg + +package warp + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func createWARPAwgEndpoint( + ctx context.Context, + router adapter.Router, + logger log.ContextLogger, + tag string, + options option.WARPEndpointOptions, + config *C.WARPConfig, + peerAddr string, + peerPort uint16, + peerPublicKey string, +) (adapter.Endpoint, error) { + return nil, E.New(`Awg is not included in this build, rebuild with -tags with_awg`) +} diff --git a/protocol/warp/endpoint_warp_wireguard.go b/protocol/warp/endpoint_warp_wireguard.go new file mode 100644 index 0000000000..ab781cddfe --- /dev/null +++ b/protocol/warp/endpoint_warp_wireguard.go @@ -0,0 +1,54 @@ +package warp + +import ( + "context" + "net/netip" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + wg "github.com/sagernet/sing-box/protocol/wireguard" + "github.com/sagernet/sing/common/json/badoption" +) + +func createWARPWireGuardEndpoint( + ctx context.Context, + router adapter.Router, + logger log.ContextLogger, + tag string, + options option.WARPEndpointOptions, + config *C.WARPConfig, + peerAddr string, + peerPort uint16, + peerPublicKey string, +) (adapter.Endpoint, error) { + return wg.NewEndpoint(ctx, router, logger, tag, option.WireGuardEndpointOptions{ + System: options.System, + Name: options.Name, + ListenPort: options.ListenPort, + UDPTimeout: options.UDPTimeout, + Workers: options.Workers, + PreallocatedBuffersPerPool: options.PreallocatedBuffersPerPool, + DisablePauses: options.DisablePauses, + Noise: options.Noise, + DialerOptions: options.DialerOptions, + Address: badoption.Listable[netip.Prefix]{ + netip.MustParsePrefix(config.Interface.Addresses.V4 + "/32"), + netip.MustParsePrefix(config.Interface.Addresses.V6 + "/128"), + }, + PrivateKey: config.PrivateKey, + Peers: []option.WireGuardPeer{ + { + Address: peerAddr, + Port: peerPort, + PublicKey: peerPublicKey, + AllowedIPs: badoption.Listable[netip.Prefix]{ + netip.MustParsePrefix("0.0.0.0/0"), + netip.MustParsePrefix("::/0"), + }, + }, + }, + MTU: options.MTU, + }) +} diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go new file mode 100644 index 0000000000..2975b05cb5 --- /dev/null +++ b/protocol/wireguard/endpoint.go @@ -0,0 +1,287 @@ +package wireguard + +import ( + "context" + "net" + "net/netip" + "sync/atomic" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/endpoint" + "github.com/sagernet/sing-box/common/dialer" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/route/rule" + "github.com/sagernet/sing-box/transport/wireguard" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" +) + +var ( + _ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil) + _ dialer.PacketDialerWithDestination = (*Endpoint)(nil) +) + +func RegisterEndpoint(registry *endpoint.Registry) { + endpoint.Register[option.WireGuardEndpointOptions](registry, C.TypeWireGuard, NewEndpoint) +} + +type Endpoint struct { + endpoint.Adapter + ctx context.Context + router adapter.Router + dnsRouter adapter.DNSRouter + logger logger.ContextLogger + localAddresses []netip.Prefix + endpoint *wireguard.Endpoint + started atomic.Bool +} + +func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardEndpointOptions) (adapter.Endpoint, error) { + ep := &Endpoint{ + Adapter: endpoint.NewAdapterWithDialerOptions(C.TypeWireGuard, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions), + ctx: ctx, + router: router, + dnsRouter: service.FromContext[adapter.DNSRouter](ctx), + logger: logger, + localAddresses: options.Address, + } + if options.Detour != "" && options.ListenPort != 0 { + return nil, E.New("`listen_port` is conflict with `detour`") + } + outboundDialer, err := dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: options.DialerOptions, + RemoteIsDomain: common.Any(options.Peers, func(it option.WireGuardPeer) bool { + return !M.ParseAddr(it.Address).IsValid() + }), + ResolverOnDetour: true, + }) + if err != nil { + return nil, err + } + var udpTimeout time.Duration + if options.UDPTimeout != 0 { + udpTimeout = time.Duration(options.UDPTimeout) + } else { + udpTimeout = C.UDPTimeout + } + wgEndpoint, err := wireguard.NewEndpoint(wireguard.EndpointOptions{ + Context: ctx, + Logger: logger, + System: options.System, + Handler: ep, + UDPTimeout: udpTimeout, + Dialer: outboundDialer, + CreateDialer: func(interfaceName string) N.Dialer { + return common.Must1(dialer.NewDefault(ctx, option.DialerOptions{ + BindInterface: interfaceName, + })) + }, + Name: options.Name, + MTU: options.MTU, + Address: options.Address, + PrivateKey: options.PrivateKey, + ListenPort: options.ListenPort, + ResolvePeer: func(domain string) (netip.Addr, error) { + endpointAddresses, lookupErr := ep.dnsRouter.Lookup(ctx, domain, outboundDialer.(dialer.ResolveDialer).QueryOptions()) + if lookupErr != nil { + return netip.Addr{}, lookupErr + } + return endpointAddresses[0], nil + }, + Peers: common.Map(options.Peers, func(it option.WireGuardPeer) wireguard.PeerOptions { + return wireguard.PeerOptions{ + Endpoint: M.ParseSocksaddrHostPort(it.Address, it.Port), + PublicKey: it.PublicKey, + PreSharedKey: it.PreSharedKey, + AllowedIPs: it.AllowedIPs, + PersistentKeepaliveInterval: it.PersistentKeepaliveInterval, + Reserved: it.Reserved, + } + }), + Workers: options.Workers, + }) + if err != nil { + return nil, err + } + ep.endpoint = wgEndpoint + return ep, nil +} + +func (w *Endpoint) Start(stage adapter.StartStage) error { + switch stage { + case adapter.StartStateStart: + return w.endpoint.Start(false) + case adapter.StartStatePostStart: + err := w.endpoint.Start(true) + if err != nil { + return err + } + w.started.Store(true) + } + return nil +} + +func (w *Endpoint) Close() error { + w.started.Store(false) + return w.endpoint.Close() +} + +func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + if !w.started.Load() { + return nil, E.New("WireGuard is not ready yet") + } + var ipVersion uint8 + if !destination.IsIPv6() { + ipVersion = 4 + } else { + ipVersion = 6 + } + routeDestination, err := w.router.PreMatch(adapter.InboundContext{ + Inbound: w.Tag(), + InboundType: w.Type(), + IPVersion: ipVersion, + Network: network, + Source: source, + Destination: destination, + }, routeContext, timeout, false) + if err != nil { + switch { + case rule.IsBypassed(err): + err = nil + case rule.IsRejected(err): + w.logger.Trace("reject ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()) + default: + if network == N.NetworkICMP { + w.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())) + } + } + } + return routeDestination, err +} + +func (w *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = w.Tag() + metadata.InboundType = w.Type() + metadata.Source = source + for _, localPrefix := range w.localAddresses { + if localPrefix.Contains(destination.Addr) { + metadata.OriginDestination = destination + if destination.Addr.Is4() { + destination.Addr = netip.AddrFrom4([4]uint8{127, 0, 0, 1}) + } else { + destination.Addr = netip.IPv6Loopback() + } + break + } + } + metadata.Destination = destination + w.logger.InfoContext(ctx, "inbound connection from ", source) + w.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + w.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (w *Endpoint) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = w.Tag() + metadata.InboundType = w.Type() + metadata.Source = source + metadata.Destination = destination + for _, localPrefix := range w.localAddresses { + if localPrefix.Contains(destination.Addr) { + metadata.OriginDestination = destination + if destination.Addr.Is4() { + metadata.Destination.Addr = netip.AddrFrom4([4]uint8{127, 0, 0, 1}) + } else { + metadata.Destination.Addr = netip.IPv6Loopback() + } + conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination) + } + } + w.logger.InfoContext(ctx, "inbound packet connection from ", source) + w.logger.InfoContext(ctx, "inbound packet connection to ", destination) + w.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} + +func (w *Endpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + switch network { + case N.NetworkTCP: + w.logger.InfoContext(ctx, "outbound connection to ", destination) + case N.NetworkUDP: + w.logger.InfoContext(ctx, "outbound packet connection to ", destination) + } + if !w.started.Load() { + return nil, E.New("WireGuard is not ready yet") + } + if destination.IsDomain() { + destinationAddresses, err := w.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) + if err != nil { + return nil, err + } + return N.DialSerial(ctx, w.endpoint, network, destination, destinationAddresses) + } else if !destination.Addr.IsValid() { + return nil, E.New("invalid destination: ", destination) + } + return w.endpoint.DialContext(ctx, network, destination) +} + +func (w *Endpoint) ListenPacketWithDestination(ctx context.Context, destination M.Socksaddr) (net.PacketConn, netip.Addr, error) { + w.logger.InfoContext(ctx, "outbound packet connection to ", destination) + if !w.started.Load() { + return nil, netip.Addr{}, E.New("WireGuard is not ready yet") + } + if destination.IsDomain() { + destinationAddresses, err := w.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) + if err != nil { + return nil, netip.Addr{}, err + } + return N.ListenSerial(ctx, w.endpoint, destination, destinationAddresses) + } + packetConn, err := w.endpoint.ListenPacket(ctx, destination) + if err != nil { + return nil, netip.Addr{}, err + } + if destination.IsIP() { + return packetConn, destination.Addr, nil + } + return packetConn, netip.Addr{}, nil +} + +func (w *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + packetConn, destinationAddress, err := w.ListenPacketWithDestination(ctx, destination) + if err != nil { + return nil, err + } + if destinationAddress.IsValid() && destination != M.SocksaddrFrom(destinationAddress, destination.Port) { + return bufio.NewNATPacketConn(bufio.NewPacketConn(packetConn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil + } + return packetConn, nil +} + +func (w *Endpoint) PreferredDomain(domain string) bool { + return false +} + +func (w *Endpoint) PreferredAddress(address netip.Addr) bool { + if !w.started.Load() { + return false + } + return w.endpoint.Lookup(address) != nil +} + +func (w *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + if !w.started.Load() { + return nil, E.New("WireGuard is not ready yet") + } + return w.endpoint.NewDirectRouteConnection(metadata, routeContext, timeout) +} diff --git a/protocol/wireguard/outbound.go b/protocol/wireguard/outbound.go new file mode 100644 index 0000000000..c1a0076b30 --- /dev/null +++ b/protocol/wireguard/outbound.go @@ -0,0 +1,175 @@ +package wireguard + +import ( + "context" + "net" + "net/netip" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/common/dialer" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/transport/wireguard" + tun "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" +) + +var _ adapter.OutboundWithPreferredRoutes = (*Outbound)(nil) + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.LegacyWireGuardOutboundOptions](registry, C.TypeWireGuard, NewOutbound) +} + +type Outbound struct { + outbound.Adapter + ctx context.Context + dnsRouter adapter.DNSRouter + logger logger.ContextLogger + localAddresses []netip.Prefix + endpoint *wireguard.Endpoint +} + +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.LegacyWireGuardOutboundOptions) (adapter.Outbound, error) { + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeWireGuard, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions), + ctx: ctx, + dnsRouter: service.FromContext[adapter.DNSRouter](ctx), + logger: logger, + localAddresses: options.LocalAddress, + } + if options.Detour != "" && options.GSO { + return nil, E.New("gso is conflict with detour") + } + outboundDialer, err := dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: options.DialerOptions, + RemoteIsDomain: options.ServerIsDomain() || common.Any(options.Peers, func(it option.LegacyWireGuardPeer) bool { + return it.ServerIsDomain() + }), + ResolverOnDetour: true, + }) + if err != nil { + return nil, err + } + peers := common.Map(options.Peers, func(it option.LegacyWireGuardPeer) wireguard.PeerOptions { + return wireguard.PeerOptions{ + Endpoint: it.ServerOptions.Build(), + PublicKey: it.PublicKey, + PreSharedKey: it.PreSharedKey, + AllowedIPs: it.AllowedIPs, + // PersistentKeepaliveInterval: time.Duration(it.PersistentKeepaliveInterval), + Reserved: it.Reserved, + } + }) + if len(peers) == 0 { + peers = []wireguard.PeerOptions{{ + Endpoint: options.ServerOptions.Build(), + PublicKey: options.PeerPublicKey, + PreSharedKey: options.PreSharedKey, + AllowedIPs: []netip.Prefix{netip.PrefixFrom(netip.IPv4Unspecified(), 0), netip.PrefixFrom(netip.IPv6Unspecified(), 0)}, + Reserved: options.Reserved, + }} + } + + wgEndpoint, err := wireguard.NewEndpoint(wireguard.EndpointOptions{ + Context: ctx, + Logger: logger, + System: options.SystemInterface, + Dialer: outboundDialer, + CreateDialer: func(interfaceName string) N.Dialer { + return common.Must1(dialer.NewDefault(ctx, option.DialerOptions{ + BindInterface: interfaceName, + })) + }, + Name: options.InterfaceName, + MTU: options.MTU, + Address: options.LocalAddress, + PrivateKey: options.PrivateKey, + ResolvePeer: func(domain string) (netip.Addr, error) { + endpointAddresses, lookupErr := outbound.dnsRouter.Lookup(ctx, domain, outboundDialer.(dialer.ResolveDialer).QueryOptions()) + if lookupErr != nil { + return netip.Addr{}, lookupErr + } + return endpointAddresses[0], nil + }, + Peers: peers, + Workers: options.Workers, + PreallocatedBuffersPerPool: options.PreallocatedBuffersPerPool, + DisablePauses: options.DisablePauses, + Noise: options.Noise, + }) + if err != nil { + return nil, err + } + outbound.endpoint = wgEndpoint + return outbound, nil +} + +func (o *Outbound) Start(stage adapter.StartStage) error { + switch stage { + case adapter.StartStateStart: + return o.endpoint.Start(false) + case adapter.StartStatePostStart: + return o.endpoint.Start(true) + } + return nil +} + +func (o *Outbound) Close() error { + return o.endpoint.Close() +} + +func (o *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + switch network { + case N.NetworkTCP: + o.logger.InfoContext(ctx, "outbound connection to ", destination) + case N.NetworkUDP: + o.logger.InfoContext(ctx, "outbound packet connection to ", destination) + } + if destination.IsFqdn() { + destinationAddresses, err := o.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) + if err != nil { + return nil, err + } + return N.DialSerial(ctx, o.endpoint, network, destination, destinationAddresses) + } else if !destination.Addr.IsValid() { + return nil, E.New("invalid destination: ", destination) + } + return o.endpoint.DialContext(ctx, network, destination) +} + +func (o *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + o.logger.InfoContext(ctx, "outbound packet connection to ", destination) + if destination.IsFqdn() { + destinationAddresses, err := o.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) + if err != nil { + return nil, err + } + packetConn, _, err := N.ListenSerial(ctx, o.endpoint, destination, destinationAddresses) + if err != nil { + return nil, err + } + return packetConn, err + } + return o.endpoint.ListenPacket(ctx, destination) +} + +func (o *Outbound) PreferredDomain(domain string) bool { + return false +} + +func (o *Outbound) PreferredAddress(address netip.Addr) bool { + return o.endpoint.Lookup(address) != nil +} + +func (o *Outbound) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + return o.endpoint.NewDirectRouteConnection(metadata, routeContext, timeout) +} diff --git a/release/DEFAULT_BUILD_TAGS b/release/DEFAULT_BUILD_TAGS new file mode 100644 index 0000000000..e06bc120e0 --- /dev/null +++ b/release/DEFAULT_BUILD_TAGS @@ -0,0 +1 @@ +with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_cloudflared,with_naive_outbound,badlinkname,tfogo_checklinkname0 \ No newline at end of file diff --git a/release/DEFAULT_BUILD_TAGS_OTHERS b/release/DEFAULT_BUILD_TAGS_OTHERS new file mode 100644 index 0000000000..a28e900e9d --- /dev/null +++ b/release/DEFAULT_BUILD_TAGS_OTHERS @@ -0,0 +1 @@ +with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_cloudflared,badlinkname,tfogo_checklinkname0 \ No newline at end of file diff --git a/release/DEFAULT_BUILD_TAGS_WINDOWS b/release/DEFAULT_BUILD_TAGS_WINDOWS new file mode 100644 index 0000000000..af4fe41620 --- /dev/null +++ b/release/DEFAULT_BUILD_TAGS_WINDOWS @@ -0,0 +1 @@ +with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_cloudflared,with_naive_outbound,with_purego,badlinkname,tfogo_checklinkname0 \ No newline at end of file diff --git a/release/LDFLAGS b/release/LDFLAGS new file mode 100644 index 0000000000..8f613f9714 --- /dev/null +++ b/release/LDFLAGS @@ -0,0 +1 @@ +-X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0 \ No newline at end of file diff --git a/release/completions/sing-box.bash b/release/completions/sing-box.bash new file mode 100644 index 0000000000..22df7763cf --- /dev/null +++ b/release/completions/sing-box.bash @@ -0,0 +1,1580 @@ +# bash completion for sing-box -*- shell-script -*- + +__sing-box_debug() +{ + if [[ -n ${BASH_COMP_DEBUG_FILE:-} ]]; then + echo "$*" >> "${BASH_COMP_DEBUG_FILE}" + fi +} + +# Homebrew on Macs have version 1.3 of bash-completion which doesn't include +# _init_completion. This is a very minimal version of that function. +__sing-box_init_completion() +{ + COMPREPLY=() + _get_comp_words_by_ref "$@" cur prev words cword +} + +__sing-box_index_of_word() +{ + local w word=$1 + shift + index=0 + for w in "$@"; do + [[ $w = "$word" ]] && return + index=$((index+1)) + done + index=-1 +} + +__sing-box_contains_word() +{ + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done + return 1 +} + +__sing-box_handle_go_custom_completion() +{ + __sing-box_debug "${FUNCNAME[0]}: cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}" + + local shellCompDirectiveError=1 + local shellCompDirectiveNoSpace=2 + local shellCompDirectiveNoFileComp=4 + local shellCompDirectiveFilterFileExt=8 + local shellCompDirectiveFilterDirs=16 + + local out requestComp lastParam lastChar comp directive args + + # Prepare the command to request completions for the program. + # Calling ${words[0]} instead of directly sing-box allows handling aliases + args=("${words[@]:1}") + # Disable ActiveHelp which is not supported for bash completion v1 + requestComp="SING_BOX_ACTIVE_HELP=0 ${words[0]} __completeNoDesc ${args[*]}" + + lastParam=${words[$((${#words[@]}-1))]} + lastChar=${lastParam:$((${#lastParam}-1)):1} + __sing-box_debug "${FUNCNAME[0]}: lastParam ${lastParam}, lastChar ${lastChar}" + + if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then + # If the last parameter is complete (there is a space following it) + # We add an extra empty parameter so we can indicate this to the go method. + __sing-box_debug "${FUNCNAME[0]}: Adding extra empty parameter" + requestComp="${requestComp} \"\"" + fi + + __sing-box_debug "${FUNCNAME[0]}: calling ${requestComp}" + # Use eval to handle any environment variables and such + out=$(eval "${requestComp}" 2>/dev/null) + + # Extract the directive integer at the very end of the output following a colon (:) + directive=${out##*:} + # Remove the directive + out=${out%:*} + if [ "${directive}" = "${out}" ]; then + # There is not directive specified + directive=0 + fi + __sing-box_debug "${FUNCNAME[0]}: the completion directive is: ${directive}" + __sing-box_debug "${FUNCNAME[0]}: the completions are: ${out}" + + if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then + # Error code. No completion. + __sing-box_debug "${FUNCNAME[0]}: received error from custom completion go code" + return + else + if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then + if [[ $(type -t compopt) = "builtin" ]]; then + __sing-box_debug "${FUNCNAME[0]}: activating no space" + compopt -o nospace + fi + fi + if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then + if [[ $(type -t compopt) = "builtin" ]]; then + __sing-box_debug "${FUNCNAME[0]}: activating no file completion" + compopt +o default + fi + fi + fi + + if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then + # File extension filtering + local fullFilter filter filteringCmd + # Do not use quotes around the $out variable or else newline + # characters will be kept. + for filter in ${out}; do + fullFilter+="$filter|" + done + + filteringCmd="_filedir $fullFilter" + __sing-box_debug "File filtering command: $filteringCmd" + $filteringCmd + elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then + # File completion for directories only + local subdir + # Use printf to strip any trailing newline + subdir=$(printf "%s" "${out}") + if [ -n "$subdir" ]; then + __sing-box_debug "Listing directories in $subdir" + __sing-box_handle_subdirs_in_dir_flag "$subdir" + else + __sing-box_debug "Listing directories in ." + _filedir -d + fi + else + while IFS='' read -r comp; do + COMPREPLY+=("$comp") + done < <(compgen -W "${out}" -- "$cur") + fi +} + +__sing-box_handle_reply() +{ + __sing-box_debug "${FUNCNAME[0]}" + local comp + case $cur in + -*) + if [[ $(type -t compopt) = "builtin" ]]; then + compopt -o nospace + fi + local allflags + if [ ${#must_have_one_flag[@]} -ne 0 ]; then + allflags=("${must_have_one_flag[@]}") + else + allflags=("${flags[*]} ${two_word_flags[*]}") + fi + while IFS='' read -r comp; do + COMPREPLY+=("$comp") + done < <(compgen -W "${allflags[*]}" -- "$cur") + if [[ $(type -t compopt) = "builtin" ]]; then + [[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace + fi + + # complete after --flag=abc + if [[ $cur == *=* ]]; then + if [[ $(type -t compopt) = "builtin" ]]; then + compopt +o nospace + fi + + local index flag + flag="${cur%=*}" + __sing-box_index_of_word "${flag}" "${flags_with_completion[@]}" + COMPREPLY=() + if [[ ${index} -ge 0 ]]; then + PREFIX="" + cur="${cur#*=}" + ${flags_completion[${index}]} + if [ -n "${ZSH_VERSION:-}" ]; then + # zsh completion needs --flag= prefix + eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )" + fi + fi + fi + + if [[ -z "${flag_parsing_disabled}" ]]; then + # If flag parsing is enabled, we have completed the flags and can return. + # If flag parsing is disabled, we may not know all (or any) of the flags, so we fallthrough + # to possibly call handle_go_custom_completion. + return 0; + fi + ;; + esac + + # check if we are handling a flag with special work handling + local index + __sing-box_index_of_word "${prev}" "${flags_with_completion[@]}" + if [[ ${index} -ge 0 ]]; then + ${flags_completion[${index}]} + return + fi + + # we are parsing a flag and don't have a special handler, no completion + if [[ ${cur} != "${words[cword]}" ]]; then + return + fi + + local completions + completions=("${commands[@]}") + if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then + completions+=("${must_have_one_noun[@]}") + elif [[ -n "${has_completion_function}" ]]; then + # if a go completion function is provided, defer to that function + __sing-box_handle_go_custom_completion + fi + if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then + completions+=("${must_have_one_flag[@]}") + fi + while IFS='' read -r comp; do + COMPREPLY+=("$comp") + done < <(compgen -W "${completions[*]}" -- "$cur") + + if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then + while IFS='' read -r comp; do + COMPREPLY+=("$comp") + done < <(compgen -W "${noun_aliases[*]}" -- "$cur") + fi + + if [[ ${#COMPREPLY[@]} -eq 0 ]]; then + if declare -F __sing-box_custom_func >/dev/null; then + # try command name qualified custom func + __sing-box_custom_func + else + # otherwise fall back to unqualified for compatibility + declare -F __custom_func >/dev/null && __custom_func + fi + fi + + # available in bash-completion >= 2, not always present on macOS + if declare -F __ltrim_colon_completions >/dev/null; then + __ltrim_colon_completions "$cur" + fi + + # If there is only 1 completion and it is a flag with an = it will be completed + # but we don't want a space after the = + if [[ "${#COMPREPLY[@]}" -eq "1" ]] && [[ $(type -t compopt) = "builtin" ]] && [[ "${COMPREPLY[0]}" == --*= ]]; then + compopt -o nospace + fi +} + +# The arguments should be in the form "ext1|ext2|extn" +__sing-box_handle_filename_extension_flag() +{ + local ext="$1" + _filedir "@(${ext})" +} + +__sing-box_handle_subdirs_in_dir_flag() +{ + local dir="$1" + pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return +} + +__sing-box_handle_flag() +{ + __sing-box_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + # if a command required a flag, and we found it, unset must_have_one_flag() + local flagname=${words[c]} + local flagvalue="" + # if the word contained an = + if [[ ${words[c]} == *"="* ]]; then + flagvalue=${flagname#*=} # take in as flagvalue after the = + flagname=${flagname%=*} # strip everything after the = + flagname="${flagname}=" # but put the = back + fi + __sing-box_debug "${FUNCNAME[0]}: looking for ${flagname}" + if __sing-box_contains_word "${flagname}" "${must_have_one_flag[@]}"; then + must_have_one_flag=() + fi + + # if you set a flag which only applies to this command, don't show subcommands + if __sing-box_contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then + commands=() + fi + + # keep flag value with flagname as flaghash + # flaghash variable is an associative array which is only supported in bash > 3. + if [[ -z "${BASH_VERSION:-}" || "${BASH_VERSINFO[0]:-}" -gt 3 ]]; then + if [ -n "${flagvalue}" ] ; then + flaghash[${flagname}]=${flagvalue} + elif [ -n "${words[ $((c+1)) ]}" ] ; then + flaghash[${flagname}]=${words[ $((c+1)) ]} + else + flaghash[${flagname}]="true" # pad "true" for bool flag + fi + fi + + # skip the argument to a two word flag + if [[ ${words[c]} != *"="* ]] && __sing-box_contains_word "${words[c]}" "${two_word_flags[@]}"; then + __sing-box_debug "${FUNCNAME[0]}: found a flag ${words[c]}, skip the next argument" + c=$((c+1)) + # if we are looking for a flags value, don't show commands + if [[ $c -eq $cword ]]; then + commands=() + fi + fi + + c=$((c+1)) + +} + +__sing-box_handle_noun() +{ + __sing-box_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + if __sing-box_contains_word "${words[c]}" "${must_have_one_noun[@]}"; then + must_have_one_noun=() + elif __sing-box_contains_word "${words[c]}" "${noun_aliases[@]}"; then + must_have_one_noun=() + fi + + nouns+=("${words[c]}") + c=$((c+1)) +} + +__sing-box_handle_command() +{ + __sing-box_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + local next_command + if [[ -n ${last_command} ]]; then + next_command="_${last_command}_${words[c]//:/__}" + else + if [[ $c -eq 0 ]]; then + next_command="_sing-box_root_command" + else + next_command="_${words[c]//:/__}" + fi + fi + c=$((c+1)) + __sing-box_debug "${FUNCNAME[0]}: looking for ${next_command}" + declare -F "$next_command" >/dev/null && $next_command +} + +__sing-box_handle_word() +{ + if [[ $c -ge $cword ]]; then + __sing-box_handle_reply + return + fi + __sing-box_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + if [[ "${words[c]}" == -* ]]; then + __sing-box_handle_flag + elif __sing-box_contains_word "${words[c]}" "${commands[@]}"; then + __sing-box_handle_command + elif [[ $c -eq 0 ]]; then + __sing-box_handle_command + elif __sing-box_contains_word "${words[c]}" "${command_aliases[@]}"; then + # aliashash variable is an associative array which is only supported in bash > 3. + if [[ -z "${BASH_VERSION:-}" || "${BASH_VERSINFO[0]:-}" -gt 3 ]]; then + words[c]=${aliashash[${words[c]}]} + __sing-box_handle_command + else + __sing-box_handle_noun + fi + else + __sing-box_handle_noun + fi + __sing-box_handle_word +} + +_sing-box_check() +{ + last_command="sing-box_check" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_format() +{ + last_command="sing-box_format" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--write") + flags+=("-w") + local_nonpersistent_flags+=("--write") + local_nonpersistent_flags+=("-w") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_generate_ech-keypair() +{ + last_command="sing-box_generate_ech-keypair" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--pq-signature-schemes-enabled") + local_nonpersistent_flags+=("--pq-signature-schemes-enabled") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_generate_rand() +{ + last_command="sing-box_generate_rand" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--base64") + local_nonpersistent_flags+=("--base64") + flags+=("--hex") + local_nonpersistent_flags+=("--hex") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_generate_reality-keypair() +{ + last_command="sing-box_generate_reality-keypair" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_generate_tls-keypair() +{ + last_command="sing-box_generate_tls-keypair" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--months=") + two_word_flags+=("--months") + two_word_flags+=("-m") + local_nonpersistent_flags+=("--months") + local_nonpersistent_flags+=("--months=") + local_nonpersistent_flags+=("-m") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_generate_uuid() +{ + last_command="sing-box_generate_uuid" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_generate_vapid-keypair() +{ + last_command="sing-box_generate_vapid-keypair" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_generate_wg-keypair() +{ + last_command="sing-box_generate_wg-keypair" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_generate() +{ + last_command="sing-box_generate" + + command_aliases=() + + commands=() + commands+=("ech-keypair") + commands+=("rand") + commands+=("reality-keypair") + commands+=("tls-keypair") + commands+=("uuid") + commands+=("vapid-keypair") + commands+=("wg-keypair") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_geoip_export() +{ + last_command="sing-box_geoip_export" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--output=") + two_word_flags+=("--output") + two_word_flags+=("-o") + local_nonpersistent_flags+=("--output") + local_nonpersistent_flags+=("--output=") + local_nonpersistent_flags+=("-o") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_geoip_list() +{ + last_command="sing-box_geoip_list" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_geoip_lookup() +{ + last_command="sing-box_geoip_lookup" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_geoip() +{ + last_command="sing-box_geoip" + + command_aliases=() + + commands=() + commands+=("export") + commands+=("list") + commands+=("lookup") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_geosite_export() +{ + last_command="sing-box_geosite_export" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--output=") + two_word_flags+=("--output") + two_word_flags+=("-o") + local_nonpersistent_flags+=("--output") + local_nonpersistent_flags+=("--output=") + local_nonpersistent_flags+=("-o") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_geosite_list() +{ + last_command="sing-box_geosite_list" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_geosite_lookup() +{ + last_command="sing-box_geosite_lookup" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_geosite() +{ + last_command="sing-box_geosite" + + command_aliases=() + + commands=() + commands+=("export") + commands+=("list") + commands+=("lookup") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--file=") + two_word_flags+=("--file") + two_word_flags+=("-f") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_merge() +{ + last_command="sing-box_merge" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_rule-set_compile() +{ + last_command="sing-box_rule-set_compile" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--output=") + two_word_flags+=("--output") + two_word_flags+=("-o") + local_nonpersistent_flags+=("--output") + local_nonpersistent_flags+=("--output=") + local_nonpersistent_flags+=("-o") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_rule-set_convert() +{ + last_command="sing-box_rule-set_convert" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--output=") + two_word_flags+=("--output") + two_word_flags+=("-o") + local_nonpersistent_flags+=("--output") + local_nonpersistent_flags+=("--output=") + local_nonpersistent_flags+=("-o") + flags+=("--type=") + two_word_flags+=("--type") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--type") + local_nonpersistent_flags+=("--type=") + local_nonpersistent_flags+=("-t") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_rule-set_decompile() +{ + last_command="sing-box_rule-set_decompile" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--output=") + two_word_flags+=("--output") + two_word_flags+=("-o") + local_nonpersistent_flags+=("--output") + local_nonpersistent_flags+=("--output=") + local_nonpersistent_flags+=("-o") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_rule-set_format() +{ + last_command="sing-box_rule-set_format" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--write") + flags+=("-w") + local_nonpersistent_flags+=("--write") + local_nonpersistent_flags+=("-w") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_rule-set_match() +{ + last_command="sing-box_rule-set_match" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--format=") + two_word_flags+=("--format") + two_word_flags+=("-f") + local_nonpersistent_flags+=("--format") + local_nonpersistent_flags+=("--format=") + local_nonpersistent_flags+=("-f") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_rule-set_merge() +{ + last_command="sing-box_rule-set_merge" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_rule-set_upgrade() +{ + last_command="sing-box_rule-set_upgrade" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--write") + flags+=("-w") + local_nonpersistent_flags+=("--write") + local_nonpersistent_flags+=("-w") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_rule-set() +{ + last_command="sing-box_rule-set" + + command_aliases=() + + commands=() + commands+=("compile") + commands+=("convert") + commands+=("decompile") + commands+=("format") + commands+=("match") + commands+=("merge") + commands+=("upgrade") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_run() +{ + last_command="sing-box_run" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_tools_connect() +{ + last_command="sing-box_tools_connect" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--network=") + two_word_flags+=("--network") + two_word_flags+=("-n") + local_nonpersistent_flags+=("--network") + local_nonpersistent_flags+=("--network=") + local_nonpersistent_flags+=("-n") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--outbound=") + two_word_flags+=("--outbound") + two_word_flags+=("-o") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_tools_fetch() +{ + last_command="sing-box_tools_fetch" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--outbound=") + two_word_flags+=("--outbound") + two_word_flags+=("-o") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_tools_synctime() +{ + last_command="sing-box_tools_synctime" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--format=") + two_word_flags+=("--format") + two_word_flags+=("-f") + local_nonpersistent_flags+=("--format") + local_nonpersistent_flags+=("--format=") + local_nonpersistent_flags+=("-f") + flags+=("--server=") + two_word_flags+=("--server") + two_word_flags+=("-s") + local_nonpersistent_flags+=("--server") + local_nonpersistent_flags+=("--server=") + local_nonpersistent_flags+=("-s") + flags+=("--write") + flags+=("-w") + local_nonpersistent_flags+=("--write") + local_nonpersistent_flags+=("-w") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + flags+=("--outbound=") + two_word_flags+=("--outbound") + two_word_flags+=("-o") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_tools() +{ + last_command="sing-box_tools" + + command_aliases=() + + commands=() + commands+=("connect") + commands+=("fetch") + commands+=("synctime") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--outbound=") + two_word_flags+=("--outbound") + two_word_flags+=("-o") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_version() +{ + last_command="sing-box_version" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--name") + flags+=("-n") + local_nonpersistent_flags+=("--name") + local_nonpersistent_flags+=("-n") + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_sing-box_root_command() +{ + last_command="sing-box" + + command_aliases=() + + commands=() + commands+=("check") + commands+=("format") + commands+=("generate") + commands+=("geoip") + commands+=("geosite") + commands+=("merge") + commands+=("rule-set") + commands+=("run") + commands+=("tools") + commands+=("version") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--config=") + two_word_flags+=("--config") + two_word_flags+=("-c") + flags+=("--config-directory=") + two_word_flags+=("--config-directory") + two_word_flags+=("-C") + flags+=("--directory=") + two_word_flags+=("--directory") + two_word_flags+=("-D") + flags+=("--disable-color") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +__start_sing-box() +{ + local cur prev words cword split + declare -A flaghash 2>/dev/null || : + declare -A aliashash 2>/dev/null || : + if declare -F _init_completion >/dev/null 2>&1; then + _init_completion -s || return + else + __sing-box_init_completion -n "=" || return + fi + + local c=0 + local flag_parsing_disabled= + local flags=() + local two_word_flags=() + local local_nonpersistent_flags=() + local flags_with_completion=() + local flags_completion=() + local commands=("sing-box") + local command_aliases=() + local must_have_one_flag=() + local must_have_one_noun=() + local has_completion_function="" + local last_command="" + local nouns=() + local noun_aliases=() + + __sing-box_handle_word +} + +if [[ $(type -t compopt) = "builtin" ]]; then + complete -o default -F __start_sing-box sing-box +else + complete -o default -o nospace -F __start_sing-box sing-box +fi + +# ex: ts=4 sw=4 et filetype=sh diff --git a/release/completions/sing-box.fish b/release/completions/sing-box.fish new file mode 100644 index 0000000000..2aec1e1d29 --- /dev/null +++ b/release/completions/sing-box.fish @@ -0,0 +1,235 @@ +# fish completion for sing-box -*- shell-script -*- + +function __sing_box_debug + set -l file "$BASH_COMP_DEBUG_FILE" + if test -n "$file" + echo "$argv" >> $file + end +end + +function __sing_box_perform_completion + __sing_box_debug "Starting __sing_box_perform_completion" + + # Extract all args except the last one + set -l args (commandline -opc) + # Extract the last arg and escape it in case it is a space + set -l lastArg (string escape -- (commandline -ct)) + + __sing_box_debug "args: $args" + __sing_box_debug "last arg: $lastArg" + + # Disable ActiveHelp which is not supported for fish shell + set -l requestComp "SING_BOX_ACTIVE_HELP=0 $args[1] __complete $args[2..-1] $lastArg" + + __sing_box_debug "Calling $requestComp" + set -l results (eval $requestComp 2> /dev/null) + + # Some programs may output extra empty lines after the directive. + # Let's ignore them or else it will break completion. + # Ref: https://github.com/spf13/cobra/issues/1279 + for line in $results[-1..1] + if test (string trim -- $line) = "" + # Found an empty line, remove it + set results $results[1..-2] + else + # Found non-empty line, we have our proper output + break + end + end + + set -l comps $results[1..-2] + set -l directiveLine $results[-1] + + # For Fish, when completing a flag with an = (e.g., -n=) + # completions must be prefixed with the flag + set -l flagPrefix (string match -r -- '-.*=' "$lastArg") + + __sing_box_debug "Comps: $comps" + __sing_box_debug "DirectiveLine: $directiveLine" + __sing_box_debug "flagPrefix: $flagPrefix" + + for comp in $comps + printf "%s%s\n" "$flagPrefix" "$comp" + end + + printf "%s\n" "$directiveLine" +end + +# this function limits calls to __sing_box_perform_completion, by caching the result behind $__sing_box_perform_completion_once_result +function __sing_box_perform_completion_once + __sing_box_debug "Starting __sing_box_perform_completion_once" + + if test -n "$__sing_box_perform_completion_once_result" + __sing_box_debug "Seems like a valid result already exists, skipping __sing_box_perform_completion" + return 0 + end + + set --global __sing_box_perform_completion_once_result (__sing_box_perform_completion) + if test -z "$__sing_box_perform_completion_once_result" + __sing_box_debug "No completions, probably due to a failure" + return 1 + end + + __sing_box_debug "Performed completions and set __sing_box_perform_completion_once_result" + return 0 +end + +# this function is used to clear the $__sing_box_perform_completion_once_result variable after completions are run +function __sing_box_clear_perform_completion_once_result + __sing_box_debug "" + __sing_box_debug "========= clearing previously set __sing_box_perform_completion_once_result variable ==========" + set --erase __sing_box_perform_completion_once_result + __sing_box_debug "Successfully erased the variable __sing_box_perform_completion_once_result" +end + +function __sing_box_requires_order_preservation + __sing_box_debug "" + __sing_box_debug "========= checking if order preservation is required ==========" + + __sing_box_perform_completion_once + if test -z "$__sing_box_perform_completion_once_result" + __sing_box_debug "Error determining if order preservation is required" + return 1 + end + + set -l directive (string sub --start 2 $__sing_box_perform_completion_once_result[-1]) + __sing_box_debug "Directive is: $directive" + + set -l shellCompDirectiveKeepOrder 32 + set -l keeporder (math (math --scale 0 $directive / $shellCompDirectiveKeepOrder) % 2) + __sing_box_debug "Keeporder is: $keeporder" + + if test $keeporder -ne 0 + __sing_box_debug "This does require order preservation" + return 0 + end + + __sing_box_debug "This doesn't require order preservation" + return 1 +end + + +# This function does two things: +# - Obtain the completions and store them in the global __sing_box_comp_results +# - Return false if file completion should be performed +function __sing_box_prepare_completions + __sing_box_debug "" + __sing_box_debug "========= starting completion logic ==========" + + # Start fresh + set --erase __sing_box_comp_results + + __sing_box_perform_completion_once + __sing_box_debug "Completion results: $__sing_box_perform_completion_once_result" + + if test -z "$__sing_box_perform_completion_once_result" + __sing_box_debug "No completion, probably due to a failure" + # Might as well do file completion, in case it helps + return 1 + end + + set -l directive (string sub --start 2 $__sing_box_perform_completion_once_result[-1]) + set --global __sing_box_comp_results $__sing_box_perform_completion_once_result[1..-2] + + __sing_box_debug "Completions are: $__sing_box_comp_results" + __sing_box_debug "Directive is: $directive" + + set -l shellCompDirectiveError 1 + set -l shellCompDirectiveNoSpace 2 + set -l shellCompDirectiveNoFileComp 4 + set -l shellCompDirectiveFilterFileExt 8 + set -l shellCompDirectiveFilterDirs 16 + + if test -z "$directive" + set directive 0 + end + + set -l compErr (math (math --scale 0 $directive / $shellCompDirectiveError) % 2) + if test $compErr -eq 1 + __sing_box_debug "Received error directive: aborting." + # Might as well do file completion, in case it helps + return 1 + end + + set -l filefilter (math (math --scale 0 $directive / $shellCompDirectiveFilterFileExt) % 2) + set -l dirfilter (math (math --scale 0 $directive / $shellCompDirectiveFilterDirs) % 2) + if test $filefilter -eq 1; or test $dirfilter -eq 1 + __sing_box_debug "File extension filtering or directory filtering not supported" + # Do full file completion instead + return 1 + end + + set -l nospace (math (math --scale 0 $directive / $shellCompDirectiveNoSpace) % 2) + set -l nofiles (math (math --scale 0 $directive / $shellCompDirectiveNoFileComp) % 2) + + __sing_box_debug "nospace: $nospace, nofiles: $nofiles" + + # If we want to prevent a space, or if file completion is NOT disabled, + # we need to count the number of valid completions. + # To do so, we will filter on prefix as the completions we have received + # may not already be filtered so as to allow fish to match on different + # criteria than the prefix. + if test $nospace -ne 0; or test $nofiles -eq 0 + set -l prefix (commandline -t | string escape --style=regex) + __sing_box_debug "prefix: $prefix" + + set -l completions (string match -r -- "^$prefix.*" $__sing_box_comp_results) + set --global __sing_box_comp_results $completions + __sing_box_debug "Filtered completions are: $__sing_box_comp_results" + + # Important not to quote the variable for count to work + set -l numComps (count $__sing_box_comp_results) + __sing_box_debug "numComps: $numComps" + + if test $numComps -eq 1; and test $nospace -ne 0 + # We must first split on \t to get rid of the descriptions to be + # able to check what the actual completion will be. + # We don't need descriptions anyway since there is only a single + # real completion which the shell will expand immediately. + set -l split (string split --max 1 \t $__sing_box_comp_results[1]) + + # Fish won't add a space if the completion ends with any + # of the following characters: @=/:., + set -l lastChar (string sub -s -1 -- $split) + if not string match -r -q "[@=/:.,]" -- "$lastChar" + # In other cases, to support the "nospace" directive we trick the shell + # by outputting an extra, longer completion. + __sing_box_debug "Adding second completion to perform nospace directive" + set --global __sing_box_comp_results $split[1] $split[1]. + __sing_box_debug "Completions are now: $__sing_box_comp_results" + end + end + + if test $numComps -eq 0; and test $nofiles -eq 0 + # To be consistent with bash and zsh, we only trigger file + # completion when there are no other completions + __sing_box_debug "Requesting file completion" + return 1 + end + end + + return 0 +end + +# Since Fish completions are only loaded once the user triggers them, we trigger them ourselves +# so we can properly delete any completions provided by another script. +# Only do this if the program can be found, or else fish may print some errors; besides, +# the existing completions will only be loaded if the program can be found. +if type -q "sing-box" + # The space after the program name is essential to trigger completion for the program + # and not completion of the program name itself. + # Also, we use '> /dev/null 2>&1' since '&>' is not supported in older versions of fish. + complete --do-complete "sing-box " > /dev/null 2>&1 +end + +# Remove any pre-existing completions for the program since we will be handling all of them. +complete -c sing-box -e + +# this will get called after the two calls below and clear the $__sing_box_perform_completion_once_result global +complete -c sing-box -n '__sing_box_clear_perform_completion_once_result' +# The call to __sing_box_prepare_completions will setup __sing_box_comp_results +# which provides the program's completion choices. +# If this doesn't require order preservation, we don't use the -k flag +complete -c sing-box -n 'not __sing_box_requires_order_preservation && __sing_box_prepare_completions' -f -a '$__sing_box_comp_results' +# otherwise we use the -k flag +complete -k -c sing-box -n '__sing_box_requires_order_preservation && __sing_box_prepare_completions' -f -a '$__sing_box_comp_results' diff --git a/release/completions/sing-box.zsh b/release/completions/sing-box.zsh new file mode 100644 index 0000000000..962005560a --- /dev/null +++ b/release/completions/sing-box.zsh @@ -0,0 +1,212 @@ +#compdef sing-box +compdef _sing-box sing-box + +# zsh completion for sing-box -*- shell-script -*- + +__sing-box_debug() +{ + local file="$BASH_COMP_DEBUG_FILE" + if [[ -n ${file} ]]; then + echo "$*" >> "${file}" + fi +} + +_sing-box() +{ + local shellCompDirectiveError=1 + local shellCompDirectiveNoSpace=2 + local shellCompDirectiveNoFileComp=4 + local shellCompDirectiveFilterFileExt=8 + local shellCompDirectiveFilterDirs=16 + local shellCompDirectiveKeepOrder=32 + + local lastParam lastChar flagPrefix requestComp out directive comp lastComp noSpace keepOrder + local -a completions + + __sing-box_debug "\n========= starting completion logic ==========" + __sing-box_debug "CURRENT: ${CURRENT}, words[*]: ${words[*]}" + + # The user could have moved the cursor backwards on the command-line. + # We need to trigger completion from the $CURRENT location, so we need + # to truncate the command-line ($words) up to the $CURRENT location. + # (We cannot use $CURSOR as its value does not work when a command is an alias.) + words=("${=words[1,CURRENT]}") + __sing-box_debug "Truncated words[*]: ${words[*]}," + + lastParam=${words[-1]} + lastChar=${lastParam[-1]} + __sing-box_debug "lastParam: ${lastParam}, lastChar: ${lastChar}" + + # For zsh, when completing a flag with an = (e.g., sing-box -n=) + # completions must be prefixed with the flag + setopt local_options BASH_REMATCH + if [[ "${lastParam}" =~ '-.*=' ]]; then + # We are dealing with a flag with an = + flagPrefix="-P ${BASH_REMATCH}" + fi + + # Prepare the command to obtain completions + requestComp="${words[1]} __complete ${words[2,-1]}" + if [ "${lastChar}" = "" ]; then + # If the last parameter is complete (there is a space following it) + # We add an extra empty parameter so we can indicate this to the go completion code. + __sing-box_debug "Adding extra empty parameter" + requestComp="${requestComp} \"\"" + fi + + __sing-box_debug "About to call: eval ${requestComp}" + + # Use eval to handle any environment variables and such + out=$(eval ${requestComp} 2>/dev/null) + __sing-box_debug "completion output: ${out}" + + # Extract the directive integer following a : from the last line + local lastLine + while IFS='\n' read -r line; do + lastLine=${line} + done < <(printf "%s\n" "${out[@]}") + __sing-box_debug "last line: ${lastLine}" + + if [ "${lastLine[1]}" = : ]; then + directive=${lastLine[2,-1]} + # Remove the directive including the : and the newline + local suffix + (( suffix=${#lastLine}+2)) + out=${out[1,-$suffix]} + else + # There is no directive specified. Leave $out as is. + __sing-box_debug "No directive found. Setting do default" + directive=0 + fi + + __sing-box_debug "directive: ${directive}" + __sing-box_debug "completions: ${out}" + __sing-box_debug "flagPrefix: ${flagPrefix}" + + if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then + __sing-box_debug "Completion received error. Ignoring completions." + return + fi + + local activeHelpMarker="_activeHelp_ " + local endIndex=${#activeHelpMarker} + local startIndex=$((${#activeHelpMarker}+1)) + local hasActiveHelp=0 + while IFS='\n' read -r comp; do + # Check if this is an activeHelp statement (i.e., prefixed with $activeHelpMarker) + if [ "${comp[1,$endIndex]}" = "$activeHelpMarker" ];then + __sing-box_debug "ActiveHelp found: $comp" + comp="${comp[$startIndex,-1]}" + if [ -n "$comp" ]; then + compadd -x "${comp}" + __sing-box_debug "ActiveHelp will need delimiter" + hasActiveHelp=1 + fi + + continue + fi + + if [ -n "$comp" ]; then + # If requested, completions are returned with a description. + # The description is preceded by a TAB character. + # For zsh's _describe, we need to use a : instead of a TAB. + # We first need to escape any : as part of the completion itself. + comp=${comp//:/\\:} + + local tab="$(printf '\t')" + comp=${comp//$tab/:} + + __sing-box_debug "Adding completion: ${comp}" + completions+=${comp} + lastComp=$comp + fi + done < <(printf "%s\n" "${out[@]}") + + # Add a delimiter after the activeHelp statements, but only if: + # - there are completions following the activeHelp statements, or + # - file completion will be performed (so there will be choices after the activeHelp) + if [ $hasActiveHelp -eq 1 ]; then + if [ ${#completions} -ne 0 ] || [ $((directive & shellCompDirectiveNoFileComp)) -eq 0 ]; then + __sing-box_debug "Adding activeHelp delimiter" + compadd -x "--" + hasActiveHelp=0 + fi + fi + + if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then + __sing-box_debug "Activating nospace." + noSpace="-S ''" + fi + + if [ $((directive & shellCompDirectiveKeepOrder)) -ne 0 ]; then + __sing-box_debug "Activating keep order." + keepOrder="-V" + fi + + if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then + # File extension filtering + local filteringCmd + filteringCmd='_files' + for filter in ${completions[@]}; do + if [ ${filter[1]} != '*' ]; then + # zsh requires a glob pattern to do file filtering + filter="\*.$filter" + fi + filteringCmd+=" -g $filter" + done + filteringCmd+=" ${flagPrefix}" + + __sing-box_debug "File filtering command: $filteringCmd" + _arguments '*:filename:'"$filteringCmd" + elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then + # File completion for directories only + local subdir + subdir="${completions[1]}" + if [ -n "$subdir" ]; then + __sing-box_debug "Listing directories in $subdir" + pushd "${subdir}" >/dev/null 2>&1 + else + __sing-box_debug "Listing directories in ." + fi + + local result + _arguments '*:dirname:_files -/'" ${flagPrefix}" + result=$? + if [ -n "$subdir" ]; then + popd >/dev/null 2>&1 + fi + return $result + else + __sing-box_debug "Calling _describe" + if eval _describe $keepOrder "completions" completions $flagPrefix $noSpace; then + __sing-box_debug "_describe found some completions" + + # Return the success of having called _describe + return 0 + else + __sing-box_debug "_describe did not find completions." + __sing-box_debug "Checking if we should do file completion." + if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then + __sing-box_debug "deactivating file completion" + + # We must return an error code here to let zsh know that there were no + # completions found by _describe; this is what will trigger other + # matching algorithms to attempt to find completions. + # For example zsh can match letters in the middle of words. + return 1 + else + # Perform file completion + __sing-box_debug "Activating file completion" + + # We must return the result of this command, so it must be the + # last command, or else we must store its result to return it. + _arguments '*:filename:_files'" ${flagPrefix}" + fi + fi + fi +} + +# don't run the completion function when being source-ed or eval-ed +if [ "$funcstack[1]" = "_sing-box" ]; then + _sing-box +fi diff --git a/release/config/config.json b/release/config/config.json index c518d18bd9..e5a2a663d8 100644 --- a/release/config/config.json +++ b/release/config/config.json @@ -5,7 +5,9 @@ "dns": { "servers": [ { - "address": "tls://8.8.8.8" + "type": "tls", + "tag": "google", + "server": "8.8.8.8" } ] }, @@ -14,26 +16,25 @@ "type": "shadowsocks", "listen": "::", "listen_port": 8080, - "sniff": true, "network": "tcp", "method": "2022-blake3-aes-128-gcm", - "password": "8JCsPssfgS8tiRwiMlhARg==" + "password": "Gn1JUS14bLUHgv1cWDDp4A==", + "multiplex": { + "enabled": true, + "padding": true + } } ], "outbounds": [ { "type": "direct" - }, - { - "type": "dns", - "tag": "dns-out" } ], "route": { "rules": [ { - "protocol": "dns", - "outbound": "dns-out" + "port": 53, + "action": "hijack-dns" } ] } diff --git a/release/config/openwrt.conf b/release/config/openwrt.conf new file mode 100644 index 0000000000..1ce4c77d29 --- /dev/null +++ b/release/config/openwrt.conf @@ -0,0 +1,5 @@ +config sing-box 'main' + option enabled '1' + option conffile '/etc/sing-box/config.json' + option workdir '/usr/share/sing-box' + option log_stderr '1' diff --git a/release/config/openwrt.init b/release/config/openwrt.init new file mode 100755 index 0000000000..9979fc1ae0 --- /dev/null +++ b/release/config/openwrt.init @@ -0,0 +1,32 @@ +#!/bin/sh /etc/rc.common + +USE_PROCD=1 +START=99 +PROG="/usr/bin/sing-box" + +start_service() { + config_load "sing-box" + + local enabled config_file working_directory + local log_stderr + config_get_bool enabled "main" "enabled" "0" + [ "$enabled" -eq "1" ] || return 0 + + config_get config_file "main" "conffile" "/etc/sing-box/config.json" + config_get working_directory "main" "workdir" "/usr/share/sing-box" + config_get_bool log_stderr "main" "log_stderr" "1" + + procd_open_instance + procd_set_param command "$PROG" run -c "$config_file" -D "$working_directory" + procd_set_param file "$config_file" + procd_set_param stderr "$log_stderr" + procd_set_param limits core="unlimited" + procd_set_param limits nofile="1000000 1000000" + procd_set_param respawn + + procd_close_instance +} + +service_triggers() { + procd_add_reload_trigger "sing-box" +} diff --git a/release/config/openwrt.keep b/release/config/openwrt.keep new file mode 100644 index 0000000000..b26dd531f4 --- /dev/null +++ b/release/config/openwrt.keep @@ -0,0 +1 @@ +/etc/sing-box/ diff --git a/release/config/openwrt.prerm b/release/config/openwrt.prerm new file mode 100755 index 0000000000..12d06ec78b --- /dev/null +++ b/release/config/openwrt.prerm @@ -0,0 +1,4 @@ +#!/bin/sh +[ -s ${IPKG_INSTROOT}/lib/functions.sh ] || exit 0 +. ${IPKG_INSTROOT}/lib/functions.sh +default_prerm $0 $@ diff --git a/release/config/sing-box-split-dns.xml b/release/config/sing-box-split-dns.xml new file mode 100644 index 0000000000..4ee64c8db1 --- /dev/null +++ b/release/config/sing-box-split-dns.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/release/config/sing-box.confd b/release/config/sing-box.confd new file mode 100644 index 0000000000..506caa325e --- /dev/null +++ b/release/config/sing-box.confd @@ -0,0 +1,6 @@ +# /etc/conf.d/sing-box: config file for /etc/init.d/sing-box + +# sing-box configuration path, could be file or directory +# SINGBOX_CONFIG=/etc/sing-box + +# SINGBOX_WORKDIR=/var/lib/sing-box diff --git a/release/config/sing-box.initd b/release/config/sing-box.initd new file mode 100755 index 0000000000..1541518ae7 --- /dev/null +++ b/release/config/sing-box.initd @@ -0,0 +1,44 @@ +#!/sbin/openrc-run + +name=$RC_SVCNAME +description="sing-box service" +supervisor="supervise-daemon" +command="/usr/bin/sing-box" +extra_commands="checkconfig" +extra_started_commands="reload" + +: ${SINGBOX_CONFIG:=${config:-"/etc/sing-box"}} + +if [ -d "$SINGBOX_CONFIG" ]; then + _config_opt="-C $SINGBOX_CONFIG" +elif [ -z "$SINGBOX_CONFIG" ]; then + _config_opt="" +else + _config_opt="-c $SINGBOX_CONFIG" +fi + +_workdir=${SINGBOX_WORKDIR:-${workdir:-"/var/lib/sing-box"}} + +command_args="run --disable-color + -D $_workdir + $_config_opt" + +depend() { + after net dns +} + +checkconfig() { + ebegin "Checking $RC_SVCNAME configuration" + sing-box check -D "$_workdir" $_config_opt + eend $? +} + +start_pre() { + checkconfig +} + +reload() { + ebegin "Reloading $RC_SVCNAME" + checkconfig && $supervisor "$RC_SVCNAME" --signal HUP + eend $? +} diff --git a/release/config/sing-box.postinst b/release/config/sing-box.postinst new file mode 100644 index 0000000000..770dd22a11 --- /dev/null +++ b/release/config/sing-box.postinst @@ -0,0 +1,3 @@ +#!/bin/sh + +systemd-sysusers sing-box.conf diff --git a/release/config/sing-box.rules b/release/config/sing-box.rules new file mode 100644 index 0000000000..668b2640a1 --- /dev/null +++ b/release/config/sing-box.rules @@ -0,0 +1,8 @@ +polkit.addRule(function(action, subject) { + if ((action.id == "org.freedesktop.resolve1.set-domains" || + action.id == "org.freedesktop.resolve1.set-default-route" || + action.id == "org.freedesktop.resolve1.set-dns-servers") && + subject.user == "sing-box") { + return polkit.Result.YES; + } +}); diff --git a/release/config/sing-box.service b/release/config/sing-box.service index 7b7a13a855..f003f8443f 100644 --- a/release/config/sing-box.service +++ b/release/config/sing-box.service @@ -4,8 +4,10 @@ Documentation=https://sing-box.sagernet.org After=network.target nss-lookup.target network-online.target [Service] -CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH -AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH +User=sing-box +StateDirectory=sing-box +CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH +AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH ExecStart=/usr/bin/sing-box -D /var/lib/sing-box -C /etc/sing-box run ExecReload=/bin/kill -HUP $MAINPID Restart=on-failure diff --git a/release/config/sing-box.sysusers b/release/config/sing-box.sysusers new file mode 100644 index 0000000000..33e381006d --- /dev/null +++ b/release/config/sing-box.sysusers @@ -0,0 +1 @@ +u sing-box - "sing-box Service" diff --git a/release/config/sing-box@.service b/release/config/sing-box@.service index 578ebd1cb5..726bbee783 100644 --- a/release/config/sing-box@.service +++ b/release/config/sing-box@.service @@ -4,8 +4,10 @@ Documentation=https://sing-box.sagernet.org After=network.target nss-lookup.target network-online.target [Service] -CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH -AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH +User=sing-box +StateDirectory=sing-box-%i +CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH +AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH ExecStart=/usr/bin/sing-box -D /var/lib/sing-box-%i -c /etc/sing-box/%i.json run ExecReload=/bin/kill -HUP $MAINPID Restart=on-failure diff --git a/release/local/common.sh b/release/local/common.sh new file mode 100755 index 0000000000..13a8415c3e --- /dev/null +++ b/release/local/common.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash + +set -e -o pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" +BINARY_NAME="sing-box" + +INSTALL_BIN_PATH="/usr/local/bin" +INSTALL_CONFIG_PATH="/usr/local/etc/sing-box" +INSTALL_DATA_PATH="/var/lib/sing-box" +SYSTEMD_SERVICE_PATH="/etc/systemd/system" + +DEFAULT_BUILD_TAGS="$(cat "$PROJECT_DIR/release/DEFAULT_BUILD_TAGS_OTHERS")" + +setup_environment() { + if [ -d /usr/local/go ]; then + export PATH="$PATH:/usr/local/go/bin" + fi + + if ! command -v go &> /dev/null; then + echo "Error: Go is not installed or not in PATH" + echo "Run install_go.sh to install Go" + exit 1 + fi +} + +get_build_tags() { + local extra_tags="$1" + if [ -n "$extra_tags" ]; then + echo "${DEFAULT_BUILD_TAGS},${extra_tags}" + else + echo "${DEFAULT_BUILD_TAGS}" + fi +} + +get_version() { + cd "$PROJECT_DIR" + GOHOSTOS=$(go env GOHOSTOS) + GOHOSTARCH=$(go env GOHOSTARCH) + CGO_ENABLED=0 GOOS=$GOHOSTOS GOARCH=$GOHOSTARCH go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest +} + +get_ldflags() { + local version + version=$(get_version) + local shared_ldflags + shared_ldflags=$(cat "$PROJECT_DIR/release/LDFLAGS") + echo "-X 'github.com/sagernet/sing-box/constant.Version=${version}' ${shared_ldflags} -s -w -buildid=" +} + +build_sing_box() { + local tags="$1" + local ldflags + ldflags=$(get_ldflags) + + echo "Building sing-box with tags: $tags" + cd "$PROJECT_DIR" + export GOTOOLCHAIN=local + go install -v -trimpath -ldflags "$ldflags" -tags "$tags" ./cmd/sing-box +} + +install_binary() { + local gopath + gopath=$(go env GOPATH) + echo "Installing binary to $INSTALL_BIN_PATH/$BINARY_NAME" + sudo cp "${gopath}/bin/${BINARY_NAME}" "${INSTALL_BIN_PATH}/" +} + +setup_config() { + echo "Setting up configuration" + sudo mkdir -p "$INSTALL_CONFIG_PATH" + if [ ! -f "$INSTALL_CONFIG_PATH/config.json" ]; then + sudo cp "$PROJECT_DIR/release/config/config.json" "$INSTALL_CONFIG_PATH/config.json" + echo "Default config installed to $INSTALL_CONFIG_PATH/config.json" + else + echo "Config already exists at $INSTALL_CONFIG_PATH/config.json (not overwriting)" + fi +} + +setup_systemd() { + echo "Setting up systemd service" + sudo cp "$SCRIPT_DIR/sing-box.service" "$SYSTEMD_SERVICE_PATH/" + sudo systemctl daemon-reload +} + +stop_service() { + if systemctl is-active --quiet sing-box; then + echo "Stopping sing-box service" + sudo systemctl stop sing-box + fi +} + +start_service() { + echo "Starting sing-box service" + sudo systemctl start sing-box +} + +restart_service() { + echo "Restarting sing-box service" + sudo systemctl restart sing-box +} diff --git a/release/local/debug.sh b/release/local/debug.sh index d6bd3057ef..d865199922 100755 --- a/release/local/debug.sh +++ b/release/local/debug.sh @@ -2,21 +2,25 @@ set -e -o pipefail -if [ -d /usr/local/go ]; then - export PATH="$PATH:/usr/local/go/bin" -fi +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "$SCRIPT_DIR/common.sh" -DIR=$(dirname "$0") -PROJECT=$DIR/../.. +setup_environment -pushd $PROJECT +echo "Updating sing-box from git repository..." +cd "$PROJECT_DIR" git fetch git reset FETCH_HEAD --hard git clean -fdx -go install -v -trimpath -ldflags "-s -w -buildid=" -tags with_quic,with_acme,debug ./cmd/sing-box -popd -sudo systemctl stop sing-box -sudo cp $(go env GOPATH)/bin/sing-box /usr/local/bin/ -sudo systemctl start sing-box +BUILD_TAGS=$(get_build_tags "debug") + +build_sing_box "$BUILD_TAGS" + +stop_service +install_binary +start_service + +echo "" +echo "Following service logs (Ctrl+C to exit)..." sudo journalctl -u sing-box --output cat -f diff --git a/release/local/install.sh b/release/local/install.sh index 24e9d006e5..d5bf94fcc4 100755 --- a/release/local/install.sh +++ b/release/local/install.sh @@ -2,19 +2,18 @@ set -e -o pipefail -if [ -d /usr/local/go ]; then - export PATH="$PATH:/usr/local/go/bin" -fi +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "$SCRIPT_DIR/common.sh" -DIR=$(dirname "$0") -PROJECT=$DIR/../.. +setup_environment -pushd $PROJECT -go install -v -trimpath -ldflags "-s -w -buildid=" -tags with_quic,with_wireguard,with_acme ./cmd/sing-box -popd +BUILD_TAGS=$(get_build_tags) -sudo cp $(go env GOPATH)/bin/sing-box /usr/local/bin/ -sudo mkdir -p /usr/local/etc/sing-box -sudo cp $PROJECT/release/config/config.json /usr/local/etc/sing-box/config.json -sudo cp $DIR/sing-box.service /etc/systemd/system -sudo systemctl daemon-reload +build_sing_box "$BUILD_TAGS" +install_binary +setup_config +setup_systemd + +echo "" +echo "Installation complete!" +echo "To enable and start the service, run: $SCRIPT_DIR/enable.sh" diff --git a/release/local/reinstall.sh b/release/local/reinstall.sh index 71d071095e..1daaa18104 100755 --- a/release/local/reinstall.sh +++ b/release/local/reinstall.sh @@ -2,17 +2,18 @@ set -e -o pipefail -if [ -d /usr/local/go ]; then - export PATH="$PATH:/usr/local/go/bin" -fi +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "$SCRIPT_DIR/common.sh" -DIR=$(dirname "$0") -PROJECT=$DIR/../.. +setup_environment -pushd $PROJECT -go install -v -trimpath -ldflags "-s -w -buildid=" -tags with_quic,with_wireguard,with_acme ./cmd/sing-box -popd +BUILD_TAGS=$(get_build_tags) -sudo systemctl stop sing-box -sudo cp $(go env GOPATH)/bin/sing-box /usr/local/bin/ -sudo systemctl start sing-box +build_sing_box "$BUILD_TAGS" + +stop_service +install_binary +start_service + +echo "" +echo "Reinstallation complete!" diff --git a/release/local/sing-box.service b/release/local/sing-box.service index 7dfd6f7950..9a152ade8a 100644 --- a/release/local/sing-box.service +++ b/release/local/sing-box.service @@ -4,8 +4,8 @@ Documentation=https://sing-box.sagernet.org After=network.target nss-lookup.target network-online.target [Service] -CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH -AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH +CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH +AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH ExecStart=/usr/local/bin/sing-box -D /var/lib/sing-box -C /usr/local/etc/sing-box run ExecReload=/bin/kill -HUP $MAINPID Restart=on-failure diff --git a/release/local/uninstall.sh b/release/local/uninstall.sh index d40107ba91..b9c89ab0dc 100755 --- a/release/local/uninstall.sh +++ b/release/local/uninstall.sh @@ -1,8 +1,30 @@ #!/usr/bin/env bash -sudo systemctl stop sing-box -sudo rm -rf /var/lib/sing-box -sudo rm -rf /usr/local/bin/sing-box -sudo rm -rf /usr/local/etc/sing-box -sudo rm -rf /etc/systemd/system/sing-box.service +set -e -o pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "$SCRIPT_DIR/common.sh" + +echo "Uninstalling sing-box..." + +if systemctl is-active --quiet sing-box 2>/dev/null; then + echo "Stopping sing-box service..." + sudo systemctl stop sing-box +fi + +if systemctl is-enabled --quiet sing-box 2>/dev/null; then + echo "Disabling sing-box service..." + sudo systemctl disable sing-box +fi + +echo "Removing files..." +sudo rm -rf "$INSTALL_DATA_PATH" +sudo rm -rf "$INSTALL_BIN_PATH/$BINARY_NAME" +sudo rm -rf "$INSTALL_CONFIG_PATH" +sudo rm -rf "$SYSTEMD_SERVICE_PATH/sing-box.service" + +echo "Reloading systemd..." sudo systemctl daemon-reload + +echo "" +echo "Uninstallation complete!" diff --git a/release/local/update.sh b/release/local/update.sh index 86ea315d36..2331d2703e 100755 --- a/release/local/update.sh +++ b/release/local/update.sh @@ -2,13 +2,15 @@ set -e -o pipefail -DIR=$(dirname "$0") -PROJECT=$DIR/../.. +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "$SCRIPT_DIR/common.sh" -pushd $PROJECT +echo "Updating sing-box from git repository..." +cd "$PROJECT_DIR" git fetch git reset FETCH_HEAD --hard git clean -fdx -popd -$DIR/reinstall.sh \ No newline at end of file +echo "" +echo "Running reinstall..." +exec "$SCRIPT_DIR/reinstall.sh" \ No newline at end of file diff --git a/replace/psiphon-quic-go b/replace/psiphon-quic-go new file mode 160000 index 0000000000..47042a7c24 --- /dev/null +++ b/replace/psiphon-quic-go @@ -0,0 +1 @@ +Subproject commit 47042a7c2475c081b370b8c9da2c22525774b27b diff --git a/replace/psiphon-tls b/replace/psiphon-tls new file mode 160000 index 0000000000..4af85c2fb9 --- /dev/null +++ b/replace/psiphon-tls @@ -0,0 +1 @@ +Subproject commit 4af85c2fb9f25576c15ccdb71d8299581dcd47fd diff --git a/replace/tailscale b/replace/tailscale new file mode 160000 index 0000000000..788aa623ed --- /dev/null +++ b/replace/tailscale @@ -0,0 +1 @@ +Subproject commit 788aa623edebf3e9918919cee4c590b177c61ec4 diff --git a/replace/wireguard-go b/replace/wireguard-go new file mode 160000 index 0000000000..b120224503 --- /dev/null +++ b/replace/wireguard-go @@ -0,0 +1 @@ +Subproject commit b12022450359150cfb54790bc7316dee899e2336 diff --git a/route/conn.go b/route/conn.go new file mode 100644 index 0000000000..a7dd779598 --- /dev/null +++ b/route/conn.go @@ -0,0 +1,432 @@ +package route + +import ( + "context" + "io" + "net" + "net/netip" + "os" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/tlsfragment" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + "github.com/sagernet/sing/common/canceler" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/x/list" +) + +var _ adapter.ConnectionManager = (*ConnectionManager)(nil) + +type ConnectionManager struct { + logger logger.ContextLogger + access sync.Mutex + connections list.List[io.Closer] +} + +func NewConnectionManager(logger logger.ContextLogger) *ConnectionManager { + return &ConnectionManager{ + logger: logger, + } +} + +func (m *ConnectionManager) Start(stage adapter.StartStage) error { + return nil +} + +func (m *ConnectionManager) Count() int { + return m.connections.Len() +} + +func (m *ConnectionManager) CloseAll() { + m.access.Lock() + var closers []io.Closer + for element := m.connections.Front(); element != nil; { + nextElement := element.Next() + closers = append(closers, element.Value) + m.connections.Remove(element) + element = nextElement + } + m.access.Unlock() + for _, closer := range closers { + common.Close(closer) + } +} + +func (m *ConnectionManager) Close() error { + m.CloseAll() + return nil +} + +func (m *ConnectionManager) TrackConn(conn net.Conn) net.Conn { + m.access.Lock() + element := m.connections.PushBack(conn) + m.access.Unlock() + return &trackedConn{ + Conn: conn, + manager: m, + element: element, + } +} + +func (m *ConnectionManager) TrackPacketConn(conn net.PacketConn) net.PacketConn { + m.access.Lock() + element := m.connections.PushBack(conn) + m.access.Unlock() + return &trackedPacketConn{ + PacketConn: conn, + manager: m, + element: element, + } +} + +func (m *ConnectionManager) NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + ctx = adapter.WithContext(ctx, &metadata) + var ( + remoteConn net.Conn + err error + ) + if len(metadata.DestinationAddresses) > 0 || metadata.Destination.IsIP() { + remoteConn, err = dialer.DialSerialNetwork(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) + } else { + remoteConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination) + } + if err != nil { + var remoteString string + if len(metadata.DestinationAddresses) > 0 { + remoteString = "[" + strings.Join(common.Map(metadata.DestinationAddresses, netip.Addr.String), ",") + "]" + } else { + remoteString = metadata.Destination.String() + } + var dialerString string + if outbound, isOutbound := this.(adapter.Outbound); isOutbound { + dialerString = " using outbound/" + outbound.Type() + "[" + outbound.Tag() + "]" + if outbound.Type() == C.TypeBalancer { + dialerString += "[" + metadata.GetRealOutbound() + "]" + } + } + err = E.Cause(err, "open connection to ", remoteString, dialerString) + N.CloseOnHandshakeFailure(conn, onClose, err) + m.logger.ErrorContext(ctx, err) + return + } + err = N.ReportConnHandshakeSuccess(conn, remoteConn) + if err != nil { + err = E.Cause(err, "report handshake success") + remoteConn.Close() + N.CloseOnHandshakeFailure(conn, onClose, err) + m.logger.ErrorContext(ctx, err) + return + } + if metadata.TLSFragment || metadata.TLSRecordFragment { + remoteConn = tf.NewConn(remoteConn, ctx, metadata.TLSFragment, metadata.TLSRecordFragment, metadata.TLSFragmentFallbackDelay) + } + var done atomic.Bool + if m.kickWriteHandshake(ctx, conn, remoteConn, false, &done, onClose) { + return + } + if m.kickWriteHandshake(ctx, remoteConn, conn, true, &done, onClose) { + return + } + go m.connectionCopy(ctx, conn, remoteConn, false, &done, onClose) + go m.connectionCopy(ctx, remoteConn, conn, true, &done, onClose) +} + +func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + ctx = adapter.WithContext(ctx, &metadata) + var ( + remotePacketConn net.PacketConn + remoteConn net.Conn + destinationAddress netip.Addr + err error + ) + if metadata.UDPConnect { + parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer) + if len(metadata.DestinationAddresses) > 0 { + if isParallelDialer { + remoteConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) + } else { + remoteConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses) + } + } else if metadata.Destination.IsIP() { + if isParallelDialer { + remoteConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) + } else { + remoteConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination) + } + } else { + remoteConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination) + } + if err != nil { + var remoteString string + if len(metadata.DestinationAddresses) > 0 { + remoteString = "[" + strings.Join(common.Map(metadata.DestinationAddresses, netip.Addr.String), ",") + "]" + } else { + remoteString = metadata.Destination.String() + } + var dialerString string + if outbound, isOutbound := this.(adapter.Outbound); isOutbound { + dialerString = " using outbound/" + outbound.Type() + "[" + outbound.Tag() + "]" + if outbound.Type() == C.TypeBalancer { + dialerString += "[" + metadata.GetRealOutbound() + "]" + } + } + err = E.Cause(err, "open packet connection to ", remoteString, dialerString) + N.CloseOnHandshakeFailure(conn, onClose, err) + m.logger.ErrorContext(ctx, err) + return + } + remotePacketConn = bufio.NewUnbindPacketConn(remoteConn) + connRemoteAddr := M.AddrFromNet(remoteConn.RemoteAddr()) + if connRemoteAddr != metadata.Destination.Addr { + destinationAddress = connRemoteAddr + } + } else { + if len(metadata.DestinationAddresses) > 0 { + remotePacketConn, destinationAddress, err = dialer.ListenSerialNetworkPacket(ctx, this, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) + } else if packetDialer, withDestination := this.(dialer.PacketDialerWithDestination); withDestination { + remotePacketConn, destinationAddress, err = packetDialer.ListenPacketWithDestination(ctx, metadata.Destination) + } else { + remotePacketConn, err = this.ListenPacket(ctx, metadata.Destination) + } + if err != nil { + var dialerString string + if outbound, isOutbound := this.(adapter.Outbound); isOutbound { + dialerString = " using outbound/" + outbound.Type() + "[" + outbound.Tag() + "]" + if outbound.Type() == C.TypeBalancer { + dialerString += "[" + metadata.GetRealOutbound() + "]" + } + } + err = E.Cause(err, "listen packet connection using ", dialerString) + N.CloseOnHandshakeFailure(conn, onClose, err) + m.logger.ErrorContext(ctx, err) + return + } + } + err = N.ReportPacketConnHandshakeSuccess(conn, remotePacketConn) + if err != nil { + conn.Close() + remotePacketConn.Close() + m.logger.ErrorContext(ctx, "report handshake success: ", err) + return + } + if destinationAddress.IsValid() { + var originDestination M.Socksaddr + if metadata.RouteOriginalDestination.IsValid() { + originDestination = metadata.RouteOriginalDestination + } else { + originDestination = metadata.Destination + } + if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded { + natConn.UpdateDestination(destinationAddress) + } else { + destination := M.SocksaddrFrom(destinationAddress, metadata.Destination.Port) + if metadata.Destination != destination { + if metadata.UDPDisableDomainUnmapping { + remotePacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(remotePacketConn), destination, originDestination) + } else { + remotePacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(remotePacketConn), destination, originDestination) + } + } else if metadata.RouteOriginalDestination.IsValid() && metadata.RouteOriginalDestination != metadata.Destination { + remotePacketConn = bufio.NewDestinationNATPacketConn(bufio.NewPacketConn(remotePacketConn), metadata.Destination, metadata.RouteOriginalDestination) + } + } + } else if metadata.RouteOriginalDestination.IsValid() && metadata.RouteOriginalDestination != metadata.Destination { + remotePacketConn = bufio.NewDestinationNATPacketConn(bufio.NewPacketConn(remotePacketConn), metadata.Destination, metadata.RouteOriginalDestination) + } + var udpTimeout time.Duration + if metadata.UDPTimeout > 0 { + udpTimeout = metadata.UDPTimeout + } else { + protocol := metadata.Protocol + if protocol == "" { + protocol = C.PortProtocols[metadata.Destination.Port] + } + if protocol != "" { + udpTimeout = C.ProtocolTimeouts[protocol] + } + } + if udpTimeout > 0 { + ctx, conn = canceler.NewPacketConn(ctx, conn, udpTimeout) + } + destination := bufio.NewPacketConn(remotePacketConn) + var done atomic.Bool + go m.packetConnectionCopy(ctx, conn, destination, false, &done, onClose) + go m.packetConnectionCopy(ctx, destination, conn, true, &done, onClose) +} + +func (m *ConnectionManager) connectionCopy(ctx context.Context, source net.Conn, destination net.Conn, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) { + _, err := bufio.CopyWithIncreateBuffer(destination, source, bufio.DefaultIncreaseBufferAfter, bufio.DefaultBatchSize) + if err != nil { + common.Close(source, destination) + } else if duplexDst, isDuplex := destination.(N.WriteCloser); isDuplex { + err = duplexDst.CloseWrite() + if err != nil { + common.Close(source, destination) + } + } else { + destination.Close() + } + if done.Swap(true) { + if onClose != nil { + onClose(err) + } + common.Close(source, destination) + } + if !direction { + if err == nil { + m.logger.DebugContext(ctx, "connection upload finished") + } else if !E.IsClosedOrCanceled(err) && !strings.Contains(err.Error(), "NO_ERROR") { + m.logger.ErrorContext(ctx, "connection upload closed: ", err) + } else { + m.logger.TraceContext(ctx, "connection upload closed") + } + } else { + if err == nil { + m.logger.DebugContext(ctx, "connection download finished") + } else if !E.IsClosedOrCanceled(err) && !strings.Contains(err.Error(), "NO_ERROR") && !strings.Contains(err.Error(), "response body closed") { + m.logger.ErrorContext(ctx, "connection download closed: ", err) + } else { + m.logger.TraceContext(ctx, "connection download closed") + } + } +} + +func (m *ConnectionManager) kickWriteHandshake(ctx context.Context, source net.Conn, destination net.Conn, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) bool { + if !N.NeedHandshakeForWrite(destination) { + return false + } + var ( + cachedBuffer *buf.Buffer + wrotePayload bool + ) + sourceReader, readCounters := N.UnwrapCountReader(source, nil) + destinationWriter, writeCounters := N.UnwrapCountWriter(destination, nil) + if cachedReader, ok := sourceReader.(N.CachedReader); ok { + cachedBuffer = cachedReader.ReadCached() + } + var err error + if cachedBuffer != nil { + wrotePayload = true + dataLen := cachedBuffer.Len() + _, err = destinationWriter.Write(cachedBuffer.Bytes()) + cachedBuffer.Release() + if err == nil { + for _, counter := range readCounters { + counter(int64(dataLen)) + } + for _, counter := range writeCounters { + counter(int64(dataLen)) + } + } + } else { + _ = destination.SetWriteDeadline(time.Now().Add(C.ReadPayloadTimeout)) + _, err = destinationWriter.Write(nil) + _ = destination.SetWriteDeadline(time.Time{}) + } + if err == nil { + return false + } + if !wrotePayload && (E.IsMulti(err, os.ErrInvalid, context.DeadlineExceeded, io.EOF) || E.IsTimeout(err)) { + return false + } + if !done.Swap(true) { + if onClose != nil { + onClose(err) + } + } + common.Close(source, destination) + if !direction { + m.logger.ErrorContext(ctx, "connection upload handshake: ", err) + } else { + m.logger.ErrorContext(ctx, "connection download handshake: ", err) + } + return true +} + +func (m *ConnectionManager) packetConnectionCopy(ctx context.Context, source N.PacketReader, destination N.PacketWriter, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) { + _, err := bufio.CopyPacket(destination, source) + if !direction { + if err == nil { + m.logger.DebugContext(ctx, "packet upload finished") + } else if E.IsClosedOrCanceled(err) { + m.logger.TraceContext(ctx, "packet upload closed") + } else { + m.logger.DebugContext(ctx, "packet upload closed: ", err) + } + } else { + if err == nil { + m.logger.DebugContext(ctx, "packet download finished") + } else if E.IsClosedOrCanceled(err) { + m.logger.TraceContext(ctx, "packet download closed") + } else { + m.logger.DebugContext(ctx, "packet download closed: ", err) + } + } + if !done.Swap(true) { + if onClose != nil { + onClose(err) + } + } + common.Close(source, destination) +} + +type trackedConn struct { + net.Conn + manager *ConnectionManager + element *list.Element[io.Closer] +} + +func (c *trackedConn) Close() error { + c.manager.access.Lock() + c.manager.connections.Remove(c.element) + c.manager.access.Unlock() + return c.Conn.Close() +} + +func (c *trackedConn) Upstream() any { + return c.Conn +} + +func (c *trackedConn) ReaderReplaceable() bool { + return true +} + +func (c *trackedConn) WriterReplaceable() bool { + return true +} + +type trackedPacketConn struct { + net.PacketConn + manager *ConnectionManager + element *list.Element[io.Closer] +} + +func (c *trackedPacketConn) Close() error { + c.manager.access.Lock() + c.manager.connections.Remove(c.element) + c.manager.access.Unlock() + return c.PacketConn.Close() +} + +func (c *trackedPacketConn) Upstream() any { + return bufio.NewPacketConn(c.PacketConn) +} + +func (c *trackedPacketConn) ReaderReplaceable() bool { + return true +} + +func (c *trackedPacketConn) WriterReplaceable() bool { + return true +} diff --git a/route/dns.go b/route/dns.go new file mode 100644 index 0000000000..dee4a756a7 --- /dev/null +++ b/route/dns.go @@ -0,0 +1,109 @@ +package route + +import ( + "context" + "net" + "time" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + dnsOutbound "github.com/sagernet/sing-box/protocol/dns" + R "github.com/sagernet/sing-box/route/rule" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/udpnat2" + + mDNS "github.com/miekg/dns" +) + +func (r *Router) hijackDNSStream(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + metadata.Destination = M.Socksaddr{} + for { + conn.SetReadDeadline(time.Now().Add(C.DNSTimeout)) + err := dnsOutbound.HandleStreamDNSRequest(ctx, r.dns, conn, metadata) + if err != nil { + if !E.IsClosedOrCanceled(err) { + return err + } else { + return nil + } + } + } +} + +func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetBuffers []*N.PacketBuffer, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { + if natConn, isNatConn := conn.(udpnat.Conn); isNatConn { + metadata.Destination = M.Socksaddr{} + for _, packet := range packetBuffers { + buffer := packet.Buffer + destination := packet.Destination + N.PutPacketBuffer(packet) + go ExchangeDNSPacket(ctx, r.dns, r.logger, natConn, buffer, metadata, destination) + } + natConn.SetHandler(&dnsHijacker{ + router: r.dns, + logger: r.logger, + conn: conn, + ctx: ctx, + metadata: metadata, + onClose: onClose, + }) + return nil + } + err := dnsOutbound.NewDNSPacketConnection(ctx, r.dns, conn, packetBuffers, metadata) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil && !E.IsClosedOrCanceled(err) { + return E.Cause(err, "process DNS packet") + } + return nil +} + +func ExchangeDNSPacket(ctx context.Context, router adapter.DNSRouter, logger logger.ContextLogger, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext, destination M.Socksaddr) { + err := exchangeDNSPacket(ctx, router, conn, buffer, metadata, destination) + if err != nil && !R.IsRejected(err) && !E.IsClosedOrCanceled(err) { + logger.ErrorContext(ctx, E.Cause(err, "process DNS packet")) + } +} + +func exchangeDNSPacket(ctx context.Context, router adapter.DNSRouter, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext, destination M.Socksaddr) error { + var message mDNS.Msg + err := message.Unpack(buffer.Bytes()) + buffer.Release() + if err != nil { + return E.Cause(err, "unpack request") + } + response, err := router.Exchange(adapter.WithContext(ctx, &metadata), &message, adapter.DNSQueryOptions{}) + if err != nil { + return err + } + responseBuffer, err := dns.TruncateDNSMessage(&message, response, 1024) + if err != nil { + return err + } + err = conn.WritePacket(responseBuffer, destination) + return err +} + +type dnsHijacker struct { + router adapter.DNSRouter + logger logger.ContextLogger + conn N.PacketConn + ctx context.Context + metadata adapter.InboundContext + onClose N.CloseHandlerFunc +} + +func (h *dnsHijacker) NewPacketEx(buffer *buf.Buffer, destination M.Socksaddr) { + go ExchangeDNSPacket(h.ctx, h.router, h.logger, h.conn, buffer, h.metadata, destination) +} + +func (h *dnsHijacker) Close() error { + if h.onClose != nil { + h.onClose(nil) + } + return nil +} diff --git a/route/neighbor_resolver_darwin.go b/route/neighbor_resolver_darwin.go new file mode 100644 index 0000000000..cc24ec73ec --- /dev/null +++ b/route/neighbor_resolver_darwin.go @@ -0,0 +1,245 @@ +//go:build darwin + +package route + +import ( + "net" + "net/netip" + "os" + "sync" + "time" + + "github.com/sagernet/fswatch" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + + "golang.org/x/net/route" + "golang.org/x/sys/unix" +) + +var defaultLeaseFiles = []string{ + "/var/db/dhcpd_leases", + "/tmp/dhcp.leases", +} + +type neighborResolver struct { + logger logger.ContextLogger + leaseFiles []string + access sync.RWMutex + neighborIPToMAC map[netip.Addr]net.HardwareAddr + leaseIPToMAC map[netip.Addr]net.HardwareAddr + ipToHostname map[netip.Addr]string + macToHostname map[string]string + watcher *fswatch.Watcher + done chan struct{} +} + +func newNeighborResolver(resolverLogger logger.ContextLogger, leaseFiles []string) (adapter.NeighborResolver, error) { + if len(leaseFiles) == 0 { + for _, path := range defaultLeaseFiles { + info, err := os.Stat(path) + if err == nil && info.Size() > 0 { + leaseFiles = append(leaseFiles, path) + } + } + } + return &neighborResolver{ + logger: resolverLogger, + leaseFiles: leaseFiles, + neighborIPToMAC: make(map[netip.Addr]net.HardwareAddr), + leaseIPToMAC: make(map[netip.Addr]net.HardwareAddr), + ipToHostname: make(map[netip.Addr]string), + macToHostname: make(map[string]string), + done: make(chan struct{}), + }, nil +} + +func (r *neighborResolver) Start() error { + err := r.loadNeighborTable() + if err != nil { + r.logger.Warn(E.Cause(err, "load neighbor table")) + } + r.doReloadLeaseFiles() + go r.subscribeNeighborUpdates() + if len(r.leaseFiles) > 0 { + watcher, err := fswatch.NewWatcher(fswatch.Options{ + Path: r.leaseFiles, + Logger: r.logger, + Callback: func(_ string) { + r.doReloadLeaseFiles() + }, + }) + if err != nil { + r.logger.Warn(E.Cause(err, "create lease file watcher")) + } else { + r.watcher = watcher + err = watcher.Start() + if err != nil { + r.logger.Warn(E.Cause(err, "start lease file watcher")) + } + } + } + return nil +} + +func (r *neighborResolver) Close() error { + close(r.done) + if r.watcher != nil { + return r.watcher.Close() + } + return nil +} + +func (r *neighborResolver) LookupMAC(address netip.Addr) (net.HardwareAddr, bool) { + r.access.RLock() + defer r.access.RUnlock() + mac, found := r.neighborIPToMAC[address] + if found { + return mac, true + } + mac, found = r.leaseIPToMAC[address] + if found { + return mac, true + } + mac, found = extractMACFromEUI64(address) + if found { + return mac, true + } + return nil, false +} + +func (r *neighborResolver) LookupAddresses(hostname string) []netip.Addr { + r.access.RLock() + defer r.access.RUnlock() + return lookupAddressesByHostname(hostname, r.ipToHostname, r.macToHostname, r.neighborIPToMAC, r.leaseIPToMAC) +} + +func (r *neighborResolver) LookupHostname(address netip.Addr) (string, bool) { + r.access.RLock() + defer r.access.RUnlock() + hostname, found := r.ipToHostname[address] + if found { + return hostname, true + } + mac, macFound := r.neighborIPToMAC[address] + if !macFound { + mac, macFound = r.leaseIPToMAC[address] + } + if !macFound { + mac, macFound = extractMACFromEUI64(address) + } + if macFound { + hostname, found = r.macToHostname[mac.String()] + if found { + return hostname, true + } + } + return "", false +} + +func (r *neighborResolver) loadNeighborTable() error { + entries, err := ReadNeighborEntries() + if err != nil { + return err + } + r.access.Lock() + defer r.access.Unlock() + for _, entry := range entries { + r.neighborIPToMAC[entry.Address] = entry.MACAddress + } + return nil +} + +func (r *neighborResolver) subscribeNeighborUpdates() { + routeSocket, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0) + if err != nil { + r.logger.Warn(E.Cause(err, "subscribe neighbor updates")) + return + } + err = unix.SetNonblock(routeSocket, true) + if err != nil { + unix.Close(routeSocket) + r.logger.Warn(E.Cause(err, "set route socket nonblock")) + return + } + routeSocketFile := os.NewFile(uintptr(routeSocket), "route") + defer routeSocketFile.Close() + buffer := buf.NewPacket() + defer buffer.Release() + for { + select { + case <-r.done: + return + default: + } + err = setReadDeadline(routeSocketFile, 3*time.Second) + if err != nil { + r.logger.Warn(E.Cause(err, "set route socket read deadline")) + return + } + n, err := routeSocketFile.Read(buffer.FreeBytes()) + if err != nil { + if nerr, ok := err.(net.Error); ok && nerr.Timeout() { + continue + } + select { + case <-r.done: + return + default: + } + r.logger.Warn(E.Cause(err, "receive neighbor update")) + continue + } + messages, err := route.ParseRIB(route.RIBTypeRoute, buffer.FreeBytes()[:n]) + if err != nil { + continue + } + for _, message := range messages { + routeMessage, isRouteMessage := message.(*route.RouteMessage) + if !isRouteMessage { + continue + } + if routeMessage.Flags&unix.RTF_LLINFO == 0 { + continue + } + address, mac, isDelete, ok := ParseRouteNeighborMessage(routeMessage) + if !ok { + continue + } + r.access.Lock() + if isDelete { + delete(r.neighborIPToMAC, address) + } else { + r.neighborIPToMAC[address] = mac + } + r.access.Unlock() + } + } +} + +func (r *neighborResolver) doReloadLeaseFiles() { + leaseIPToMAC, ipToHostname, macToHostname := ReloadLeaseFiles(r.leaseFiles) + r.access.Lock() + r.leaseIPToMAC = leaseIPToMAC + r.ipToHostname = ipToHostname + r.macToHostname = macToHostname + r.access.Unlock() +} + +func setReadDeadline(file *os.File, timeout time.Duration) error { + rawConn, err := file.SyscallConn() + if err != nil { + return err + } + var controlErr error + err = rawConn.Control(func(fd uintptr) { + tv := unix.NsecToTimeval(int64(timeout)) + controlErr = unix.SetsockoptTimeval(int(fd), unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv) + }) + if err != nil { + return err + } + return controlErr +} diff --git a/route/neighbor_resolver_hostname.go b/route/neighbor_resolver_hostname.go new file mode 100644 index 0000000000..d030bd1cd3 --- /dev/null +++ b/route/neighbor_resolver_hostname.go @@ -0,0 +1,56 @@ +package route + +import ( + "net" + "net/netip" + "strings" + + "github.com/sagernet/sing-box/dns" +) + +func lookupAddressesByHostname( + hostname string, + ipToHostname map[netip.Addr]string, + macToHostname map[string]string, + ipToMACTables ...map[netip.Addr]net.HardwareAddr, +) []netip.Addr { + hostname = dns.FqdnToDomain(hostname) + if hostname == "" { + return nil + } + resultSet := make(map[netip.Addr]struct{}) + var result []netip.Addr + addAddress := func(address netip.Addr) { + if isScopedIPv6Address(address) { + return + } + if _, exists := resultSet[address]; exists { + return + } + resultSet[address] = struct{}{} + result = append(result, address) + } + for address, entryHostname := range ipToHostname { + if strings.EqualFold(entryHostname, hostname) { + addAddress(address) + } + } + for mac, entryHostname := range macToHostname { + if !strings.EqualFold(entryHostname, hostname) { + continue + } + for _, table := range ipToMACTables { + for address, entryMAC := range table { + if entryMAC.String() == mac { + addAddress(address) + } + } + } + } + return result +} + +func isScopedIPv6Address(address netip.Addr) bool { + // DNS AAAA records cannot carry an interface zone. + return address.Is6() && (address.IsLinkLocalUnicast() || address.Zone() != "") +} diff --git a/route/neighbor_resolver_lease.go b/route/neighbor_resolver_lease.go new file mode 100644 index 0000000000..e3f9c0b464 --- /dev/null +++ b/route/neighbor_resolver_lease.go @@ -0,0 +1,386 @@ +package route + +import ( + "bufio" + "encoding/hex" + "net" + "net/netip" + "os" + "strconv" + "strings" + "time" +) + +func parseLeaseFile(path string, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) { + file, err := os.Open(path) + if err != nil { + return + } + defer file.Close() + if strings.HasSuffix(path, "dhcpd_leases") { + parseBootpdLeases(file, ipToMAC, ipToHostname, macToHostname) + return + } + if strings.HasSuffix(path, "kea-leases4.csv") { + parseKeaCSV4(file, ipToMAC, ipToHostname, macToHostname) + return + } + if strings.HasSuffix(path, "kea-leases6.csv") { + parseKeaCSV6(file, ipToMAC, ipToHostname, macToHostname) + return + } + if strings.HasSuffix(path, "dhcpd.leases") { + parseISCDhcpd(file, ipToMAC, ipToHostname, macToHostname) + return + } + parseDnsmasqOdhcpd(file, ipToMAC, ipToHostname, macToHostname) +} + +func ReloadLeaseFiles(leaseFiles []string) (leaseIPToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) { + leaseIPToMAC = make(map[netip.Addr]net.HardwareAddr) + ipToHostname = make(map[netip.Addr]string) + macToHostname = make(map[string]string) + for _, path := range leaseFiles { + parseLeaseFile(path, leaseIPToMAC, ipToHostname, macToHostname) + } + return +} + +func parseDnsmasqOdhcpd(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) { + now := time.Now().Unix() + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "duid ") { + continue + } + if strings.HasPrefix(line, "# ") { + parseOdhcpdLine(line[2:], ipToMAC, ipToHostname, macToHostname) + continue + } + fields := strings.Fields(line) + if len(fields) < 4 { + continue + } + expiry, err := strconv.ParseInt(fields[0], 10, 64) + if err != nil { + continue + } + if expiry != 0 && expiry < now { + continue + } + if strings.Contains(fields[1], ":") { + mac, macErr := net.ParseMAC(fields[1]) + if macErr != nil { + continue + } + address, addrOK := netip.AddrFromSlice(net.ParseIP(fields[2])) + if !addrOK { + continue + } + address = address.Unmap() + ipToMAC[address] = mac + hostname := fields[3] + if hostname != "*" { + ipToHostname[address] = hostname + macToHostname[mac.String()] = hostname + } + } else { + var mac net.HardwareAddr + if len(fields) >= 5 { + duid, duidErr := parseDUID(fields[4]) + if duidErr == nil { + mac, _ = extractMACFromDUID(duid) + } + } + address, addrOK := netip.AddrFromSlice(net.ParseIP(fields[2])) + if !addrOK { + continue + } + address = address.Unmap() + if mac != nil { + ipToMAC[address] = mac + } + hostname := fields[3] + if hostname != "*" { + ipToHostname[address] = hostname + if mac != nil { + macToHostname[mac.String()] = hostname + } + } + } + } +} + +func parseOdhcpdLine(line string, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) { + fields := strings.Fields(line) + if len(fields) < 5 { + return + } + validTime, err := strconv.ParseInt(fields[4], 10, 64) + if err != nil { + return + } + if validTime == 0 { + return + } + if validTime > 0 && validTime < time.Now().Unix() { + return + } + hostname := fields[3] + if hostname == "-" || strings.HasPrefix(hostname, `broken\x20`) { + hostname = "" + } + if len(fields) >= 8 && fields[2] == "ipv4" { + mac, macErr := net.ParseMAC(fields[1]) + if macErr != nil { + return + } + addressField := fields[7] + slashIndex := strings.IndexByte(addressField, '/') + if slashIndex >= 0 { + addressField = addressField[:slashIndex] + } + address, addrOK := netip.AddrFromSlice(net.ParseIP(addressField)) + if !addrOK { + return + } + address = address.Unmap() + ipToMAC[address] = mac + if hostname != "" { + ipToHostname[address] = hostname + macToHostname[mac.String()] = hostname + } + return + } + var mac net.HardwareAddr + duidHex := fields[1] + duidBytes, hexErr := hex.DecodeString(duidHex) + if hexErr == nil { + mac, _ = extractMACFromDUID(duidBytes) + } + for i := 7; i < len(fields); i++ { + addressField := fields[i] + slashIndex := strings.IndexByte(addressField, '/') + if slashIndex >= 0 { + addressField = addressField[:slashIndex] + } + address, addrOK := netip.AddrFromSlice(net.ParseIP(addressField)) + if !addrOK { + continue + } + address = address.Unmap() + if mac != nil { + ipToMAC[address] = mac + } + if hostname != "" { + ipToHostname[address] = hostname + if mac != nil { + macToHostname[mac.String()] = hostname + } + } + } +} + +func parseISCDhcpd(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) { + scanner := bufio.NewScanner(file) + var currentIP netip.Addr + var currentMAC net.HardwareAddr + var currentHostname string + var currentActive bool + var inLease bool + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if strings.HasPrefix(line, "lease ") && strings.HasSuffix(line, "{") { + ipString := strings.TrimSuffix(strings.TrimPrefix(line, "lease "), " {") + parsed, addrOK := netip.AddrFromSlice(net.ParseIP(ipString)) + if addrOK { + currentIP = parsed.Unmap() + inLease = true + currentMAC = nil + currentHostname = "" + currentActive = false + } + continue + } + if line == "}" && inLease { + if currentActive && currentMAC != nil { + ipToMAC[currentIP] = currentMAC + if currentHostname != "" { + ipToHostname[currentIP] = currentHostname + macToHostname[currentMAC.String()] = currentHostname + } + } else { + delete(ipToMAC, currentIP) + delete(ipToHostname, currentIP) + } + inLease = false + continue + } + if !inLease { + continue + } + if strings.HasPrefix(line, "hardware ethernet ") { + macString := strings.TrimSuffix(strings.TrimPrefix(line, "hardware ethernet "), ";") + parsed, macErr := net.ParseMAC(macString) + if macErr == nil { + currentMAC = parsed + } + } else if strings.HasPrefix(line, "client-hostname ") { + hostname := strings.TrimSuffix(strings.TrimPrefix(line, "client-hostname "), ";") + hostname = strings.Trim(hostname, "\"") + if hostname != "" { + currentHostname = hostname + } + } else if strings.HasPrefix(line, "binding state ") { + state := strings.TrimSuffix(strings.TrimPrefix(line, "binding state "), ";") + currentActive = state == "active" + } + } +} + +func parseKeaCSV4(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) { + scanner := bufio.NewScanner(file) + firstLine := true + for scanner.Scan() { + if firstLine { + firstLine = false + continue + } + fields := strings.Split(scanner.Text(), ",") + if len(fields) < 10 { + continue + } + if fields[9] != "0" { + continue + } + address, addrOK := netip.AddrFromSlice(net.ParseIP(fields[0])) + if !addrOK { + continue + } + address = address.Unmap() + mac, macErr := net.ParseMAC(fields[1]) + if macErr != nil { + continue + } + ipToMAC[address] = mac + hostname := "" + if len(fields) > 8 { + hostname = fields[8] + } + if hostname != "" { + ipToHostname[address] = hostname + macToHostname[mac.String()] = hostname + } + } +} + +func parseKeaCSV6(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) { + scanner := bufio.NewScanner(file) + firstLine := true + for scanner.Scan() { + if firstLine { + firstLine = false + continue + } + fields := strings.Split(scanner.Text(), ",") + if len(fields) < 14 { + continue + } + if fields[13] != "0" { + continue + } + address, addrOK := netip.AddrFromSlice(net.ParseIP(fields[0])) + if !addrOK { + continue + } + address = address.Unmap() + var mac net.HardwareAddr + if fields[12] != "" { + mac, _ = net.ParseMAC(fields[12]) + } + if mac == nil { + duid, duidErr := hex.DecodeString(strings.ReplaceAll(fields[1], ":", "")) + if duidErr == nil { + mac, _ = extractMACFromDUID(duid) + } + } + hostname := "" + if len(fields) > 11 { + hostname = fields[11] + } + if mac != nil { + ipToMAC[address] = mac + } + if hostname != "" { + ipToHostname[address] = hostname + if mac != nil { + macToHostname[mac.String()] = hostname + } + } + } +} + +func parseBootpdLeases(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) { + now := time.Now().Unix() + scanner := bufio.NewScanner(file) + var currentName string + var currentIP netip.Addr + var currentMAC net.HardwareAddr + var currentLease int64 + var inBlock bool + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "{" { + inBlock = true + currentName = "" + currentIP = netip.Addr{} + currentMAC = nil + currentLease = 0 + continue + } + if line == "}" && inBlock { + if currentMAC != nil && currentIP.IsValid() { + if currentLease == 0 || currentLease >= now { + ipToMAC[currentIP] = currentMAC + if currentName != "" { + ipToHostname[currentIP] = currentName + macToHostname[currentMAC.String()] = currentName + } + } + } + inBlock = false + continue + } + if !inBlock { + continue + } + key, value, found := strings.Cut(line, "=") + if !found { + continue + } + switch key { + case "name": + currentName = value + case "ip_address": + parsed, addrOK := netip.AddrFromSlice(net.ParseIP(value)) + if addrOK { + currentIP = parsed.Unmap() + } + case "hw_address": + typeAndMAC, hasSep := strings.CutPrefix(value, "1,") + if hasSep { + mac, macErr := net.ParseMAC(typeAndMAC) + if macErr == nil { + currentMAC = mac + } + } + case "lease": + leaseHex := strings.TrimPrefix(value, "0x") + parsed, parseErr := strconv.ParseInt(leaseHex, 16, 64) + if parseErr == nil { + currentLease = parsed + } + } + } +} diff --git a/route/neighbor_resolver_linux.go b/route/neighbor_resolver_linux.go new file mode 100644 index 0000000000..5c6cdcb722 --- /dev/null +++ b/route/neighbor_resolver_linux.go @@ -0,0 +1,230 @@ +//go:build linux + +package route + +import ( + "net" + "net/netip" + "os" + "slices" + "sync" + "time" + + "github.com/sagernet/fswatch" + "github.com/sagernet/sing-box/adapter" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + + "github.com/jsimonetti/rtnetlink" + "github.com/mdlayher/netlink" + "golang.org/x/sys/unix" +) + +var defaultLeaseFiles = []string{ + "/tmp/dhcp.leases", + "/var/lib/dhcp/dhcpd.leases", + "/var/lib/dhcpd/dhcpd.leases", + "/var/lib/kea/kea-leases4.csv", + "/var/lib/kea/kea-leases6.csv", +} + +type neighborResolver struct { + logger logger.ContextLogger + leaseFiles []string + access sync.RWMutex + neighborIPToMAC map[netip.Addr]net.HardwareAddr + leaseIPToMAC map[netip.Addr]net.HardwareAddr + ipToHostname map[netip.Addr]string + macToHostname map[string]string + watcher *fswatch.Watcher + done chan struct{} +} + +func newNeighborResolver(resolverLogger logger.ContextLogger, leaseFiles []string) (adapter.NeighborResolver, error) { + if len(leaseFiles) == 0 { + for _, path := range defaultLeaseFiles { + info, err := os.Stat(path) + if err == nil && info.Size() > 0 { + leaseFiles = append(leaseFiles, path) + } + } + } + return &neighborResolver{ + logger: resolverLogger, + leaseFiles: leaseFiles, + neighborIPToMAC: make(map[netip.Addr]net.HardwareAddr), + leaseIPToMAC: make(map[netip.Addr]net.HardwareAddr), + ipToHostname: make(map[netip.Addr]string), + macToHostname: make(map[string]string), + done: make(chan struct{}), + }, nil +} + +func (r *neighborResolver) Start() error { + err := r.loadNeighborTable() + if err != nil { + r.logger.Warn(E.Cause(err, "load neighbor table")) + } + r.doReloadLeaseFiles() + go r.subscribeNeighborUpdates() + if len(r.leaseFiles) > 0 { + watcher, err := fswatch.NewWatcher(fswatch.Options{ + Path: r.leaseFiles, + Logger: r.logger, + Callback: func(_ string) { + r.doReloadLeaseFiles() + }, + }) + if err != nil { + r.logger.Warn(E.Cause(err, "create lease file watcher")) + } else { + r.watcher = watcher + err = watcher.Start() + if err != nil { + r.logger.Warn(E.Cause(err, "start lease file watcher")) + } + } + } + return nil +} + +func (r *neighborResolver) Close() error { + close(r.done) + if r.watcher != nil { + return r.watcher.Close() + } + return nil +} + +func (r *neighborResolver) LookupMAC(address netip.Addr) (net.HardwareAddr, bool) { + r.access.RLock() + defer r.access.RUnlock() + mac, found := r.neighborIPToMAC[address] + if found { + return mac, true + } + mac, found = r.leaseIPToMAC[address] + if found { + return mac, true + } + mac, found = extractMACFromEUI64(address) + if found { + return mac, true + } + return nil, false +} + +func (r *neighborResolver) LookupAddresses(hostname string) []netip.Addr { + r.access.RLock() + defer r.access.RUnlock() + return lookupAddressesByHostname(hostname, r.ipToHostname, r.macToHostname, r.neighborIPToMAC, r.leaseIPToMAC) +} + +func (r *neighborResolver) LookupHostname(address netip.Addr) (string, bool) { + r.access.RLock() + defer r.access.RUnlock() + hostname, found := r.ipToHostname[address] + if found { + return hostname, true + } + mac, macFound := r.neighborIPToMAC[address] + if !macFound { + mac, macFound = r.leaseIPToMAC[address] + } + if !macFound { + mac, macFound = extractMACFromEUI64(address) + } + if macFound { + hostname, found = r.macToHostname[mac.String()] + if found { + return hostname, true + } + } + return "", false +} + +func (r *neighborResolver) loadNeighborTable() error { + connection, err := rtnetlink.Dial(nil) + if err != nil { + return E.Cause(err, "dial rtnetlink") + } + defer connection.Close() + neighbors, err := connection.Neigh.List() + if err != nil { + return E.Cause(err, "list neighbors") + } + r.access.Lock() + defer r.access.Unlock() + for _, neigh := range neighbors { + if neigh.Attributes == nil { + continue + } + if neigh.Attributes.LLAddress == nil || len(neigh.Attributes.Address) == 0 { + continue + } + address, ok := netip.AddrFromSlice(neigh.Attributes.Address) + if !ok { + continue + } + r.neighborIPToMAC[address] = slices.Clone(neigh.Attributes.LLAddress) + } + return nil +} + +func (r *neighborResolver) subscribeNeighborUpdates() { + connection, err := netlink.Dial(unix.NETLINK_ROUTE, &netlink.Config{ + Groups: 1 << (unix.RTNLGRP_NEIGH - 1), + }) + if err != nil { + r.logger.Warn(E.Cause(err, "subscribe neighbor updates")) + return + } + defer connection.Close() + for { + select { + case <-r.done: + return + default: + } + err = connection.SetReadDeadline(time.Now().Add(3 * time.Second)) + if err != nil { + r.logger.Warn(E.Cause(err, "set netlink read deadline")) + return + } + messages, err := connection.Receive() + if err != nil { + if nerr, ok := err.(net.Error); ok && nerr.Timeout() { + continue + } + select { + case <-r.done: + return + default: + } + r.logger.Warn(E.Cause(err, "receive neighbor update")) + continue + } + for _, message := range messages { + address, mac, isDelete, ok := ParseNeighborMessage(message) + if !ok { + continue + } + r.access.Lock() + if isDelete { + delete(r.neighborIPToMAC, address) + } else { + r.neighborIPToMAC[address] = mac + } + r.access.Unlock() + } + } +} + +func (r *neighborResolver) doReloadLeaseFiles() { + leaseIPToMAC, ipToHostname, macToHostname := ReloadLeaseFiles(r.leaseFiles) + r.access.Lock() + r.leaseIPToMAC = leaseIPToMAC + r.ipToHostname = ipToHostname + r.macToHostname = macToHostname + r.access.Unlock() +} diff --git a/route/neighbor_resolver_parse.go b/route/neighbor_resolver_parse.go new file mode 100644 index 0000000000..1979b7eabc --- /dev/null +++ b/route/neighbor_resolver_parse.go @@ -0,0 +1,50 @@ +package route + +import ( + "encoding/binary" + "encoding/hex" + "net" + "net/netip" + "slices" + "strings" +) + +func extractMACFromDUID(duid []byte) (net.HardwareAddr, bool) { + if len(duid) < 4 { + return nil, false + } + duidType := binary.BigEndian.Uint16(duid[0:2]) + hwType := binary.BigEndian.Uint16(duid[2:4]) + if hwType != 1 { + return nil, false + } + switch duidType { + case 1: + if len(duid) < 14 { + return nil, false + } + return net.HardwareAddr(slices.Clone(duid[8:14])), true + case 3: + if len(duid) < 10 { + return nil, false + } + return net.HardwareAddr(slices.Clone(duid[4:10])), true + } + return nil, false +} + +func extractMACFromEUI64(address netip.Addr) (net.HardwareAddr, bool) { + if !address.Is6() { + return nil, false + } + b := address.As16() + if b[11] != 0xff || b[12] != 0xfe { + return nil, false + } + return net.HardwareAddr{b[8] ^ 0x02, b[9], b[10], b[13], b[14], b[15]}, true +} + +func parseDUID(s string) ([]byte, error) { + cleaned := strings.ReplaceAll(s, ":", "") + return hex.DecodeString(cleaned) +} diff --git a/route/neighbor_resolver_platform.go b/route/neighbor_resolver_platform.go new file mode 100644 index 0000000000..a9930c0e43 --- /dev/null +++ b/route/neighbor_resolver_platform.go @@ -0,0 +1,90 @@ +package route + +import ( + "net" + "net/netip" + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common/logger" +) + +type platformNeighborResolver struct { + logger logger.ContextLogger + platform adapter.PlatformInterface + access sync.RWMutex + ipToMAC map[netip.Addr]net.HardwareAddr + ipToHostname map[netip.Addr]string + macToHostname map[string]string +} + +func newPlatformNeighborResolver(resolverLogger logger.ContextLogger, platform adapter.PlatformInterface) adapter.NeighborResolver { + return &platformNeighborResolver{ + logger: resolverLogger, + platform: platform, + ipToMAC: make(map[netip.Addr]net.HardwareAddr), + ipToHostname: make(map[netip.Addr]string), + macToHostname: make(map[string]string), + } +} + +func (r *platformNeighborResolver) Start() error { + return r.platform.StartNeighborMonitor(r) +} + +func (r *platformNeighborResolver) Close() error { + return r.platform.CloseNeighborMonitor(r) +} + +func (r *platformNeighborResolver) LookupMAC(address netip.Addr) (net.HardwareAddr, bool) { + r.access.RLock() + defer r.access.RUnlock() + mac, found := r.ipToMAC[address] + if found { + return mac, true + } + return extractMACFromEUI64(address) +} + +func (r *platformNeighborResolver) LookupAddresses(hostname string) []netip.Addr { + r.access.RLock() + defer r.access.RUnlock() + return lookupAddressesByHostname(hostname, r.ipToHostname, r.macToHostname, r.ipToMAC) +} + +func (r *platformNeighborResolver) LookupHostname(address netip.Addr) (string, bool) { + r.access.RLock() + defer r.access.RUnlock() + hostname, found := r.ipToHostname[address] + if found { + return hostname, true + } + mac, found := r.ipToMAC[address] + if !found { + mac, found = extractMACFromEUI64(address) + } + if !found { + return "", false + } + hostname, found = r.macToHostname[mac.String()] + return hostname, found +} + +func (r *platformNeighborResolver) UpdateNeighborTable(entries []adapter.NeighborEntry) { + ipToMAC := make(map[netip.Addr]net.HardwareAddr) + ipToHostname := make(map[netip.Addr]string) + macToHostname := make(map[string]string) + for _, entry := range entries { + ipToMAC[entry.Address] = entry.MACAddress + if entry.Hostname != "" { + ipToHostname[entry.Address] = entry.Hostname + macToHostname[entry.MACAddress.String()] = entry.Hostname + } + } + r.access.Lock() + r.ipToMAC = ipToMAC + r.ipToHostname = ipToHostname + r.macToHostname = macToHostname + r.access.Unlock() + r.logger.Info("updated neighbor table: ", len(entries), " entries") +} diff --git a/route/neighbor_resolver_stub.go b/route/neighbor_resolver_stub.go new file mode 100644 index 0000000000..177a1fccbc --- /dev/null +++ b/route/neighbor_resolver_stub.go @@ -0,0 +1,14 @@ +//go:build !linux && !darwin + +package route + +import ( + "os" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common/logger" +) + +func newNeighborResolver(_ logger.ContextLogger, _ []string) (adapter.NeighborResolver, error) { + return nil, os.ErrInvalid +} diff --git a/route/neighbor_table_darwin.go b/route/neighbor_table_darwin.go new file mode 100644 index 0000000000..8ca2d0f0b7 --- /dev/null +++ b/route/neighbor_table_darwin.go @@ -0,0 +1,104 @@ +//go:build darwin + +package route + +import ( + "net" + "net/netip" + "syscall" + + "github.com/sagernet/sing-box/adapter" + E "github.com/sagernet/sing/common/exceptions" + + "golang.org/x/net/route" + "golang.org/x/sys/unix" +) + +func ReadNeighborEntries() ([]adapter.NeighborEntry, error) { + var entries []adapter.NeighborEntry + ipv4Entries, err := readNeighborEntriesAF(syscall.AF_INET) + if err != nil { + return nil, E.Cause(err, "read IPv4 neighbors") + } + entries = append(entries, ipv4Entries...) + ipv6Entries, err := readNeighborEntriesAF(syscall.AF_INET6) + if err != nil { + return nil, E.Cause(err, "read IPv6 neighbors") + } + entries = append(entries, ipv6Entries...) + return entries, nil +} + +func readNeighborEntriesAF(addressFamily int) ([]adapter.NeighborEntry, error) { + rib, err := route.FetchRIB(addressFamily, route.RIBType(syscall.NET_RT_FLAGS), syscall.RTF_LLINFO) + if err != nil { + return nil, err + } + messages, err := route.ParseRIB(route.RIBType(syscall.NET_RT_FLAGS), rib) + if err != nil { + return nil, err + } + var entries []adapter.NeighborEntry + for _, message := range messages { + routeMessage, isRouteMessage := message.(*route.RouteMessage) + if !isRouteMessage { + continue + } + address, macAddress, ok := parseRouteNeighborEntry(routeMessage) + if !ok { + continue + } + entries = append(entries, adapter.NeighborEntry{ + Address: address, + MACAddress: macAddress, + }) + } + return entries, nil +} + +func parseRouteNeighborEntry(message *route.RouteMessage) (address netip.Addr, macAddress net.HardwareAddr, ok bool) { + if len(message.Addrs) <= unix.RTAX_GATEWAY { + return + } + gateway, isLinkAddr := message.Addrs[unix.RTAX_GATEWAY].(*route.LinkAddr) + if !isLinkAddr || len(gateway.Addr) < 6 { + return + } + switch destination := message.Addrs[unix.RTAX_DST].(type) { + case *route.Inet4Addr: + address = netip.AddrFrom4(destination.IP) + case *route.Inet6Addr: + address = netip.AddrFrom16(destination.IP) + default: + return + } + macAddress = net.HardwareAddr(make([]byte, len(gateway.Addr))) + copy(macAddress, gateway.Addr) + ok = true + return +} + +func ParseRouteNeighborMessage(message *route.RouteMessage) (address netip.Addr, macAddress net.HardwareAddr, isDelete bool, ok bool) { + isDelete = message.Type == unix.RTM_DELETE + if len(message.Addrs) <= unix.RTAX_GATEWAY { + return + } + switch destination := message.Addrs[unix.RTAX_DST].(type) { + case *route.Inet4Addr: + address = netip.AddrFrom4(destination.IP) + case *route.Inet6Addr: + address = netip.AddrFrom16(destination.IP) + default: + return + } + if !isDelete { + gateway, isLinkAddr := message.Addrs[unix.RTAX_GATEWAY].(*route.LinkAddr) + if !isLinkAddr || len(gateway.Addr) < 6 { + return + } + macAddress = net.HardwareAddr(make([]byte, len(gateway.Addr))) + copy(macAddress, gateway.Addr) + } + ok = true + return +} diff --git a/route/neighbor_table_linux.go b/route/neighbor_table_linux.go new file mode 100644 index 0000000000..61a214fd3a --- /dev/null +++ b/route/neighbor_table_linux.go @@ -0,0 +1,68 @@ +//go:build linux + +package route + +import ( + "net" + "net/netip" + "slices" + + "github.com/sagernet/sing-box/adapter" + E "github.com/sagernet/sing/common/exceptions" + + "github.com/jsimonetti/rtnetlink" + "github.com/mdlayher/netlink" + "golang.org/x/sys/unix" +) + +func ReadNeighborEntries() ([]adapter.NeighborEntry, error) { + connection, err := rtnetlink.Dial(nil) + if err != nil { + return nil, E.Cause(err, "dial rtnetlink") + } + defer connection.Close() + neighbors, err := connection.Neigh.List() + if err != nil { + return nil, E.Cause(err, "list neighbors") + } + var entries []adapter.NeighborEntry + for _, neighbor := range neighbors { + if neighbor.Attributes == nil { + continue + } + if neighbor.Attributes.LLAddress == nil || len(neighbor.Attributes.Address) == 0 { + continue + } + address, ok := netip.AddrFromSlice(neighbor.Attributes.Address) + if !ok { + continue + } + entries = append(entries, adapter.NeighborEntry{ + Address: address, + MACAddress: slices.Clone(neighbor.Attributes.LLAddress), + }) + } + return entries, nil +} + +func ParseNeighborMessage(message netlink.Message) (address netip.Addr, macAddress net.HardwareAddr, isDelete bool, ok bool) { + var neighMessage rtnetlink.NeighMessage + err := neighMessage.UnmarshalBinary(message.Data) + if err != nil { + return + } + if neighMessage.Attributes == nil || len(neighMessage.Attributes.Address) == 0 { + return + } + address, ok = netip.AddrFromSlice(neighMessage.Attributes.Address) + if !ok { + return + } + isDelete = message.Header.Type == unix.RTM_DELNEIGH + if !isDelete && neighMessage.Attributes.LLAddress == nil { + ok = false + return + } + macAddress = slices.Clone(neighMessage.Attributes.LLAddress) + return +} diff --git a/route/network.go b/route/network.go new file mode 100644 index 0000000000..1393ca90a0 --- /dev/null +++ b/route/network.go @@ -0,0 +1,542 @@ +package route + +import ( + "context" + "errors" + "net" + "net/netip" + "os" + "runtime" + "strings" + "sync" + "syscall" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/settings" + "github.com/sagernet/sing-box/common/taskmonitor" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/winpowrprof" + "github.com/sagernet/sing/service" + "github.com/sagernet/sing/service/pause" + + "golang.org/x/exp/slices" +) + +var _ adapter.NetworkManager = (*NetworkManager)(nil) + +type NetworkManager struct { + logger logger.ContextLogger + interfaceFinder *control.DefaultInterfaceFinder + networkInterfaces common.TypedValue[[]adapter.NetworkInterface] + + autoDetectInterface bool + defaultOptions adapter.NetworkOptions + autoRedirectOutputMark uint32 + networkMonitor tun.NetworkUpdateMonitor + interfaceMonitor tun.DefaultInterfaceMonitor + packageManager tun.PackageManager + powerListener winpowrprof.EventListener + pauseManager pause.Manager + platformInterface adapter.PlatformInterface + connectionManager adapter.ConnectionManager + endpoint adapter.EndpointManager + inbound adapter.InboundManager + outbound adapter.OutboundManager + needWIFIState bool + wifiMonitor settings.WIFIMonitor + wifiState adapter.WIFIState + wifiStateMutex sync.RWMutex + started bool +} + +func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, options option.RouteOptions, dnsOptions option.DNSOptions) (*NetworkManager, error) { + defaultDomainResolver := common.PtrValueOrDefault(options.DefaultDomainResolver) + if options.AutoDetectInterface && !(C.IsLinux || C.IsDarwin || C.IsWindows) { + return nil, E.New("`auto_detect_interface` is only supported on Linux, Windows and macOS") + } else if options.OverrideAndroidVPN && !C.IsAndroid { + return nil, E.New("`override_android_vpn` is only supported on Android") + } else if options.DefaultInterface != "" && !(C.IsLinux || C.IsDarwin || C.IsWindows) { + return nil, E.New("`default_interface` is only supported on Linux, Windows and macOS") + } else if options.DefaultMark != 0 && !C.IsLinux { + return nil, E.New("`default_mark` is only supported on linux") + } + nm := &NetworkManager{ + logger: logger, + interfaceFinder: control.NewDefaultInterfaceFinder(), + autoDetectInterface: options.AutoDetectInterface, + defaultOptions: adapter.NetworkOptions{ + BindInterface: options.DefaultInterface, + RoutingMark: uint32(options.DefaultMark), + DomainResolver: defaultDomainResolver.Server, + DomainResolveOptions: adapter.DNSQueryOptions{ + Strategy: C.DomainStrategy(defaultDomainResolver.Strategy), + DisableCache: defaultDomainResolver.DisableCache, + DisableOptimisticCache: defaultDomainResolver.DisableOptimisticCache, + RewriteTTL: defaultDomainResolver.RewriteTTL, + ClientSubnet: defaultDomainResolver.ClientSubnet.Build(netip.Prefix{}), + }, + NetworkStrategy: (*C.NetworkStrategy)(options.DefaultNetworkStrategy), + NetworkType: common.Map(options.DefaultNetworkType, option.InterfaceType.Build), + FallbackNetworkType: common.Map(options.DefaultFallbackNetworkType, option.InterfaceType.Build), + FallbackDelay: time.Duration(options.DefaultFallbackDelay), + }, + pauseManager: service.FromContext[pause.Manager](ctx), + platformInterface: service.FromContext[adapter.PlatformInterface](ctx), + connectionManager: service.FromContext[adapter.ConnectionManager](ctx), + endpoint: service.FromContext[adapter.EndpointManager](ctx), + inbound: service.FromContext[adapter.InboundManager](ctx), + outbound: service.FromContext[adapter.OutboundManager](ctx), + needWIFIState: hasRule(options.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule), + } + if options.DefaultNetworkStrategy != nil { + if options.DefaultInterface != "" { + return nil, E.New("`default_network_strategy` is conflict with `default_interface`") + } + if !options.AutoDetectInterface { + return nil, E.New("`auto_detect_interface` is required by `default_network_strategy`") + } + } + usePlatformDefaultInterfaceMonitor := nm.platformInterface != nil + enforceInterfaceMonitor := options.AutoDetectInterface + if !usePlatformDefaultInterfaceMonitor { + networkMonitor, err := tun.NewNetworkUpdateMonitor(logger) + if !((err != nil && !enforceInterfaceMonitor) || errors.Is(err, os.ErrInvalid)) { + if err != nil { + return nil, E.Cause(err, "create network monitor") + } + nm.networkMonitor = networkMonitor + interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(nm.networkMonitor, logger, tun.DefaultInterfaceMonitorOptions{ + InterfaceFinder: nm.interfaceFinder, + OverrideAndroidVPN: options.OverrideAndroidVPN, + UnderNetworkExtension: nm.platformInterface != nil && nm.platformInterface.UnderNetworkExtension(), + }) + if err != nil { + return nil, E.New("auto_detect_interface unsupported on current platform") + } + interfaceMonitor.RegisterCallback(nm.notifyInterfaceUpdate) + nm.interfaceMonitor = interfaceMonitor + } + } else { + interfaceMonitor := nm.platformInterface.CreateDefaultInterfaceMonitor(logger) + interfaceMonitor.RegisterCallback(nm.notifyInterfaceUpdate) + nm.interfaceMonitor = interfaceMonitor + } + return nm, nil +} + +func (r *NetworkManager) Start(stage adapter.StartStage) error { + monitor := taskmonitor.New(r.logger, C.StartTimeout) + switch stage { + case adapter.StartStateInitialize: + if r.networkMonitor != nil { + monitor.Start("initialize network monitor") + err := r.networkMonitor.Start() + monitor.Finish() + if err != nil { + return err + } + } + if r.interfaceMonitor != nil { + monitor.Start("initialize interface monitor") + err := r.interfaceMonitor.Start() + monitor.Finish() + if err != nil { + return err + } + } + case adapter.StartStateStart: + if runtime.GOOS == "windows" { + powerListener, err := winpowrprof.NewEventListener(r.notifyWindowsPowerEvent) + if err == nil { + r.powerListener = powerListener + } else { + r.logger.Warn("initialize power listener: ", err) + } + } + if r.powerListener != nil { + monitor.Start("start power listener") + err := r.powerListener.Start() + monitor.Finish() + if err != nil { + return E.Cause(err, "start power listener") + } + } + if C.IsAndroid && r.platformInterface == nil { + monitor.Start("initialize package manager") + packageManager, err := tun.NewPackageManager(tun.PackageManagerOptions{ + Callback: r, + Logger: r.logger, + }) + monitor.Finish() + if err != nil { + return E.Cause(err, "create package manager") + } + monitor.Start("start package manager") + err = packageManager.Start() + monitor.Finish() + if err != nil { + r.logger.Warn("initialize package manager: ", err) + } else { + r.packageManager = packageManager + } + } + case adapter.StartStatePostStart: + if r.needWIFIState && !(r.platformInterface != nil && r.platformInterface.UsePlatformWIFIMonitor()) { + wifiMonitor, err := settings.NewWIFIMonitor(r.onWIFIStateChanged) + if err != nil { + if err != os.ErrInvalid { + r.logger.Warn(E.Cause(err, "create WIFI monitor")) + } + } else { + r.wifiMonitor = wifiMonitor + err = r.wifiMonitor.Start() + if err != nil { + r.logger.Warn(E.Cause(err, "start WIFI monitor")) + } + } + } + r.started = true + } + return nil +} + +func (r *NetworkManager) Initialize(ruleSets []adapter.RuleSet) { + for _, ruleSet := range ruleSets { + metadata := ruleSet.Metadata() + if metadata.ContainsWIFIRule { + r.needWIFIState = true + break + } + } +} + +func (r *NetworkManager) Close() error { + monitor := taskmonitor.New(r.logger, C.StopTimeout) + var err error + if r.packageManager != nil { + monitor.Start("close package manager") + err = E.Append(err, r.packageManager.Close(), func(err error) error { + return E.Cause(err, "close package manager") + }) + monitor.Finish() + } + if r.powerListener != nil { + monitor.Start("close power listener") + err = E.Append(err, r.powerListener.Close(), func(err error) error { + return E.Cause(err, "close power listener") + }) + monitor.Finish() + } + if r.interfaceMonitor != nil { + monitor.Start("close interface monitor") + err = E.Append(err, r.interfaceMonitor.Close(), func(err error) error { + return E.Cause(err, "close interface monitor") + }) + monitor.Finish() + } + if r.networkMonitor != nil { + monitor.Start("close network monitor") + err = E.Append(err, r.networkMonitor.Close(), func(err error) error { + return E.Cause(err, "close network monitor") + }) + monitor.Finish() + } + if r.wifiMonitor != nil { + monitor.Start("close WIFI monitor") + err = E.Append(err, r.wifiMonitor.Close(), func(err error) error { + return E.Cause(err, "close WIFI monitor") + }) + monitor.Finish() + } + return err +} + +func (r *NetworkManager) InterfaceFinder() control.InterfaceFinder { + return r.interfaceFinder +} + +func (r *NetworkManager) UpdateInterfaces() error { + if r.platformInterface == nil || !r.platformInterface.UsePlatformNetworkInterfaces() { + return r.interfaceFinder.Update() + } else { + interfaces, err := r.platformInterface.NetworkInterfaces() + if err != nil { + return err + } + if C.IsDarwin { + err = r.interfaceFinder.Update() + if err != nil { + return err + } + // NEInterface only provides name,index and type + interfaces = common.Map(interfaces, func(it adapter.NetworkInterface) adapter.NetworkInterface { + iif, _ := r.interfaceFinder.ByIndex(it.Index) + if iif != nil { + it.Interface = *iif + } + return it + }) + } else { + r.interfaceFinder.UpdateInterfaces(common.Map(interfaces, func(it adapter.NetworkInterface) control.Interface { return it.Interface })) + } + oldInterfaces := r.networkInterfaces.Load() + newInterfaces := common.Filter(interfaces, func(it adapter.NetworkInterface) bool { + return it.Flags&net.FlagUp != 0 + }) + r.networkInterfaces.Store(newInterfaces) + if len(newInterfaces) > 0 && !slices.EqualFunc(oldInterfaces, newInterfaces, func(oldInterface adapter.NetworkInterface, newInterface adapter.NetworkInterface) bool { + return oldInterface.Interface.Index == newInterface.Interface.Index && + oldInterface.Interface.Name == newInterface.Interface.Name && + oldInterface.Interface.Flags == newInterface.Interface.Flags && + oldInterface.Type == newInterface.Type && + oldInterface.Expensive == newInterface.Expensive && + oldInterface.Constrained == newInterface.Constrained + }) { + r.logger.Info("updated available networks: ", strings.Join(common.Map(newInterfaces, func(it adapter.NetworkInterface) string { + var options []string + options = append(options, F.ToString(it.Type)) + if it.Expensive { + options = append(options, "expensive") + } + if it.Constrained { + options = append(options, "constrained") + } + return F.ToString(it.Name, " (", strings.Join(options, ", "), ")") + }), ", ")) + } + return nil + } +} + +func (r *NetworkManager) DefaultNetworkInterface() *adapter.NetworkInterface { + iif := r.interfaceMonitor.DefaultInterface() + if iif == nil { + return nil + } + for _, it := range r.networkInterfaces.Load() { + if it.Interface.Index == iif.Index { + return &it + } + } + return &adapter.NetworkInterface{Interface: *iif} +} + +func (r *NetworkManager) NetworkInterfaces() []adapter.NetworkInterface { + return r.networkInterfaces.Load() +} + +func (r *NetworkManager) AutoDetectInterface() bool { + return r.autoDetectInterface +} + +func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func { + if r.platformInterface != nil && r.platformInterface.UsePlatformAutoDetectInterfaceControl() { + return func(network, address string, conn syscall.RawConn) error { + return control.Raw(conn, func(fd uintptr) error { + return r.platformInterface.AutoDetectInterfaceControl(int(fd)) + }) + } + } else { + if r.interfaceMonitor == nil { + return nil + } + return control.BindToInterfaceFunc(r.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int, err error) { + remoteAddr := M.ParseSocksaddr(address).Addr + if remoteAddr.IsValid() { + iif, err := r.interfaceFinder.ByAddr(remoteAddr) + if err == nil { + return iif.Name, iif.Index, nil + } + } + defaultInterface := r.interfaceMonitor.DefaultInterface() + if defaultInterface == nil { + return "", -1, tun.ErrNoRoute + } + return defaultInterface.Name, defaultInterface.Index, nil + }) + } +} + +func (r *NetworkManager) ProtectFunc() control.Func { + if r.platformInterface != nil && r.platformInterface.UsePlatformAutoDetectInterfaceControl() { + return func(network, address string, conn syscall.RawConn) error { + return control.Raw(conn, func(fd uintptr) error { + return r.platformInterface.AutoDetectInterfaceControl(int(fd)) + }) + } + } + return nil +} + +func (r *NetworkManager) DefaultOptions() adapter.NetworkOptions { + return r.defaultOptions +} + +func (r *NetworkManager) RegisterAutoRedirectOutputMark(mark uint32) error { + if r.autoRedirectOutputMark > 0 { + return E.New("only one auto-redirect can be configured") + } + r.autoRedirectOutputMark = mark + return nil +} + +func (r *NetworkManager) AutoRedirectOutputMark() uint32 { + return r.autoRedirectOutputMark +} + +func (r *NetworkManager) AutoRedirectOutputMarkFunc() control.Func { + return func(network, address string, conn syscall.RawConn) error { + if r.autoRedirectOutputMark == 0 { + return nil + } + return control.RoutingMark(r.autoRedirectOutputMark)(network, address, conn) + } +} + +func (r *NetworkManager) NetworkMonitor() tun.NetworkUpdateMonitor { + return r.networkMonitor +} + +func (r *NetworkManager) InterfaceMonitor() tun.DefaultInterfaceMonitor { + return r.interfaceMonitor +} + +func (r *NetworkManager) PackageManager() tun.PackageManager { + return r.packageManager +} + +func (r *NetworkManager) NeedWIFIState() bool { + return r.needWIFIState +} + +func (r *NetworkManager) WIFIState() adapter.WIFIState { + r.wifiStateMutex.RLock() + defer r.wifiStateMutex.RUnlock() + return r.wifiState +} + +func (r *NetworkManager) onWIFIStateChanged(state adapter.WIFIState) { + r.wifiStateMutex.Lock() + if state != r.wifiState { + r.wifiState = state + r.wifiStateMutex.Unlock() + if state.SSID != "" { + r.logger.Info("WIFI state changed: SSID=", state.SSID, ", BSSID=", state.BSSID) + } else { + r.logger.Info("WIFI disconnected") + } + } else { + r.wifiStateMutex.Unlock() + } +} + +func (r *NetworkManager) UpdateWIFIState() { + var state adapter.WIFIState + if r.wifiMonitor != nil { + state = r.wifiMonitor.ReadWIFIState() + } else if r.platformInterface != nil && r.platformInterface.UsePlatformWIFIMonitor() { + state = r.platformInterface.ReadWIFIState() + } else { + return + } + r.onWIFIStateChanged(state) +} + +func (r *NetworkManager) ResetNetwork() { + if r.connectionManager != nil { + r.connectionManager.CloseAll() + } + + for _, endpoint := range r.endpoint.Endpoints() { + listener, isListener := endpoint.(adapter.InterfaceUpdateListener) + if isListener { + listener.InterfaceUpdated() + } + } + + for _, inbound := range r.inbound.Inbounds() { + listener, isListener := inbound.(adapter.InterfaceUpdateListener) + if isListener { + listener.InterfaceUpdated() + } + } + + for _, outbound := range r.outbound.Outbounds() { + listener, isListener := outbound.(adapter.InterfaceUpdateListener) + if isListener { + listener.InterfaceUpdated() + } + } +} + +func (r *NetworkManager) notifyInterfaceUpdate(defaultInterface *control.Interface, flags int) { + if defaultInterface == nil { + r.pauseManager.NetworkPause() + r.logger.Error("missing default interface") + return + } + + r.pauseManager.NetworkWake() + var options []string + options = append(options, F.ToString("index ", defaultInterface.Index)) + if C.IsAndroid && r.platformInterface == nil { + var vpnStatus string + if r.interfaceMonitor.AndroidVPNEnabled() { + vpnStatus = "enabled" + } else { + vpnStatus = "disabled" + } + options = append(options, "vpn "+vpnStatus) + } else if r.platformInterface != nil { + networkInterface := common.Find(r.networkInterfaces.Load(), func(it adapter.NetworkInterface) bool { + return it.Interface.Index == defaultInterface.Index + }) + if networkInterface.Name == "" { + // race + return + } + options = append(options, F.ToString("type ", networkInterface.Type)) + if networkInterface.Expensive { + options = append(options, "expensive") + } + if networkInterface.Constrained { + options = append(options, "constrained") + } + } + r.logger.Info("updated default interface ", defaultInterface.Name, ", ", strings.Join(options, ", ")) + r.UpdateWIFIState() + + if !r.started { + return + } + r.ResetNetwork() +} + +func (r *NetworkManager) notifyWindowsPowerEvent(event int) { + switch event { + case winpowrprof.EVENT_SUSPEND: + r.pauseManager.DevicePause() + r.ResetNetwork() + case winpowrprof.EVENT_RESUME: + if !r.pauseManager.IsDevicePaused() { + return + } + fallthrough + case winpowrprof.EVENT_RESUME_AUTOMATIC: + r.pauseManager.DeviceWake() + r.ResetNetwork() + } +} + +func (r *NetworkManager) OnPackagesUpdated(packages int, sharedUsers int) { + r.logger.Info("updated packages list: ", packages, " packages, ", sharedUsers, " shared users") +} diff --git a/route/platform_searcher.go b/route/platform_searcher.go new file mode 100644 index 0000000000..20fbda3f32 --- /dev/null +++ b/route/platform_searcher.go @@ -0,0 +1,49 @@ +package route + +import ( + "context" + "net/netip" + "syscall" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/process" + N "github.com/sagernet/sing/common/network" +) + +type platformSearcher struct { + platform adapter.PlatformInterface +} + +func newPlatformSearcher(platform adapter.PlatformInterface) process.Searcher { + return &platformSearcher{platform: platform} +} + +func (s *platformSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) { + if !s.platform.UsePlatformConnectionOwnerFinder() { + return nil, process.ErrNotFound + } + + var ipProtocol int32 + switch N.NetworkName(network) { + case N.NetworkTCP: + ipProtocol = syscall.IPPROTO_TCP + case N.NetworkUDP: + ipProtocol = syscall.IPPROTO_UDP + default: + return nil, process.ErrNotFound + } + + request := &adapter.FindConnectionOwnerRequest{ + IpProtocol: ipProtocol, + SourceAddress: source.Addr().String(), + SourcePort: int32(source.Port()), + DestinationAddress: destination.Addr().String(), + DestinationPort: int32(destination.Port()), + } + + return s.platform.FindConnectionOwner(request) +} + +func (s *platformSearcher) Close() error { + return nil +} diff --git a/route/process_cache.go b/route/process_cache.go new file mode 100644 index 0000000000..691a4e8e9d --- /dev/null +++ b/route/process_cache.go @@ -0,0 +1,34 @@ +package route + +import ( + "context" + "net/netip" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/process" +) + +type processCacheKey struct { + Network string + Source netip.AddrPort + Destination netip.AddrPort +} + +type processCacheEntry struct { + result *adapter.ConnectionOwner + err error +} + +func (r *Router) findProcessInfoCached(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) { + key := processCacheKey{ + Network: network, + Source: source, + Destination: destination, + } + if entry, ok := r.processCache.Get(key); ok { + return entry.result, entry.err + } + result, err := process.FindProcessInfo(r.processSearcher, ctx, network, source, destination) + r.processCache.Add(key, processCacheEntry{result: result, err: err}) + return result, err +} diff --git a/route/route.go b/route/route.go new file mode 100644 index 0000000000..339c00f69c --- /dev/null +++ b/route/route.go @@ -0,0 +1,836 @@ +package route + +import ( + "context" + "errors" + "net" + "net/netip" + "strings" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/sniff" + C "github.com/sagernet/sing-box/constant" + R "github.com/sagernet/sing-box/route/rule" + "github.com/sagernet/sing-mux" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing-tun/ping" + "github.com/sagernet/sing-vmess" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + "github.com/sagernet/sing/common/bufio/deadline" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/uot" + + "golang.org/x/exp/slices" +) + +// Deprecated: use RouteConnectionEx instead. +func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + done := make(chan interface{}) + err := r.routeConnection(ctx, conn, metadata, N.OnceClose(func(it error) { + close(done) + })) + if err != nil { + return err + } + select { + case <-done: + case <-r.ctx.Done(): + } + return nil +} + +func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := r.routeConnection(ctx, conn, metadata, onClose) + if err != nil { + N.CloseOnHandshakeFailure(conn, onClose, err) + if E.IsClosedOrCanceled(err) || R.IsRejected(err) { + r.logger.DebugContext(ctx, "connection closed: ", err) + } else { + r.logger.ErrorContext(ctx, err) + } + } +} + +func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { + //nolint:staticcheck + if metadata.InboundDetour != "" { + if metadata.LastInbound == metadata.InboundDetour { + return E.New("routing loop on detour: ", metadata.InboundDetour) + } + detour, loaded := r.inbound.Get(metadata.InboundDetour) + if !loaded { + return E.New("inbound detour not found: ", metadata.InboundDetour) + } + injectable, isInjectable := detour.(adapter.TCPInjectableInbound) + if !isInjectable { + return E.New("inbound detour is not TCP injectable: ", metadata.InboundDetour) + } + metadata.LastInbound = metadata.Inbound + metadata.Inbound = metadata.InboundDetour + metadata.InboundDetour = "" + injectable.NewConnection(ctx, conn, metadata, onClose) + return nil + } + metadata.Network = N.NetworkTCP + switch metadata.Destination.Fqdn { + case mux.Destination.Fqdn: + return E.New("global multiplex is deprecated since sing-box v1.7.0, enable multiplex in Inbound fields instead.") + case vmess.MuxDestination.Fqdn: + return E.New("global multiplex (v2ray legacy) not supported since sing-box v1.7.0.") + case uot.MagicAddress: + return E.New("global UoT not supported since sing-box v1.7.0.") + case uot.LegacyMagicAddress: + return E.New("global UoT (legacy) not supported since sing-box v1.7.0.") + } + if deadline.NeedAdditionalReadDeadline(conn) { + conn = deadline.NewConn(conn) + } + selectedRule, _, buffers, _, err := r.matchRule(ctx, &metadata, false, false, conn, nil) + if err != nil { + return err + } + var selectedOutbound adapter.Outbound + if selectedRule != nil { + switch action := selectedRule.Action().(type) { + case *R.RuleActionRoute: + var loaded bool + selectedOutbound, loaded = r.outbound.Outbound(action.Outbound) + if !loaded { + buf.ReleaseMulti(buffers) + return E.New("outbound not found: ", action.Outbound) + } + if !common.Contains(selectedOutbound.Network(), N.NetworkTCP) { + buf.ReleaseMulti(buffers) + return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag()) + } + case *R.RuleActionBypass: + if action.Outbound == "" { + break + } + var loaded bool + selectedOutbound, loaded = r.outbound.Outbound(action.Outbound) + if !loaded { + buf.ReleaseMulti(buffers) + return E.New("outbound not found: ", action.Outbound) + } + if !common.Contains(selectedOutbound.Network(), N.NetworkTCP) { + buf.ReleaseMulti(buffers) + return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag()) + } + case *R.RuleActionReject: + buf.ReleaseMulti(buffers) + if action.Method == C.RuleActionRejectMethodReply { + return E.New("reject method `reply` is not supported for TCP connections") + } + return action.Error(ctx) + case *R.RuleActionHijackDNS: + for _, buffer := range buffers { + conn = bufio.NewCachedConn(conn, buffer) + } + N.CloseOnHandshakeFailure(conn, onClose, r.hijackDNSStream(ctx, conn, metadata)) + return nil + } + } + if selectedRule == nil { + defaultOutbound := r.outbound.Default() + if !common.Contains(defaultOutbound.Network(), N.NetworkTCP) { + buf.ReleaseMulti(buffers) + return E.New("TCP is not supported by default outbound: ", defaultOutbound.Tag()) + } + selectedOutbound = defaultOutbound + } + + for _, buffer := range buffers { + conn = bufio.NewCachedConn(conn, buffer) + } + for _, tracker := range r.trackers { + conn = tracker.RoutedConnection(ctx, conn, metadata, selectedRule, selectedOutbound) + } + if outboundHandler, isHandler := selectedOutbound.(adapter.ConnectionHandler); isHandler { + outboundHandler.NewConnection(ctx, conn, metadata, onClose) + } else { + r.connection.NewConnection(ctx, selectedOutbound, conn, metadata, onClose) + } + return nil +} + +func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + done := make(chan interface{}) + err := r.routePacketConnection(ctx, conn, metadata, N.OnceClose(func(it error) { + close(done) + })) + if err != nil { + conn.Close() + if E.IsClosedOrCanceled(err) || R.IsRejected(err) { + r.logger.DebugContext(ctx, "connection closed: ", err) + } else { + r.logger.ErrorContext(ctx, err) + } + } + select { + case <-done: + case <-r.ctx.Done(): + } + return nil +} + +func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := r.routePacketConnection(ctx, conn, metadata, onClose) + if err != nil { + N.CloseOnHandshakeFailure(conn, onClose, err) + if E.IsClosedOrCanceled(err) || R.IsRejected(err) { + r.logger.DebugContext(ctx, "connection closed: ", err) + } else { + r.logger.ErrorContext(ctx, err) + } + } +} + +func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { + //nolint:staticcheck + if metadata.InboundDetour != "" { + if metadata.LastInbound == metadata.InboundDetour { + return E.New("routing loop on detour: ", metadata.InboundDetour) + } + detour, loaded := r.inbound.Get(metadata.InboundDetour) + if !loaded { + return E.New("inbound detour not found: ", metadata.InboundDetour) + } + injectable, isInjectable := detour.(adapter.UDPInjectableInbound) + if !isInjectable { + return E.New("inbound detour is not UDP injectable: ", metadata.InboundDetour) + } + metadata.LastInbound = metadata.Inbound + metadata.Inbound = metadata.InboundDetour + metadata.InboundDetour = "" + injectable.NewPacketConnection(ctx, conn, metadata, onClose) + return nil + } + // TODO: move to UoT + metadata.Network = N.NetworkUDP + + // Currently we don't have deadline usages for UDP connections + /*if deadline.NeedAdditionalReadDeadline(conn) { + conn = deadline.NewPacketConn(bufio.NewNetPacketConn(conn)) + }*/ + + selectedRule, _, _, packetBuffers, err := r.matchRule(ctx, &metadata, false, false, nil, conn) + if err != nil { + return err + } + var selectedOutbound adapter.Outbound + var selectReturn bool + if selectedRule != nil { + switch action := selectedRule.Action().(type) { + case *R.RuleActionRoute: + var loaded bool + selectedOutbound, loaded = r.outbound.Outbound(action.Outbound) + if !loaded { + N.ReleaseMultiPacketBuffer(packetBuffers) + return E.New("outbound not found: ", action.Outbound) + } + if !common.Contains(selectedOutbound.Network(), N.NetworkUDP) { + N.ReleaseMultiPacketBuffer(packetBuffers) + return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag()) + } + case *R.RuleActionBypass: + if action.Outbound == "" { + break + } + var loaded bool + selectedOutbound, loaded = r.outbound.Outbound(action.Outbound) + if !loaded { + N.ReleaseMultiPacketBuffer(packetBuffers) + return E.New("outbound not found: ", action.Outbound) + } + if !common.Contains(selectedOutbound.Network(), N.NetworkUDP) { + N.ReleaseMultiPacketBuffer(packetBuffers) + return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag()) + } + case *R.RuleActionReject: + N.ReleaseMultiPacketBuffer(packetBuffers) + if action.Method == C.RuleActionRejectMethodReply { + return E.New("reject method `reply` is not supported for UDP connections") + } + return action.Error(ctx) + case *R.RuleActionHijackDNS: + return r.hijackDNSPacket(ctx, conn, packetBuffers, metadata, onClose) + } + } + if selectedRule == nil || selectReturn { + defaultOutbound := r.outbound.Default() + if !common.Contains(defaultOutbound.Network(), N.NetworkUDP) { + N.ReleaseMultiPacketBuffer(packetBuffers) + return E.New("UDP is not supported by outbound: ", defaultOutbound.Tag()) + } + selectedOutbound = defaultOutbound + } + for _, buffer := range packetBuffers { + conn = bufio.NewCachedPacketConn(conn, buffer.Buffer, buffer.Destination) + N.PutPacketBuffer(buffer) + } + for _, tracker := range r.trackers { + conn = tracker.RoutedPacketConnection(ctx, conn, metadata, selectedRule, selectedOutbound) + } + if metadata.FakeIP { + conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination) + } + if outboundHandler, isHandler := selectedOutbound.(adapter.PacketConnectionHandler); isHandler { + outboundHandler.NewPacketConnection(ctx, conn, metadata, onClose) + } else { + r.connection.NewPacketConnection(ctx, selectedOutbound, conn, metadata, onClose) + } + return nil +} + +func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration, supportBypass bool) (tun.DirectRouteDestination, error) { + selectedRule, _, _, _, err := r.matchRule(r.ctx, &metadata, true, supportBypass, nil, nil) + if err != nil { + return nil, err + } + var directRouteOutbound adapter.DirectRouteOutbound + if selectedRule != nil { + switch action := selectedRule.Action().(type) { + case *R.RuleActionReject: + switch metadata.Network { + case N.NetworkTCP: + if action.Method == C.RuleActionRejectMethodReply { + return nil, E.New("reject method `reply` is not supported for TCP connections") + } + case N.NetworkUDP: + if action.Method == C.RuleActionRejectMethodReply { + return nil, E.New("reject method `reply` is not supported for UDP connections") + } + } + return nil, action.Error(context.Background()) + case *R.RuleActionBypass: + if supportBypass { + return nil, &R.BypassedError{Cause: tun.ErrBypass} + } + if routeContext == nil { + return nil, nil + } + outbound, loaded := r.outbound.Outbound(action.Outbound) + if !loaded { + return nil, E.New("outbound not found: ", action.Outbound) + } + if !common.Contains(outbound.Network(), metadata.Network) { + return nil, E.New(metadata.Network, " is not supported by outbound: ", action.Outbound) + } + directRouteOutbound = outbound.(adapter.DirectRouteOutbound) + case *R.RuleActionRoute: + if routeContext == nil { + return nil, nil + } + outbound, loaded := r.outbound.Outbound(action.Outbound) + if !loaded { + return nil, E.New("outbound not found: ", action.Outbound) + } + if !common.Contains(outbound.Network(), metadata.Network) { + return nil, E.New(metadata.Network, " is not supported by outbound: ", action.Outbound) + } + directRouteOutbound = outbound.(adapter.DirectRouteOutbound) + } + } + if directRouteOutbound == nil { + if selectedRule != nil || metadata.Network != N.NetworkICMP { + return nil, nil + } + defaultOutbound := r.outbound.Default() + if !common.Contains(defaultOutbound.Network(), metadata.Network) { + return nil, E.New(metadata.Network, " is not supported by default outbound: ", defaultOutbound.Tag()) + } + directRouteOutbound = defaultOutbound.(adapter.DirectRouteOutbound) + } + if metadata.Destination.IsDomain() { + if len(metadata.DestinationAddresses) == 0 { + var strategy C.DomainStrategy + if metadata.Source.IsIPv4() { + strategy = C.DomainStrategyIPv4Only + } else { + strategy = C.DomainStrategyIPv6Only + } + err = r.actionResolve(r.ctx, &metadata, &R.RuleActionResolve{ + Strategy: strategy, + }) + if err != nil { + return nil, err + } + } + var newDestination netip.Addr + if metadata.Source.IsIPv4() { + for _, address := range metadata.DestinationAddresses { + if address.Is4() { + newDestination = address + break + } + } + } else { + for _, address := range metadata.DestinationAddresses { + if address.Is6() { + newDestination = address + break + } + } + } + if !newDestination.IsValid() { + if metadata.Source.IsIPv4() { + return nil, E.New("no IPv4 address found for domain: ", metadata.Destination.Fqdn) + } else { + return nil, E.New("no IPv6 address found for domain: ", metadata.Destination.Fqdn) + } + } + metadata.Destination = M.Socksaddr{ + Addr: newDestination, + } + routeContext = ping.NewContextDestinationWriter(routeContext, metadata.OriginDestination.Addr) + var routeDestination tun.DirectRouteDestination + routeDestination, err = directRouteOutbound.NewDirectRouteConnection(metadata, routeContext, timeout) + if err != nil { + return nil, err + } + return ping.NewDestinationWriter(routeDestination, newDestination), nil + } + return directRouteOutbound.NewDirectRouteConnection(metadata, routeContext, timeout) +} + +func (r *Router) matchRule( + ctx context.Context, metadata *adapter.InboundContext, preMatch bool, supportBypass bool, + inputConn net.Conn, inputPacketConn N.PacketConn, +) ( + selectedRule adapter.Rule, selectedRuleIndex int, + buffers []*buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error, +) { + if r.processSearcher != nil && metadata.ProcessInfo == nil { + var originDestination netip.AddrPort + if metadata.OriginDestination.IsValid() { + originDestination = metadata.OriginDestination.AddrPort() + } else if metadata.Destination.IsIP() { + originDestination = metadata.Destination.AddrPort() + } + processInfo, fErr := r.findProcessInfoCached(ctx, metadata.Network, metadata.Source.AddrPort(), originDestination) + if fErr != nil { + r.logger.InfoContext(ctx, "failed to search process: ", fErr) + } else { + if processInfo.ProcessPath != "" { + if processInfo.UserName != "" { + r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath, ", user: ", processInfo.UserName) + } else if processInfo.UserId != -1 { + r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath, ", user id: ", processInfo.UserId) + } else { + r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath) + } + } else if len(processInfo.AndroidPackageNames) > 0 { + r.logger.InfoContext(ctx, "found package name: ", strings.Join(processInfo.AndroidPackageNames, ", ")) + } else if processInfo.UserId != -1 { + if processInfo.UserName != "" { + r.logger.InfoContext(ctx, "found user: ", processInfo.UserName) + } else { + r.logger.InfoContext(ctx, "found user id: ", processInfo.UserId) + } + } + metadata.ProcessInfo = processInfo + } + } + if r.neighborResolver != nil && metadata.SourceMACAddress == nil && metadata.Source.Addr.IsValid() { + mac, macFound := r.neighborResolver.LookupMAC(metadata.Source.Addr) + if macFound { + metadata.SourceMACAddress = mac + } + hostname, hostnameFound := r.neighborResolver.LookupHostname(metadata.Source.Addr) + if hostnameFound { + metadata.SourceHostname = hostname + if macFound { + r.logger.InfoContext(ctx, "found neighbor: ", mac, ", hostname: ", hostname) + } else { + r.logger.InfoContext(ctx, "found neighbor hostname: ", hostname) + } + } else if macFound { + r.logger.InfoContext(ctx, "found neighbor: ", mac) + } + } + if metadata.Destination.Addr.IsValid() && r.dnsTransport.FakeIP() != nil && r.dnsTransport.FakeIP().Store().Contains(metadata.Destination.Addr) { + domain, loaded := r.dnsTransport.FakeIP().Store().Lookup(metadata.Destination.Addr) + if !loaded { + fatalErr = E.New("missing fakeip record, try enable `experimental.cache_file`") + return + } + if domain != "" { + metadata.OriginDestination = metadata.Destination + metadata.Destination = M.Socksaddr{ + Fqdn: domain, + Port: metadata.Destination.Port, + } + metadata.FakeIP = true + r.logger.DebugContext(ctx, "found fakeip domain: ", domain) + } + } else if metadata.Domain == "" { + domain, loaded := r.dns.LookupReverseMapping(metadata.Destination.Addr) + if loaded { + metadata.Domain = domain + r.logger.DebugContext(ctx, "found reserve mapped domain: ", metadata.Domain) + } + } + if metadata.Destination.IsIPv4() { + metadata.IPVersion = 4 + } else if metadata.Destination.IsIPv6() { + metadata.IPVersion = 6 + } + +match: + for currentRuleIndex, currentRule := range r.rules { + metadata.ResetRuleCache() + if !currentRule.Match(metadata) { + continue + } + if !preMatch { + ruleDescription := currentRule.String() + if ruleDescription != "" { + r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action()) + } else { + r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] => ", currentRule.Action()) + } + } else { + switch currentRule.Action().Type() { + case C.RuleActionTypeReject: + ruleDescription := currentRule.String() + if ruleDescription != "" { + r.logger.DebugContext(ctx, "pre-match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action()) + } else { + r.logger.DebugContext(ctx, "pre-match[", currentRuleIndex, "] => ", currentRule.Action()) + } + } + } + var routeOptions *R.RuleActionRouteOptions + switch action := currentRule.Action().(type) { + case *R.RuleActionRoute: + routeOptions = &action.RuleActionRouteOptions + case *R.RuleActionRouteOptions: + routeOptions = action + } + if routeOptions != nil { + // TODO: add nat + if (routeOptions.OverrideAddress.IsValid() || routeOptions.OverridePort > 0) && !metadata.RouteOriginalDestination.IsValid() { + metadata.RouteOriginalDestination = metadata.Destination + } + if routeOptions.OverrideAddress.IsValid() { + metadata.Destination = M.Socksaddr{ + Addr: routeOptions.OverrideAddress.Addr, + Port: metadata.Destination.Port, + Fqdn: routeOptions.OverrideAddress.Fqdn, + } + metadata.DestinationAddresses = nil + } + if routeOptions.OverridePort > 0 { + metadata.Destination = M.Socksaddr{ + Addr: metadata.Destination.Addr, + Port: routeOptions.OverridePort, + Fqdn: metadata.Destination.Fqdn, + } + } + if routeOptions.OverrideTunnelDestination != "" { + metadata.TunnelDestination = routeOptions.OverrideTunnelDestination + } + if routeOptions.NetworkStrategy != nil { + metadata.NetworkStrategy = routeOptions.NetworkStrategy + } + if len(routeOptions.NetworkType) > 0 { + metadata.NetworkType = routeOptions.NetworkType + } + if len(routeOptions.FallbackNetworkType) > 0 { + metadata.FallbackNetworkType = routeOptions.FallbackNetworkType + } + if routeOptions.FallbackDelay != 0 { + metadata.FallbackDelay = routeOptions.FallbackDelay + } + if routeOptions.UDPDisableDomainUnmapping { + metadata.UDPDisableDomainUnmapping = true + } + if routeOptions.UDPConnect { + metadata.UDPConnect = true + } + if routeOptions.UDPTimeout > 0 { + metadata.UDPTimeout = routeOptions.UDPTimeout + } + if routeOptions.TLSFragment { + metadata.TLSFragment = true + metadata.TLSFragmentFallbackDelay = routeOptions.TLSFragmentFallbackDelay + } + if routeOptions.TLSRecordFragment { + metadata.TLSRecordFragment = true + } + } + switch action := currentRule.Action().(type) { + case *R.RuleActionSniff: + if !preMatch { + newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers, packetBuffers) + if newBuffer != nil { + buffers = append(buffers, newBuffer) + } else if len(newPacketBuffers) > 0 { + packetBuffers = append(packetBuffers, newPacketBuffers...) + } + if newErr != nil { + fatalErr = newErr + return + } + } else if metadata.Network != N.NetworkICMP { + selectedRule = currentRule + selectedRuleIndex = currentRuleIndex + break match + } + case *R.RuleActionResolve: + fatalErr = r.actionResolve(ctx, metadata, action) + if fatalErr != nil { + return + } + } + actionType := currentRule.Action().Type() + if actionType == C.RuleActionTypeRoute || + actionType == C.RuleActionTypeReject || + actionType == C.RuleActionTypeHijackDNS { + selectedRule = currentRule + selectedRuleIndex = currentRuleIndex + break match + } + if actionType == C.RuleActionTypeBypass { + bypassAction := currentRule.Action().(*R.RuleActionBypass) + if !supportBypass && bypassAction.Outbound == "" { + continue match + } + selectedRule = currentRule + selectedRuleIndex = currentRuleIndex + break match + } + } + return +} + +func (r *Router) actionSniff( + ctx context.Context, metadata *adapter.InboundContext, action *R.RuleActionSniff, + inputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer, inputPacketBuffers []*N.PacketBuffer, +) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) { + if sniff.Skip(metadata) { + r.logger.DebugContext(ctx, "sniff skipped due to port considered as server-first") + return + } else if metadata.Protocol != "" { + r.logger.DebugContext(ctx, "duplicate sniff skipped") + return + } + if inputConn != nil { + if len(action.StreamSniffers) == 0 && len(action.PacketSniffers) > 0 { + return + } else if slices.Equal(metadata.SnifferNames, action.SnifferNames) && metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) { + r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError) + return + } + var streamSniffers []sniff.StreamSniffer + if len(action.StreamSniffers) > 0 { + streamSniffers = action.StreamSniffers + } else { + streamSniffers = []sniff.StreamSniffer{ + sniff.TLSClientHello, + sniff.HTTPHost, + sniff.StreamDomainNameQuery, + sniff.BitTorrent, + sniff.SSH, + sniff.RDP, + } + } + sniffBuffer := buf.NewPacket() + err := sniff.PeekStream( + ctx, + metadata, + inputConn, + inputBuffers, + sniffBuffer, + action.Timeout, + streamSniffers..., + ) + metadata.SnifferNames = action.SnifferNames + metadata.SniffError = err + if err == nil { + //goland:noinspection GoDeprecation + if action.OverrideDestination && M.IsDomainName(metadata.Domain) { + metadata.Destination = M.Socksaddr{ + Fqdn: metadata.Domain, + Port: metadata.Destination.Port, + } + } + if metadata.Domain != "" && metadata.Client != "" { + r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol, ", domain: ", metadata.Domain, ", client: ", metadata.Client) + } else if metadata.Domain != "" { + r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol, ", domain: ", metadata.Domain) + } else { + r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol) + } + } + if !sniffBuffer.IsEmpty() { + buffer = sniffBuffer + } else { + sniffBuffer.Release() + } + } else if inputPacketConn != nil { + if len(action.PacketSniffers) == 0 && len(action.StreamSniffers) > 0 { + return + } else if slices.Equal(metadata.SnifferNames, action.SnifferNames) && metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) { + r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError) + return + } + quicMoreData := func() bool { + return slices.Equal(metadata.SnifferNames, action.SnifferNames) && errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) + } + var packetSniffers []sniff.PacketSniffer + if len(action.PacketSniffers) > 0 { + packetSniffers = action.PacketSniffers + } else { + packetSniffers = []sniff.PacketSniffer{ + sniff.DomainNameQuery, + sniff.QUICClientHello, + sniff.STUNMessage, + sniff.UTP, + sniff.UDPTracker, + sniff.DTLSRecord, + sniff.NTP, + } + } + var err error + for _, packetBuffer := range inputPacketBuffers { + if quicMoreData() { + err = sniff.PeekPacket( + ctx, + metadata, + packetBuffer.Buffer.Bytes(), + sniff.QUICClientHello, + ) + } else { + err = sniff.PeekPacket( + ctx, metadata, + packetBuffer.Buffer.Bytes(), + packetSniffers..., + ) + } + metadata.SnifferNames = action.SnifferNames + metadata.SniffError = err + if errors.Is(err, sniff.ErrNeedMoreData) { + // TODO: replace with generic message when there are more multi-packet protocols + r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello") + continue + } + goto finally + } + packetBuffers = inputPacketBuffers + for { + var ( + sniffBuffer = buf.NewPacket() + destination M.Socksaddr + done = make(chan struct{}) + ) + go func() { + sniffTimeout := C.ReadPayloadTimeout + if action.Timeout > 0 { + sniffTimeout = action.Timeout + } + inputPacketConn.SetReadDeadline(time.Now().Add(sniffTimeout)) + destination, err = inputPacketConn.ReadPacket(sniffBuffer) + inputPacketConn.SetReadDeadline(time.Time{}) + close(done) + }() + select { + case <-done: + case <-ctx.Done(): + inputPacketConn.Close() + fatalErr = ctx.Err() + return + } + if err != nil { + sniffBuffer.Release() + if !errors.Is(err, context.DeadlineExceeded) { + fatalErr = err + return + } + } else { + if quicMoreData() { + err = sniff.PeekPacket( + ctx, + metadata, + sniffBuffer.Bytes(), + sniff.QUICClientHello, + ) + } else { + err = sniff.PeekPacket( + ctx, metadata, + sniffBuffer.Bytes(), + packetSniffers..., + ) + } + packetBuffer := N.NewPacketBuffer() + *packetBuffer = N.PacketBuffer{ + Buffer: sniffBuffer, + Destination: destination, + } + packetBuffers = append(packetBuffers, packetBuffer) + metadata.SnifferNames = action.SnifferNames + metadata.SniffError = err + if errors.Is(err, sniff.ErrNeedMoreData) { + // TODO: replace with generic message when there are more multi-packet protocols + r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello") + continue + } + } + goto finally + } + finally: + if err == nil { + //goland:noinspection GoDeprecation + if action.OverrideDestination && M.IsDomainName(metadata.Domain) { + metadata.Destination = M.Socksaddr{ + Fqdn: metadata.Domain, + Port: metadata.Destination.Port, + } + } + if metadata.Domain != "" && metadata.Client != "" { + r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain, ", client: ", metadata.Client) + } else if metadata.Domain != "" { + r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain) + } else if metadata.Client != "" { + r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", client: ", metadata.Client) + } else { + r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol) + } + } + } + return +} + +func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundContext, action *R.RuleActionResolve) error { + if metadata.Destination.IsDomain() { + var transport adapter.DNSTransport + if action.Server != "" { + var loaded bool + transport, loaded = r.dnsTransport.Transport(action.Server) + if !loaded { + return E.New("DNS server not found: ", action.Server) + } + } + addresses, err := r.dns.Lookup(adapter.WithContext(ctx, metadata), metadata.Destination.Fqdn, adapter.DNSQueryOptions{ + Transport: transport, + Strategy: action.Strategy, + DisableCache: action.DisableCache, + DisableOptimisticCache: action.DisableOptimisticCache, + RewriteTTL: action.RewriteTTL, + ClientSubnet: action.ClientSubnet, + }) + if err != nil { + return err + } + metadata.DestinationAddresses = addresses + r.logger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]") + } + return nil +} diff --git a/route/router.go b/route/router.go index 0c13b1986b..72f549c382 100644 --- a/route/router.go +++ b/route/router.go @@ -2,47 +2,22 @@ package route import ( "context" - "errors" - "net" - "net/netip" - "net/url" "os" - "os/user" "runtime" - "strings" "time" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/conntrack" - "github.com/sagernet/sing-box/common/dialer" - "github.com/sagernet/sing-box/common/geoip" - "github.com/sagernet/sing-box/common/geosite" "github.com/sagernet/sing-box/common/process" - "github.com/sagernet/sing-box/common/sniff" "github.com/sagernet/sing-box/common/taskmonitor" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-box/outbound" - "github.com/sagernet/sing-box/transport/fakeip" - "github.com/sagernet/sing-dns" - "github.com/sagernet/sing-mux" - "github.com/sagernet/sing-tun" - "github.com/sagernet/sing-vmess" + R "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/bufio" - "github.com/sagernet/sing/common/bufio/deadline" - "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/common/task" - "github.com/sagernet/sing/common/uot" - "github.com/sagernet/sing/common/winpowrprof" + "github.com/sagernet/sing/contrab/freelru" + "github.com/sagernet/sing/contrab/maphash" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/pause" ) @@ -50,528 +25,198 @@ import ( var _ adapter.Router = (*Router)(nil) type Router struct { - ctx context.Context - logger log.ContextLogger - dnsLogger log.ContextLogger - inboundByTag map[string]adapter.Inbound - outbounds []adapter.Outbound - outboundByTag map[string]adapter.Outbound - rules []adapter.Rule - defaultDetour string - defaultOutboundForConnection adapter.Outbound - defaultOutboundForPacketConnection adapter.Outbound - needGeoIPDatabase bool - needGeositeDatabase bool - geoIPOptions option.GeoIPOptions - geositeOptions option.GeositeOptions - geoIPReader *geoip.Reader - geositeReader *geosite.Reader - geositeCache map[string]adapter.Rule - needFindProcess bool - dnsClient *dns.Client - defaultDomainStrategy dns.DomainStrategy - dnsRules []adapter.DNSRule - ruleSets []adapter.RuleSet - ruleSetMap map[string]adapter.RuleSet - defaultTransport dns.Transport - transports []dns.Transport - transportMap map[string]dns.Transport - transportDomainStrategy map[dns.Transport]dns.DomainStrategy - dnsReverseMapping *DNSReverseMapping - fakeIPStore adapter.FakeIPStore - interfaceFinder *control.DefaultInterfaceFinder - autoDetectInterface bool - defaultInterface string - defaultMark int - networkMonitor tun.NetworkUpdateMonitor - interfaceMonitor tun.DefaultInterfaceMonitor - packageManager tun.PackageManager - powerListener winpowrprof.EventListener - processSearcher process.Searcher - timeService *ntp.Service - pauseManager pause.Manager - clashServer adapter.ClashServer - v2rayServer adapter.V2RayServer - platformInterface platform.Interface - needWIFIState bool - needPackageManager bool - wifiState adapter.WIFIState - started bool -} - -func NewRouter( - ctx context.Context, - logFactory log.Factory, - options option.RouteOptions, - dnsOptions option.DNSOptions, - ntpOptions option.NTPOptions, - inbounds []option.Inbound, - platformInterface platform.Interface, -) (*Router, error) { - router := &Router{ - ctx: ctx, - logger: logFactory.NewLogger("router"), - dnsLogger: logFactory.NewLogger("dns"), - outboundByTag: make(map[string]adapter.Outbound), - rules: make([]adapter.Rule, 0, len(options.Rules)), - dnsRules: make([]adapter.DNSRule, 0, len(dnsOptions.Rules)), - ruleSetMap: make(map[string]adapter.RuleSet), - needGeoIPDatabase: hasRule(options.Rules, isGeoIPRule) || hasDNSRule(dnsOptions.Rules, isGeoIPDNSRule), - needGeositeDatabase: hasRule(options.Rules, isGeositeRule) || hasDNSRule(dnsOptions.Rules, isGeositeDNSRule), - geoIPOptions: common.PtrValueOrDefault(options.GeoIP), - geositeOptions: common.PtrValueOrDefault(options.Geosite), - geositeCache: make(map[string]adapter.Rule), - needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess, - defaultDetour: options.Final, - defaultDomainStrategy: dns.DomainStrategy(dnsOptions.Strategy), - interfaceFinder: control.NewDefaultInterfaceFinder(), - autoDetectInterface: options.AutoDetectInterface, - defaultInterface: options.DefaultInterface, - defaultMark: options.DefaultMark, - pauseManager: service.FromContext[pause.Manager](ctx), - platformInterface: platformInterface, - needWIFIState: hasRule(options.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule), - needPackageManager: common.Any(inbounds, func(inbound option.Inbound) bool { - return len(inbound.TunOptions.IncludePackage) > 0 || len(inbound.TunOptions.ExcludePackage) > 0 - }), - } - router.dnsClient = dns.NewClient(dns.ClientOptions{ - DisableCache: dnsOptions.DNSClientOptions.DisableCache, - DisableExpire: dnsOptions.DNSClientOptions.DisableExpire, - IndependentCache: dnsOptions.DNSClientOptions.IndependentCache, - RDRC: func() dns.RDRCStore { - cacheFile := service.FromContext[adapter.CacheFile](ctx) - if cacheFile == nil { - return nil - } - if !cacheFile.StoreRDRC() { - return nil - } - return cacheFile - }, - Logger: router.dnsLogger, - }) - for i, ruleOptions := range options.Rules { - routeRule, err := NewRule(router, router.logger, ruleOptions, true) + ctx context.Context + logger log.ContextLogger + inbound adapter.InboundManager + outbound adapter.OutboundManager + dns adapter.DNSRouter + dnsTransport adapter.DNSTransportManager + connection adapter.ConnectionManager + network adapter.NetworkManager + httpClientManager adapter.HTTPClientManager + rules []adapter.Rule + needFindProcess bool + needFindNeighbor bool + leaseFiles []string + ruleSets []adapter.RuleSet + ruleSetMap map[string]adapter.RuleSet + processSearcher process.Searcher + processCache freelru.Cache[processCacheKey, processCacheEntry] + neighborResolver adapter.NeighborResolver + pauseManager pause.Manager + trackers []adapter.ConnectionTracker + platformInterface adapter.PlatformInterface + started bool +} + +func NewRouter(ctx context.Context, logFactory log.Factory, options option.RouteOptions, dnsOptions option.DNSOptions) *Router { + return &Router{ + ctx: ctx, + logger: logFactory.NewLogger("router"), + inbound: service.FromContext[adapter.InboundManager](ctx), + outbound: service.FromContext[adapter.OutboundManager](ctx), + dns: service.FromContext[adapter.DNSRouter](ctx), + dnsTransport: service.FromContext[adapter.DNSTransportManager](ctx), + connection: service.FromContext[adapter.ConnectionManager](ctx), + network: service.FromContext[adapter.NetworkManager](ctx), + httpClientManager: service.FromContext[adapter.HTTPClientManager](ctx), + rules: make([]adapter.Rule, 0, len(options.Rules)), + ruleSetMap: make(map[string]adapter.RuleSet), + needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess, + needFindNeighbor: hasRule(options.Rules, isNeighborRule) || hasDNSRule(dnsOptions.Rules, isNeighborDNSRule) || options.FindNeighbor, + leaseFiles: options.DHCPLeaseFiles, + pauseManager: service.FromContext[pause.Manager](ctx), + platformInterface: service.FromContext[adapter.PlatformInterface](ctx), + } +} + +func (r *Router) Initialize(rules []option.Rule, ruleSets []option.RuleSet) error { + for i, options := range rules { + err := R.ValidateNoNestedRuleActions(options) if err != nil { - return nil, E.Cause(err, "parse rule[", i, "]") + return E.Cause(err, "parse rule[", i, "]") } - router.rules = append(router.rules, routeRule) - } - for i, dnsRuleOptions := range dnsOptions.Rules { - dnsRule, err := NewDNSRule(router, router.logger, dnsRuleOptions, true) + rule, err := R.NewRule(r.ctx, r.logger, options, false) if err != nil { - return nil, E.Cause(err, "parse dns rule[", i, "]") + return E.Cause(err, "parse rule[", i, "]") } - router.dnsRules = append(router.dnsRules, dnsRule) + r.rules = append(r.rules, rule) } - for i, ruleSetOptions := range options.RuleSet { - if _, exists := router.ruleSetMap[ruleSetOptions.Tag]; exists { - return nil, E.New("duplicate rule-set tag: ", ruleSetOptions.Tag) + for i, options := range ruleSets { + if _, exists := r.ruleSetMap[options.Tag]; exists { + return E.New("duplicate rule-set tag: ", options.Tag) } - ruleSet, err := NewRuleSet(ctx, router, router.logger, ruleSetOptions) + ruleSet, err := R.NewRuleSet(r.ctx, r.logger, options) if err != nil { - return nil, E.Cause(err, "parse rule-set[", i, "]") + return E.Cause(err, "parse rule-set[", i, "]") } - router.ruleSets = append(router.ruleSets, ruleSet) - router.ruleSetMap[ruleSetOptions.Tag] = ruleSet + r.ruleSets = append(r.ruleSets, ruleSet) + r.ruleSetMap[options.Tag] = ruleSet } + return nil +} - transports := make([]dns.Transport, len(dnsOptions.Servers)) - dummyTransportMap := make(map[string]dns.Transport) - transportMap := make(map[string]dns.Transport) - transportTags := make([]string, len(dnsOptions.Servers)) - transportTagMap := make(map[string]bool) - transportDomainStrategy := make(map[dns.Transport]dns.DomainStrategy) - for i, server := range dnsOptions.Servers { - var tag string - if server.Tag != "" { - tag = server.Tag - } else { - tag = F.ToString(i) - } - if transportTagMap[tag] { - return nil, E.New("duplicate dns server tag: ", tag) - } - transportTags[i] = tag - transportTagMap[tag] = true - } - ctx = adapter.ContextWithRouter(ctx, router) - for { - lastLen := len(dummyTransportMap) - for i, server := range dnsOptions.Servers { - tag := transportTags[i] - if _, exists := dummyTransportMap[tag]; exists { - continue - } - var detour N.Dialer - if server.Detour == "" { - detour = dialer.NewRouter(router) - } else { - detour = dialer.NewDetour(router, server.Detour) - } - switch server.Address { - case "local": - default: - serverURL, _ := url.Parse(server.Address) - var serverAddress string - if serverURL != nil { - serverAddress = serverURL.Hostname() - } - if serverAddress == "" { - serverAddress = server.Address - } - notIpAddress := !M.ParseSocksaddr(serverAddress).Addr.IsValid() - if server.AddressResolver != "" { - if !transportTagMap[server.AddressResolver] { - return nil, E.New("parse dns server[", tag, "]: address resolver not found: ", server.AddressResolver) - } - if upstream, exists := dummyTransportMap[server.AddressResolver]; exists { - detour = dns.NewDialerWrapper(detour, router.dnsClient, upstream, dns.DomainStrategy(server.AddressStrategy), time.Duration(server.AddressFallbackDelay)) - } else { - continue +func (r *Router) Start(stage adapter.StartStage) error { + monitor := taskmonitor.New(r.logger, C.StartTimeout) + switch stage { + case adapter.StartStateStart: + var startContext *adapter.HTTPStartContext + if len(r.ruleSets) > 0 { + monitor.Start("initialize rule-set") + startContext = adapter.NewHTTPStartContext() + var ruleSetStartGroup task.Group + for i, ruleSet := range r.ruleSets { + ruleSetInPlace := ruleSet + ruleSetStartGroup.Append0(func(ctx context.Context) error { + err := ruleSetInPlace.StartContext(ctx, startContext) + if err != nil { + return E.Cause(err, "initialize rule-set[", i, "]") } - } else if notIpAddress && strings.Contains(server.Address, ".") { - return nil, E.New("parse dns server[", tag, "]: missing address_resolver") - } - } - var clientSubnet netip.Prefix - if server.ClientSubnet != nil { - clientSubnet = server.ClientSubnet.Build() - } else if dnsOptions.ClientSubnet != nil { - clientSubnet = dnsOptions.ClientSubnet.Build() + return nil + }) } - transport, err := dns.CreateTransport(dns.TransportOptions{ - Context: ctx, - Logger: logFactory.NewLogger(F.ToString("dns/transport[", tag, "]")), - Name: tag, - Dialer: detour, - Address: server.Address, - ClientSubnet: clientSubnet, - }) + ruleSetStartGroup.Concurrency(5) + ruleSetStartGroup.FastFail() + err := ruleSetStartGroup.Run(r.ctx) + monitor.Finish() if err != nil { - return nil, E.Cause(err, "parse dns server[", tag, "]") - } - transports[i] = transport - dummyTransportMap[tag] = transport - if server.Tag != "" { - transportMap[server.Tag] = transport - } - strategy := dns.DomainStrategy(server.Strategy) - if strategy != dns.DomainStrategyAsIS { - transportDomainStrategy[transport] = strategy + return err } } - if len(transports) == len(dummyTransportMap) { - break - } - if lastLen != len(dummyTransportMap) { - continue - } - unresolvedTags := common.MapIndexed(common.FilterIndexed(dnsOptions.Servers, func(index int, server option.DNSServerOptions) bool { - _, exists := dummyTransportMap[transportTags[index]] - return !exists - }), func(index int, server option.DNSServerOptions) string { - return transportTags[index] - }) - if len(unresolvedTags) == 0 { - panic(F.ToString("unexpected unresolved dns servers: ", len(transports), " ", len(dummyTransportMap), " ", len(transportMap))) - } - return nil, E.New("found circular reference in dns servers: ", strings.Join(unresolvedTags, " ")) - } - var defaultTransport dns.Transport - if dnsOptions.Final != "" { - defaultTransport = dummyTransportMap[dnsOptions.Final] - if defaultTransport == nil { - return nil, E.New("default dns server not found: ", dnsOptions.Final) - } - } - if defaultTransport == nil { - if len(transports) == 0 { - transports = append(transports, common.Must1(dns.CreateTransport(dns.TransportOptions{ - Context: ctx, - Name: "local", - Address: "local", - Dialer: common.Must1(dialer.NewDefault(router, option.DialerOptions{})), - }))) - } - defaultTransport = transports[0] - } - if _, isFakeIP := defaultTransport.(adapter.FakeIPTransport); isFakeIP { - return nil, E.New("default DNS server cannot be fakeip") - } - router.defaultTransport = defaultTransport - router.transports = transports - router.transportMap = transportMap - router.transportDomainStrategy = transportDomainStrategy - - if dnsOptions.ReverseMapping { - router.dnsReverseMapping = NewDNSReverseMapping() - } - - if fakeIPOptions := dnsOptions.FakeIP; fakeIPOptions != nil && dnsOptions.FakeIP.Enabled { - var inet4Range netip.Prefix - var inet6Range netip.Prefix - if fakeIPOptions.Inet4Range != nil { - inet4Range = *fakeIPOptions.Inet4Range - } - if fakeIPOptions.Inet6Range != nil { - inet6Range = *fakeIPOptions.Inet6Range + if startContext != nil { + startContext.Close() } - router.fakeIPStore = fakeip.NewStore(ctx, router.logger, inet4Range, inet6Range) - } - - usePlatformDefaultInterfaceMonitor := platformInterface != nil && platformInterface.UsePlatformDefaultInterfaceMonitor() - needInterfaceMonitor := options.AutoDetectInterface || common.Any(inbounds, func(inbound option.Inbound) bool { - return inbound.HTTPOptions.SetSystemProxy || inbound.MixedOptions.SetSystemProxy || inbound.TunOptions.AutoRoute - }) - - if !usePlatformDefaultInterfaceMonitor { - networkMonitor, err := tun.NewNetworkUpdateMonitor(router.logger) - if !((err != nil && !needInterfaceMonitor) || errors.Is(err, os.ErrInvalid)) { - if err != nil { - return nil, err - } - router.networkMonitor = networkMonitor - networkMonitor.RegisterCallback(func() { - _ = router.interfaceFinder.Update() - }) - interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(router.networkMonitor, router.logger, tun.DefaultInterfaceMonitorOptions{ - OverrideAndroidVPN: options.OverrideAndroidVPN, - UnderNetworkExtension: platformInterface != nil && platformInterface.UnderNetworkExtension(), - }) - if err != nil { - return nil, E.New("auto_detect_interface unsupported on current platform") + r.network.Initialize(r.ruleSets) + needFindProcess := r.needFindProcess + needFindNeighbor := r.needFindNeighbor + for _, ruleSet := range r.ruleSets { + metadata := ruleSet.Metadata() + if metadata.ContainsProcessRule { + needFindProcess = true } - interfaceMonitor.RegisterCallback(router.notifyNetworkUpdate) - router.interfaceMonitor = interfaceMonitor - } - } else { - interfaceMonitor := platformInterface.CreateDefaultInterfaceMonitor(router.logger) - interfaceMonitor.RegisterCallback(router.notifyNetworkUpdate) - router.interfaceMonitor = interfaceMonitor - } - - if ntpOptions.Enabled { - ntpDialer, err := dialer.New(router, ntpOptions.DialerOptions) - if err != nil { - return nil, E.Cause(err, "create NTP service") - } - timeService := ntp.NewService(ntp.Options{ - Context: ctx, - Dialer: ntpDialer, - Logger: logFactory.NewLogger("ntp"), - Server: ntpOptions.ServerOptions.Build(), - Interval: time.Duration(ntpOptions.Interval), - WriteToSystem: ntpOptions.WriteToSystem, - }) - service.MustRegister[ntp.TimeService](ctx, timeService) - router.timeService = timeService - } - return router, nil -} - -func (r *Router) Initialize(inbounds []adapter.Inbound, outbounds []adapter.Outbound, defaultOutbound func() adapter.Outbound) error { - inboundByTag := make(map[string]adapter.Inbound) - for _, inbound := range inbounds { - inboundByTag[inbound.Tag()] = inbound - } - outboundByTag := make(map[string]adapter.Outbound) - for _, detour := range outbounds { - outboundByTag[detour.Tag()] = detour - } - var defaultOutboundForConnection adapter.Outbound - var defaultOutboundForPacketConnection adapter.Outbound - if r.defaultDetour != "" { - detour, loaded := outboundByTag[r.defaultDetour] - if !loaded { - return E.New("default detour not found: ", r.defaultDetour) - } - if common.Contains(detour.Network(), N.NetworkTCP) { - defaultOutboundForConnection = detour } - if common.Contains(detour.Network(), N.NetworkUDP) { - defaultOutboundForPacketConnection = detour - } - } - if defaultOutboundForConnection == nil { - for _, detour := range outbounds { - if common.Contains(detour.Network(), N.NetworkTCP) { - defaultOutboundForConnection = detour - break - } + if C.IsAndroid && r.platformInterface != nil { + needFindProcess = true } - } - if defaultOutboundForPacketConnection == nil { - for _, detour := range outbounds { - if common.Contains(detour.Network(), N.NetworkUDP) { - defaultOutboundForPacketConnection = detour - break + r.needFindProcess = needFindProcess + if needFindProcess { + if r.platformInterface != nil && r.platformInterface.UsePlatformConnectionOwnerFinder() { + r.processSearcher = newPlatformSearcher(r.platformInterface) + } else { + monitor.Start("initialize process searcher") + searcher, err := process.NewSearcher(process.Config{ + Logger: r.logger, + PackageManager: r.network.PackageManager(), + }) + monitor.Finish() + if err != nil { + if err != os.ErrInvalid { + r.logger.Warn(E.Cause(err, "create process searcher")) + } + } else { + r.processSearcher = searcher + } } } - } - if defaultOutboundForConnection == nil || defaultOutboundForPacketConnection == nil { - detour := defaultOutbound() - if defaultOutboundForConnection == nil { - defaultOutboundForConnection = detour - } - if defaultOutboundForPacketConnection == nil { - defaultOutboundForPacketConnection = detour - } - outbounds = append(outbounds, detour) - outboundByTag[detour.Tag()] = detour - } - r.inboundByTag = inboundByTag - r.outbounds = outbounds - r.defaultOutboundForConnection = defaultOutboundForConnection - r.defaultOutboundForPacketConnection = defaultOutboundForPacketConnection - r.outboundByTag = outboundByTag - for i, rule := range r.rules { - if _, loaded := outboundByTag[rule.Outbound()]; !loaded { - return E.New("outbound not found for rule[", i, "]: ", rule.Outbound()) - } - } - return nil -} - -func (r *Router) Outbounds() []adapter.Outbound { - if !r.started { - return nil - } - return r.outbounds -} - -func (r *Router) PreStart() error { - monitor := taskmonitor.New(r.logger, C.StartTimeout) - if r.interfaceMonitor != nil { - monitor.Start("initialize interface monitor") - err := r.interfaceMonitor.Start() - monitor.Finish() - if err != nil { - return err - } - } - if r.networkMonitor != nil { - monitor.Start("initialize network monitor") - err := r.networkMonitor.Start() - monitor.Finish() - if err != nil { - return err - } - } - if r.fakeIPStore != nil { - monitor.Start("initialize fakeip store") - err := r.fakeIPStore.Start() - monitor.Finish() - if err != nil { - return err - } - } - return nil -} - -func (r *Router) Start() error { - monitor := taskmonitor.New(r.logger, C.StartTimeout) - if r.needGeoIPDatabase { - monitor.Start("initialize geoip database") - err := r.prepareGeoIPDatabase() - monitor.Finish() - if err != nil { - return err - } - } - if r.needGeositeDatabase { - monitor.Start("initialize geosite database") - err := r.prepareGeositeDatabase() - monitor.Finish() - if err != nil { - return err - } - } - if r.needGeositeDatabase { - for _, rule := range r.rules { - err := rule.UpdateGeosite() - if err != nil { - r.logger.Error("failed to initialize geosite: ", err) + if r.processSearcher != nil { + processCache := common.Must1(freelru.NewSharded[processCacheKey, processCacheEntry](256, maphash.NewHasher[processCacheKey]().Hash32)) + processCache.SetLifetime(200 * time.Millisecond) + r.processCache = processCache + } + r.needFindNeighbor = needFindNeighbor + if needFindNeighbor { + if r.platformInterface != nil && r.platformInterface.UsePlatformNeighborResolver() { + monitor.Start("initialize neighbor resolver") + resolver := newPlatformNeighborResolver(r.logger, r.platformInterface) + err := resolver.Start() + monitor.Finish() + if err != nil { + r.logger.Error(E.Cause(err, "start neighbor resolver")) + } else { + r.neighborResolver = resolver + } + } else { + monitor.Start("initialize neighbor resolver") + resolver, err := newNeighborResolver(r.logger, r.leaseFiles) + monitor.Finish() + if err != nil { + if err != os.ErrInvalid { + r.logger.Error(E.Cause(err, "create neighbor resolver")) + } + } else { + err = resolver.Start() + if err != nil { + r.logger.Error(E.Cause(err, "start neighbor resolver")) + } else { + r.neighborResolver = resolver + } + } } } - for _, rule := range r.dnsRules { - err := rule.UpdateGeosite() + case adapter.StartStatePostStart: + for i, rule := range r.rules { + monitor.Start("initialize rule[", i, "]") + err := rule.Start() + monitor.Finish() if err != nil { - r.logger.Error("failed to initialize geosite: ", err) + return E.Cause(err, "initialize rule[", i, "]") } } - err := common.Close(r.geositeReader) - if err != nil { - return err - } - r.geositeCache = nil - r.geositeReader = nil - } - - if runtime.GOOS == "windows" { - powerListener, err := winpowrprof.NewEventListener(r.notifyWindowsPowerEvent) - if err == nil { - r.powerListener = powerListener - } else { - r.logger.Warn("initialize power listener: ", err) - } - } - - if r.powerListener != nil { - monitor.Start("start power listener") - err := r.powerListener.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "start power listener") - } - } - - monitor.Start("initialize DNS client") - r.dnsClient.Start() - monitor.Finish() - - if C.IsAndroid && r.platformInterface == nil { - monitor.Start("initialize package manager") - packageManager, err := tun.NewPackageManager(r) - monitor.Finish() - if err != nil { - return E.Cause(err, "create package manager") - } - if r.needPackageManager { - monitor.Start("start package manager") - err = packageManager.Start() + for _, ruleSet := range r.ruleSets { + monitor.Start("post start rule_set[", ruleSet.Name(), "]") + err := ruleSet.PostStart() monitor.Finish() if err != nil { - return E.Cause(err, "start package manager") + return E.Cause(err, "post start rule_set[", ruleSet.Name(), "]") } } - r.packageManager = packageManager - } - - for i, rule := range r.dnsRules { - monitor.Start("initialize DNS rule[", i, "]") - err := rule.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "initialize DNS rule[", i, "]") - } - } - for i, transport := range r.transports { - monitor.Start("initialize DNS transport[", i, "]") - err := transport.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "initialize DNS server[", i, "]") - } - } - if r.timeService != nil { - monitor.Start("initialize time service") - err := r.timeService.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "initialize time service") + r.started = true + return nil + case adapter.StartStateStarted: + for _, ruleSet := range r.ruleSets { + ruleSet.Cleanup() } + runtime.GC() } return nil } @@ -579,702 +224,64 @@ func (r *Router) Start() error { func (r *Router) Close() error { monitor := taskmonitor.New(r.logger, C.StopTimeout) var err error - for i, rule := range r.rules { - monitor.Start("close rule[", i, "]") - err = E.Append(err, rule.Close(), func(err error) error { - return E.Cause(err, "close rule[", i, "]") + if r.neighborResolver != nil { + monitor.Start("close neighbor resolver") + err = E.Append(err, r.neighborResolver.Close(), func(closeErr error) error { + return E.Cause(closeErr, "close neighbor resolver") }) monitor.Finish() } - for i, rule := range r.dnsRules { - monitor.Start("close dns rule[", i, "]") + for i, rule := range r.rules { + monitor.Start("close rule[", i, "]") err = E.Append(err, rule.Close(), func(err error) error { - return E.Cause(err, "close dns rule[", i, "]") - }) - monitor.Finish() - } - for i, transport := range r.transports { - monitor.Start("close dns transport[", i, "]") - err = E.Append(err, transport.Close(), func(err error) error { - return E.Cause(err, "close dns transport[", i, "]") - }) - monitor.Finish() - } - if r.geoIPReader != nil { - monitor.Start("close geoip reader") - err = E.Append(err, r.geoIPReader.Close(), func(err error) error { - return E.Cause(err, "close geoip reader") - }) - monitor.Finish() - } - if r.interfaceMonitor != nil { - monitor.Start("close interface monitor") - err = E.Append(err, r.interfaceMonitor.Close(), func(err error) error { - return E.Cause(err, "close interface monitor") - }) - monitor.Finish() - } - if r.networkMonitor != nil { - monitor.Start("close network monitor") - err = E.Append(err, r.networkMonitor.Close(), func(err error) error { - return E.Cause(err, "close network monitor") - }) - monitor.Finish() - } - if r.packageManager != nil { - monitor.Start("close package manager") - err = E.Append(err, r.packageManager.Close(), func(err error) error { - return E.Cause(err, "close package manager") - }) - monitor.Finish() - } - if r.powerListener != nil { - monitor.Start("close power listener") - err = E.Append(err, r.powerListener.Close(), func(err error) error { - return E.Cause(err, "close power listener") + return E.Cause(err, "close rule[", i, "]") }) monitor.Finish() } - if r.timeService != nil { - monitor.Start("close time service") - err = E.Append(err, r.timeService.Close(), func(err error) error { - return E.Cause(err, "close time service") + for i, ruleSet := range r.ruleSets { + monitor.Start("close rule-set[", i, "]") + err = E.Append(err, ruleSet.Close(), func(err error) error { + return E.Cause(err, "close rule-set[", i, "]") }) monitor.Finish() } - if r.fakeIPStore != nil { - monitor.Start("close fakeip store") - err = E.Append(err, r.fakeIPStore.Close(), func(err error) error { - return E.Cause(err, "close fakeip store") + if r.processSearcher != nil { + monitor.Start("close process searcher") + err = E.Append(err, r.processSearcher.Close(), func(err error) error { + return E.Cause(err, "close process searcher") }) monitor.Finish() } return err } -func (r *Router) PostStart() error { - monitor := taskmonitor.New(r.logger, C.StopTimeout) - if len(r.ruleSets) > 0 { - monitor.Start("initialize rule-set") - ruleSetStartContext := NewRuleSetStartContext() - var ruleSetStartGroup task.Group - for i, ruleSet := range r.ruleSets { - ruleSetInPlace := ruleSet - ruleSetStartGroup.Append0(func(ctx context.Context) error { - err := ruleSetInPlace.StartContext(ctx, ruleSetStartContext) - if err != nil { - return E.Cause(err, "initialize rule-set[", i, "]") - } - return nil - }) - } - ruleSetStartGroup.Concurrency(5) - ruleSetStartGroup.FastFail() - err := ruleSetStartGroup.Run(r.ctx) - monitor.Finish() - if err != nil { - return err - } - ruleSetStartContext.Close() - } - needFindProcess := r.needFindProcess - needWIFIState := r.needWIFIState - for _, ruleSet := range r.ruleSets { - metadata := ruleSet.Metadata() - if metadata.ContainsProcessRule { - needFindProcess = true - } - if metadata.ContainsWIFIRule { - needWIFIState = true - } - } - if C.IsAndroid && r.platformInterface == nil && !r.needPackageManager { - if needFindProcess { - monitor.Start("start package manager") - err := r.packageManager.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "start package manager") - } - } else { - r.packageManager = nil - } - } - if needFindProcess { - if r.platformInterface != nil { - r.processSearcher = r.platformInterface - } else { - monitor.Start("initialize process searcher") - searcher, err := process.NewSearcher(process.Config{ - Logger: r.logger, - PackageManager: r.packageManager, - }) - monitor.Finish() - if err != nil { - if err != os.ErrInvalid { - r.logger.Warn(E.Cause(err, "create process searcher")) - } - } else { - r.processSearcher = searcher - } - } - } - if needWIFIState && r.platformInterface != nil { - monitor.Start("initialize WIFI state") - r.needWIFIState = true - r.interfaceMonitor.RegisterCallback(func(_ int) { - r.updateWIFIState() - }) - r.updateWIFIState() - monitor.Finish() - } - for i, rule := range r.rules { - monitor.Start("initialize rule[", i, "]") - err := rule.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "initialize rule[", i, "]") - } - } - r.started = true - return nil -} - -func (r *Router) Outbound(tag string) (adapter.Outbound, bool) { - outbound, loaded := r.outboundByTag[tag] - return outbound, loaded -} - -func (r *Router) DefaultOutbound(network string) (adapter.Outbound, error) { - if network == N.NetworkTCP { - if r.defaultOutboundForConnection == nil { - return nil, E.New("missing default outbound for TCP connections") - } - return r.defaultOutboundForConnection, nil - } else { - if r.defaultOutboundForPacketConnection == nil { - return nil, E.New("missing default outbound for UDP connections") - } - return r.defaultOutboundForPacketConnection, nil - } -} - -func (r *Router) FakeIPStore() adapter.FakeIPStore { - return r.fakeIPStore -} - func (r *Router) RuleSet(tag string) (adapter.RuleSet, bool) { ruleSet, loaded := r.ruleSetMap[tag] return ruleSet, loaded } -func (r *Router) NeedWIFIState() bool { - return r.needWIFIState -} - -func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - if r.pauseManager.IsDevicePaused() { - return E.New("reject connection to ", metadata.Destination, " while device paused") - } - - if metadata.InboundDetour != "" { - if metadata.LastInbound == metadata.InboundDetour { - return E.New("routing loop on detour: ", metadata.InboundDetour) - } - detour := r.inboundByTag[metadata.InboundDetour] - if detour == nil { - return E.New("inbound detour not found: ", metadata.InboundDetour) - } - injectable, isInjectable := detour.(adapter.InjectableInbound) - if !isInjectable { - return E.New("inbound detour is not injectable: ", metadata.InboundDetour) - } - if !common.Contains(injectable.Network(), N.NetworkTCP) { - return E.New("inject: TCP unsupported") - } - metadata.LastInbound = metadata.Inbound - metadata.Inbound = metadata.InboundDetour - metadata.InboundDetour = "" - err := injectable.NewConnection(ctx, conn, metadata) - if err != nil { - return E.Cause(err, "inject ", detour.Tag()) - } - return nil - } - conntrack.KillerCheck() - metadata.Network = N.NetworkTCP - switch metadata.Destination.Fqdn { - case mux.Destination.Fqdn: - return E.New("global multiplex is deprecated since sing-box v1.7.0, enable multiplex in inbound options instead.") - case vmess.MuxDestination.Fqdn: - return E.New("global multiplex (v2ray legacy) not supported since sing-box v1.7.0.") - case uot.MagicAddress: - return E.New("global UoT not supported since sing-box v1.7.0.") - case uot.LegacyMagicAddress: - return E.New("global UoT (legacy) not supported since sing-box v1.7.0.") - } - - if r.fakeIPStore != nil && r.fakeIPStore.Contains(metadata.Destination.Addr) { - domain, loaded := r.fakeIPStore.Lookup(metadata.Destination.Addr) - if !loaded { - return E.New("missing fakeip context") - } - metadata.OriginDestination = metadata.Destination - metadata.Destination = M.Socksaddr{ - Fqdn: domain, - Port: metadata.Destination.Port, - } - metadata.FakeIP = true - r.logger.DebugContext(ctx, "found fakeip domain: ", domain) - } - - if deadline.NeedAdditionalReadDeadline(conn) { - conn = deadline.NewConn(conn) - } - - if metadata.InboundOptions.SniffEnabled { - buffer := buf.NewPacket() - sniffMetadata, err := sniff.PeekStream(ctx, conn, buffer, time.Duration(metadata.InboundOptions.SniffTimeout), sniff.StreamDomainNameQuery, sniff.TLSClientHello, sniff.HTTPHost) - if sniffMetadata != nil { - metadata.Protocol = sniffMetadata.Protocol - metadata.Domain = sniffMetadata.Domain - if metadata.InboundOptions.SniffOverrideDestination && M.IsDomainName(metadata.Domain) { - metadata.Destination = M.Socksaddr{ - Fqdn: metadata.Domain, - Port: metadata.Destination.Port, - } - } - if metadata.Domain != "" { - r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol, ", domain: ", metadata.Domain) - } else { - r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol) - } - } else if err != nil { - r.logger.TraceContext(ctx, "sniffed no protocol: ", err) - } - if !buffer.IsEmpty() { - conn = bufio.NewCachedConn(conn, buffer) - } else { - buffer.Release() - } - } - - if r.dnsReverseMapping != nil && metadata.Domain == "" { - domain, loaded := r.dnsReverseMapping.Query(metadata.Destination.Addr) - if loaded { - metadata.Domain = domain - r.logger.DebugContext(ctx, "found reserve mapped domain: ", metadata.Domain) - } - } - - if metadata.Destination.IsFqdn() && dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS { - addresses, err := r.Lookup(adapter.WithContext(ctx, &metadata), metadata.Destination.Fqdn, dns.DomainStrategy(metadata.InboundOptions.DomainStrategy)) - if err != nil { - return err - } - metadata.DestinationAddresses = addresses - r.dnsLogger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]") - } - if metadata.Destination.IsIPv4() { - metadata.IPVersion = 4 - } else if metadata.Destination.IsIPv6() { - metadata.IPVersion = 6 - } - ctx, matchedRule, detour, err := r.match(ctx, &metadata, r.defaultOutboundForConnection) - if err != nil { - return err - } - if !common.Contains(detour.Network(), N.NetworkTCP) { - return E.New("missing supported outbound, closing connection") - } - if r.clashServer != nil { - trackerConn, tracker := r.clashServer.RoutedConnection(ctx, conn, metadata, matchedRule) - defer tracker.Leave() - conn = trackerConn - } - if r.v2rayServer != nil { - if statsService := r.v2rayServer.StatsService(); statsService != nil { - conn = statsService.RoutedConnection(metadata.Inbound, detour.Tag(), metadata.User, conn) - } - } - return detour.NewConnection(ctx, conn, metadata) -} - -func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - if r.pauseManager.IsDevicePaused() { - return E.New("reject packet connection to ", metadata.Destination, " while device paused") - } - if metadata.InboundDetour != "" { - if metadata.LastInbound == metadata.InboundDetour { - return E.New("routing loop on detour: ", metadata.InboundDetour) - } - detour := r.inboundByTag[metadata.InboundDetour] - if detour == nil { - return E.New("inbound detour not found: ", metadata.InboundDetour) - } - injectable, isInjectable := detour.(adapter.InjectableInbound) - if !isInjectable { - return E.New("inbound detour is not injectable: ", metadata.InboundDetour) - } - if !common.Contains(injectable.Network(), N.NetworkUDP) { - return E.New("inject: UDP unsupported") - } - metadata.LastInbound = metadata.Inbound - metadata.Inbound = metadata.InboundDetour - metadata.InboundDetour = "" - err := injectable.NewPacketConnection(ctx, conn, metadata) - if err != nil { - return E.Cause(err, "inject ", detour.Tag()) - } - return nil - } - conntrack.KillerCheck() - metadata.Network = N.NetworkUDP - - if r.fakeIPStore != nil && r.fakeIPStore.Contains(metadata.Destination.Addr) { - domain, loaded := r.fakeIPStore.Lookup(metadata.Destination.Addr) - if !loaded { - return E.New("missing fakeip context") - } - metadata.OriginDestination = metadata.Destination - metadata.Destination = M.Socksaddr{ - Fqdn: domain, - Port: metadata.Destination.Port, - } - metadata.FakeIP = true - r.logger.DebugContext(ctx, "found fakeip domain: ", domain) - } - - // Currently we don't have deadline usages for UDP connections - /*if deadline.NeedAdditionalReadDeadline(conn) { - conn = deadline.NewPacketConn(bufio.NewNetPacketConn(conn)) - }*/ - - if metadata.InboundOptions.SniffEnabled || metadata.Destination.Addr.IsUnspecified() { - var ( - buffer = buf.NewPacket() - destination M.Socksaddr - done = make(chan struct{}) - err error - ) - go func() { - sniffTimeout := C.ReadPayloadTimeout - if metadata.InboundOptions.SniffTimeout > 0 { - sniffTimeout = time.Duration(metadata.InboundOptions.SniffTimeout) - } - conn.SetReadDeadline(time.Now().Add(sniffTimeout)) - destination, err = conn.ReadPacket(buffer) - conn.SetReadDeadline(time.Time{}) - close(done) - }() - select { - case <-done: - case <-ctx.Done(): - conn.Close() - return ctx.Err() - } - if err != nil { - buffer.Release() - if !errors.Is(err, os.ErrDeadlineExceeded) { - return err - } - } else { - if metadata.Destination.Addr.IsUnspecified() { - metadata.Destination = destination - } - if metadata.InboundOptions.SniffEnabled { - sniffMetadata, _ := sniff.PeekPacket(ctx, buffer.Bytes(), sniff.DomainNameQuery, sniff.QUICClientHello, sniff.STUNMessage) - if sniffMetadata != nil { - metadata.Protocol = sniffMetadata.Protocol - metadata.Domain = sniffMetadata.Domain - if metadata.InboundOptions.SniffOverrideDestination && M.IsDomainName(metadata.Domain) { - metadata.Destination = M.Socksaddr{ - Fqdn: metadata.Domain, - Port: metadata.Destination.Port, - } - } - if metadata.Domain != "" { - r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain) - } else { - r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol) - } - } - } - conn = bufio.NewCachedPacketConn(conn, buffer, destination) - } - } - if r.dnsReverseMapping != nil && metadata.Domain == "" { - domain, loaded := r.dnsReverseMapping.Query(metadata.Destination.Addr) - if loaded { - metadata.Domain = domain - r.logger.DebugContext(ctx, "found reserve mapped domain: ", metadata.Domain) - } - } - if metadata.Destination.IsFqdn() && dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS { - addresses, err := r.Lookup(adapter.WithContext(ctx, &metadata), metadata.Destination.Fqdn, dns.DomainStrategy(metadata.InboundOptions.DomainStrategy)) - if err != nil { - return err - } - metadata.DestinationAddresses = addresses - r.dnsLogger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]") - } - if metadata.Destination.IsIPv4() { - metadata.IPVersion = 4 - } else if metadata.Destination.IsIPv6() { - metadata.IPVersion = 6 - } - ctx, matchedRule, detour, err := r.match(ctx, &metadata, r.defaultOutboundForPacketConnection) - if err != nil { - return err - } - if !common.Contains(detour.Network(), N.NetworkUDP) { - return E.New("missing supported outbound, closing packet connection") - } - if r.clashServer != nil { - trackerConn, tracker := r.clashServer.RoutedPacketConnection(ctx, conn, metadata, matchedRule) - defer tracker.Leave() - conn = trackerConn - } - if r.v2rayServer != nil { - if statsService := r.v2rayServer.StatsService(); statsService != nil { - conn = statsService.RoutedPacketConnection(metadata.Inbound, detour.Tag(), metadata.User, conn) - } - } - if metadata.FakeIP { - conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination) - } - return detour.NewPacketConnection(ctx, conn, metadata) -} - -func (r *Router) match(ctx context.Context, metadata *adapter.InboundContext, defaultOutbound adapter.Outbound) (context.Context, adapter.Rule, adapter.Outbound, error) { - matchRule, matchOutbound := r.match0(ctx, metadata, defaultOutbound) - if contextOutbound, loaded := outbound.TagFromContext(ctx); loaded { - if contextOutbound == matchOutbound.Tag() { - return nil, nil, nil, E.New("connection loopback in outbound/", matchOutbound.Type(), "[", matchOutbound.Tag(), "]") - } - } - ctx = outbound.ContextWithTag(ctx, matchOutbound.Tag()) - return ctx, matchRule, matchOutbound, nil -} - -func (r *Router) match0(ctx context.Context, metadata *adapter.InboundContext, defaultOutbound adapter.Outbound) (adapter.Rule, adapter.Outbound) { - if r.processSearcher != nil { - var originDestination netip.AddrPort - if metadata.OriginDestination.IsValid() { - originDestination = metadata.OriginDestination.AddrPort() - } else if metadata.Destination.IsIP() { - originDestination = metadata.Destination.AddrPort() - } - processInfo, err := process.FindProcessInfo(r.processSearcher, ctx, metadata.Network, metadata.Source.AddrPort(), originDestination) - if err != nil { - r.logger.InfoContext(ctx, "failed to search process: ", err) - } else { - if processInfo.ProcessPath != "" { - r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath) - } else if processInfo.PackageName != "" { - r.logger.InfoContext(ctx, "found package name: ", processInfo.PackageName) - } else if processInfo.UserId != -1 { - if /*needUserName &&*/ true { - osUser, _ := user.LookupId(F.ToString(processInfo.UserId)) - if osUser != nil { - processInfo.User = osUser.Username - } - } - if processInfo.User != "" { - r.logger.InfoContext(ctx, "found user: ", processInfo.User) - } else { - r.logger.InfoContext(ctx, "found user id: ", processInfo.UserId) - } - } - metadata.ProcessInfo = processInfo - } - } - for i, rule := range r.rules { - metadata.ResetRuleCache() - if rule.Match(metadata) { - detour := rule.Outbound() - r.logger.DebugContext(ctx, "match[", i, "] ", rule.String(), " => ", detour) - if outbound, loaded := r.Outbound(detour); loaded { - return rule, outbound - } - r.logger.ErrorContext(ctx, "outbound not found: ", detour) - } - } - return nil, defaultOutbound -} - -func (r *Router) InterfaceFinder() control.InterfaceFinder { - return r.interfaceFinder -} - -func (r *Router) UpdateInterfaces() error { - if r.platformInterface == nil || !r.platformInterface.UsePlatformInterfaceGetter() { - return r.interfaceFinder.Update() - } else { - interfaces, err := r.platformInterface.Interfaces() - if err != nil { - return err - } - r.interfaceFinder.UpdateInterfaces(interfaces) - return nil - } -} - -func (r *Router) AutoDetectInterface() bool { - return r.autoDetectInterface -} - -func (r *Router) AutoDetectInterfaceFunc() control.Func { - if r.platformInterface != nil && r.platformInterface.UsePlatformAutoDetectInterfaceControl() { - return r.platformInterface.AutoDetectInterfaceControl() - } else { - if r.interfaceMonitor == nil { - return nil - } - return control.BindToInterfaceFunc(r.InterfaceFinder(), func(network string, address string) (interfaceName string, interfaceIndex int, err error) { - remoteAddr := M.ParseSocksaddr(address).Addr - if C.IsLinux { - interfaceName, interfaceIndex = r.InterfaceMonitor().DefaultInterface(remoteAddr) - if interfaceIndex == -1 { - err = tun.ErrNoRoute - } - } else { - interfaceIndex = r.InterfaceMonitor().DefaultInterfaceIndex(remoteAddr) - if interfaceIndex == -1 { - err = tun.ErrNoRoute - } - } - return - }) - } -} - -func (r *Router) DefaultInterface() string { - return r.defaultInterface -} - -func (r *Router) DefaultMark() int { - return r.defaultMark -} - func (r *Router) Rules() []adapter.Rule { return r.rules } -func (r *Router) WIFIState() adapter.WIFIState { - return r.wifiState -} - -func (r *Router) NetworkMonitor() tun.NetworkUpdateMonitor { - return r.networkMonitor +func (r *Router) AppendTracker(tracker adapter.ConnectionTracker) { + r.trackers = append(r.trackers, tracker) } -func (r *Router) InterfaceMonitor() tun.DefaultInterfaceMonitor { - return r.interfaceMonitor +func (r *Router) NeedFindProcess() bool { + return r.needFindProcess } -func (r *Router) PackageManager() tun.PackageManager { - return r.packageManager +func (r *Router) NeedFindNeighbor() bool { + return r.needFindNeighbor } -func (r *Router) ClashServer() adapter.ClashServer { - return r.clashServer +func (r *Router) NeighborResolver() adapter.NeighborResolver { + return r.neighborResolver } -func (r *Router) SetClashServer(server adapter.ClashServer) { - r.clashServer = server -} - -func (r *Router) V2RayServer() adapter.V2RayServer { - return r.v2rayServer -} - -func (r *Router) SetV2RayServer(server adapter.V2RayServer) { - r.v2rayServer = server -} - -func (r *Router) OnPackagesUpdated(packages int, sharedUsers int) { - r.logger.Info("updated packages list: ", packages, " packages, ", sharedUsers, " shared users") -} - -func (r *Router) NewError(ctx context.Context, err error) { - common.Close(err) - if E.IsClosedOrCanceled(err) { - r.logger.DebugContext(ctx, "connection closed: ", err) - return - } - r.logger.ErrorContext(ctx, err) -} - -func (r *Router) notifyNetworkUpdate(event int) { - if event == tun.EventNoRoute { - r.pauseManager.NetworkPause() - r.logger.Error("missing default interface") - } else { - r.pauseManager.NetworkWake() - if C.IsAndroid && r.platformInterface == nil { - var vpnStatus string - if r.interfaceMonitor.AndroidVPNEnabled() { - vpnStatus = "enabled" - } else { - vpnStatus = "disabled" - } - r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified()), ", vpn ", vpnStatus) - } else { - r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified())) - } - } - - if !r.started { - return - } - - _ = r.ResetNetwork() -} - -func (r *Router) ResetNetwork() error { - conntrack.Close() - - for _, outbound := range r.outbounds { - listener, isListener := outbound.(adapter.InterfaceUpdateListener) - if isListener { - listener.InterfaceUpdated() - } - } - - for _, transport := range r.transports { - transport.Reset() - } - return nil -} - -func (r *Router) updateWIFIState() { - if r.platformInterface == nil { - return - } - state := r.platformInterface.ReadWIFIState() - if state != r.wifiState { - r.wifiState = state - if state.SSID == "" && state.BSSID == "" { - r.logger.Info("updated WIFI state: disconnected") - } else { - r.logger.Info("updated WIFI state: SSID=", state.SSID, ", BSSID=", state.BSSID) - } - } -} - -func (r *Router) notifyWindowsPowerEvent(event int) { - switch event { - case winpowrprof.EVENT_SUSPEND: - r.pauseManager.DevicePause() - _ = r.ResetNetwork() - case winpowrprof.EVENT_RESUME: - if !r.pauseManager.IsDevicePaused() { - return - } - fallthrough - case winpowrprof.EVENT_RESUME_AUTOMATIC: - r.pauseManager.DeviceWake() - _ = r.ResetNetwork() - } +func (r *Router) ResetNetwork() { + r.network.ResetNetwork() + r.httpClientManager.ResetNetwork() + r.dns.ResetNetwork() } diff --git a/route/router_dns.go b/route/router_dns.go deleted file mode 100644 index a8c4f44148..0000000000 --- a/route/router_dns.go +++ /dev/null @@ -1,285 +0,0 @@ -package route - -import ( - "context" - "errors" - "net/netip" - "strings" - "time" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-dns" - "github.com/sagernet/sing/common/cache" - E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" - M "github.com/sagernet/sing/common/metadata" - - mDNS "github.com/miekg/dns" -) - -type DNSReverseMapping struct { - cache *cache.LruCache[netip.Addr, string] -} - -func NewDNSReverseMapping() *DNSReverseMapping { - return &DNSReverseMapping{ - cache: cache.New[netip.Addr, string](), - } -} - -func (m *DNSReverseMapping) Save(address netip.Addr, domain string, ttl int) { - m.cache.StoreWithExpire(address, domain, time.Now().Add(time.Duration(ttl)*time.Second)) -} - -func (m *DNSReverseMapping) Query(address netip.Addr) (string, bool) { - domain, loaded := m.cache.Load(address) - return domain, loaded -} - -func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, index int, isAddressQuery bool) (context.Context, dns.Transport, dns.DomainStrategy, adapter.DNSRule, int) { - metadata := adapter.ContextFrom(ctx) - if metadata == nil { - panic("no context") - } - if index < len(r.dnsRules) { - dnsRules := r.dnsRules - if index != -1 { - dnsRules = dnsRules[index+1:] - } - for currentRuleIndex, rule := range dnsRules { - if rule.WithAddressLimit() && !isAddressQuery { - continue - } - metadata.ResetRuleCache() - if rule.Match(metadata) { - detour := rule.Outbound() - transport, loaded := r.transportMap[detour] - if !loaded { - r.dnsLogger.ErrorContext(ctx, "transport not found: ", detour) - continue - } - _, isFakeIP := transport.(adapter.FakeIPTransport) - if isFakeIP && !allowFakeIP { - continue - } - ruleIndex := currentRuleIndex - if index != -1 { - ruleIndex += index + 1 - } - r.dnsLogger.DebugContext(ctx, "match[", ruleIndex, "] ", rule.String(), " => ", detour) - if isFakeIP || rule.DisableCache() { - ctx = dns.ContextWithDisableCache(ctx, true) - } - if rewriteTTL := rule.RewriteTTL(); rewriteTTL != nil { - ctx = dns.ContextWithRewriteTTL(ctx, *rewriteTTL) - } - if clientSubnet := rule.ClientSubnet(); clientSubnet != nil { - ctx = dns.ContextWithClientSubnet(ctx, *clientSubnet) - } - if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded { - return ctx, transport, domainStrategy, rule, ruleIndex - } else { - return ctx, transport, r.defaultDomainStrategy, rule, ruleIndex - } - } - } - } - if domainStrategy, dsLoaded := r.transportDomainStrategy[r.defaultTransport]; dsLoaded { - return ctx, r.defaultTransport, domainStrategy, nil, -1 - } else { - return ctx, r.defaultTransport, r.defaultDomainStrategy, nil, -1 - } -} - -func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { - if len(message.Question) > 0 { - r.dnsLogger.DebugContext(ctx, "exchange ", formatQuestion(message.Question[0].String())) - } - var ( - response *mDNS.Msg - cached bool - transport dns.Transport - err error - ) - response, cached = r.dnsClient.ExchangeCache(ctx, message) - if !cached { - var metadata *adapter.InboundContext - ctx, metadata = adapter.AppendContext(ctx) - if len(message.Question) > 0 { - metadata.QueryType = message.Question[0].Qtype - switch metadata.QueryType { - case mDNS.TypeA: - metadata.IPVersion = 4 - case mDNS.TypeAAAA: - metadata.IPVersion = 6 - } - metadata.Domain = fqdnToDomain(message.Question[0].Name) - } - var ( - strategy dns.DomainStrategy - rule adapter.DNSRule - ruleIndex int - ) - ruleIndex = -1 - for { - var ( - dnsCtx context.Context - cancel context.CancelFunc - addressLimit bool - ) - - dnsCtx, transport, strategy, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message)) - dnsCtx, cancel = context.WithTimeout(dnsCtx, C.DNSTimeout) - if rule != nil && rule.WithAddressLimit() { - addressLimit = true - response, err = r.dnsClient.ExchangeWithResponseCheck(dnsCtx, transport, message, strategy, func(response *mDNS.Msg) bool { - metadata.DestinationAddresses, _ = dns.MessageToAddresses(response) - return rule.MatchAddressLimit(metadata) - }) - } else { - addressLimit = false - response, err = r.dnsClient.Exchange(dnsCtx, transport, message, strategy) - } - cancel() - var rejected bool - if err != nil { - if errors.Is(err, dns.ErrResponseRejectedCached) { - rejected = true - r.dnsLogger.DebugContext(ctx, E.Cause(err, "response rejected for ", formatQuestion(message.Question[0].String())), " (cached)") - } else if errors.Is(err, dns.ErrResponseRejected) { - rejected = true - r.dnsLogger.DebugContext(ctx, E.Cause(err, "response rejected for ", formatQuestion(message.Question[0].String()))) - } else if len(message.Question) > 0 { - r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", formatQuestion(message.Question[0].String()))) - } else { - r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for ")) - } - } - if addressLimit && rejected { - continue - } - break - } - } - if err != nil { - return nil, err - } - if r.dnsReverseMapping != nil && len(message.Question) > 0 && response != nil && len(response.Answer) > 0 { - if _, isFakeIP := transport.(adapter.FakeIPTransport); !isFakeIP { - for _, answer := range response.Answer { - switch record := answer.(type) { - case *mDNS.A: - r.dnsReverseMapping.Save(M.AddrFromIP(record.A), fqdnToDomain(record.Hdr.Name), int(record.Hdr.Ttl)) - case *mDNS.AAAA: - r.dnsReverseMapping.Save(M.AddrFromIP(record.AAAA), fqdnToDomain(record.Hdr.Name), int(record.Hdr.Ttl)) - } - } - } - } - return response, nil -} - -func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) { - var ( - responseAddrs []netip.Addr - cached bool - err error - ) - responseAddrs, cached = r.dnsClient.LookupCache(ctx, domain, strategy) - if cached { - return responseAddrs, nil - } - r.dnsLogger.DebugContext(ctx, "lookup domain ", domain) - ctx, metadata := adapter.AppendContext(ctx) - metadata.Domain = domain - var ( - transport dns.Transport - transportStrategy dns.DomainStrategy - rule adapter.DNSRule - ruleIndex int - ) - ruleIndex = -1 - for { - var ( - dnsCtx context.Context - cancel context.CancelFunc - addressLimit bool - ) - metadata.ResetRuleCache() - metadata.DestinationAddresses = nil - dnsCtx, transport, transportStrategy, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true) - if strategy == dns.DomainStrategyAsIS { - strategy = transportStrategy - } - dnsCtx, cancel = context.WithTimeout(dnsCtx, C.DNSTimeout) - if rule != nil && rule.WithAddressLimit() { - addressLimit = true - responseAddrs, err = r.dnsClient.LookupWithResponseCheck(dnsCtx, transport, domain, strategy, func(responseAddrs []netip.Addr) bool { - metadata.DestinationAddresses = responseAddrs - return rule.MatchAddressLimit(metadata) - }) - } else { - addressLimit = false - responseAddrs, err = r.dnsClient.Lookup(dnsCtx, transport, domain, strategy) - } - cancel() - if err != nil { - if errors.Is(err, dns.ErrResponseRejectedCached) { - r.dnsLogger.DebugContext(ctx, "response rejected for ", domain, " (cached)") - } else if errors.Is(err, dns.ErrResponseRejected) { - r.dnsLogger.DebugContext(ctx, "response rejected for ", domain) - } else { - r.dnsLogger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain)) - } - } else if len(responseAddrs) == 0 { - r.dnsLogger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result") - err = dns.RCodeNameError - } - if !addressLimit || err == nil { - break - } - } - if len(responseAddrs) > 0 { - r.dnsLogger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(responseAddrs), " ")) - } - return responseAddrs, err -} - -func (r *Router) LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error) { - return r.Lookup(ctx, domain, dns.DomainStrategyAsIS) -} - -func (r *Router) ClearDNSCache() { - r.dnsClient.ClearCache() - if r.platformInterface != nil { - r.platformInterface.ClearDNSCache() - } -} - -func isAddressQuery(message *mDNS.Msg) bool { - for _, question := range message.Question { - if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA || question.Qtype == mDNS.TypeHTTPS { - return true - } - } - return false -} - -func fqdnToDomain(fqdn string) string { - if mDNS.IsFqdn(fqdn) { - return fqdn[:len(fqdn)-1] - } - return fqdn -} - -func formatQuestion(string string) string { - if strings.HasPrefix(string, ";") { - string = string[1:] - } - string = strings.ReplaceAll(string, "\t", " ") - for strings.Contains(string, " ") { - string = strings.ReplaceAll(string, " ", " ") - } - return string -} diff --git a/route/router_geo_resources.go b/route/router_geo_resources.go deleted file mode 100644 index e0a572c92f..0000000000 --- a/route/router_geo_resources.go +++ /dev/null @@ -1,243 +0,0 @@ -package route - -import ( - "context" - "io" - "net" - "net/http" - "os" - "path/filepath" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/geoip" - "github.com/sagernet/sing-box/common/geosite" - C "github.com/sagernet/sing-box/constant" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - "github.com/sagernet/sing/common/rw" - "github.com/sagernet/sing/service/filemanager" -) - -func (r *Router) GeoIPReader() *geoip.Reader { - return r.geoIPReader -} - -func (r *Router) LoadGeosite(code string) (adapter.Rule, error) { - rule, cached := r.geositeCache[code] - if cached { - return rule, nil - } - items, err := r.geositeReader.Read(code) - if err != nil { - return nil, err - } - rule, err = NewDefaultRule(r, nil, geosite.Compile(items)) - if err != nil { - return nil, err - } - r.geositeCache[code] = rule - return rule, nil -} - -func (r *Router) prepareGeoIPDatabase() error { - var geoPath string - if r.geoIPOptions.Path != "" { - geoPath = r.geoIPOptions.Path - } else { - geoPath = "geoip.db" - if foundPath, loaded := C.FindPath(geoPath); loaded { - geoPath = foundPath - } - } - if !rw.FileExists(geoPath) { - geoPath = filemanager.BasePath(r.ctx, geoPath) - } - if stat, err := os.Stat(geoPath); err == nil { - if stat.IsDir() { - return E.New("geoip path is a directory: ", geoPath) - } - if stat.Size() == 0 { - os.Remove(geoPath) - } - } - if !rw.FileExists(geoPath) { - r.logger.Warn("geoip database not exists: ", geoPath) - var err error - for attempts := 0; attempts < 3; attempts++ { - err = r.downloadGeoIPDatabase(geoPath) - if err == nil { - break - } - r.logger.Error("download geoip database: ", err) - os.Remove(geoPath) - // time.Sleep(10 * time.Second) - } - if err != nil { - return err - } - } - geoReader, codes, err := geoip.Open(geoPath) - if err != nil { - return E.Cause(err, "open geoip database") - } - r.logger.Info("loaded geoip database: ", len(codes), " codes") - r.geoIPReader = geoReader - return nil -} - -func (r *Router) prepareGeositeDatabase() error { - var geoPath string - if r.geositeOptions.Path != "" { - geoPath = r.geositeOptions.Path - } else { - geoPath = "geosite.db" - if foundPath, loaded := C.FindPath(geoPath); loaded { - geoPath = foundPath - } - } - if !rw.FileExists(geoPath) { - geoPath = filemanager.BasePath(r.ctx, geoPath) - } - if stat, err := os.Stat(geoPath); err == nil { - if stat.IsDir() { - return E.New("geoip path is a directory: ", geoPath) - } - if stat.Size() == 0 { - os.Remove(geoPath) - } - } - if !rw.FileExists(geoPath) { - r.logger.Warn("geosite database not exists: ", geoPath) - var err error - for attempts := 0; attempts < 3; attempts++ { - err = r.downloadGeositeDatabase(geoPath) - if err == nil { - break - } - r.logger.Error("download geosite database: ", err) - os.Remove(geoPath) - } - if err != nil { - return err - } - } - geoReader, codes, err := geosite.Open(geoPath) - if err == nil { - r.logger.Info("loaded geosite database: ", len(codes), " codes") - r.geositeReader = geoReader - } else { - return E.Cause(err, "open geosite database") - } - return nil -} - -func (r *Router) downloadGeoIPDatabase(savePath string) error { - var downloadURL string - if r.geoIPOptions.DownloadURL != "" { - downloadURL = r.geoIPOptions.DownloadURL - } else { - downloadURL = "https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db" - } - r.logger.Info("downloading geoip database") - var detour adapter.Outbound - if r.geoIPOptions.DownloadDetour != "" { - outbound, loaded := r.Outbound(r.geoIPOptions.DownloadDetour) - if !loaded { - return E.New("detour outbound not found: ", r.geoIPOptions.DownloadDetour) - } - detour = outbound - } else { - detour = r.defaultOutboundForConnection - } - - if parentDir := filepath.Dir(savePath); parentDir != "" { - filemanager.MkdirAll(r.ctx, parentDir, 0o755) - } - - httpClient := &http.Client{ - Transport: &http.Transport{ - ForceAttemptHTTP2: true, - TLSHandshakeTimeout: 5 * time.Second, - DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - return detour.DialContext(ctx, network, M.ParseSocksaddr(addr)) - }, - }, - } - defer httpClient.CloseIdleConnections() - request, err := http.NewRequest("GET", downloadURL, nil) - if err != nil { - return err - } - response, err := httpClient.Do(request.WithContext(r.ctx)) - if err != nil { - return err - } - defer response.Body.Close() - - saveFile, err := filemanager.Create(r.ctx, savePath) - if err != nil { - return E.Cause(err, "open output file: ", downloadURL) - } - _, err = io.Copy(saveFile, response.Body) - saveFile.Close() - if err != nil { - filemanager.Remove(r.ctx, savePath) - } - return err -} - -func (r *Router) downloadGeositeDatabase(savePath string) error { - var downloadURL string - if r.geositeOptions.DownloadURL != "" { - downloadURL = r.geositeOptions.DownloadURL - } else { - downloadURL = "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db" - } - r.logger.Info("downloading geosite database") - var detour adapter.Outbound - if r.geositeOptions.DownloadDetour != "" { - outbound, loaded := r.Outbound(r.geositeOptions.DownloadDetour) - if !loaded { - return E.New("detour outbound not found: ", r.geositeOptions.DownloadDetour) - } - detour = outbound - } else { - detour = r.defaultOutboundForConnection - } - - if parentDir := filepath.Dir(savePath); parentDir != "" { - filemanager.MkdirAll(r.ctx, parentDir, 0o755) - } - - httpClient := &http.Client{ - Transport: &http.Transport{ - ForceAttemptHTTP2: true, - TLSHandshakeTimeout: 5 * time.Second, - DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - return detour.DialContext(ctx, network, M.ParseSocksaddr(addr)) - }, - }, - } - defer httpClient.CloseIdleConnections() - request, err := http.NewRequest("GET", downloadURL, nil) - if err != nil { - return err - } - response, err := httpClient.Do(request.WithContext(r.ctx)) - if err != nil { - return err - } - defer response.Body.Close() - - saveFile, err := filemanager.Create(r.ctx, savePath) - if err != nil { - return E.Cause(err, "open output file: ", downloadURL) - } - _, err = io.Copy(saveFile, response.Body) - saveFile.Close() - if err != nil { - filemanager.Remove(r.ctx, savePath) - } - return err -} diff --git a/route/router_rule.go b/route/router_rule.go deleted file mode 100644 index 4a99a31cc3..0000000000 --- a/route/router_rule.go +++ /dev/null @@ -1,103 +0,0 @@ -package route - -import ( - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common" -) - -func hasRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool { - for _, rule := range rules { - switch rule.Type { - case C.RuleTypeDefault: - if cond(rule.DefaultOptions) { - return true - } - case C.RuleTypeLogical: - if hasRule(rule.LogicalOptions.Rules, cond) { - return true - } - } - } - return false -} - -func hasDNSRule(rules []option.DNSRule, cond func(rule option.DefaultDNSRule) bool) bool { - for _, rule := range rules { - switch rule.Type { - case C.RuleTypeDefault: - if cond(rule.DefaultOptions) { - return true - } - case C.RuleTypeLogical: - if hasDNSRule(rule.LogicalOptions.Rules, cond) { - return true - } - } - } - return false -} - -func hasHeadlessRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool { - for _, rule := range rules { - switch rule.Type { - case C.RuleTypeDefault: - if cond(rule.DefaultOptions) { - return true - } - case C.RuleTypeLogical: - if hasHeadlessRule(rule.LogicalOptions.Rules, cond) { - return true - } - } - } - return false -} - -func isGeoIPRule(rule option.DefaultRule) bool { - return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) || len(rule.GeoIP) > 0 && common.Any(rule.GeoIP, notPrivateNode) -} - -func isGeoIPDNSRule(rule option.DefaultDNSRule) bool { - return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) || len(rule.GeoIP) > 0 && common.Any(rule.GeoIP, notPrivateNode) -} - -func isGeositeRule(rule option.DefaultRule) bool { - return len(rule.Geosite) > 0 -} - -func isGeositeDNSRule(rule option.DefaultDNSRule) bool { - return len(rule.Geosite) > 0 -} - -func isProcessRule(rule option.DefaultRule) bool { - return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0 -} - -func isProcessDNSRule(rule option.DefaultDNSRule) bool { - return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0 -} - -func isProcessHeadlessRule(rule option.DefaultHeadlessRule) bool { - return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 -} - -func notPrivateNode(code string) bool { - return code != "private" -} - -func isWIFIRule(rule option.DefaultRule) bool { - return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0 -} - -func isWIFIDNSRule(rule option.DefaultDNSRule) bool { - return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0 -} - -func isWIFIHeadlessRule(rule option.DefaultHeadlessRule) bool { - return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0 -} - -func isIPCIDRHeadlessRule(rule option.DefaultHeadlessRule) bool { - return len(rule.IPCIDR) > 0 || rule.IPSet != nil -} diff --git a/route/rule/match_state.go b/route/rule/match_state.go new file mode 100644 index 0000000000..feac8418a2 --- /dev/null +++ b/route/rule/match_state.go @@ -0,0 +1,126 @@ +package rule + +import "github.com/sagernet/sing-box/adapter" + +type ruleMatchState uint8 + +const ( + ruleMatchSourceAddress ruleMatchState = 1 << iota + ruleMatchSourcePort + ruleMatchDestinationAddress + ruleMatchDestinationPort +) + +type ruleMatchStateSet uint16 + +func singleRuleMatchState(state ruleMatchState) ruleMatchStateSet { + return 1 << state +} + +func emptyRuleMatchState() ruleMatchStateSet { + return singleRuleMatchState(0) +} + +func (s ruleMatchStateSet) isEmpty() bool { + return s == 0 +} + +func (s ruleMatchStateSet) contains(state ruleMatchState) bool { + return s&(1< 0 +} + +func (r *abstractDefaultRule) destinationIPCIDRMatchesDestination(metadata *adapter.InboundContext) bool { + return !metadata.IgnoreDestinationIPCIDRMatch && !metadata.IPCIDRMatchSource && len(r.destinationIPCIDRItems) > 0 +} + +func (r *abstractDefaultRule) requiresSourceAddressMatch(metadata *adapter.InboundContext) bool { + return len(r.sourceAddressItems) > 0 || r.destinationIPCIDRMatchesSource(metadata) +} + +func (r *abstractDefaultRule) requiresDestinationAddressMatch(metadata *adapter.InboundContext) bool { + return len(r.destinationAddressItems) > 0 || r.destinationIPCIDRMatchesDestination(metadata) +} + +func (r *abstractDefaultRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet { + return r.matchStatesWithBase(metadata, 0) +} + +func (r *abstractDefaultRule) matchStatesWithBase(metadata *adapter.InboundContext, inheritedBase ruleMatchState) ruleMatchStateSet { + if len(r.allItems) == 0 { + return emptyRuleMatchState().withBase(inheritedBase) + } + evaluationBase := inheritedBase + if r.invert { + evaluationBase = 0 + } + baseState := evaluationBase + if len(r.sourceAddressItems) > 0 { + metadata.DidMatch = true + if matchAnyItem(r.sourceAddressItems, metadata) { + baseState |= ruleMatchSourceAddress + } + } + if r.destinationIPCIDRMatchesSource(metadata) && !baseState.has(ruleMatchSourceAddress) { + metadata.DidMatch = true + if matchAnyItem(r.destinationIPCIDRItems, metadata) { + baseState |= ruleMatchSourceAddress + } + } else if r.destinationIPCIDRMatchesSource(metadata) { + metadata.DidMatch = true + } + if len(r.sourcePortItems) > 0 { + metadata.DidMatch = true + if matchAnyItem(r.sourcePortItems, metadata) { + baseState |= ruleMatchSourcePort + } + } + if len(r.destinationAddressItems) > 0 { + metadata.DidMatch = true + if matchAnyItem(r.destinationAddressItems, metadata) { + baseState |= ruleMatchDestinationAddress + } + } + if r.destinationIPCIDRMatchesDestination(metadata) && !baseState.has(ruleMatchDestinationAddress) { + metadata.DidMatch = true + if matchAnyItem(r.destinationIPCIDRItems, metadata) { + baseState |= ruleMatchDestinationAddress + } + } else if r.destinationIPCIDRMatchesDestination(metadata) { + metadata.DidMatch = true + } + if len(r.destinationPortItems) > 0 { + metadata.DidMatch = true + if matchAnyItem(r.destinationPortItems, metadata) { + baseState |= ruleMatchDestinationPort + } + } + for _, item := range r.items { + metadata.DidMatch = true + if !item.Match(metadata) { + return r.invertedFailure(inheritedBase) + } + } + var stateSet ruleMatchStateSet + if r.ruleSetItem != nil { + metadata.DidMatch = true + stateSet = matchRuleItemStatesWithBase(r.ruleSetItem, metadata, baseState) + } else { + stateSet = singleRuleMatchState(baseState) + } + stateSet = stateSet.filter(func(state ruleMatchState) bool { + if r.requiresSourceAddressMatch(metadata) && !state.has(ruleMatchSourceAddress) { + return false + } + if len(r.sourcePortItems) > 0 && !state.has(ruleMatchSourcePort) { + return false + } + if r.requiresDestinationAddressMatch(metadata) && !state.has(ruleMatchDestinationAddress) { + return false + } + if len(r.destinationPortItems) > 0 && !state.has(ruleMatchDestinationPort) { + return false + } + return true + }) + if stateSet.isEmpty() { + return r.invertedFailure(inheritedBase) + } + if r.invert { + if metadata.IgnoreDestinationIPCIDRMatch && stateSet == emptyRuleMatchState() && !metadata.DidMatch && len(r.destinationIPCIDRItems) > 0 { + return emptyRuleMatchState().withBase(inheritedBase) + } + return 0 + } + return stateSet +} + +func (r *abstractDefaultRule) invertedFailure(base ruleMatchState) ruleMatchStateSet { + if r.invert { + return emptyRuleMatchState().withBase(base) + } + return 0 +} + +func (r *abstractDefaultRule) Action() adapter.RuleAction { + return r.action +} + +func (r *abstractDefaultRule) String() string { + if !r.invert { + return strings.Join(F.MapToString(r.allItems), " ") + } else { + return "!(" + strings.Join(F.MapToString(r.allItems), " ") + ")" + } +} + +type abstractLogicalRule struct { + rules []adapter.HeadlessRule + mode string + invert bool + action adapter.RuleAction +} + +func (r *abstractLogicalRule) Type() string { + return C.RuleTypeLogical +} + +func (r *abstractLogicalRule) Start() error { + for _, rule := range common.FilterIsInstance(r.rules, func(it adapter.HeadlessRule) (interface { + Start() error + }, bool, + ) { + rule, loaded := it.(interface { + Start() error + }) + return rule, loaded + }) { + err := rule.Start() + if err != nil { + return err + } + } + return nil +} + +func (r *abstractLogicalRule) Close() error { + for _, rule := range common.FilterIsInstance(r.rules, func(it adapter.HeadlessRule) (io.Closer, bool) { + rule, loaded := it.(io.Closer) + return rule, loaded + }) { + err := rule.Close() + if err != nil { + return err + } + } + return nil +} + +func (r *abstractLogicalRule) Match(metadata *adapter.InboundContext) bool { + return !r.matchStates(metadata).isEmpty() +} + +func (r *abstractLogicalRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet { + return r.matchStatesWithBase(metadata, 0) +} + +func (r *abstractLogicalRule) matchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet { + evaluationBase := base + if r.invert { + evaluationBase = 0 + } + var stateSet ruleMatchStateSet + if r.mode == C.LogicalTypeAnd { + stateSet = emptyRuleMatchState().withBase(evaluationBase) + for _, rule := range r.rules { + nestedMetadata := *metadata + nestedMetadata.ResetRuleCache() + nestedStateSet := matchHeadlessRuleStatesWithBase(rule, &nestedMetadata, evaluationBase) + if nestedStateSet.isEmpty() { + if r.invert { + return emptyRuleMatchState().withBase(base) + } + return 0 + } + stateSet = stateSet.combine(nestedStateSet) + } + } else { + for _, rule := range r.rules { + nestedMetadata := *metadata + nestedMetadata.ResetRuleCache() + stateSet = stateSet.merge(matchHeadlessRuleStatesWithBase(rule, &nestedMetadata, evaluationBase)) + } + if stateSet.isEmpty() { + if r.invert { + return emptyRuleMatchState().withBase(base) + } + return 0 + } + } + if r.invert { + return 0 + } + return stateSet +} + +func (r *abstractLogicalRule) Action() adapter.RuleAction { + return r.action +} + +func (r *abstractLogicalRule) String() string { + var op string + switch r.mode { + case C.LogicalTypeAnd: + op = "&&" + case C.LogicalTypeOr: + op = "||" + } + if !r.invert { + return strings.Join(F.MapToString(r.rules), " "+op+" ") + } else { + return "!(" + strings.Join(F.MapToString(r.rules), " "+op+" ") + ")" + } +} + +func matchAnyItem(items []RuleItem, metadata *adapter.InboundContext) bool { + return common.Any(items, func(it RuleItem) bool { + return it.Match(metadata) + }) +} + +func (s ruleMatchState) has(target ruleMatchState) bool { + return s&target != 0 +} diff --git a/route/rule/rule_abstract_test.go b/route/rule/rule_abstract_test.go new file mode 100644 index 0000000000..ace3dec6ef --- /dev/null +++ b/route/rule/rule_abstract_test.go @@ -0,0 +1,157 @@ +package rule + +import ( + "context" + "testing" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common/x/list" + + "github.com/stretchr/testify/require" + "go4.org/netipx" +) + +type fakeRuleSet struct { + matched bool +} + +func (f *fakeRuleSet) Name() string { + return "fake-rule-set" +} + +func (f *fakeRuleSet) StartContext(context.Context, *adapter.HTTPStartContext) error { + return nil +} + +func (f *fakeRuleSet) PostStart() error { + return nil +} + +func (f *fakeRuleSet) Metadata() adapter.RuleSetMetadata { + return adapter.RuleSetMetadata{} +} + +func (f *fakeRuleSet) ExtractIPSet() []*netipx.IPSet { + return nil +} + +func (f *fakeRuleSet) IncRef() {} + +func (f *fakeRuleSet) DecRef() {} + +func (f *fakeRuleSet) Cleanup() {} + +func (f *fakeRuleSet) RegisterCallback(adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] { + return nil +} + +func (f *fakeRuleSet) UnregisterCallback(*list.Element[adapter.RuleSetUpdateCallback]) {} + +func (f *fakeRuleSet) Close() error { + return nil +} + +func (f *fakeRuleSet) Match(*adapter.InboundContext) bool { + return f.matched +} + +func (f *fakeRuleSet) String() string { + return "fake-rule-set" +} + +type fakeRuleItem struct { + matched bool +} + +func (f *fakeRuleItem) Match(*adapter.InboundContext) bool { + return f.matched +} + +func (f *fakeRuleItem) String() string { + return "fake-rule-item" +} + +func newRuleSetOnlyRule(ruleSetMatched bool, invert bool) *DefaultRule { + ruleSetItem := &RuleSetItem{ + setList: []adapter.RuleSet{&fakeRuleSet{matched: ruleSetMatched}}, + } + return &DefaultRule{ + abstractDefaultRule: abstractDefaultRule{ + ruleSetItem: ruleSetItem, + allItems: []RuleItem{ruleSetItem}, + invert: invert, + }, + } +} + +func newSingleItemRule(matched bool) *DefaultRule { + item := &fakeRuleItem{matched: matched} + return &DefaultRule{ + abstractDefaultRule: abstractDefaultRule{ + items: []RuleItem{item}, + allItems: []RuleItem{item}, + }, + } +} + +func TestAbstractDefaultRule_RuleSetOnly_InvertFalse(t *testing.T) { + t.Parallel() + require.True(t, newRuleSetOnlyRule(true, false).Match(&adapter.InboundContext{})) + require.False(t, newRuleSetOnlyRule(false, false).Match(&adapter.InboundContext{})) +} + +func TestAbstractDefaultRule_RuleSetOnly_InvertTrue(t *testing.T) { + t.Parallel() + require.False(t, newRuleSetOnlyRule(true, true).Match(&adapter.InboundContext{})) + require.True(t, newRuleSetOnlyRule(false, true).Match(&adapter.InboundContext{})) +} + +func TestAbstractLogicalRule_And_WithRuleSetInvert(t *testing.T) { + t.Parallel() + testCases := []struct { + name string + aMatched bool + ruleSetBMatch bool + expected bool + }{ + { + name: "A true B true", + aMatched: true, + ruleSetBMatch: true, + expected: false, + }, + { + name: "A true B false", + aMatched: true, + ruleSetBMatch: false, + expected: true, + }, + { + name: "A false B true", + aMatched: false, + ruleSetBMatch: true, + expected: false, + }, + { + name: "A false B false", + aMatched: false, + ruleSetBMatch: false, + expected: false, + }, + } + for _, testCase := range testCases { + testCase := testCase + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + logicalRule := &abstractLogicalRule{ + mode: C.LogicalTypeAnd, + rules: []adapter.HeadlessRule{ + newSingleItemRule(testCase.aMatched), + newRuleSetOnlyRule(testCase.ruleSetBMatch, true), + }, + } + require.Equal(t, testCase.expected, logicalRule.Match(&adapter.InboundContext{})) + }) + } +} diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go new file mode 100644 index 0000000000..7cb4ff6973 --- /dev/null +++ b/route/rule/rule_action.go @@ -0,0 +1,618 @@ +package rule + +import ( + "context" + "errors" + "net/netip" + "strings" + "sync" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/sniff" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + tun "github.com/sagernet/sing-tun" + "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" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + "github.com/miekg/dns" +) + +func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action option.RuleAction) (adapter.RuleAction, error) { + switch action.Action { + case "": + return nil, nil + case C.RuleActionTypeRoute: + return &RuleActionRoute{ + Outbound: action.RouteOptions.Outbound, + RuleActionRouteOptions: RuleActionRouteOptions{ + OverrideAddress: M.ParseSocksaddrHostPort(action.RouteOptions.OverrideAddress, 0), + OverridePort: action.RouteOptions.OverridePort, + OverrideTunnelDestination: action.RouteOptions.OverrideTunnelDestination, + NetworkStrategy: (*C.NetworkStrategy)(action.RouteOptions.NetworkStrategy), + FallbackDelay: time.Duration(action.RouteOptions.FallbackDelay), + UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping, + UDPConnect: action.RouteOptions.UDPConnect, + TLSFragment: action.RouteOptions.TLSFragment, + TLSFragmentFallbackDelay: time.Duration(action.RouteOptions.TLSFragmentFallbackDelay), + TLSRecordFragment: action.RouteOptions.TLSRecordFragment, + }, + }, nil + case C.RuleActionTypeRouteOptions: + return &RuleActionRouteOptions{ + OverrideAddress: M.ParseSocksaddrHostPort(action.RouteOptionsOptions.OverrideAddress, 0), + OverridePort: action.RouteOptionsOptions.OverridePort, + NetworkStrategy: (*C.NetworkStrategy)(action.RouteOptionsOptions.NetworkStrategy), + FallbackDelay: time.Duration(action.RouteOptionsOptions.FallbackDelay), + UDPDisableDomainUnmapping: action.RouteOptionsOptions.UDPDisableDomainUnmapping, + UDPConnect: action.RouteOptionsOptions.UDPConnect, + UDPTimeout: time.Duration(action.RouteOptionsOptions.UDPTimeout), + TLSFragment: action.RouteOptionsOptions.TLSFragment, + TLSFragmentFallbackDelay: time.Duration(action.RouteOptionsOptions.TLSFragmentFallbackDelay), + TLSRecordFragment: action.RouteOptionsOptions.TLSRecordFragment, + }, nil + case C.RuleActionTypeBypass: + return &RuleActionBypass{ + Outbound: action.BypassOptions.Outbound, + RuleActionRouteOptions: RuleActionRouteOptions{ + OverrideAddress: M.ParseSocksaddrHostPort(action.BypassOptions.OverrideAddress, 0), + OverridePort: action.BypassOptions.OverridePort, + NetworkStrategy: (*C.NetworkStrategy)(action.BypassOptions.NetworkStrategy), + FallbackDelay: time.Duration(action.BypassOptions.FallbackDelay), + UDPDisableDomainUnmapping: action.BypassOptions.UDPDisableDomainUnmapping, + UDPConnect: action.BypassOptions.UDPConnect, + TLSFragment: action.BypassOptions.TLSFragment, + TLSFragmentFallbackDelay: time.Duration(action.BypassOptions.TLSFragmentFallbackDelay), + TLSRecordFragment: action.BypassOptions.TLSRecordFragment, + }, + }, nil + case C.RuleActionTypeDirect: + directDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions), false) + if err != nil { + return nil, err + } + var description string + descriptions := action.DirectOptions.Descriptions() + switch len(descriptions) { + case 0: + case 1: + description = F.ToString("(", descriptions[0], ")") + case 2: + description = F.ToString("(", descriptions[0], ",", descriptions[1], ")") + default: + description = F.ToString("(", descriptions[0], ",", descriptions[1], ",...)") + } + return &RuleActionDirect{ + Dialer: directDialer, + description: description, + }, nil + case C.RuleActionTypeReject: + return &RuleActionReject{ + Method: action.RejectOptions.Method, + NoDrop: action.RejectOptions.NoDrop, + logger: logger, + }, nil + case C.RuleActionTypeHijackDNS: + return &RuleActionHijackDNS{}, nil + case C.RuleActionTypeSniff: + sniffAction := &RuleActionSniff{ + SnifferNames: action.SniffOptions.Sniffer, + Timeout: time.Duration(action.SniffOptions.Timeout), + } + return sniffAction, sniffAction.build() + case C.RuleActionTypeResolve: + return &RuleActionResolve{ + Server: action.ResolveOptions.Server, + Strategy: C.DomainStrategy(action.ResolveOptions.Strategy), + DisableCache: action.ResolveOptions.DisableCache, + DisableOptimisticCache: action.ResolveOptions.DisableOptimisticCache, + RewriteTTL: action.ResolveOptions.RewriteTTL, + ClientSubnet: action.ResolveOptions.ClientSubnet.Build(netip.Prefix{}), + }, nil + default: + panic(F.ToString("unknown rule action: ", action.Action)) + } +} + +func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) adapter.RuleAction { + switch action.Action { + case "": + return nil + case C.RuleActionTypeRoute: + return &RuleActionDNSRoute{ + Server: action.RouteOptions.Server, + RuleActionDNSRouteOptions: RuleActionDNSRouteOptions{ + Strategy: C.DomainStrategy(action.RouteOptions.Strategy), + DisableCache: action.RouteOptions.DisableCache, + DisableOptimisticCache: action.RouteOptions.DisableOptimisticCache, + RewriteTTL: action.RouteOptions.RewriteTTL, + ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)), + BypassIfFailed: action.RouteOptions.BypassIfFailed, + }, + } + case C.RuleActionTypeEvaluate: + return &RuleActionEvaluate{ + Server: action.RouteOptions.Server, + RuleActionDNSRouteOptions: RuleActionDNSRouteOptions{ + Strategy: C.DomainStrategy(action.RouteOptions.Strategy), + DisableCache: action.RouteOptions.DisableCache, + DisableOptimisticCache: action.RouteOptions.DisableOptimisticCache, + RewriteTTL: action.RouteOptions.RewriteTTL, + ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)), + }, + } + case C.RuleActionTypeRespond: + return &RuleActionRespond{} + case C.RuleActionTypeRouteOptions: + return &RuleActionDNSRouteOptions{ + Strategy: C.DomainStrategy(action.RouteOptionsOptions.Strategy), + DisableCache: action.RouteOptionsOptions.DisableCache, + DisableOptimisticCache: action.RouteOptionsOptions.DisableOptimisticCache, + RewriteTTL: action.RouteOptionsOptions.RewriteTTL, + ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptionsOptions.ClientSubnet)), + } + case C.RuleActionTypeReject: + return &RuleActionReject{ + Method: action.RejectOptions.Method, + NoDrop: action.RejectOptions.NoDrop, + logger: logger, + } + case C.RuleActionTypePredefined: + return &RuleActionPredefined{ + Rcode: action.PredefinedOptions.Rcode.Build(), + Answer: common.Map(action.PredefinedOptions.Answer, option.DNSRecordOptions.Build), + Ns: common.Map(action.PredefinedOptions.Ns, option.DNSRecordOptions.Build), + Extra: common.Map(action.PredefinedOptions.Extra, option.DNSRecordOptions.Build), + } + default: + panic(F.ToString("unknown rule action: ", action.Action)) + } +} + +type RuleActionRoute struct { + Outbound string + RuleActionRouteOptions +} + +func (r *RuleActionRoute) Type() string { + return C.RuleActionTypeRoute +} + +func (r *RuleActionRoute) String() string { + var descriptions []string + descriptions = append(descriptions, r.Outbound) + descriptions = append(descriptions, r.Descriptions()...) + return F.ToString("route(", strings.Join(descriptions, ","), ")") +} + +type RuleActionBypass struct { + Outbound string + RuleActionRouteOptions +} + +func (r *RuleActionBypass) Type() string { + return C.RuleActionTypeBypass +} + +func (r *RuleActionBypass) String() string { + if r.Outbound == "" { + return "bypass()" + } + var descriptions []string + descriptions = append(descriptions, r.Outbound) + descriptions = append(descriptions, r.Descriptions()...) + return F.ToString("bypass(", strings.Join(descriptions, ","), ")") +} + +type RuleActionRouteOptions struct { + OverrideAddress M.Socksaddr + OverridePort uint16 + OverrideTunnelDestination string + NetworkStrategy *C.NetworkStrategy + NetworkType []C.InterfaceType + FallbackNetworkType []C.InterfaceType + FallbackDelay time.Duration + UDPDisableDomainUnmapping bool + UDPConnect bool + UDPTimeout time.Duration + TLSFragment bool + TLSFragmentFallbackDelay time.Duration + TLSRecordFragment bool + BypassIfFailed bool +} + +func (r *RuleActionRouteOptions) Type() string { + return C.RuleActionTypeRouteOptions +} + +func (r *RuleActionRouteOptions) String() string { + return F.ToString("route-options(", strings.Join(r.Descriptions(), ","), ")") +} + +func (r *RuleActionRouteOptions) Descriptions() []string { + var descriptions []string + if r.OverrideAddress.IsValid() { + descriptions = append(descriptions, F.ToString("override-address=", r.OverrideAddress.AddrString())) + } + if r.OverridePort > 0 { + descriptions = append(descriptions, F.ToString("override-port=", r.OverridePort)) + } + if r.OverrideTunnelDestination != "" { + descriptions = append(descriptions, F.ToString("override-tunnel-destination=", r.OverrideTunnelDestination)) + } + if r.NetworkStrategy != nil { + descriptions = append(descriptions, F.ToString("network-strategy=", r.NetworkStrategy)) + } + if r.NetworkType != nil { + descriptions = append(descriptions, F.ToString("network-type=", strings.Join(common.Map(r.NetworkType, C.InterfaceType.String), ","))) + } + if r.FallbackNetworkType != nil { + descriptions = append(descriptions, F.ToString("fallback-network-type=", strings.Join(common.Map(r.FallbackNetworkType, C.InterfaceType.String), ","))) + } + if r.FallbackDelay > 0 { + descriptions = append(descriptions, F.ToString("fallback-delay=", r.FallbackDelay.String())) + } + if r.UDPDisableDomainUnmapping { + descriptions = append(descriptions, "udp-disable-domain-unmapping") + } + if r.UDPConnect { + descriptions = append(descriptions, "udp-connect") + } + if r.UDPTimeout > 0 { + descriptions = append(descriptions, "udp-timeout") + } + if r.TLSFragment { + descriptions = append(descriptions, "tls-fragment") + } + if r.TLSFragmentFallbackDelay > 0 { + descriptions = append(descriptions, F.ToString("tls-fragment-fallback-delay=", r.TLSFragmentFallbackDelay.String())) + } + if r.TLSRecordFragment { + descriptions = append(descriptions, "tls-record-fragment") + } + return descriptions +} + +type RuleActionDNSRoute struct { + Server string + RuleActionDNSRouteOptions +} + +func (r *RuleActionDNSRoute) Type() string { + return C.RuleActionTypeRoute +} + +func (r *RuleActionDNSRoute) String() string { + return formatDNSRouteAction("route", r.Server, r.RuleActionDNSRouteOptions) +} + +type RuleActionEvaluate struct { + Server string + RuleActionDNSRouteOptions +} + +func (r *RuleActionEvaluate) Type() string { + return C.RuleActionTypeEvaluate +} + +func (r *RuleActionEvaluate) String() string { + return formatDNSRouteAction("evaluate", r.Server, r.RuleActionDNSRouteOptions) +} + +type RuleActionRespond struct{} + +func (r *RuleActionRespond) Type() string { + return C.RuleActionTypeRespond +} + +func (r *RuleActionRespond) String() string { + return "respond" +} + +func formatDNSRouteAction(action string, server string, options RuleActionDNSRouteOptions) string { + var descriptions []string + descriptions = append(descriptions, server) + if options.DisableCache { + descriptions = append(descriptions, "disable-cache") + } + if options.DisableOptimisticCache { + descriptions = append(descriptions, "disable-optimistic-cache") + } + if options.RewriteTTL != nil { + descriptions = append(descriptions, F.ToString("rewrite-ttl=", *options.RewriteTTL)) + } + if options.ClientSubnet.IsValid() { + descriptions = append(descriptions, F.ToString("client-subnet=", options.ClientSubnet)) + } + return F.ToString(action, "(", strings.Join(descriptions, ","), ")") +} + +type RuleActionDNSRouteOptions struct { + Strategy C.DomainStrategy + DisableCache bool + DisableOptimisticCache bool + RewriteTTL *uint32 + ClientSubnet netip.Prefix + BypassIfFailed bool +} + +func (r *RuleActionDNSRouteOptions) Type() string { + return C.RuleActionTypeRouteOptions +} + +func (r *RuleActionDNSRouteOptions) String() string { + var descriptions []string + if r.DisableCache { + descriptions = append(descriptions, "disable-cache") + } + if r.DisableOptimisticCache { + descriptions = append(descriptions, "disable-optimistic-cache") + } + if r.RewriteTTL != nil { + descriptions = append(descriptions, F.ToString("rewrite-ttl=", *r.RewriteTTL)) + } + if r.ClientSubnet.IsValid() { + descriptions = append(descriptions, F.ToString("client-subnet=", r.ClientSubnet)) + } + return F.ToString("route-options(", strings.Join(descriptions, ","), ")") +} + +type RuleActionDirect struct { + Dialer N.Dialer + description string +} + +func (r *RuleActionDirect) Type() string { + return C.RuleActionTypeDirect +} + +func (r *RuleActionDirect) String() string { + return "direct" + r.description +} + +type RejectedError struct { + Cause error +} + +func (r *RejectedError) Error() string { + return "rejected" +} + +func (r *RejectedError) Unwrap() error { + return r.Cause +} + +func IsRejected(err error) bool { + var rejected *RejectedError + return errors.As(err, &rejected) +} + +type BypassedError struct { + Cause error +} + +func (b *BypassedError) Error() string { + return "bypassed" +} + +func (b *BypassedError) Unwrap() error { + return b.Cause +} + +func IsBypassed(err error) bool { + var bypassed *BypassedError + return errors.As(err, &bypassed) +} + +type RuleActionReject struct { + Method string + NoDrop bool + logger logger.ContextLogger + dropAccess sync.Mutex + dropCounter []time.Time +} + +func (r *RuleActionReject) Type() string { + return C.RuleActionTypeReject +} + +func (r *RuleActionReject) String() string { + if r.Method == C.RuleActionRejectMethodDefault { + return "reject" + } + return F.ToString("reject(", r.Method, ")") +} + +func (r *RuleActionReject) Error(ctx context.Context) error { + var returnErr error + switch r.Method { + case C.RuleActionRejectMethodDefault: + returnErr = &RejectedError{tun.ErrReset} + case C.RuleActionRejectMethodDrop: + return &RejectedError{tun.ErrDrop} + case C.RuleActionRejectMethodReply: + return nil + default: + panic(F.ToString("unknown reject method: ", r.Method)) + } + if r.NoDrop { + return returnErr + } + r.dropAccess.Lock() + defer r.dropAccess.Unlock() + timeNow := time.Now() + r.dropCounter = common.Filter(r.dropCounter, func(t time.Time) bool { + return timeNow.Sub(t) <= 30*time.Second + }) + r.dropCounter = append(r.dropCounter, timeNow) + if len(r.dropCounter) > 50 { + if ctx != nil { + r.logger.DebugContext(ctx, "dropped due to flooding") + } + return &RejectedError{tun.ErrDrop} + } + return returnErr +} + +type RuleActionHijackDNS struct{} + +func (r *RuleActionHijackDNS) Type() string { + return C.RuleActionTypeHijackDNS +} + +func (r *RuleActionHijackDNS) String() string { + return "hijack-dns" +} + +type RuleActionSniff struct { + SnifferNames []string + StreamSniffers []sniff.StreamSniffer + PacketSniffers []sniff.PacketSniffer + Timeout time.Duration + // Deprecated + OverrideDestination bool +} + +func (r *RuleActionSniff) Type() string { + return C.RuleActionTypeSniff +} + +func (r *RuleActionSniff) build() error { + for _, name := range r.SnifferNames { + switch name { + case C.ProtocolTLS: + r.StreamSniffers = append(r.StreamSniffers, sniff.TLSClientHello) + case C.ProtocolHTTP: + r.StreamSniffers = append(r.StreamSniffers, sniff.HTTPHost) + case C.ProtocolQUIC: + r.PacketSniffers = append(r.PacketSniffers, sniff.QUICClientHello) + case C.ProtocolDNS: + r.StreamSniffers = append(r.StreamSniffers, sniff.StreamDomainNameQuery) + r.PacketSniffers = append(r.PacketSniffers, sniff.DomainNameQuery) + case C.ProtocolSTUN: + r.PacketSniffers = append(r.PacketSniffers, sniff.STUNMessage) + case C.ProtocolBitTorrent: + r.StreamSniffers = append(r.StreamSniffers, sniff.BitTorrent) + r.PacketSniffers = append(r.PacketSniffers, sniff.UTP) + r.PacketSniffers = append(r.PacketSniffers, sniff.UDPTracker) + case C.ProtocolDTLS: + r.PacketSniffers = append(r.PacketSniffers, sniff.DTLSRecord) + case C.ProtocolSSH: + r.StreamSniffers = append(r.StreamSniffers, sniff.SSH) + case C.ProtocolRDP: + r.StreamSniffers = append(r.StreamSniffers, sniff.RDP) + case C.ProtocolNTP: + r.PacketSniffers = append(r.PacketSniffers, sniff.NTP) + default: + return E.New("unknown sniffer: ", name) + } + } + return nil +} + +func (r *RuleActionSniff) String() string { + if len(r.SnifferNames) == 0 && r.Timeout == 0 { + return "sniff" + } else if len(r.SnifferNames) > 0 && r.Timeout == 0 { + return F.ToString("sniff(", strings.Join(r.SnifferNames, ","), ")") + } else if len(r.SnifferNames) == 0 && r.Timeout > 0 { + return F.ToString("sniff(", r.Timeout.String(), ")") + } else { + return F.ToString("sniff(", strings.Join(r.SnifferNames, ","), ",", r.Timeout.String(), ")") + } +} + +type RuleActionResolve struct { + Server string + Strategy C.DomainStrategy + DisableCache bool + DisableOptimisticCache bool + RewriteTTL *uint32 + ClientSubnet netip.Prefix +} + +func (r *RuleActionResolve) Type() string { + return C.RuleActionTypeResolve +} + +func (r *RuleActionResolve) String() string { + var options []string + if r.Server != "" { + options = append(options, r.Server) + } + if r.Strategy != C.DomainStrategyAsIS { + options = append(options, F.ToString(option.DomainStrategy(r.Strategy))) + } + if r.DisableCache { + options = append(options, "disable_cache") + } + if r.DisableOptimisticCache { + options = append(options, "disable_optimistic_cache") + } + if r.RewriteTTL != nil { + options = append(options, F.ToString("rewrite_ttl=", *r.RewriteTTL)) + } + if r.ClientSubnet.IsValid() { + options = append(options, F.ToString("client_subnet=", r.ClientSubnet)) + } + if len(options) == 0 { + return "resolve" + } else { + return F.ToString("resolve(", strings.Join(options, ","), ")") + } +} + +type RuleActionPredefined struct { + Rcode int + Answer []dns.RR + Ns []dns.RR + Extra []dns.RR +} + +func (r *RuleActionPredefined) Type() string { + return C.RuleActionTypePredefined +} + +func (r *RuleActionPredefined) String() string { + var options []string + options = append(options, dns.RcodeToString[r.Rcode]) + options = append(options, common.Map(r.Answer, dns.RR.String)...) + options = append(options, common.Map(r.Ns, dns.RR.String)...) + options = append(options, common.Map(r.Extra, dns.RR.String)...) + return F.ToString("predefined(", strings.Join(options, ","), ")") +} + +func (r *RuleActionPredefined) Response(request *dns.Msg) *dns.Msg { + return &dns.Msg{ + MsgHdr: dns.MsgHdr{ + Id: request.Id, + Response: true, + Authoritative: true, + RecursionDesired: true, + RecursionAvailable: true, + Rcode: r.Rcode, + }, + Question: request.Question, + Answer: rewriteRecords(r.Answer, request.Question[0]), + Ns: rewriteRecords(r.Ns, request.Question[0]), + Extra: rewriteRecords(r.Extra, request.Question[0]), + } +} + +func rewriteRecords(records []dns.RR, question dns.Question) []dns.RR { + return common.Map(records, func(it dns.RR) dns.RR { + if strings.HasPrefix(it.Header().Name, "*") { + if strings.HasSuffix(question.Name, it.Header().Name[1:]) { + it = dns.Copy(it) + it.Header().Name = question.Name + } + } + return it + }) +} diff --git a/route/rule_default.go b/route/rule/rule_default.go similarity index 51% rename from route/rule_default.go rename to route/rule/rule_default.go index d1d13f7d72..02409c335a 100644 --- a/route/rule_default.go +++ b/route/rule/rule_default.go @@ -1,31 +1,41 @@ -package route +package rule import ( + "context" + "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/service" ) -func NewRule(router adapter.Router, logger log.ContextLogger, options option.Rule, checkOutbound bool) (adapter.Rule, error) { +func NewRule(ctx context.Context, logger log.ContextLogger, options option.Rule, checkOutbound bool) (adapter.Rule, error) { switch options.Type { case "", C.RuleTypeDefault: if !options.DefaultOptions.IsValid() { return nil, E.New("missing conditions") } - if options.DefaultOptions.Outbound == "" && checkOutbound { - return nil, E.New("missing outbound field") + switch options.DefaultOptions.Action { + case "", C.RuleActionTypeRoute: + if options.DefaultOptions.RouteOptions.Outbound == "" && checkOutbound { + return nil, E.New("missing outbound field") + } } - return NewDefaultRule(router, logger, options.DefaultOptions) + return NewDefaultRule(ctx, logger, options.DefaultOptions) case C.RuleTypeLogical: if !options.LogicalOptions.IsValid() { return nil, E.New("missing conditions") } - if options.LogicalOptions.Outbound == "" && checkOutbound { - return nil, E.New("missing outbound field") + switch options.LogicalOptions.Action { + case "", C.RuleActionTypeRoute: + if options.LogicalOptions.RouteOptions.Outbound == "" && checkOutbound { + return nil, E.New("missing outbound field") + } } - return NewLogicalRule(router, logger, options.LogicalOptions) + return NewLogicalRule(ctx, logger, options.LogicalOptions) default: return nil, E.New("unknown rule type: ", options.Type) } @@ -37,18 +47,28 @@ type DefaultRule struct { abstractDefaultRule } +func (r *DefaultRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet { + return r.abstractDefaultRule.matchStates(metadata) +} + type RuleItem interface { Match(metadata *adapter.InboundContext) bool String() string } -func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) { +func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) { + action, err := NewRuleAction(ctx, logger, options.RuleAction) + if err != nil { + return nil, E.Cause(err, "action") + } rule := &DefaultRule{ abstractDefaultRule{ - invert: options.Invert, - outbound: options.Outbound, + invert: options.Invert, + action: action, }, } + router := service.FromContext[adapter.Router](ctx) + networkManager := service.FromContext[adapter.NetworkManager](ctx) if len(options.Inbound) > 0 { item := NewInboundRule(options.Inbound) rule.items = append(rule.items, item) @@ -79,8 +99,16 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } + if len(options.Client) > 0 { + item := NewClientItem(options.Client) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } if len(options.Domain) > 0 || len(options.DomainSuffix) > 0 { - item := NewDomainItem(options.Domain, options.DomainSuffix) + item, err := NewDomainItem(options.Domain, options.DomainSuffix) + if err != nil { + return nil, err + } rule.destinationAddressItems = append(rule.destinationAddressItems, item) rule.allItems = append(rule.allItems, item) } @@ -92,25 +120,19 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt if len(options.DomainRegex) > 0 { item, err := NewDomainRegexItem(options.DomainRegex) if err != nil { - return nil, E.Cause(err, "domain_regex") + return nil, err } rule.destinationAddressItems = append(rule.destinationAddressItems, item) rule.allItems = append(rule.allItems, item) } if len(options.Geosite) > 0 { - item := NewGeositeItem(router, logger, options.Geosite) - rule.destinationAddressItems = append(rule.destinationAddressItems, item) - rule.allItems = append(rule.allItems, item) + return nil, E.New("geosite database is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0") } if len(options.SourceGeoIP) > 0 { - item := NewGeoIPItem(router, logger, true, options.SourceGeoIP) - rule.sourceAddressItems = append(rule.sourceAddressItems, item) - rule.allItems = append(rule.allItems, item) + return nil, E.New("geoip database is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0") } if len(options.GeoIP) > 0 { - item := NewGeoIPItem(router, logger, false, options.GeoIP) - rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) - rule.allItems = append(rule.allItems, item) + return nil, E.New("geoip database is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0") } if len(options.SourceIPCIDR) > 0 { item, err := NewIPCIDRItem(true, options.SourceIPCIDR) @@ -164,6 +186,16 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt rule.destinationPortItems = append(rule.destinationPortItems, item) rule.allItems = append(rule.allItems, item) } + if len(options.TunnelSource) > 0 { + item := NewTunnelSourceItem(options.TunnelSource) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.TunnelDestination) > 0 { + item := NewTunnelDestinationItem(options.TunnelDestination) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } if len(options.ProcessName) > 0 { item := NewProcessItem(options.ProcessName) rule.items = append(rule.items, item) @@ -174,11 +206,27 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } + if len(options.ProcessPathRegex) > 0 { + item, err := NewProcessPathRegexItem(options.ProcessPathRegex) + if err != nil { + return nil, E.Cause(err, "process_path_regex") + } + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } if len(options.PackageName) > 0 { item := NewPackageNameItem(options.PackageName) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } + if len(options.PackageNameRegex) > 0 { + item, err := NewPackageNameRegexItem(options.PackageNameRegex) + if err != nil { + return nil, E.Cause(err, "package_name_regex") + } + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } if len(options.User) > 0 { item := NewUserItem(options.User) rule.items = append(rule.items, item) @@ -190,25 +238,78 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt rule.allItems = append(rule.allItems, item) } if options.ClashMode != "" { - item := NewClashModeItem(router, options.ClashMode) + item := NewClashModeItem(ctx, options.ClashMode) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.NetworkType) > 0 { + item := NewNetworkTypeItem(networkManager, common.Map(options.NetworkType, option.InterfaceType.Build)) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkIsExpensive { + item := NewNetworkIsExpensiveItem(networkManager) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkIsConstrained { + item := NewNetworkIsConstrainedItem(networkManager) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } if len(options.WIFISSID) > 0 { - item := NewWIFISSIDItem(router, options.WIFISSID) + item := NewWIFISSIDItem(networkManager, options.WIFISSID) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } if len(options.WIFIBSSID) > 0 { - item := NewWIFIBSSIDItem(router, options.WIFIBSSID) + item := NewWIFIBSSIDItem(networkManager, options.WIFIBSSID) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } - if len(options.RuleSet) > 0 { - item := NewRuleSetItem(router, options.RuleSet, options.RuleSetIPCIDRMatchSource) + if options.InterfaceAddress != nil && options.InterfaceAddress.Size() > 0 { + item := NewInterfaceAddressItem(networkManager, options.InterfaceAddress) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkInterfaceAddress != nil && options.NetworkInterfaceAddress.Size() > 0 { + item := NewNetworkInterfaceAddressItem(networkManager, options.NetworkInterfaceAddress) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.DefaultInterfaceAddress) > 0 { + item := NewDefaultInterfaceAddressItem(networkManager, options.DefaultInterfaceAddress) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.SourceMACAddress) > 0 { + item := NewSourceMACAddressItem(options.SourceMACAddress) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.SourceHostname) > 0 { + item := NewSourceHostnameItem(options.SourceHostname) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.PreferredBy) > 0 { + item := NewPreferredByItem(ctx, options.PreferredBy) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } + if len(options.RuleSet) > 0 { + //nolint:staticcheck + if options.Deprecated_RulesetIPCIDRMatchSource { + return nil, E.New("rule_set_ipcidr_match_source is deprecated in sing-box 1.10.0 and removed in sing-box 1.11.0") + } + var matchSource bool + if options.RuleSetIPCIDRMatchSource { + matchSource = true + } + item := NewRuleSetItem(router, options.RuleSet, matchSource, false) + rule.ruleSetItem = item + rule.allItems = append(rule.allItems, item) + } return rule, nil } @@ -218,28 +319,40 @@ type LogicalRule struct { abstractLogicalRule } -func NewLogicalRule(router adapter.Router, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) { - r := &LogicalRule{ +func (r *LogicalRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet { + return r.abstractLogicalRule.matchStates(metadata) +} + +func NewLogicalRule(ctx context.Context, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) { + action, err := NewRuleAction(ctx, logger, options.RuleAction) + if err != nil { + return nil, E.Cause(err, "action") + } + rule := &LogicalRule{ abstractLogicalRule{ - rules: make([]adapter.HeadlessRule, len(options.Rules)), - invert: options.Invert, - outbound: options.Outbound, + rules: make([]adapter.HeadlessRule, len(options.Rules)), + invert: options.Invert, + action: action, }, } switch options.Mode { case C.LogicalTypeAnd: - r.mode = C.LogicalTypeAnd + rule.mode = C.LogicalTypeAnd case C.LogicalTypeOr: - r.mode = C.LogicalTypeOr + rule.mode = C.LogicalTypeOr default: return nil, E.New("unknown logical mode: ", options.Mode) } - for i, subRule := range options.Rules { - rule, err := NewRule(router, logger, subRule, false) + for i, subOptions := range options.Rules { + err = validateNoNestedRuleActions(subOptions, true) if err != nil { return nil, E.Cause(err, "sub rule[", i, "]") } - r.rules[i] = rule + subRule, err := NewRule(ctx, logger, subOptions, false) + if err != nil { + return nil, E.Cause(err, "sub rule[", i, "]") + } + rule.rules[i] = subRule } - return r, nil + return rule, nil } diff --git a/route/rule/rule_default_interface_address.go b/route/rule/rule_default_interface_address.go new file mode 100644 index 0000000000..2d7fdebe68 --- /dev/null +++ b/route/rule/rule_default_interface_address.go @@ -0,0 +1,56 @@ +package rule + +import ( + "net/netip" + "strings" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" +) + +var _ RuleItem = (*DefaultInterfaceAddressItem)(nil) + +type DefaultInterfaceAddressItem struct { + interfaceMonitor tun.DefaultInterfaceMonitor + interfaceAddresses []netip.Prefix +} + +func NewDefaultInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses badoption.Listable[*badoption.Prefixable]) *DefaultInterfaceAddressItem { + item := &DefaultInterfaceAddressItem{ + interfaceMonitor: networkManager.InterfaceMonitor(), + interfaceAddresses: make([]netip.Prefix, 0, len(interfaceAddresses)), + } + for _, prefixable := range interfaceAddresses { + item.interfaceAddresses = append(item.interfaceAddresses, prefixable.Build(netip.Prefix{})) + } + return item +} + +func (r *DefaultInterfaceAddressItem) Match(metadata *adapter.InboundContext) bool { + defaultInterface := r.interfaceMonitor.DefaultInterface() + if defaultInterface == nil { + return false + } + for _, address := range r.interfaceAddresses { + if common.All(defaultInterface.Addresses, func(it netip.Prefix) bool { + return !address.Overlaps(it) + }) { + return false + } + } + return true +} + +func (r *DefaultInterfaceAddressItem) String() string { + addressLen := len(r.interfaceAddresses) + switch { + case addressLen == 1: + return "default_interface_address=" + r.interfaceAddresses[0].String() + case addressLen > 3: + return "default_interface_address=[" + strings.Join(common.Map(r.interfaceAddresses[:3], netip.Prefix.String), " ") + "...]" + default: + return "default_interface_address=[" + strings.Join(common.Map(r.interfaceAddresses, netip.Prefix.String), " ") + "]" + } +} diff --git a/route/rule/rule_dns.go b/route/rule/rule_dns.go new file mode 100644 index 0000000000..438c6fbdaf --- /dev/null +++ b/route/rule/rule_dns.go @@ -0,0 +1,549 @@ +package rule + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/experimental/deprecated" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/service" + + "github.com/miekg/dns" +) + +func NewDNSRule(ctx context.Context, logger log.ContextLogger, options option.DNSRule, checkServer bool, legacyDNSMode bool) (adapter.DNSRule, error) { + switch options.Type { + case "", C.RuleTypeDefault: + if !options.DefaultOptions.IsValid() { + return nil, E.New("missing conditions") + } + if !checkServer && options.DefaultOptions.Action == C.RuleActionTypeEvaluate { + return nil, E.New(options.DefaultOptions.Action, " is only allowed on top-level DNS rules") + } + err := validateDNSRuleAction(options.DefaultOptions.DNSRuleAction) + if err != nil { + return nil, err + } + switch options.DefaultOptions.Action { + case "", C.RuleActionTypeRoute, C.RuleActionTypeEvaluate: + if options.DefaultOptions.RouteOptions.Server == "" && checkServer { + return nil, E.New("missing server field") + } + } + return NewDefaultDNSRule(ctx, logger, options.DefaultOptions, legacyDNSMode) + case C.RuleTypeLogical: + if !options.LogicalOptions.IsValid() { + return nil, E.New("missing conditions") + } + if !checkServer && options.LogicalOptions.Action == C.RuleActionTypeEvaluate { + return nil, E.New(options.LogicalOptions.Action, " is only allowed on top-level DNS rules") + } + err := validateDNSRuleAction(options.LogicalOptions.DNSRuleAction) + if err != nil { + return nil, err + } + switch options.LogicalOptions.Action { + case "", C.RuleActionTypeRoute, C.RuleActionTypeEvaluate: + if options.LogicalOptions.RouteOptions.Server == "" && checkServer { + return nil, E.New("missing server field") + } + } + return NewLogicalDNSRule(ctx, logger, options.LogicalOptions, legacyDNSMode) + default: + return nil, E.New("unknown rule type: ", options.Type) + } +} + +func validateDNSRuleAction(action option.DNSRuleAction) error { + if action.Action == C.RuleActionTypeReject && action.RejectOptions.Method == C.RuleActionRejectMethodReply { + return E.New("reject method `reply` is not supported for DNS rules") + } + return nil +} + +var _ adapter.DNSRule = (*DefaultDNSRule)(nil) + +type DefaultDNSRule struct { + abstractDefaultRule + matchResponse bool +} + +func (r *DefaultDNSRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet { + return r.abstractDefaultRule.matchStates(metadata) +} + +func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options option.DefaultDNSRule, legacyDNSMode bool) (*DefaultDNSRule, error) { + rule := &DefaultDNSRule{ + abstractDefaultRule: abstractDefaultRule{ + invert: options.Invert, + action: NewDNSRuleAction(logger, options.DNSRuleAction), + }, + matchResponse: options.MatchResponse, + } + if len(options.Inbound) > 0 { + item := NewInboundRule(options.Inbound) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + router := service.FromContext[adapter.Router](ctx) + networkManager := service.FromContext[adapter.NetworkManager](ctx) + if options.IPVersion > 0 { + switch options.IPVersion { + case 4, 6: + item := NewIPVersionItem(options.IPVersion == 6) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + default: + return nil, E.New("invalid ip version: ", options.IPVersion) + } + } + if len(options.QueryType) > 0 { + item := NewQueryTypeItem(options.QueryType) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.Network) > 0 { + item := NewNetworkItem(options.Network) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.AuthUser) > 0 { + item := NewAuthUserItem(options.AuthUser) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.Protocol) > 0 { + item := NewProtocolItem(options.Protocol) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.Domain) > 0 || len(options.DomainSuffix) > 0 { + item, err := NewDomainItem(options.Domain, options.DomainSuffix) + if err != nil { + return nil, err + } + rule.destinationAddressItems = append(rule.destinationAddressItems, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.DomainKeyword) > 0 { + item := NewDomainKeywordItem(options.DomainKeyword) + rule.destinationAddressItems = append(rule.destinationAddressItems, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.DomainRegex) > 0 { + item, err := NewDomainRegexItem(options.DomainRegex) + if err != nil { + return nil, E.Cause(err, "domain_regex") + } + rule.destinationAddressItems = append(rule.destinationAddressItems, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.Geosite) > 0 { //nolint:staticcheck + return nil, E.New("geosite database is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0") + } + if len(options.SourceGeoIP) > 0 { + return nil, E.New("geoip database is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0") + } + if len(options.GeoIP) > 0 { + return nil, E.New("geoip database is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0") + } + if len(options.SourceIPCIDR) > 0 { + item, err := NewIPCIDRItem(true, options.SourceIPCIDR) + if err != nil { + return nil, E.Cause(err, "source_ip_cidr") + } + rule.sourceAddressItems = append(rule.sourceAddressItems, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.IPCIDR) > 0 { + item, err := NewIPCIDRItem(false, options.IPCIDR) + if err != nil { + return nil, E.Cause(err, "ip_cidr") + } + rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) + rule.allItems = append(rule.allItems, item) + } + if options.SourceIPIsPrivate { + item := NewIPIsPrivateItem(true) + rule.sourceAddressItems = append(rule.sourceAddressItems, item) + rule.allItems = append(rule.allItems, item) + } + if options.IPIsPrivate { + item := NewIPIsPrivateItem(false) + rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) + rule.allItems = append(rule.allItems, item) + } + if options.IPAcceptAny { + item := NewIPAcceptAnyItem() + rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) + rule.allItems = append(rule.allItems, item) + } + if options.ResponseRcode != nil { + item := NewDNSResponseRCodeItem(int(*options.ResponseRcode)) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.ResponseAnswer) > 0 { + item := NewDNSResponseRecordItem("response_answer", options.ResponseAnswer, dnsResponseAnswers) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.ResponseNs) > 0 { + item := NewDNSResponseRecordItem("response_ns", options.ResponseNs, dnsResponseNS) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.ResponseExtra) > 0 { + item := NewDNSResponseRecordItem("response_extra", options.ResponseExtra, dnsResponseExtra) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.SourcePort) > 0 { + item := NewPortItem(true, options.SourcePort) + rule.sourcePortItems = append(rule.sourcePortItems, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.SourcePortRange) > 0 { + item, err := NewPortRangeItem(true, options.SourcePortRange) + if err != nil { + return nil, E.Cause(err, "source_port_range") + } + rule.sourcePortItems = append(rule.sourcePortItems, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.Port) > 0 { + item := NewPortItem(false, options.Port) + rule.destinationPortItems = append(rule.destinationPortItems, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.PortRange) > 0 { + item, err := NewPortRangeItem(false, options.PortRange) + if err != nil { + return nil, E.Cause(err, "port_range") + } + rule.destinationPortItems = append(rule.destinationPortItems, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.TunnelSource) > 0 { + item := NewTunnelSourceItem(options.TunnelSource) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.TunnelDestination) > 0 { + item := NewTunnelDestinationItem(options.TunnelDestination) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.ProcessName) > 0 { + item := NewProcessItem(options.ProcessName) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.ProcessPath) > 0 { + item := NewProcessPathItem(options.ProcessPath) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.ProcessPathRegex) > 0 { + item, err := NewProcessPathRegexItem(options.ProcessPathRegex) + if err != nil { + return nil, E.Cause(err, "process_path_regex") + } + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.PackageName) > 0 { + item := NewPackageNameItem(options.PackageName) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.PackageNameRegex) > 0 { + item, err := NewPackageNameRegexItem(options.PackageNameRegex) + if err != nil { + return nil, E.Cause(err, "package_name_regex") + } + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.User) > 0 { + item := NewUserItem(options.User) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.UserID) > 0 { + item := NewUserIDItem(options.UserID) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.Outbound) > 0 { + item := NewOutboundRule(ctx, options.Outbound) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.ClashMode != "" { + item := NewClashModeItem(ctx, options.ClashMode) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.NetworkType) > 0 { + item := NewNetworkTypeItem(networkManager, common.Map(options.NetworkType, option.InterfaceType.Build)) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkIsExpensive { + item := NewNetworkIsExpensiveItem(networkManager) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkIsConstrained { + item := NewNetworkIsConstrainedItem(networkManager) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.WIFISSID) > 0 { + item := NewWIFISSIDItem(networkManager, options.WIFISSID) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.WIFIBSSID) > 0 { + item := NewWIFIBSSIDItem(networkManager, options.WIFIBSSID) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.InterfaceAddress != nil && options.InterfaceAddress.Size() > 0 { + item := NewInterfaceAddressItem(networkManager, options.InterfaceAddress) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkInterfaceAddress != nil && options.NetworkInterfaceAddress.Size() > 0 { + item := NewNetworkInterfaceAddressItem(networkManager, options.NetworkInterfaceAddress) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.DefaultInterfaceAddress) > 0 { + item := NewDefaultInterfaceAddressItem(networkManager, options.DefaultInterfaceAddress) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.SourceMACAddress) > 0 { + item := NewSourceMACAddressItem(options.SourceMACAddress) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.SourceHostname) > 0 { + item := NewSourceHostnameItem(options.SourceHostname) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.RuleSetIPCIDRAcceptEmpty { //nolint:staticcheck + if legacyDNSMode { + deprecated.Report(ctx, deprecated.OptionRuleSetIPCIDRAcceptEmpty) + } else { + return nil, E.New(deprecated.OptionRuleSetIPCIDRAcceptEmpty.MessageWithLink()) + } + } + if len(options.RuleSet) > 0 { + //nolint:staticcheck + if options.Deprecated_RulesetIPCIDRMatchSource { + return nil, E.New("rule_set_ipcidr_match_source is deprecated in sing-box 1.10.0 and removed in sing-box 1.11.0") + } + var matchSource bool + if options.RuleSetIPCIDRMatchSource { + matchSource = true + } + item := NewRuleSetItem(router, options.RuleSet, matchSource, options.RuleSetIPCIDRAcceptEmpty) //nolint:staticcheck + rule.ruleSetItem = item + rule.allItems = append(rule.allItems, item) + } + return rule, nil +} + +func (r *DefaultDNSRule) Action() adapter.RuleAction { + return r.action +} + +func (r *DefaultDNSRule) WithAddressLimit() bool { + if len(r.destinationIPCIDRItems) > 0 { + return true + } + if r.ruleSetItem != nil { + ruleSet, isRuleSet := r.ruleSetItem.(*RuleSetItem) + if isRuleSet && ruleSet.ContainsDestinationIPCIDRRule() { + return true + } + } + return false +} + +func (r *DefaultDNSRule) Match(metadata *adapter.InboundContext) bool { + return !r.matchStatesForMatch(metadata).isEmpty() +} + +func (r *DefaultDNSRule) LegacyPreMatch(metadata *adapter.InboundContext) bool { + if r.matchResponse { + return false + } + metadata.IgnoreDestinationIPCIDRMatch = true + defer func() { metadata.IgnoreDestinationIPCIDRMatch = false }() + return !r.abstractDefaultRule.matchStates(metadata).isEmpty() +} + +func (r *DefaultDNSRule) matchStatesForMatch(metadata *adapter.InboundContext) ruleMatchStateSet { + if r.matchResponse { + if metadata.DNSResponse == nil { + return r.abstractDefaultRule.invertedFailure(0) + } + matchMetadata := *metadata + matchMetadata.DestinationAddressMatchFromResponse = true + return r.abstractDefaultRule.matchStates(&matchMetadata) + } + return r.abstractDefaultRule.matchStates(metadata) +} + +func (r *DefaultDNSRule) MatchAddressLimit(metadata *adapter.InboundContext, response *dns.Msg) bool { + matchMetadata := *metadata + matchMetadata.DNSResponse = response + matchMetadata.DestinationAddressMatchFromResponse = true + return !r.abstractDefaultRule.matchStates(&matchMetadata).isEmpty() +} + +var _ adapter.DNSRule = (*LogicalDNSRule)(nil) + +type LogicalDNSRule struct { + abstractLogicalRule +} + +func (r *LogicalDNSRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet { + return r.abstractLogicalRule.matchStates(metadata) +} + +func matchDNSHeadlessRuleStatesForMatch(rule adapter.HeadlessRule, metadata *adapter.InboundContext) ruleMatchStateSet { + switch typedRule := rule.(type) { + case *DefaultDNSRule: + return typedRule.matchStatesForMatch(metadata) + case *LogicalDNSRule: + return typedRule.matchStatesForMatch(metadata) + default: + return matchHeadlessRuleStates(typedRule, metadata) + } +} + +func (r *LogicalDNSRule) matchStatesForMatch(metadata *adapter.InboundContext) ruleMatchStateSet { + var stateSet ruleMatchStateSet + if r.mode == C.LogicalTypeAnd { + stateSet = emptyRuleMatchState() + for _, rule := range r.rules { + nestedMetadata := *metadata + nestedMetadata.ResetRuleCache() + nestedStateSet := matchDNSHeadlessRuleStatesForMatch(rule, &nestedMetadata) + if nestedStateSet.isEmpty() { + if r.invert { + return emptyRuleMatchState() + } + return 0 + } + stateSet = stateSet.combine(nestedStateSet) + } + } else { + for _, rule := range r.rules { + nestedMetadata := *metadata + nestedMetadata.ResetRuleCache() + stateSet = stateSet.merge(matchDNSHeadlessRuleStatesForMatch(rule, &nestedMetadata)) + } + if stateSet.isEmpty() { + if r.invert { + return emptyRuleMatchState() + } + return 0 + } + } + if r.invert { + return 0 + } + return stateSet +} + +func NewLogicalDNSRule(ctx context.Context, logger log.ContextLogger, options option.LogicalDNSRule, legacyDNSMode bool) (*LogicalDNSRule, error) { + r := &LogicalDNSRule{ + abstractLogicalRule: abstractLogicalRule{ + rules: make([]adapter.HeadlessRule, len(options.Rules)), + invert: options.Invert, + action: NewDNSRuleAction(logger, options.DNSRuleAction), + }, + } + switch options.Mode { + case C.LogicalTypeAnd: + r.mode = C.LogicalTypeAnd + case C.LogicalTypeOr: + r.mode = C.LogicalTypeOr + default: + return nil, E.New("unknown logical mode: ", options.Mode) + } + for i, subRule := range options.Rules { + err := validateNoNestedDNSRuleActions(subRule, true) + if err != nil { + return nil, E.Cause(err, "sub rule[", i, "]") + } + rule, err := NewDNSRule(ctx, logger, subRule, false, legacyDNSMode) + if err != nil { + return nil, E.Cause(err, "sub rule[", i, "]") + } + r.rules[i] = rule + } + return r, nil +} + +func (r *LogicalDNSRule) Action() adapter.RuleAction { + return r.action +} + +func (r *LogicalDNSRule) WithAddressLimit() bool { + for _, rawRule := range r.rules { + switch rule := rawRule.(type) { + case *DefaultDNSRule: + if rule.WithAddressLimit() { + return true + } + case *LogicalDNSRule: + if rule.WithAddressLimit() { + return true + } + } + } + return false +} + +func (r *LogicalDNSRule) Match(metadata *adapter.InboundContext) bool { + return !r.matchStatesForMatch(metadata).isEmpty() +} + +func (r *LogicalDNSRule) LegacyPreMatch(metadata *adapter.InboundContext) bool { + metadata.IgnoreDestinationIPCIDRMatch = true + defer func() { metadata.IgnoreDestinationIPCIDRMatch = false }() + return !r.abstractLogicalRule.matchStates(metadata).isEmpty() +} + +func (r *LogicalDNSRule) MatchAddressLimit(metadata *adapter.InboundContext, response *dns.Msg) bool { + matchMetadata := *metadata + matchMetadata.DNSResponse = response + matchMetadata.DestinationAddressMatchFromResponse = true + return !r.abstractLogicalRule.matchStates(&matchMetadata).isEmpty() +} +func (r *LogicalDNSRule) BypassIfFailed() bool { + + if act, ok := r.action.(*RuleActionDNSRoute); ok && act != nil { + return act.RuleActionDNSRouteOptions.BypassIfFailed + } + return false +} + +func (r *DefaultDNSRule) BypassIfFailed() bool { + if act, ok := r.action.(*RuleActionDNSRoute); ok && act != nil { + return act.RuleActionDNSRouteOptions.BypassIfFailed + } + return false +} diff --git a/route/rule_headless.go b/route/rule/rule_headless.go similarity index 56% rename from route/rule_headless.go rename to route/rule/rule_headless.go index 67ac3a1e44..bef30d46c1 100644 --- a/route/rule_headless.go +++ b/route/rule/rule_headless.go @@ -1,24 +1,28 @@ -package route +package rule import ( + "context" + "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/service" ) -func NewHeadlessRule(router adapter.Router, options option.HeadlessRule) (adapter.HeadlessRule, error) { +func NewHeadlessRule(ctx context.Context, options option.HeadlessRule) (adapter.HeadlessRule, error) { switch options.Type { case "", C.RuleTypeDefault: if !options.DefaultOptions.IsValid() { return nil, E.New("missing conditions") } - return NewDefaultHeadlessRule(router, options.DefaultOptions) + return NewDefaultHeadlessRule(ctx, options.DefaultOptions) case C.RuleTypeLogical: if !options.LogicalOptions.IsValid() { return nil, E.New("missing conditions") } - return NewLogicalHeadlessRule(router, options.LogicalOptions) + return NewLogicalHeadlessRule(ctx, options.LogicalOptions) default: return nil, E.New("unknown rule type: ", options.Type) } @@ -30,19 +34,32 @@ type DefaultHeadlessRule struct { abstractDefaultRule } -func NewDefaultHeadlessRule(router adapter.Router, options option.DefaultHeadlessRule) (*DefaultHeadlessRule, error) { +func (r *DefaultHeadlessRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet { + return r.abstractDefaultRule.matchStates(metadata) +} + +func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessRule) (*DefaultHeadlessRule, error) { + networkManager := service.FromContext[adapter.NetworkManager](ctx) rule := &DefaultHeadlessRule{ abstractDefaultRule{ invert: options.Invert, }, } + if len(options.QueryType) > 0 { + item := NewQueryTypeItem(options.QueryType) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } if len(options.Network) > 0 { item := NewNetworkItem(options.Network) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } if len(options.Domain) > 0 || len(options.DomainSuffix) > 0 { - item := NewDomainItem(options.Domain, options.DomainSuffix) + item, err := NewDomainItem(options.Domain, options.DomainSuffix) + if err != nil { + return nil, err + } rule.destinationAddressItems = append(rule.destinationAddressItems, item) rule.allItems = append(rule.allItems, item) } else if options.DomainMatcher != nil { @@ -113,6 +130,16 @@ func NewDefaultHeadlessRule(router adapter.Router, options option.DefaultHeadles rule.destinationPortItems = append(rule.destinationPortItems, item) rule.allItems = append(rule.allItems, item) } + if len(options.TunnelSource) > 0 { + item := NewTunnelSourceItem(options.TunnelSource) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.TunnelDestination) > 0 { + item := NewTunnelDestinationItem(options.TunnelDestination) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } if len(options.ProcessName) > 0 { item := NewProcessItem(options.ProcessName) rule.items = append(rule.items, item) @@ -123,25 +150,73 @@ func NewDefaultHeadlessRule(router adapter.Router, options option.DefaultHeadles rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } + if len(options.ProcessPathRegex) > 0 { + item, err := NewProcessPathRegexItem(options.ProcessPathRegex) + if err != nil { + return nil, E.Cause(err, "process_path_regex") + } + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } if len(options.PackageName) > 0 { item := NewPackageNameItem(options.PackageName) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } - if len(options.WIFISSID) > 0 { - if router != nil { - item := NewWIFISSIDItem(router, options.WIFISSID) + if len(options.PackageNameRegex) > 0 { + item, err := NewPackageNameRegexItem(options.PackageNameRegex) + if err != nil { + return nil, E.Cause(err, "package_name_regex") + } + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if networkManager != nil { + if len(options.NetworkType) > 0 { + item := NewNetworkTypeItem(networkManager, common.Map(options.NetworkType, option.InterfaceType.Build)) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } - } - if len(options.WIFIBSSID) > 0 { - if router != nil { - item := NewWIFIBSSIDItem(router, options.WIFIBSSID) + if options.NetworkIsExpensive { + item := NewNetworkIsExpensiveItem(networkManager) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkIsConstrained { + item := NewNetworkIsConstrainedItem(networkManager) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.WIFISSID) > 0 { + item := NewWIFISSIDItem(networkManager, options.WIFISSID) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.WIFIBSSID) > 0 { + item := NewWIFIBSSIDItem(networkManager, options.WIFIBSSID) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkInterfaceAddress != nil && options.NetworkInterfaceAddress.Size() > 0 { + item := NewNetworkInterfaceAddressItem(networkManager, options.NetworkInterfaceAddress) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.DefaultInterfaceAddress) > 0 { + item := NewDefaultInterfaceAddressItem(networkManager, options.DefaultInterfaceAddress) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } } + if len(options.AdGuardDomain) > 0 { + item := NewAdGuardDomainItem(options.AdGuardDomain) + rule.destinationAddressItems = append(rule.destinationAddressItems, item) + rule.allItems = append(rule.allItems, item) + } else if options.AdGuardDomainMatcher != nil { + item := NewRawAdGuardDomainItem(options.AdGuardDomainMatcher) + rule.destinationAddressItems = append(rule.destinationAddressItems, item) + rule.allItems = append(rule.allItems, item) + } return rule, nil } @@ -151,7 +226,11 @@ type LogicalHeadlessRule struct { abstractLogicalRule } -func NewLogicalHeadlessRule(router adapter.Router, options option.LogicalHeadlessRule) (*LogicalHeadlessRule, error) { +func (r *LogicalHeadlessRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet { + return r.abstractLogicalRule.matchStates(metadata) +} + +func NewLogicalHeadlessRule(ctx context.Context, options option.LogicalHeadlessRule) (*LogicalHeadlessRule, error) { r := &LogicalHeadlessRule{ abstractLogicalRule{ rules: make([]adapter.HeadlessRule, len(options.Rules)), @@ -167,7 +246,7 @@ func NewLogicalHeadlessRule(router adapter.Router, options option.LogicalHeadles return nil, E.New("unknown logical mode: ", options.Mode) } for i, subRule := range options.Rules { - rule, err := NewHeadlessRule(router, subRule) + rule, err := NewHeadlessRule(ctx, subRule) if err != nil { return nil, E.Cause(err, "sub rule[", i, "]") } diff --git a/route/rule/rule_interface_address.go b/route/rule/rule_interface_address.go new file mode 100644 index 0000000000..d4c75d38d4 --- /dev/null +++ b/route/rule/rule_interface_address.go @@ -0,0 +1,62 @@ +package rule + +import ( + "net/netip" + "strings" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/control" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" +) + +var _ RuleItem = (*InterfaceAddressItem)(nil) + +type InterfaceAddressItem struct { + networkManager adapter.NetworkManager + interfaceAddresses map[string][]netip.Prefix + description string +} + +func NewInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses *badjson.TypedMap[string, badoption.Listable[*badoption.Prefixable]]) *InterfaceAddressItem { + item := &InterfaceAddressItem{ + networkManager: networkManager, + interfaceAddresses: make(map[string][]netip.Prefix, interfaceAddresses.Size()), + } + var entryDescriptions []string + for _, entry := range interfaceAddresses.Entries() { + prefixes := make([]netip.Prefix, 0, len(entry.Value)) + for _, prefixable := range entry.Value { + prefixes = append(prefixes, prefixable.Build(netip.Prefix{})) + } + item.interfaceAddresses[entry.Key] = prefixes + entryDescriptions = append(entryDescriptions, entry.Key+"="+strings.Join(common.Map(prefixes, netip.Prefix.String), ",")) + } + item.description = "interface_address=[" + strings.Join(entryDescriptions, " ") + "]" + return item +} + +func (r *InterfaceAddressItem) Match(metadata *adapter.InboundContext) bool { + interfaces := r.networkManager.InterfaceFinder().Interfaces() + for ifName, addresses := range r.interfaceAddresses { + iface := common.Find(interfaces, func(it control.Interface) bool { + return it.Name == ifName + }) + if iface.Name == "" { + return false + } + if common.All(addresses, func(address netip.Prefix) bool { + return common.All(iface.Addresses, func(it netip.Prefix) bool { + return !address.Overlaps(it) + }) + }) { + return false + } + } + return true +} + +func (r *InterfaceAddressItem) String() string { + return r.description +} diff --git a/route/rule/rule_item_adguard.go b/route/rule/rule_item_adguard.go new file mode 100644 index 0000000000..84252e606d --- /dev/null +++ b/route/rule/rule_item_adguard.go @@ -0,0 +1,43 @@ +package rule + +import ( + "strings" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common/domain" +) + +var _ RuleItem = (*AdGuardDomainItem)(nil) + +type AdGuardDomainItem struct { + matcher *domain.AdGuardMatcher +} + +func NewAdGuardDomainItem(ruleLines []string) *AdGuardDomainItem { + return &AdGuardDomainItem{ + domain.NewAdGuardMatcher(ruleLines), + } +} + +func NewRawAdGuardDomainItem(matcher *domain.AdGuardMatcher) *AdGuardDomainItem { + return &AdGuardDomainItem{ + matcher, + } +} + +func (r *AdGuardDomainItem) Match(metadata *adapter.InboundContext) bool { + var domainHost string + if metadata.Domain != "" { + domainHost = metadata.Domain + } else { + domainHost = metadata.Destination.Fqdn + } + if domainHost == "" { + return false + } + return r.matcher.Match(strings.ToLower(domainHost)) +} + +func (r *AdGuardDomainItem) String() string { + return "!adguard_domain_rules=" +} diff --git a/route/rule_item_auth_user.go b/route/rule/rule_item_auth_user.go similarity index 98% rename from route/rule_item_auth_user.go rename to route/rule/rule_item_auth_user.go index fbe053e6be..5799e3c718 100644 --- a/route/rule_item_auth_user.go +++ b/route/rule/rule_item_auth_user.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_cidr.go b/route/rule/rule_item_cidr.go similarity index 70% rename from route/rule_item_cidr.go rename to route/rule/rule_item_cidr.go index 85b9c8d7d3..28f74161f1 100644 --- a/route/rule_item_cidr.go +++ b/route/rule/rule_item_cidr.go @@ -1,4 +1,4 @@ -package route +package rule import ( "net/netip" @@ -75,18 +75,34 @@ func NewRawIPCIDRItem(isSource bool, ipSet *netipx.IPSet) *IPCIDRItem { func (r *IPCIDRItem) Match(metadata *adapter.InboundContext) bool { if r.isSource || metadata.IPCIDRMatchSource { return r.ipSet.Contains(metadata.Source.Addr) - } else { - if metadata.Destination.IsIP() { - return r.ipSet.Contains(metadata.Destination.Addr) - } else { - for _, address := range metadata.DestinationAddresses { - if r.ipSet.Contains(address) { - return true - } + } + if metadata.DestinationAddressMatchFromResponse { + addresses := metadata.DNSResponseAddressesForMatch() + if len(addresses) == 0 { + // Legacy rule_set_ip_cidr_accept_empty only applies when the DNS response + // does not expose any address answers for matching. + return metadata.IPCIDRAcceptEmpty + } + for _, address := range addresses { + if r.ipSet.Contains(address) { + return true + } + } + return false + } + if metadata.Destination.IsIP() { + return r.ipSet.Contains(metadata.Destination.Addr) + } + addresses := metadata.DestinationAddresses + if len(addresses) > 0 { + for _, address := range addresses { + if r.ipSet.Contains(address) { + return true } } + return false } - return false + return metadata.IPCIDRAcceptEmpty } func (r *IPCIDRItem) String() string { diff --git a/route/rule/rule_item_clash_mode.go b/route/rule/rule_item_clash_mode.go new file mode 100644 index 0000000000..fe2347a06f --- /dev/null +++ b/route/rule/rule_item_clash_mode.go @@ -0,0 +1,40 @@ +package rule + +import ( + "context" + "strings" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/service" +) + +var _ RuleItem = (*ClashModeItem)(nil) + +type ClashModeItem struct { + ctx context.Context + clashServer adapter.ClashServer + mode string +} + +func NewClashModeItem(ctx context.Context, mode string) *ClashModeItem { + return &ClashModeItem{ + ctx: ctx, + mode: mode, + } +} + +func (r *ClashModeItem) Start() error { + r.clashServer = service.FromContext[adapter.ClashServer](r.ctx) + return nil +} + +func (r *ClashModeItem) Match(metadata *adapter.InboundContext) bool { + if r.clashServer == nil { + return false + } + return strings.EqualFold(r.clashServer.Mode(), r.mode) +} + +func (r *ClashModeItem) String() string { + return "clash_mode=" + r.mode +} diff --git a/route/rule/rule_item_client.go b/route/rule/rule_item_client.go new file mode 100644 index 0000000000..63ff410355 --- /dev/null +++ b/route/rule/rule_item_client.go @@ -0,0 +1,37 @@ +package rule + +import ( + "strings" + + "github.com/sagernet/sing-box/adapter" + F "github.com/sagernet/sing/common/format" +) + +var _ RuleItem = (*ClientItem)(nil) + +type ClientItem struct { + clients []string + clientMap map[string]bool +} + +func NewClientItem(clients []string) *ClientItem { + clientMap := make(map[string]bool) + for _, client := range clients { + clientMap[client] = true + } + return &ClientItem{ + clients: clients, + clientMap: clientMap, + } +} + +func (r *ClientItem) Match(metadata *adapter.InboundContext) bool { + return r.clientMap[metadata.Client] +} + +func (r *ClientItem) String() string { + if len(r.clients) == 1 { + return F.ToString("client=", r.clients[0]) + } + return F.ToString("client=[", strings.Join(r.clients, " "), "]") +} diff --git a/route/rule_item_domain.go b/route/rule/rule_item_domain.go similarity index 74% rename from route/rule_item_domain.go rename to route/rule/rule_item_domain.go index 36839a5556..af790aa385 100644 --- a/route/rule_item_domain.go +++ b/route/rule/rule_item_domain.go @@ -1,10 +1,11 @@ -package route +package rule import ( "strings" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing/common/domain" + E "github.com/sagernet/sing/common/exceptions" ) var _ RuleItem = (*DomainItem)(nil) @@ -14,7 +15,17 @@ type DomainItem struct { description string } -func NewDomainItem(domains []string, domainSuffixes []string) *DomainItem { +func NewDomainItem(domains []string, domainSuffixes []string) (*DomainItem, error) { + for _, domainItem := range domains { + if domainItem == "" { + return nil, E.New("domain: empty item is not allowed") + } + } + for _, domainSuffixItem := range domainSuffixes { + if domainSuffixItem == "" { + return nil, E.New("domain_suffix: empty item is not allowed") + } + } var description string if dLen := len(domains); dLen > 0 { if dLen == 1 { @@ -38,9 +49,9 @@ func NewDomainItem(domains []string, domainSuffixes []string) *DomainItem { } } return &DomainItem{ - domain.NewMatcher(domains, domainSuffixes), + domain.NewMatcher(domains, domainSuffixes, false), description, - } + }, nil } func NewRawDomainItem(matcher *domain.Matcher) *DomainItem { diff --git a/route/rule_item_domain_keyword.go b/route/rule/rule_item_domain_keyword.go similarity index 98% rename from route/rule_item_domain_keyword.go rename to route/rule/rule_item_domain_keyword.go index c6ca1e8c28..6e19a10ccd 100644 --- a/route/rule_item_domain_keyword.go +++ b/route/rule/rule_item_domain_keyword.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_domain_regex.go b/route/rule/rule_item_domain_regex.go similarity index 99% rename from route/rule_item_domain_regex.go rename to route/rule/rule_item_domain_regex.go index b3555168a2..b9752a45ad 100644 --- a/route/rule_item_domain_regex.go +++ b/route/rule/rule_item_domain_regex.go @@ -1,4 +1,4 @@ -package route +package rule import ( "regexp" diff --git a/route/rule_item_inbound.go b/route/rule/rule_item_inbound.go similarity index 98% rename from route/rule_item_inbound.go rename to route/rule/rule_item_inbound.go index 7e28781f00..87e84740c9 100644 --- a/route/rule_item_inbound.go +++ b/route/rule/rule_item_inbound.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule/rule_item_ip_accept_any.go b/route/rule/rule_item_ip_accept_any.go new file mode 100644 index 0000000000..fceebc1860 --- /dev/null +++ b/route/rule/rule_item_ip_accept_any.go @@ -0,0 +1,24 @@ +package rule + +import ( + "github.com/sagernet/sing-box/adapter" +) + +var _ RuleItem = (*IPAcceptAnyItem)(nil) + +type IPAcceptAnyItem struct{} + +func NewIPAcceptAnyItem() *IPAcceptAnyItem { + return &IPAcceptAnyItem{} +} + +func (r *IPAcceptAnyItem) Match(metadata *adapter.InboundContext) bool { + if metadata.DestinationAddressMatchFromResponse { + return len(metadata.DNSResponseAddressesForMatch()) > 0 + } + return len(metadata.DestinationAddresses) > 0 +} + +func (r *IPAcceptAnyItem) String() string { + return "ip_accept_any=true" +} diff --git a/route/rule_item_ip_is_private.go b/route/rule/rule_item_ip_is_private.go similarity index 58% rename from route/rule_item_ip_is_private.go rename to route/rule/rule_item_ip_is_private.go index 6592a9d3fe..c968877395 100644 --- a/route/rule_item_ip_is_private.go +++ b/route/rule/rule_item_ip_is_private.go @@ -1,8 +1,6 @@ -package route +package rule import ( - "net/netip" - "github.com/sagernet/sing-box/adapter" N "github.com/sagernet/sing/common/network" ) @@ -18,21 +16,24 @@ func NewIPIsPrivateItem(isSource bool) *IPIsPrivateItem { } func (r *IPIsPrivateItem) Match(metadata *adapter.InboundContext) bool { - var destination netip.Addr if r.isSource { - destination = metadata.Source.Addr - } else { - destination = metadata.Destination.Addr - } - if destination.IsValid() { - return !N.IsPublicAddr(destination) + return !N.IsPublicAddr(metadata.Source.Addr) } - if !r.isSource { - for _, destinationAddress := range metadata.DestinationAddresses { + if metadata.DestinationAddressMatchFromResponse { + for _, destinationAddress := range metadata.DNSResponseAddressesForMatch() { if !N.IsPublicAddr(destinationAddress) { return true } } + return false + } + if metadata.Destination.Addr.IsValid() { + return !N.IsPublicAddr(metadata.Destination.Addr) + } + for _, destinationAddress := range metadata.DestinationAddresses { + if !N.IsPublicAddr(destinationAddress) { + return true + } } return false } diff --git a/route/rule_item_ipversion.go b/route/rule/rule_item_ipversion.go similarity index 97% rename from route/rule_item_ipversion.go rename to route/rule/rule_item_ipversion.go index 3d8762b417..8ab6494270 100644 --- a/route/rule_item_ipversion.go +++ b/route/rule/rule_item_ipversion.go @@ -1,4 +1,4 @@ -package route +package rule import ( "github.com/sagernet/sing-box/adapter" diff --git a/route/rule_item_network.go b/route/rule/rule_item_network.go similarity index 98% rename from route/rule_item_network.go rename to route/rule/rule_item_network.go index fc54f425da..bfb334d310 100644 --- a/route/rule_item_network.go +++ b/route/rule/rule_item_network.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule/rule_item_network_is_constrained.go b/route/rule/rule_item_network_is_constrained.go new file mode 100644 index 0000000000..e0368b753f --- /dev/null +++ b/route/rule/rule_item_network_is_constrained.go @@ -0,0 +1,29 @@ +package rule + +import ( + "github.com/sagernet/sing-box/adapter" +) + +var _ RuleItem = (*NetworkIsConstrainedItem)(nil) + +type NetworkIsConstrainedItem struct { + networkManager adapter.NetworkManager +} + +func NewNetworkIsConstrainedItem(networkManager adapter.NetworkManager) *NetworkIsConstrainedItem { + return &NetworkIsConstrainedItem{ + networkManager: networkManager, + } +} + +func (r *NetworkIsConstrainedItem) Match(metadata *adapter.InboundContext) bool { + networkInterface := r.networkManager.DefaultNetworkInterface() + if networkInterface == nil { + return false + } + return networkInterface.Constrained +} + +func (r *NetworkIsConstrainedItem) String() string { + return "network_is_expensive=true" +} diff --git a/route/rule/rule_item_network_is_expensive.go b/route/rule/rule_item_network_is_expensive.go new file mode 100644 index 0000000000..83e4f96f88 --- /dev/null +++ b/route/rule/rule_item_network_is_expensive.go @@ -0,0 +1,29 @@ +package rule + +import ( + "github.com/sagernet/sing-box/adapter" +) + +var _ RuleItem = (*NetworkIsExpensiveItem)(nil) + +type NetworkIsExpensiveItem struct { + networkManager adapter.NetworkManager +} + +func NewNetworkIsExpensiveItem(networkManager adapter.NetworkManager) *NetworkIsExpensiveItem { + return &NetworkIsExpensiveItem{ + networkManager: networkManager, + } +} + +func (r *NetworkIsExpensiveItem) Match(metadata *adapter.InboundContext) bool { + networkInterface := r.networkManager.DefaultNetworkInterface() + if networkInterface == nil { + return false + } + return networkInterface.Expensive +} + +func (r *NetworkIsExpensiveItem) String() string { + return "network_is_expensive=true" +} diff --git a/route/rule/rule_item_network_type.go b/route/rule/rule_item_network_type.go new file mode 100644 index 0000000000..31856e70df --- /dev/null +++ b/route/rule/rule_item_network_type.go @@ -0,0 +1,40 @@ +package rule + +import ( + "strings" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common" + F "github.com/sagernet/sing/common/format" +) + +var _ RuleItem = (*NetworkTypeItem)(nil) + +type NetworkTypeItem struct { + networkManager adapter.NetworkManager + networkType []C.InterfaceType +} + +func NewNetworkTypeItem(networkManager adapter.NetworkManager, networkType []C.InterfaceType) *NetworkTypeItem { + return &NetworkTypeItem{ + networkManager: networkManager, + networkType: networkType, + } +} + +func (r *NetworkTypeItem) Match(metadata *adapter.InboundContext) bool { + networkInterface := r.networkManager.DefaultNetworkInterface() + if networkInterface == nil { + return false + } + return common.Contains(r.networkType, networkInterface.Type) +} + +func (r *NetworkTypeItem) String() string { + if len(r.networkType) == 1 { + return F.ToString("network_type=", r.networkType[0]) + } else { + return F.ToString("network_type=", "["+strings.Join(F.MapToString(r.networkType), " ")+"]") + } +} diff --git a/route/rule_item_outbound.go b/route/rule/rule_item_outbound.go similarity index 74% rename from route/rule_item_outbound.go rename to route/rule/rule_item_outbound.go index 4b3e16fc6b..a13d059700 100644 --- a/route/rule_item_outbound.go +++ b/route/rule/rule_item_outbound.go @@ -1,9 +1,11 @@ -package route +package rule import ( + "context" "strings" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/experimental/deprecated" F "github.com/sagernet/sing/common/format" ) @@ -15,7 +17,8 @@ type OutboundItem struct { matchAny bool } -func NewOutboundRule(outbounds []string) *OutboundItem { +func NewOutboundRule(ctx context.Context, outbounds []string) *OutboundItem { + deprecated.Report(ctx, deprecated.OptionOutboundDNSRuleItem) rule := &OutboundItem{outbounds: outbounds, outboundMap: make(map[string]bool)} for _, outbound := range outbounds { if outbound == "any" { @@ -28,8 +31,8 @@ func NewOutboundRule(outbounds []string) *OutboundItem { } func (r *OutboundItem) Match(metadata *adapter.InboundContext) bool { - if r.matchAny && metadata.Outbound != "" { - return true + if r.matchAny { + return metadata.Outbound != "" } return r.outboundMap[metadata.Outbound] } diff --git a/route/rule_item_package_name.go b/route/rule/rule_item_package_name.go similarity index 77% rename from route/rule_item_package_name.go rename to route/rule/rule_item_package_name.go index d1ca09eb2d..514768de74 100644 --- a/route/rule_item_package_name.go +++ b/route/rule/rule_item_package_name.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" @@ -25,10 +25,15 @@ func NewPackageNameItem(packageNameList []string) *PackageNameItem { } func (r *PackageNameItem) Match(metadata *adapter.InboundContext) bool { - if metadata.ProcessInfo == nil || metadata.ProcessInfo.PackageName == "" { + if metadata.ProcessInfo == nil || len(metadata.ProcessInfo.AndroidPackageNames) == 0 { return false } - return r.packageMap[metadata.ProcessInfo.PackageName] + for _, packageName := range metadata.ProcessInfo.AndroidPackageNames { + if r.packageMap[packageName] { + return true + } + } + return false } func (r *PackageNameItem) String() string { diff --git a/route/rule/rule_item_package_name_regex.go b/route/rule/rule_item_package_name_regex.go new file mode 100644 index 0000000000..9db4504acf --- /dev/null +++ b/route/rule/rule_item_package_name_regex.go @@ -0,0 +1,56 @@ +package rule + +import ( + "regexp" + "strings" + + "github.com/sagernet/sing-box/adapter" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" +) + +var _ RuleItem = (*PackageNameRegexItem)(nil) + +type PackageNameRegexItem struct { + matchers []*regexp.Regexp + description string +} + +func NewPackageNameRegexItem(expressions []string) (*PackageNameRegexItem, error) { + matchers := make([]*regexp.Regexp, 0, len(expressions)) + for i, regex := range expressions { + matcher, err := regexp.Compile(regex) + if err != nil { + return nil, E.Cause(err, "parse expression ", i) + } + matchers = append(matchers, matcher) + } + description := "package_name_regex=" + eLen := len(expressions) + if eLen == 1 { + description += expressions[0] + } else if eLen > 3 { + description += F.ToString("[", strings.Join(expressions[:3], " "), "]") + } else { + description += F.ToString("[", strings.Join(expressions, " "), "]") + } + return &PackageNameRegexItem{matchers, description}, nil +} + +func (r *PackageNameRegexItem) Match(metadata *adapter.InboundContext) bool { + if metadata.ProcessInfo == nil || len(metadata.ProcessInfo.AndroidPackageNames) == 0 { + return false + } + for _, matcher := range r.matchers { + for _, packageName := range metadata.ProcessInfo.AndroidPackageNames { + if matcher.MatchString(packageName) { + return true + } + } + } + return false +} + +func (r *PackageNameRegexItem) String() string { + return r.description +} diff --git a/route/rule_item_port.go b/route/rule/rule_item_port.go similarity index 98% rename from route/rule_item_port.go rename to route/rule/rule_item_port.go index 6247893364..af166ee640 100644 --- a/route/rule_item_port.go +++ b/route/rule/rule_item_port.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_port_range.go b/route/rule/rule_item_port_range.go similarity index 99% rename from route/rule_item_port_range.go rename to route/rule/rule_item_port_range.go index f87575f2ce..980f7d2391 100644 --- a/route/rule_item_port_range.go +++ b/route/rule/rule_item_port_range.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strconv" diff --git a/route/rule/rule_item_preferred_by.go b/route/rule/rule_item_preferred_by.go new file mode 100644 index 0000000000..42c8a62786 --- /dev/null +++ b/route/rule/rule_item_preferred_by.go @@ -0,0 +1,86 @@ +package rule + +import ( + "context" + "strings" + + "github.com/sagernet/sing-box/adapter" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/service" +) + +var _ RuleItem = (*PreferredByItem)(nil) + +type PreferredByItem struct { + ctx context.Context + outboundTags []string + outbounds []adapter.OutboundWithPreferredRoutes +} + +func NewPreferredByItem(ctx context.Context, outboundTags []string) *PreferredByItem { + return &PreferredByItem{ + ctx: ctx, + outboundTags: outboundTags, + } +} + +func (r *PreferredByItem) Start() error { + outboundManager := service.FromContext[adapter.OutboundManager](r.ctx) + for _, outboundTag := range r.outboundTags { + rawOutbound, loaded := outboundManager.Outbound(outboundTag) + if !loaded { + return E.New("outbound not found: ", outboundTag) + } + outboundWithPreferredRoutes, withRoutes := rawOutbound.(adapter.OutboundWithPreferredRoutes) + if !withRoutes { + return E.New("outbound type does not support preferred routes: ", rawOutbound.Type()) + } + r.outbounds = append(r.outbounds, outboundWithPreferredRoutes) + } + return nil +} + +func (r *PreferredByItem) Match(metadata *adapter.InboundContext) bool { + var domainHost string + if metadata.Domain != "" { + domainHost = metadata.Domain + } else { + domainHost = metadata.Destination.Fqdn + } + if domainHost != "" { + for _, outbound := range r.outbounds { + if outbound.PreferredDomain(domainHost) { + return true + } + } + } + if metadata.Destination.IsIP() { + for _, outbound := range r.outbounds { + if outbound.PreferredAddress(metadata.Destination.Addr) { + return true + } + } + } + if len(metadata.DestinationAddresses) > 0 { + for _, address := range metadata.DestinationAddresses { + for _, outbound := range r.outbounds { + if outbound.PreferredAddress(address) { + return true + } + } + } + } + return false +} + +func (r *PreferredByItem) String() string { + description := "preferred_by=" + pLen := len(r.outboundTags) + if pLen == 1 { + description += F.ToString(r.outboundTags[0]) + } else { + description += "[" + strings.Join(F.MapToString(r.outboundTags), " ") + "]" + } + return description +} diff --git a/route/rule/rule_item_preferred_by_dns.go b/route/rule/rule_item_preferred_by_dns.go new file mode 100644 index 0000000000..d00d780c62 --- /dev/null +++ b/route/rule/rule_item_preferred_by_dns.go @@ -0,0 +1,74 @@ +package rule + +import ( + "context" + "strings" + + "github.com/sagernet/sing-box/adapter" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/service" + + mDNS "github.com/miekg/dns" +) + +var _ RuleItem = (*PreferredByDNSItem)(nil) + +type PreferredByDNSItem struct { + ctx context.Context + transportTags []string + transports []adapter.DNSTransportWithPreferredDomain +} + +func NewPreferredByDNSItem(ctx context.Context, transportTags []string) *PreferredByDNSItem { + return &PreferredByDNSItem{ + ctx: ctx, + transportTags: transportTags, + } +} + +func (r *PreferredByDNSItem) Start() error { + transportManager := service.FromContext[adapter.DNSTransportManager](r.ctx) + for _, transportTag := range r.transportTags { + rawTransport, loaded := transportManager.Transport(transportTag) + if !loaded { + return E.New("DNS server not found: ", transportTag) + } + transportWithPreferredDomain, withPreferredDomain := rawTransport.(adapter.DNSTransportWithPreferredDomain) + if !withPreferredDomain { + return E.New("DNS server type does not support preferred_by: ", rawTransport.Type()) + } + r.transports = append(r.transports, transportWithPreferredDomain) + } + return nil +} + +func (r *PreferredByDNSItem) Match(metadata *adapter.InboundContext) bool { + var domainHost string + if metadata.Domain != "" { + domainHost = metadata.Domain + } else { + domainHost = metadata.Destination.Fqdn + } + if domainHost == "" { + return false + } + canonical := mDNS.CanonicalName(domainHost) + for _, transport := range r.transports { + if transport.PreferredDomain(canonical) { + return true + } + } + return false +} + +func (r *PreferredByDNSItem) String() string { + description := "preferred_by=" + pLen := len(r.transportTags) + if pLen == 1 { + description += F.ToString(r.transportTags[0]) + } else { + description += "[" + strings.Join(F.MapToString(r.transportTags), " ") + "]" + } + return description +} diff --git a/route/rule_item_process_name.go b/route/rule/rule_item_process_name.go similarity index 98% rename from route/rule_item_process_name.go rename to route/rule/rule_item_process_name.go index ce051666d0..fa0f71651d 100644 --- a/route/rule_item_process_name.go +++ b/route/rule/rule_item_process_name.go @@ -1,4 +1,4 @@ -package route +package rule import ( "path/filepath" diff --git a/route/rule_item_process_path.go b/route/rule/rule_item_process_path.go similarity index 68% rename from route/rule_item_process_path.go rename to route/rule/rule_item_process_path.go index feae4b2756..ac5c6a180e 100644 --- a/route/rule_item_process_path.go +++ b/route/rule/rule_item_process_path.go @@ -1,9 +1,10 @@ -package route +package rule import ( "strings" "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" ) var _ RuleItem = (*ProcessPathItem)(nil) @@ -25,10 +26,20 @@ func NewProcessPathItem(processNameList []string) *ProcessPathItem { } func (r *ProcessPathItem) Match(metadata *adapter.InboundContext) bool { - if metadata.ProcessInfo == nil || metadata.ProcessInfo.ProcessPath == "" { + if metadata.ProcessInfo == nil { return false } - return r.processMap[metadata.ProcessInfo.ProcessPath] + if metadata.ProcessInfo.ProcessPath != "" && r.processMap[metadata.ProcessInfo.ProcessPath] { + return true + } + if C.IsAndroid { + for _, packageName := range metadata.ProcessInfo.AndroidPackageNames { + if r.processMap[packageName] { + return true + } + } + } + return false } func (r *ProcessPathItem) String() string { diff --git a/route/rule/rule_item_process_path_regex.go b/route/rule/rule_item_process_path_regex.go new file mode 100644 index 0000000000..76cf67b9f5 --- /dev/null +++ b/route/rule/rule_item_process_path_regex.go @@ -0,0 +1,54 @@ +package rule + +import ( + "regexp" + "strings" + + "github.com/sagernet/sing-box/adapter" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" +) + +var _ RuleItem = (*ProcessPathRegexItem)(nil) + +type ProcessPathRegexItem struct { + matchers []*regexp.Regexp + description string +} + +func NewProcessPathRegexItem(expressions []string) (*ProcessPathRegexItem, error) { + matchers := make([]*regexp.Regexp, 0, len(expressions)) + for i, regex := range expressions { + matcher, err := regexp.Compile(regex) + if err != nil { + return nil, E.Cause(err, "parse expression ", i) + } + matchers = append(matchers, matcher) + } + description := "process_path_regex=" + eLen := len(expressions) + if eLen == 1 { + description += expressions[0] + } else if eLen > 3 { + description += F.ToString("[", strings.Join(expressions[:3], " "), "]") + } else { + description += F.ToString("[", strings.Join(expressions, " "), "]") + } + return &ProcessPathRegexItem{matchers, description}, nil +} + +func (r *ProcessPathRegexItem) Match(metadata *adapter.InboundContext) bool { + if metadata.ProcessInfo == nil || metadata.ProcessInfo.ProcessPath == "" { + return false + } + for _, matcher := range r.matchers { + if matcher.MatchString(metadata.ProcessInfo.ProcessPath) { + return true + } + } + return false +} + +func (r *ProcessPathRegexItem) String() string { + return r.description +} diff --git a/route/rule_item_protocol.go b/route/rule/rule_item_protocol.go similarity index 98% rename from route/rule_item_protocol.go rename to route/rule/rule_item_protocol.go index 1988f8ade1..319b81d50d 100644 --- a/route/rule_item_protocol.go +++ b/route/rule/rule_item_protocol.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_query_type.go b/route/rule/rule_item_query_type.go similarity index 98% rename from route/rule_item_query_type.go rename to route/rule/rule_item_query_type.go index 7b6efdd044..36b615f3af 100644 --- a/route/rule_item_query_type.go +++ b/route/rule/rule_item_query_type.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule/rule_item_response_rcode.go b/route/rule/rule_item_response_rcode.go new file mode 100644 index 0000000000..cac75e8034 --- /dev/null +++ b/route/rule/rule_item_response_rcode.go @@ -0,0 +1,26 @@ +package rule + +import ( + "github.com/sagernet/sing-box/adapter" + F "github.com/sagernet/sing/common/format" + + "github.com/miekg/dns" +) + +var _ RuleItem = (*DNSResponseRCodeItem)(nil) + +type DNSResponseRCodeItem struct { + rcode int +} + +func NewDNSResponseRCodeItem(rcode int) *DNSResponseRCodeItem { + return &DNSResponseRCodeItem{rcode: rcode} +} + +func (r *DNSResponseRCodeItem) Match(metadata *adapter.InboundContext) bool { + return metadata.DNSResponse != nil && metadata.DNSResponse.Rcode == r.rcode +} + +func (r *DNSResponseRCodeItem) String() string { + return F.ToString("response_rcode=", dns.RcodeToString[r.rcode]) +} diff --git a/route/rule/rule_item_response_record.go b/route/rule/rule_item_response_record.go new file mode 100644 index 0000000000..3a2c889beb --- /dev/null +++ b/route/rule/rule_item_response_record.go @@ -0,0 +1,63 @@ +package rule + +import ( + "strings" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/option" + + "github.com/miekg/dns" +) + +var _ RuleItem = (*DNSResponseRecordItem)(nil) + +type DNSResponseRecordItem struct { + field string + records []option.DNSRecordOptions + selector func(*dns.Msg) []dns.RR +} + +func NewDNSResponseRecordItem(field string, records []option.DNSRecordOptions, selector func(*dns.Msg) []dns.RR) *DNSResponseRecordItem { + return &DNSResponseRecordItem{ + field: field, + records: records, + selector: selector, + } +} + +func (r *DNSResponseRecordItem) Match(metadata *adapter.InboundContext) bool { + if metadata.DNSResponse == nil { + return false + } + records := r.selector(metadata.DNSResponse) + for _, expected := range r.records { + for _, record := range records { + if expected.Match(record) { + return true + } + } + } + return false +} + +func (r *DNSResponseRecordItem) String() string { + descriptions := make([]string, 0, len(r.records)) + for _, record := range r.records { + if record.RR != nil { + descriptions = append(descriptions, record.RR.String()) + } + } + return r.field + "=[" + strings.Join(descriptions, " ") + "]" +} + +func dnsResponseAnswers(message *dns.Msg) []dns.RR { + return message.Answer +} + +func dnsResponseNS(message *dns.Msg) []dns.RR { + return message.Ns +} + +func dnsResponseExtra(message *dns.Msg) []dns.RR { + return message.Extra +} diff --git a/route/rule_item_rule_set.go b/route/rule/rule_item_rule_set.go similarity index 54% rename from route/rule_item_rule_set.go rename to route/rule/rule_item_rule_set.go index 482a9c7b45..0136494353 100644 --- a/route/rule_item_rule_set.go +++ b/route/rule/rule_item_rule_set.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" @@ -15,40 +15,64 @@ type RuleSetItem struct { router adapter.Router tagList []string setList []adapter.RuleSet - ipcidrMatchSource bool + ipCidrMatchSource bool + ipCidrAcceptEmpty bool } -func NewRuleSetItem(router adapter.Router, tagList []string, ipCIDRMatchSource bool) *RuleSetItem { +func NewRuleSetItem(router adapter.Router, tagList []string, ipCIDRMatchSource bool, ipCidrAcceptEmpty bool) *RuleSetItem { return &RuleSetItem{ router: router, tagList: tagList, - ipcidrMatchSource: ipCIDRMatchSource, + ipCidrMatchSource: ipCIDRMatchSource, + ipCidrAcceptEmpty: ipCidrAcceptEmpty, } } func (r *RuleSetItem) Start() error { + _ = r.Close() for _, tag := range r.tagList { ruleSet, loaded := r.router.RuleSet(tag) if !loaded { + _ = r.Close() return E.New("rule-set not found: ", tag) } + ruleSet.IncRef() r.setList = append(r.setList, ruleSet) } return nil } +func (r *RuleSetItem) Close() error { + for _, ruleSet := range r.setList { + ruleSet.DecRef() + } + clear(r.setList) + r.setList = nil + return nil +} + func (r *RuleSetItem) Match(metadata *adapter.InboundContext) bool { - metadata.IPCIDRMatchSource = r.ipcidrMatchSource + return !r.matchStates(metadata).isEmpty() +} + +func (r *RuleSetItem) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet { + return r.matchStatesWithBase(metadata, 0) +} + +func (r *RuleSetItem) matchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet { + var stateSet ruleMatchStateSet for _, ruleSet := range r.setList { - if ruleSet.Match(metadata) { - return true - } + nestedMetadata := *metadata + nestedMetadata.ResetRuleMatchCache() + nestedMetadata.IPCIDRMatchSource = r.ipCidrMatchSource + nestedMetadata.IPCIDRAcceptEmpty = r.ipCidrAcceptEmpty + stateSet = stateSet.merge(matchHeadlessRuleStatesWithBase(ruleSet, &nestedMetadata, base)) } - return false + return stateSet } func (r *RuleSetItem) ContainsDestinationIPCIDRRule() bool { - if r.ipcidrMatchSource { + if r.ipCidrMatchSource { return false } return common.Any(r.setList, func(ruleSet adapter.RuleSet) bool { diff --git a/route/rule/rule_item_rule_set_test.go b/route/rule/rule_item_rule_set_test.go new file mode 100644 index 0000000000..21d2070d9b --- /dev/null +++ b/route/rule/rule_item_rule_set_test.go @@ -0,0 +1,138 @@ +package rule + +import ( + "context" + "net" + "sync/atomic" + "testing" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-tun" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/x/list" + + "github.com/stretchr/testify/require" + "go4.org/netipx" +) + +type ruleSetItemTestRouter struct { + ruleSets map[string]adapter.RuleSet +} + +func (r *ruleSetItemTestRouter) Start(adapter.StartStage) error { return nil } +func (r *ruleSetItemTestRouter) Close() error { return nil } +func (r *ruleSetItemTestRouter) PreMatch(adapter.InboundContext, tun.DirectRouteContext, time.Duration, bool) (tun.DirectRouteDestination, error) { + return nil, nil +} + +func (r *ruleSetItemTestRouter) RouteConnection(context.Context, net.Conn, adapter.InboundContext) error { + return nil +} + +func (r *ruleSetItemTestRouter) RoutePacketConnection(context.Context, N.PacketConn, adapter.InboundContext) error { + return nil +} + +func (r *ruleSetItemTestRouter) RouteConnectionEx(context.Context, net.Conn, adapter.InboundContext, N.CloseHandlerFunc) { +} + +func (r *ruleSetItemTestRouter) RoutePacketConnectionEx(context.Context, N.PacketConn, adapter.InboundContext, N.CloseHandlerFunc) { +} + +func (r *ruleSetItemTestRouter) RuleSet(tag string) (adapter.RuleSet, bool) { + ruleSet, loaded := r.ruleSets[tag] + return ruleSet, loaded +} +func (r *ruleSetItemTestRouter) Rules() []adapter.Rule { return nil } +func (r *ruleSetItemTestRouter) NeedFindProcess() bool { return false } +func (r *ruleSetItemTestRouter) NeedFindNeighbor() bool { return false } +func (r *ruleSetItemTestRouter) NeighborResolver() adapter.NeighborResolver { return nil } +func (r *ruleSetItemTestRouter) AppendTracker(adapter.ConnectionTracker) {} +func (r *ruleSetItemTestRouter) ResetNetwork() {} + +type countingRuleSet struct { + name string + refs atomic.Int32 +} + +func (s *countingRuleSet) Name() string { return s.name } +func (s *countingRuleSet) StartContext(context.Context, *adapter.HTTPStartContext) error { return nil } +func (s *countingRuleSet) PostStart() error { return nil } +func (s *countingRuleSet) Metadata() adapter.RuleSetMetadata { return adapter.RuleSetMetadata{} } +func (s *countingRuleSet) ExtractIPSet() []*netipx.IPSet { return nil } +func (s *countingRuleSet) IncRef() { s.refs.Add(1) } +func (s *countingRuleSet) DecRef() { + if s.refs.Add(-1) < 0 { + panic("rule-set: negative refs") + } +} +func (s *countingRuleSet) Cleanup() {} +func (s *countingRuleSet) RegisterCallback(adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] { + return nil +} +func (s *countingRuleSet) UnregisterCallback(*list.Element[adapter.RuleSetUpdateCallback]) {} +func (s *countingRuleSet) Close() error { return nil } +func (s *countingRuleSet) Match(*adapter.InboundContext) bool { return true } +func (s *countingRuleSet) String() string { return s.name } +func (s *countingRuleSet) RefCount() int32 { return s.refs.Load() } + +func TestRuleSetItemCloseReleasesRefs(t *testing.T) { + t.Parallel() + + firstSet := &countingRuleSet{name: "first"} + secondSet := &countingRuleSet{name: "second"} + item := NewRuleSetItem(&ruleSetItemTestRouter{ + ruleSets: map[string]adapter.RuleSet{ + "first": firstSet, + "second": secondSet, + }, + }, []string{"first", "second"}, false, false) + + require.NoError(t, item.Start()) + require.EqualValues(t, 1, firstSet.RefCount()) + require.EqualValues(t, 1, secondSet.RefCount()) + + require.NoError(t, item.Close()) + require.Zero(t, firstSet.RefCount()) + require.Zero(t, secondSet.RefCount()) + + require.NoError(t, item.Close()) + require.Zero(t, firstSet.RefCount()) + require.Zero(t, secondSet.RefCount()) +} + +func TestRuleSetItemStartRollbackOnFailure(t *testing.T) { + t.Parallel() + + firstSet := &countingRuleSet{name: "first"} + item := NewRuleSetItem(&ruleSetItemTestRouter{ + ruleSets: map[string]adapter.RuleSet{ + "first": firstSet, + }, + }, []string{"first", "missing"}, false, false) + + err := item.Start() + require.ErrorContains(t, err, "rule-set not found: missing") + require.Zero(t, firstSet.RefCount()) +} + +func TestRuleSetItemRestartKeepsBalancedRefs(t *testing.T) { + t.Parallel() + + firstSet := &countingRuleSet{name: "first"} + item := NewRuleSetItem(&ruleSetItemTestRouter{ + ruleSets: map[string]adapter.RuleSet{ + "first": firstSet, + }, + }, []string{"first"}, false, false) + + require.NoError(t, item.Start()) + require.EqualValues(t, 1, firstSet.RefCount()) + + require.NoError(t, item.Start()) + require.EqualValues(t, 1, firstSet.RefCount()) + + require.NoError(t, item.Close()) + require.Zero(t, firstSet.RefCount()) +} diff --git a/route/rule/rule_item_source_hostname.go b/route/rule/rule_item_source_hostname.go new file mode 100644 index 0000000000..0df11c8c8a --- /dev/null +++ b/route/rule/rule_item_source_hostname.go @@ -0,0 +1,42 @@ +package rule + +import ( + "strings" + + "github.com/sagernet/sing-box/adapter" +) + +var _ RuleItem = (*SourceHostnameItem)(nil) + +type SourceHostnameItem struct { + hostnames []string + hostnameMap map[string]bool +} + +func NewSourceHostnameItem(hostnameList []string) *SourceHostnameItem { + rule := &SourceHostnameItem{ + hostnames: hostnameList, + hostnameMap: make(map[string]bool), + } + for _, hostname := range hostnameList { + rule.hostnameMap[hostname] = true + } + return rule +} + +func (r *SourceHostnameItem) Match(metadata *adapter.InboundContext) bool { + if metadata.SourceHostname == "" { + return false + } + return r.hostnameMap[metadata.SourceHostname] +} + +func (r *SourceHostnameItem) String() string { + var description string + if len(r.hostnames) == 1 { + description = "source_hostname=" + r.hostnames[0] + } else { + description = "source_hostname=[" + strings.Join(r.hostnames, " ") + "]" + } + return description +} diff --git a/route/rule/rule_item_source_mac_address.go b/route/rule/rule_item_source_mac_address.go new file mode 100644 index 0000000000..feeadb1dbf --- /dev/null +++ b/route/rule/rule_item_source_mac_address.go @@ -0,0 +1,48 @@ +package rule + +import ( + "net" + "strings" + + "github.com/sagernet/sing-box/adapter" +) + +var _ RuleItem = (*SourceMACAddressItem)(nil) + +type SourceMACAddressItem struct { + addresses []string + addressMap map[string]bool +} + +func NewSourceMACAddressItem(addressList []string) *SourceMACAddressItem { + rule := &SourceMACAddressItem{ + addresses: addressList, + addressMap: make(map[string]bool), + } + for _, address := range addressList { + parsed, err := net.ParseMAC(address) + if err == nil { + rule.addressMap[parsed.String()] = true + } else { + rule.addressMap[address] = true + } + } + return rule +} + +func (r *SourceMACAddressItem) Match(metadata *adapter.InboundContext) bool { + if metadata.SourceMACAddress == nil { + return false + } + return r.addressMap[metadata.SourceMACAddress.String()] +} + +func (r *SourceMACAddressItem) String() string { + var description string + if len(r.addresses) == 1 { + description = "source_mac_address=" + r.addresses[0] + } else { + description = "source_mac_address=[" + strings.Join(r.addresses, " ") + "]" + } + return description +} diff --git a/route/rule/rule_item_tunnel_destination.go b/route/rule/rule_item_tunnel_destination.go new file mode 100644 index 0000000000..34f711d6eb --- /dev/null +++ b/route/rule/rule_item_tunnel_destination.go @@ -0,0 +1,35 @@ +package rule + +import ( + "strings" + + "github.com/sagernet/sing-box/adapter" + F "github.com/sagernet/sing/common/format" +) + +var _ RuleItem = (*TunnelDestinationItem)(nil) + +type TunnelDestinationItem struct { + destinations []string + destinationMap map[string]bool +} + +func NewTunnelDestinationItem(destinations []string) *TunnelDestinationItem { + rule := &TunnelDestinationItem{destinations, make(map[string]bool)} + for _, destination := range destinations { + rule.destinationMap[destination] = true + } + return rule +} + +func (r *TunnelDestinationItem) Match(metadata *adapter.InboundContext) bool { + return r.destinationMap[metadata.TunnelDestination] +} + +func (r *TunnelDestinationItem) String() string { + if len(r.destinations) == 1 { + return F.ToString("tunnel_destination=", r.destinations[0]) + } else { + return F.ToString("tunnel_destination=[", strings.Join(r.destinations, " "), "]") + } +} diff --git a/route/rule/rule_item_tunnel_source.go b/route/rule/rule_item_tunnel_source.go new file mode 100644 index 0000000000..6a2f01cb2f --- /dev/null +++ b/route/rule/rule_item_tunnel_source.go @@ -0,0 +1,35 @@ +package rule + +import ( + "strings" + + "github.com/sagernet/sing-box/adapter" + F "github.com/sagernet/sing/common/format" +) + +var _ RuleItem = (*TunnelSourceItem)(nil) + +type TunnelSourceItem struct { + sources []string + sourceMap map[string]bool +} + +func NewTunnelSourceItem(sources []string) *TunnelSourceItem { + rule := &TunnelSourceItem{sources, make(map[string]bool)} + for _, source := range sources { + rule.sourceMap[source] = true + } + return rule +} + +func (r *TunnelSourceItem) Match(metadata *adapter.InboundContext) bool { + return r.sourceMap[metadata.TunnelSource] +} + +func (r *TunnelSourceItem) String() string { + if len(r.sources) == 1 { + return F.ToString("tunnel_source=", r.sources[0]) + } else { + return F.ToString("tunnel_source=[", strings.Join(r.sources, " "), "]") + } +} diff --git a/route/rule_item_user.go b/route/rule/rule_item_user.go similarity index 83% rename from route/rule_item_user.go rename to route/rule/rule_item_user.go index bed97fbaa6..87a8bff10d 100644 --- a/route/rule_item_user.go +++ b/route/rule/rule_item_user.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" @@ -26,10 +26,10 @@ func NewUserItem(users []string) *UserItem { } func (r *UserItem) Match(metadata *adapter.InboundContext) bool { - if metadata.ProcessInfo == nil || metadata.ProcessInfo.User == "" { + if metadata.ProcessInfo == nil || metadata.ProcessInfo.UserName == "" { return false } - return r.userMap[metadata.ProcessInfo.User] + return r.userMap[metadata.ProcessInfo.UserName] } func (r *UserItem) String() string { diff --git a/route/rule_item_user_id.go b/route/rule/rule_item_user_id.go similarity index 98% rename from route/rule_item_user_id.go rename to route/rule/rule_item_user_id.go index 43ab704e3a..57372de003 100644 --- a/route/rule_item_user_id.go +++ b/route/rule/rule_item_user_id.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" diff --git a/route/rule_item_wifi_bssid.go b/route/rule/rule_item_wifi_bssid.go similarity index 68% rename from route/rule_item_wifi_bssid.go rename to route/rule/rule_item_wifi_bssid.go index 3b1ff9c852..8f887322e5 100644 --- a/route/rule_item_wifi_bssid.go +++ b/route/rule/rule_item_wifi_bssid.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" @@ -10,12 +10,12 @@ import ( var _ RuleItem = (*WIFIBSSIDItem)(nil) type WIFIBSSIDItem struct { - bssidList []string - bssidMap map[string]bool - router adapter.Router + bssidList []string + bssidMap map[string]bool + networkManager adapter.NetworkManager } -func NewWIFIBSSIDItem(router adapter.Router, bssidList []string) *WIFIBSSIDItem { +func NewWIFIBSSIDItem(networkManager adapter.NetworkManager, bssidList []string) *WIFIBSSIDItem { bssidMap := make(map[string]bool) for _, bssid := range bssidList { bssidMap[bssid] = true @@ -23,12 +23,12 @@ func NewWIFIBSSIDItem(router adapter.Router, bssidList []string) *WIFIBSSIDItem return &WIFIBSSIDItem{ bssidList, bssidMap, - router, + networkManager, } } func (r *WIFIBSSIDItem) Match(metadata *adapter.InboundContext) bool { - return r.bssidMap[r.router.WIFIState().BSSID] + return r.bssidMap[r.networkManager.WIFIState().BSSID] } func (r *WIFIBSSIDItem) String() string { diff --git a/route/rule_item_wifi_ssid.go b/route/rule/rule_item_wifi_ssid.go similarity index 68% rename from route/rule_item_wifi_ssid.go rename to route/rule/rule_item_wifi_ssid.go index 62cf935eb0..ab9fdd884c 100644 --- a/route/rule_item_wifi_ssid.go +++ b/route/rule/rule_item_wifi_ssid.go @@ -1,4 +1,4 @@ -package route +package rule import ( "strings" @@ -10,12 +10,12 @@ import ( var _ RuleItem = (*WIFISSIDItem)(nil) type WIFISSIDItem struct { - ssidList []string - ssidMap map[string]bool - router adapter.Router + ssidList []string + ssidMap map[string]bool + networkManager adapter.NetworkManager } -func NewWIFISSIDItem(router adapter.Router, ssidList []string) *WIFISSIDItem { +func NewWIFISSIDItem(networkManager adapter.NetworkManager, ssidList []string) *WIFISSIDItem { ssidMap := make(map[string]bool) for _, ssid := range ssidList { ssidMap[ssid] = true @@ -23,12 +23,12 @@ func NewWIFISSIDItem(router adapter.Router, ssidList []string) *WIFISSIDItem { return &WIFISSIDItem{ ssidList, ssidMap, - router, + networkManager, } } func (r *WIFISSIDItem) Match(metadata *adapter.InboundContext) bool { - return r.ssidMap[r.router.WIFIState().SSID] + return r.ssidMap[r.networkManager.WIFIState().SSID] } func (r *WIFISSIDItem) String() string { diff --git a/route/rule/rule_nested_action.go b/route/rule/rule_nested_action.go new file mode 100644 index 0000000000..44e58839b5 --- /dev/null +++ b/route/rule/rule_nested_action.go @@ -0,0 +1,71 @@ +package rule + +import ( + "reflect" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func ValidateNoNestedRuleActions(rule option.Rule) error { + return validateNoNestedRuleActions(rule, false) +} + +func ValidateNoNestedDNSRuleActions(rule option.DNSRule) error { + return validateNoNestedDNSRuleActions(rule, false) +} + +func validateNoNestedRuleActions(rule option.Rule, nested bool) error { + if nested && ruleHasConfiguredAction(rule) { + return E.New(option.RouteRuleActionNestedUnsupportedMessage) + } + if rule.Type != C.RuleTypeLogical { + return nil + } + for i, subRule := range rule.LogicalOptions.Rules { + err := validateNoNestedRuleActions(subRule, true) + if err != nil { + return E.Cause(err, "sub rule[", i, "]") + } + } + return nil +} + +func validateNoNestedDNSRuleActions(rule option.DNSRule, nested bool) error { + if nested && dnsRuleHasConfiguredAction(rule) { + return E.New(option.DNSRuleActionNestedUnsupportedMessage) + } + if rule.Type != C.RuleTypeLogical { + return nil + } + for i, subRule := range rule.LogicalOptions.Rules { + err := validateNoNestedDNSRuleActions(subRule, true) + if err != nil { + return E.Cause(err, "sub rule[", i, "]") + } + } + return nil +} + +func ruleHasConfiguredAction(rule option.Rule) bool { + switch rule.Type { + case "", C.RuleTypeDefault: + return !reflect.DeepEqual(rule.DefaultOptions.RuleAction, option.RuleAction{}) + case C.RuleTypeLogical: + return !reflect.DeepEqual(rule.LogicalOptions.RuleAction, option.RuleAction{}) + default: + return false + } +} + +func dnsRuleHasConfiguredAction(rule option.DNSRule) bool { + switch rule.Type { + case "", C.RuleTypeDefault: + return !reflect.DeepEqual(rule.DefaultOptions.DNSRuleAction, option.DNSRuleAction{}) + case C.RuleTypeLogical: + return !reflect.DeepEqual(rule.LogicalOptions.DNSRuleAction, option.DNSRuleAction{}) + default: + return false + } +} diff --git a/route/rule/rule_nested_action_test.go b/route/rule/rule_nested_action_test.go new file mode 100644 index 0000000000..f895b89282 --- /dev/null +++ b/route/rule/rule_nested_action_test.go @@ -0,0 +1,88 @@ +package rule + +import ( + "context" + "testing" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + + "github.com/stretchr/testify/require" +) + +func TestNewRuleRejectsNestedRuleAction(t *testing.T) { + t.Parallel() + + _, err := NewRule(context.Background(), log.NewNOPFactory().NewLogger("router"), option.Rule{ + Type: C.RuleTypeLogical, + LogicalOptions: option.LogicalRule{ + RawLogicalRule: option.RawLogicalRule{ + Mode: C.LogicalTypeAnd, + Rules: []option.Rule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.RouteActionOptions{ + Outbound: "direct", + }, + }, + }, + }}, + }, + }, + }, false) + require.ErrorContains(t, err, option.RouteRuleActionNestedUnsupportedMessage) +} + +func TestNewDNSRuleRejectsNestedRuleAction(t *testing.T) { + t.Parallel() + + _, err := NewDNSRule(context.Background(), log.NewNOPFactory().NewLogger("dns"), option.DNSRule{ + Type: C.RuleTypeLogical, + LogicalOptions: option.LogicalDNSRule{ + RawLogicalDNSRule: option.RawLogicalDNSRule{ + Mode: C.LogicalTypeAnd, + Rules: []option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{ + Server: "default", + }, + }, + }, + }}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{ + Server: "default", + }, + }, + }, + }, true, false) + require.ErrorContains(t, err, option.DNSRuleActionNestedUnsupportedMessage) +} + +func TestNewDNSRuleRejectsReplyRejectMethod(t *testing.T) { + t.Parallel() + + _, err := NewDNSRule(context.Background(), log.NewNOPFactory().NewLogger("dns"), option.DNSRule{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: []string{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeReject, + RejectOptions: option.RejectActionOptions{ + Method: C.RuleActionRejectMethodReply, + }, + }, + }, + }, false, false) + require.ErrorContains(t, err, "reject method `reply` is not supported for DNS rules") +} diff --git a/route/rule/rule_network_interface_address.go b/route/rule/rule_network_interface_address.go new file mode 100644 index 0000000000..c699c593fa --- /dev/null +++ b/route/rule/rule_network_interface_address.go @@ -0,0 +1,64 @@ +package rule + +import ( + "net/netip" + "strings" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" +) + +var _ RuleItem = (*NetworkInterfaceAddressItem)(nil) + +type NetworkInterfaceAddressItem struct { + networkManager adapter.NetworkManager + interfaceAddresses map[C.InterfaceType][]netip.Prefix + description string +} + +func NewNetworkInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses *badjson.TypedMap[option.InterfaceType, badoption.Listable[*badoption.Prefixable]]) *NetworkInterfaceAddressItem { + item := &NetworkInterfaceAddressItem{ + networkManager: networkManager, + interfaceAddresses: make(map[C.InterfaceType][]netip.Prefix, interfaceAddresses.Size()), + } + var entryDescriptions []string + for _, entry := range interfaceAddresses.Entries() { + prefixes := make([]netip.Prefix, 0, len(entry.Value)) + for _, prefixable := range entry.Value { + prefixes = append(prefixes, prefixable.Build(netip.Prefix{})) + } + item.interfaceAddresses[entry.Key.Build()] = prefixes + entryDescriptions = append(entryDescriptions, entry.Key.Build().String()+"="+strings.Join(common.Map(prefixes, netip.Prefix.String), ",")) + } + item.description = "network_interface_address=[" + strings.Join(entryDescriptions, " ") + "]" + return item +} + +func (r *NetworkInterfaceAddressItem) Match(metadata *adapter.InboundContext) bool { + interfaces := r.networkManager.NetworkInterfaces() +match: + for ifType, addresses := range r.interfaceAddresses { + for _, networkInterface := range interfaces { + if networkInterface.Type != ifType { + continue + } + if common.Any(networkInterface.Addresses, func(it netip.Prefix) bool { + return common.Any(addresses, func(prefix netip.Prefix) bool { + return prefix.Overlaps(it) + }) + }) { + continue match + } + } + return false + } + return true +} + +func (r *NetworkInterfaceAddressItem) String() string { + return r.description +} diff --git a/route/rule/rule_set.go b/route/rule/rule_set.go new file mode 100644 index 0000000000..6720e788b7 --- /dev/null +++ b/route/rule/rule_set.go @@ -0,0 +1,104 @@ +package rule + +import ( + "context" + "reflect" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + "github.com/sagernet/sing/service" + + "go4.org/netipx" +) + +func NewRuleSet(ctx context.Context, logger logger.ContextLogger, options option.RuleSet) (adapter.RuleSet, error) { + switch options.Type { + case C.RuleSetTypeInline, C.RuleSetTypeLocal, "": + return NewLocalRuleSet(ctx, logger, options) + case C.RuleSetTypeRemote: + return NewRemoteRuleSet(ctx, logger, options) + default: + return nil, E.New("unknown rule-set type: ", options.Type) + } +} + +func extractIPSetFromRule(rawRule adapter.HeadlessRule) []*netipx.IPSet { + switch rule := rawRule.(type) { + case *DefaultHeadlessRule: + return common.FlatMap(rule.destinationIPCIDRItems, func(rawItem RuleItem) []*netipx.IPSet { + switch item := rawItem.(type) { + case *IPCIDRItem: + return []*netipx.IPSet{item.ipSet} + default: + return nil + } + }) + case *LogicalHeadlessRule: + return common.FlatMap(rule.rules, extractIPSetFromRule) + default: + panic("unexpected rule type") + } +} + +func HasHeadlessRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool { + for _, rule := range rules { + switch rule.Type { + case C.RuleTypeDefault: + if cond(rule.DefaultOptions) { + return true + } + case C.RuleTypeLogical: + if HasHeadlessRule(rule.LogicalOptions.Rules, cond) { + return true + } + } + } + return false +} + +func isProcessHeadlessRule(rule option.DefaultHeadlessRule) bool { + return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.ProcessPathRegex) > 0 || len(rule.PackageName) > 0 || len(rule.PackageNameRegex) > 0 +} + +func isWIFIHeadlessRule(rule option.DefaultHeadlessRule) bool { + return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0 +} + +func isIPCIDRHeadlessRule(rule option.DefaultHeadlessRule) bool { + return len(rule.IPCIDR) > 0 || rule.IPSet != nil +} + +func isDNSQueryTypeHeadlessRule(rule option.DefaultHeadlessRule) bool { + return len(rule.QueryType) > 0 +} + +func isNonIPCIDRHeadlessRule(rule option.DefaultHeadlessRule) bool { + ipOnly := option.DefaultHeadlessRule{ + IPCIDR: rule.IPCIDR, + IPSet: rule.IPSet, + Invert: rule.Invert, + } + return !reflect.DeepEqual(rule, ipOnly) +} + +func buildRuleSetMetadata(headlessRules []option.HeadlessRule) adapter.RuleSetMetadata { + return adapter.RuleSetMetadata{ + ContainsProcessRule: HasHeadlessRule(headlessRules, isProcessHeadlessRule), + ContainsWIFIRule: HasHeadlessRule(headlessRules, isWIFIHeadlessRule), + ContainsIPCIDRRule: HasHeadlessRule(headlessRules, isIPCIDRHeadlessRule), + ContainsDNSQueryTypeRule: HasHeadlessRule(headlessRules, isDNSQueryTypeHeadlessRule), + ContainsNonIPCIDRRule: HasHeadlessRule(headlessRules, isNonIPCIDRHeadlessRule), + } +} + +func validateRuleSetMetadataUpdate(ctx context.Context, tag string, metadata adapter.RuleSetMetadata) error { + validator := service.FromContext[adapter.DNSRuleSetUpdateValidator](ctx) + if validator == nil { + return nil + } + return validator.ValidateRuleSetMetadataUpdate(tag, metadata) +} diff --git a/route/rule/rule_set_local.go b/route/rule/rule_set_local.go new file mode 100644 index 0000000000..5408615fc0 --- /dev/null +++ b/route/rule/rule_set_local.go @@ -0,0 +1,221 @@ +package rule + +import ( + "context" + "os" + "path/filepath" + "strings" + "sync" + "sync/atomic" + + "github.com/sagernet/fswatch" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/srs" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/logger" + "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service/filemanager" + + "go4.org/netipx" +) + +var _ adapter.RuleSet = (*LocalRuleSet)(nil) + +type LocalRuleSet struct { + ctx context.Context + logger logger.Logger + tag string + access sync.RWMutex + rules []adapter.HeadlessRule + metadata adapter.RuleSetMetadata + fileFormat string + watcher *fswatch.Watcher + callbacks list.List[adapter.RuleSetUpdateCallback] + refs atomic.Int32 +} + +func NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.RuleSet) (*LocalRuleSet, error) { + ruleSet := &LocalRuleSet{ + ctx: ctx, + logger: logger, + tag: options.Tag, + fileFormat: options.Format, + } + if options.Type == C.RuleSetTypeInline { + if len(options.InlineOptions.Rules) == 0 { + return nil, E.New("empty inline rule-set") + } + err := ruleSet.reloadRules(options.InlineOptions.Rules) + if err != nil { + return nil, err + } + } else { + filePath := filemanager.BasePath(ctx, options.LocalOptions.Path) + filePath, _ = filepath.Abs(filePath) + err := ruleSet.reloadFile(filePath) + if err != nil { + return nil, err + } + watcher, err := fswatch.NewWatcher(fswatch.Options{ + Path: []string{filePath}, + Callback: func(path string) { + uErr := ruleSet.reloadFile(path) + if uErr != nil { + logger.Error(E.Cause(uErr, "reload rule-set ", options.Tag)) + } + }, + }) + if err != nil { + return nil, err + } + ruleSet.watcher = watcher + } + return ruleSet, nil +} + +func (s *LocalRuleSet) Name() string { + return s.tag +} + +func (s *LocalRuleSet) String() string { + return strings.Join(F.MapToString(s.rules), " ") +} + +func (s *LocalRuleSet) StartContext(ctx context.Context, startContext *adapter.HTTPStartContext) error { + if s.watcher != nil { + err := s.watcher.Start() + if err != nil { + s.logger.Error(E.Cause(err, "watch rule-set file")) + } + } + return nil +} + +func (s *LocalRuleSet) reloadFile(path string) error { + var ruleSet option.PlainRuleSetCompat + switch s.fileFormat { + case C.RuleSetFormatSource, "": + content, err := os.ReadFile(path) + if err != nil { + return err + } + ruleSet, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content) + if err != nil { + return err + } + + case C.RuleSetFormatBinary: + setFile, err := os.Open(path) + if err != nil { + return err + } + ruleSet, err = srs.Read(setFile, false) + if err != nil { + return err + } + default: + return E.New("unknown rule-set format: ", s.fileFormat) + } + plainRuleSet, err := ruleSet.Upgrade() + if err != nil { + return err + } + return s.reloadRules(plainRuleSet.Rules) +} + +func (s *LocalRuleSet) reloadRules(headlessRules []option.HeadlessRule) error { + rules := make([]adapter.HeadlessRule, len(headlessRules)) + var err error + for i, ruleOptions := range headlessRules { + rules[i], err = NewHeadlessRule(s.ctx, ruleOptions) + if err != nil { + return E.Cause(err, "parse rule_set.rules.[", i, "]") + } + } + metadata := buildRuleSetMetadata(headlessRules) + err = validateRuleSetMetadataUpdate(s.ctx, s.tag, metadata) + if err != nil { + return err + } + s.access.Lock() + s.rules = rules + s.metadata = metadata + callbacks := s.callbacks.Array() + s.access.Unlock() + for _, callback := range callbacks { + callback(s) + } + return nil +} + +func (s *LocalRuleSet) PostStart() error { + return nil +} + +func (s *LocalRuleSet) Metadata() adapter.RuleSetMetadata { + s.access.RLock() + defer s.access.RUnlock() + return s.metadata +} + +func (s *LocalRuleSet) ExtractIPSet() []*netipx.IPSet { + s.access.RLock() + defer s.access.RUnlock() + return common.FlatMap(s.rules, extractIPSetFromRule) +} + +func (s *LocalRuleSet) IncRef() { + s.refs.Add(1) +} + +func (s *LocalRuleSet) DecRef() { + if s.refs.Add(-1) < 0 { + panic("rule-set: negative refs") + } +} + +func (s *LocalRuleSet) Cleanup() { + if s.refs.Load() == 0 { + s.rules = nil + } +} + +func (s *LocalRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] { + s.access.Lock() + defer s.access.Unlock() + return s.callbacks.PushBack(callback) +} + +func (s *LocalRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) { + s.access.Lock() + defer s.access.Unlock() + s.callbacks.Remove(element) +} + +func (s *LocalRuleSet) Close() error { + s.rules = nil + return common.Close(common.PtrOrNil(s.watcher)) +} + +func (s *LocalRuleSet) Match(metadata *adapter.InboundContext) bool { + return !s.matchStates(metadata).isEmpty() +} + +func (s *LocalRuleSet) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet { + return s.matchStatesWithBase(metadata, 0) +} + +func (s *LocalRuleSet) matchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet { + var stateSet ruleMatchStateSet + for _, rule := range s.rules { + nestedMetadata := *metadata + nestedMetadata.ResetRuleMatchCache() + stateSet = stateSet.merge(matchHeadlessRuleStatesWithBase(rule, &nestedMetadata, base)) + } + return stateSet +} diff --git a/route/rule/rule_set_remote.go b/route/rule/rule_set_remote.go new file mode 100644 index 0000000000..3802482cda --- /dev/null +++ b/route/rule/rule_set_remote.go @@ -0,0 +1,350 @@ +package rule + +import ( + "bytes" + "context" + "io" + "net/http" + "runtime" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/srs" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/experimental/deprecated" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/logger" + "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service" + "github.com/sagernet/sing/service/pause" + + "go4.org/netipx" +) + +var _ adapter.RuleSet = (*RemoteRuleSet)(nil) + +type RemoteRuleSet struct { + ctx context.Context + cancel context.CancelFunc + logger logger.ContextLogger + outbound adapter.OutboundManager + options option.RuleSet + updateInterval time.Duration + httpClient *http.Client + access sync.RWMutex + rules []adapter.HeadlessRule + metadata adapter.RuleSetMetadata + lastUpdated time.Time + lastEtag string + updateTicker *time.Ticker + startupTicker *time.Ticker //H + cacheFile adapter.CacheFile + pauseManager pause.Manager + callbacks list.List[adapter.RuleSetUpdateCallback] + refs atomic.Int32 +} + +func NewRemoteRuleSet(ctx context.Context, logger logger.ContextLogger, options option.RuleSet) (*RemoteRuleSet, error) { + ctx, cancel := context.WithCancel(ctx) + var updateInterval time.Duration + if options.RemoteOptions.UpdateInterval > 0 { + updateInterval = time.Duration(options.RemoteOptions.UpdateInterval) + } else { + updateInterval = 24 * time.Hour + } + return &RemoteRuleSet{ + ctx: ctx, + cancel: cancel, + outbound: service.FromContext[adapter.OutboundManager](ctx), + logger: logger, + options: options, + updateInterval: updateInterval, + pauseManager: service.FromContext[pause.Manager](ctx), + }, nil +} + +func (s *RemoteRuleSet) Name() string { + return s.options.Tag +} + +func (s *RemoteRuleSet) String() string { + return strings.Join(F.MapToString(s.rules), " ") +} + +func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext *adapter.HTTPStartContext) error { + s.cacheFile = service.FromContext[adapter.CacheFile](s.ctx) + transport, err := s.resolveTransport() + if err != nil { + return E.Cause(err, "create rule-set http client") + } + startContext.Register(transport) + s.httpClient = &http.Client{Transport: transport} + if s.cacheFile != nil { + if savedSet := s.cacheFile.LoadRuleSet(s.options.Tag); savedSet != nil { + err = s.loadBytes(savedSet.Content) + if err != nil { + return E.Cause(err, "restore cached rule-set") + } + s.lastUpdated = savedSet.LastUpdated + s.lastEtag = savedSet.LastEtag + } + } + if s.lastUpdated.IsZero() { + err = s.fetch(ctx, true) + if err != nil { + s.logger.Error(E.Cause(err, "initial rule-set: ", s.options.Tag)) + } + } + s.updateTicker = time.NewTicker(s.updateInterval) + return nil +} + +func (s *RemoteRuleSet) PostStart() error { + go s.loopUpdate() + return nil +} + +func (s *RemoteRuleSet) Metadata() adapter.RuleSetMetadata { + s.access.RLock() + defer s.access.RUnlock() + return s.metadata +} + +func (s *RemoteRuleSet) ExtractIPSet() []*netipx.IPSet { + s.access.RLock() + defer s.access.RUnlock() + return common.FlatMap(s.rules, extractIPSetFromRule) +} + +func (s *RemoteRuleSet) IncRef() { + s.refs.Add(1) +} + +func (s *RemoteRuleSet) DecRef() { + if s.refs.Add(-1) < 0 { + panic("rule-set: negative refs") + } +} + +func (s *RemoteRuleSet) Cleanup() { + if s.refs.Load() == 0 { + s.rules = nil + } +} + +func (s *RemoteRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] { + s.access.Lock() + defer s.access.Unlock() + return s.callbacks.PushBack(callback) +} + +func (s *RemoteRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) { + s.access.Lock() + defer s.access.Unlock() + s.callbacks.Remove(element) +} + +func (s *RemoteRuleSet) loadBytes(content []byte) error { + var ( + ruleSet option.PlainRuleSetCompat + err error + ) + switch s.options.Format { + case C.RuleSetFormatSource: + ruleSet, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content) + if err != nil { + return err + } + case C.RuleSetFormatBinary: + ruleSet, err = srs.Read(bytes.NewReader(content), false) + if err != nil { + return err + } + default: + return E.New("unknown rule-set format: ", s.options.Format) + } + plainRuleSet, err := ruleSet.Upgrade() + if err != nil { + return err + } + rules := make([]adapter.HeadlessRule, len(plainRuleSet.Rules)) + for i, ruleOptions := range plainRuleSet.Rules { + rules[i], err = NewHeadlessRule(s.ctx, ruleOptions) + if err != nil { + return E.Cause(err, "parse rule_set.rules.[", i, "]") + } + } + metadata := buildRuleSetMetadata(plainRuleSet.Rules) + err = validateRuleSetMetadataUpdate(s.ctx, s.options.Tag, metadata) + if err != nil { + return err + } + s.access.Lock() + s.metadata = metadata + s.rules = rules + callbacks := s.callbacks.Array() + s.access.Unlock() + for _, callback := range callbacks { + callback(s) + } + return nil +} + +func (s *RemoteRuleSet) loopUpdate() { + if time.Since(s.lastUpdated) > s.updateInterval { + s.updateOnce() + } + for s.lastUpdated.IsZero() { + select { + case <-s.ctx.Done(): + return + case <-s.startupTicker.C: + s.updateOnce() + } + + } + + for { + runtime.GC() + select { + case <-s.ctx.Done(): + return + case <-s.updateTicker.C: + s.updateOnce() + } + } +} + +func (s *RemoteRuleSet) updateOnce() { + err := s.fetch(s.ctx, false) + if err != nil { + s.logger.Error("fetch rule-set ", s.options.Tag, ": ", err) + } else if s.refs.Load() == 0 { + s.rules = nil + } +} + +func (s *RemoteRuleSet) fetch(ctx context.Context, isStart bool) error { + s.logger.Debug("updating rule-set ", s.options.Tag, " from URL: ", s.options.RemoteOptions.URL) + request, err := http.NewRequest("GET", s.options.RemoteOptions.URL, nil) + if err != nil { + return err + } + if s.lastEtag != "" { + request.Header.Set("If-None-Match", s.lastEtag) + } + if !isStart { + defer s.httpClient.CloseIdleConnections() + } + response, err := s.httpClient.Do(request.WithContext(ctx)) + if err != nil { + return err + } + defer response.Body.Close() + switch response.StatusCode { + case http.StatusOK: + case http.StatusNotModified: + s.lastUpdated = time.Now() + if s.cacheFile != nil { + savedRuleSet := s.cacheFile.LoadRuleSet(s.options.Tag) + if savedRuleSet != nil { + savedRuleSet.LastUpdated = s.lastUpdated + err = s.cacheFile.SaveRuleSet(s.options.Tag, savedRuleSet) + if err != nil { + s.logger.Error("save rule-set updated time: ", err) + return nil + } + } + } + s.logger.Info("update rule-set ", s.options.Tag, ": not modified") + return nil + default: + return E.New("unexpected status: ", response.Status) + } + content, err := io.ReadAll(response.Body) + if err != nil { + return err + } + err = s.loadBytes(content) + if err != nil { + return err + } + eTagHeader := response.Header.Get("Etag") + if eTagHeader != "" { + s.lastEtag = eTagHeader + } + s.lastUpdated = time.Now() + if s.cacheFile != nil { + err = s.cacheFile.SaveRuleSet(s.options.Tag, &adapter.SavedBinary{ + LastUpdated: s.lastUpdated, + Content: content, + LastEtag: s.lastEtag, + }) + if err != nil { + s.logger.Error("save rule-set cache: ", err) + } + } + s.logger.Info("updated rule-set ", s.options.Tag) + return nil +} + +func (s *RemoteRuleSet) resolveTransport() (adapter.HTTPTransport, error) { + httpClientManager := service.FromContext[adapter.HTTPClientManager](s.ctx) + if s.options.RemoteOptions.HTTPClient != nil && !s.options.RemoteOptions.HTTPClient.IsEmpty() { + if s.options.RemoteOptions.DownloadDetour != "" { //nolint:staticcheck + return nil, E.New("http_client is conflict with deprecated download_detour field") + } + return httpClientManager.ResolveTransport(s.ctx, s.logger, *s.options.RemoteOptions.HTTPClient) + } + if s.options.RemoteOptions.DownloadDetour != "" { //nolint:staticcheck + deprecated.Report(s.ctx, deprecated.OptionLegacyRuleSetDownloadDetour) + return httpClientManager.ResolveTransport(s.ctx, s.logger, option.HTTPClientOptions{ + DialerOptions: option.DialerOptions{ + Detour: s.options.RemoteOptions.DownloadDetour, //nolint:staticcheck + }, + DisableEmptyDirectCheck: true, + }) + } + defaultTransport := httpClientManager.DefaultTransport() + if defaultTransport == nil { + return nil, E.New("default http client transport is not initialized") + } + return defaultTransport, nil +} + +func (s *RemoteRuleSet) Close() error { + s.rules = nil + s.cancel() + if s.startupTicker != nil { + s.startupTicker.Stop() + } + if s.updateTicker != nil { + s.updateTicker.Stop() + } + return nil +} + +func (s *RemoteRuleSet) Match(metadata *adapter.InboundContext) bool { + return !s.matchStates(metadata).isEmpty() +} + +func (s *RemoteRuleSet) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet { + return s.matchStatesWithBase(metadata, 0) +} + +func (s *RemoteRuleSet) matchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet { + var stateSet ruleMatchStateSet + for _, rule := range s.rules { + nestedMetadata := *metadata + nestedMetadata.ResetRuleMatchCache() + stateSet = stateSet.merge(matchHeadlessRuleStatesWithBase(rule, &nestedMetadata, base)) + } + return stateSet +} diff --git a/route/rule/rule_set_semantics_test.go b/route/rule/rule_set_semantics_test.go new file mode 100644 index 0000000000..2fc559d204 --- /dev/null +++ b/route/rule/rule_set_semantics_test.go @@ -0,0 +1,1261 @@ +package rule + +import ( + "context" + "net" + "net/netip" + "strings" + "testing" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/convertor/adguard" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + slogger "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + mDNS "github.com/miekg/dns" + "github.com/stretchr/testify/require" +) + +func TestRouteRuleSetMergeDestinationAddressGroup(t *testing.T) { + t.Parallel() + testCases := []struct { + name string + metadata adapter.InboundContext + inner adapter.HeadlessRule + }{ + { + name: "domain", + metadata: testMetadata("www.example.com"), + inner: headlessDefaultRule(t, func(rule *abstractDefaultRule) { addDestinationAddressItem(t, rule, []string{"www.example.com"}, nil) }), + }, + { + name: "domain_suffix", + metadata: testMetadata("www.example.com"), + inner: headlessDefaultRule(t, func(rule *abstractDefaultRule) { addDestinationAddressItem(t, rule, nil, []string{"example.com"}) }), + }, + { + name: "domain_keyword", + metadata: testMetadata("www.example.com"), + inner: headlessDefaultRule(t, func(rule *abstractDefaultRule) { addDestinationKeywordItem(rule, []string{"example"}) }), + }, + { + name: "domain_regex", + metadata: testMetadata("www.example.com"), + inner: headlessDefaultRule(t, func(rule *abstractDefaultRule) { addDestinationRegexItem(t, rule, []string{`^www\.example\.com$`}) }), + }, + { + name: "ip_cidr", + metadata: func() adapter.InboundContext { + metadata := testMetadata("lookup.example") + metadata.DestinationAddresses = []netip.Addr{netip.MustParseAddr("8.8.8.8")} + return metadata + }(), + inner: headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationIPCIDRItem(t, rule, []string{"8.8.8.0/24"}) + }), + }, + } + for _, testCase := range testCases { + testCase := testCase + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + ruleSet := newLocalRuleSetForTest("merge-destination", testCase.inner) + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + }) + require.True(t, rule.Match(&testCase.metadata)) + }) + } +} + +func TestRouteRuleSetMergeSourceAndPortGroups(t *testing.T) { + t.Parallel() + t.Run("source address", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + ruleSet := newLocalRuleSetForTest("merge-source-address", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addSourceAddressItem(t, rule, []string{"10.0.0.0/8"}) + })) + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + addSourceAddressItem(t, rule, []string{"198.51.100.0/24"}) + }) + require.True(t, rule.Match(&metadata)) + }) + t.Run("source address via ruleset ipcidr match source", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + ruleSet := newLocalRuleSetForTest("merge-source-address-ipcidr", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationIPCIDRItem(t, rule, []string{"10.0.0.0/8"}) + })) + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{ + setList: []adapter.RuleSet{ruleSet}, + ipCidrMatchSource: true, + }) + addSourceAddressItem(t, rule, []string{"198.51.100.0/24"}) + }) + require.True(t, rule.Match(&metadata)) + }) + t.Run("destination port", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + ruleSet := newLocalRuleSetForTest("merge-destination-port", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationPortItem(rule, []uint16{443}) + })) + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + addDestinationPortItem(rule, []uint16{8443}) + }) + require.True(t, rule.Match(&metadata)) + }) + t.Run("destination port range", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + ruleSet := newLocalRuleSetForTest("merge-destination-port-range", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationPortRangeItem(t, rule, []string{"400:500"}) + })) + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + addDestinationPortItem(rule, []uint16{8443}) + }) + require.True(t, rule.Match(&metadata)) + }) + t.Run("source port", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + ruleSet := newLocalRuleSetForTest("merge-source-port", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addSourcePortItem(rule, []uint16{1000}) + })) + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + addSourcePortItem(rule, []uint16{2000}) + }) + require.True(t, rule.Match(&metadata)) + }) + t.Run("source port range", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + ruleSet := newLocalRuleSetForTest("merge-source-port-range", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addSourcePortRangeItem(t, rule, []string{"900:1100"}) + })) + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + addSourcePortItem(rule, []uint16{2000}) + }) + require.True(t, rule.Match(&metadata)) + }) +} + +func TestRouteRuleSetOuterGroupedStateMergesIntoSameGroup(t *testing.T) { + t.Parallel() + testCases := []struct { + name string + metadata adapter.InboundContext + buildOuter func(*testing.T, *abstractDefaultRule) + buildInner func(*testing.T, *abstractDefaultRule) + }{ + { + name: "destination address", + metadata: testMetadata("www.example.com"), + buildOuter: func(t *testing.T, rule *abstractDefaultRule) { + t.Helper() + addDestinationAddressItem(t, rule, nil, []string{"example.com"}) + }, + buildInner: func(t *testing.T, rule *abstractDefaultRule) { + t.Helper() + addDestinationAddressItem(t, rule, nil, []string{"google.com"}) + }, + }, + { + name: "source address", + metadata: testMetadata("www.example.com"), + buildOuter: func(t *testing.T, rule *abstractDefaultRule) { + t.Helper() + addSourceAddressItem(t, rule, []string{"10.0.0.0/8"}) + }, + buildInner: func(t *testing.T, rule *abstractDefaultRule) { + t.Helper() + addSourceAddressItem(t, rule, []string{"198.51.100.0/24"}) + }, + }, + { + name: "source port", + metadata: testMetadata("www.example.com"), + buildOuter: func(t *testing.T, rule *abstractDefaultRule) { + t.Helper() + addSourcePortItem(rule, []uint16{1000}) + }, + buildInner: func(t *testing.T, rule *abstractDefaultRule) { + t.Helper() + addSourcePortItem(rule, []uint16{2000}) + }, + }, + { + name: "destination port", + metadata: testMetadata("www.example.com"), + buildOuter: func(t *testing.T, rule *abstractDefaultRule) { + t.Helper() + addDestinationPortItem(rule, []uint16{443}) + }, + buildInner: func(t *testing.T, rule *abstractDefaultRule) { + t.Helper() + addDestinationPortItem(rule, []uint16{8443}) + }, + }, + { + name: "destination ip cidr", + metadata: func() adapter.InboundContext { + metadata := testMetadata("lookup.example") + metadata.DestinationAddresses = []netip.Addr{netip.MustParseAddr("203.0.113.1")} + return metadata + }(), + buildOuter: func(t *testing.T, rule *abstractDefaultRule) { + t.Helper() + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + }, + buildInner: func(t *testing.T, rule *abstractDefaultRule) { + t.Helper() + addDestinationIPCIDRItem(t, rule, []string{"198.51.100.0/24"}) + }, + }, + } + for _, testCase := range testCases { + testCase := testCase + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + ruleSet := newLocalRuleSetForTest("outer-merge-"+testCase.name, headlessDefaultRule(t, func(rule *abstractDefaultRule) { + testCase.buildInner(t, rule) + })) + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + testCase.buildOuter(t, rule) + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + }) + require.True(t, rule.Match(&testCase.metadata)) + }) + } +} + +func TestRouteRuleSetOtherFieldsStayAnd(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + ruleSet := newLocalRuleSetForTest("other-fields-and", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"example.com"}) + })) + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + addOtherItem(rule, NewNetworkItem([]string{N.NetworkUDP})) + }) + require.False(t, rule.Match(&metadata)) +} + +func TestRouteRuleSetMergedBranchKeepsAndConstraints(t *testing.T) { + t.Parallel() + t.Run("outer group does not bypass inner non grouped condition", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + ruleSet := newLocalRuleSetForTest("network-and", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addOtherItem(rule, NewNetworkItem([]string{N.NetworkUDP})) + })) + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"example.com"}) + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + }) + require.False(t, rule.Match(&metadata)) + }) + t.Run("outer group does not satisfy different grouped branch", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + ruleSet := newLocalRuleSetForTest("different-group", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"google.com"}) + })) + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + addSourcePortItem(rule, []uint16{1000}) + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + }) + require.False(t, rule.Match(&metadata)) + }) +} + +func TestRouteRuleSetOrSemantics(t *testing.T) { + t.Parallel() + t.Run("later ruleset can satisfy outer group", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + emptyStateSet := newLocalRuleSetForTest("network-only", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addOtherItem(rule, NewNetworkItem([]string{N.NetworkTCP})) + })) + destinationStateSet := newLocalRuleSetForTest("domain-only", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"example.com"}) + })) + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{emptyStateSet, destinationStateSet}}) + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + }) + require.True(t, rule.Match(&metadata)) + }) + t.Run("later rule in same set can satisfy outer group", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + ruleSet := newLocalRuleSetForTest( + "rule-set-or", + headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addOtherItem(rule, NewNetworkItem([]string{N.NetworkTCP})) + }), + headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"example.com"}) + }), + ) + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + }) + require.True(t, rule.Match(&metadata)) + }) + t.Run("cross ruleset union is not allowed", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + sourceStateSet := newLocalRuleSetForTest("source-only", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addSourcePortItem(rule, []uint16{1000}) + })) + destinationStateSet := newLocalRuleSetForTest("destination-only", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"example.com"}) + })) + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{sourceStateSet, destinationStateSet}}) + addSourcePortItem(rule, []uint16{2000}) + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + }) + require.False(t, rule.Match(&metadata)) + }) +} + +func TestRouteRuleSetLogicalSemantics(t *testing.T) { + t.Parallel() + t.Run("logical or keeps all successful branch states", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + ruleSet := newLocalRuleSetForTest("logical-or", headlessLogicalRule( + C.LogicalTypeOr, + false, + headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addOtherItem(rule, NewNetworkItem([]string{N.NetworkTCP})) + }), + headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"example.com"}) + }), + )) + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + }) + require.True(t, rule.Match(&metadata)) + }) + t.Run("logical and unions child states", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + ruleSet := newLocalRuleSetForTest("logical-and", headlessLogicalRule( + C.LogicalTypeAnd, + false, + headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"example.com"}) + }), + headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addSourcePortItem(rule, []uint16{1000}) + }), + )) + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + addSourcePortItem(rule, []uint16{2000}) + }) + require.True(t, rule.Match(&metadata)) + }) + t.Run("invert success does not contribute positive state", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + ruleSet := newLocalRuleSetForTest("invert", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + rule.invert = true + addDestinationAddressItem(t, rule, nil, []string{"cn"}) + })) + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + }) + require.False(t, rule.Match(&metadata)) + }) +} + +func TestRouteRuleSetInvertMergedBranchSemantics(t *testing.T) { + t.Parallel() + t.Run("default invert keeps inherited group outside grouped predicate", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + ruleSet := newLocalRuleSetForTest("invert-grouped", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + rule.invert = true + addDestinationAddressItem(t, rule, nil, []string{"google.com"}) + })) + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"example.com"}) + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + }) + require.True(t, rule.Match(&metadata)) + }) + t.Run("default invert keeps inherited group after negation succeeds", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + ruleSet := newLocalRuleSetForTest("invert-network", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + rule.invert = true + addOtherItem(rule, NewNetworkItem([]string{N.NetworkUDP})) + })) + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"example.com"}) + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + }) + require.True(t, rule.Match(&metadata)) + }) + t.Run("logical invert keeps inherited group outside grouped predicate", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + ruleSet := newLocalRuleSetForTest("logical-invert-grouped", headlessLogicalRule( + C.LogicalTypeOr, + true, + headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"google.com"}) + }), + )) + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"example.com"}) + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + }) + require.True(t, rule.Match(&metadata)) + }) + t.Run("logical invert keeps inherited group after negation succeeds", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + ruleSet := newLocalRuleSetForTest("logical-invert-network", headlessLogicalRule( + C.LogicalTypeOr, + true, + headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addOtherItem(rule, NewNetworkItem([]string{N.NetworkUDP})) + }), + )) + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"example.com"}) + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + }) + require.True(t, rule.Match(&metadata)) + }) +} + +func TestRouteRuleSetNoLeakageRegressions(t *testing.T) { + t.Parallel() + t.Run("same ruleset failed branch does not leak", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + ruleSet := newLocalRuleSetForTest( + "same-set", + headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"example.com"}) + addSourcePortItem(rule, []uint16{1}) + }), + headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + addSourcePortItem(rule, []uint16{1000}) + }), + ) + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + }) + require.False(t, rule.Match(&metadata)) + }) + t.Run("adguard exclusion remains isolated across rulesets", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("im.qq.com") + excludeSet := newLocalRuleSetForTest("adguard", mustAdGuardRule(t, "@@||im.qq.com^\n||whatever1.com^\n")) + otherSet := newLocalRuleSetForTest("other", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"whatever2.com"}) + })) + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{excludeSet, otherSet}}) + }) + require.False(t, rule.Match(&metadata)) + }) +} + +func TestDefaultRuleDoesNotReuseGroupedMatchCacheAcrossEvaluations(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"example.com"}) + }) + require.True(t, rule.Match(&metadata)) + + metadata.Destination.Fqdn = "www.example.org" + require.False(t, rule.Match(&metadata)) +} + +func TestRouteRuleSetRemoteUsesSameSemantics(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + ruleSet := newRemoteRuleSetForTest( + "remote", + headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addOtherItem(rule, NewNetworkItem([]string{N.NetworkTCP})) + }), + headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"example.com"}) + }), + ) + rule := routeRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + }) + require.True(t, rule.Match(&metadata)) +} + +func TestDNSRuleSetSemantics(t *testing.T) { + t.Parallel() + t.Run("outer destination group merges into matching ruleset branch", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.baidu.com") + ruleSet := newLocalRuleSetForTest("dns-merged-branch", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"google.com"}) + })) + rule := dnsRuleForTest(func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"baidu.com"}) + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + }) + require.True(t, rule.Match(&metadata)) + }) + t.Run("outer destination group does not bypass ruleset non grouped condition", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + ruleSet := newLocalRuleSetForTest("dns-network-and", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addOtherItem(rule, NewNetworkItem([]string{N.NetworkUDP})) + })) + rule := dnsRuleForTest(func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"example.com"}) + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + }) + require.False(t, rule.Match(&metadata)) + }) + t.Run("outer destination group stays outside inverted grouped branch", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.baidu.com") + ruleSet := newLocalRuleSetForTest("dns-invert-grouped", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + rule.invert = true + addDestinationAddressItem(t, rule, nil, []string{"google.com"}) + })) + rule := dnsRuleForTest(func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"baidu.com"}) + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + }) + require.True(t, rule.Match(&metadata)) + }) + t.Run("outer destination group stays outside inverted logical branch", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + ruleSet := newLocalRuleSetForTest("dns-logical-invert-network", headlessLogicalRule( + C.LogicalTypeOr, + true, + headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addOtherItem(rule, NewNetworkItem([]string{N.NetworkUDP})) + }), + )) + rule := dnsRuleForTest(func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"example.com"}) + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + }) + require.True(t, rule.Match(&metadata)) + }) + t.Run("match address limit merges destination group", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + ruleSet := newLocalRuleSetForTest("dns-merge", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"example.com"}) + })) + rule := dnsRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + }) + require.True(t, rule.MatchAddressLimit(&metadata, dnsResponseForTest(netip.MustParseAddr("203.0.113.1")))) + }) + t.Run("dns keeps ruleset or semantics", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + emptyStateSet := newLocalRuleSetForTest("dns-empty", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addOtherItem(rule, NewNetworkItem([]string{N.NetworkTCP})) + })) + destinationStateSet := newLocalRuleSetForTest("dns-destination", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"example.com"}) + })) + rule := dnsRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{emptyStateSet, destinationStateSet}}) + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + }) + require.True(t, rule.MatchAddressLimit(&metadata, dnsResponseForTest(netip.MustParseAddr("203.0.113.1")))) + }) + t.Run("ruleset ip cidr flags stay scoped", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + ruleSet := newLocalRuleSetForTest("dns-ipcidr", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + })) + rule := dnsRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{ + setList: []adapter.RuleSet{ruleSet}, + ipCidrAcceptEmpty: true, + }) + }) + require.True(t, rule.MatchAddressLimit(&metadata, dnsResponseForTest(netip.MustParseAddr("203.0.113.1")))) + require.False(t, rule.MatchAddressLimit(&metadata, dnsResponseForTest(netip.MustParseAddr("8.8.8.8")))) + require.True(t, rule.MatchAddressLimit(&metadata, dnsResponseForTest())) + require.False(t, metadata.IPCIDRMatchSource) + require.False(t, metadata.IPCIDRAcceptEmpty) + }) + t.Run("pre lookup ruleset only deferred fields fail closed", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("lookup.example") + ruleSet := newLocalRuleSetForTest("dns-prelookup-ipcidr", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + })) + rule := dnsRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + }) + // This is accepted without match_response so mixed rule_set deployments keep + // working; the destination-IP-only branch simply cannot match before a DNS + // response is available. + require.False(t, rule.Match(&metadata)) + }) + t.Run("pre lookup ruleset destination cidr does not fall back to other predicates", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("lookup.example") + ruleSet := newLocalRuleSetForTest("dns-prelookup-network-and-ipcidr", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addOtherItem(rule, NewNetworkItem([]string{N.NetworkTCP})) + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + })) + rule := dnsRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + }) + require.False(t, rule.Match(&metadata)) + }) + t.Run("pre lookup mixed ruleset still matches non response branch", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("www.example.com") + ruleSet := newLocalRuleSetForTest( + "dns-prelookup-mixed", + headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addOtherItem(rule, NewNetworkItem([]string{N.NetworkTCP})) + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + }), + headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationAddressItem(t, rule, nil, []string{"example.com"}) + }), + ) + rule := dnsRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + }) + // Destination-IP predicates inside rule_set fail closed before the DNS response, + // but they must not force validation errors or suppress sibling non-response + // branches. + require.True(t, rule.Match(&metadata)) + }) +} + +func TestDNSMatchResponseRuleSetDestinationCIDRUsesDNSResponse(t *testing.T) { + t.Parallel() + + ruleSet := newLocalRuleSetForTest("dns-response-ipcidr", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + })) + rule := dnsRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + }) + rule.matchResponse = true + + matchedMetadata := testMetadata("lookup.example") + matchedMetadata.DNSResponse = dnsResponseForTest(netip.MustParseAddr("203.0.113.1")) + require.True(t, rule.Match(&matchedMetadata)) + require.Empty(t, matchedMetadata.DestinationAddresses) + + unmatchedMetadata := testMetadata("lookup.example") + unmatchedMetadata.DNSResponse = dnsResponseForTest(netip.MustParseAddr("8.8.8.8")) + require.False(t, rule.Match(&unmatchedMetadata)) +} + +func TestDNSMatchResponseMissingResponseUsesBooleanSemantics(t *testing.T) { + t.Parallel() + + t.Run("plain rule remains false", func(t *testing.T) { + t.Parallel() + + rule := dnsRuleForTest(func(rule *abstractDefaultRule) {}) + rule.matchResponse = true + + metadata := testMetadata("lookup.example") + require.False(t, rule.Match(&metadata)) + }) + + t.Run("invert rule becomes true", func(t *testing.T) { + t.Parallel() + + rule := dnsRuleForTest(func(rule *abstractDefaultRule) { + rule.invert = true + }) + rule.matchResponse = true + + metadata := testMetadata("lookup.example") + require.True(t, rule.Match(&metadata)) + }) + + t.Run("logical wrapper respects inverted child", func(t *testing.T) { + t.Parallel() + + nestedRule := dnsRuleForTest(func(rule *abstractDefaultRule) { + rule.invert = true + }) + nestedRule.matchResponse = true + + logicalRule := &LogicalDNSRule{ + abstractLogicalRule: abstractLogicalRule{ + rules: []adapter.HeadlessRule{nestedRule}, + mode: C.LogicalTypeAnd, + }, + } + + metadata := testMetadata("lookup.example") + require.True(t, logicalRule.Match(&metadata)) + }) +} + +func TestDNSAddressLimitIgnoresDestinationAddresses(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + build func(*testing.T, *abstractDefaultRule) + matchedResponse *mDNS.Msg + unmatchedResponse *mDNS.Msg + }{ + { + name: "ip_cidr", + build: func(t *testing.T, rule *abstractDefaultRule) { + t.Helper() + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + }, + matchedResponse: dnsResponseForTest(netip.MustParseAddr("203.0.113.1")), + unmatchedResponse: dnsResponseForTest(netip.MustParseAddr("8.8.8.8")), + }, + { + name: "ip_is_private", + build: func(t *testing.T, rule *abstractDefaultRule) { + t.Helper() + addDestinationIPIsPrivateItem(rule) + }, + matchedResponse: dnsResponseForTest(netip.MustParseAddr("10.0.0.1")), + unmatchedResponse: dnsResponseForTest(netip.MustParseAddr("8.8.8.8")), + }, + { + name: "ip_accept_any", + build: func(t *testing.T, rule *abstractDefaultRule) { + t.Helper() + addDestinationIPAcceptAnyItem(rule) + }, + matchedResponse: dnsResponseForTest(netip.MustParseAddr("203.0.113.1")), + unmatchedResponse: dnsResponseForTest(), + }, + } + for _, testCase := range testCases { + testCase := testCase + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + rule := dnsRuleForTest(func(rule *abstractDefaultRule) { + testCase.build(t, rule) + }) + + mismatchMetadata := testMetadata("lookup.example") + mismatchMetadata.DestinationAddresses = []netip.Addr{netip.MustParseAddr("203.0.113.1")} + require.False(t, rule.MatchAddressLimit(&mismatchMetadata, testCase.unmatchedResponse)) + + matchMetadata := testMetadata("lookup.example") + matchMetadata.DestinationAddresses = []netip.Addr{netip.MustParseAddr("8.8.8.8")} + require.True(t, rule.MatchAddressLimit(&matchMetadata, testCase.matchedResponse)) + }) + } +} + +func TestDNSLegacyAddressLimitPreLookupDefersDirectRules(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + build func(*testing.T, *abstractDefaultRule) + matchedResponse *mDNS.Msg + unmatchedResponse *mDNS.Msg + }{ + { + name: "ip_cidr", + build: func(t *testing.T, rule *abstractDefaultRule) { + t.Helper() + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + }, + matchedResponse: dnsResponseForTest(netip.MustParseAddr("203.0.113.1")), + unmatchedResponse: dnsResponseForTest(netip.MustParseAddr("8.8.8.8")), + }, + { + name: "ip_is_private", + build: func(t *testing.T, rule *abstractDefaultRule) { + t.Helper() + addDestinationIPIsPrivateItem(rule) + }, + matchedResponse: dnsResponseForTest(netip.MustParseAddr("10.0.0.1")), + unmatchedResponse: dnsResponseForTest(netip.MustParseAddr("8.8.8.8")), + }, + { + name: "ip_accept_any", + build: func(t *testing.T, rule *abstractDefaultRule) { + t.Helper() + addDestinationIPAcceptAnyItem(rule) + }, + matchedResponse: dnsResponseForTest(netip.MustParseAddr("203.0.113.1")), + unmatchedResponse: dnsResponseForTest(), + }, + } + for _, testCase := range testCases { + testCase := testCase + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + rule := dnsRuleForTest(func(rule *abstractDefaultRule) { + testCase.build(t, rule) + }) + + preLookupMetadata := testMetadata("lookup.example") + require.True(t, rule.LegacyPreMatch(&preLookupMetadata)) + + matchedMetadata := testMetadata("lookup.example") + require.True(t, rule.MatchAddressLimit(&matchedMetadata, testCase.matchedResponse)) + + unmatchedMetadata := testMetadata("lookup.example") + require.False(t, rule.MatchAddressLimit(&unmatchedMetadata, testCase.unmatchedResponse)) + }) + } +} + +func TestDNSLegacyAddressLimitPreLookupDefersRuleSetDestinationCIDR(t *testing.T) { + t.Parallel() + + ruleSet := newLocalRuleSetForTest("dns-legacy-ipcidr", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + })) + rule := dnsRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + }) + + preLookupMetadata := testMetadata("lookup.example") + require.True(t, rule.LegacyPreMatch(&preLookupMetadata)) + + matchedMetadata := testMetadata("lookup.example") + require.True(t, rule.MatchAddressLimit(&matchedMetadata, dnsResponseForTest(netip.MustParseAddr("203.0.113.1")))) + + unmatchedMetadata := testMetadata("lookup.example") + require.False(t, rule.MatchAddressLimit(&unmatchedMetadata, dnsResponseForTest(netip.MustParseAddr("8.8.8.8")))) +} + +func TestDNSLegacyLogicalAddressLimitPreLookupDefersNestedRules(t *testing.T) { + t.Parallel() + + nestedRule := dnsRuleForTest(func(rule *abstractDefaultRule) { + addDestinationIPIsPrivateItem(rule) + }) + logicalRule := &LogicalDNSRule{ + abstractLogicalRule: abstractLogicalRule{ + rules: []adapter.HeadlessRule{nestedRule}, + mode: C.LogicalTypeAnd, + }, + } + + preLookupMetadata := testMetadata("lookup.example") + require.True(t, logicalRule.LegacyPreMatch(&preLookupMetadata)) + + matchedMetadata := testMetadata("lookup.example") + require.True(t, logicalRule.MatchAddressLimit(&matchedMetadata, dnsResponseForTest(netip.MustParseAddr("10.0.0.1")))) + + unmatchedMetadata := testMetadata("lookup.example") + require.False(t, logicalRule.MatchAddressLimit(&unmatchedMetadata, dnsResponseForTest(netip.MustParseAddr("8.8.8.8")))) +} + +func TestDNSLegacyInvertAddressLimitPreLookupRegression(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + build func(*testing.T, *abstractDefaultRule) + matchedAddrs []netip.Addr + unmatchedAddrs []netip.Addr + }{ + { + name: "ip_cidr", + build: func(t *testing.T, rule *abstractDefaultRule) { + t.Helper() + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + }, + matchedAddrs: []netip.Addr{netip.MustParseAddr("203.0.113.1")}, + unmatchedAddrs: []netip.Addr{netip.MustParseAddr("8.8.8.8")}, + }, + { + name: "ip_is_private", + build: func(t *testing.T, rule *abstractDefaultRule) { + t.Helper() + addDestinationIPIsPrivateItem(rule) + }, + matchedAddrs: []netip.Addr{netip.MustParseAddr("10.0.0.1")}, + unmatchedAddrs: []netip.Addr{netip.MustParseAddr("8.8.8.8")}, + }, + { + name: "ip_accept_any", + build: func(t *testing.T, rule *abstractDefaultRule) { + t.Helper() + addDestinationIPAcceptAnyItem(rule) + }, + matchedAddrs: []netip.Addr{netip.MustParseAddr("203.0.113.1")}, + }, + } + for _, testCase := range testCases { + testCase := testCase + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + rule := dnsRuleForTest(func(rule *abstractDefaultRule) { + rule.invert = true + testCase.build(t, rule) + }) + + preLookupMetadata := testMetadata("lookup.example") + require.True(t, rule.LegacyPreMatch(&preLookupMetadata)) + + matchedMetadata := testMetadata("lookup.example") + require.False(t, rule.MatchAddressLimit(&matchedMetadata, dnsResponseForTest(testCase.matchedAddrs...))) + + unmatchedMetadata := testMetadata("lookup.example") + require.True(t, rule.MatchAddressLimit(&unmatchedMetadata, dnsResponseForTest(testCase.unmatchedAddrs...))) + }) + } +} + +func TestDNSLegacyInvertLogicalAddressLimitPreLookupRegression(t *testing.T) { + t.Parallel() + + t.Run("inverted deferred child does not suppress branch", func(t *testing.T) { + t.Parallel() + + logicalRule := &LogicalDNSRule{ + abstractLogicalRule: abstractLogicalRule{ + rules: []adapter.HeadlessRule{ + dnsRuleForTest(func(rule *abstractDefaultRule) { + rule.invert = true + addDestinationIPIsPrivateItem(rule) + }), + }, + mode: C.LogicalTypeAnd, + }, + } + + preLookupMetadata := testMetadata("lookup.example") + require.True(t, logicalRule.LegacyPreMatch(&preLookupMetadata)) + }) +} + +func TestDNSLegacyInvertRuleSetAddressLimitPreLookupRegression(t *testing.T) { + t.Parallel() + + ruleSet := newLocalRuleSetForTest("dns-legacy-invert-ipcidr", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + rule.invert = true + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + })) + rule := dnsRuleForTest(func(rule *abstractDefaultRule) { + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + }) + + preLookupMetadata := testMetadata("lookup.example") + require.True(t, rule.LegacyPreMatch(&preLookupMetadata)) + + matchedMetadata := testMetadata("lookup.example") + require.False(t, rule.MatchAddressLimit(&matchedMetadata, dnsResponseForTest(netip.MustParseAddr("203.0.113.1")))) + + unmatchedMetadata := testMetadata("lookup.example") + require.True(t, rule.MatchAddressLimit(&unmatchedMetadata, dnsResponseForTest(netip.MustParseAddr("8.8.8.8")))) +} + +func TestDNSInvertAddressLimitPreLookupRegression(t *testing.T) { + t.Parallel() + testCases := []struct { + name string + build func(*testing.T, *abstractDefaultRule) + matchedAddrs []netip.Addr + unmatchedAddrs []netip.Addr + }{ + { + name: "ip_cidr", + build: func(t *testing.T, rule *abstractDefaultRule) { + t.Helper() + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + }, + matchedAddrs: []netip.Addr{netip.MustParseAddr("203.0.113.1")}, + unmatchedAddrs: []netip.Addr{netip.MustParseAddr("8.8.8.8")}, + }, + { + name: "ip_is_private", + build: func(t *testing.T, rule *abstractDefaultRule) { + t.Helper() + addDestinationIPIsPrivateItem(rule) + }, + matchedAddrs: []netip.Addr{netip.MustParseAddr("10.0.0.1")}, + unmatchedAddrs: []netip.Addr{netip.MustParseAddr("8.8.8.8")}, + }, + { + name: "ip_accept_any", + build: func(t *testing.T, rule *abstractDefaultRule) { + t.Helper() + addDestinationIPAcceptAnyItem(rule) + }, + matchedAddrs: []netip.Addr{netip.MustParseAddr("203.0.113.1")}, + }, + } + for _, testCase := range testCases { + testCase := testCase + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + rule := dnsRuleForTest(func(rule *abstractDefaultRule) { + rule.invert = true + testCase.build(t, rule) + }) + + preLookupMetadata := testMetadata("lookup.example") + require.True(t, rule.Match(&preLookupMetadata)) + + matchedMetadata := testMetadata("lookup.example") + matchedMetadata.DestinationAddresses = testCase.matchedAddrs + require.False(t, rule.MatchAddressLimit(&matchedMetadata, dnsResponseForTest(testCase.matchedAddrs...))) + + unmatchedMetadata := testMetadata("lookup.example") + unmatchedMetadata.DestinationAddresses = testCase.unmatchedAddrs + require.True(t, rule.MatchAddressLimit(&unmatchedMetadata, dnsResponseForTest(testCase.unmatchedAddrs...))) + }) + } + t.Run("mixed resolved and deferred fields invert matches pre lookup", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("lookup.example") + rule := dnsRuleForTest(func(rule *abstractDefaultRule) { + rule.invert = true + addOtherItem(rule, NewNetworkItem([]string{N.NetworkTCP})) + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + }) + require.True(t, rule.Match(&metadata)) + }) + t.Run("ruleset only deferred fields invert matches pre lookup", func(t *testing.T) { + t.Parallel() + metadata := testMetadata("lookup.example") + ruleSet := newLocalRuleSetForTest("dns-ruleset-ipcidr", headlessDefaultRule(t, func(rule *abstractDefaultRule) { + addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"}) + })) + rule := dnsRuleForTest(func(rule *abstractDefaultRule) { + rule.invert = true + addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}}) + }) + require.True(t, rule.Match(&metadata)) + }) +} + +func routeRuleForTest(build func(*abstractDefaultRule)) *DefaultRule { + rule := &DefaultRule{} + build(&rule.abstractDefaultRule) + return rule +} + +func dnsRuleForTest(build func(*abstractDefaultRule)) *DefaultDNSRule { + rule := &DefaultDNSRule{} + build(&rule.abstractDefaultRule) + return rule +} + +func headlessDefaultRule(t *testing.T, build func(*abstractDefaultRule)) *DefaultHeadlessRule { + t.Helper() + rule := &DefaultHeadlessRule{} + build(&rule.abstractDefaultRule) + return rule +} + +func headlessLogicalRule(mode string, invert bool, rules ...adapter.HeadlessRule) *LogicalHeadlessRule { + return &LogicalHeadlessRule{ + abstractLogicalRule: abstractLogicalRule{ + rules: rules, + mode: mode, + invert: invert, + }, + } +} + +func newLocalRuleSetForTest(tag string, rules ...adapter.HeadlessRule) *LocalRuleSet { + return &LocalRuleSet{ + tag: tag, + rules: rules, + } +} + +func newRemoteRuleSetForTest(tag string, rules ...adapter.HeadlessRule) *RemoteRuleSet { + return &RemoteRuleSet{ + options: option.RuleSet{Tag: tag}, + rules: rules, + } +} + +func mustAdGuardRule(t *testing.T, content string) adapter.HeadlessRule { + t.Helper() + rules, err := adguard.ToOptions(strings.NewReader(content), slogger.NOP()) + require.NoError(t, err) + require.Len(t, rules, 1) + rule, err := NewHeadlessRule(context.Background(), rules[0]) + require.NoError(t, err) + return rule +} + +func testMetadata(domain string) adapter.InboundContext { + return adapter.InboundContext{ + Network: N.NetworkTCP, + Source: M.Socksaddr{ + Addr: netip.MustParseAddr("10.0.0.1"), + Port: 1000, + }, + Destination: M.Socksaddr{ + Fqdn: domain, + Port: 443, + }, + } +} + +func dnsResponseForTest(addresses ...netip.Addr) *mDNS.Msg { + response := &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Response: true, + Rcode: mDNS.RcodeSuccess, + }, + } + for _, address := range addresses { + if address.Is4() { + response.Answer = append(response.Answer, &mDNS.A{ + Hdr: mDNS.RR_Header{ + Name: mDNS.Fqdn("lookup.example"), + Rrtype: mDNS.TypeA, + Class: mDNS.ClassINET, + Ttl: 60, + }, + A: net.IP(append([]byte(nil), address.AsSlice()...)), + }) + } else { + response.Answer = append(response.Answer, &mDNS.AAAA{ + Hdr: mDNS.RR_Header{ + Name: mDNS.Fqdn("lookup.example"), + Rrtype: mDNS.TypeAAAA, + Class: mDNS.ClassINET, + Ttl: 60, + }, + AAAA: net.IP(append([]byte(nil), address.AsSlice()...)), + }) + } + } + return response +} + +func addRuleSetItem(rule *abstractDefaultRule, item *RuleSetItem) { + rule.ruleSetItem = item + rule.allItems = append(rule.allItems, item) +} + +func addOtherItem(rule *abstractDefaultRule, item RuleItem) { + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) +} + +func addSourceAddressItem(t *testing.T, rule *abstractDefaultRule, cidrs []string) { + t.Helper() + item, err := NewIPCIDRItem(true, cidrs) + require.NoError(t, err) + rule.sourceAddressItems = append(rule.sourceAddressItems, item) + rule.allItems = append(rule.allItems, item) +} + +func addDestinationAddressItem(t *testing.T, rule *abstractDefaultRule, domains []string, suffixes []string) { + t.Helper() + item, err := NewDomainItem(domains, suffixes) + require.NoError(t, err) + rule.destinationAddressItems = append(rule.destinationAddressItems, item) + rule.allItems = append(rule.allItems, item) +} + +func addDestinationKeywordItem(rule *abstractDefaultRule, keywords []string) { + item := NewDomainKeywordItem(keywords) + rule.destinationAddressItems = append(rule.destinationAddressItems, item) + rule.allItems = append(rule.allItems, item) +} + +func addDestinationRegexItem(t *testing.T, rule *abstractDefaultRule, regexes []string) { + t.Helper() + item, err := NewDomainRegexItem(regexes) + require.NoError(t, err) + rule.destinationAddressItems = append(rule.destinationAddressItems, item) + rule.allItems = append(rule.allItems, item) +} + +func addDestinationIPCIDRItem(t *testing.T, rule *abstractDefaultRule, cidrs []string) { + t.Helper() + item, err := NewIPCIDRItem(false, cidrs) + require.NoError(t, err) + rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) + rule.allItems = append(rule.allItems, item) +} + +func addDestinationIPIsPrivateItem(rule *abstractDefaultRule) { + item := NewIPIsPrivateItem(false) + rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) + rule.allItems = append(rule.allItems, item) +} + +func addDestinationIPAcceptAnyItem(rule *abstractDefaultRule) { + item := NewIPAcceptAnyItem() + rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) + rule.allItems = append(rule.allItems, item) +} + +func addSourcePortItem(rule *abstractDefaultRule, ports []uint16) { + item := NewPortItem(true, ports) + rule.sourcePortItems = append(rule.sourcePortItems, item) + rule.allItems = append(rule.allItems, item) +} + +func addSourcePortRangeItem(t *testing.T, rule *abstractDefaultRule, ranges []string) { + t.Helper() + item, err := NewPortRangeItem(true, ranges) + require.NoError(t, err) + rule.sourcePortItems = append(rule.sourcePortItems, item) + rule.allItems = append(rule.allItems, item) +} + +func addDestinationPortItem(rule *abstractDefaultRule, ports []uint16) { + item := NewPortItem(false, ports) + rule.destinationPortItems = append(rule.destinationPortItems, item) + rule.allItems = append(rule.allItems, item) +} + +func addDestinationPortRangeItem(t *testing.T, rule *abstractDefaultRule, ranges []string) { + t.Helper() + item, err := NewPortRangeItem(false, ranges) + require.NoError(t, err) + rule.destinationPortItems = append(rule.destinationPortItems, item) + rule.allItems = append(rule.allItems, item) +} diff --git a/route/rule/rule_set_update_validation_test.go b/route/rule/rule_set_update_validation_test.go new file mode 100644 index 0000000000..0583d7bb62 --- /dev/null +++ b/route/rule/rule_set_update_validation_test.go @@ -0,0 +1,111 @@ +package rule + +import ( + "context" + "sync/atomic" + "testing" + + "github.com/sagernet/sing-box/adapter" + 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/json/badoption" + "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service" + + "github.com/stretchr/testify/require" +) + +type fakeDNSRuleSetUpdateValidator struct { + validate func(tag string, metadata adapter.RuleSetMetadata) error +} + +func (v *fakeDNSRuleSetUpdateValidator) ValidateRuleSetMetadataUpdate(tag string, metadata adapter.RuleSetMetadata) error { + if v.validate == nil { + return nil + } + return v.validate(tag, metadata) +} + +func TestLocalRuleSetReloadRulesRejectsInvalidUpdateBeforeCommit(t *testing.T) { + t.Parallel() + + var callbackCount atomic.Int32 + ctx := service.ContextWith[adapter.DNSRuleSetUpdateValidator](context.Background(), &fakeDNSRuleSetUpdateValidator{ + validate: func(tag string, metadata adapter.RuleSetMetadata) error { + require.Equal(t, "dynamic-set", tag) + if metadata.ContainsDNSQueryTypeRule { + return E.New("dns conflict") + } + return nil + }, + }) + ruleSet := &LocalRuleSet{ + ctx: ctx, + tag: "dynamic-set", + fileFormat: C.RuleSetFormatSource, + } + _ = ruleSet.callbacks.PushBack(func(adapter.RuleSet) { + callbackCount.Add(1) + }) + + err := ruleSet.reloadRules([]option.HeadlessRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultHeadlessRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + }}) + require.NoError(t, err) + require.Equal(t, int32(1), callbackCount.Load()) + require.False(t, ruleSet.metadata.ContainsDNSQueryTypeRule) + require.True(t, ruleSet.Match(&adapter.InboundContext{Domain: "example.com"})) + + err = ruleSet.reloadRules([]option.HeadlessRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultHeadlessRule{ + QueryType: badoption.Listable[option.DNSQueryType]{option.DNSQueryType(1)}, + }, + }}) + require.ErrorContains(t, err, "dns conflict") + require.Equal(t, int32(1), callbackCount.Load()) + require.False(t, ruleSet.metadata.ContainsDNSQueryTypeRule) + require.True(t, ruleSet.Match(&adapter.InboundContext{Domain: "example.com"})) +} + +func TestRemoteRuleSetLoadBytesRejectsInvalidUpdateBeforeCommit(t *testing.T) { + t.Parallel() + + var callbackCount atomic.Int32 + ctx := service.ContextWith[adapter.DNSRuleSetUpdateValidator](context.Background(), &fakeDNSRuleSetUpdateValidator{ + validate: func(tag string, metadata adapter.RuleSetMetadata) error { + require.Equal(t, "dynamic-set", tag) + if metadata.ContainsDNSQueryTypeRule { + return E.New("dns conflict") + } + return nil + }, + }) + ruleSet := &RemoteRuleSet{ + ctx: ctx, + options: option.RuleSet{ + Tag: "dynamic-set", + Format: C.RuleSetFormatSource, + }, + callbacks: list.List[adapter.RuleSetUpdateCallback]{}, + } + _ = ruleSet.callbacks.PushBack(func(adapter.RuleSet) { + callbackCount.Add(1) + }) + + err := ruleSet.loadBytes([]byte(`{"version":4,"rules":[{"domain":["example.com"]}]}`)) + require.NoError(t, err) + require.Equal(t, int32(1), callbackCount.Load()) + require.False(t, ruleSet.metadata.ContainsDNSQueryTypeRule) + require.True(t, ruleSet.Match(&adapter.InboundContext{Domain: "example.com"})) + + err = ruleSet.loadBytes([]byte(`{"version":4,"rules":[{"query_type":["A"]}]}`)) + require.ErrorContains(t, err, "dns conflict") + require.Equal(t, int32(1), callbackCount.Load()) + require.False(t, ruleSet.metadata.ContainsDNSQueryTypeRule) + require.True(t, ruleSet.Match(&adapter.InboundContext{Domain: "example.com"})) +} diff --git a/route/rule_abstract.go b/route/rule_abstract.go deleted file mode 100644 index c13bdd8d96..0000000000 --- a/route/rule_abstract.go +++ /dev/null @@ -1,242 +0,0 @@ -package route - -import ( - "io" - "strings" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing/common" - F "github.com/sagernet/sing/common/format" -) - -type abstractDefaultRule struct { - items []RuleItem - sourceAddressItems []RuleItem - sourcePortItems []RuleItem - destinationAddressItems []RuleItem - destinationIPCIDRItems []RuleItem - destinationPortItems []RuleItem - allItems []RuleItem - ruleSetItem RuleItem - invert bool - outbound string -} - -func (r *abstractDefaultRule) Type() string { - return C.RuleTypeDefault -} - -func (r *abstractDefaultRule) Start() error { - for _, item := range r.allItems { - err := common.Start(item) - if err != nil { - return err - } - } - return nil -} - -func (r *abstractDefaultRule) Close() error { - for _, item := range r.allItems { - err := common.Close(item) - if err != nil { - return err - } - } - return nil -} - -func (r *abstractDefaultRule) UpdateGeosite() error { - for _, item := range r.allItems { - if geositeItem, isSite := item.(*GeositeItem); isSite { - err := geositeItem.Update() - if err != nil { - return err - } - } - } - return nil -} - -func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool { - if len(r.allItems) == 0 { - return true - } - - if len(r.sourceAddressItems) > 0 && !metadata.SourceAddressMatch { - metadata.DidMatch = true - for _, item := range r.sourceAddressItems { - if item.Match(metadata) { - metadata.SourceAddressMatch = true - break - } - } - } - - if len(r.sourcePortItems) > 0 && !metadata.SourcePortMatch { - metadata.DidMatch = true - for _, item := range r.sourcePortItems { - if item.Match(metadata) { - metadata.SourcePortMatch = true - break - } - } - } - - if len(r.destinationAddressItems) > 0 && !metadata.DestinationAddressMatch { - metadata.DidMatch = true - for _, item := range r.destinationAddressItems { - if item.Match(metadata) { - metadata.DestinationAddressMatch = true - break - } - } - } - - if !metadata.IgnoreDestinationIPCIDRMatch && len(r.destinationIPCIDRItems) > 0 && !metadata.DestinationAddressMatch { - metadata.DidMatch = true - for _, item := range r.destinationIPCIDRItems { - if item.Match(metadata) { - metadata.DestinationAddressMatch = true - break - } - } - } - - if len(r.destinationPortItems) > 0 && !metadata.DestinationPortMatch { - metadata.DidMatch = true - for _, item := range r.destinationPortItems { - if item.Match(metadata) { - metadata.DestinationPortMatch = true - break - } - } - } - - for _, item := range r.items { - if _, isRuleSet := item.(*RuleSetItem); !isRuleSet { - metadata.DidMatch = true - } - if !item.Match(metadata) { - return r.invert - } - } - - if len(r.sourceAddressItems) > 0 && !metadata.SourceAddressMatch { - return r.invert - } - - if len(r.sourcePortItems) > 0 && !metadata.SourcePortMatch { - return r.invert - } - - if ((!metadata.IgnoreDestinationIPCIDRMatch && len(r.destinationIPCIDRItems) > 0) || len(r.destinationAddressItems) > 0) && !metadata.DestinationAddressMatch { - return r.invert - } - - if len(r.destinationPortItems) > 0 && !metadata.DestinationPortMatch { - return r.invert - } - - if !metadata.DidMatch { - return true - } - - return !r.invert -} - -func (r *abstractDefaultRule) Outbound() string { - return r.outbound -} - -func (r *abstractDefaultRule) String() string { - if !r.invert { - return strings.Join(F.MapToString(r.allItems), " ") - } else { - return "!(" + strings.Join(F.MapToString(r.allItems), " ") + ")" - } -} - -type abstractLogicalRule struct { - rules []adapter.HeadlessRule - mode string - invert bool - outbound string -} - -func (r *abstractLogicalRule) Type() string { - return C.RuleTypeLogical -} - -func (r *abstractLogicalRule) UpdateGeosite() error { - for _, rule := range common.FilterIsInstance(r.rules, func(it adapter.HeadlessRule) (adapter.Rule, bool) { - rule, loaded := it.(adapter.Rule) - return rule, loaded - }) { - err := rule.UpdateGeosite() - if err != nil { - return err - } - } - return nil -} - -func (r *abstractLogicalRule) Start() error { - for _, rule := range common.FilterIsInstance(r.rules, func(it adapter.HeadlessRule) (common.Starter, bool) { - rule, loaded := it.(common.Starter) - return rule, loaded - }) { - err := rule.Start() - if err != nil { - return err - } - } - return nil -} - -func (r *abstractLogicalRule) Close() error { - for _, rule := range common.FilterIsInstance(r.rules, func(it adapter.HeadlessRule) (io.Closer, bool) { - rule, loaded := it.(io.Closer) - return rule, loaded - }) { - err := rule.Close() - if err != nil { - return err - } - } - return nil -} - -func (r *abstractLogicalRule) Match(metadata *adapter.InboundContext) bool { - if r.mode == C.LogicalTypeAnd { - return common.All(r.rules, func(it adapter.HeadlessRule) bool { - metadata.ResetRuleCache() - return it.Match(metadata) - }) != r.invert - } else { - return common.Any(r.rules, func(it adapter.HeadlessRule) bool { - metadata.ResetRuleCache() - return it.Match(metadata) - }) != r.invert - } -} - -func (r *abstractLogicalRule) Outbound() string { - return r.outbound -} - -func (r *abstractLogicalRule) String() string { - var op string - switch r.mode { - case C.LogicalTypeAnd: - op = "&&" - case C.LogicalTypeOr: - op = "||" - } - if !r.invert { - return strings.Join(F.MapToString(r.rules), " "+op+" ") - } else { - return "!(" + strings.Join(F.MapToString(r.rules), " "+op+" ") + ")" - } -} diff --git a/route/rule_conds.go b/route/rule_conds.go new file mode 100644 index 0000000000..2c62902949 --- /dev/null +++ b/route/rule_conds.go @@ -0,0 +1,62 @@ +package route + +import ( + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" +) + +func hasRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool { + for _, rule := range rules { + switch rule.Type { + case C.RuleTypeDefault: + if cond(rule.DefaultOptions) { + return true + } + case C.RuleTypeLogical: + if hasRule(rule.LogicalOptions.Rules, cond) { + return true + } + } + } + return false +} + +func hasDNSRule(rules []option.DNSRule, cond func(rule option.DefaultDNSRule) bool) bool { + for _, rule := range rules { + switch rule.Type { + case C.RuleTypeDefault: + if cond(rule.DefaultOptions) { + return true + } + case C.RuleTypeLogical: + if hasDNSRule(rule.LogicalOptions.Rules, cond) { + return true + } + } + } + return false +} + +func isProcessRule(rule option.DefaultRule) bool { + return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.ProcessPathRegex) > 0 || len(rule.PackageName) > 0 || len(rule.PackageNameRegex) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0 +} + +func isProcessDNSRule(rule option.DefaultDNSRule) bool { + return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.ProcessPathRegex) > 0 || len(rule.PackageName) > 0 || len(rule.PackageNameRegex) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0 +} + +func isNeighborRule(rule option.DefaultRule) bool { + return len(rule.SourceMACAddress) > 0 || len(rule.SourceHostname) > 0 +} + +func isNeighborDNSRule(rule option.DefaultDNSRule) bool { + return len(rule.SourceMACAddress) > 0 || len(rule.SourceHostname) > 0 +} + +func isWIFIRule(rule option.DefaultRule) bool { + return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0 +} + +func isWIFIDNSRule(rule option.DefaultDNSRule) bool { + return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0 +} diff --git a/route/rule_dns.go b/route/rule_dns.go deleted file mode 100644 index 955526fc6f..0000000000 --- a/route/rule_dns.go +++ /dev/null @@ -1,361 +0,0 @@ -package route - -import ( - "net/netip" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" -) - -func NewDNSRule(router adapter.Router, logger log.ContextLogger, options option.DNSRule, checkServer bool) (adapter.DNSRule, error) { - switch options.Type { - case "", C.RuleTypeDefault: - if !options.DefaultOptions.IsValid() { - return nil, E.New("missing conditions") - } - if options.DefaultOptions.Server == "" && checkServer { - return nil, E.New("missing server field") - } - return NewDefaultDNSRule(router, logger, options.DefaultOptions) - case C.RuleTypeLogical: - if !options.LogicalOptions.IsValid() { - return nil, E.New("missing conditions") - } - if options.LogicalOptions.Server == "" && checkServer { - return nil, E.New("missing server field") - } - return NewLogicalDNSRule(router, logger, options.LogicalOptions) - default: - return nil, E.New("unknown rule type: ", options.Type) - } -} - -var _ adapter.DNSRule = (*DefaultDNSRule)(nil) - -type DefaultDNSRule struct { - abstractDefaultRule - disableCache bool - rewriteTTL *uint32 - clientSubnet *netip.Prefix -} - -func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) { - rule := &DefaultDNSRule{ - abstractDefaultRule: abstractDefaultRule{ - invert: options.Invert, - outbound: options.Server, - }, - disableCache: options.DisableCache, - rewriteTTL: options.RewriteTTL, - clientSubnet: (*netip.Prefix)(options.ClientSubnet), - } - if len(options.Inbound) > 0 { - item := NewInboundRule(options.Inbound) - rule.items = append(rule.items, item) - rule.allItems = append(rule.allItems, item) - } - if options.IPVersion > 0 { - switch options.IPVersion { - case 4, 6: - item := NewIPVersionItem(options.IPVersion == 6) - rule.items = append(rule.items, item) - rule.allItems = append(rule.allItems, item) - default: - return nil, E.New("invalid ip version: ", options.IPVersion) - } - } - if len(options.QueryType) > 0 { - item := NewQueryTypeItem(options.QueryType) - rule.items = append(rule.items, item) - rule.allItems = append(rule.allItems, item) - } - if len(options.Network) > 0 { - item := NewNetworkItem(options.Network) - rule.items = append(rule.items, item) - rule.allItems = append(rule.allItems, item) - } - if len(options.AuthUser) > 0 { - item := NewAuthUserItem(options.AuthUser) - rule.items = append(rule.items, item) - rule.allItems = append(rule.allItems, item) - } - if len(options.Protocol) > 0 { - item := NewProtocolItem(options.Protocol) - rule.items = append(rule.items, item) - rule.allItems = append(rule.allItems, item) - } - if len(options.Domain) > 0 || len(options.DomainSuffix) > 0 { - item := NewDomainItem(options.Domain, options.DomainSuffix) - rule.destinationAddressItems = append(rule.destinationAddressItems, item) - rule.allItems = append(rule.allItems, item) - } - if len(options.DomainKeyword) > 0 { - item := NewDomainKeywordItem(options.DomainKeyword) - rule.destinationAddressItems = append(rule.destinationAddressItems, item) - rule.allItems = append(rule.allItems, item) - } - if len(options.DomainRegex) > 0 { - item, err := NewDomainRegexItem(options.DomainRegex) - if err != nil { - return nil, E.Cause(err, "domain_regex") - } - rule.destinationAddressItems = append(rule.destinationAddressItems, item) - rule.allItems = append(rule.allItems, item) - } - if len(options.Geosite) > 0 { - item := NewGeositeItem(router, logger, options.Geosite) - rule.destinationAddressItems = append(rule.destinationAddressItems, item) - rule.allItems = append(rule.allItems, item) - } - if len(options.SourceGeoIP) > 0 { - item := NewGeoIPItem(router, logger, true, options.SourceGeoIP) - rule.sourceAddressItems = append(rule.sourceAddressItems, item) - rule.allItems = append(rule.allItems, item) - } - if len(options.GeoIP) > 0 { - item := NewGeoIPItem(router, logger, false, options.GeoIP) - rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) - rule.allItems = append(rule.allItems, item) - } - if len(options.SourceIPCIDR) > 0 { - item, err := NewIPCIDRItem(true, options.SourceIPCIDR) - if err != nil { - return nil, E.Cause(err, "source_ip_cidr") - } - rule.sourceAddressItems = append(rule.sourceAddressItems, item) - rule.allItems = append(rule.allItems, item) - } - if len(options.IPCIDR) > 0 { - item, err := NewIPCIDRItem(false, options.IPCIDR) - if err != nil { - return nil, E.Cause(err, "ip_cidr") - } - rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) - rule.allItems = append(rule.allItems, item) - } - if options.SourceIPIsPrivate { - item := NewIPIsPrivateItem(true) - rule.sourceAddressItems = append(rule.sourceAddressItems, item) - rule.allItems = append(rule.allItems, item) - } - if options.IPIsPrivate { - item := NewIPIsPrivateItem(false) - rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) - rule.allItems = append(rule.allItems, item) - } - if len(options.SourcePort) > 0 { - item := NewPortItem(true, options.SourcePort) - rule.sourcePortItems = append(rule.sourcePortItems, item) - rule.allItems = append(rule.allItems, item) - } - if len(options.SourcePortRange) > 0 { - item, err := NewPortRangeItem(true, options.SourcePortRange) - if err != nil { - return nil, E.Cause(err, "source_port_range") - } - rule.sourcePortItems = append(rule.sourcePortItems, item) - rule.allItems = append(rule.allItems, item) - } - if len(options.Port) > 0 { - item := NewPortItem(false, options.Port) - rule.destinationPortItems = append(rule.destinationPortItems, item) - rule.allItems = append(rule.allItems, item) - } - if len(options.PortRange) > 0 { - item, err := NewPortRangeItem(false, options.PortRange) - if err != nil { - return nil, E.Cause(err, "port_range") - } - rule.destinationPortItems = append(rule.destinationPortItems, item) - rule.allItems = append(rule.allItems, item) - } - if len(options.ProcessName) > 0 { - item := NewProcessItem(options.ProcessName) - rule.items = append(rule.items, item) - rule.allItems = append(rule.allItems, item) - } - if len(options.ProcessPath) > 0 { - item := NewProcessPathItem(options.ProcessPath) - rule.items = append(rule.items, item) - rule.allItems = append(rule.allItems, item) - } - if len(options.PackageName) > 0 { - item := NewPackageNameItem(options.PackageName) - rule.items = append(rule.items, item) - rule.allItems = append(rule.allItems, item) - } - if len(options.User) > 0 { - item := NewUserItem(options.User) - rule.items = append(rule.items, item) - rule.allItems = append(rule.allItems, item) - } - if len(options.UserID) > 0 { - item := NewUserIDItem(options.UserID) - rule.items = append(rule.items, item) - rule.allItems = append(rule.allItems, item) - } - if len(options.Outbound) > 0 { - item := NewOutboundRule(options.Outbound) - rule.items = append(rule.items, item) - rule.allItems = append(rule.allItems, item) - } - if options.ClashMode != "" { - item := NewClashModeItem(router, options.ClashMode) - rule.items = append(rule.items, item) - rule.allItems = append(rule.allItems, item) - } - if len(options.WIFISSID) > 0 { - item := NewWIFISSIDItem(router, options.WIFISSID) - rule.items = append(rule.items, item) - rule.allItems = append(rule.allItems, item) - } - if len(options.WIFIBSSID) > 0 { - item := NewWIFIBSSIDItem(router, options.WIFIBSSID) - rule.items = append(rule.items, item) - rule.allItems = append(rule.allItems, item) - } - if len(options.RuleSet) > 0 { - item := NewRuleSetItem(router, options.RuleSet, options.RuleSetIPCIDRMatchSource) - rule.items = append(rule.items, item) - rule.allItems = append(rule.allItems, item) - } - return rule, nil -} - -func (r *DefaultDNSRule) DisableCache() bool { - return r.disableCache -} - -func (r *DefaultDNSRule) RewriteTTL() *uint32 { - return r.rewriteTTL -} - -func (r *DefaultDNSRule) ClientSubnet() *netip.Prefix { - return r.clientSubnet -} - -func (r *DefaultDNSRule) WithAddressLimit() bool { - if len(r.destinationIPCIDRItems) > 0 { - return true - } - for _, rawRule := range r.items { - ruleSet, isRuleSet := rawRule.(*RuleSetItem) - if !isRuleSet { - continue - } - if ruleSet.ContainsDestinationIPCIDRRule() { - return true - } - } - return false -} - -func (r *DefaultDNSRule) Match(metadata *adapter.InboundContext) bool { - metadata.IgnoreDestinationIPCIDRMatch = true - defer func() { - metadata.IgnoreDestinationIPCIDRMatch = false - }() - return r.abstractDefaultRule.Match(metadata) -} - -func (r *DefaultDNSRule) MatchAddressLimit(metadata *adapter.InboundContext) bool { - return r.abstractDefaultRule.Match(metadata) -} - -var _ adapter.DNSRule = (*LogicalDNSRule)(nil) - -type LogicalDNSRule struct { - abstractLogicalRule - disableCache bool - rewriteTTL *uint32 - clientSubnet *netip.Prefix -} - -func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) { - r := &LogicalDNSRule{ - abstractLogicalRule: abstractLogicalRule{ - rules: make([]adapter.HeadlessRule, len(options.Rules)), - invert: options.Invert, - outbound: options.Server, - }, - disableCache: options.DisableCache, - rewriteTTL: options.RewriteTTL, - clientSubnet: (*netip.Prefix)(options.ClientSubnet), - } - switch options.Mode { - case C.LogicalTypeAnd: - r.mode = C.LogicalTypeAnd - case C.LogicalTypeOr: - r.mode = C.LogicalTypeOr - default: - return nil, E.New("unknown logical mode: ", options.Mode) - } - for i, subRule := range options.Rules { - rule, err := NewDNSRule(router, logger, subRule, false) - if err != nil { - return nil, E.Cause(err, "sub rule[", i, "]") - } - r.rules[i] = rule - } - return r, nil -} - -func (r *LogicalDNSRule) DisableCache() bool { - return r.disableCache -} - -func (r *LogicalDNSRule) RewriteTTL() *uint32 { - return r.rewriteTTL -} - -func (r *LogicalDNSRule) ClientSubnet() *netip.Prefix { - return r.clientSubnet -} - -func (r *LogicalDNSRule) WithAddressLimit() bool { - for _, rawRule := range r.rules { - switch rule := rawRule.(type) { - case *DefaultDNSRule: - if rule.WithAddressLimit() { - return true - } - case *LogicalDNSRule: - if rule.WithAddressLimit() { - return true - } - } - } - return false -} - -func (r *LogicalDNSRule) Match(metadata *adapter.InboundContext) bool { - if r.mode == C.LogicalTypeAnd { - return common.All(r.rules, func(it adapter.HeadlessRule) bool { - metadata.ResetRuleCache() - return it.(adapter.DNSRule).Match(metadata) - }) != r.invert - } else { - return common.Any(r.rules, func(it adapter.HeadlessRule) bool { - metadata.ResetRuleCache() - return it.(adapter.DNSRule).Match(metadata) - }) != r.invert - } -} - -func (r *LogicalDNSRule) MatchAddressLimit(metadata *adapter.InboundContext) bool { - if r.mode == C.LogicalTypeAnd { - return common.All(r.rules, func(it adapter.HeadlessRule) bool { - metadata.ResetRuleCache() - return it.(adapter.DNSRule).MatchAddressLimit(metadata) - }) != r.invert - } else { - return common.Any(r.rules, func(it adapter.HeadlessRule) bool { - metadata.ResetRuleCache() - return it.(adapter.DNSRule).MatchAddressLimit(metadata) - }) != r.invert - } -} diff --git a/route/rule_item_clash_mode.go b/route/rule_item_clash_mode.go deleted file mode 100644 index 70141f1116..0000000000 --- a/route/rule_item_clash_mode.go +++ /dev/null @@ -1,33 +0,0 @@ -package route - -import ( - "strings" - - "github.com/sagernet/sing-box/adapter" -) - -var _ RuleItem = (*ClashModeItem)(nil) - -type ClashModeItem struct { - router adapter.Router - mode string -} - -func NewClashModeItem(router adapter.Router, mode string) *ClashModeItem { - return &ClashModeItem{ - router: router, - mode: mode, - } -} - -func (r *ClashModeItem) Match(metadata *adapter.InboundContext) bool { - clashServer := r.router.ClashServer() - if clashServer == nil { - return false - } - return strings.EqualFold(clashServer.Mode(), r.mode) -} - -func (r *ClashModeItem) String() string { - return "clash_mode=" + r.mode -} diff --git a/route/rule_item_geoip.go b/route/rule_item_geoip.go deleted file mode 100644 index 3611613a48..0000000000 --- a/route/rule_item_geoip.go +++ /dev/null @@ -1,98 +0,0 @@ -package route - -import ( - "net/netip" - "strings" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/log" - N "github.com/sagernet/sing/common/network" -) - -var _ RuleItem = (*GeoIPItem)(nil) - -type GeoIPItem struct { - router adapter.Router - logger log.ContextLogger - isSource bool - codes []string - codeMap map[string]bool -} - -func NewGeoIPItem(router adapter.Router, logger log.ContextLogger, isSource bool, codes []string) *GeoIPItem { - codeMap := make(map[string]bool) - for _, code := range codes { - codeMap[code] = true - } - return &GeoIPItem{ - router: router, - logger: logger, - codes: codes, - isSource: isSource, - codeMap: codeMap, - } -} - -func (r *GeoIPItem) Match(metadata *adapter.InboundContext) bool { - var geoipCode string - if r.isSource && metadata.SourceGeoIPCode != "" { - geoipCode = metadata.SourceGeoIPCode - } else if !r.isSource && metadata.GeoIPCode != "" { - geoipCode = metadata.GeoIPCode - } - if geoipCode != "" { - return r.codeMap[geoipCode] - } - var destination netip.Addr - if r.isSource { - destination = metadata.Source.Addr - } else { - destination = metadata.Destination.Addr - } - if destination.IsValid() { - return r.match(metadata, destination) - } - for _, destinationAddress := range metadata.DestinationAddresses { - if r.match(metadata, destinationAddress) { - return true - } - } - return false -} - -func (r *GeoIPItem) match(metadata *adapter.InboundContext, destination netip.Addr) bool { - var geoipCode string - geoReader := r.router.GeoIPReader() - if !N.IsPublicAddr(destination) { - geoipCode = "private" - } else if geoReader != nil { - geoipCode = geoReader.Lookup(destination) - } - if geoipCode == "" { - return false - } - if r.isSource { - metadata.SourceGeoIPCode = geoipCode - } else { - metadata.GeoIPCode = geoipCode - } - return r.codeMap[geoipCode] -} - -func (r *GeoIPItem) String() string { - var description string - if r.isSource { - description = "source_geoip=" - } else { - description = "geoip=" - } - cLen := len(r.codes) - if cLen == 1 { - description += r.codes[0] - } else if cLen > 3 { - description += "[" + strings.Join(r.codes[:3], " ") + "...]" - } else { - description += "[" + strings.Join(r.codes, " ") + "]" - } - return description -} diff --git a/route/rule_item_geosite.go b/route/rule_item_geosite.go deleted file mode 100644 index 5fdbfe5962..0000000000 --- a/route/rule_item_geosite.go +++ /dev/null @@ -1,61 +0,0 @@ -package route - -import ( - "strings" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/log" - E "github.com/sagernet/sing/common/exceptions" -) - -var _ RuleItem = (*GeositeItem)(nil) - -type GeositeItem struct { - router adapter.Router - logger log.ContextLogger - codes []string - matchers []adapter.Rule -} - -func NewGeositeItem(router adapter.Router, logger log.ContextLogger, codes []string) *GeositeItem { - return &GeositeItem{ - router: router, - logger: logger, - codes: codes, - } -} - -func (r *GeositeItem) Update() error { - matchers := make([]adapter.Rule, 0, len(r.codes)) - for _, code := range r.codes { - matcher, err := r.router.LoadGeosite(code) - if err != nil { - return E.Cause(err, "read geosite") - } - matchers = append(matchers, matcher) - } - r.matchers = matchers - return nil -} - -func (r *GeositeItem) Match(metadata *adapter.InboundContext) bool { - for _, matcher := range r.matchers { - if matcher.Match(metadata) { - return true - } - } - return false -} - -func (r *GeositeItem) String() string { - description := "geosite=" - cLen := len(r.codes) - if cLen == 1 { - description += r.codes[0] - } else if cLen > 3 { - description += "[" + strings.Join(r.codes[:3], " ") + "...]" - } else { - description += "[" + strings.Join(r.codes, " ") + "]" - } - return description -} diff --git a/route/rule_set.go b/route/rule_set.go deleted file mode 100644 index f644fb406f..0000000000 --- a/route/rule_set.go +++ /dev/null @@ -1,67 +0,0 @@ -package route - -import ( - "context" - "net" - "net/http" - "sync" - - "github.com/sagernet/sing-box/adapter" - 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" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -func NewRuleSet(ctx context.Context, router adapter.Router, logger logger.ContextLogger, options option.RuleSet) (adapter.RuleSet, error) { - switch options.Type { - case C.RuleSetTypeLocal: - return NewLocalRuleSet(router, options) - case C.RuleSetTypeRemote: - return NewRemoteRuleSet(ctx, router, logger, options), nil - default: - return nil, E.New("unknown rule set type: ", options.Type) - } -} - -var _ adapter.RuleSetStartContext = (*RuleSetStartContext)(nil) - -type RuleSetStartContext struct { - access sync.Mutex - httpClientCache map[string]*http.Client -} - -func NewRuleSetStartContext() *RuleSetStartContext { - return &RuleSetStartContext{ - httpClientCache: make(map[string]*http.Client), - } -} - -func (c *RuleSetStartContext) HTTPClient(detour string, dialer N.Dialer) *http.Client { - c.access.Lock() - defer c.access.Unlock() - if httpClient, loaded := c.httpClientCache[detour]; loaded { - return httpClient - } - httpClient := &http.Client{ - Transport: &http.Transport{ - ForceAttemptHTTP2: true, - TLSHandshakeTimeout: C.TCPTimeout, - DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) - }, - }, - } - c.httpClientCache[detour] = httpClient - return httpClient -} - -func (c *RuleSetStartContext) Close() { - c.access.Lock() - defer c.access.Unlock() - for _, client := range c.httpClientCache { - client.CloseIdleConnections() - } -} diff --git a/route/rule_set_local.go b/route/rule_set_local.go deleted file mode 100644 index 3945826708..0000000000 --- a/route/rule_set_local.go +++ /dev/null @@ -1,87 +0,0 @@ -package route - -import ( - "context" - "os" - "strings" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/srs" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" - "github.com/sagernet/sing/common/json" -) - -var _ adapter.RuleSet = (*LocalRuleSet)(nil) - -type LocalRuleSet struct { - rules []adapter.HeadlessRule - metadata adapter.RuleSetMetadata -} - -func NewLocalRuleSet(router adapter.Router, options option.RuleSet) (*LocalRuleSet, error) { - var plainRuleSet option.PlainRuleSet - switch options.Format { - case C.RuleSetFormatSource, "": - content, err := os.ReadFile(options.LocalOptions.Path) - if err != nil { - return nil, err - } - compat, err := json.UnmarshalExtended[option.PlainRuleSetCompat](content) - if err != nil { - return nil, err - } - plainRuleSet = compat.Upgrade() - case C.RuleSetFormatBinary: - setFile, err := os.Open(options.LocalOptions.Path) - if err != nil { - return nil, err - } - plainRuleSet, err = srs.Read(setFile, false) - if err != nil { - return nil, err - } - default: - return nil, E.New("unknown rule set format: ", options.Format) - } - rules := make([]adapter.HeadlessRule, len(plainRuleSet.Rules)) - var err error - for i, ruleOptions := range plainRuleSet.Rules { - rules[i], err = NewHeadlessRule(router, ruleOptions) - if err != nil { - return nil, E.Cause(err, "parse rule_set.rules.[", i, "]") - } - } - var metadata adapter.RuleSetMetadata - metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule) - metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule) - metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule) - return &LocalRuleSet{rules, metadata}, nil -} - -func (s *LocalRuleSet) Match(metadata *adapter.InboundContext) bool { - for _, rule := range s.rules { - if rule.Match(metadata) { - return true - } - } - return false -} - -func (s *LocalRuleSet) String() string { - return strings.Join(F.MapToString(s.rules), " ") -} - -func (s *LocalRuleSet) StartContext(ctx context.Context, startContext adapter.RuleSetStartContext) error { - return nil -} - -func (s *LocalRuleSet) Metadata() adapter.RuleSetMetadata { - return s.metadata -} - -func (s *LocalRuleSet) Close() error { - return nil -} diff --git a/route/rule_set_remote.go b/route/rule_set_remote.go deleted file mode 100644 index 8389c2f46b..0000000000 --- a/route/rule_set_remote.go +++ /dev/null @@ -1,259 +0,0 @@ -package route - -import ( - "bytes" - "context" - "io" - "net" - "net/http" - "runtime" - "strings" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/srs" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" - "github.com/sagernet/sing/common/json" - "github.com/sagernet/sing/common/logger" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/service" - "github.com/sagernet/sing/service/pause" -) - -var _ adapter.RuleSet = (*RemoteRuleSet)(nil) - -type RemoteRuleSet struct { - ctx context.Context - cancel context.CancelFunc - router adapter.Router - logger logger.ContextLogger - options option.RuleSet - metadata adapter.RuleSetMetadata - updateInterval time.Duration - dialer N.Dialer - rules []adapter.HeadlessRule - lastUpdated time.Time - lastEtag string - updateTicker *time.Ticker - pauseManager pause.Manager -} - -func NewRemoteRuleSet(ctx context.Context, router adapter.Router, logger logger.ContextLogger, options option.RuleSet) *RemoteRuleSet { - ctx, cancel := context.WithCancel(ctx) - var updateInterval time.Duration - if options.RemoteOptions.UpdateInterval > 0 { - updateInterval = time.Duration(options.RemoteOptions.UpdateInterval) - } else { - updateInterval = 24 * time.Hour - } - return &RemoteRuleSet{ - ctx: ctx, - cancel: cancel, - router: router, - logger: logger, - options: options, - updateInterval: updateInterval, - pauseManager: service.FromContext[pause.Manager](ctx), - } -} - -func (s *RemoteRuleSet) Match(metadata *adapter.InboundContext) bool { - for _, rule := range s.rules { - if rule.Match(metadata) { - return true - } - } - return false -} - -func (s *RemoteRuleSet) String() string { - return strings.Join(F.MapToString(s.rules), " ") -} - -func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext adapter.RuleSetStartContext) error { - var dialer N.Dialer - if s.options.RemoteOptions.DownloadDetour != "" { - outbound, loaded := s.router.Outbound(s.options.RemoteOptions.DownloadDetour) - if !loaded { - return E.New("download_detour not found: ", s.options.RemoteOptions.DownloadDetour) - } - dialer = outbound - } else { - outbound, err := s.router.DefaultOutbound(N.NetworkTCP) - if err != nil { - return err - } - dialer = outbound - } - s.dialer = dialer - cacheFile := service.FromContext[adapter.CacheFile](s.ctx) - if cacheFile != nil { - if savedSet := cacheFile.LoadRuleSet(s.options.Tag); savedSet != nil { - err := s.loadBytes(savedSet.Content) - if err != nil { - return E.Cause(err, "restore cached rule-set") - } - s.lastUpdated = savedSet.LastUpdated - s.lastEtag = savedSet.LastEtag - } - } - if s.lastUpdated.IsZero() { - err := s.fetchOnce(ctx, startContext) - if err != nil { - return E.Cause(err, "initial rule-set: ", s.options.Tag) - } - } - s.updateTicker = time.NewTicker(s.updateInterval) - go s.loopUpdate() - return nil -} - -func (s *RemoteRuleSet) Metadata() adapter.RuleSetMetadata { - return s.metadata -} - -func (s *RemoteRuleSet) loadBytes(content []byte) error { - var ( - plainRuleSet option.PlainRuleSet - err error - ) - switch s.options.Format { - case C.RuleSetFormatSource: - var compat option.PlainRuleSetCompat - compat, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content) - if err != nil { - return err - } - plainRuleSet = compat.Upgrade() - case C.RuleSetFormatBinary: - plainRuleSet, err = srs.Read(bytes.NewReader(content), false) - if err != nil { - return err - } - default: - return E.New("unknown rule set format: ", s.options.Format) - } - rules := make([]adapter.HeadlessRule, len(plainRuleSet.Rules)) - for i, ruleOptions := range plainRuleSet.Rules { - rules[i], err = NewHeadlessRule(s.router, ruleOptions) - if err != nil { - return E.Cause(err, "parse rule_set.rules.[", i, "]") - } - } - s.metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule) - s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule) - s.metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule) - s.rules = rules - return nil -} - -func (s *RemoteRuleSet) loopUpdate() { - if time.Since(s.lastUpdated) > s.updateInterval { - err := s.fetchOnce(s.ctx, nil) - if err != nil { - s.logger.Error("fetch rule-set ", s.options.Tag, ": ", err) - } - } - for { - runtime.GC() - select { - case <-s.ctx.Done(): - return - case <-s.updateTicker.C: - s.pauseManager.WaitActive() - err := s.fetchOnce(s.ctx, nil) - if err != nil { - s.logger.Error("fetch rule-set ", s.options.Tag, ": ", err) - } - } - } -} - -func (s *RemoteRuleSet) fetchOnce(ctx context.Context, startContext adapter.RuleSetStartContext) error { - s.logger.Debug("updating rule-set ", s.options.Tag, " from URL: ", s.options.RemoteOptions.URL) - var httpClient *http.Client - if startContext != nil { - httpClient = startContext.HTTPClient(s.options.RemoteOptions.DownloadDetour, s.dialer) - } else { - httpClient = &http.Client{ - Transport: &http.Transport{ - ForceAttemptHTTP2: true, - TLSHandshakeTimeout: C.TCPTimeout, - DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - return s.dialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) - }, - }, - } - } - request, err := http.NewRequest("GET", s.options.RemoteOptions.URL, nil) - if err != nil { - return err - } - if s.lastEtag != "" { - request.Header.Set("If-None-Match", s.lastEtag) - } - response, err := httpClient.Do(request.WithContext(ctx)) - if err != nil { - return err - } - switch response.StatusCode { - case http.StatusOK: - case http.StatusNotModified: - s.lastUpdated = time.Now() - cacheFile := service.FromContext[adapter.CacheFile](s.ctx) - if cacheFile != nil { - savedRuleSet := cacheFile.LoadRuleSet(s.options.Tag) - if savedRuleSet != nil { - savedRuleSet.LastUpdated = s.lastUpdated - err = cacheFile.SaveRuleSet(s.options.Tag, savedRuleSet) - if err != nil { - s.logger.Error("save rule-set updated time: ", err) - return nil - } - } - } - s.logger.Info("update rule-set ", s.options.Tag, ": not modified") - return nil - default: - return E.New("unexpected status: ", response.Status) - } - content, err := io.ReadAll(response.Body) - if err != nil { - response.Body.Close() - return err - } - err = s.loadBytes(content) - if err != nil { - response.Body.Close() - return err - } - response.Body.Close() - eTagHeader := response.Header.Get("Etag") - if eTagHeader != "" { - s.lastEtag = eTagHeader - } - s.lastUpdated = time.Now() - cacheFile := service.FromContext[adapter.CacheFile](s.ctx) - if cacheFile != nil { - err = cacheFile.SaveRuleSet(s.options.Tag, &adapter.SavedRuleSet{ - LastUpdated: s.lastUpdated, - Content: content, - LastEtag: s.lastEtag, - }) - if err != nil { - s.logger.Error("save rule-set cache: ", err) - } - } - s.logger.Info("updated rule-set ", s.options.Tag) - return nil -} - -func (s *RemoteRuleSet) Close() error { - s.updateTicker.Stop() - s.cancel() - return nil -} diff --git a/service/acme/service.go b/service/acme/service.go new file mode 100644 index 0000000000..1b96770997 --- /dev/null +++ b/service/acme/service.go @@ -0,0 +1,399 @@ +//go:build with_acme + +package acme + +import ( + "bytes" + "context" + "crypto/tls" + "encoding/json" + "net/http" + "net/url" + "reflect" + "slices" + "strings" + "time" + "unsafe" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/certificate" + boxtls "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/service" + + "github.com/caddyserver/certmagic" + "github.com/caddyserver/zerossl" + "github.com/libdns/alidns" + "github.com/libdns/cloudflare" + "github.com/libdns/libdns" + "github.com/mholt/acmez/v3/acme" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +func RegisterCertificateProvider(registry *certificate.Registry) { + certificate.Register[option.ACMECertificateProviderOptions](registry, C.TypeACME, NewCertificateProvider) +} + +var ( + _ adapter.CertificateProviderService = (*Service)(nil) + _ adapter.ACMECertificateProvider = (*Service)(nil) +) + +type Service struct { + certificate.Adapter + ctx context.Context + config *certmagic.Config + cache *certmagic.Cache + domain []string + nextProtos []string +} + +func NewCertificateProvider(ctx context.Context, logger log.ContextLogger, tag string, options option.ACMECertificateProviderOptions) (adapter.CertificateProviderService, error) { + if len(options.Domain) == 0 { + return nil, E.New("missing domain") + } + var acmeServer string + switch options.Provider { + case "", "letsencrypt": + acmeServer = certmagic.LetsEncryptProductionCA + case "zerossl": + acmeServer = certmagic.ZeroSSLProductionCA + default: + if !strings.HasPrefix(options.Provider, "https://") { + return nil, E.New("unsupported ACME provider: ", options.Provider) + } + acmeServer = options.Provider + } + if acmeServer == certmagic.ZeroSSLProductionCA && + (options.ExternalAccount == nil || options.ExternalAccount.KeyID == "") && + strings.TrimSpace(options.Email) == "" && + strings.TrimSpace(options.AccountKey) == "" { + return nil, E.New("email is required to use the ZeroSSL ACME endpoint without external_account or account_key") + } + + var storage certmagic.Storage + if options.DataDirectory != "" { + storage = &certmagic.FileStorage{Path: options.DataDirectory} + } else { + storage = certmagic.Default.Storage + } + + zapLogger := zap.New(zapcore.NewCore( + zapcore.NewConsoleEncoder(boxtls.ACMEEncoderConfig()), + &boxtls.ACMELogWriter{Logger: logger}, + zap.DebugLevel, + )) + + config := &certmagic.Config{ + DefaultServerName: options.DefaultServerName, + Storage: storage, + Logger: zapLogger, + } + if options.KeyType != "" { + var keyType certmagic.KeyType + switch options.KeyType { + case option.ACMEKeyTypeED25519: + keyType = certmagic.ED25519 + case option.ACMEKeyTypeP256: + keyType = certmagic.P256 + case option.ACMEKeyTypeP384: + keyType = certmagic.P384 + case option.ACMEKeyTypeRSA2048: + keyType = certmagic.RSA2048 + case option.ACMEKeyTypeRSA4096: + keyType = certmagic.RSA4096 + default: + return nil, E.New("unsupported ACME key type: ", options.KeyType) + } + config.KeySource = certmagic.StandardKeyGenerator{KeyType: keyType} + } + + profile := options.Profile + if profile == "" && acmeServer == certmagic.LetsEncryptProductionCA && slices.ContainsFunc(options.Domain, certmagic.SubjectIsIP) { + profile = "shortlived" + } + + acmeIssuer := certmagic.ACMEIssuer{ + CA: acmeServer, + Email: options.Email, + AccountKeyPEM: options.AccountKey, + Agreed: true, + Profile: profile, + DisableHTTPChallenge: options.DisableHTTPChallenge, + DisableTLSALPNChallenge: options.DisableTLSALPNChallenge, + AltHTTPPort: int(options.AlternativeHTTPPort), + AltTLSALPNPort: int(options.AlternativeTLSPort), + Logger: zapLogger, + } + acmeHTTPClient, err := newACMEHTTPClient(ctx, logger, options) + if err != nil { + return nil, err + } + dnsSolver, err := newDNSSolver(options.DNS01Challenge, zapLogger, acmeHTTPClient) + if err != nil { + return nil, err + } + if dnsSolver != nil { + acmeIssuer.DNS01Solver = dnsSolver + } + if options.ExternalAccount != nil && options.ExternalAccount.KeyID != "" { + acmeIssuer.ExternalAccount = (*acme.EAB)(options.ExternalAccount) + } + if acmeServer == certmagic.ZeroSSLProductionCA { + acmeIssuer.NewAccountFunc = func(ctx context.Context, acmeIssuer *certmagic.ACMEIssuer, account acme.Account) (acme.Account, error) { + if acmeIssuer.ExternalAccount != nil { + return account, nil + } + var err error + acmeIssuer.ExternalAccount, account, err = createZeroSSLExternalAccountBinding(ctx, acmeIssuer, account, acmeHTTPClient) + return account, err + } + } + + certmagicIssuer := certmagic.NewACMEIssuer(config, acmeIssuer) + httpClientField := reflect.ValueOf(certmagicIssuer).Elem().FieldByName("httpClient") + if !httpClientField.IsValid() || !httpClientField.CanAddr() { + return nil, E.New("certmagic ACME issuer HTTP client field is unavailable") + } + reflect.NewAt(httpClientField.Type(), unsafe.Pointer(httpClientField.UnsafeAddr())).Elem().Set(reflect.ValueOf(acmeHTTPClient)) + config.Issuers = []certmagic.Issuer{certmagicIssuer} + cache := certmagic.NewCache(certmagic.CacheOptions{ + GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) { + return config, nil + }, + Logger: zapLogger, + }) + config = certmagic.New(cache, *config) + + var nextProtos []string + if !acmeIssuer.DisableTLSALPNChallenge && acmeIssuer.DNS01Solver == nil { + nextProtos = []string{C.ACMETLS1Protocol} + } + return &Service{ + Adapter: certificate.NewAdapter(C.TypeACME, tag), + ctx: ctx, + config: config, + cache: cache, + domain: options.Domain, + nextProtos: nextProtos, + }, nil +} + +func (s *Service) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + return s.config.ManageAsync(s.ctx, s.domain) +} + +func (s *Service) Close() error { + if s.cache != nil { + s.cache.Stop() + } + return nil +} + +func (s *Service) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { + return s.config.GetCertificate(hello) +} + +func (s *Service) GetACMENextProtos() []string { + return s.nextProtos +} + +func newDNSSolver(dnsOptions *option.ACMEProviderDNS01ChallengeOptions, logger *zap.Logger, httpClient *http.Client) (*certmagic.DNS01Solver, error) { + if dnsOptions == nil || dnsOptions.Provider == "" { + return nil, nil + } + if dnsOptions.TTL < 0 { + return nil, E.New("invalid ACME DNS01 ttl: ", dnsOptions.TTL) + } + if dnsOptions.PropagationDelay < 0 { + return nil, E.New("invalid ACME DNS01 propagation_delay: ", dnsOptions.PropagationDelay) + } + if dnsOptions.PropagationTimeout < -1 { + return nil, E.New("invalid ACME DNS01 propagation_timeout: ", dnsOptions.PropagationTimeout) + } + solver := &certmagic.DNS01Solver{ + DNSManager: certmagic.DNSManager{ + TTL: time.Duration(dnsOptions.TTL), + PropagationDelay: time.Duration(dnsOptions.PropagationDelay), + PropagationTimeout: time.Duration(dnsOptions.PropagationTimeout), + Resolvers: dnsOptions.Resolvers, + OverrideDomain: dnsOptions.OverrideDomain, + Logger: logger.Named("dns_manager"), + }, + } + switch dnsOptions.Provider { + case C.DNSProviderAliDNS: + solver.DNSProvider = &alidns.Provider{ + CredentialInfo: alidns.CredentialInfo{ + AccessKeyID: dnsOptions.AliDNSOptions.AccessKeyID, + AccessKeySecret: dnsOptions.AliDNSOptions.AccessKeySecret, + RegionID: dnsOptions.AliDNSOptions.RegionID, + SecurityToken: dnsOptions.AliDNSOptions.SecurityToken, + }, + } + case C.DNSProviderCloudflare: + solver.DNSProvider = &cloudflare.Provider{ + APIToken: dnsOptions.CloudflareOptions.APIToken, + ZoneToken: dnsOptions.CloudflareOptions.ZoneToken, + HTTPClient: httpClient, + } + case C.DNSProviderACMEDNS: + solver.DNSProvider = &acmeDNSProvider{ + username: dnsOptions.ACMEDNSOptions.Username, + password: dnsOptions.ACMEDNSOptions.Password, + subdomain: dnsOptions.ACMEDNSOptions.Subdomain, + serverURL: dnsOptions.ACMEDNSOptions.ServerURL, + httpClient: httpClient, + } + default: + return nil, E.New("unsupported ACME DNS01 provider type: ", dnsOptions.Provider) + } + return solver, nil +} + +func createZeroSSLExternalAccountBinding(ctx context.Context, acmeIssuer *certmagic.ACMEIssuer, account acme.Account, httpClient *http.Client) (*acme.EAB, acme.Account, error) { + email := strings.TrimSpace(acmeIssuer.Email) + if email == "" { + return nil, acme.Account{}, E.New("email is required to use the ZeroSSL ACME endpoint without external_account") + } + if len(account.Contact) == 0 { + account.Contact = []string{"mailto:" + email} + } + if acmeIssuer.CertObtainTimeout > 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, acmeIssuer.CertObtainTimeout) + defer cancel() + } + + form := url.Values{"email": []string{email}} + request, err := http.NewRequestWithContext(ctx, http.MethodPost, zerossl.BaseURL+"/acme/eab-credentials-email", strings.NewReader(form.Encode())) + if err != nil { + return nil, account, E.Cause(err, "create ZeroSSL EAB request") + } + request.Header.Set("Content-Type", "application/x-www-form-urlencoded") + request.Header.Set("User-Agent", certmagic.UserAgent) + + response, err := httpClient.Do(request) + if err != nil { + return nil, account, E.Cause(err, "request ZeroSSL EAB") + } + defer response.Body.Close() + + var result struct { + Success bool `json:"success"` + Error struct { + Code int `json:"code"` + Type string `json:"type"` + } `json:"error"` + EABKID string `json:"eab_kid"` + EABHMACKey string `json:"eab_hmac_key"` + } + err = json.NewDecoder(response.Body).Decode(&result) + if err != nil { + return nil, account, E.Cause(err, "decode ZeroSSL EAB response") + } + if response.StatusCode != http.StatusOK { + return nil, account, E.New("failed getting ZeroSSL EAB credentials: HTTP ", response.StatusCode) + } + if result.Error.Code != 0 { + return nil, account, E.New("failed getting ZeroSSL EAB credentials: ", result.Error.Type, " (code ", result.Error.Code, ")") + } + + acmeIssuer.Logger.Info("generated ZeroSSL EAB credentials", zap.String("key_id", result.EABKID)) + + return &acme.EAB{ + KeyID: result.EABKID, + MACKey: result.EABHMACKey, + }, account, nil +} + +func newACMEHTTPClient(ctx context.Context, logger log.ContextLogger, options option.ACMECertificateProviderOptions) (*http.Client, error) { + httpClientOptions := common.PtrValueOrDefault(options.HTTPClient) + httpClientManager := service.FromContext[adapter.HTTPClientManager](ctx) + transport, err := httpClientManager.ResolveTransport(ctx, logger, httpClientOptions) + if err != nil { + return nil, E.Cause(err, "create ACME provider http client") + } + return &http.Client{ + Transport: transport, + Timeout: certmagic.HTTPTimeout, + }, nil +} + +type acmeDNSProvider struct { + username string + password string + subdomain string + serverURL string + httpClient *http.Client +} + +type acmeDNSRecord struct { + resourceRecord libdns.RR +} + +func (r acmeDNSRecord) RR() libdns.RR { + return r.resourceRecord +} + +func (p *acmeDNSProvider) AppendRecords(ctx context.Context, _ string, records []libdns.Record) ([]libdns.Record, error) { + if p.username == "" { + return nil, E.New("ACME-DNS username cannot be empty") + } + if p.password == "" { + return nil, E.New("ACME-DNS password cannot be empty") + } + if p.subdomain == "" { + return nil, E.New("ACME-DNS subdomain cannot be empty") + } + if p.serverURL == "" { + return nil, E.New("ACME-DNS server_url cannot be empty") + } + appendedRecords := make([]libdns.Record, 0, len(records)) + for _, record := range records { + resourceRecord := record.RR() + if resourceRecord.Type != "TXT" { + return appendedRecords, E.New("ACME-DNS only supports adding TXT records") + } + requestBody, err := json.Marshal(map[string]string{ + "subdomain": p.subdomain, + "txt": resourceRecord.Data, + }) + if err != nil { + return appendedRecords, E.Cause(err, "marshal ACME-DNS update request") + } + request, err := http.NewRequestWithContext(ctx, http.MethodPost, p.serverURL+"/update", bytes.NewReader(requestBody)) + if err != nil { + return appendedRecords, E.Cause(err, "create ACME-DNS update request") + } + request.Header.Set("X-Api-User", p.username) + request.Header.Set("X-Api-Key", p.password) + request.Header.Set("Content-Type", "application/json") + response, err := p.httpClient.Do(request) + if err != nil { + return appendedRecords, E.Cause(err, "update ACME-DNS record") + } + _ = response.Body.Close() + if response.StatusCode != http.StatusOK { + return appendedRecords, E.New("update ACME-DNS record: HTTP ", response.StatusCode) + } + appendedRecords = append(appendedRecords, acmeDNSRecord{resourceRecord: libdns.RR{ + Type: "TXT", + Name: resourceRecord.Name, + Data: resourceRecord.Data, + }}) + } + return appendedRecords, nil +} + +func (p *acmeDNSProvider) DeleteRecords(context.Context, string, []libdns.Record) ([]libdns.Record, error) { + return nil, nil +} diff --git a/service/acme/stub.go b/service/acme/stub.go new file mode 100644 index 0000000000..43a58d6449 --- /dev/null +++ b/service/acme/stub.go @@ -0,0 +1,3 @@ +//go:build !with_acme + +package acme diff --git a/service/ccm/credential.go b/service/ccm/credential.go new file mode 100644 index 0000000000..695efc7ae3 --- /dev/null +++ b/service/ccm/credential.go @@ -0,0 +1,139 @@ +package ccm + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "os" + "os/user" + "path/filepath" + "time" + + E "github.com/sagernet/sing/common/exceptions" +) + +const ( + oauth2ClientID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e" + oauth2TokenURL = "https://console.anthropic.com/v1/oauth/token" + claudeAPIBaseURL = "https://api.anthropic.com" + tokenRefreshBufferMs = 60000 + anthropicBetaOAuthValue = "oauth-2025-04-20" +) + +func getRealUser() (*user.User, error) { + if sudoUser := os.Getenv("SUDO_USER"); sudoUser != "" { + sudoUserInfo, err := user.Lookup(sudoUser) + if err == nil { + return sudoUserInfo, nil + } + } + return user.Current() +} + +func getDefaultCredentialsPath() (string, error) { + if configDir := os.Getenv("CLAUDE_CONFIG_DIR"); configDir != "" { + return filepath.Join(configDir, ".credentials.json"), nil + } + userInfo, err := getRealUser() + if err != nil { + return "", err + } + return filepath.Join(userInfo.HomeDir, ".claude", ".credentials.json"), nil +} + +func readCredentialsFromFile(path string) (*oauthCredentials, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + var credentialsContainer struct { + ClaudeAIAuth *oauthCredentials `json:"claudeAiOauth,omitempty"` + } + err = json.Unmarshal(data, &credentialsContainer) + if err != nil { + return nil, err + } + if credentialsContainer.ClaudeAIAuth == nil { + return nil, E.New("claudeAiOauth field not found in credentials") + } + return credentialsContainer.ClaudeAIAuth, nil +} + +func writeCredentialsToFile(oauthCredentials *oauthCredentials, path string) error { + data, err := json.MarshalIndent(map[string]any{ + "claudeAiOauth": oauthCredentials, + }, "", " ") + if err != nil { + return err + } + return os.WriteFile(path, data, 0o600) +} + +type oauthCredentials struct { + AccessToken string `json:"accessToken"` + RefreshToken string `json:"refreshToken"` + ExpiresAt int64 `json:"expiresAt"` + Scopes []string `json:"scopes,omitempty"` + SubscriptionType string `json:"subscriptionType,omitempty"` + IsMax bool `json:"isMax,omitempty"` +} + +func (c *oauthCredentials) needsRefresh() bool { + if c.ExpiresAt == 0 { + return false + } + return time.Now().UnixMilli() >= c.ExpiresAt-tokenRefreshBufferMs +} + +func refreshToken(httpClient *http.Client, credentials *oauthCredentials) (*oauthCredentials, error) { + if credentials.RefreshToken == "" { + return nil, E.New("refresh token is empty") + } + + requestBody, err := json.Marshal(map[string]string{ + "grant_type": "refresh_token", + "refresh_token": credentials.RefreshToken, + "client_id": oauth2ClientID, + }) + if err != nil { + return nil, E.Cause(err, "marshal request") + } + + request, err := http.NewRequest("POST", oauth2TokenURL, bytes.NewReader(requestBody)) + if err != nil { + return nil, err + } + request.Header.Set("Content-Type", "application/json") + request.Header.Set("Accept", "application/json") + + response, err := httpClient.Do(request) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + body, _ := io.ReadAll(response.Body) + return nil, E.New("refresh failed: ", response.Status, " ", string(body)) + } + + var tokenResponse struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + ExpiresIn int `json:"expires_in"` + } + err = json.NewDecoder(response.Body).Decode(&tokenResponse) + if err != nil { + return nil, E.Cause(err, "decode response") + } + + newCredentials := *credentials + newCredentials.AccessToken = tokenResponse.AccessToken + if tokenResponse.RefreshToken != "" { + newCredentials.RefreshToken = tokenResponse.RefreshToken + } + newCredentials.ExpiresAt = time.Now().UnixMilli() + int64(tokenResponse.ExpiresIn)*1000 + + return &newCredentials, nil +} diff --git a/service/ccm/credential_darwin.go b/service/ccm/credential_darwin.go new file mode 100644 index 0000000000..24047b8585 --- /dev/null +++ b/service/ccm/credential_darwin.go @@ -0,0 +1,116 @@ +//go:build darwin && cgo + +package ccm + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "os" + "path/filepath" + + E "github.com/sagernet/sing/common/exceptions" + + "github.com/keybase/go-keychain" +) + +func getKeychainServiceName() string { + configDirectory := os.Getenv("CLAUDE_CONFIG_DIR") + if configDirectory == "" { + return "Claude Code-credentials" + } + + userInfo, err := getRealUser() + if err != nil { + return "Claude Code-credentials" + } + defaultConfigDirectory := filepath.Join(userInfo.HomeDir, ".claude") + if configDirectory == defaultConfigDirectory { + return "Claude Code-credentials" + } + + hash := sha256.Sum256([]byte(configDirectory)) + return "Claude Code-credentials-" + hex.EncodeToString(hash[:])[:8] +} + +func platformReadCredentials(customPath string) (*oauthCredentials, error) { + if customPath != "" { + return readCredentialsFromFile(customPath) + } + + userInfo, err := getRealUser() + if err == nil { + query := keychain.NewItem() + query.SetSecClass(keychain.SecClassGenericPassword) + query.SetService(getKeychainServiceName()) + query.SetAccount(userInfo.Username) + query.SetMatchLimit(keychain.MatchLimitOne) + query.SetReturnData(true) + + results, err := keychain.QueryItem(query) + if err == nil && len(results) == 1 { + var container struct { + ClaudeAIAuth *oauthCredentials `json:"claudeAiOauth,omitempty"` + } + unmarshalErr := json.Unmarshal(results[0].Data, &container) + if unmarshalErr == nil && container.ClaudeAIAuth != nil { + return container.ClaudeAIAuth, nil + } + } + if err != nil && err != keychain.ErrorItemNotFound { + return nil, E.Cause(err, "query keychain") + } + } + + defaultPath, err := getDefaultCredentialsPath() + if err != nil { + return nil, err + } + return readCredentialsFromFile(defaultPath) +} + +func platformWriteCredentials(oauthCredentials *oauthCredentials, customPath string) error { + if customPath != "" { + return writeCredentialsToFile(oauthCredentials, customPath) + } + + userInfo, err := getRealUser() + if err == nil { + data, err := json.Marshal(map[string]any{"claudeAiOauth": oauthCredentials}) + if err == nil { + serviceName := getKeychainServiceName() + item := keychain.NewItem() + item.SetSecClass(keychain.SecClassGenericPassword) + item.SetService(serviceName) + item.SetAccount(userInfo.Username) + item.SetData(data) + item.SetAccessible(keychain.AccessibleWhenUnlocked) + + err = keychain.AddItem(item) + if err == nil { + return nil + } + + if err == keychain.ErrorDuplicateItem { + query := keychain.NewItem() + query.SetSecClass(keychain.SecClassGenericPassword) + query.SetService(serviceName) + query.SetAccount(userInfo.Username) + + updateItem := keychain.NewItem() + updateItem.SetData(data) + + updateErr := keychain.UpdateItem(query, updateItem) + if updateErr == nil { + return nil + } + } + } + } + + defaultPath, err := getDefaultCredentialsPath() + if err != nil { + return err + } + return writeCredentialsToFile(oauthCredentials, defaultPath) +} diff --git a/service/ccm/credential_other.go b/service/ccm/credential_other.go new file mode 100644 index 0000000000..828c78c078 --- /dev/null +++ b/service/ccm/credential_other.go @@ -0,0 +1,25 @@ +//go:build !darwin || !cgo + +package ccm + +func platformReadCredentials(customPath string) (*oauthCredentials, error) { + if customPath == "" { + var err error + customPath, err = getDefaultCredentialsPath() + if err != nil { + return nil, err + } + } + return readCredentialsFromFile(customPath) +} + +func platformWriteCredentials(oauthCredentials *oauthCredentials, customPath string) error { + if customPath == "" { + var err error + customPath, err = getDefaultCredentialsPath() + if err != nil { + return err + } + } + return writeCredentialsToFile(oauthCredentials, customPath) +} diff --git a/service/ccm/service.go b/service/ccm/service.go new file mode 100644 index 0000000000..3aca535df8 --- /dev/null +++ b/service/ccm/service.go @@ -0,0 +1,595 @@ +package ccm + +import ( + "bytes" + "context" + stdTLS "crypto/tls" + "encoding/json" + "errors" + "io" + "mime" + "net" + "net/http" + "strconv" + "strings" + "sync" + "time" + + "github.com/sagernet/sing-box/adapter" + boxService "github.com/sagernet/sing-box/adapter/service" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/ntp" + aTLS "github.com/sagernet/sing/common/tls" + + "github.com/anthropics/anthropic-sdk-go" + "github.com/go-chi/chi/v5" + "golang.org/x/net/http2" +) + +const ( + contextWindowStandard = 200000 + contextWindowPremium = 1000000 + premiumContextThreshold = 200000 +) + +func RegisterService(registry *boxService.Registry) { + boxService.Register[option.CCMServiceOptions](registry, C.TypeCCM, NewService) +} + +type errorResponse struct { + Type string `json:"type"` + Error errorDetails `json:"error"` + RequestID string `json:"request_id,omitempty"` +} + +type errorDetails struct { + Type string `json:"type"` + Message string `json:"message"` +} + +func writeJSONError(w http.ResponseWriter, r *http.Request, statusCode int, errorType string, message string) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + + json.NewEncoder(w).Encode(errorResponse{ + Type: "error", + Error: errorDetails{ + Type: errorType, + Message: message, + }, + RequestID: r.Header.Get("Request-Id"), + }) +} + +func isHopByHopHeader(header string) bool { + switch strings.ToLower(header) { + case "connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailers", "transfer-encoding", "upgrade", "host": + return true + default: + return false + } +} + +const ( + weeklyWindowSeconds = 604800 + weeklyWindowMinutes = weeklyWindowSeconds / 60 +) + +func parseInt64Header(headers http.Header, headerName string) (int64, bool) { + headerValue := strings.TrimSpace(headers.Get(headerName)) + if headerValue == "" { + return 0, false + } + parsedValue, parseError := strconv.ParseInt(headerValue, 10, 64) + if parseError != nil { + return 0, false + } + return parsedValue, true +} + +func extractWeeklyCycleHint(headers http.Header) *WeeklyCycleHint { + resetAtUnix, hasResetAt := parseInt64Header(headers, "anthropic-ratelimit-unified-7d-reset") + if !hasResetAt || resetAtUnix <= 0 { + return nil + } + + return &WeeklyCycleHint{ + WindowMinutes: weeklyWindowMinutes, + ResetAt: time.Unix(resetAtUnix, 0).UTC(), + } +} + +type Service struct { + boxService.Adapter + ctx context.Context + logger log.ContextLogger + credentialPath string + credentials *oauthCredentials + users []option.CCMUser + httpClient *http.Client + httpHeaders http.Header + listener *listener.Listener + tlsConfig tls.ServerConfig + httpServer *http.Server + userManager *UserManager + accessMutex sync.RWMutex + usageTracker *AggregatedUsage +} + +func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.CCMServiceOptions) (adapter.Service, error) { + serviceDialer, err := dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: option.DialerOptions{ + Detour: options.Detour, + }, + RemoteIsDomain: true, + }) + if err != nil { + return nil, E.Cause(err, "create dialer") + } + + httpClient := &http.Client{ + Transport: &http.Transport{ + ForceAttemptHTTP2: true, + TLSClientConfig: &stdTLS.Config{ + RootCAs: adapter.RootPoolFromContext(ctx), + Time: ntp.TimeFuncFromContext(ctx), + }, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return serviceDialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) + }, + }, + } + + userManager := &UserManager{ + tokenMap: make(map[string]string), + } + + var usageTracker *AggregatedUsage + if options.UsagesPath != "" { + usageTracker = &AggregatedUsage{ + LastUpdated: time.Now(), + Combinations: make([]CostCombination, 0), + filePath: options.UsagesPath, + logger: logger, + } + } + + service := &Service{ + Adapter: boxService.NewAdapter(C.TypeCCM, tag), + ctx: ctx, + logger: logger, + credentialPath: options.CredentialPath, + users: options.Users, + httpClient: httpClient, + httpHeaders: options.Headers.Build(), + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + }), + userManager: userManager, + usageTracker: usageTracker, + } + + if options.TLS != nil { + tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + service.tlsConfig = tlsConfig + } + + return service, nil +} + +func (s *Service) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + + s.userManager.UpdateUsers(s.users) + + credentials, err := platformReadCredentials(s.credentialPath) + if err != nil { + return E.Cause(err, "read credentials") + } + s.credentials = credentials + + if s.usageTracker != nil { + err = s.usageTracker.Load() + if err != nil { + s.logger.Warn("load usage statistics: ", err) + } + } + + router := chi.NewRouter() + router.Mount("/", s) + + s.httpServer = &http.Server{Handler: router} + + if s.tlsConfig != nil { + err = s.tlsConfig.Start() + if err != nil { + return E.Cause(err, "create TLS config") + } + } + + tcpListener, err := s.listener.ListenTCP() + if err != nil { + return err + } + + if s.tlsConfig != nil { + if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) { + s.tlsConfig.SetNextProtos(append([]string{"h2"}, s.tlsConfig.NextProtos()...)) + } + tcpListener = aTLS.NewListener(tcpListener, s.tlsConfig) + } + + go func() { + serveErr := s.httpServer.Serve(tcpListener) + if serveErr != nil && !errors.Is(serveErr, http.ErrServerClosed) { + s.logger.Error("serve error: ", serveErr) + } + }() + + return nil +} + +func (s *Service) getAccessToken() (string, error) { + s.accessMutex.RLock() + if !s.credentials.needsRefresh() { + token := s.credentials.AccessToken + s.accessMutex.RUnlock() + return token, nil + } + s.accessMutex.RUnlock() + + s.accessMutex.Lock() + defer s.accessMutex.Unlock() + + if !s.credentials.needsRefresh() { + return s.credentials.AccessToken, nil + } + + newCredentials, err := refreshToken(s.httpClient, s.credentials) + if err != nil { + return "", err + } + + s.credentials = newCredentials + + err = platformWriteCredentials(newCredentials, s.credentialPath) + if err != nil { + s.logger.Warn("persist refreshed token: ", err) + } + + return newCredentials.AccessToken, nil +} + +func detectContextWindow(betaHeader string, totalInputTokens int64) int { + if totalInputTokens > premiumContextThreshold { + features := strings.SplitSeq(betaHeader, ",") + for feature := range features { + if strings.HasPrefix(strings.TrimSpace(feature), "context-1m") { + return contextWindowPremium + } + } + } + return contextWindowStandard +} + +func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if !strings.HasPrefix(r.URL.Path, "/v1/") { + writeJSONError(w, r, http.StatusNotFound, "not_found_error", "Not found") + return + } + + var username string + if len(s.users) > 0 { + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + s.logger.Warn("authentication failed for request from ", r.RemoteAddr, ": missing Authorization header") + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "missing api key") + return + } + clientToken := strings.TrimPrefix(authHeader, "Bearer ") + if clientToken == authHeader { + s.logger.Warn("authentication failed for request from ", r.RemoteAddr, ": invalid Authorization format") + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key format") + return + } + var ok bool + username, ok = s.userManager.Authenticate(clientToken) + if !ok { + s.logger.Warn("authentication failed for request from ", r.RemoteAddr, ": unknown key: ", clientToken) + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key") + return + } + } + + var requestModel string + var messagesCount int + + if s.usageTracker != nil && r.Body != nil { + bodyBytes, err := io.ReadAll(r.Body) + if err == nil { + var request struct { + Model string `json:"model"` + Messages []anthropic.MessageParam `json:"messages"` + } + err := json.Unmarshal(bodyBytes, &request) + if err == nil { + requestModel = request.Model + messagesCount = len(request.Messages) + } + r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) + } + } + + accessToken, err := s.getAccessToken() + if err != nil { + s.logger.Error("get access token: ", err) + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "Authentication failed") + return + } + + proxyURL := claudeAPIBaseURL + r.URL.RequestURI() + proxyRequest, err := http.NewRequestWithContext(r.Context(), r.Method, proxyURL, r.Body) + if err != nil { + s.logger.Error("create proxy request: ", err) + writeJSONError(w, r, http.StatusInternalServerError, "api_error", "Internal server error") + return + } + + for key, values := range r.Header { + if !isHopByHopHeader(key) && key != "Authorization" { + proxyRequest.Header[key] = values + } + } + + serviceOverridesAcceptEncoding := len(s.httpHeaders.Values("Accept-Encoding")) > 0 + if s.usageTracker != nil && !serviceOverridesAcceptEncoding { + // Strip Accept-Encoding so Go Transport adds it automatically + // and transparently decompresses the response for correct usage counting. + proxyRequest.Header.Del("Accept-Encoding") + } + + anthropicBetaHeader := proxyRequest.Header.Get("anthropic-beta") + if anthropicBetaHeader != "" { + proxyRequest.Header.Set("anthropic-beta", anthropicBetaOAuthValue+","+anthropicBetaHeader) + } else { + proxyRequest.Header.Set("anthropic-beta", anthropicBetaOAuthValue) + } + + for key, values := range s.httpHeaders { + proxyRequest.Header.Del(key) + proxyRequest.Header[key] = values + } + + proxyRequest.Header.Set("Authorization", "Bearer "+accessToken) + + response, err := s.httpClient.Do(proxyRequest) + if err != nil { + writeJSONError(w, r, http.StatusBadGateway, "api_error", err.Error()) + return + } + defer response.Body.Close() + + for key, values := range response.Header { + if !isHopByHopHeader(key) { + w.Header()[key] = values + } + } + w.WriteHeader(response.StatusCode) + + if s.usageTracker != nil && response.StatusCode == http.StatusOK { + s.handleResponseWithTracking(w, response, requestModel, anthropicBetaHeader, messagesCount, username) + } else { + mediaType, _, err := mime.ParseMediaType(response.Header.Get("Content-Type")) + if err == nil && mediaType != "text/event-stream" { + _, _ = io.Copy(w, response.Body) + return + } + flusher, ok := w.(http.Flusher) + if !ok { + s.logger.Error("streaming not supported") + return + } + buffer := make([]byte, buf.BufferSize) + for { + n, err := response.Body.Read(buffer) + if n > 0 { + _, writeError := w.Write(buffer[:n]) + if writeError != nil { + s.logger.Error("write streaming response: ", writeError) + return + } + flusher.Flush() + } + if err != nil { + return + } + } + } +} + +func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, response *http.Response, requestModel string, anthropicBetaHeader string, messagesCount int, username string) { + weeklyCycleHint := extractWeeklyCycleHint(response.Header) + mediaType, _, err := mime.ParseMediaType(response.Header.Get("Content-Type")) + isStreaming := err == nil && mediaType == "text/event-stream" + + if !isStreaming { + bodyBytes, err := io.ReadAll(response.Body) + if err != nil { + s.logger.Error("read response body: ", err) + return + } + + var message anthropic.Message + var usage anthropic.Usage + var responseModel string + err = json.Unmarshal(bodyBytes, &message) + if err == nil { + responseModel = string(message.Model) + usage = message.Usage + } + if responseModel == "" { + responseModel = requestModel + } + + if usage.InputTokens > 0 || usage.OutputTokens > 0 { + if responseModel != "" { + totalInputTokens := usage.InputTokens + usage.CacheCreationInputTokens + usage.CacheReadInputTokens + contextWindow := detectContextWindow(anthropicBetaHeader, totalInputTokens) + s.usageTracker.AddUsageWithCycleHint( + responseModel, + contextWindow, + messagesCount, + usage.InputTokens, + usage.OutputTokens, + usage.CacheReadInputTokens, + usage.CacheCreationInputTokens, + usage.CacheCreation.Ephemeral5mInputTokens, + usage.CacheCreation.Ephemeral1hInputTokens, + username, + time.Now(), + weeklyCycleHint, + ) + } + } + + _, _ = writer.Write(bodyBytes) + return + } + + flusher, ok := writer.(http.Flusher) + if !ok { + s.logger.Error("streaming not supported") + return + } + + var accumulatedUsage anthropic.Usage + var responseModel string + buffer := make([]byte, buf.BufferSize) + var leftover []byte + + for { + n, err := response.Body.Read(buffer) + if n > 0 { + data := append(leftover, buffer[:n]...) + lines := bytes.Split(data, []byte("\n")) + + if err == nil { + leftover = lines[len(lines)-1] + lines = lines[:len(lines)-1] + } else { + leftover = nil + } + + for _, line := range lines { + line = bytes.TrimSpace(line) + if len(line) == 0 { + continue + } + + if after, ok0 := bytes.CutPrefix(line, []byte("data: ")); ok0 { + eventData := after + if bytes.Equal(eventData, []byte("[DONE]")) { + continue + } + + var event anthropic.MessageStreamEventUnion + err := json.Unmarshal(eventData, &event) + if err != nil { + continue + } + switch event.Type { + case "message_start": + messageStart := event.AsMessageStart() + if messageStart.Message.Model != "" { + responseModel = string(messageStart.Message.Model) + } + if messageStart.Message.Usage.InputTokens > 0 { + accumulatedUsage.InputTokens = messageStart.Message.Usage.InputTokens + accumulatedUsage.CacheReadInputTokens = messageStart.Message.Usage.CacheReadInputTokens + accumulatedUsage.CacheCreationInputTokens = messageStart.Message.Usage.CacheCreationInputTokens + accumulatedUsage.CacheCreation.Ephemeral5mInputTokens = messageStart.Message.Usage.CacheCreation.Ephemeral5mInputTokens + accumulatedUsage.CacheCreation.Ephemeral1hInputTokens = messageStart.Message.Usage.CacheCreation.Ephemeral1hInputTokens + } + case "message_delta": + messageDelta := event.AsMessageDelta() + if messageDelta.Usage.OutputTokens > 0 { + accumulatedUsage.OutputTokens = messageDelta.Usage.OutputTokens + } + } + } + } + + _, writeError := writer.Write(buffer[:n]) + if writeError != nil { + s.logger.Error("write streaming response: ", writeError) + return + } + flusher.Flush() + } + + if err != nil { + if responseModel == "" { + responseModel = requestModel + } + + if accumulatedUsage.InputTokens > 0 || accumulatedUsage.OutputTokens > 0 { + if responseModel != "" { + totalInputTokens := accumulatedUsage.InputTokens + accumulatedUsage.CacheCreationInputTokens + accumulatedUsage.CacheReadInputTokens + contextWindow := detectContextWindow(anthropicBetaHeader, totalInputTokens) + s.usageTracker.AddUsageWithCycleHint( + responseModel, + contextWindow, + messagesCount, + accumulatedUsage.InputTokens, + accumulatedUsage.OutputTokens, + accumulatedUsage.CacheReadInputTokens, + accumulatedUsage.CacheCreationInputTokens, + accumulatedUsage.CacheCreation.Ephemeral5mInputTokens, + accumulatedUsage.CacheCreation.Ephemeral1hInputTokens, + username, + time.Now(), + weeklyCycleHint, + ) + } + } + return + } + } +} + +func (s *Service) Close() error { + err := common.Close( + common.PtrOrNil(s.httpServer), + common.PtrOrNil(s.listener), + s.tlsConfig, + ) + + if s.usageTracker != nil { + s.usageTracker.cancelPendingSave() + saveErr := s.usageTracker.Save() + if saveErr != nil { + s.logger.Error("save usage statistics: ", saveErr) + } + } + + return err +} diff --git a/service/ccm/service_usage.go b/service/ccm/service_usage.go new file mode 100644 index 0000000000..36e9ee65df --- /dev/null +++ b/service/ccm/service_usage.go @@ -0,0 +1,706 @@ +package ccm + +import ( + "encoding/json" + "fmt" + "math" + "os" + "regexp" + "sync" + "time" + + "github.com/sagernet/sing-box/log" + E "github.com/sagernet/sing/common/exceptions" +) + +type UsageStats struct { + RequestCount int `json:"request_count"` + MessagesCount int `json:"messages_count"` + InputTokens int64 `json:"input_tokens"` + OutputTokens int64 `json:"output_tokens"` + CacheReadInputTokens int64 `json:"cache_read_input_tokens"` + CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"` + CacheCreation5MinuteInputTokens int64 `json:"cache_creation_5m_input_tokens,omitempty"` + CacheCreation1HourInputTokens int64 `json:"cache_creation_1h_input_tokens,omitempty"` +} + +type CostCombination struct { + Model string `json:"model"` + ContextWindow int `json:"context_window"` + WeekStartUnix int64 `json:"week_start_unix,omitempty"` + Total UsageStats `json:"total"` + ByUser map[string]UsageStats `json:"by_user"` +} + +type AggregatedUsage struct { + LastUpdated time.Time `json:"last_updated"` + Combinations []CostCombination `json:"combinations"` + mutex sync.Mutex + filePath string + logger log.ContextLogger + lastSaveTime time.Time + pendingSave bool + saveTimer *time.Timer + saveMutex sync.Mutex +} + +type UsageStatsJSON struct { + RequestCount int `json:"request_count"` + MessagesCount int `json:"messages_count"` + InputTokens int64 `json:"input_tokens"` + OutputTokens int64 `json:"output_tokens"` + CacheReadInputTokens int64 `json:"cache_read_input_tokens"` + CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"` + CacheCreation5MinuteInputTokens int64 `json:"cache_creation_5m_input_tokens,omitempty"` + CacheCreation1HourInputTokens int64 `json:"cache_creation_1h_input_tokens,omitempty"` + CostUSD float64 `json:"cost_usd"` +} + +type CostCombinationJSON struct { + Model string `json:"model"` + ContextWindow int `json:"context_window"` + WeekStartUnix int64 `json:"week_start_unix,omitempty"` + Total UsageStatsJSON `json:"total"` + ByUser map[string]UsageStatsJSON `json:"by_user"` +} + +type CostsSummaryJSON struct { + TotalUSD float64 `json:"total_usd"` + ByUser map[string]float64 `json:"by_user"` + ByWeek map[string]float64 `json:"by_week,omitempty"` + ByUserAndWeek map[string]map[string]float64 `json:"by_user_and_week,omitempty"` +} + +type AggregatedUsageJSON struct { + LastUpdated time.Time `json:"last_updated"` + Costs CostsSummaryJSON `json:"costs"` + Combinations []CostCombinationJSON `json:"combinations"` +} + +type WeeklyCycleHint struct { + WindowMinutes int64 + ResetAt time.Time +} + +type ModelPricing struct { + InputPrice float64 + OutputPrice float64 + CacheReadPrice float64 + CacheWritePrice5Minute float64 + CacheWritePrice1Hour float64 +} + +type modelFamily struct { + pattern *regexp.Regexp + standardPricing ModelPricing + premiumPricing *ModelPricing +} + +var ( + opus46StandardPricing = ModelPricing{ + InputPrice: 5.0, + OutputPrice: 25.0, + CacheReadPrice: 0.5, + CacheWritePrice5Minute: 6.25, + CacheWritePrice1Hour: 10.0, + } + + opus46PremiumPricing = ModelPricing{ + InputPrice: 10.0, + OutputPrice: 37.5, + CacheReadPrice: 1.0, + CacheWritePrice5Minute: 12.5, + CacheWritePrice1Hour: 20.0, + } + + opus45Pricing = ModelPricing{ + InputPrice: 5.0, + OutputPrice: 25.0, + CacheReadPrice: 0.5, + CacheWritePrice5Minute: 6.25, + CacheWritePrice1Hour: 10.0, + } + + opus4Pricing = ModelPricing{ + InputPrice: 15.0, + OutputPrice: 75.0, + CacheReadPrice: 1.5, + CacheWritePrice5Minute: 18.75, + CacheWritePrice1Hour: 30.0, + } + + sonnet46StandardPricing = ModelPricing{ + InputPrice: 3.0, + OutputPrice: 15.0, + CacheReadPrice: 0.3, + CacheWritePrice5Minute: 3.75, + CacheWritePrice1Hour: 6.0, + } + + sonnet46PremiumPricing = ModelPricing{ + InputPrice: 6.0, + OutputPrice: 22.5, + CacheReadPrice: 0.6, + CacheWritePrice5Minute: 7.5, + CacheWritePrice1Hour: 12.0, + } + + sonnet45StandardPricing = ModelPricing{ + InputPrice: 3.0, + OutputPrice: 15.0, + CacheReadPrice: 0.3, + CacheWritePrice5Minute: 3.75, + CacheWritePrice1Hour: 6.0, + } + + sonnet45PremiumPricing = ModelPricing{ + InputPrice: 6.0, + OutputPrice: 22.5, + CacheReadPrice: 0.6, + CacheWritePrice5Minute: 7.5, + CacheWritePrice1Hour: 12.0, + } + + sonnet4StandardPricing = ModelPricing{ + InputPrice: 3.0, + OutputPrice: 15.0, + CacheReadPrice: 0.3, + CacheWritePrice5Minute: 3.75, + CacheWritePrice1Hour: 6.0, + } + + sonnet4PremiumPricing = ModelPricing{ + InputPrice: 6.0, + OutputPrice: 22.5, + CacheReadPrice: 0.6, + CacheWritePrice5Minute: 7.5, + CacheWritePrice1Hour: 12.0, + } + + sonnet37Pricing = ModelPricing{ + InputPrice: 3.0, + OutputPrice: 15.0, + CacheReadPrice: 0.3, + CacheWritePrice5Minute: 3.75, + CacheWritePrice1Hour: 6.0, + } + + sonnet35Pricing = ModelPricing{ + InputPrice: 3.0, + OutputPrice: 15.0, + CacheReadPrice: 0.3, + CacheWritePrice5Minute: 3.75, + CacheWritePrice1Hour: 6.0, + } + + haiku45Pricing = ModelPricing{ + InputPrice: 1.0, + OutputPrice: 5.0, + CacheReadPrice: 0.1, + CacheWritePrice5Minute: 1.25, + CacheWritePrice1Hour: 2.0, + } + + haiku4Pricing = ModelPricing{ + InputPrice: 1.0, + OutputPrice: 5.0, + CacheReadPrice: 0.1, + CacheWritePrice5Minute: 1.25, + CacheWritePrice1Hour: 2.0, + } + + haiku35Pricing = ModelPricing{ + InputPrice: 0.8, + OutputPrice: 4.0, + CacheReadPrice: 0.08, + CacheWritePrice5Minute: 1.0, + CacheWritePrice1Hour: 1.6, + } + + haiku3Pricing = ModelPricing{ + InputPrice: 0.25, + OutputPrice: 1.25, + CacheReadPrice: 0.03, + CacheWritePrice5Minute: 0.3, + CacheWritePrice1Hour: 0.5, + } + + opus3Pricing = ModelPricing{ + InputPrice: 15.0, + OutputPrice: 75.0, + CacheReadPrice: 1.5, + CacheWritePrice5Minute: 18.75, + CacheWritePrice1Hour: 30.0, + } + + modelFamilies = []modelFamily{ + { + pattern: regexp.MustCompile(`^claude-opus-4-6(?:-|$)`), + standardPricing: opus46StandardPricing, + premiumPricing: &opus46PremiumPricing, + }, + { + pattern: regexp.MustCompile(`^claude-opus-4-5(?:-|$)`), + standardPricing: opus45Pricing, + premiumPricing: nil, + }, + { + pattern: regexp.MustCompile(`^claude-(?:opus-4(?:-|$)|4-opus-)`), + standardPricing: opus4Pricing, + premiumPricing: nil, + }, + { + pattern: regexp.MustCompile(`^claude-(?:opus-3(?:-|$)|3-opus-)`), + standardPricing: opus3Pricing, + premiumPricing: nil, + }, + { + pattern: regexp.MustCompile(`^claude-(?:sonnet-4-6(?:-|$)|4-6-sonnet-)`), + standardPricing: sonnet46StandardPricing, + premiumPricing: &sonnet46PremiumPricing, + }, + { + pattern: regexp.MustCompile(`^claude-(?:sonnet-4-5(?:-|$)|4-5-sonnet-)`), + standardPricing: sonnet45StandardPricing, + premiumPricing: &sonnet45PremiumPricing, + }, + { + pattern: regexp.MustCompile(`^claude-(?:sonnet-4(?:-|$)|4-sonnet-)`), + standardPricing: sonnet4StandardPricing, + premiumPricing: &sonnet4PremiumPricing, + }, + { + pattern: regexp.MustCompile(`^claude-3-7-sonnet(?:-|$)`), + standardPricing: sonnet37Pricing, + premiumPricing: nil, + }, + { + pattern: regexp.MustCompile(`^claude-3-5-sonnet(?:-|$)`), + standardPricing: sonnet35Pricing, + premiumPricing: nil, + }, + { + pattern: regexp.MustCompile(`^claude-(?:haiku-4-5(?:-|$)|4-5-haiku-)`), + standardPricing: haiku45Pricing, + premiumPricing: nil, + }, + { + pattern: regexp.MustCompile(`^claude-haiku-4(?:-|$)`), + standardPricing: haiku4Pricing, + premiumPricing: nil, + }, + { + pattern: regexp.MustCompile(`^claude-3-5-haiku(?:-|$)`), + standardPricing: haiku35Pricing, + premiumPricing: nil, + }, + { + pattern: regexp.MustCompile(`^claude-3-haiku(?:-|$)`), + standardPricing: haiku3Pricing, + premiumPricing: nil, + }, + } +) + +func getPricing(model string, contextWindow int) ModelPricing { + isPremium := contextWindow >= contextWindowPremium + + for _, family := range modelFamilies { + if family.pattern.MatchString(model) { + if isPremium && family.premiumPricing != nil { + return *family.premiumPricing + } + return family.standardPricing + } + } + + return sonnet4StandardPricing +} + +func calculateCost(stats UsageStats, model string, contextWindow int) float64 { + pricing := getPricing(model, contextWindow) + + cacheCreationCost := 0.0 + if stats.CacheCreation5MinuteInputTokens > 0 || stats.CacheCreation1HourInputTokens > 0 { + cacheCreationCost = float64(stats.CacheCreation5MinuteInputTokens)*pricing.CacheWritePrice5Minute + + float64(stats.CacheCreation1HourInputTokens)*pricing.CacheWritePrice1Hour + } else { + // Backward compatibility for usage files generated before TTL split tracking. + cacheCreationCost = float64(stats.CacheCreationInputTokens) * pricing.CacheWritePrice5Minute + } + + cost := (float64(stats.InputTokens)*pricing.InputPrice + + float64(stats.OutputTokens)*pricing.OutputPrice + + float64(stats.CacheReadInputTokens)*pricing.CacheReadPrice + + cacheCreationCost) / 1_000_000 + + return math.Round(cost*100) / 100 +} + +func roundCost(cost float64) float64 { + return math.Round(cost*100) / 100 +} + +func normalizeCombinations(combinations []CostCombination) { + for index := range combinations { + if combinations[index].ByUser == nil { + combinations[index].ByUser = make(map[string]UsageStats) + } + } +} + +func addUsageToCombinations( + combinations *[]CostCombination, + model string, + contextWindow int, + weekStartUnix int64, + messagesCount int, + inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, cacheCreation5MinuteTokens, cacheCreation1HourTokens int64, + user string, +) { + var matchedCombination *CostCombination + for index := range *combinations { + combination := &(*combinations)[index] + if combination.Model == model && combination.ContextWindow == contextWindow && combination.WeekStartUnix == weekStartUnix { + matchedCombination = combination + break + } + } + + if matchedCombination == nil { + newCombination := CostCombination{ + Model: model, + ContextWindow: contextWindow, + WeekStartUnix: weekStartUnix, + Total: UsageStats{}, + ByUser: make(map[string]UsageStats), + } + *combinations = append(*combinations, newCombination) + matchedCombination = &(*combinations)[len(*combinations)-1] + } + + if cacheCreationTokens == 0 { + cacheCreationTokens = cacheCreation5MinuteTokens + cacheCreation1HourTokens + } + + matchedCombination.Total.RequestCount++ + matchedCombination.Total.MessagesCount += messagesCount + matchedCombination.Total.InputTokens += inputTokens + matchedCombination.Total.OutputTokens += outputTokens + matchedCombination.Total.CacheReadInputTokens += cacheReadTokens + matchedCombination.Total.CacheCreationInputTokens += cacheCreationTokens + matchedCombination.Total.CacheCreation5MinuteInputTokens += cacheCreation5MinuteTokens + matchedCombination.Total.CacheCreation1HourInputTokens += cacheCreation1HourTokens + + if user != "" { + userStats := matchedCombination.ByUser[user] + userStats.RequestCount++ + userStats.MessagesCount += messagesCount + userStats.InputTokens += inputTokens + userStats.OutputTokens += outputTokens + userStats.CacheReadInputTokens += cacheReadTokens + userStats.CacheCreationInputTokens += cacheCreationTokens + userStats.CacheCreation5MinuteInputTokens += cacheCreation5MinuteTokens + userStats.CacheCreation1HourInputTokens += cacheCreation1HourTokens + matchedCombination.ByUser[user] = userStats + } +} + +func buildCombinationJSON(combinations []CostCombination, aggregateUserCosts map[string]float64) ([]CostCombinationJSON, float64) { + result := make([]CostCombinationJSON, len(combinations)) + var totalCost float64 + + for index, combination := range combinations { + combinationTotalCost := calculateCost(combination.Total, combination.Model, combination.ContextWindow) + totalCost += combinationTotalCost + + combinationJSON := CostCombinationJSON{ + Model: combination.Model, + ContextWindow: combination.ContextWindow, + WeekStartUnix: combination.WeekStartUnix, + Total: UsageStatsJSON{ + RequestCount: combination.Total.RequestCount, + MessagesCount: combination.Total.MessagesCount, + InputTokens: combination.Total.InputTokens, + OutputTokens: combination.Total.OutputTokens, + CacheReadInputTokens: combination.Total.CacheReadInputTokens, + CacheCreationInputTokens: combination.Total.CacheCreationInputTokens, + CacheCreation5MinuteInputTokens: combination.Total.CacheCreation5MinuteInputTokens, + CacheCreation1HourInputTokens: combination.Total.CacheCreation1HourInputTokens, + CostUSD: combinationTotalCost, + }, + ByUser: make(map[string]UsageStatsJSON), + } + + for user, userStats := range combination.ByUser { + userCost := calculateCost(userStats, combination.Model, combination.ContextWindow) + if aggregateUserCosts != nil { + aggregateUserCosts[user] += userCost + } + + combinationJSON.ByUser[user] = UsageStatsJSON{ + RequestCount: userStats.RequestCount, + MessagesCount: userStats.MessagesCount, + InputTokens: userStats.InputTokens, + OutputTokens: userStats.OutputTokens, + CacheReadInputTokens: userStats.CacheReadInputTokens, + CacheCreationInputTokens: userStats.CacheCreationInputTokens, + CacheCreation5MinuteInputTokens: userStats.CacheCreation5MinuteInputTokens, + CacheCreation1HourInputTokens: userStats.CacheCreation1HourInputTokens, + CostUSD: userCost, + } + } + + result[index] = combinationJSON + } + + return result, roundCost(totalCost) +} + +func formatUTCOffsetLabel(timestamp time.Time) string { + _, offsetSeconds := timestamp.Zone() + sign := "+" + if offsetSeconds < 0 { + sign = "-" + offsetSeconds = -offsetSeconds + } + offsetHours := offsetSeconds / 3600 + offsetMinutes := (offsetSeconds % 3600) / 60 + if offsetMinutes == 0 { + return fmt.Sprintf("UTC%s%d", sign, offsetHours) + } + return fmt.Sprintf("UTC%s%d:%02d", sign, offsetHours, offsetMinutes) +} + +func formatWeekStartKey(cycleStartAt time.Time) string { + localCycleStart := cycleStartAt.In(time.Local) + return fmt.Sprintf("%s %s", localCycleStart.Format("2006-01-02 15:04:05"), formatUTCOffsetLabel(localCycleStart)) +} + +func buildByWeekCost(combinations []CostCombination) map[string]float64 { + byWeek := make(map[string]float64) + for _, combination := range combinations { + if combination.WeekStartUnix <= 0 { + continue + } + weekStartAt := time.Unix(combination.WeekStartUnix, 0).UTC() + weekKey := formatWeekStartKey(weekStartAt) + byWeek[weekKey] += calculateCost(combination.Total, combination.Model, combination.ContextWindow) + } + for weekKey, weekCost := range byWeek { + byWeek[weekKey] = roundCost(weekCost) + } + return byWeek +} + +func buildByUserAndWeekCost(combinations []CostCombination) map[string]map[string]float64 { + byUserAndWeek := make(map[string]map[string]float64) + for _, combination := range combinations { + if combination.WeekStartUnix <= 0 { + continue + } + weekStartAt := time.Unix(combination.WeekStartUnix, 0).UTC() + weekKey := formatWeekStartKey(weekStartAt) + for user, userStats := range combination.ByUser { + userWeeks, exists := byUserAndWeek[user] + if !exists { + userWeeks = make(map[string]float64) + byUserAndWeek[user] = userWeeks + } + userWeeks[weekKey] += calculateCost(userStats, combination.Model, combination.ContextWindow) + } + } + for _, weekCosts := range byUserAndWeek { + for weekKey, cost := range weekCosts { + weekCosts[weekKey] = roundCost(cost) + } + } + return byUserAndWeek +} + +func deriveWeekStartUnix(cycleHint *WeeklyCycleHint) int64 { + if cycleHint == nil || cycleHint.WindowMinutes <= 0 || cycleHint.ResetAt.IsZero() { + return 0 + } + windowDuration := time.Duration(cycleHint.WindowMinutes) * time.Minute + return cycleHint.ResetAt.UTC().Add(-windowDuration).Unix() +} + +func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON { + u.mutex.Lock() + defer u.mutex.Unlock() + + result := &AggregatedUsageJSON{ + LastUpdated: u.LastUpdated, + Costs: CostsSummaryJSON{ + TotalUSD: 0, + ByUser: make(map[string]float64), + ByWeek: make(map[string]float64), + }, + } + + globalCombinationsJSON, totalCost := buildCombinationJSON(u.Combinations, result.Costs.ByUser) + result.Combinations = globalCombinationsJSON + result.Costs.TotalUSD = totalCost + result.Costs.ByWeek = buildByWeekCost(u.Combinations) + + if len(result.Costs.ByWeek) == 0 { + result.Costs.ByWeek = nil + } + + result.Costs.ByUserAndWeek = buildByUserAndWeekCost(u.Combinations) + if len(result.Costs.ByUserAndWeek) == 0 { + result.Costs.ByUserAndWeek = nil + } + + for user, cost := range result.Costs.ByUser { + result.Costs.ByUser[user] = roundCost(cost) + } + + return result +} + +func (u *AggregatedUsage) Load() error { + u.mutex.Lock() + defer u.mutex.Unlock() + + u.LastUpdated = time.Time{} + u.Combinations = nil + + data, err := os.ReadFile(u.filePath) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + + var temp struct { + LastUpdated time.Time `json:"last_updated"` + Combinations []CostCombination `json:"combinations"` + } + + err = json.Unmarshal(data, &temp) + if err != nil { + return err + } + + u.LastUpdated = temp.LastUpdated + u.Combinations = temp.Combinations + normalizeCombinations(u.Combinations) + + return nil +} + +func (u *AggregatedUsage) Save() error { + jsonData := u.ToJSON() + + data, err := json.MarshalIndent(jsonData, "", " ") + if err != nil { + return err + } + + tmpFile := u.filePath + ".tmp" + err = os.WriteFile(tmpFile, data, 0o644) + if err != nil { + return err + } + defer os.Remove(tmpFile) + err = os.Rename(tmpFile, u.filePath) + if err == nil { + u.saveMutex.Lock() + u.lastSaveTime = time.Now() + u.saveMutex.Unlock() + } + return err +} + +func (u *AggregatedUsage) AddUsage( + model string, + contextWindow int, + messagesCount int, + inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, cacheCreation5MinuteTokens, cacheCreation1HourTokens int64, + user string, +) error { + return u.AddUsageWithCycleHint(model, contextWindow, messagesCount, inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, cacheCreation5MinuteTokens, cacheCreation1HourTokens, user, time.Now(), nil) +} + +func (u *AggregatedUsage) AddUsageWithCycleHint( + model string, + contextWindow int, + messagesCount int, + inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, cacheCreation5MinuteTokens, cacheCreation1HourTokens int64, + user string, + observedAt time.Time, + cycleHint *WeeklyCycleHint, +) error { + if model == "" { + return E.New("model cannot be empty") + } + if contextWindow <= 0 { + return E.New("contextWindow must be positive") + } + if observedAt.IsZero() { + observedAt = time.Now() + } + + u.mutex.Lock() + defer u.mutex.Unlock() + + u.LastUpdated = observedAt + weekStartUnix := deriveWeekStartUnix(cycleHint) + + addUsageToCombinations(&u.Combinations, model, contextWindow, weekStartUnix, messagesCount, inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, cacheCreation5MinuteTokens, cacheCreation1HourTokens, user) + + go u.scheduleSave() + + return nil +} + +func (u *AggregatedUsage) scheduleSave() { + const saveInterval = time.Minute + + u.saveMutex.Lock() + defer u.saveMutex.Unlock() + + timeSinceLastSave := time.Since(u.lastSaveTime) + + if timeSinceLastSave >= saveInterval { + go u.saveAsync() + return + } + + if u.pendingSave { + return + } + + u.pendingSave = true + remainingTime := saveInterval - timeSinceLastSave + + u.saveTimer = time.AfterFunc(remainingTime, func() { + u.saveMutex.Lock() + u.pendingSave = false + u.saveMutex.Unlock() + u.saveAsync() + }) +} + +func (u *AggregatedUsage) saveAsync() { + err := u.Save() + if err != nil { + if u.logger != nil { + u.logger.Error("save usage statistics: ", err) + } + } +} + +func (u *AggregatedUsage) cancelPendingSave() { + u.saveMutex.Lock() + defer u.saveMutex.Unlock() + + if u.saveTimer != nil { + u.saveTimer.Stop() + u.saveTimer = nil + } + u.pendingSave = false +} diff --git a/service/ccm/service_user.go b/service/ccm/service_user.go new file mode 100644 index 0000000000..94637ed814 --- /dev/null +++ b/service/ccm/service_user.go @@ -0,0 +1,29 @@ +package ccm + +import ( + "sync" + + "github.com/sagernet/sing-box/option" +) + +type UserManager struct { + accessMutex sync.RWMutex + tokenMap map[string]string +} + +func (m *UserManager) UpdateUsers(users []option.CCMUser) { + m.accessMutex.Lock() + defer m.accessMutex.Unlock() + tokenMap := make(map[string]string, len(users)) + for _, user := range users { + tokenMap[user.Token] = user.Name + } + m.tokenMap = tokenMap +} + +func (m *UserManager) Authenticate(token string) (string, bool) { + m.accessMutex.RLock() + username, found := m.tokenMap[token] + m.accessMutex.RUnlock() + return username, found +} diff --git a/service/derp/service.go b/service/derp/service.go new file mode 100644 index 0000000000..ee91e3a166 --- /dev/null +++ b/service/derp/service.go @@ -0,0 +1,513 @@ +//go:build with_gvisor + +package derp + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "net/netip" + "os" + "path/filepath" + "regexp" + "strings" + "time" + + "github.com/sagernet/sing-box/adapter" + boxService "github.com/sagernet/sing-box/adapter/service" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + boxScale "github.com/sagernet/sing-box/protocol/tailscale" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/json/badoption" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + aTLS "github.com/sagernet/sing/common/tls" + "github.com/sagernet/sing/service" + "github.com/sagernet/sing/service/filemanager" + "github.com/sagernet/tailscale/client/local" + "github.com/sagernet/tailscale/derp" + "github.com/sagernet/tailscale/derp/derphttp" + "github.com/sagernet/tailscale/derp/derpserver" + "github.com/sagernet/tailscale/net/netmon" + "github.com/sagernet/tailscale/net/stun" + "github.com/sagernet/tailscale/net/wsconn" + "github.com/sagernet/tailscale/tsweb" + "github.com/sagernet/tailscale/types/key" + + "github.com/coder/websocket" + "github.com/go-chi/render" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" +) + +func Register(registry *boxService.Registry) { + boxService.Register[option.DERPServiceOptions](registry, C.TypeDERP, NewService) +} + +type Service struct { + boxService.Adapter + ctx context.Context + logger logger.ContextLogger + listener *listener.Listener + stunListener *listener.Listener + tlsConfig tls.ServerConfig + server *derpserver.Server + configPath string + verifyClientEndpoint []string + verifyClientURL []*option.DERPVerifyClientURLOptions + home string + meshKey string + meshKeyPath string + meshWith []*option.DERPMeshOptions +} + +func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.DERPServiceOptions) (adapter.Service, error) { + if options.TLS == nil || !options.TLS.Enabled { + return nil, E.New("TLS is required for DERP server") + } + tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + + var configPath string + if options.ConfigPath != "" { + configPath = filemanager.BasePath(ctx, os.ExpandEnv(options.ConfigPath)) + } else { + return nil, E.New("missing config_path") + } + + if options.MeshPSK != "" { + err = checkMeshKey(options.MeshPSK) + if err != nil { + return nil, E.Cause(err, "invalid mesh_psk") + } + } + + var stunListener *listener.Listener + if options.STUN != nil && options.STUN.Enabled { + if options.STUN.Listen == nil { + options.STUN.Listen = (*badoption.Addr)(common.Ptr(netip.IPv6Unspecified())) + } + if options.STUN.ListenPort == 0 { + options.STUN.ListenPort = 3478 + } + stunListener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkUDP}, + Listen: options.STUN.ListenOptions, + }) + } + + return &Service{ + Adapter: boxService.NewAdapter(C.TypeDERP, tag), + ctx: ctx, + logger: logger, + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + }), + stunListener: stunListener, + tlsConfig: tlsConfig, + configPath: configPath, + verifyClientEndpoint: options.VerifyClientEndpoint, + verifyClientURL: options.VerifyClientURL, + home: options.Home, + meshKey: options.MeshPSK, + meshKeyPath: options.MeshPSKFile, + meshWith: options.MeshWith, + }, nil +} + +func (d *Service) Start(stage adapter.StartStage) error { + switch stage { + case adapter.StartStateStart: + config, err := readDERPConfig(filemanager.BasePath(d.ctx, d.configPath)) + if err != nil { + return err + } + + server := derpserver.New(config.PrivateKey, func(format string, args ...any) { + d.logger.Debug(fmt.Sprintf(format, args...)) + }) + + if len(d.verifyClientURL) > 0 { + var httpClients []*http.Client + var urls []string + httpClientManager := service.FromContext[adapter.HTTPClientManager](d.ctx) + for index, verifyOptions := range d.verifyClientURL { + transport, createErr := httpClientManager.ResolveTransport(d.ctx, d.logger, verifyOptions.HTTPClientOptions) + if createErr != nil { + return E.Cause(createErr, "verify_client_url[", index, "]") + } + httpClients = append(httpClients, &http.Client{Transport: transport}) + urls = append(urls, verifyOptions.URL) + } + server.SetVerifyClientHTTPClient(httpClients) + server.SetVerifyClientURL(urls) + } + + if d.meshKey != "" { + server.SetMeshKey(d.meshKey) + } else if d.meshKeyPath != "" { + var meshKeyContent []byte + meshKeyContent, err = os.ReadFile(d.meshKeyPath) + if err != nil { + return err + } + err = checkMeshKey(string(meshKeyContent)) + if err != nil { + return E.Cause(err, "invalid mesh_psk_path file") + } + server.SetMeshKey(string(meshKeyContent)) + } + d.server = server + + derpMux := http.NewServeMux() + derpHandler := derpserver.Handler(server) + derpHandler = addWebSocketSupport(server, derpHandler) + derpMux.Handle("/derp", derpHandler) + + homeHandler, ok := getHomeHandler(d.home) + if !ok { + return E.New("invalid home value: ", d.home) + } + + derpMux.HandleFunc("/derp/probe", derpserver.ProbeHandler) + derpMux.HandleFunc("/derp/latency-check", derpserver.ProbeHandler) + derpMux.HandleFunc("/bootstrap-dns", tsweb.BrowserHeaderHandlerFunc(handleBootstrapDNS(d.ctx))) + derpMux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + tsweb.AddBrowserHeaders(w) + homeHandler.ServeHTTP(w, r) + })) + derpMux.Handle("/robots.txt", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + tsweb.AddBrowserHeaders(w) + io.WriteString(w, "User-agent: *\nDisallow: /\n") + })) + derpMux.Handle("/generate_204", http.HandlerFunc(derpserver.ServeNoContent)) + + err = d.tlsConfig.Start() + if err != nil { + return err + } + + tcpListener, err := d.listener.ListenTCP() + if err != nil { + return err + } + if len(d.tlsConfig.NextProtos()) == 0 { + d.tlsConfig.SetNextProtos([]string{http2.NextProtoTLS, "http/1.1"}) + } else if !common.Contains(d.tlsConfig.NextProtos(), http2.NextProtoTLS) { + d.tlsConfig.SetNextProtos(append([]string{http2.NextProtoTLS}, d.tlsConfig.NextProtos()...)) + } + tcpListener = aTLS.NewListener(tcpListener, d.tlsConfig) + httpServer := &http.Server{ + Handler: h2c.NewHandler(derpMux, &http2.Server{}), + } + go httpServer.Serve(tcpListener) + + if d.stunListener != nil { + stunConn, err := d.stunListener.ListenUDP() + if err != nil { + return err + } + go d.loopSTUNPacket(stunConn.(*net.UDPConn)) + } + case adapter.StartStatePostStart: + if len(d.verifyClientEndpoint) > 0 { + var endpoints []*local.Client + endpointManager := service.FromContext[adapter.EndpointManager](d.ctx) + for _, endpointTag := range d.verifyClientEndpoint { + endpoint, loaded := endpointManager.Get(endpointTag) + if !loaded { + return E.New("verify_client_endpoint: endpoint not found: ", endpointTag) + } + tsEndpoint, isTailscale := endpoint.(*boxScale.Endpoint) + if !isTailscale { + return E.New("verify_client_endpoint: endpoint is not Tailscale: ", endpointTag) + } + localClient, err := tsEndpoint.Server().LocalClient() + if err != nil { + return err + } + endpoints = append(endpoints, localClient) + } + d.server.SetVerifyClientLocalClient(endpoints) + } + if len(d.meshWith) > 0 { + if !d.server.HasMeshKey() { + return E.New("missing mesh psk") + } + for _, options := range d.meshWith { + err := d.startMeshWithHost(d.server, options) + if err != nil { + return err + } + } + } + } + return nil +} + +func checkMeshKey(meshKey string) error { + checkRegex, err := regexp.Compile(`^[0-9a-f]{64}$`) + if err != nil { + return err + } + if !checkRegex.MatchString(meshKey) { + return E.New("key must contain exactly 64 hex digits") + } + return nil +} + +func (d *Service) startMeshWithHost(derpServer *derpserver.Server, server *option.DERPMeshOptions) error { + meshDialer, err := dialer.NewWithOptions(dialer.Options{ + Context: d.ctx, + Options: server.DialerOptions, + RemoteIsDomain: server.ServerIsDomain(), + NewDialer: true, + }) + if err != nil { + return err + } + var hostname string + if server.Host != "" { + hostname = server.Host + } else { + hostname = server.Server + } + var stdConfig *tls.STDConfig + if server.TLS != nil && server.TLS.Enabled { + tlsConfig, err := tls.NewClient(d.ctx, d.logger, hostname, *server.TLS) + if err != nil { + return err + } + stdConfig, err = tlsConfig.STDConfig() + if err != nil { + return err + } + } + logf := func(format string, args ...any) { + d.logger.Debug(F.ToString("mesh(", hostname, "): ", fmt.Sprintf(format, args...))) + } + var meshHost string + if server.ServerPort == 0 || server.ServerPort == 443 { + meshHost = hostname + } else { + meshHost = M.ParseSocksaddrHostPort(hostname, server.ServerPort).String() + } + var serverURL string + if stdConfig != nil { + serverURL = "https://" + meshHost + "/derp" + } else { + serverURL = "http://" + meshHost + "/derp" + } + meshClient, err := derphttp.NewClient(derpServer.PrivateKey(), serverURL, logf, netmon.NewStatic()) + if err != nil { + return err + } + meshClient.TLSConfig = stdConfig + meshClient.MeshKey = derpServer.MeshKey() + meshClient.WatchConnectionChanges = true + meshClient.SetURLDialer(func(ctx context.Context, network, addr string) (net.Conn, error) { + return meshDialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) + }) + add := func(m derp.PeerPresentMessage) { derpServer.AddPacketForwarder(m.Key, meshClient) } + remove := func(m derp.PeerGoneMessage) { derpServer.RemovePacketForwarder(m.Peer, meshClient) } + notifyError := func(err error) { d.logger.Error(err) } + go meshClient.RunWatchConnectionLoop(context.Background(), derpServer.PublicKey(), logf, add, remove, notifyError) + return nil +} + +func (d *Service) Close() error { + err := common.Close( + common.PtrOrNil(d.listener), + d.tlsConfig, + ) + return err +} + +var homePage = ` +

DERP

+

+ This is a Tailscale DERP server. +

+ +

+ It provides STUN, interactive connectivity establishment, and relaying of end-to-end encrypted traffic + for Tailscale clients. +

+ +

+ Documentation: +

+ +
    + +
  • About DERP
  • +
  • Protocol & Go docs
  • +
  • How to run a DERP server
  • + + + +` + +func getHomeHandler(val string) (_ http.Handler, ok bool) { + if val == "" { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(200) + w.Write([]byte(homePage)) + }), true + } + if val == "blank" { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(200) + }), true + } + if strings.HasPrefix(val, "http://") || strings.HasPrefix(val, "https://") { + return http.RedirectHandler(val, http.StatusFound), true + } + return nil, false +} + +func addWebSocketSupport(s *derpserver.Server, base http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + up := strings.ToLower(r.Header.Get("Upgrade")) + + // Very early versions of Tailscale set "Upgrade: WebSocket" but didn't actually + // speak WebSockets (they still assumed DERP's binary framing). So to distinguish + // clients that actually want WebSockets, look for an explicit "derp" subprotocol. + if up != "websocket" || !strings.Contains(r.Header.Get("Sec-Websocket-Protocol"), "derp") { + base.ServeHTTP(w, r) + return + } + + c, err := websocket.Accept(w, r, &websocket.AcceptOptions{ + Subprotocols: []string{"derp"}, + OriginPatterns: []string{"*"}, + // Disable compression because we transmit WireGuard messages that + // are not compressible. + // Additionally, Safari has a broken implementation of compression + // (see https://github.com/nhooyr/websocket/issues/218) that makes + // enabling it actively harmful. + CompressionMode: websocket.CompressionDisabled, + }) + if err != nil { + return + } + defer c.Close(websocket.StatusInternalError, "closing") + if c.Subprotocol() != "derp" { + c.Close(websocket.StatusPolicyViolation, "client must speak the derp subprotocol") + return + } + wc := wsconn.NetConn(r.Context(), c, websocket.MessageBinary, r.RemoteAddr) + brw := bufio.NewReadWriter(bufio.NewReader(wc), bufio.NewWriter(wc)) + s.Accept(r.Context(), wc, brw, r.RemoteAddr) + }) +} + +func handleBootstrapDNS(ctx context.Context) http.HandlerFunc { + dnsRouter := service.FromContext[adapter.DNSRouter](ctx) + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Connection", "close") + if queryDomain := r.URL.Query().Get("q"); queryDomain != "" { + addresses, err := dnsRouter.Lookup(ctx, queryDomain, adapter.DNSQueryOptions{}) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + render.JSON(w, r, render.M{ + queryDomain: addresses, + }) + return + } + w.Write([]byte("{}")) + } +} + +type derpConfig struct { + PrivateKey key.NodePrivate +} + +func readDERPConfig(path string) (*derpConfig, error) { + content, err := os.ReadFile(path) + if err != nil { + if os.IsNotExist(err) { + return writeNewDERPConfig(path) + } + return nil, err + } + var config derpConfig + err = json.Unmarshal(content, &config) + if err != nil { + return nil, err + } + return &config, nil +} + +func writeNewDERPConfig(path string) (*derpConfig, error) { + newKey := key.NewNode() + err := os.MkdirAll(filepath.Dir(path), 0o777) + if err != nil { + return nil, err + } + config := derpConfig{ + PrivateKey: newKey, + } + content, err := json.Marshal(config) + if err != nil { + return nil, err + } + err = os.WriteFile(path, content, 0o644) + if err != nil { + return nil, err + } + return &config, nil +} + +func (d *Service) loopSTUNPacket(packetConn *net.UDPConn) { + buffer := make([]byte, 65535) + oob := make([]byte, 1024) + var ( + n int + oobN int + addrPort netip.AddrPort + err error + ) + for { + n, oobN, _, addrPort, err = packetConn.ReadMsgUDPAddrPort(buffer, oob) + if err != nil { + if E.IsClosedOrCanceled(err) { + return + } + time.Sleep(time.Second) + continue + } + if !stun.Is(buffer[:n]) { + continue + } + txid, err := stun.ParseBindingRequest(buffer[:n]) + if err != nil { + continue + } + packetConn.WriteMsgUDPAddrPort(stun.Response(txid, addrPort), oob[:oobN], addrPort) + } +} diff --git a/service/ocm/credential.go b/service/ocm/credential.go new file mode 100644 index 0000000000..76651a8e14 --- /dev/null +++ b/service/ocm/credential.go @@ -0,0 +1,173 @@ +package ocm + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "os" + "os/user" + "path/filepath" + "time" + + E "github.com/sagernet/sing/common/exceptions" +) + +const ( + oauth2ClientID = "app_EMoamEEZ73f0CkXaXp7hrann" + oauth2TokenURL = "https://auth.openai.com/oauth/token" + openaiAPIBaseURL = "https://api.openai.com" + chatGPTBackendURL = "https://chatgpt.com/backend-api/codex" + tokenRefreshIntervalDays = 8 +) + +func getRealUser() (*user.User, error) { + if sudoUser := os.Getenv("SUDO_USER"); sudoUser != "" { + sudoUserInfo, err := user.Lookup(sudoUser) + if err == nil { + return sudoUserInfo, nil + } + } + return user.Current() +} + +func getDefaultCredentialsPath() (string, error) { + if codexHome := os.Getenv("CODEX_HOME"); codexHome != "" { + return filepath.Join(codexHome, "auth.json"), nil + } + userInfo, err := getRealUser() + if err != nil { + return "", err + } + return filepath.Join(userInfo.HomeDir, ".codex", "auth.json"), nil +} + +func readCredentialsFromFile(path string) (*oauthCredentials, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + var credentials oauthCredentials + err = json.Unmarshal(data, &credentials) + if err != nil { + return nil, err + } + return &credentials, nil +} + +func writeCredentialsToFile(credentials *oauthCredentials, path string) error { + data, err := json.MarshalIndent(credentials, "", " ") + if err != nil { + return err + } + return os.WriteFile(path, data, 0o600) +} + +type oauthCredentials struct { + APIKey string `json:"OPENAI_API_KEY,omitempty"` + Tokens *tokenData `json:"tokens,omitempty"` + LastRefresh *time.Time `json:"last_refresh,omitempty"` +} + +type tokenData struct { + IDToken string `json:"id_token,omitempty"` + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + AccountID string `json:"account_id,omitempty"` +} + +func (c *oauthCredentials) isAPIKeyMode() bool { + return c.APIKey != "" +} + +func (c *oauthCredentials) getAccessToken() string { + if c.APIKey != "" { + return c.APIKey + } + if c.Tokens != nil { + return c.Tokens.AccessToken + } + return "" +} + +func (c *oauthCredentials) getAccountID() string { + if c.Tokens != nil { + return c.Tokens.AccountID + } + return "" +} + +func (c *oauthCredentials) needsRefresh() bool { + if c.APIKey != "" { + return false + } + if c.Tokens == nil || c.Tokens.RefreshToken == "" { + return false + } + if c.LastRefresh == nil { + return true + } + return time.Since(*c.LastRefresh) >= time.Duration(tokenRefreshIntervalDays)*24*time.Hour +} + +func refreshToken(httpClient *http.Client, credentials *oauthCredentials) (*oauthCredentials, error) { + if credentials.Tokens == nil || credentials.Tokens.RefreshToken == "" { + return nil, E.New("refresh token is empty") + } + + requestBody, err := json.Marshal(map[string]string{ + "grant_type": "refresh_token", + "refresh_token": credentials.Tokens.RefreshToken, + "client_id": oauth2ClientID, + "scope": "openid profile email", + }) + if err != nil { + return nil, E.Cause(err, "marshal request") + } + + request, err := http.NewRequest("POST", oauth2TokenURL, bytes.NewReader(requestBody)) + if err != nil { + return nil, err + } + request.Header.Set("Content-Type", "application/json") + request.Header.Set("Accept", "application/json") + + response, err := httpClient.Do(request) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + body, _ := io.ReadAll(response.Body) + return nil, E.New("refresh failed: ", response.Status, " ", string(body)) + } + + var tokenResponse struct { + IDToken string `json:"id_token"` + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + } + err = json.NewDecoder(response.Body).Decode(&tokenResponse) + if err != nil { + return nil, E.Cause(err, "decode response") + } + + newCredentials := *credentials + if newCredentials.Tokens == nil { + newCredentials.Tokens = &tokenData{} + } + if tokenResponse.IDToken != "" { + newCredentials.Tokens.IDToken = tokenResponse.IDToken + } + if tokenResponse.AccessToken != "" { + newCredentials.Tokens.AccessToken = tokenResponse.AccessToken + } + if tokenResponse.RefreshToken != "" { + newCredentials.Tokens.RefreshToken = tokenResponse.RefreshToken + } + now := time.Now() + newCredentials.LastRefresh = &now + + return &newCredentials, nil +} diff --git a/service/ocm/credential_darwin.go b/service/ocm/credential_darwin.go new file mode 100644 index 0000000000..f3da2a63ed --- /dev/null +++ b/service/ocm/credential_darwin.go @@ -0,0 +1,25 @@ +//go:build darwin + +package ocm + +func platformReadCredentials(customPath string) (*oauthCredentials, error) { + if customPath == "" { + var err error + customPath, err = getDefaultCredentialsPath() + if err != nil { + return nil, err + } + } + return readCredentialsFromFile(customPath) +} + +func platformWriteCredentials(credentials *oauthCredentials, customPath string) error { + if customPath == "" { + var err error + customPath, err = getDefaultCredentialsPath() + if err != nil { + return err + } + } + return writeCredentialsToFile(credentials, customPath) +} diff --git a/service/ocm/credential_other.go b/service/ocm/credential_other.go new file mode 100644 index 0000000000..22dfd0337a --- /dev/null +++ b/service/ocm/credential_other.go @@ -0,0 +1,25 @@ +//go:build !darwin + +package ocm + +func platformReadCredentials(customPath string) (*oauthCredentials, error) { + if customPath == "" { + var err error + customPath, err = getDefaultCredentialsPath() + if err != nil { + return nil, err + } + } + return readCredentialsFromFile(customPath) +} + +func platformWriteCredentials(credentials *oauthCredentials, customPath string) error { + if customPath == "" { + var err error + customPath, err = getDefaultCredentialsPath() + if err != nil { + return err + } + } + return writeCredentialsToFile(credentials, customPath) +} diff --git a/service/ocm/service.go b/service/ocm/service.go new file mode 100644 index 0000000000..18bae4573c --- /dev/null +++ b/service/ocm/service.go @@ -0,0 +1,707 @@ +package ocm + +import ( + "bytes" + "context" + stdTLS "crypto/tls" + "encoding/json" + "errors" + "io" + "mime" + "net" + "net/http" + "strconv" + "strings" + "sync" + "time" + + "github.com/sagernet/sing-box/adapter" + boxService "github.com/sagernet/sing-box/adapter/service" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/ntp" + aTLS "github.com/sagernet/sing/common/tls" + + "github.com/go-chi/chi/v5" + "github.com/openai/openai-go/v3" + "github.com/openai/openai-go/v3/responses" + "golang.org/x/net/http2" +) + +func RegisterService(registry *boxService.Registry) { + boxService.Register[option.OCMServiceOptions](registry, C.TypeOCM, NewService) +} + +type errorResponse struct { + Error errorDetails `json:"error"` +} + +type errorDetails struct { + Type string `json:"type"` + Code string `json:"code,omitempty"` + Message string `json:"message"` +} + +func writeJSONError(w http.ResponseWriter, r *http.Request, statusCode int, errorType string, message string) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + + json.NewEncoder(w).Encode(errorResponse{ + Error: errorDetails{ + Type: errorType, + Message: message, + }, + }) +} + +func isHopByHopHeader(header string) bool { + switch strings.ToLower(header) { + case "connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailers", "transfer-encoding", "upgrade", "host": + return true + default: + return false + } +} + +func normalizeRateLimitIdentifier(limitIdentifier string) string { + trimmedIdentifier := strings.TrimSpace(strings.ToLower(limitIdentifier)) + if trimmedIdentifier == "" { + return "" + } + return strings.ReplaceAll(trimmedIdentifier, "_", "-") +} + +func parseInt64Header(headers http.Header, headerName string) (int64, bool) { + headerValue := strings.TrimSpace(headers.Get(headerName)) + if headerValue == "" { + return 0, false + } + parsedValue, parseError := strconv.ParseInt(headerValue, 10, 64) + if parseError != nil { + return 0, false + } + return parsedValue, true +} + +func weeklyCycleHintForLimit(headers http.Header, limitIdentifier string) *WeeklyCycleHint { + normalizedLimitIdentifier := normalizeRateLimitIdentifier(limitIdentifier) + if normalizedLimitIdentifier == "" { + return nil + } + + windowHeader := "x-" + normalizedLimitIdentifier + "-secondary-window-minutes" + resetHeader := "x-" + normalizedLimitIdentifier + "-secondary-reset-at" + + windowMinutes, hasWindowMinutes := parseInt64Header(headers, windowHeader) + resetAtUnix, hasResetAt := parseInt64Header(headers, resetHeader) + if !hasWindowMinutes || !hasResetAt || windowMinutes <= 0 || resetAtUnix <= 0 { + return nil + } + + return &WeeklyCycleHint{ + WindowMinutes: windowMinutes, + ResetAt: time.Unix(resetAtUnix, 0).UTC(), + } +} + +func extractWeeklyCycleHint(headers http.Header) *WeeklyCycleHint { + activeLimitIdentifier := normalizeRateLimitIdentifier(headers.Get("x-codex-active-limit")) + if activeLimitIdentifier != "" { + if activeHint := weeklyCycleHintForLimit(headers, activeLimitIdentifier); activeHint != nil { + return activeHint + } + } + return weeklyCycleHintForLimit(headers, "codex") +} + +type Service struct { + boxService.Adapter + ctx context.Context + logger log.ContextLogger + credentialPath string + credentials *oauthCredentials + users []option.OCMUser + dialer N.Dialer + httpClient *http.Client + httpHeaders http.Header + listener *listener.Listener + tlsConfig tls.ServerConfig + httpServer *http.Server + userManager *UserManager + accessMutex sync.RWMutex + usageTracker *AggregatedUsage + webSocketMutex sync.Mutex + webSocketGroup sync.WaitGroup + webSocketConns map[*webSocketSession]struct{} + shuttingDown bool +} + +func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.OCMServiceOptions) (adapter.Service, error) { + serviceDialer, err := dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: option.DialerOptions{ + Detour: options.Detour, + }, + RemoteIsDomain: true, + }) + if err != nil { + return nil, E.Cause(err, "create dialer") + } + + httpClient := &http.Client{ + Transport: &http.Transport{ + ForceAttemptHTTP2: true, + TLSClientConfig: &stdTLS.Config{ + RootCAs: adapter.RootPoolFromContext(ctx), + Time: ntp.TimeFuncFromContext(ctx), + }, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return serviceDialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) + }, + }, + } + + userManager := &UserManager{ + tokenMap: make(map[string]string), + } + + var usageTracker *AggregatedUsage + if options.UsagesPath != "" { + usageTracker = &AggregatedUsage{ + LastUpdated: time.Now(), + Combinations: make([]CostCombination, 0), + filePath: options.UsagesPath, + logger: logger, + } + } + + service := &Service{ + Adapter: boxService.NewAdapter(C.TypeOCM, tag), + ctx: ctx, + logger: logger, + credentialPath: options.CredentialPath, + users: options.Users, + dialer: serviceDialer, + httpClient: httpClient, + httpHeaders: options.Headers.Build(), + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + }), + userManager: userManager, + usageTracker: usageTracker, + webSocketConns: make(map[*webSocketSession]struct{}), + } + + if options.TLS != nil { + tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + service.tlsConfig = tlsConfig + } + + return service, nil +} + +func (s *Service) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + + s.userManager.UpdateUsers(s.users) + + credentials, err := platformReadCredentials(s.credentialPath) + if err != nil { + return E.Cause(err, "read credentials") + } + s.credentials = credentials + + if s.usageTracker != nil { + err = s.usageTracker.Load() + if err != nil { + s.logger.Warn("load usage statistics: ", err) + } + } + + router := chi.NewRouter() + router.Mount("/", s) + + s.httpServer = &http.Server{Handler: router} + + if s.tlsConfig != nil { + err = s.tlsConfig.Start() + if err != nil { + return E.Cause(err, "create TLS config") + } + } + + tcpListener, err := s.listener.ListenTCP() + if err != nil { + return err + } + + if s.tlsConfig != nil { + if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) { + s.tlsConfig.SetNextProtos(append([]string{"h2"}, s.tlsConfig.NextProtos()...)) + } + tcpListener = aTLS.NewListener(tcpListener, s.tlsConfig) + } + + go func() { + serveErr := s.httpServer.Serve(tcpListener) + if serveErr != nil && !errors.Is(serveErr, http.ErrServerClosed) { + s.logger.Error("serve error: ", serveErr) + } + }() + + return nil +} + +func (s *Service) getAccessToken() (string, error) { + s.accessMutex.RLock() + if !s.credentials.needsRefresh() { + token := s.credentials.getAccessToken() + s.accessMutex.RUnlock() + return token, nil + } + s.accessMutex.RUnlock() + + s.accessMutex.Lock() + defer s.accessMutex.Unlock() + + if !s.credentials.needsRefresh() { + return s.credentials.getAccessToken(), nil + } + + newCredentials, err := refreshToken(s.httpClient, s.credentials) + if err != nil { + return "", err + } + + s.credentials = newCredentials + + err = platformWriteCredentials(newCredentials, s.credentialPath) + if err != nil { + s.logger.Warn("persist refreshed token: ", err) + } + + return newCredentials.getAccessToken(), nil +} + +func (s *Service) getAccountID() string { + s.accessMutex.RLock() + defer s.accessMutex.RUnlock() + return s.credentials.getAccountID() +} + +func (s *Service) isAPIKeyMode() bool { + s.accessMutex.RLock() + defer s.accessMutex.RUnlock() + return s.credentials.isAPIKeyMode() +} + +func (s *Service) getBaseURL() string { + if s.isAPIKeyMode() { + return openaiAPIBaseURL + } + return chatGPTBackendURL +} + +func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { + path := r.URL.Path + if !strings.HasPrefix(path, "/v1/") { + writeJSONError(w, r, http.StatusNotFound, "invalid_request_error", "path must start with /v1/") + return + } + + var proxyPath string + if s.isAPIKeyMode() { + proxyPath = path + } else { + if path == "/v1/chat/completions" { + writeJSONError(w, r, http.StatusBadRequest, "invalid_request_error", + "chat completions endpoint is only available in API key mode") + return + } + proxyPath = strings.TrimPrefix(path, "/v1") + } + + var username string + if len(s.users) > 0 { + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + s.logger.Warn("authentication failed for request from ", r.RemoteAddr, ": missing Authorization header") + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "missing api key") + return + } + clientToken := strings.TrimPrefix(authHeader, "Bearer ") + if clientToken == authHeader { + s.logger.Warn("authentication failed for request from ", r.RemoteAddr, ": invalid Authorization format") + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key format") + return + } + var ok bool + username, ok = s.userManager.Authenticate(clientToken) + if !ok { + s.logger.Warn("authentication failed for request from ", r.RemoteAddr, ": unknown key: ", clientToken) + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key") + return + } + } + + if strings.EqualFold(r.Header.Get("Upgrade"), "websocket") && strings.HasPrefix(path, "/v1/responses") { + s.handleWebSocket(w, r, proxyPath, username) + return + } + + var requestModel string + + if s.usageTracker != nil && r.Body != nil { + bodyBytes, err := io.ReadAll(r.Body) + if err == nil { + var request struct { + Model string `json:"model"` + } + err := json.Unmarshal(bodyBytes, &request) + if err == nil { + requestModel = request.Model + } + r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) + } + } + + accessToken, err := s.getAccessToken() + if err != nil { + s.logger.Error("get access token: ", err) + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "Authentication failed") + return + } + + proxyURL := s.getBaseURL() + proxyPath + if r.URL.RawQuery != "" { + proxyURL += "?" + r.URL.RawQuery + } + proxyRequest, err := http.NewRequestWithContext(r.Context(), r.Method, proxyURL, r.Body) + if err != nil { + s.logger.Error("create proxy request: ", err) + writeJSONError(w, r, http.StatusInternalServerError, "api_error", "Internal server error") + return + } + + for key, values := range r.Header { + if !isHopByHopHeader(key) && key != "Authorization" { + proxyRequest.Header[key] = values + } + } + + for key, values := range s.httpHeaders { + proxyRequest.Header.Del(key) + proxyRequest.Header[key] = values + } + + proxyRequest.Header.Set("Authorization", "Bearer "+accessToken) + + if accountID := s.getAccountID(); accountID != "" { + proxyRequest.Header.Set("ChatGPT-Account-Id", accountID) + } + + response, err := s.httpClient.Do(proxyRequest) + if err != nil { + writeJSONError(w, r, http.StatusBadGateway, "api_error", err.Error()) + return + } + defer response.Body.Close() + + for key, values := range response.Header { + if !isHopByHopHeader(key) { + w.Header()[key] = values + } + } + w.WriteHeader(response.StatusCode) + + trackUsage := s.usageTracker != nil && response.StatusCode == http.StatusOK && + (path == "/v1/chat/completions" || strings.HasPrefix(path, "/v1/responses")) + if trackUsage { + s.handleResponseWithTracking(w, response, path, requestModel, username) + } else { + mediaType, _, err := mime.ParseMediaType(response.Header.Get("Content-Type")) + if err == nil && mediaType != "text/event-stream" { + _, _ = io.Copy(w, response.Body) + return + } + flusher, ok := w.(http.Flusher) + if !ok { + s.logger.Error("streaming not supported") + return + } + buffer := make([]byte, buf.BufferSize) + for { + n, err := response.Body.Read(buffer) + if n > 0 { + _, writeError := w.Write(buffer[:n]) + if writeError != nil { + s.logger.Error("write streaming response: ", writeError) + return + } + flusher.Flush() + } + if err != nil { + return + } + } + } +} + +func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, response *http.Response, path string, requestModel string, username string) { + isChatCompletions := path == "/v1/chat/completions" + weeklyCycleHint := extractWeeklyCycleHint(response.Header) + mediaType, _, err := mime.ParseMediaType(response.Header.Get("Content-Type")) + isStreaming := err == nil && mediaType == "text/event-stream" + if !isStreaming && !isChatCompletions && response.Header.Get("Content-Type") == "" { + isStreaming = true + } + if !isStreaming { + bodyBytes, err := io.ReadAll(response.Body) + if err != nil { + s.logger.Error("read response body: ", err) + return + } + + var responseModel, serviceTier string + var inputTokens, outputTokens, cachedTokens int64 + + if isChatCompletions { + var chatCompletion openai.ChatCompletion + if json.Unmarshal(bodyBytes, &chatCompletion) == nil { + responseModel = chatCompletion.Model + serviceTier = string(chatCompletion.ServiceTier) + inputTokens = chatCompletion.Usage.PromptTokens + outputTokens = chatCompletion.Usage.CompletionTokens + cachedTokens = chatCompletion.Usage.PromptTokensDetails.CachedTokens + } + } else { + var responsesResponse responses.Response + if json.Unmarshal(bodyBytes, &responsesResponse) == nil { + responseModel = string(responsesResponse.Model) + serviceTier = string(responsesResponse.ServiceTier) + inputTokens = responsesResponse.Usage.InputTokens + outputTokens = responsesResponse.Usage.OutputTokens + cachedTokens = responsesResponse.Usage.InputTokensDetails.CachedTokens + } + } + + if inputTokens > 0 || outputTokens > 0 { + if responseModel == "" { + responseModel = requestModel + } + if responseModel != "" { + contextWindow := detectContextWindow(responseModel, serviceTier, inputTokens) + s.usageTracker.AddUsageWithCycleHint( + responseModel, + contextWindow, + inputTokens, + outputTokens, + cachedTokens, + serviceTier, + username, + time.Now(), + weeklyCycleHint, + ) + } + } + + _, _ = writer.Write(bodyBytes) + return + } + + flusher, ok := writer.(http.Flusher) + if !ok { + s.logger.Error("streaming not supported") + return + } + + var inputTokens, outputTokens, cachedTokens int64 + var responseModel, serviceTier string + buffer := make([]byte, buf.BufferSize) + var leftover []byte + + for { + n, err := response.Body.Read(buffer) + if n > 0 { + data := append(leftover, buffer[:n]...) + lines := bytes.Split(data, []byte("\n")) + + if err == nil { + leftover = lines[len(lines)-1] + lines = lines[:len(lines)-1] + } else { + leftover = nil + } + + for _, line := range lines { + line = bytes.TrimSpace(line) + if len(line) == 0 { + continue + } + + if after, ok0 := bytes.CutPrefix(line, []byte("data: ")); ok0 { + eventData := after + if bytes.Equal(eventData, []byte("[DONE]")) { + continue + } + + if isChatCompletions { + var chatChunk openai.ChatCompletionChunk + if json.Unmarshal(eventData, &chatChunk) == nil { + if chatChunk.Model != "" { + responseModel = chatChunk.Model + } + if chatChunk.ServiceTier != "" { + serviceTier = string(chatChunk.ServiceTier) + } + if chatChunk.Usage.PromptTokens > 0 { + inputTokens = chatChunk.Usage.PromptTokens + cachedTokens = chatChunk.Usage.PromptTokensDetails.CachedTokens + } + if chatChunk.Usage.CompletionTokens > 0 { + outputTokens = chatChunk.Usage.CompletionTokens + } + } + } else { + var streamEvent responses.ResponseStreamEventUnion + if json.Unmarshal(eventData, &streamEvent) == nil { + if streamEvent.Type == "response.completed" { + completedEvent := streamEvent.AsResponseCompleted() + if string(completedEvent.Response.Model) != "" { + responseModel = string(completedEvent.Response.Model) + } + if completedEvent.Response.ServiceTier != "" { + serviceTier = string(completedEvent.Response.ServiceTier) + } + if completedEvent.Response.Usage.InputTokens > 0 { + inputTokens = completedEvent.Response.Usage.InputTokens + cachedTokens = completedEvent.Response.Usage.InputTokensDetails.CachedTokens + } + if completedEvent.Response.Usage.OutputTokens > 0 { + outputTokens = completedEvent.Response.Usage.OutputTokens + } + } + } + } + } + } + + _, writeError := writer.Write(buffer[:n]) + if writeError != nil { + s.logger.Error("write streaming response: ", writeError) + return + } + flusher.Flush() + } + + if err != nil { + if responseModel == "" { + responseModel = requestModel + } + + if inputTokens > 0 || outputTokens > 0 { + if responseModel != "" { + contextWindow := detectContextWindow(responseModel, serviceTier, inputTokens) + s.usageTracker.AddUsageWithCycleHint( + responseModel, + contextWindow, + inputTokens, + outputTokens, + cachedTokens, + serviceTier, + username, + time.Now(), + weeklyCycleHint, + ) + } + } + return + } + } +} + +func (s *Service) Close() error { + webSocketSessions := s.startWebSocketShutdown() + + err := common.Close( + common.PtrOrNil(s.httpServer), + common.PtrOrNil(s.listener), + s.tlsConfig, + ) + for _, session := range webSocketSessions { + session.Close() + } + s.webSocketGroup.Wait() + + if s.usageTracker != nil { + s.usageTracker.cancelPendingSave() + saveErr := s.usageTracker.Save() + if saveErr != nil { + s.logger.Error("save usage statistics: ", saveErr) + } + } + + return err +} + +func (s *Service) registerWebSocketSession(session *webSocketSession) bool { + s.webSocketMutex.Lock() + defer s.webSocketMutex.Unlock() + + if s.shuttingDown { + return false + } + + s.webSocketConns[session] = struct{}{} + s.webSocketGroup.Add(1) + return true +} + +func (s *Service) unregisterWebSocketSession(session *webSocketSession) { + s.webSocketMutex.Lock() + _, loaded := s.webSocketConns[session] + if loaded { + delete(s.webSocketConns, session) + } + s.webSocketMutex.Unlock() + + if loaded { + s.webSocketGroup.Done() + } +} + +func (s *Service) isShuttingDown() bool { + s.webSocketMutex.Lock() + defer s.webSocketMutex.Unlock() + return s.shuttingDown +} + +func (s *Service) startWebSocketShutdown() []*webSocketSession { + s.webSocketMutex.Lock() + defer s.webSocketMutex.Unlock() + + s.shuttingDown = true + + webSocketSessions := make([]*webSocketSession, 0, len(s.webSocketConns)) + for session := range s.webSocketConns { + webSocketSessions = append(webSocketSessions, session) + } + return webSocketSessions +} diff --git a/service/ocm/service_usage.go b/service/ocm/service_usage.go new file mode 100644 index 0000000000..18696f3b44 --- /dev/null +++ b/service/ocm/service_usage.go @@ -0,0 +1,1199 @@ +package ocm + +import ( + "encoding/json" + "fmt" + "math" + "os" + "regexp" + "strings" + "sync" + "time" + + "github.com/sagernet/sing-box/log" + E "github.com/sagernet/sing/common/exceptions" +) + +type UsageStats struct { + RequestCount int `json:"request_count"` + InputTokens int64 `json:"input_tokens"` + OutputTokens int64 `json:"output_tokens"` + CachedTokens int64 `json:"cached_tokens"` +} + +func (u *UsageStats) UnmarshalJSON(data []byte) error { + type Alias UsageStats + aux := &struct { + *Alias + PromptTokens int64 `json:"prompt_tokens"` + CompletionTokens int64 `json:"completion_tokens"` + }{ + Alias: (*Alias)(u), + } + err := json.Unmarshal(data, aux) + if err != nil { + return err + } + if u.InputTokens == 0 && aux.PromptTokens > 0 { + u.InputTokens = aux.PromptTokens + } + if u.OutputTokens == 0 && aux.CompletionTokens > 0 { + u.OutputTokens = aux.CompletionTokens + } + return nil +} + +type CostCombination struct { + Model string `json:"model"` + ServiceTier string `json:"service_tier,omitempty"` + ContextWindow int `json:"context_window"` + WeekStartUnix int64 `json:"week_start_unix,omitempty"` + Total UsageStats `json:"total"` + ByUser map[string]UsageStats `json:"by_user"` +} + +type AggregatedUsage struct { + LastUpdated time.Time `json:"last_updated"` + Combinations []CostCombination `json:"combinations"` + mutex sync.Mutex + filePath string + logger log.ContextLogger + lastSaveTime time.Time + pendingSave bool + saveTimer *time.Timer + saveMutex sync.Mutex +} + +type UsageStatsJSON struct { + RequestCount int `json:"request_count"` + InputTokens int64 `json:"input_tokens"` + OutputTokens int64 `json:"output_tokens"` + CachedTokens int64 `json:"cached_tokens"` + CostUSD float64 `json:"cost_usd"` +} + +type CostCombinationJSON struct { + Model string `json:"model"` + ServiceTier string `json:"service_tier,omitempty"` + ContextWindow int `json:"context_window"` + WeekStartUnix int64 `json:"week_start_unix,omitempty"` + Total UsageStatsJSON `json:"total"` + ByUser map[string]UsageStatsJSON `json:"by_user"` +} + +type CostsSummaryJSON struct { + TotalUSD float64 `json:"total_usd"` + ByUser map[string]float64 `json:"by_user"` + ByWeek map[string]float64 `json:"by_week,omitempty"` + ByUserAndWeek map[string]map[string]float64 `json:"by_user_and_week,omitempty"` +} + +type AggregatedUsageJSON struct { + LastUpdated time.Time `json:"last_updated"` + Costs CostsSummaryJSON `json:"costs"` + Combinations []CostCombinationJSON `json:"combinations"` +} + +type WeeklyCycleHint struct { + WindowMinutes int64 + ResetAt time.Time +} + +type ModelPricing struct { + InputPrice float64 + OutputPrice float64 + CachedInputPrice float64 +} + +type modelFamily struct { + pattern *regexp.Regexp + pricing ModelPricing + premiumPricing *ModelPricing +} + +const ( + serviceTierAuto = "auto" + serviceTierDefault = "default" + serviceTierFlex = "flex" + serviceTierPriority = "priority" + serviceTierScale = "scale" +) + +const ( + contextWindowStandard = 272000 + contextWindowPremium = 1050000 + premiumContextThreshold = 272000 +) + +var ( + gpt52Pricing = ModelPricing{ + InputPrice: 1.75, + OutputPrice: 14.0, + CachedInputPrice: 0.175, + } + + gpt5Pricing = ModelPricing{ + InputPrice: 1.25, + OutputPrice: 10.0, + CachedInputPrice: 0.125, + } + + gpt5MiniPricing = ModelPricing{ + InputPrice: 0.25, + OutputPrice: 2.0, + CachedInputPrice: 0.025, + } + + gpt5NanoPricing = ModelPricing{ + InputPrice: 0.05, + OutputPrice: 0.4, + CachedInputPrice: 0.005, + } + + gpt52CodexPricing = ModelPricing{ + InputPrice: 1.75, + OutputPrice: 14.0, + CachedInputPrice: 0.175, + } + + gpt51CodexPricing = ModelPricing{ + InputPrice: 1.25, + OutputPrice: 10.0, + CachedInputPrice: 0.125, + } + + gpt51CodexMiniPricing = ModelPricing{ + InputPrice: 0.25, + OutputPrice: 2.0, + CachedInputPrice: 0.025, + } + + gpt54StandardPricing = ModelPricing{ + InputPrice: 2.5, + OutputPrice: 15.0, + CachedInputPrice: 0.25, + } + + gpt54PremiumPricing = ModelPricing{ + InputPrice: 5.0, + OutputPrice: 22.5, + CachedInputPrice: 0.5, + } + + gpt54ProPricing = ModelPricing{ + InputPrice: 30.0, + OutputPrice: 180.0, + CachedInputPrice: 30.0, + } + + gpt54ProPremiumPricing = ModelPricing{ + InputPrice: 60.0, + OutputPrice: 270.0, + CachedInputPrice: 60.0, + } + + gpt52ProPricing = ModelPricing{ + InputPrice: 21.0, + OutputPrice: 168.0, + CachedInputPrice: 21.0, + } + + gpt5ProPricing = ModelPricing{ + InputPrice: 15.0, + OutputPrice: 120.0, + CachedInputPrice: 15.0, + } + + gpt54FlexPricing = ModelPricing{ + InputPrice: 1.25, + OutputPrice: 7.5, + CachedInputPrice: 0.125, + } + + gpt54PremiumFlexPricing = ModelPricing{ + InputPrice: 2.5, + OutputPrice: 11.25, + CachedInputPrice: 0.25, + } + + gpt54ProFlexPricing = ModelPricing{ + InputPrice: 15.0, + OutputPrice: 90.0, + CachedInputPrice: 15.0, + } + + gpt54ProPremiumFlexPricing = ModelPricing{ + InputPrice: 30.0, + OutputPrice: 135.0, + CachedInputPrice: 30.0, + } + + gpt52FlexPricing = ModelPricing{ + InputPrice: 0.875, + OutputPrice: 7.0, + CachedInputPrice: 0.0875, + } + + gpt5FlexPricing = ModelPricing{ + InputPrice: 0.625, + OutputPrice: 5.0, + CachedInputPrice: 0.0625, + } + + gpt5MiniFlexPricing = ModelPricing{ + InputPrice: 0.125, + OutputPrice: 1.0, + CachedInputPrice: 0.0125, + } + + gpt5NanoFlexPricing = ModelPricing{ + InputPrice: 0.025, + OutputPrice: 0.2, + CachedInputPrice: 0.0025, + } + + gpt54PriorityPricing = ModelPricing{ + InputPrice: 5.0, + OutputPrice: 30.0, + CachedInputPrice: 0.5, + } + + gpt54PremiumPriorityPricing = ModelPricing{ + InputPrice: 10.0, + OutputPrice: 45.0, + CachedInputPrice: 1.0, + } + + gpt52PriorityPricing = ModelPricing{ + InputPrice: 3.5, + OutputPrice: 28.0, + CachedInputPrice: 0.35, + } + + gpt5PriorityPricing = ModelPricing{ + InputPrice: 2.5, + OutputPrice: 20.0, + CachedInputPrice: 0.25, + } + + gpt5MiniPriorityPricing = ModelPricing{ + InputPrice: 0.45, + OutputPrice: 3.6, + CachedInputPrice: 0.045, + } + + gpt52CodexPriorityPricing = ModelPricing{ + InputPrice: 3.5, + OutputPrice: 28.0, + CachedInputPrice: 0.35, + } + + gpt51CodexPriorityPricing = ModelPricing{ + InputPrice: 2.5, + OutputPrice: 20.0, + CachedInputPrice: 0.25, + } + + gpt4oPricing = ModelPricing{ + InputPrice: 2.5, + OutputPrice: 10.0, + CachedInputPrice: 1.25, + } + + gpt4oMiniPricing = ModelPricing{ + InputPrice: 0.15, + OutputPrice: 0.6, + CachedInputPrice: 0.075, + } + + gpt4oAudioPricing = ModelPricing{ + InputPrice: 2.5, + OutputPrice: 10.0, + CachedInputPrice: 2.5, + } + + gpt4oMiniAudioPricing = ModelPricing{ + InputPrice: 0.15, + OutputPrice: 0.6, + CachedInputPrice: 0.15, + } + + gptAudioMiniPricing = ModelPricing{ + InputPrice: 0.6, + OutputPrice: 2.4, + CachedInputPrice: 0.6, + } + + o1Pricing = ModelPricing{ + InputPrice: 15.0, + OutputPrice: 60.0, + CachedInputPrice: 7.5, + } + + o1ProPricing = ModelPricing{ + InputPrice: 150.0, + OutputPrice: 600.0, + CachedInputPrice: 150.0, + } + + o1MiniPricing = ModelPricing{ + InputPrice: 1.1, + OutputPrice: 4.4, + CachedInputPrice: 0.55, + } + + o3MiniPricing = ModelPricing{ + InputPrice: 1.1, + OutputPrice: 4.4, + CachedInputPrice: 0.55, + } + + o3Pricing = ModelPricing{ + InputPrice: 2.0, + OutputPrice: 8.0, + CachedInputPrice: 0.5, + } + + o3ProPricing = ModelPricing{ + InputPrice: 20.0, + OutputPrice: 80.0, + CachedInputPrice: 20.0, + } + + o3DeepResearchPricing = ModelPricing{ + InputPrice: 10.0, + OutputPrice: 40.0, + CachedInputPrice: 2.5, + } + + o4MiniPricing = ModelPricing{ + InputPrice: 1.1, + OutputPrice: 4.4, + CachedInputPrice: 0.275, + } + + o4MiniDeepResearchPricing = ModelPricing{ + InputPrice: 2.0, + OutputPrice: 8.0, + CachedInputPrice: 0.5, + } + + o3FlexPricing = ModelPricing{ + InputPrice: 1.0, + OutputPrice: 4.0, + CachedInputPrice: 0.25, + } + + o4MiniFlexPricing = ModelPricing{ + InputPrice: 0.55, + OutputPrice: 2.2, + CachedInputPrice: 0.138, + } + + o3PriorityPricing = ModelPricing{ + InputPrice: 3.5, + OutputPrice: 14.0, + CachedInputPrice: 0.875, + } + + o4MiniPriorityPricing = ModelPricing{ + InputPrice: 2.0, + OutputPrice: 8.0, + CachedInputPrice: 0.5, + } + + gpt41Pricing = ModelPricing{ + InputPrice: 2.0, + OutputPrice: 8.0, + CachedInputPrice: 0.5, + } + + gpt41MiniPricing = ModelPricing{ + InputPrice: 0.4, + OutputPrice: 1.6, + CachedInputPrice: 0.1, + } + + gpt41NanoPricing = ModelPricing{ + InputPrice: 0.1, + OutputPrice: 0.4, + CachedInputPrice: 0.025, + } + + gpt41PriorityPricing = ModelPricing{ + InputPrice: 3.5, + OutputPrice: 14.0, + CachedInputPrice: 0.875, + } + + gpt41MiniPriorityPricing = ModelPricing{ + InputPrice: 0.7, + OutputPrice: 2.8, + CachedInputPrice: 0.175, + } + + gpt41NanoPriorityPricing = ModelPricing{ + InputPrice: 0.2, + OutputPrice: 0.8, + CachedInputPrice: 0.05, + } + + gpt4oPriorityPricing = ModelPricing{ + InputPrice: 4.25, + OutputPrice: 17.0, + CachedInputPrice: 2.125, + } + + gpt4oMiniPriorityPricing = ModelPricing{ + InputPrice: 0.25, + OutputPrice: 1.0, + CachedInputPrice: 0.125, + } + + standardModelFamilies = []modelFamily{ + { + pattern: regexp.MustCompile(`^gpt-5\.4-pro(?:$|-)`), + pricing: gpt54ProPricing, + premiumPricing: &gpt54ProPremiumPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.4(?:$|-)`), + pricing: gpt54StandardPricing, + premiumPricing: &gpt54PremiumPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.3-codex(?:$|-)`), + pricing: gpt52CodexPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.2-codex(?:$|-)`), + pricing: gpt52CodexPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.1-codex-max(?:$|-)`), + pricing: gpt51CodexPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.1-codex-mini(?:$|-)`), + pricing: gpt51CodexMiniPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.1-codex(?:$|-)`), + pricing: gpt51CodexPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-codex-mini(?:$|-)`), + pricing: gpt51CodexMiniPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-codex(?:$|-)`), + pricing: gpt51CodexPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.2-chat-latest$`), + pricing: gpt52Pricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.1-chat-latest$`), + pricing: gpt5Pricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-chat-latest$`), + pricing: gpt5Pricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.2-pro(?:$|-)`), + pricing: gpt52ProPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-pro(?:$|-)`), + pricing: gpt5ProPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-mini(?:$|-)`), + pricing: gpt5MiniPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-nano(?:$|-)`), + pricing: gpt5NanoPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.2(?:$|-)`), + pricing: gpt52Pricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.1(?:$|-)`), + pricing: gpt5Pricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5(?:$|-)`), + pricing: gpt5Pricing, + }, + { + pattern: regexp.MustCompile(`^o4-mini-deep-research(?:$|-)`), + pricing: o4MiniDeepResearchPricing, + }, + { + pattern: regexp.MustCompile(`^o4-mini(?:$|-)`), + pricing: o4MiniPricing, + }, + { + pattern: regexp.MustCompile(`^o3-pro(?:$|-)`), + pricing: o3ProPricing, + }, + { + pattern: regexp.MustCompile(`^o3-deep-research(?:$|-)`), + pricing: o3DeepResearchPricing, + }, + { + pattern: regexp.MustCompile(`^o3-mini(?:$|-)`), + pricing: o3MiniPricing, + }, + { + pattern: regexp.MustCompile(`^o3(?:$|-)`), + pricing: o3Pricing, + }, + { + pattern: regexp.MustCompile(`^o1-pro(?:$|-)`), + pricing: o1ProPricing, + }, + { + pattern: regexp.MustCompile(`^o1-mini(?:$|-)`), + pricing: o1MiniPricing, + }, + { + pattern: regexp.MustCompile(`^o1(?:$|-)`), + pricing: o1Pricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4o-mini-audio(?:$|-)`), + pricing: gpt4oMiniAudioPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-audio-mini(?:$|-)`), + pricing: gptAudioMiniPricing, + }, + { + pattern: regexp.MustCompile(`^(?:gpt-4o-audio|gpt-audio)(?:$|-)`), + pricing: gpt4oAudioPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4\.1-nano(?:$|-)`), + pricing: gpt41NanoPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4\.1-mini(?:$|-)`), + pricing: gpt41MiniPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4\.1(?:$|-)`), + pricing: gpt41Pricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4o-mini(?:$|-)`), + pricing: gpt4oMiniPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4o(?:$|-)`), + pricing: gpt4oPricing, + }, + { + pattern: regexp.MustCompile(`^chatgpt-4o(?:$|-)`), + pricing: gpt4oPricing, + }, + } + + flexModelFamilies = []modelFamily{ + { + pattern: regexp.MustCompile(`^gpt-5\.4-pro(?:$|-)`), + pricing: gpt54ProFlexPricing, + premiumPricing: &gpt54ProPremiumFlexPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.4(?:$|-)`), + pricing: gpt54FlexPricing, + premiumPricing: &gpt54PremiumFlexPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-mini(?:$|-)`), + pricing: gpt5MiniFlexPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-nano(?:$|-)`), + pricing: gpt5NanoFlexPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.2(?:$|-)`), + pricing: gpt52FlexPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.1(?:$|-)`), + pricing: gpt5FlexPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5(?:$|-)`), + pricing: gpt5FlexPricing, + }, + { + pattern: regexp.MustCompile(`^o4-mini(?:$|-)`), + pricing: o4MiniFlexPricing, + }, + { + pattern: regexp.MustCompile(`^o3(?:$|-)`), + pricing: o3FlexPricing, + }, + } + + priorityModelFamilies = []modelFamily{ + { + pattern: regexp.MustCompile(`^gpt-5\.4(?:$|-)`), + pricing: gpt54PriorityPricing, + premiumPricing: &gpt54PremiumPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.3-codex(?:$|-)`), + pricing: gpt52CodexPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.2-codex(?:$|-)`), + pricing: gpt52CodexPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.1-codex-max(?:$|-)`), + pricing: gpt51CodexPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.1-codex(?:$|-)`), + pricing: gpt51CodexPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-codex-mini(?:$|-)`), + pricing: gpt5MiniPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-codex(?:$|-)`), + pricing: gpt51CodexPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-mini(?:$|-)`), + pricing: gpt5MiniPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.2(?:$|-)`), + pricing: gpt52PriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.1(?:$|-)`), + pricing: gpt5PriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5(?:$|-)`), + pricing: gpt5PriorityPricing, + }, + { + pattern: regexp.MustCompile(`^o4-mini(?:$|-)`), + pricing: o4MiniPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^o3(?:$|-)`), + pricing: o3PriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4\.1-nano(?:$|-)`), + pricing: gpt41NanoPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4\.1-mini(?:$|-)`), + pricing: gpt41MiniPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4\.1(?:$|-)`), + pricing: gpt41PriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4o-mini(?:$|-)`), + pricing: gpt4oMiniPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4o(?:$|-)`), + pricing: gpt4oPriorityPricing, + }, + } +) + +func modelFamiliesForTier(serviceTier string) []modelFamily { + switch serviceTier { + case serviceTierFlex: + return flexModelFamilies + case serviceTierPriority: + return priorityModelFamilies + default: + return standardModelFamilies + } +} + +func findPricingInFamilies(model string, contextWindow int, modelFamilies []modelFamily) (ModelPricing, bool) { + isPremium := contextWindow >= contextWindowPremium + for _, family := range modelFamilies { + if family.pattern.MatchString(model) { + if isPremium && family.premiumPricing != nil { + return *family.premiumPricing, true + } + return family.pricing, true + } + } + return ModelPricing{}, false +} + +func hasPremiumPricingInFamilies(model string, modelFamilies []modelFamily) bool { + for _, family := range modelFamilies { + if family.pattern.MatchString(model) { + return family.premiumPricing != nil + } + } + return false +} + +func normalizeServiceTier(serviceTier string) string { + switch strings.ToLower(strings.TrimSpace(serviceTier)) { + case "", serviceTierAuto, serviceTierDefault: + return serviceTierDefault + case serviceTierFlex: + return serviceTierFlex + case serviceTierPriority: + return serviceTierPriority + case serviceTierScale: + // Scale-tier requests are prepaid differently and not listed in this usage file. + return serviceTierDefault + default: + return serviceTierDefault + } +} + +func getPricing(model string, serviceTier string, contextWindow int) ModelPricing { + normalizedServiceTier := normalizeServiceTier(serviceTier) + families := modelFamiliesForTier(normalizedServiceTier) + + if pricing, found := findPricingInFamilies(model, contextWindow, families); found { + return pricing + } + + normalizedModel := normalizeGPT5Model(model) + if normalizedModel != model { + if pricing, found := findPricingInFamilies(normalizedModel, contextWindow, families); found { + return pricing + } + } + + if normalizedServiceTier != serviceTierDefault { + if pricing, found := findPricingInFamilies(model, contextWindow, standardModelFamilies); found { + return pricing + } + if normalizedModel != model { + if pricing, found := findPricingInFamilies(normalizedModel, contextWindow, standardModelFamilies); found { + return pricing + } + } + } + + return gpt4oPricing +} + +func detectContextWindow(model string, serviceTier string, inputTokens int64) int { + if inputTokens <= premiumContextThreshold { + return contextWindowStandard + } + normalizedServiceTier := normalizeServiceTier(serviceTier) + families := modelFamiliesForTier(normalizedServiceTier) + if hasPremiumPricingInFamilies(model, families) { + return contextWindowPremium + } + normalizedModel := normalizeGPT5Model(model) + if normalizedModel != model && hasPremiumPricingInFamilies(normalizedModel, families) { + return contextWindowPremium + } + if normalizedServiceTier != serviceTierDefault { + if hasPremiumPricingInFamilies(model, standardModelFamilies) { + return contextWindowPremium + } + if normalizedModel != model && hasPremiumPricingInFamilies(normalizedModel, standardModelFamilies) { + return contextWindowPremium + } + } + return contextWindowStandard +} + +func normalizeGPT5Model(model string) string { + if !strings.HasPrefix(model, "gpt-5.") { + return model + } + + switch { + case strings.Contains(model, "-codex-mini"): + return "gpt-5.1-codex-mini" + case strings.Contains(model, "-codex-max"): + return "gpt-5.1-codex-max" + case strings.Contains(model, "-codex"): + return "gpt-5.3-codex" + case strings.Contains(model, "-chat-latest"): + return "gpt-5.2-chat-latest" + case strings.Contains(model, "-pro"): + return "gpt-5.4-pro" + case strings.Contains(model, "-mini"): + return "gpt-5-mini" + case strings.Contains(model, "-nano"): + return "gpt-5-nano" + default: + return "gpt-5.4" + } +} + +func calculateCost(stats UsageStats, model string, serviceTier string, contextWindow int) float64 { + pricing := getPricing(model, serviceTier, contextWindow) + + regularInputTokens := max(stats.InputTokens-stats.CachedTokens, 0) + + cost := (float64(regularInputTokens)*pricing.InputPrice + + float64(stats.OutputTokens)*pricing.OutputPrice + + float64(stats.CachedTokens)*pricing.CachedInputPrice) / 1_000_000 + + return math.Round(cost*100) / 100 +} + +func roundCost(cost float64) float64 { + return math.Round(cost*100) / 100 +} + +func normalizeCombinations(combinations []CostCombination) { + for index := range combinations { + combinations[index].ServiceTier = normalizeServiceTier(combinations[index].ServiceTier) + if combinations[index].ContextWindow <= 0 { + combinations[index].ContextWindow = contextWindowStandard + } + if combinations[index].ByUser == nil { + combinations[index].ByUser = make(map[string]UsageStats) + } + } +} + +func addUsageToCombinations(combinations *[]CostCombination, model string, serviceTier string, contextWindow int, weekStartUnix int64, user string, inputTokens, outputTokens, cachedTokens int64) { + var matchedCombination *CostCombination + for index := range *combinations { + combination := &(*combinations)[index] + combinationServiceTier := normalizeServiceTier(combination.ServiceTier) + if combination.ServiceTier != combinationServiceTier { + combination.ServiceTier = combinationServiceTier + } + if combination.Model == model && combinationServiceTier == serviceTier && combination.ContextWindow == contextWindow && combination.WeekStartUnix == weekStartUnix { + matchedCombination = combination + break + } + } + + if matchedCombination == nil { + newCombination := CostCombination{ + Model: model, + ServiceTier: serviceTier, + ContextWindow: contextWindow, + WeekStartUnix: weekStartUnix, + Total: UsageStats{}, + ByUser: make(map[string]UsageStats), + } + *combinations = append(*combinations, newCombination) + matchedCombination = &(*combinations)[len(*combinations)-1] + } + + matchedCombination.Total.RequestCount++ + matchedCombination.Total.InputTokens += inputTokens + matchedCombination.Total.OutputTokens += outputTokens + matchedCombination.Total.CachedTokens += cachedTokens + + if user != "" { + userStats := matchedCombination.ByUser[user] + userStats.RequestCount++ + userStats.InputTokens += inputTokens + userStats.OutputTokens += outputTokens + userStats.CachedTokens += cachedTokens + matchedCombination.ByUser[user] = userStats + } +} + +func buildCombinationJSON(combinations []CostCombination, aggregateUserCosts map[string]float64) ([]CostCombinationJSON, float64) { + result := make([]CostCombinationJSON, len(combinations)) + var totalCost float64 + + for index, combination := range combinations { + combinationTotalCost := calculateCost(combination.Total, combination.Model, combination.ServiceTier, combination.ContextWindow) + totalCost += combinationTotalCost + + combinationJSON := CostCombinationJSON{ + Model: combination.Model, + ServiceTier: combination.ServiceTier, + ContextWindow: combination.ContextWindow, + WeekStartUnix: combination.WeekStartUnix, + Total: UsageStatsJSON{ + RequestCount: combination.Total.RequestCount, + InputTokens: combination.Total.InputTokens, + OutputTokens: combination.Total.OutputTokens, + CachedTokens: combination.Total.CachedTokens, + CostUSD: combinationTotalCost, + }, + ByUser: make(map[string]UsageStatsJSON), + } + + for user, userStats := range combination.ByUser { + userCost := calculateCost(userStats, combination.Model, combination.ServiceTier, combination.ContextWindow) + if aggregateUserCosts != nil { + aggregateUserCosts[user] += userCost + } + + combinationJSON.ByUser[user] = UsageStatsJSON{ + RequestCount: userStats.RequestCount, + InputTokens: userStats.InputTokens, + OutputTokens: userStats.OutputTokens, + CachedTokens: userStats.CachedTokens, + CostUSD: userCost, + } + } + + result[index] = combinationJSON + } + + return result, roundCost(totalCost) +} + +func formatUTCOffsetLabel(timestamp time.Time) string { + _, offsetSeconds := timestamp.Zone() + sign := "+" + if offsetSeconds < 0 { + sign = "-" + offsetSeconds = -offsetSeconds + } + offsetHours := offsetSeconds / 3600 + offsetMinutes := (offsetSeconds % 3600) / 60 + if offsetMinutes == 0 { + return fmt.Sprintf("UTC%s%d", sign, offsetHours) + } + return fmt.Sprintf("UTC%s%d:%02d", sign, offsetHours, offsetMinutes) +} + +func formatWeekStartKey(cycleStartAt time.Time) string { + localCycleStart := cycleStartAt.In(time.Local) + return fmt.Sprintf("%s %s", localCycleStart.Format("2006-01-02 15:04:05"), formatUTCOffsetLabel(localCycleStart)) +} + +func buildByWeekCost(combinations []CostCombination) map[string]float64 { + byWeek := make(map[string]float64) + for _, combination := range combinations { + if combination.WeekStartUnix <= 0 { + continue + } + weekStartAt := time.Unix(combination.WeekStartUnix, 0).UTC() + weekKey := formatWeekStartKey(weekStartAt) + byWeek[weekKey] += calculateCost(combination.Total, combination.Model, combination.ServiceTier, combination.ContextWindow) + } + for weekKey, weekCost := range byWeek { + byWeek[weekKey] = roundCost(weekCost) + } + return byWeek +} + +func buildByUserAndWeekCost(combinations []CostCombination) map[string]map[string]float64 { + byUserAndWeek := make(map[string]map[string]float64) + for _, combination := range combinations { + if combination.WeekStartUnix <= 0 { + continue + } + weekStartAt := time.Unix(combination.WeekStartUnix, 0).UTC() + weekKey := formatWeekStartKey(weekStartAt) + for user, userStats := range combination.ByUser { + userWeeks, exists := byUserAndWeek[user] + if !exists { + userWeeks = make(map[string]float64) + byUserAndWeek[user] = userWeeks + } + userWeeks[weekKey] += calculateCost(userStats, combination.Model, combination.ServiceTier, combination.ContextWindow) + } + } + for _, weekCosts := range byUserAndWeek { + for weekKey, cost := range weekCosts { + weekCosts[weekKey] = roundCost(cost) + } + } + return byUserAndWeek +} + +func deriveWeekStartUnix(cycleHint *WeeklyCycleHint) int64 { + if cycleHint == nil || cycleHint.WindowMinutes <= 0 || cycleHint.ResetAt.IsZero() { + return 0 + } + windowDuration := time.Duration(cycleHint.WindowMinutes) * time.Minute + return cycleHint.ResetAt.UTC().Add(-windowDuration).Unix() +} + +func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON { + u.mutex.Lock() + defer u.mutex.Unlock() + + result := &AggregatedUsageJSON{ + LastUpdated: u.LastUpdated, + Costs: CostsSummaryJSON{ + TotalUSD: 0, + ByUser: make(map[string]float64), + ByWeek: make(map[string]float64), + }, + } + + globalCombinationsJSON, totalCost := buildCombinationJSON(u.Combinations, result.Costs.ByUser) + result.Combinations = globalCombinationsJSON + result.Costs.TotalUSD = totalCost + result.Costs.ByWeek = buildByWeekCost(u.Combinations) + + if len(result.Costs.ByWeek) == 0 { + result.Costs.ByWeek = nil + } + + result.Costs.ByUserAndWeek = buildByUserAndWeekCost(u.Combinations) + if len(result.Costs.ByUserAndWeek) == 0 { + result.Costs.ByUserAndWeek = nil + } + + for user, cost := range result.Costs.ByUser { + result.Costs.ByUser[user] = roundCost(cost) + } + + return result +} + +func (u *AggregatedUsage) Load() error { + u.mutex.Lock() + defer u.mutex.Unlock() + + u.LastUpdated = time.Time{} + u.Combinations = nil + + data, err := os.ReadFile(u.filePath) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + + var temp struct { + LastUpdated time.Time `json:"last_updated"` + Combinations []CostCombination `json:"combinations"` + } + + err = json.Unmarshal(data, &temp) + if err != nil { + return err + } + + u.LastUpdated = temp.LastUpdated + u.Combinations = temp.Combinations + normalizeCombinations(u.Combinations) + + return nil +} + +func (u *AggregatedUsage) Save() error { + jsonData := u.ToJSON() + + data, err := json.MarshalIndent(jsonData, "", " ") + if err != nil { + return err + } + + tmpFile := u.filePath + ".tmp" + err = os.WriteFile(tmpFile, data, 0o644) + if err != nil { + return err + } + defer os.Remove(tmpFile) + err = os.Rename(tmpFile, u.filePath) + if err == nil { + u.saveMutex.Lock() + u.lastSaveTime = time.Now() + u.saveMutex.Unlock() + } + return err +} + +func (u *AggregatedUsage) AddUsage(model string, contextWindow int, inputTokens, outputTokens, cachedTokens int64, serviceTier string, user string) error { + return u.AddUsageWithCycleHint(model, contextWindow, inputTokens, outputTokens, cachedTokens, serviceTier, user, time.Now(), nil) +} + +func (u *AggregatedUsage) AddUsageWithCycleHint(model string, contextWindow int, inputTokens, outputTokens, cachedTokens int64, serviceTier string, user string, observedAt time.Time, cycleHint *WeeklyCycleHint) error { + if model == "" { + return E.New("model cannot be empty") + } + if contextWindow <= 0 { + return E.New("contextWindow must be positive") + } + + normalizedServiceTier := normalizeServiceTier(serviceTier) + if observedAt.IsZero() { + observedAt = time.Now() + } + + u.mutex.Lock() + defer u.mutex.Unlock() + + u.LastUpdated = observedAt + weekStartUnix := deriveWeekStartUnix(cycleHint) + + addUsageToCombinations(&u.Combinations, model, normalizedServiceTier, contextWindow, weekStartUnix, user, inputTokens, outputTokens, cachedTokens) + + go u.scheduleSave() + + return nil +} + +func (u *AggregatedUsage) scheduleSave() { + const saveInterval = time.Minute + + u.saveMutex.Lock() + defer u.saveMutex.Unlock() + + timeSinceLastSave := time.Since(u.lastSaveTime) + + if timeSinceLastSave >= saveInterval { + go u.saveAsync() + return + } + + if u.pendingSave { + return + } + + u.pendingSave = true + remainingTime := saveInterval - timeSinceLastSave + + u.saveTimer = time.AfterFunc(remainingTime, func() { + u.saveMutex.Lock() + u.pendingSave = false + u.saveMutex.Unlock() + u.saveAsync() + }) +} + +func (u *AggregatedUsage) saveAsync() { + err := u.Save() + if err != nil { + if u.logger != nil { + u.logger.Error("save usage statistics: ", err) + } + } +} + +func (u *AggregatedUsage) cancelPendingSave() { + u.saveMutex.Lock() + defer u.saveMutex.Unlock() + + if u.saveTimer != nil { + u.saveTimer.Stop() + u.saveTimer = nil + } + u.pendingSave = false +} diff --git a/service/ocm/service_user.go b/service/ocm/service_user.go new file mode 100644 index 0000000000..494b981b9b --- /dev/null +++ b/service/ocm/service_user.go @@ -0,0 +1,29 @@ +package ocm + +import ( + "sync" + + "github.com/sagernet/sing-box/option" +) + +type UserManager struct { + accessMutex sync.RWMutex + tokenMap map[string]string +} + +func (m *UserManager) UpdateUsers(users []option.OCMUser) { + m.accessMutex.Lock() + defer m.accessMutex.Unlock() + tokenMap := make(map[string]string, len(users)) + for _, user := range users { + tokenMap[user.Token] = user.Name + } + m.tokenMap = tokenMap +} + +func (m *UserManager) Authenticate(token string) (string, bool) { + m.accessMutex.RLock() + username, found := m.tokenMap[token] + m.accessMutex.RUnlock() + return username, found +} diff --git a/service/ocm/service_websocket.go b/service/ocm/service_websocket.go new file mode 100644 index 0000000000..d19f2df81b --- /dev/null +++ b/service/ocm/service_websocket.go @@ -0,0 +1,285 @@ +package ocm + +import ( + "context" + stdTLS "crypto/tls" + "encoding/json" + "io" + "net" + "net/http" + "strings" + "sync" + "time" + + "github.com/sagernet/sing-box/adapter" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/ntp" + "github.com/sagernet/ws" + "github.com/sagernet/ws/wsutil" + + "github.com/openai/openai-go/v3/responses" +) + +type webSocketSession struct { + clientConn net.Conn + upstreamConn net.Conn + closeOnce sync.Once +} + +func (s *webSocketSession) Close() { + s.closeOnce.Do(func() { + s.clientConn.Close() + s.upstreamConn.Close() + }) +} + +func buildUpstreamWebSocketURL(baseURL string, proxyPath string) string { + upstreamURL := baseURL + if strings.HasPrefix(upstreamURL, "https://") { + upstreamURL = "wss://" + upstreamURL[len("https://"):] + } else if strings.HasPrefix(upstreamURL, "http://") { + upstreamURL = "ws://" + upstreamURL[len("http://"):] + } + return upstreamURL + proxyPath +} + +func isForwardableResponseHeader(key string) bool { + lowerKey := strings.ToLower(key) + switch { + case strings.HasPrefix(lowerKey, "x-codex-"): + return true + case strings.HasPrefix(lowerKey, "x-reasoning"): + return true + case lowerKey == "openai-model": + return true + case strings.Contains(lowerKey, "-secondary-"): + return true + default: + return false + } +} + +func isForwardableWebSocketRequestHeader(key string) bool { + if isHopByHopHeader(key) { + return false + } + + lowerKey := strings.ToLower(key) + switch { + case lowerKey == "authorization": + return false + case strings.HasPrefix(lowerKey, "sec-websocket-"): + return false + default: + return true + } +} + +func (s *Service) handleWebSocket(w http.ResponseWriter, r *http.Request, proxyPath string, username string) { + accessToken, err := s.getAccessToken() + if err != nil { + s.logger.Error("get access token for websocket: ", err) + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "authentication failed") + return + } + + upstreamURL := buildUpstreamWebSocketURL(s.getBaseURL(), proxyPath) + if r.URL.RawQuery != "" { + upstreamURL += "?" + r.URL.RawQuery + } + + upstreamHeaders := make(http.Header) + for key, values := range r.Header { + if isForwardableWebSocketRequestHeader(key) { + upstreamHeaders[key] = values + } + } + for key, values := range s.httpHeaders { + upstreamHeaders.Del(key) + upstreamHeaders[key] = values + } + upstreamHeaders.Set("Authorization", "Bearer "+accessToken) + if accountID := s.getAccountID(); accountID != "" { + upstreamHeaders.Set("ChatGPT-Account-Id", accountID) + } + + upstreamResponseHeaders := make(http.Header) + upstreamDialer := ws.Dialer{ + NetDial: func(ctx context.Context, network, addr string) (net.Conn, error) { + return s.dialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) + }, + TLSConfig: &stdTLS.Config{ + RootCAs: adapter.RootPoolFromContext(s.ctx), + Time: ntp.TimeFuncFromContext(s.ctx), + }, + Header: ws.HandshakeHeaderHTTP(upstreamHeaders), + OnHeader: func(key, value []byte) error { + upstreamResponseHeaders.Add(string(key), string(value)) + return nil + }, + } + + upstreamConn, upstreamBufferedReader, _, err := upstreamDialer.Dial(r.Context(), upstreamURL) + if err != nil { + s.logger.Error("dial upstream websocket: ", err) + writeJSONError(w, r, http.StatusBadGateway, "api_error", "upstream websocket connection failed") + return + } + + weeklyCycleHint := extractWeeklyCycleHint(upstreamResponseHeaders) + + clientResponseHeaders := make(http.Header) + for key, values := range upstreamResponseHeaders { + if isForwardableResponseHeader(key) { + clientResponseHeaders[key] = values + } + } + + clientUpgrader := ws.HTTPUpgrader{ + Header: clientResponseHeaders, + } + if s.isShuttingDown() { + upstreamConn.Close() + writeJSONError(w, r, http.StatusServiceUnavailable, "api_error", "service is shutting down") + return + } + clientConn, _, _, err := clientUpgrader.Upgrade(r, w) + if err != nil { + s.logger.Error("upgrade client websocket: ", err) + upstreamConn.Close() + return + } + session := &webSocketSession{ + clientConn: clientConn, + upstreamConn: upstreamConn, + } + if !s.registerWebSocketSession(session) { + session.Close() + return + } + defer s.unregisterWebSocketSession(session) + + var upstreamReadWriter io.ReadWriter + if upstreamBufferedReader != nil { + upstreamReadWriter = struct { + io.Reader + io.Writer + }{upstreamBufferedReader, upstreamConn} + } else { + upstreamReadWriter = upstreamConn + } + + modelChannel := make(chan string, 1) + var waitGroup sync.WaitGroup + + waitGroup.Add(2) + go func() { + defer waitGroup.Done() + defer session.Close() + s.proxyWebSocketClientToUpstream(clientConn, upstreamConn, modelChannel) + }() + go func() { + defer waitGroup.Done() + defer session.Close() + s.proxyWebSocketUpstreamToClient(upstreamReadWriter, clientConn, modelChannel, username, weeklyCycleHint) + }() + waitGroup.Wait() +} + +func (s *Service) proxyWebSocketClientToUpstream(clientConn net.Conn, upstreamConn net.Conn, modelChannel chan<- string) { + for { + data, opCode, err := wsutil.ReadClientData(clientConn) + if err != nil { + if !E.IsClosedOrCanceled(err) { + s.logger.Debug("read client websocket: ", err) + } + return + } + + if opCode == ws.OpText && s.usageTracker != nil { + var request struct { + Type string `json:"type"` + Model string `json:"model"` + } + if json.Unmarshal(data, &request) == nil && request.Type == "response.create" && request.Model != "" { + select { + case modelChannel <- request.Model: + default: + } + } + } + + err = wsutil.WriteClientMessage(upstreamConn, opCode, data) + if err != nil { + if !E.IsClosedOrCanceled(err) { + s.logger.Debug("write upstream websocket: ", err) + } + return + } + } +} + +func (s *Service) proxyWebSocketUpstreamToClient(upstreamReadWriter io.ReadWriter, clientConn net.Conn, modelChannel <-chan string, username string, weeklyCycleHint *WeeklyCycleHint) { + var requestModel string + for { + data, opCode, err := wsutil.ReadServerData(upstreamReadWriter) + if err != nil { + if !E.IsClosedOrCanceled(err) { + s.logger.Debug("read upstream websocket: ", err) + } + return + } + + if opCode == ws.OpText && s.usageTracker != nil { + select { + case model := <-modelChannel: + requestModel = model + default: + } + + var event struct { + Type string `json:"type"` + } + if json.Unmarshal(data, &event) == nil && event.Type == "response.completed" { + var streamEvent responses.ResponseStreamEventUnion + if json.Unmarshal(data, &streamEvent) == nil { + completedEvent := streamEvent.AsResponseCompleted() + responseModel := string(completedEvent.Response.Model) + serviceTier := string(completedEvent.Response.ServiceTier) + inputTokens := completedEvent.Response.Usage.InputTokens + outputTokens := completedEvent.Response.Usage.OutputTokens + cachedTokens := completedEvent.Response.Usage.InputTokensDetails.CachedTokens + + if inputTokens > 0 || outputTokens > 0 { + if responseModel == "" { + responseModel = requestModel + } + if responseModel != "" { + contextWindow := detectContextWindow(responseModel, serviceTier, inputTokens) + s.usageTracker.AddUsageWithCycleHint( + responseModel, + contextWindow, + inputTokens, + outputTokens, + cachedTokens, + serviceTier, + username, + time.Now(), + weeklyCycleHint, + ) + } + } + } + } + } + + err = wsutil.WriteServerMessage(clientConn, opCode, data) + if err != nil { + if !E.IsClosedOrCanceled(err) { + s.logger.Debug("write client websocket: ", err) + } + return + } + } +} diff --git a/service/oomkiller/badcleanup.go b/service/oomkiller/badcleanup.go new file mode 100644 index 0000000000..4ba4b0cef8 --- /dev/null +++ b/service/oomkiller/badcleanup.go @@ -0,0 +1,19 @@ +//go:build badlinkname + +package oomkiller + +import ( + "sync" + _ "unsafe" +) + +//go:linkname jsonFieldCache json.fieldCache +var jsonFieldCache sync.Map + +//go:linkname contextJSONFieldCache github.com/sagernet/sing/common/json/internal/contextjson.fieldCache +var contextJSONFieldCache sync.Map + +func badCleanup() { + jsonFieldCache.Clear() + contextJSONFieldCache.Clear() +} diff --git a/service/oomkiller/badcleanup_stub.go b/service/oomkiller/badcleanup_stub.go new file mode 100644 index 0000000000..d734eb9f16 --- /dev/null +++ b/service/oomkiller/badcleanup_stub.go @@ -0,0 +1,6 @@ +//go:build !badlinkname + +package oomkiller + +func badCleanup() { +} diff --git a/service/oomkiller/policy.go b/service/oomkiller/policy.go new file mode 100644 index 0000000000..aa74430157 --- /dev/null +++ b/service/oomkiller/policy.go @@ -0,0 +1,46 @@ +package oomkiller + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/memory" + "github.com/sagernet/sing/service" +) + +const DefaultAppleNetworkExtensionMemoryLimit = 50 * 1024 * 1024 + +type policyMode uint8 + +const ( + policyModeNone policyMode = iota + policyModeMemoryLimit + policyModeAvailable + policyModeNetworkExtension +) + +func (m policyMode) hasTimerMode() bool { + return m != policyModeNone +} + +func resolvePolicyMode(ctx context.Context, options option.OOMKillerServiceOptions) (uint64, policyMode) { + platformInterface := service.FromContext[adapter.PlatformInterface](ctx) + if C.IsIos && platformInterface != nil && platformInterface.UnderNetworkExtension() { + return DefaultAppleNetworkExtensionMemoryLimit, policyModeNetworkExtension + } + if options.MemoryLimitOverride > 0 { + return options.MemoryLimitOverride, policyModeMemoryLimit + } + if options.MemoryLimit != nil { + memoryLimit := options.MemoryLimit.Value() + if memoryLimit > 0 { + return memoryLimit, policyModeMemoryLimit + } + } + if memory.AvailableAvailable() { + return 0, policyModeAvailable + } + return 0, policyModeNone +} diff --git a/service/oomkiller/service.go b/service/oomkiller/service.go new file mode 100644 index 0000000000..bbf032d0fa --- /dev/null +++ b/service/oomkiller/service.go @@ -0,0 +1,87 @@ +package oomkiller + +import ( + "context" + "sync/atomic" + "time" + + "github.com/sagernet/sing-box/adapter" + boxService "github.com/sagernet/sing-box/adapter/service" + boxConstant "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/service" +) + +type OOMReporter interface { + WriteReport(memoryUsage uint64) error + WriteDraft(memoryUsage uint64) error + DiscardDraft() error +} + +func RegisterService(registry *boxService.Registry) { + boxService.Register[option.OOMKillerServiceOptions](registry, boxConstant.TypeOOMKiller, NewService) +} + +type Service struct { + boxService.Adapter + ctx context.Context + logger log.ContextLogger + network adapter.NetworkManager + timerConfig timerConfig + adaptiveTimer *adaptiveTimer + lastReportTime atomic.Int64 + //nolint:unused // touched only on darwin && cgo via writeOOMDraft/discardOOMDraft. + draftCancelled atomic.Bool +} + +func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.OOMKillerServiceOptions) (adapter.Service, error) { + memoryLimit, mode := resolvePolicyMode(ctx, options) + config, err := buildTimerConfig(options, memoryLimit, mode, options.KillerDisabled) + if err != nil { + return nil, err + } + return &Service{ + Adapter: boxService.NewAdapter(boxConstant.TypeOOMKiller, tag), + ctx: ctx, + logger: logger, + network: service.FromContext[adapter.NetworkManager](ctx), + timerConfig: config, + }, nil +} + +func (s *Service) createTimer() { + s.adaptiveTimer = newAdaptiveTimer(s.logger, s.network, s.timerConfig, s.writeOOMReport) +} + +func (s *Service) startTimer() { + s.createTimer() + s.adaptiveTimer.start() +} + +func (s *Service) stopTimer() { + if s.adaptiveTimer != nil { + s.adaptiveTimer.stop() + } +} + +func (s *Service) writeOOMReport(memoryUsage uint64) { + now := time.Now().Unix() + lastReport := s.lastReportTime.Load() + if now-lastReport < 3600 { + return + } + if !s.lastReportTime.CompareAndSwap(lastReport, now) { + return + } + reporter := service.FromContext[OOMReporter](s.ctx) + if reporter == nil { + return + } + err := reporter.WriteReport(memoryUsage) + if err != nil { + s.logger.Warn("failed to write OOM report: ", err) + } else { + s.logger.Info("OOM report saved") + } +} diff --git a/service/oomkiller/service_darwin.go b/service/oomkiller/service_darwin.go new file mode 100644 index 0000000000..f1d228369a --- /dev/null +++ b/service/oomkiller/service_darwin.go @@ -0,0 +1,138 @@ +//go:build darwin && cgo + +package oomkiller + +/* +#include + +static dispatch_source_t memoryPressureSource; + +extern void goMemoryPressureCallback(unsigned long status); + +static void startMemoryPressureMonitor() { + memoryPressureSource = dispatch_source_create( + DISPATCH_SOURCE_TYPE_MEMORYPRESSURE, + 0, + DISPATCH_MEMORYPRESSURE_CRITICAL, + dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0) + ); + dispatch_source_set_event_handler(memoryPressureSource, ^{ + unsigned long status = dispatch_source_get_data(memoryPressureSource); + goMemoryPressureCallback(status); + }); + dispatch_activate(memoryPressureSource); +} + +static void stopMemoryPressureMonitor() { + if (memoryPressureSource) { + dispatch_source_cancel(memoryPressureSource); + memoryPressureSource = NULL; + } +} +*/ +import "C" + +import ( + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common/byteformats" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/service" +) + +var ( + globalAccess sync.Mutex + globalServices []*Service +) + +func (s *Service) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + if s.timerConfig.policyMode == policyModeNetworkExtension { + s.createTimer() + globalAccess.Lock() + isFirst := len(globalServices) == 0 + globalServices = append(globalServices, s) + globalAccess.Unlock() + if isFirst { + C.startMemoryPressureMonitor() + } + return nil + } + if !s.timerConfig.policyMode.hasTimerMode() { + return E.New("memory pressure monitoring is not available on this platform without memory_limit") + } + s.startTimer() + return nil +} + +func (s *Service) Close() error { + s.stopTimer() + if s.timerConfig.policyMode == policyModeNetworkExtension { + globalAccess.Lock() + for i, svc := range globalServices { + if svc == s { + globalServices = append(globalServices[:i], globalServices[i+1:]...) + break + } + } + isLast := len(globalServices) == 0 + globalAccess.Unlock() + if isLast { + C.stopMemoryPressureMonitor() + } + s.discardOOMDraft() + } + return nil +} + +//export goMemoryPressureCallback +func goMemoryPressureCallback(status C.ulong) { + globalAccess.Lock() + services := make([]*Service, len(globalServices)) + copy(services, globalServices) + globalAccess.Unlock() + if len(services) == 0 { + return + } + sample := readMemorySample(policyModeNetworkExtension) + for _, s := range services { + s.logger.Warn("memory pressure: critical, usage: ", byteformats.FormatMemoryBytes(sample.usage)) + s.writeOOMDraft(sample.usage) + s.adaptiveTimer.notifyPressure() + } +} + +func (s *Service) writeOOMDraft(memoryUsage uint64) { + if s.draftCancelled.Load() { + return + } + reporter := service.FromContext[OOMReporter](s.ctx) + if reporter == nil { + return + } + err := reporter.WriteDraft(memoryUsage) + if s.draftCancelled.Load() { + reporter.DiscardDraft() + return + } + if err != nil { + s.logger.Error("failed to write OOM draft: ", err) + } else { + s.logger.Warn("OOM draft saved") + } +} + +func (s *Service) discardOOMDraft() { + s.draftCancelled.Store(true) + reporter := service.FromContext[OOMReporter](s.ctx) + if reporter == nil { + return + } + err := reporter.DiscardDraft() + if err != nil { + s.logger.Error("failed to discard OOM draft: ", err) + } +} diff --git a/service/oomkiller/service_stub.go b/service/oomkiller/service_stub.go new file mode 100644 index 0000000000..5eaf82046a --- /dev/null +++ b/service/oomkiller/service_stub.go @@ -0,0 +1,24 @@ +//go:build !darwin || !cgo + +package oomkiller + +import ( + "github.com/sagernet/sing-box/adapter" + E "github.com/sagernet/sing/common/exceptions" +) + +func (s *Service) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + if !s.timerConfig.policyMode.hasTimerMode() { + return E.New("memory pressure monitoring is not available on this platform without memory_limit") + } + s.startTimer() + return nil +} + +func (s *Service) Close() error { + s.stopTimer() + return nil +} diff --git a/service/oomkiller/timer.go b/service/oomkiller/timer.go new file mode 100644 index 0000000000..6e9db4c1d8 --- /dev/null +++ b/service/oomkiller/timer.go @@ -0,0 +1,333 @@ +package oomkiller + +import ( + runtimeDebug "runtime/debug" + "sync" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/byteformats" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/memory" +) + +const ( + defaultMinInterval = 100 * time.Millisecond + defaultArmedInterval = time.Second + defaultMaxInterval = 10 * time.Second + defaultSafetyMargin = 5 * 1024 * 1024 + defaultAvailableTriggerMarginMin = 32 * 1024 * 1024 + defaultAvailableTriggerMarginMax = 128 * 1024 * 1024 +) + +type pressureState uint8 + +const ( + pressureStateNormal pressureState = iota + pressureStateArmed + pressureStateTriggered +) + +type memorySample struct { + usage uint64 + available uint64 + availableKnown bool +} + +type pressureThresholds struct { + trigger uint64 + armed uint64 + resume uint64 +} + +type timerConfig struct { + memoryLimit uint64 + safetyMargin uint64 + hasSafetyMargin bool + minInterval time.Duration + armedInterval time.Duration + maxInterval time.Duration + policyMode policyMode + killerDisabled bool +} + +func buildTimerConfig(options option.OOMKillerServiceOptions, memoryLimit uint64, policyMode policyMode, killerDisabled bool) (timerConfig, error) { + minInterval := defaultMinInterval + if options.MinInterval != 0 { + minInterval = time.Duration(options.MinInterval.Build()) + if minInterval <= 0 { + return timerConfig{}, E.New("min_interval must be greater than 0") + } + } + + maxInterval := defaultMaxInterval + if options.MaxInterval != 0 { + maxInterval = time.Duration(options.MaxInterval.Build()) + if maxInterval <= 0 { + return timerConfig{}, E.New("max_interval must be greater than 0") + } + } + if maxInterval < minInterval { + return timerConfig{}, E.New("max_interval must be greater than or equal to min_interval") + } + + var ( + safetyMargin uint64 + hasSafetyMargin bool + ) + if options.SafetyMargin != nil && options.SafetyMargin.Value() > 0 { + safetyMargin = options.SafetyMargin.Value() + hasSafetyMargin = true + } else if memoryLimit > 0 { + safetyMargin = defaultSafetyMargin + hasSafetyMargin = true + } + + return timerConfig{ + memoryLimit: memoryLimit, + safetyMargin: safetyMargin, + hasSafetyMargin: hasSafetyMargin, + minInterval: minInterval, + armedInterval: max(min(defaultArmedInterval, maxInterval), minInterval), + maxInterval: maxInterval, + policyMode: policyMode, + killerDisabled: killerDisabled, + }, nil +} + +type adaptiveTimer struct { + timerConfig + logger log.ContextLogger + network adapter.NetworkManager + onTriggered func(uint64) + limitThresholds pressureThresholds + + access sync.Mutex + cleanupTriggered bool + timer *time.Timer + state pressureState + currentInterval time.Duration + forceMinInterval bool + pendingPressureBaseline bool + pressureBaseline memorySample + pressureBaselineTime time.Time +} + +func newAdaptiveTimer(logger log.ContextLogger, network adapter.NetworkManager, config timerConfig, onTriggered func(uint64)) *adaptiveTimer { + t := &adaptiveTimer{ + timerConfig: config, + logger: logger, + network: network, + onTriggered: onTriggered, + } + if config.policyMode == policyModeMemoryLimit || config.policyMode == policyModeNetworkExtension { + t.limitThresholds = computeLimitThresholds(config.memoryLimit, config.safetyMargin) + } + return t +} + +func (t *adaptiveTimer) start() { + t.access.Lock() + defer t.access.Unlock() + t.startLocked() +} + +func (t *adaptiveTimer) startLocked() { + if t.timer != nil { + return + } + t.state = pressureStateNormal + t.forceMinInterval = false + t.timer = time.AfterFunc(t.minInterval, t.poll) +} + +func (t *adaptiveTimer) stop() { + t.access.Lock() + defer t.access.Unlock() + if t.timer != nil { + t.timer.Stop() + t.timer = nil + } +} + +func (t *adaptiveTimer) poll() { + var triggered bool + var rateTriggered bool + sample := readMemorySample(t.policyMode) + + t.access.Lock() + if t.timer == nil { + t.access.Unlock() + return + } + if t.timerConfig.policyMode == policyModeNetworkExtension { + if t.cleanupTriggered { + runtimeDebug.FreeOSMemory() + t.cleanupTriggered = true + } + } + if t.pendingPressureBaseline { + t.pressureBaseline = sample + t.pressureBaselineTime = time.Now() + t.pendingPressureBaseline = false + } + previousState := t.state + t.state = t.nextState(sample) + if t.state == pressureStateNormal { + t.forceMinInterval = false + if !t.pressureBaselineTime.IsZero() && time.Since(t.pressureBaselineTime) > t.maxInterval { + t.pressureBaselineTime = time.Time{} + } + } + t.timer.Reset(t.intervalForState()) + triggered = previousState != pressureStateTriggered && t.state == pressureStateTriggered + if !triggered && !t.pressureBaselineTime.IsZero() && t.memoryLimit > 0 && + sample.usage > t.pressureBaseline.usage && sample.usage < t.memoryLimit { + elapsed := time.Since(t.pressureBaselineTime) + if elapsed >= t.minInterval/2 { + growth := sample.usage - t.pressureBaseline.usage + ratePerSecond := float64(growth) / elapsed.Seconds() + headroom := t.memoryLimit - sample.usage + timeToLimit := time.Duration(float64(headroom)/ratePerSecond) * time.Second + if timeToLimit < t.minInterval { + triggered = true + rateTriggered = true + t.state = pressureStateTriggered + } + } + } + t.access.Unlock() + if !triggered { + return + } + t.cleanupTriggered = false + t.onTriggered(sample.usage) + if rateTriggered { + if t.killerDisabled { + t.logger.Warn("memory growth rate critical (report only), usage: ", byteformats.FormatMemoryBytes(sample.usage), t.logDetails(sample)) + } else { + t.logger.Error("memory growth rate critical, usage: ", byteformats.FormatMemoryBytes(sample.usage), t.logDetails(sample), ", resetting network") + t.network.ResetNetwork() + } + } else { + if t.killerDisabled { + t.logger.Warn("memory threshold reached (report only), usage: ", byteformats.FormatMemoryBytes(sample.usage), t.logDetails(sample)) + } else { + t.logger.Error("memory threshold reached, usage: ", byteformats.FormatMemoryBytes(sample.usage), t.logDetails(sample), ", resetting network") + t.network.ResetNetwork() + } + } + badCleanup() + runtimeDebug.FreeOSMemory() +} + +func (t *adaptiveTimer) nextState(sample memorySample) pressureState { + switch t.policyMode { + case policyModeMemoryLimit, policyModeNetworkExtension: + return nextPressureState(t.state, + sample.usage >= t.limitThresholds.trigger, + sample.usage >= t.limitThresholds.armed, + sample.usage >= t.limitThresholds.resume, + ) + case policyModeAvailable: + if !sample.availableKnown { + return pressureStateNormal + } + thresholds := t.availableThresholds(sample) + return nextPressureState(t.state, + sample.available <= thresholds.trigger, + sample.available <= thresholds.armed, + sample.available <= thresholds.resume, + ) + default: + return pressureStateNormal + } +} + +func computeLimitThresholds(memoryLimit uint64, safetyMargin uint64) pressureThresholds { + triggerMargin := min(safetyMargin, memoryLimit) + armedMargin := min(triggerMargin*2, memoryLimit) + resumeMargin := min(triggerMargin*4, memoryLimit) + return pressureThresholds{ + trigger: memoryLimit - triggerMargin, + armed: memoryLimit - armedMargin, + resume: memoryLimit - resumeMargin, + } +} + +func (t *adaptiveTimer) availableThresholds(sample memorySample) pressureThresholds { + var triggerMargin uint64 + if t.hasSafetyMargin { + triggerMargin = t.safetyMargin + } else if sample.usage == 0 { + triggerMargin = defaultAvailableTriggerMarginMin + } else { + triggerMargin = max(defaultAvailableTriggerMarginMin, min(sample.usage/4, defaultAvailableTriggerMarginMax)) + } + return pressureThresholds{ + trigger: triggerMargin, + armed: triggerMargin * 2, + resume: triggerMargin * 4, + } +} + +func (t *adaptiveTimer) intervalForState() time.Duration { + switch { + case t.forceMinInterval || t.state == pressureStateTriggered: + t.currentInterval = t.minInterval + case t.state == pressureStateArmed: + t.currentInterval = t.armedInterval + default: + if t.currentInterval == 0 { + t.currentInterval = t.maxInterval + } else { + t.currentInterval = min(t.currentInterval*2, t.maxInterval) + } + } + return t.currentInterval +} + +func (t *adaptiveTimer) logDetails(sample memorySample) string { + switch t.policyMode { + case policyModeMemoryLimit, policyModeNetworkExtension: + headroom := uint64(0) + if sample.usage < t.memoryLimit { + headroom = t.memoryLimit - sample.usage + } + return ", limit: " + byteformats.FormatMemoryBytes(t.memoryLimit) + ", headroom: " + byteformats.FormatMemoryBytes(headroom) + case policyModeAvailable: + if sample.availableKnown { + return ", available: " + byteformats.FormatMemoryBytes(sample.available) + } + } + return "" +} + +func nextPressureState(current pressureState, shouldTrigger, shouldArm, shouldStayTriggered bool) pressureState { + if current == pressureStateTriggered { + if shouldStayTriggered { + return pressureStateTriggered + } + return pressureStateNormal + } + if shouldTrigger { + return pressureStateTriggered + } + if shouldArm { + return pressureStateArmed + } + return pressureStateNormal +} + +func readMemorySample(mode policyMode) memorySample { + sample := memorySample{ + usage: memory.Total(), + } + if mode == policyModeAvailable { + sample.availableKnown = true + sample.available = memory.Available() + } + return sample +} diff --git a/service/oomkiller/timer_darwin.go b/service/oomkiller/timer_darwin.go new file mode 100644 index 0000000000..6c4f7efab4 --- /dev/null +++ b/service/oomkiller/timer_darwin.go @@ -0,0 +1,12 @@ +//go:build darwin && cgo + +package oomkiller + +func (t *adaptiveTimer) notifyPressure() { + t.access.Lock() + t.startLocked() + t.forceMinInterval = true + t.pendingPressureBaseline = true + t.access.Unlock() + t.poll() +} diff --git a/service/origin_ca/service.go b/service/origin_ca/service.go new file mode 100644 index 0000000000..e9fca42c60 --- /dev/null +++ b/service/origin_ca/service.go @@ -0,0 +1,608 @@ +package originca + +import ( + "bytes" + "context" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/json" + "encoding/pem" + "errors" + "io" + "io/fs" + "net" + "net/http" + "slices" + "strconv" + "strings" + "sync" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/certificate" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/ntp" + "github.com/sagernet/sing/service" + + "github.com/caddyserver/certmagic" +) + +const ( + cloudflareOriginCAEndpoint = "https://api.cloudflare.com/client/v4/certificates" + defaultRequestedValidity = option.CloudflareOriginCARequestValidity5475 + // min of 30 days and certmagic's 1/3 lifetime ratio (maintain.go) + defaultRenewBefore = 30 * 24 * time.Hour + // from certmagic retry backoff range (async.go) + minimumRenewRetryDelay = time.Minute + maximumRenewRetryDelay = time.Hour + storageLockPrefix = "cloudflare-origin-ca" +) + +func RegisterCertificateProvider(registry *certificate.Registry) { + certificate.Register[option.CloudflareOriginCACertificateProviderOptions](registry, C.TypeCloudflareOriginCA, NewCertificateProvider) +} + +var _ adapter.CertificateProviderService = (*Service)(nil) + +type Service struct { + certificate.Adapter + logger log.ContextLogger + ctx context.Context + cancel context.CancelFunc + done chan struct{} + timeFunc func() time.Time + httpClient *http.Client + storage certmagic.Storage + storageIssuerKey string + storageNamesKey string + storageLockKey string + apiToken string + originCAKey string + domain []string + requestType option.CloudflareOriginCARequestType + requestedValidity option.CloudflareOriginCARequestValidity + + access sync.RWMutex + currentCertificate *tls.Certificate + currentLeaf *x509.Certificate +} + +func NewCertificateProvider(ctx context.Context, logger log.ContextLogger, tag string, options option.CloudflareOriginCACertificateProviderOptions) (adapter.CertificateProviderService, error) { + domain, err := normalizeHostnames(options.Domain) + if err != nil { + return nil, err + } + if len(domain) == 0 { + return nil, E.New("missing domain") + } + apiToken := strings.TrimSpace(options.APIToken) + originCAKey := strings.TrimSpace(options.OriginCAKey) + switch { + case apiToken == "" && originCAKey == "": + return nil, E.New("api_token or origin_ca_key is required") + case apiToken != "" && originCAKey != "": + return nil, E.New("api_token and origin_ca_key are mutually exclusive") + } + requestType := options.RequestType + if requestType == "" { + requestType = option.CloudflareOriginCARequestTypeOriginRSA + } + requestedValidity := options.RequestedValidity + if requestedValidity == 0 { + requestedValidity = defaultRequestedValidity + } + ctx, cancel := context.WithCancel(ctx) + httpClient, err := originCAHTTPClient(ctx, logger, options) + if err != nil { + cancel() + return nil, err + } + var storage certmagic.Storage + if options.DataDirectory != "" { + storage = &certmagic.FileStorage{Path: options.DataDirectory} + } else { + storage = certmagic.Default.Storage + } + timeFunc := ntp.TimeFuncFromContext(ctx) + if timeFunc == nil { + timeFunc = time.Now + } + storageIssuerKey := C.TypeCloudflareOriginCA + "-" + string(requestType) + storageNamesKey := (&certmagic.CertificateResource{SANs: slices.Clone(domain)}).NamesKey() + storageLockKey := strings.Join([]string{ + storageLockPrefix, + certmagic.StorageKeys.Safe(storageIssuerKey), + certmagic.StorageKeys.Safe(storageNamesKey), + }, "/") + return &Service{ + Adapter: certificate.NewAdapter(C.TypeCloudflareOriginCA, tag), + logger: logger, + ctx: ctx, + cancel: cancel, + timeFunc: timeFunc, + httpClient: httpClient, + storage: storage, + storageIssuerKey: storageIssuerKey, + storageNamesKey: storageNamesKey, + storageLockKey: storageLockKey, + apiToken: apiToken, + originCAKey: originCAKey, + domain: domain, + requestType: requestType, + requestedValidity: requestedValidity, + }, nil +} + +func originCAHTTPClient(ctx context.Context, logger log.ContextLogger, options option.CloudflareOriginCACertificateProviderOptions) (*http.Client, error) { + httpClientOptions := common.PtrValueOrDefault(options.HTTPClient) + httpClientManager := service.FromContext[adapter.HTTPClientManager](ctx) + transport, err := httpClientManager.ResolveTransport(ctx, logger, httpClientOptions) + if err != nil { + return nil, E.Cause(err, "create Cloudflare Origin CA http client") + } + return &http.Client{Transport: transport}, nil +} + +func (s *Service) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + cachedCertificate, cachedLeaf, err := s.loadCachedCertificate() + if err != nil { + s.logger.Warn(E.Cause(err, "load cached Cloudflare Origin CA certificate")) + } else if cachedCertificate != nil { + s.setCurrentCertificate(cachedCertificate, cachedLeaf) + } + if cachedCertificate == nil { + err = s.issueAndStoreCertificate() + if err != nil { + return err + } + } else if s.shouldRenew(cachedLeaf, s.timeFunc()) { + err = s.issueAndStoreCertificate() + if err != nil { + s.logger.Warn(E.Cause(err, "renew cached Cloudflare Origin CA certificate")) + } + } + s.done = make(chan struct{}) + go s.refreshLoop() + return nil +} + +func (s *Service) Close() error { + s.cancel() + if done := s.done; done != nil { + <-done + } + return nil +} + +func (s *Service) GetCertificate(_ *tls.ClientHelloInfo) (*tls.Certificate, error) { + s.access.RLock() + certificate := s.currentCertificate + s.access.RUnlock() + if certificate == nil { + return nil, E.New("Cloudflare Origin CA certificate is unavailable") + } + return certificate, nil +} + +func (s *Service) refreshLoop() { + defer close(s.done) + var retryDelay time.Duration + for { + waitDuration := retryDelay + if waitDuration == 0 { + s.access.RLock() + leaf := s.currentLeaf + s.access.RUnlock() + if leaf == nil { + waitDuration = minimumRenewRetryDelay + } else { + refreshAt := leaf.NotAfter.Add(-s.effectiveRenewBefore(leaf)) + waitDuration = max(refreshAt.Sub(s.timeFunc()), minimumRenewRetryDelay) + } + } + timer := time.NewTimer(waitDuration) + select { + case <-s.ctx.Done(): + if !timer.Stop() { + select { + case <-timer.C: + default: + } + } + return + case <-timer.C: + } + err := s.issueAndStoreCertificate() + if err != nil { + s.logger.Error(E.Cause(err, "renew Cloudflare Origin CA certificate")) + s.access.RLock() + leaf := s.currentLeaf + s.access.RUnlock() + if leaf == nil { + retryDelay = minimumRenewRetryDelay + } else { + remaining := leaf.NotAfter.Sub(s.timeFunc()) + switch { + case remaining <= minimumRenewRetryDelay: + retryDelay = minimumRenewRetryDelay + case remaining < maximumRenewRetryDelay: + retryDelay = max(remaining/2, minimumRenewRetryDelay) + default: + retryDelay = maximumRenewRetryDelay + } + } + continue + } + retryDelay = 0 + } +} + +func (s *Service) shouldRenew(leaf *x509.Certificate, now time.Time) bool { + return !now.Before(leaf.NotAfter.Add(-s.effectiveRenewBefore(leaf))) +} + +func (s *Service) effectiveRenewBefore(leaf *x509.Certificate) time.Duration { + lifetime := leaf.NotAfter.Sub(leaf.NotBefore) + if lifetime <= 0 { + return 0 + } + return min(lifetime/3, defaultRenewBefore) +} + +func (s *Service) issueAndStoreCertificate() error { + err := s.storage.Lock(s.ctx, s.storageLockKey) + if err != nil { + return E.Cause(err, "lock Cloudflare Origin CA certificate storage") + } + defer func() { + err = s.storage.Unlock(context.WithoutCancel(s.ctx), s.storageLockKey) + if err != nil { + s.logger.Warn(E.Cause(err, "unlock Cloudflare Origin CA certificate storage")) + } + }() + cachedCertificate, cachedLeaf, err := s.loadCachedCertificate() + if err != nil { + s.logger.Warn(E.Cause(err, "load cached Cloudflare Origin CA certificate")) + } else if cachedCertificate != nil && !s.shouldRenew(cachedLeaf, s.timeFunc()) { + s.setCurrentCertificate(cachedCertificate, cachedLeaf) + return nil + } + certificatePEM, privateKeyPEM, tlsCertificate, leaf, err := s.requestCertificate(s.ctx) + if err != nil { + return err + } + issuerData, err := json.Marshal(originCAIssuerData{ + RequestType: s.requestType, + RequestedValidity: s.requestedValidity, + }) + if err != nil { + return E.Cause(err, "encode Cloudflare Origin CA certificate metadata") + } + err = storeCertificateResource(s.ctx, s.storage, s.storageIssuerKey, certmagic.CertificateResource{ + SANs: slices.Clone(s.domain), + CertificatePEM: certificatePEM, + PrivateKeyPEM: privateKeyPEM, + IssuerData: issuerData, + }) + if err != nil { + return E.Cause(err, "store Cloudflare Origin CA certificate") + } + s.setCurrentCertificate(tlsCertificate, leaf) + s.logger.Info("updated Cloudflare Origin CA certificate, expires at ", leaf.NotAfter.Format(time.RFC3339)) + return nil +} + +func (s *Service) requestCertificate(ctx context.Context) ([]byte, []byte, *tls.Certificate, *x509.Certificate, error) { + var privateKey crypto.Signer + switch s.requestType { + case option.CloudflareOriginCARequestTypeOriginRSA: + rsaKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, nil, nil, err + } + privateKey = rsaKey + case option.CloudflareOriginCARequestTypeOriginECC: + ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, nil, nil, nil, err + } + privateKey = ecKey + default: + return nil, nil, nil, nil, E.New("unsupported Cloudflare Origin CA request type: ", s.requestType) + } + privateKeyDER, err := x509.MarshalPKCS8PrivateKey(privateKey) + if err != nil { + return nil, nil, nil, nil, E.Cause(err, "encode private key") + } + privateKeyPEM := pem.EncodeToMemory(&pem.Block{ + Type: "PRIVATE KEY", + Bytes: privateKeyDER, + }) + certificateRequestDER, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{ + Subject: pkix.Name{CommonName: s.domain[0]}, + DNSNames: s.domain, + }, privateKey) + if err != nil { + return nil, nil, nil, nil, E.Cause(err, "create certificate request") + } + certificateRequestPEM := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE REQUEST", + Bytes: certificateRequestDER, + }) + requestBody, err := json.Marshal(originCARequest{ + CSR: string(certificateRequestPEM), + Hostnames: s.domain, + RequestType: string(s.requestType), + RequestedValidity: uint16(s.requestedValidity), + }) + if err != nil { + return nil, nil, nil, nil, E.Cause(err, "marshal request") + } + request, err := http.NewRequestWithContext(ctx, http.MethodPost, cloudflareOriginCAEndpoint, bytes.NewReader(requestBody)) + if err != nil { + return nil, nil, nil, nil, E.Cause(err, "create request") + } + request.Header.Set("Accept", "application/json") + request.Header.Set("Content-Type", "application/json") + request.Header.Set("User-Agent", "sing-box/"+C.Version) + if s.apiToken != "" { + request.Header.Set("Authorization", "Bearer "+s.apiToken) + } else { + request.Header.Set("X-Auth-User-Service-Key", s.originCAKey) + } + defer s.httpClient.CloseIdleConnections() + response, err := s.httpClient.Do(request) + if err != nil { + return nil, nil, nil, nil, E.Cause(err, "request certificate from Cloudflare") + } + defer response.Body.Close() + responseBody, err := io.ReadAll(response.Body) + if err != nil { + return nil, nil, nil, nil, E.Cause(err, "read Cloudflare response") + } + var responseEnvelope originCAResponse + err = json.Unmarshal(responseBody, &responseEnvelope) + if err != nil && response.StatusCode >= http.StatusOK && response.StatusCode < http.StatusMultipleChoices { + return nil, nil, nil, nil, E.Cause(err, "decode Cloudflare response") + } + if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices { + return nil, nil, nil, nil, buildOriginCAError(response.StatusCode, responseEnvelope.Errors, responseBody) + } + if !responseEnvelope.Success { + return nil, nil, nil, nil, buildOriginCAError(response.StatusCode, responseEnvelope.Errors, responseBody) + } + if responseEnvelope.Result.Certificate == "" { + return nil, nil, nil, nil, E.New("Cloudflare Origin CA response is missing certificate data") + } + certificatePEM := []byte(responseEnvelope.Result.Certificate) + tlsCertificate, leaf, err := parseKeyPair(certificatePEM, privateKeyPEM) + if err != nil { + return nil, nil, nil, nil, E.Cause(err, "parse issued certificate") + } + if !s.matchesCertificate(leaf) { + return nil, nil, nil, nil, E.New("issued Cloudflare Origin CA certificate does not match requested hostnames or key type") + } + return certificatePEM, privateKeyPEM, tlsCertificate, leaf, nil +} + +func (s *Service) loadCachedCertificate() (*tls.Certificate, *x509.Certificate, error) { + certificateResource, err := loadCertificateResource(s.ctx, s.storage, s.storageIssuerKey, s.storageNamesKey) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return nil, nil, nil + } + return nil, nil, err + } + tlsCertificate, leaf, err := parseKeyPair(certificateResource.CertificatePEM, certificateResource.PrivateKeyPEM) + if err != nil { + return nil, nil, E.Cause(err, "parse cached key pair") + } + if s.timeFunc().After(leaf.NotAfter) { + return nil, nil, nil + } + if !s.matchesCertificate(leaf) { + return nil, nil, nil + } + return tlsCertificate, leaf, nil +} + +func (s *Service) matchesCertificate(leaf *x509.Certificate) bool { + if leaf == nil { + return false + } + leafHostnames := leaf.DNSNames + if len(leafHostnames) == 0 && leaf.Subject.CommonName != "" { + leafHostnames = []string{leaf.Subject.CommonName} + } + normalizedLeafHostnames, err := normalizeHostnames(leafHostnames) + if err != nil { + return false + } + if !slices.Equal(normalizedLeafHostnames, s.domain) { + return false + } + switch s.requestType { + case option.CloudflareOriginCARequestTypeOriginRSA: + return leaf.PublicKeyAlgorithm == x509.RSA + case option.CloudflareOriginCARequestTypeOriginECC: + return leaf.PublicKeyAlgorithm == x509.ECDSA + default: + return false + } +} + +func (s *Service) setCurrentCertificate(certificate *tls.Certificate, leaf *x509.Certificate) { + s.access.Lock() + s.currentCertificate = certificate + s.currentLeaf = leaf + s.access.Unlock() +} + +func normalizeHostnames(hostnames []string) ([]string, error) { + normalizedHostnames := make([]string, 0, len(hostnames)) + seen := make(map[string]struct{}, len(hostnames)) + for _, hostname := range hostnames { + normalizedHostname := strings.ToLower(strings.TrimSpace(strings.TrimSuffix(hostname, "."))) + if normalizedHostname == "" { + return nil, E.New("hostname is empty") + } + if net.ParseIP(normalizedHostname) != nil { + return nil, E.New("hostname cannot be an IP address: ", normalizedHostname) + } + if strings.Contains(normalizedHostname, "*") { + if !strings.HasPrefix(normalizedHostname, "*.") || strings.Count(normalizedHostname, "*") != 1 { + return nil, E.New("invalid wildcard hostname: ", normalizedHostname) + } + suffix := strings.TrimPrefix(normalizedHostname, "*.") + if strings.Count(suffix, ".") == 0 { + return nil, E.New("wildcard hostname must cover a multi-label domain: ", normalizedHostname) + } + normalizedHostname = "*." + suffix + } + if _, loaded := seen[normalizedHostname]; loaded { + continue + } + seen[normalizedHostname] = struct{}{} + normalizedHostnames = append(normalizedHostnames, normalizedHostname) + } + slices.Sort(normalizedHostnames) + return normalizedHostnames, nil +} + +func parseKeyPair(certificatePEM []byte, privateKeyPEM []byte) (*tls.Certificate, *x509.Certificate, error) { + keyPair, err := tls.X509KeyPair(certificatePEM, privateKeyPEM) + if err != nil { + return nil, nil, err + } + if len(keyPair.Certificate) == 0 { + return nil, nil, E.New("certificate chain is empty") + } + leaf, err := x509.ParseCertificate(keyPair.Certificate[0]) + if err != nil { + return nil, nil, err + } + keyPair.Leaf = leaf + return &keyPair, leaf, nil +} + +func storeCertificateResource(ctx context.Context, storage certmagic.Storage, issuerKey string, certificateResource certmagic.CertificateResource) error { + metaBytes, err := json.MarshalIndent(certificateResource, "", "\t") + if err != nil { + return err + } + namesKey := certificateResource.NamesKey() + keyValueList := []struct { + key string + value []byte + }{ + { + key: certmagic.StorageKeys.SitePrivateKey(issuerKey, namesKey), + value: certificateResource.PrivateKeyPEM, + }, + { + key: certmagic.StorageKeys.SiteCert(issuerKey, namesKey), + value: certificateResource.CertificatePEM, + }, + { + key: certmagic.StorageKeys.SiteMeta(issuerKey, namesKey), + value: metaBytes, + }, + } + for i, item := range keyValueList { + err = storage.Store(ctx, item.key, item.value) + if err != nil { + for j := i - 1; j >= 0; j-- { + storage.Delete(ctx, keyValueList[j].key) + } + return err + } + } + return nil +} + +func loadCertificateResource(ctx context.Context, storage certmagic.Storage, issuerKey string, namesKey string) (certmagic.CertificateResource, error) { + privateKeyPEM, err := storage.Load(ctx, certmagic.StorageKeys.SitePrivateKey(issuerKey, namesKey)) + if err != nil { + return certmagic.CertificateResource{}, err + } + certificatePEM, err := storage.Load(ctx, certmagic.StorageKeys.SiteCert(issuerKey, namesKey)) + if err != nil { + return certmagic.CertificateResource{}, err + } + metaBytes, err := storage.Load(ctx, certmagic.StorageKeys.SiteMeta(issuerKey, namesKey)) + if err != nil { + return certmagic.CertificateResource{}, err + } + var certificateResource certmagic.CertificateResource + err = json.Unmarshal(metaBytes, &certificateResource) + if err != nil { + return certmagic.CertificateResource{}, E.Cause(err, "decode Cloudflare Origin CA certificate metadata") + } + certificateResource.PrivateKeyPEM = privateKeyPEM + certificateResource.CertificatePEM = certificatePEM + return certificateResource, nil +} + +func buildOriginCAError(statusCode int, responseErrors []originCAResponseError, responseBody []byte) error { + if len(responseErrors) > 0 { + messageList := make([]string, 0, len(responseErrors)) + for _, responseError := range responseErrors { + if responseError.Message == "" { + continue + } + if responseError.Code != 0 { + messageList = append(messageList, responseError.Message+" (code "+strconv.Itoa(responseError.Code)+")") + } else { + messageList = append(messageList, responseError.Message) + } + } + if len(messageList) > 0 { + return E.New("Cloudflare Origin CA request failed: HTTP ", statusCode, " ", strings.Join(messageList, ", ")) + } + } + responseText := strings.TrimSpace(string(responseBody)) + if responseText == "" { + return E.New("Cloudflare Origin CA request failed: HTTP ", statusCode) + } + return E.New("Cloudflare Origin CA request failed: HTTP ", statusCode, " ", responseText) +} + +type originCARequest struct { + CSR string `json:"csr"` + Hostnames []string `json:"hostnames"` + RequestType string `json:"request_type"` + RequestedValidity uint16 `json:"requested_validity"` +} + +type originCAResponse struct { + Success bool `json:"success"` + Errors []originCAResponseError `json:"errors"` + Result originCAResponseResult `json:"result"` +} + +type originCAResponseError struct { + Code int `json:"code"` + Message string `json:"message"` +} + +type originCAResponseResult struct { + Certificate string `json:"certificate"` +} + +type originCAIssuerData struct { + RequestType option.CloudflareOriginCARequestType `json:"request_type,omitempty"` + RequestedValidity option.CloudflareOriginCARequestValidity `json:"requested_validity,omitempty"` +} diff --git a/service/resolved/resolve1.go b/service/resolved/resolve1.go new file mode 100644 index 0000000000..6b347060d0 --- /dev/null +++ b/service/resolved/resolve1.go @@ -0,0 +1,657 @@ +//go:build linux + +package resolved + +import ( + "context" + "errors" + "fmt" + "net/netip" + "os" + "os/user" + "path/filepath" + "slices" + "strconv" + "strings" + "syscall" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "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" + M "github.com/sagernet/sing/common/metadata" + + "github.com/godbus/dbus/v5" + mDNS "github.com/miekg/dns" +) + +type resolve1Manager Service + +type Address struct { + IfIndex int32 + Family int32 + Address []byte +} + +type Name struct { + IfIndex int32 + Hostname string +} + +type ResourceRecord struct { + IfIndex int32 + Type uint16 + Class uint16 + Data []byte +} + +type SRVRecord struct { + Priority uint16 + Weight uint16 + Port uint16 + Hostname string + Addresses []Address + CNAME string +} + +type TXTRecord []byte + +type LinkDNS struct { + Family int32 + Address []byte +} + +type LinkDNSEx struct { + Family int32 + Address []byte + Port uint16 + Name string +} + +type LinkDomain struct { + Domain string + RoutingOnly bool +} + +func (t *resolve1Manager) getLink(ifIndex int32) (*TransportLink, *dbus.Error) { + link, loaded := t.links[ifIndex] + if !loaded { + link = &TransportLink{} + t.links[ifIndex] = link + iif, err := t.network.InterfaceFinder().ByIndex(int(ifIndex)) + if err != nil { + return nil, wrapError(err) + } + link.iif = iif + } + return link, nil +} + +func (t *resolve1Manager) getSenderProcess(sender dbus.Sender) (int32, error) { + var senderPid int32 + dbusObject := t.systemBus.Object("org.freedesktop.DBus", "/org/freedesktop/DBus") + if dbusObject == nil { + return 0, E.New("missing dbus object") + } + err := dbusObject.Call("org.freedesktop.DBus.GetConnectionUnixProcessID", 0, string(sender)).Store(&senderPid) + if err != nil { + return 0, E.Cause(err, "GetConnectionUnixProcessID") + } + return senderPid, nil +} + +func (t *resolve1Manager) createMetadata(sender dbus.Sender) adapter.InboundContext { + var metadata adapter.InboundContext + metadata.Inbound = t.Tag() + metadata.InboundType = C.TypeResolved + senderPid, err := t.getSenderProcess(sender) + if err != nil { + return metadata + } + var processInfo adapter.ConnectionOwner + metadata.ProcessInfo = &processInfo + processInfo.ProcessID = uint32(senderPid) + + processPath, err := os.Readlink(F.ToString("/proc/", senderPid, "/exe")) + if err == nil { + processInfo.ProcessPath = processPath + } else { + processPath, err = os.Readlink(F.ToString("/proc/", senderPid, "/comm")) + if err == nil { + processInfo.ProcessPath = processPath + } + } + + var uidFound bool + statusContent, err := os.ReadFile(F.ToString("/proc/", senderPid, "/status")) + if err == nil { + for line := range strings.SplitSeq(string(statusContent), "\n") { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "Uid:") { + fields := strings.Fields(line) + if len(fields) >= 2 { + uid, parseErr := strconv.ParseUint(fields[1], 10, 32) + if parseErr != nil { + break + } + processInfo.UserId = int32(uid) + uidFound = true + if osUser, _ := user.LookupId(F.ToString(uid)); osUser != nil { + processInfo.UserName = osUser.Username + } + break + } + } + } + } + if !uidFound { + metadata.ProcessInfo.UserId = -1 + } + return metadata +} + +func (t *resolve1Manager) log(sender dbus.Sender, message ...any) { + metadata := t.createMetadata(sender) + if metadata.ProcessInfo != nil { + var prefix string + if metadata.ProcessInfo.ProcessPath != "" { + prefix = filepath.Base(metadata.ProcessInfo.ProcessPath) + } else if metadata.ProcessInfo.UserName != "" { + prefix = F.ToString("user:", metadata.ProcessInfo.UserName) + } else if metadata.ProcessInfo.UserId != 0 { + prefix = F.ToString("uid:", metadata.ProcessInfo.UserId) + } + t.logger.Info("(", prefix, ") ", F.ToString(message...)) + } else { + t.logger.Info(F.ToString(message...)) + } +} + +func (t *resolve1Manager) logRequest(sender dbus.Sender, message ...any) context.Context { + ctx := log.ContextWithNewID(t.ctx) + metadata := t.createMetadata(sender) + if metadata.ProcessInfo != nil { + var prefix string + if metadata.ProcessInfo.ProcessPath != "" { + prefix = filepath.Base(metadata.ProcessInfo.ProcessPath) + } else if metadata.ProcessInfo.UserName != "" { + prefix = F.ToString("user:", metadata.ProcessInfo.UserName) + } else if metadata.ProcessInfo.UserId != 0 { + prefix = F.ToString("uid:", metadata.ProcessInfo.UserId) + } + t.logger.InfoContext(ctx, "(", prefix, ") ", strings.Join(F.MapToString(message), " ")) + } else { + t.logger.InfoContext(ctx, strings.Join(F.MapToString(message), " ")) + } + return adapter.WithContext(ctx, &metadata) +} + +func familyToString(family int32) string { + switch family { + case syscall.AF_UNSPEC: + return "AF_UNSPEC" + case syscall.AF_INET: + return "AF_INET" + case syscall.AF_INET6: + return "AF_INET6" + default: + return F.ToString(family) + } +} + +func (t *resolve1Manager) ResolveHostname(sender dbus.Sender, ifIndex int32, hostname string, family int32, flags uint64) (addresses []Address, canonical string, outflags uint64, err *dbus.Error) { + t.linkAccess.Lock() + link, err := t.getLink(ifIndex) + if err != nil { + return + } + t.linkAccess.Unlock() + var strategy C.DomainStrategy + switch family { + case syscall.AF_UNSPEC: + strategy = C.DomainStrategyAsIS + case syscall.AF_INET: + strategy = C.DomainStrategyIPv4Only + case syscall.AF_INET6: + strategy = C.DomainStrategyIPv6Only + } + ctx := t.logRequest(sender, "ResolveHostname ", link.iif.Name, " ", hostname, " ", familyToString(family), " ", flags) + responseAddresses, lookupErr := t.dnsRouter.Lookup(ctx, hostname, adapter.DNSQueryOptions{ + LookupStrategy: strategy, + }) + if lookupErr != nil { + err = wrapError(err) + return + } + addresses = common.Map(responseAddresses, func(it netip.Addr) Address { + var addrFamily int32 + if it.Is4() { + addrFamily = syscall.AF_INET + } else { + addrFamily = syscall.AF_INET6 + } + return Address{ + IfIndex: ifIndex, + Family: addrFamily, + Address: it.AsSlice(), + } + }) + canonical = mDNS.CanonicalName(hostname) + return +} + +func (t *resolve1Manager) ResolveAddress(sender dbus.Sender, ifIndex int32, family int32, address []byte, flags uint64) (names []Name, outflags uint64, err *dbus.Error) { + t.linkAccess.Lock() + link, err := t.getLink(ifIndex) + if err != nil { + return + } + t.linkAccess.Unlock() + addr, ok := netip.AddrFromSlice(address) + if !ok { + err = wrapError(E.New("invalid address")) + return + } + var nibbles []string + for _, v := range slices.Backward(address) { + b := v + nibbles = append(nibbles, fmt.Sprintf("%x", b&0x0F)) + nibbles = append(nibbles, fmt.Sprintf("%x", b>>4)) + } + var ptrDomain string + if addr.Is4() { + ptrDomain = strings.Join(nibbles, ".") + ".in-addr.arpa." + } else { + ptrDomain = strings.Join(nibbles, ".") + ".ip6.arpa." + } + request := &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + RecursionDesired: true, + }, + Question: []mDNS.Question{ + { + Name: mDNS.Fqdn(ptrDomain), + Qtype: mDNS.TypePTR, + Qclass: mDNS.ClassINET, + }, + }, + } + ctx := t.logRequest(sender, "ResolveAddress ", link.iif.Name, familyToString(family), addr, flags) + var metadata adapter.InboundContext + metadata.InboundType = t.Type() + metadata.Inbound = t.Tag() + response, lookupErr := t.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), request, adapter.DNSQueryOptions{}) + if lookupErr != nil { + err = wrapError(err) + return + } + if response.Rcode != mDNS.RcodeSuccess { + err = rcodeError(response.Rcode) + return + } + for _, rawRR := range response.Answer { + switch rr := rawRR.(type) { + case *mDNS.PTR: + names = append(names, Name{ + IfIndex: ifIndex, + Hostname: rr.Ptr, + }) + } + } + return +} + +func (t *resolve1Manager) ResolveRecord(sender dbus.Sender, ifIndex int32, hostname string, qClass uint16, qType uint16, flags uint64) (records []ResourceRecord, outflags uint64, err *dbus.Error) { + t.linkAccess.Lock() + link, err := t.getLink(ifIndex) + if err != nil { + return + } + t.linkAccess.Unlock() + request := &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + RecursionDesired: true, + }, + Question: []mDNS.Question{ + { + Name: mDNS.Fqdn(hostname), + Qtype: qType, + Qclass: qClass, + }, + }, + } + ctx := t.logRequest(sender, "ResolveRecord", link.iif.Name, hostname, mDNS.Class(qClass), mDNS.Type(qType), flags) + var metadata adapter.InboundContext + metadata.InboundType = t.Type() + metadata.Inbound = t.Tag() + response, exchangeErr := t.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), request, adapter.DNSQueryOptions{}) + if exchangeErr != nil { + err = wrapError(exchangeErr) + return + } + if response.Rcode != mDNS.RcodeSuccess { + err = rcodeError(response.Rcode) + return + } + for _, rr := range response.Answer { + var record ResourceRecord + record.IfIndex = ifIndex + record.Type = rr.Header().Rrtype + record.Class = rr.Header().Class + data := make([]byte, mDNS.Len(rr)) + _, unpackErr := mDNS.PackRR(rr, data, 0, nil, false) + if unpackErr != nil { + err = wrapError(unpackErr) + } + record.Data = data + records = append(records, record) + } + return +} + +func (t *resolve1Manager) ResolveService(sender dbus.Sender, ifIndex int32, hostname string, sType string, domain string, family int32, flags uint64) (srvData []SRVRecord, txtData []TXTRecord, canonicalName string, canonicalType string, canonicalDomain string, outflags uint64, err *dbus.Error) { + t.linkAccess.Lock() + link, err := t.getLink(ifIndex) + if err != nil { + return + } + t.linkAccess.Unlock() + + serviceName := hostname + if hostname != "" && !strings.HasSuffix(hostname, ".") { + serviceName += "." + } + serviceName += sType + if !strings.HasSuffix(serviceName, ".") { + serviceName += "." + } + serviceName += domain + if !strings.HasSuffix(serviceName, ".") { + serviceName += "." + } + + ctx := t.logRequest(sender, "ResolveService ", link.iif.Name, " ", hostname, " ", sType, " ", domain, " ", familyToString(family), " ", flags) + + srvRequest := &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + RecursionDesired: true, + }, + Question: []mDNS.Question{ + { + Name: serviceName, + Qtype: mDNS.TypeSRV, + Qclass: mDNS.ClassINET, + }, + }, + } + var metadata adapter.InboundContext + metadata.InboundType = t.Type() + metadata.Inbound = t.Tag() + srvResponse, exchangeErr := t.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), srvRequest, adapter.DNSQueryOptions{}) + if exchangeErr != nil { + err = wrapError(exchangeErr) + return + } + if srvResponse.Rcode != mDNS.RcodeSuccess { + err = rcodeError(srvResponse.Rcode) + return + } + + txtRequest := &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + RecursionDesired: true, + }, + Question: []mDNS.Question{ + { + Name: serviceName, + Qtype: mDNS.TypeTXT, + Qclass: mDNS.ClassINET, + }, + }, + } + + txtResponse, exchangeErr := t.dnsRouter.Exchange(ctx, txtRequest, adapter.DNSQueryOptions{}) + if exchangeErr != nil { + err = wrapError(exchangeErr) + return + } + + for _, rawRR := range srvResponse.Answer { + switch rr := rawRR.(type) { + case *mDNS.SRV: + var srvRecord SRVRecord + srvRecord.Priority = rr.Priority + srvRecord.Weight = rr.Weight + srvRecord.Port = rr.Port + srvRecord.Hostname = rr.Target + + var strategy C.DomainStrategy + switch family { + case syscall.AF_UNSPEC: + strategy = C.DomainStrategyAsIS + case syscall.AF_INET: + strategy = C.DomainStrategyIPv4Only + case syscall.AF_INET6: + strategy = C.DomainStrategyIPv6Only + } + + addrs, lookupErr := t.dnsRouter.Lookup(ctx, rr.Target, adapter.DNSQueryOptions{ + LookupStrategy: strategy, + }) + if lookupErr == nil { + srvRecord.Addresses = common.Map(addrs, func(it netip.Addr) Address { + var addrFamily int32 + if it.Is4() { + addrFamily = syscall.AF_INET + } else { + addrFamily = syscall.AF_INET6 + } + return Address{ + IfIndex: ifIndex, + Family: addrFamily, + Address: it.AsSlice(), + } + }) + } + for _, a := range srvResponse.Answer { + if cname, ok := a.(*mDNS.CNAME); ok && cname.Header().Name == rr.Target { + srvRecord.CNAME = cname.Target + break + } + } + srvData = append(srvData, srvRecord) + } + } + for _, rawRR := range txtResponse.Answer { + switch rr := rawRR.(type) { + case *mDNS.TXT: + data := make([]byte, mDNS.Len(rr)) + _, packErr := mDNS.PackRR(rr, data, 0, nil, false) + if packErr == nil { + txtData = append(txtData, data) + } + } + } + canonicalName = mDNS.CanonicalName(hostname) + canonicalType = mDNS.CanonicalName(sType) + canonicalDomain = mDNS.CanonicalName(domain) + return +} + +func (t *resolve1Manager) SetLinkDNS(sender dbus.Sender, ifIndex int32, addresses []LinkDNS) *dbus.Error { + t.linkAccess.Lock() + defer t.linkAccess.Unlock() + link, err := t.getLink(ifIndex) + if err != nil { + return wrapError(err) + } + link.address = addresses + if len(addresses) > 0 { + t.log(sender, "SetLinkDNS ", link.iif.Name, " ", strings.Join(common.Map(addresses, func(it LinkDNS) string { + return M.AddrFromIP(it.Address).String() + }), ", ")) + } else { + t.log(sender, "SetLinkDNS ", link.iif.Name, " (empty)") + } + return t.postUpdate(link) +} + +func (t *resolve1Manager) SetLinkDNSEx(sender dbus.Sender, ifIndex int32, addresses []LinkDNSEx) *dbus.Error { + t.linkAccess.Lock() + defer t.linkAccess.Unlock() + link, err := t.getLink(ifIndex) + if err != nil { + return wrapError(err) + } + link.addressEx = addresses + if len(addresses) > 0 { + t.log(sender, "SetLinkDNSEx ", link.iif.Name, " ", strings.Join(common.Map(addresses, func(it LinkDNSEx) string { + return M.SocksaddrFrom(M.AddrFromIP(it.Address), it.Port).String() + }), ", ")) + } else { + t.log(sender, "SetLinkDNSEx ", link.iif.Name, " (empty)") + } + return t.postUpdate(link) +} + +func (t *resolve1Manager) SetLinkDomains(sender dbus.Sender, ifIndex int32, domains []LinkDomain) *dbus.Error { + t.linkAccess.Lock() + defer t.linkAccess.Unlock() + link, err := t.getLink(ifIndex) + if err != nil { + return wrapError(err) + } + link.domain = domains + if len(domains) > 0 { + t.log(sender, "SetLinkDomains ", link.iif.Name, " ", strings.Join(common.Map(domains, func(domain LinkDomain) string { + if !domain.RoutingOnly { + return domain.Domain + } else { + return "~" + domain.Domain + } + }), ", ")) + } else { + t.log(sender, "SetLinkDomains ", link.iif.Name, " (empty)") + } + return t.postUpdate(link) +} + +func (t *resolve1Manager) SetLinkDefaultRoute(sender dbus.Sender, ifIndex int32, defaultRoute bool) *dbus.Error { + t.linkAccess.Lock() + defer t.linkAccess.Unlock() + link, err := t.getLink(ifIndex) + if err != nil { + return err + } + link.defaultRoute = defaultRoute + if defaultRoute { + t.defaultRouteSequence = append(common.Filter(t.defaultRouteSequence, func(it int32) bool { return it != ifIndex }), ifIndex) + } else { + t.defaultRouteSequence = common.Filter(t.defaultRouteSequence, func(it int32) bool { return it != ifIndex }) + } + var defaultRouteString string + if defaultRoute { + defaultRouteString = "yes" + } else { + defaultRouteString = "no" + } + t.log(sender, "SetLinkDefaultRoute ", link.iif.Name, " ", defaultRouteString) + return t.postUpdate(link) +} + +func (t *resolve1Manager) SetLinkLLMNR(ifIndex int32, llmnrMode string) *dbus.Error { + return nil +} + +func (t *resolve1Manager) SetLinkMulticastDNS(ifIndex int32, mdnsMode string) *dbus.Error { + return nil +} + +func (t *resolve1Manager) SetLinkDNSOverTLS(sender dbus.Sender, ifIndex int32, dotMode string) *dbus.Error { + t.linkAccess.Lock() + defer t.linkAccess.Unlock() + link, err := t.getLink(ifIndex) + if err != nil { + return wrapError(err) + } + switch dotMode { + case "yes": + link.dnsOverTLS = true + case "": + dotMode = "no" + fallthrough + case "opportunistic", "no": + link.dnsOverTLS = false + } + t.log(sender, "SetLinkDNSOverTLS ", link.iif.Name, " ", dotMode) + return t.postUpdate(link) +} + +func (t *resolve1Manager) SetLinkDNSSEC(ifIndex int32, dnssecMode string) *dbus.Error { + return nil +} + +func (t *resolve1Manager) SetLinkDNSSECNegativeTrustAnchors(ifIndex int32, domains []string) *dbus.Error { + return nil +} + +func (t *resolve1Manager) RevertLink(sender dbus.Sender, ifIndex int32) *dbus.Error { + t.linkAccess.Lock() + defer t.linkAccess.Unlock() + link, err := t.getLink(ifIndex) + if err != nil { + return wrapError(err) + } + delete(t.links, ifIndex) + t.log(sender, "RevertLink ", link.iif.Name) + return t.postUpdate(link) +} + +// TODO: implement RegisterService, UnregisterService + +func (t *resolve1Manager) RegisterService(sender dbus.Sender, identifier string, nameTemplate string, serviceType string, port uint16, priority uint16, weight uint16, txtRecords []TXTRecord) (objectPath dbus.ObjectPath, dbusErr *dbus.Error) { + return "", wrapError(E.New("not implemented")) +} + +func (t *resolve1Manager) UnregisterService(sender dbus.Sender, servicePath dbus.ObjectPath) error { + return wrapError(E.New("not implemented")) +} + +func (t *resolve1Manager) ResetStatistics() *dbus.Error { + return nil +} + +func (t *resolve1Manager) FlushCaches(sender dbus.Sender) *dbus.Error { + t.dnsRouter.ClearCache() + t.log(sender, "FlushCaches") + return nil +} + +func (t *resolve1Manager) ResetServerFeatures() *dbus.Error { + return nil +} + +func (t *resolve1Manager) postUpdate(link *TransportLink) *dbus.Error { + if t.updateCallback != nil { + return wrapError(t.updateCallback(link)) + } + return nil +} + +func rcodeError(rcode int) *dbus.Error { + return dbus.NewError("org.freedesktop.resolve1.DnsError."+mDNS.RcodeToString[rcode], []any{mDNS.RcodeToString[rcode]}) +} + +func wrapError(err error) *dbus.Error { + if err == nil { + return nil + } + var rcode dns.RcodeError + if errors.As(err, &rcode) { + return rcodeError(int(rcode)) + } + return dbus.MakeFailedError(err) +} diff --git a/service/resolved/service.go b/service/resolved/service.go new file mode 100644 index 0000000000..8f9740a06d --- /dev/null +++ b/service/resolved/service.go @@ -0,0 +1,254 @@ +//go:build linux + +package resolved + +import ( + "context" + "net" + "strings" + "sync" + "time" + + "github.com/sagernet/sing-box/adapter" + boxService "github.com/sagernet/sing-box/adapter/service" + "github.com/sagernet/sing-box/common/listener" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + dnsOutbound "github.com/sagernet/sing-box/protocol/dns" + tun "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service" + + "github.com/godbus/dbus/v5" + mDNS "github.com/miekg/dns" +) + +func RegisterService(registry *boxService.Registry) { + boxService.Register[option.ResolvedServiceOptions](registry, C.TypeResolved, NewService) +} + +type Service struct { + boxService.Adapter + ctx context.Context + logger log.ContextLogger + network adapter.NetworkManager + dnsRouter adapter.DNSRouter + listener *listener.Listener + systemBus *dbus.Conn + linkAccess sync.RWMutex + links map[int32]*TransportLink + defaultRouteSequence []int32 + networkUpdateCallback *list.Element[tun.NetworkUpdateCallback] + updateCallback func(*TransportLink) error + deleteCallback func(*TransportLink) +} + +type TransportLink struct { + iif *control.Interface + address []LinkDNS + addressEx []LinkDNSEx + domain []LinkDomain + defaultRoute bool + dnsOverTLS bool + // dnsOverTLSFallback bool +} + +func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.ResolvedServiceOptions) (adapter.Service, error) { + inbound := &Service{ + Adapter: boxService.NewAdapter(C.TypeResolved, tag), + ctx: ctx, + logger: logger, + network: service.FromContext[adapter.NetworkManager](ctx), + dnsRouter: service.FromContext[adapter.DNSRouter](ctx), + links: make(map[int32]*TransportLink), + } + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP, N.NetworkUDP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + OOBPacketHandler: inbound, + ThreadUnsafePacketWriter: true, + }) + return inbound, nil +} + +func (i *Service) Start(stage adapter.StartStage) error { + switch stage { + case adapter.StartStateInitialize: + inboundManager := service.FromContext[adapter.ServiceManager](i.ctx) + for _, transport := range inboundManager.Services() { + if transport.Type() == C.TypeResolved && transport != i { + return E.New("multiple resolved service are not supported") + } + } + systemBus, err := dbus.SystemBus() + if err != nil { + return err + } + i.systemBus = systemBus + err = systemBus.Export((*resolve1Manager)(i), "/org/freedesktop/resolve1", "org.freedesktop.resolve1.Manager") + if err != nil { + return err + } + reply, err := systemBus.RequestName("org.freedesktop.resolve1", dbus.NameFlagDoNotQueue) + if err != nil { + return err + } + switch reply { + case dbus.RequestNameReplyPrimaryOwner: + case dbus.RequestNameReplyExists: + return E.New("D-Bus object already exists, maybe real resolved is running") + default: + return E.New("unknown request name reply: ", reply) + } + i.networkUpdateCallback = i.network.NetworkMonitor().RegisterCallback(i.onNetworkUpdate) + case adapter.StartStateStart: + err := i.listener.Start() + if err != nil { + return err + } + } + return nil +} + +func (i *Service) Close() error { + if i.networkUpdateCallback != nil { + i.network.NetworkMonitor().UnregisterCallback(i.networkUpdateCallback) + } + if i.systemBus != nil { + i.systemBus.ReleaseName("org.freedesktop.resolve1") + i.systemBus.Close() + } + return i.listener.Close() +} + +func (i *Service) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + metadata.Inbound = i.Tag() + metadata.InboundType = i.Type() + metadata.Destination = M.Socksaddr{} + for { + conn.SetReadDeadline(time.Now().Add(C.DNSTimeout)) + err := dnsOutbound.HandleStreamDNSRequest(ctx, i.dnsRouter, conn, metadata) + if err != nil { + N.CloseOnHandshakeFailure(conn, onClose, err) + return + } + } +} + +func (i *Service) NewPacket(buffer *buf.Buffer, oob []byte, source M.Socksaddr) { + go i.exchangePacket(buffer, oob, source) +} + +func (i *Service) exchangePacket(buffer *buf.Buffer, oob []byte, source M.Socksaddr) { + ctx := log.ContextWithNewID(i.ctx) + err := i.exchangePacket0(ctx, buffer, oob, source) + if err != nil { + i.logger.ErrorContext(ctx, "process DNS packet: ", err) + } +} + +func (i *Service) exchangePacket0(ctx context.Context, buffer *buf.Buffer, oob []byte, source M.Socksaddr) error { + var message mDNS.Msg + err := message.Unpack(buffer.Bytes()) + buffer.Release() + if err != nil { + return E.Cause(err, "unpack request") + } + var metadata adapter.InboundContext + metadata.Source = source + metadata.InboundType = i.Type() + metadata.Inbound = i.Tag() + response, err := i.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), &message, adapter.DNSQueryOptions{}) + if err != nil { + return err + } + responseBuffer, err := dns.TruncateDNSMessage(&message, response, 0) + if err != nil { + return err + } + defer responseBuffer.Release() + _, _, err = i.listener.UDPConn().WriteMsgUDPAddrPort(responseBuffer.Bytes(), oob, source.AddrPort()) + return err +} + +func (i *Service) onNetworkUpdate() { + i.linkAccess.Lock() + defer i.linkAccess.Unlock() + var deleteIfIndex []int + for ifIndex, link := range i.links { + iif, err := i.network.InterfaceFinder().ByIndex(int(ifIndex)) + if err != nil || iif != link.iif { + deleteIfIndex = append(deleteIfIndex, int(ifIndex)) + } + i.defaultRouteSequence = common.Filter(i.defaultRouteSequence, func(it int32) bool { + return it != ifIndex + }) + if i.deleteCallback != nil { + i.deleteCallback(link) + } + } + for _, ifIndex := range deleteIfIndex { + delete(i.links, int32(ifIndex)) + } +} + +func (conf *TransportLink) nameList(ndots int, name string) []string { + search := common.Map(common.Filter(conf.domain, func(it LinkDomain) bool { + return !it.RoutingOnly + }), func(it LinkDomain) string { + return it.Domain + }) + + l := len(name) + rooted := l > 0 && name[l-1] == '.' + if l > 254 || l == 254 && !rooted { + return nil + } + + if rooted { + if avoidDNS(name) { + return nil + } + return []string{name} + } + + hasNdots := strings.Count(name, ".") >= ndots + name += "." + // l++ + + names := make([]string, 0, 1+len(search)) + if hasNdots && !avoidDNS(name) { + names = append(names, name) + } + for _, suffix := range search { + fqdn := name + suffix + if !avoidDNS(fqdn) && len(fqdn) <= 254 { + names = append(names, fqdn) + } + } + if !hasNdots && !avoidDNS(name) { + names = append(names, name) + } + return names +} + +func avoidDNS(name string) bool { + if name == "" { + return true + } + if name[len(name)-1] == '.' { + name = name[:len(name)-1] + } + return strings.HasSuffix(name, ".onion") +} diff --git a/service/resolved/stub.go b/service/resolved/stub.go new file mode 100644 index 0000000000..ede646919a --- /dev/null +++ b/service/resolved/stub.go @@ -0,0 +1,27 @@ +//go:build !linux + +package resolved + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + boxService "github.com/sagernet/sing-box/adapter/service" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func RegisterService(registry *boxService.Registry) { + boxService.Register[option.ResolvedServiceOptions](registry, C.TypeResolved, func(ctx context.Context, logger log.ContextLogger, tag string, options option.ResolvedServiceOptions) (adapter.Service, error) { + return nil, E.New("resolved service is only supported on Linux") + }) +} + +func RegisterTransport(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.ResolvedDNSServerOptions](registry, C.TypeResolved, func(ctx context.Context, logger log.ContextLogger, tag string, options option.ResolvedDNSServerOptions) (adapter.DNSTransport, error) { + return nil, E.New("resolved DNS server is only supported on Linux") + }) +} diff --git a/service/resolved/transport.go b/service/resolved/transport.go new file mode 100644 index 0000000000..df062a79c7 --- /dev/null +++ b/service/resolved/transport.go @@ -0,0 +1,326 @@ +//go:build linux + +package resolved + +import ( + "context" + "net/netip" + "os" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/dns/transport" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/service" + + mDNS "github.com/miekg/dns" +) + +func RegisterTransport(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.ResolvedDNSServerOptions](registry, C.TypeResolved, NewTransport) +} + +var ( + _ adapter.DNSTransport = (*Transport)(nil) + _ adapter.DNSTransportWithPreferredDomain = (*Transport)(nil) +) + +type Transport struct { + dns.TransportAdapter + ctx context.Context + logger logger.ContextLogger + serviceTag string + acceptDefaultResolvers bool + ndots int + timeout time.Duration + attempts int + rotate bool + service *Service + linkAccess sync.RWMutex + linkServers map[*TransportLink]*LinkServers +} + +type LinkServers struct { + Link *TransportLink + Servers []adapter.DNSTransport + serverOffset uint32 +} + +func (c *LinkServers) ServerOffset(rotate bool) uint32 { + if rotate { + return atomic.AddUint32(&c.serverOffset, 1) - 1 + } + return 0 +} + +func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.ResolvedDNSServerOptions) (adapter.DNSTransport, error) { + return &Transport{ + TransportAdapter: dns.NewTransportAdapter(C.DNSTypeDHCP, tag, nil), + ctx: ctx, + logger: logger, + serviceTag: options.Service, + acceptDefaultResolvers: options.AcceptDefaultResolvers, + // ndots: options.NDots, + // timeout: time.Duration(options.Timeout), + // attempts: options.Attempts, + // rotate: options.Rotate, + ndots: 1, + timeout: 5 * time.Second, + attempts: 2, + linkServers: make(map[*TransportLink]*LinkServers), + }, nil +} + +func (t *Transport) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateInitialize { + return nil + } + serviceManager := service.FromContext[adapter.ServiceManager](t.ctx) + service, loaded := serviceManager.Get(t.serviceTag) + if !loaded { + return E.New("service not found: ", t.serviceTag) + } + resolvedInbound, isResolved := service.(*Service) + if !isResolved { + return E.New("service is not resolved: ", t.serviceTag) + } + resolvedInbound.updateCallback = t.updateTransports + resolvedInbound.deleteCallback = t.deleteTransport + t.service = resolvedInbound + return nil +} + +func (t *Transport) Close() error { + t.linkAccess.RLock() + defer t.linkAccess.RUnlock() + for _, servers := range t.linkServers { + for _, server := range servers.Servers { + server.Close() + } + } + return nil +} + +func (t *Transport) Reset() { + t.linkAccess.RLock() + defer t.linkAccess.RUnlock() + for _, servers := range t.linkServers { + for _, server := range servers.Servers { + server.Reset() + } + } +} + +func (t *Transport) updateTransports(link *TransportLink) error { + t.linkAccess.Lock() + defer t.linkAccess.Unlock() + if servers, loaded := t.linkServers[link]; loaded { + for _, server := range servers.Servers { + server.Close() + } + } + serverDialer := common.Must1(dialer.NewDefault(t.ctx, option.DialerOptions{ + BindInterface: link.iif.Name, + UDPFragmentDefault: true, + })) + var transports []adapter.DNSTransport + for _, address := range link.address { + serverAddr, ok := netip.AddrFromSlice(address.Address) + if !ok { + return os.ErrInvalid + } + if link.dnsOverTLS { + tlsConfig := common.Must1(tls.NewClient(t.ctx, t.logger, serverAddr.String(), option.OutboundTLSOptions{ + Enabled: true, + ServerName: serverAddr.String(), + })) + transports = append(transports, transport.NewTLSRaw(t.logger, t.TransportAdapter, serverDialer, M.SocksaddrFrom(serverAddr, 53), tlsConfig)) + + } else { + transports = append(transports, transport.NewUDPRaw(t.logger, t.TransportAdapter, serverDialer, M.SocksaddrFrom(serverAddr, 53))) + } + } + for _, address := range link.addressEx { + serverAddr, ok := netip.AddrFromSlice(address.Address) + if !ok { + return os.ErrInvalid + } + if link.dnsOverTLS { + var serverName string + if address.Name != "" { + serverName = address.Name + } else { + serverName = serverAddr.String() + } + tlsConfig := common.Must1(tls.NewClient(t.ctx, t.logger, serverAddr.String(), option.OutboundTLSOptions{ + Enabled: true, + ServerName: serverName, + })) + transports = append(transports, transport.NewTLSRaw(t.logger, t.TransportAdapter, serverDialer, M.SocksaddrFrom(serverAddr, address.Port), tlsConfig)) + + } else { + transports = append(transports, transport.NewUDPRaw(t.logger, t.TransportAdapter, serverDialer, M.SocksaddrFrom(serverAddr, address.Port))) + } + } + t.linkServers[link] = &LinkServers{ + Link: link, + Servers: transports, + } + return nil +} + +func (t *Transport) deleteTransport(link *TransportLink) { + t.linkAccess.Lock() + defer t.linkAccess.Unlock() + servers, loaded := t.linkServers[link] + if !loaded { + return + } + for _, server := range servers.Servers { + server.Close() + } + delete(t.linkServers, link) +} + +func (t *Transport) PreferredDomain(domain string) bool { + t.service.linkAccess.RLock() + defer t.service.linkAccess.RUnlock() + for _, link := range t.service.links { + for _, linkDomain := range link.domain { + if linkDomain.Domain == "." { + continue + } + if strings.HasSuffix(domain, linkDomain.Domain) { + return true + } + } + } + return false +} + +func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + question := message.Question[0] + var selectedLink *TransportLink + t.service.linkAccess.RLock() + for _, link := range t.service.links { + for _, domain := range link.domain { + if domain.Domain == "." && domain.RoutingOnly && !t.acceptDefaultResolvers { + continue + } + if strings.HasSuffix(question.Name, domain.Domain) { + selectedLink = link + } + } + } + if selectedLink == nil && t.acceptDefaultResolvers { + for l := len(t.service.defaultRouteSequence); l > 0; l-- { + selectedLink = t.service.links[t.service.defaultRouteSequence[l-1]] + if len(selectedLink.address) > 0 || len(selectedLink.addressEx) > 0 { + break + } + } + } + t.service.linkAccess.RUnlock() + if selectedLink == nil { + return dns.FixedResponseStatus(message, mDNS.RcodeNameError), nil + } + t.linkAccess.RLock() + servers := t.linkServers[selectedLink] + t.linkAccess.RUnlock() + if len(servers.Servers) == 0 { + return dns.FixedResponseStatus(message, mDNS.RcodeNameError), nil + } + if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA { + return t.exchangeParallel(ctx, servers, message) + } else { + return t.exchangeSingleRequest(ctx, servers, message) + } +} + +func (t *Transport) exchangeSingleRequest(ctx context.Context, servers *LinkServers, message *mDNS.Msg) (*mDNS.Msg, error) { + var lastErr error + for _, fqdn := range servers.Link.nameList(t.ndots, message.Question[0].Name) { + response, err := t.tryOneName(ctx, servers, message, fqdn) + if err != nil { + lastErr = err + continue + } + return response, nil + } + return nil, lastErr +} + +func (t *Transport) tryOneName(ctx context.Context, servers *LinkServers, message *mDNS.Msg, fqdn string) (*mDNS.Msg, error) { + serverOffset := servers.ServerOffset(t.rotate) + sLen := uint32(len(servers.Servers)) + var lastErr error + for i := 0; i < t.attempts; i++ { + for j := range sLen { + server := servers.Servers[(serverOffset+j)%sLen] + question := message.Question[0] + question.Name = fqdn + exchangeMessage := *message + exchangeMessage.Question = []mDNS.Question{question} + exchangeCtx, cancel := context.WithTimeout(ctx, t.timeout) + response, err := server.Exchange(exchangeCtx, &exchangeMessage) + cancel() + if err != nil { + lastErr = err + continue + } + return response, nil + } + } + return nil, E.Cause(lastErr, fqdn) +} + +func (t *Transport) exchangeParallel(ctx context.Context, servers *LinkServers, message *mDNS.Msg) (*mDNS.Msg, error) { + returned := make(chan struct{}) + defer close(returned) + type queryResult struct { + response *mDNS.Msg + err error + } + results := make(chan queryResult) + startRacer := func(ctx context.Context, fqdn string) { + response, err := t.tryOneName(ctx, servers, message, fqdn) + select { + case results <- queryResult{response, err}: + case <-returned: + } + } + queryCtx, queryCancel := context.WithCancel(ctx) + defer queryCancel() + var nameCount int + for _, fqdn := range servers.Link.nameList(t.ndots, message.Question[0].Name) { + nameCount++ + go startRacer(queryCtx, fqdn) + } + var errors []error + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + case result := <-results: + if result.err == nil { + return result.response, nil + } + errors = append(errors, result.err) + if len(errors) == nameCount { + return nil, E.Errors(errors...) + } + } + } +} diff --git a/service/smart_dns_pool/service.go b/service/smart_dns_pool/service.go new file mode 100644 index 0000000000..d04375ceab --- /dev/null +++ b/service/smart_dns_pool/service.go @@ -0,0 +1,210 @@ +// Package smart_dns_pool provides a sing-box service that runs a local DNS +// server fronting many recursive upstream resolvers, using +// github.com/hiddify/hmrd_multi_resolver_dns under the hood. +// +// Why: dnstt's tunnel sends DNS queries to a single recursive resolver +// (e.g. `8.8.8.8`). When that resolver gets rate-limited or blocked the +// tunnel stalls. Configure this service with the recursive resolvers you +// want to distribute load across, then point dnstt's `resolvers` at the +// service's listen address (e.g. `udp://127.0.0.1:19876`). dnstt sees a +// normal local resolver; the service transparently fans queries out to +// many real upstreams with deadline-aware failover, AIMD rate-limit +// throttling, and recovery probing. +package smart_dns_pool + +import ( + "context" + "fmt" + "net" + "net/netip" + "strconv" + "sync" + "time" + + "github.com/sagernet/sing-box/adapter" + boxService "github.com/sagernet/sing-box/adapter/service" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + + multidns "github.com/hiddify/hmrd_multi_resolver_dns" +) + +// RegisterService hooks the smart_dns_pool service type into the registry. +func RegisterService(registry *boxService.Registry) { + boxService.Register[option.SmartDNSPoolServiceOptions](registry, C.TypeSmartDNSPool, NewService) +} + +var _ adapter.Service = (*Service)(nil) + +type Service struct { + boxService.Adapter + logger log.ContextLogger + opts option.SmartDNSPoolServiceOptions + + listenAddr string + + mu sync.Mutex + mgr *multidns.Manager + server *multidns.Server + serverOnce sync.Once + serveErr chan error +} + +// NewService constructs a smart_dns_pool service. It validates the options +// up-front so config errors surface at parse time, but defers any network +// activity (binding the listener, building upstream transports) to Start. +func NewService(ctx context.Context, logger log.ContextLogger, tag string, opts option.SmartDNSPoolServiceOptions) (adapter.Service, error) { + if len(opts.Upstreams) == 0 { + return nil, E.New("smart_dns_pool: at least one upstream is required") + } + + host := "127.0.0.1" + if opts.Listen != nil { + host = opts.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})).String() + } + port := opts.ListenPort + if port == 0 { + return nil, E.New("smart_dns_pool: listen_port is required (e.g. 19876)") + } + + for i, up := range opts.Upstreams { + if up.Address == "" { + return nil, E.New("smart_dns_pool: upstream #", i, " missing address") + } + if _, err := protocolFromString(up.Type); err != nil { + return nil, E.Cause(err, "smart_dns_pool: upstream #", i) + } + } + + return &Service{ + Adapter: boxService.NewAdapter(C.TypeSmartDNSPool, tag), + logger: logger, + opts: opts, + listenAddr: net.JoinHostPort(host, strconv.Itoa(int(port))), + }, nil +} + +// Start brings up the smart pool and binds the local DNS listener. It runs +// at the StartStart stage so dnstt outbounds (which start later) find a +// listener already accepting queries on their first request. +func (s *Service) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + + s.mu.Lock() + defer s.mu.Unlock() + + mgr := multidns.New(multidns.Options{ + DefaultDeadline: time.Duration(s.opts.Deadline), + DefaultResolverTimeout: time.Duration(s.opts.PerAttempt), + ProbeInterval: time.Duration(s.opts.ProbeInterval), + DownAfterFailures: s.opts.DownAfter, + LoadBalance: lbFromString(s.opts.LoadBalance), + Logger: newLoggerAdapter(s.logger, s.Tag()), + }) + + for i, up := range s.opts.Upstreams { + proto, _ := protocolFromString(up.Type) // already validated in NewService + cfg := multidns.ResolverConfig{ + Name: up.Name, + Protocol: proto, + Address: up.Address, + Weight: up.Weight, + } + if _, err := mgr.AddResolver(cfg); err != nil { + _ = mgr.Close() + return E.Cause(err, "smart_dns_pool: register upstream #", i, " (", up.Type, " ", up.Address, ")") + } + } + + srv := mgr.NewServer(s.listenAddr) // UDP+TCP by default + s.serveErr = make(chan error, 1) + go func() { + if err := srv.ListenAndServe(); err != nil { + s.serveErr <- err + s.logger.Error("smart_dns_pool: listener exited: ", err) + } + }() + + s.mgr = mgr + s.server = srv + s.logger.Info("smart_dns_pool: listening on ", s.listenAddr, " (udp+tcp), ", len(s.opts.Upstreams), " upstream(s)") + return nil +} + +func (s *Service) Close() error { + s.mu.Lock() + defer s.mu.Unlock() + + var firstErr error + if s.server != nil { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + if err := s.server.Shutdown(ctx); err != nil { + firstErr = err + } + cancel() + s.server = nil + } + if s.mgr != nil { + if err := s.mgr.Close(); err != nil && firstErr == nil { + firstErr = err + } + s.mgr = nil + } + return firstErr +} + +func protocolFromString(s string) (multidns.Protocol, error) { + switch s { + case "udp", "": + return multidns.ProtoUDP, nil + case "tcp": + return multidns.ProtoTCP, nil + case "tls", "dot": + return multidns.ProtoDoT, nil + case "https", "doh": + return multidns.ProtoDoH, nil + default: + return "", fmt.Errorf("unsupported upstream type %q (want udp|tcp|tls|https)", s) + } +} + +func lbFromString(s string) multidns.LBStrategy { + switch s { + case "weighted": + return multidns.LBWeighted + case "lowest_latency": + return multidns.LBLowestLatency + default: + return multidns.LBRoundRobin + } +} + +// loggerAdapter bridges sing-box's ContextLogger to multidns.Logger +// (printf-style). The tag prefix lets operators tell which service +// instance produced a line when multiple smart_dns_pool services are +// configured. +type loggerAdapter struct { + logger log.ContextLogger + tag string +} + +func newLoggerAdapter(logger log.ContextLogger, tag string) *loggerAdapter { + return &loggerAdapter{logger: logger, tag: tag} +} + +func (l *loggerAdapter) Debugf(format string, args ...any) { + l.logger.Debug(l.tag, ": ", fmt.Sprintf(format, args...)) +} +func (l *loggerAdapter) Infof(format string, args ...any) { + l.logger.Info(l.tag, ": ", fmt.Sprintf(format, args...)) +} +func (l *loggerAdapter) Warnf(format string, args ...any) { + l.logger.Warn(l.tag, ": ", fmt.Sprintf(format, args...)) +} +func (l *loggerAdapter) Errorf(format string, args ...any) { + l.logger.Error(l.tag, ": ", fmt.Sprintf(format, args...)) +} diff --git a/service/smart_dns_pool/service_test.go b/service/smart_dns_pool/service_test.go new file mode 100644 index 0000000000..e1055296f0 --- /dev/null +++ b/service/smart_dns_pool/service_test.go @@ -0,0 +1,259 @@ +package smart_dns_pool + +import ( + "context" + "net" + "strconv" + "strings" + "testing" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/json/badoption" + + "github.com/miekg/dns" +) + +// startUDPUpstream spins up a tiny in-process miekg/dns UDP server that +// answers any A query with answerIP, simulating a recursive resolver. +func startUDPUpstream(t *testing.T, answerIP string) (string, func()) { + t.Helper() + pc, err := net.ListenPacket("udp", "127.0.0.1:0") + if err != nil { + t.Fatalf("listen udp: %v", err) + } + srv := &dns.Server{ + PacketConn: pc, + Handler: dns.HandlerFunc(func(w dns.ResponseWriter, q *dns.Msg) { + r := new(dns.Msg) + r.SetReply(q) + r.Rcode = dns.RcodeSuccess + if len(q.Question) > 0 && q.Question[0].Qtype == dns.TypeA { + r.Answer = append(r.Answer, &dns.A{ + Hdr: dns.RR_Header{ + Name: q.Question[0].Name, Rrtype: dns.TypeA, + Class: dns.ClassINET, Ttl: 30, + }, + A: net.ParseIP(answerIP).To4(), + }) + } + _ = w.WriteMsg(r) + }), + } + go func() { _ = srv.ActivateAndServe() }() + return pc.LocalAddr().String(), func() { _ = srv.Shutdown() } +} + +// freePort returns a port number guaranteed to be free at the moment of +// the call (the listener is closed before returning, so technically there's +// a TOCTOU window, but it's fine for tests). +func freePort(t *testing.T) uint16 { + t.Helper() + l, err := net.ListenPacket("udp", "127.0.0.1:0") + if err != nil { + t.Fatalf("freePort: %v", err) + } + port := l.LocalAddr().(*net.UDPAddr).Port + l.Close() + return uint16(port) +} + +// digOnce sends a single A query for `name` to addr (host:port) over UDP and +// returns the resolved IP string, or fatals. +func digOnce(t *testing.T, addr, name string) string { + t.Helper() + c := &dns.Client{Net: "udp", Timeout: 1500 * time.Millisecond} + q := new(dns.Msg) + q.SetQuestion(dns.Fqdn(name), dns.TypeA) + resp, _, err := c.Exchange(q, addr) + if err != nil { + t.Fatalf("exchange: %v", err) + } + if resp == nil || resp.Rcode != dns.RcodeSuccess || len(resp.Answer) != 1 { + t.Fatalf("unexpected resp: %#v", resp) + } + a, ok := resp.Answer[0].(*dns.A) + if !ok || a.A == nil { + t.Fatalf("not an A record: %#v", resp.Answer[0]) + } + return a.A.String() +} + +// TestSmartDNSPool_EndToEnd builds the service from a real +// SmartDNSPoolServiceOptions, starts it, sends an A query to its bound +// address, and verifies the answer routed through one of the configured +// upstreams. This is the integration shape dnstt would use: resolver +// address = 127.0.0.1: instead of 8.8.8.8:53. +func TestSmartDNSPool_EndToEnd(t *testing.T) { + upA, stopA := startUDPUpstream(t, "192.0.2.51") + defer stopA() + upB, stopB := startUDPUpstream(t, "192.0.2.52") + defer stopB() + + port := freePort(t) + listenAddr := badoption.Addr{} + if err := listenAddr.UnmarshalJSON([]byte(`"127.0.0.1"`)); err != nil { + t.Fatalf("parse listen addr: %v", err) + } + + opts := option.SmartDNSPoolServiceOptions{ + ListenOptions: option.ListenOptions{ + Listen: &listenAddr, + ListenPort: port, + }, + Upstreams: []option.SmartDNSPoolUpstreamOptions{ + {Type: "udp", Address: upA}, + {Type: "udp", Address: upB}, + }, + LoadBalance: "roundrobin", + } + + svc, err := NewService(context.Background(), log.NewNOPFactory().NewLogger("smart-dns-pool-test"), "smart-dns-pool", opts) + if err != nil { + t.Fatalf("NewService: %v", err) + } + if err := svc.Start(adapter.StartStateStart); err != nil { + t.Fatalf("Start: %v", err) + } + defer svc.Close() + + addr := "127.0.0.1:" + strconv.Itoa(int(port)) + + // Wait for the listener to bind. + deadline := time.Now().Add(time.Second) + for time.Now().Before(deadline) { + c := &dns.Client{Net: "udp", Timeout: 100 * time.Millisecond} + q := new(dns.Msg) + q.SetQuestion("ready.test.", dns.TypeA) + if _, _, err := c.Exchange(q, addr); err == nil { + break + } + time.Sleep(20 * time.Millisecond) + } + + // Run several queries — round-robin should distribute across both upstreams. + answers := map[string]int{} + for i := 0; i < 10; i++ { + ip := digOnce(t, addr, "smart.test.") + answers[ip]++ + } + if answers["192.0.2.51"] == 0 || answers["192.0.2.52"] == 0 { + t.Fatalf("round-robin didn't reach both upstreams: %#v", answers) + } +} + +// TestSmartDNSPool_FailoverWithinDeadline configures one dropping upstream +// and one healthy one, and verifies queries through the local listener +// still get answered (the smart pool's failover keeps the tunnel alive +// even when one resolver is dead — the headline use case for dnstt). +func TestSmartDNSPool_FailoverWithinDeadline(t *testing.T) { + // Dropping upstream: a UDP socket that just drops packets. + pc, err := net.ListenPacket("udp", "127.0.0.1:0") + if err != nil { + t.Fatalf("listen drop: %v", err) + } + defer pc.Close() + dropAddr := pc.LocalAddr().String() + + good, stopGood := startUDPUpstream(t, "192.0.2.60") + defer stopGood() + + port := freePort(t) + listenAddr := badoption.Addr{} + if err := listenAddr.UnmarshalJSON([]byte(`"127.0.0.1"`)); err != nil { + t.Fatalf("parse listen addr: %v", err) + } + + opts := option.SmartDNSPoolServiceOptions{ + ListenOptions: option.ListenOptions{Listen: &listenAddr, ListenPort: port}, + Upstreams: []option.SmartDNSPoolUpstreamOptions{ + {Type: "udp", Address: dropAddr}, + {Type: "udp", Address: good}, + }, + Deadline: badoption.Duration(2 * time.Second), + PerAttempt: badoption.Duration(150 * time.Millisecond), + } + + svc, err := NewService(context.Background(), log.NewNOPFactory().NewLogger("smart-dns-pool-test"), "smart-dns-pool", opts) + if err != nil { + t.Fatalf("NewService: %v", err) + } + if err := svc.Start(adapter.StartStateStart); err != nil { + t.Fatalf("Start: %v", err) + } + defer svc.Close() + + addr := "127.0.0.1:" + strconv.Itoa(int(port)) + // Warmup until the listener accepts. + deadline := time.Now().Add(time.Second) + for time.Now().Before(deadline) { + c := &dns.Client{Net: "udp", Timeout: 100 * time.Millisecond} + q := new(dns.Msg) + q.SetQuestion("ready.test.", dns.TypeA) + if _, _, err := c.Exchange(q, addr); err == nil { + break + } + time.Sleep(20 * time.Millisecond) + } + + saw := false + for i := 0; i < 6; i++ { + ip := digOnce(t, addr, "fail.test.") + if ip == "192.0.2.60" { + saw = true + } + } + if !saw { + t.Fatalf("no successful answers — failover broken") + } +} + +// TestSmartDNSPool_RejectsBadConfig verifies validation at NewService time. +func TestSmartDNSPool_RejectsBadConfig(t *testing.T) { + cases := []struct { + name string + opts option.SmartDNSPoolServiceOptions + wantErr string + wantOK bool + }{ + { + name: "no upstreams", + opts: option.SmartDNSPoolServiceOptions{ + ListenOptions: option.ListenOptions{ListenPort: 19000}, + }, + wantErr: "at least one upstream is required", + }, + { + name: "missing port", + opts: option.SmartDNSPoolServiceOptions{ + Upstreams: []option.SmartDNSPoolUpstreamOptions{ + {Type: "udp", Address: "1.1.1.1:53"}, + }, + }, + wantErr: "listen_port is required", + }, + { + name: "unsupported type", + opts: option.SmartDNSPoolServiceOptions{ + ListenOptions: option.ListenOptions{ListenPort: 19000}, + Upstreams: []option.SmartDNSPoolUpstreamOptions{ + {Type: "h3", Address: "1.1.1.1:443"}, + }, + }, + wantErr: "unsupported upstream type", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + _, err := NewService(context.Background(), log.NewNOPFactory().NewLogger("test"), "t", tc.opts) + if err == nil { + t.Fatalf("expected error containing %q, got nil", tc.wantErr) + } + if !strings.Contains(err.Error(), tc.wantErr) { + t.Fatalf("expected error containing %q, got: %v", tc.wantErr, err) + } + }) + } +} diff --git a/service/ssmapi/api.go b/service/ssmapi/api.go new file mode 100644 index 0000000000..6a067c50cf --- /dev/null +++ b/service/ssmapi/api.go @@ -0,0 +1,177 @@ +package ssmapi + +import ( + "net/http" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common/logger" + sHTTP "github.com/sagernet/sing/protocol/http" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" +) + +type APIServer struct { + logger logger.Logger + traffic *TrafficManager + user *UserManager +} + +func NewAPIServer(logger logger.Logger, traffic *TrafficManager, user *UserManager) *APIServer { + return &APIServer{ + logger: logger, + traffic: traffic, + user: user, + } +} + +func (s *APIServer) Route(r chi.Router) { + r.Route("/server/v1", func(r chi.Router) { + r.Use(func(handler http.Handler) http.Handler { + return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + s.logger.Debug(request.Method, " ", request.RequestURI, " ", sHTTP.SourceAddress(request)) + handler.ServeHTTP(writer, request) + }) + }) + r.Get("/", s.getServerInfo) + r.Get("/users", s.listUser) + r.Post("/users", s.addUser) + r.Get("/users/{username}", s.getUser) + r.Put("/users/{username}", s.updateUser) + r.Delete("/users/{username}", s.deleteUser) + r.Get("/stats", s.getStats) + }) +} + +func (s *APIServer) getServerInfo(writer http.ResponseWriter, request *http.Request) { + render.JSON(writer, request, render.M{ + "server": "sing-box " + C.Version, + "apiVersion": "v1", + }) +} + +type UserObject struct { + UserName string `json:"username"` + Password string `json:"uPSK,omitempty"` + DownlinkBytes int64 `json:"downlinkBytes"` + UplinkBytes int64 `json:"uplinkBytes"` + DownlinkPackets int64 `json:"downlinkPackets"` + UplinkPackets int64 `json:"uplinkPackets"` + TCPSessions int64 `json:"tcpSessions"` + UDPSessions int64 `json:"udpSessions"` +} + +func (s *APIServer) listUser(writer http.ResponseWriter, request *http.Request) { + render.JSON(writer, request, render.M{ + "users": s.user.List(), + }) +} + +func (s *APIServer) addUser(writer http.ResponseWriter, request *http.Request) { + var addRequest struct { + UserName string `json:"username"` + Password string `json:"uPSK"` + } + err := render.DecodeJSON(request.Body, &addRequest) + if err != nil { + render.Status(request, http.StatusBadRequest) + render.PlainText(writer, request, err.Error()) + return + } + err = s.user.Add(addRequest.UserName, addRequest.Password) + if err != nil { + render.Status(request, http.StatusBadRequest) + render.PlainText(writer, request, err.Error()) + return + } + writer.WriteHeader(http.StatusCreated) +} + +func (s *APIServer) getUser(writer http.ResponseWriter, request *http.Request) { + userName := chi.URLParam(request, "username") + if userName == "" { + writer.WriteHeader(http.StatusBadRequest) + return + } + uPSK, loaded := s.user.Get(userName) + if !loaded { + writer.WriteHeader(http.StatusNotFound) + return + } + user := UserObject{ + UserName: userName, + Password: uPSK, + } + s.traffic.ReadUser(&user) + render.JSON(writer, request, user) +} + +func (s *APIServer) updateUser(writer http.ResponseWriter, request *http.Request) { + userName := chi.URLParam(request, "username") + if userName == "" { + writer.WriteHeader(http.StatusBadRequest) + return + } + var updateRequest struct { + Password string `json:"uPSK"` + } + err := render.DecodeJSON(request.Body, &updateRequest) + if err != nil { + render.Status(request, http.StatusBadRequest) + render.PlainText(writer, request, err.Error()) + return + } + _, loaded := s.user.Get(userName) + if !loaded { + writer.WriteHeader(http.StatusNotFound) + return + } + err = s.user.Update(userName, updateRequest.Password) + if err != nil { + render.Status(request, http.StatusBadRequest) + render.PlainText(writer, request, err.Error()) + return + } + writer.WriteHeader(http.StatusNoContent) +} + +func (s *APIServer) deleteUser(writer http.ResponseWriter, request *http.Request) { + userName := chi.URLParam(request, "username") + if userName == "" { + writer.WriteHeader(http.StatusBadRequest) + return + } + _, loaded := s.user.Get(userName) + if !loaded { + writer.WriteHeader(http.StatusNotFound) + return + } + err := s.user.Delete(userName) + if err != nil { + render.Status(request, http.StatusBadRequest) + render.PlainText(writer, request, err.Error()) + return + } + writer.WriteHeader(http.StatusNoContent) +} + +func (s *APIServer) getStats(writer http.ResponseWriter, request *http.Request) { + requireClear := request.URL.Query().Get("clear") == "true" + + users := s.user.List() + s.traffic.ReadUsers(users, requireClear) + for i := range users { + users[i].Password = "" + } + uplinkBytes, downlinkBytes, uplinkPackets, downlinkPackets, tcpSessions, udpSessions := s.traffic.ReadGlobal(requireClear) + + render.JSON(writer, request, render.M{ + "uplinkBytes": uplinkBytes, + "downlinkBytes": downlinkBytes, + "uplinkPackets": uplinkPackets, + "downlinkPackets": downlinkPackets, + "tcpSessions": tcpSessions, + "udpSessions": udpSessions, + "users": users, + }) +} diff --git a/service/ssmapi/cache.go b/service/ssmapi/cache.go new file mode 100644 index 0000000000..f942265d4a --- /dev/null +++ b/service/ssmapi/cache.go @@ -0,0 +1,239 @@ +package ssmapi + +import ( + "bytes" + "os" + "path/filepath" + "sort" + "sync/atomic" + + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/service/filemanager" +) + +type Cache struct { + Endpoints *badjson.TypedMap[string, *EndpointCache] `json:"endpoints"` +} + +type EndpointCache struct { + GlobalUplink int64 `json:"global_uplink"` + GlobalDownlink int64 `json:"global_downlink"` + GlobalUplinkPackets int64 `json:"global_uplink_packets"` + GlobalDownlinkPackets int64 `json:"global_downlink_packets"` + GlobalTCPSessions int64 `json:"global_tcp_sessions"` + GlobalUDPSessions int64 `json:"global_udp_sessions"` + UserUplink *badjson.TypedMap[string, int64] `json:"user_uplink"` + UserDownlink *badjson.TypedMap[string, int64] `json:"user_downlink"` + UserUplinkPackets *badjson.TypedMap[string, int64] `json:"user_uplink_packets"` + UserDownlinkPackets *badjson.TypedMap[string, int64] `json:"user_downlink_packets"` + UserTCPSessions *badjson.TypedMap[string, int64] `json:"user_tcp_sessions"` + UserUDPSessions *badjson.TypedMap[string, int64] `json:"user_udp_sessions"` + Users *badjson.TypedMap[string, string] `json:"users"` +} + +func (s *Service) loadCache() error { + if s.cachePath == "" { + return nil + } + basePath := filemanager.BasePath(s.ctx, s.cachePath) + cacheBinary, err := os.ReadFile(basePath) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + err = s.decodeCache(cacheBinary) + if err != nil { + os.RemoveAll(basePath) + return err + } + s.cacheMutex.Lock() + s.lastSavedCache = cacheBinary + s.cacheMutex.Unlock() + return nil +} + +func (s *Service) saveCache() error { + if s.cachePath == "" { + return nil + } + cacheBinary, err := s.encodeCache() + if err != nil { + return err + } + s.cacheMutex.Lock() + defer s.cacheMutex.Unlock() + if bytes.Equal(s.lastSavedCache, cacheBinary) { + return nil + } + return s.writeCache(cacheBinary) +} + +func (s *Service) writeCache(cacheBinary []byte) error { + basePath := filemanager.BasePath(s.ctx, s.cachePath) + err := os.MkdirAll(filepath.Dir(basePath), 0o777) + if err != nil { + return err + } + err = os.WriteFile(basePath, cacheBinary, 0o644) + if err != nil { + return err + } + s.lastSavedCache = cacheBinary + return nil +} + +func (s *Service) decodeCache(cacheBinary []byte) error { + if len(cacheBinary) == 0 { + return nil + } + cache, err := json.UnmarshalExtended[*Cache](cacheBinary) + if err != nil { + return err + } + if cache.Endpoints == nil || cache.Endpoints.Size() == 0 { + return nil + } + for _, entry := range cache.Endpoints.Entries() { + trafficManager, loaded := s.traffics[entry.Key] + if !loaded { + continue + } + trafficManager.globalUplink.Store(entry.Value.GlobalUplink) + trafficManager.globalDownlink.Store(entry.Value.GlobalDownlink) + trafficManager.globalUplinkPackets.Store(entry.Value.GlobalUplinkPackets) + trafficManager.globalDownlinkPackets.Store(entry.Value.GlobalDownlinkPackets) + trafficManager.globalTCPSessions.Store(entry.Value.GlobalTCPSessions) + trafficManager.globalUDPSessions.Store(entry.Value.GlobalUDPSessions) + trafficManager.userUplink = typedAtomicInt64Map(entry.Value.UserUplink) + trafficManager.userDownlink = typedAtomicInt64Map(entry.Value.UserDownlink) + trafficManager.userUplinkPackets = typedAtomicInt64Map(entry.Value.UserUplinkPackets) + trafficManager.userDownlinkPackets = typedAtomicInt64Map(entry.Value.UserDownlinkPackets) + trafficManager.userTCPSessions = typedAtomicInt64Map(entry.Value.UserTCPSessions) + trafficManager.userUDPSessions = typedAtomicInt64Map(entry.Value.UserUDPSessions) + userManager, loaded := s.users[entry.Key] + if !loaded { + continue + } + userManager.usersMap = typedMap(entry.Value.Users) + _ = userManager.postUpdate(false) + } + return nil +} + +func (s *Service) encodeCache() ([]byte, error) { + endpoints := new(badjson.TypedMap[string, *EndpointCache]) + for tag, traffic := range s.traffics { + var ( + userUplink = new(badjson.TypedMap[string, int64]) + userDownlink = new(badjson.TypedMap[string, int64]) + userUplinkPackets = new(badjson.TypedMap[string, int64]) + userDownlinkPackets = new(badjson.TypedMap[string, int64]) + userTCPSessions = new(badjson.TypedMap[string, int64]) + userUDPSessions = new(badjson.TypedMap[string, int64]) + userMap = new(badjson.TypedMap[string, string]) + ) + for user, uplink := range traffic.userUplink { + if uplink.Load() > 0 { + userUplink.Put(user, uplink.Load()) + } + } + for user, downlink := range traffic.userDownlink { + if downlink.Load() > 0 { + userDownlink.Put(user, downlink.Load()) + } + } + for user, uplinkPackets := range traffic.userUplinkPackets { + if uplinkPackets.Load() > 0 { + userUplinkPackets.Put(user, uplinkPackets.Load()) + } + } + for user, downlinkPackets := range traffic.userDownlinkPackets { + if downlinkPackets.Load() > 0 { + userDownlinkPackets.Put(user, downlinkPackets.Load()) + } + } + for user, tcpSessions := range traffic.userTCPSessions { + if tcpSessions.Load() > 0 { + userTCPSessions.Put(user, tcpSessions.Load()) + } + } + for user, udpSessions := range traffic.userUDPSessions { + if udpSessions.Load() > 0 { + userUDPSessions.Put(user, udpSessions.Load()) + } + } + userManager := s.users[tag] + if userManager != nil && len(userManager.usersMap) > 0 { + userMap = new(badjson.TypedMap[string, string]) + for username, password := range userManager.usersMap { + if username != "" && password != "" { + userMap.Put(username, password) + } + } + } + endpoints.Put(tag, &EndpointCache{ + GlobalUplink: traffic.globalUplink.Load(), + GlobalDownlink: traffic.globalDownlink.Load(), + GlobalUplinkPackets: traffic.globalUplinkPackets.Load(), + GlobalDownlinkPackets: traffic.globalDownlinkPackets.Load(), + GlobalTCPSessions: traffic.globalTCPSessions.Load(), + GlobalUDPSessions: traffic.globalUDPSessions.Load(), + UserUplink: sortTypedMap(userUplink), + UserDownlink: sortTypedMap(userDownlink), + UserUplinkPackets: sortTypedMap(userUplinkPackets), + UserDownlinkPackets: sortTypedMap(userDownlinkPackets), + UserTCPSessions: sortTypedMap(userTCPSessions), + UserUDPSessions: sortTypedMap(userUDPSessions), + Users: sortTypedMap(userMap), + }) + } + var buffer bytes.Buffer + encoder := json.NewEncoder(&buffer) + encoder.SetIndent("", " ") + err := encoder.Encode(&Cache{ + Endpoints: sortTypedMap(endpoints), + }) + if err != nil { + return nil, err + } + return buffer.Bytes(), nil +} + +func sortTypedMap[T comparable](trafficMap *badjson.TypedMap[string, T]) *badjson.TypedMap[string, T] { + if trafficMap == nil { + return nil + } + keys := trafficMap.Keys() + sort.Strings(keys) + sortedMap := new(badjson.TypedMap[string, T]) + for _, key := range keys { + value, _ := trafficMap.Get(key) + sortedMap.Put(key, value) + } + return sortedMap +} + +func typedAtomicInt64Map(trafficMap *badjson.TypedMap[string, int64]) map[string]*atomic.Int64 { + result := make(map[string]*atomic.Int64) + if trafficMap != nil { + for _, entry := range trafficMap.Entries() { + counter := new(atomic.Int64) + counter.Store(entry.Value) + result[entry.Key] = counter + } + } + return result +} + +func typedMap[T comparable](trafficMap *badjson.TypedMap[string, T]) map[string]T { + result := make(map[string]T) + if trafficMap != nil { + for _, entry := range trafficMap.Entries() { + result[entry.Key] = entry.Value + } + } + return result +} diff --git a/service/ssmapi/server.go b/service/ssmapi/server.go new file mode 100644 index 0000000000..157ea150b4 --- /dev/null +++ b/service/ssmapi/server.go @@ -0,0 +1,163 @@ +package ssmapi + +import ( + "context" + "errors" + "net/http" + "sync" + "time" + + "github.com/sagernet/sing-box/adapter" + boxService "github.com/sagernet/sing-box/adapter/service" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + N "github.com/sagernet/sing/common/network" + aTLS "github.com/sagernet/sing/common/tls" + "github.com/sagernet/sing/service" + + "github.com/go-chi/chi/v5" + "golang.org/x/net/http2" +) + +func RegisterService(registry *boxService.Registry) { + boxService.Register[option.SSMAPIServiceOptions](registry, C.TypeSSMAPI, NewService) +} + +type Service struct { + boxService.Adapter + ctx context.Context + cancel context.CancelFunc + logger log.ContextLogger + listener *listener.Listener + tlsConfig tls.ServerConfig + httpServer *http.Server + traffics map[string]*TrafficManager + users map[string]*UserManager + cachePath string + saveTicker *time.Ticker + lastSavedCache []byte + cacheMutex sync.Mutex +} + +func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.SSMAPIServiceOptions) (adapter.Service, error) { + ctx, cancel := context.WithCancel(ctx) + chiRouter := chi.NewRouter() + s := &Service{ + Adapter: boxService.NewAdapter(C.TypeSSMAPI, tag), + ctx: ctx, + cancel: cancel, + logger: logger, + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + }), + httpServer: &http.Server{ + Handler: chiRouter, + }, + traffics: make(map[string]*TrafficManager), + users: make(map[string]*UserManager), + cachePath: options.CachePath, + } + inboundManager := service.FromContext[adapter.InboundManager](ctx) + if options.Servers.Size() == 0 { + return nil, E.New("missing servers") + } + for i, entry := range options.Servers.Entries() { + inbound, loaded := inboundManager.Get(entry.Value) + if !loaded { + return nil, E.New("parse SSM server[", i, "]: inbound ", entry.Value, " not found") + } + managedServer, isManaged := inbound.(adapter.ManagedSSMServer) + if !isManaged { + return nil, E.New("parse SSM server[", i, "]: inbound/", inbound.Type(), "[", inbound.Tag(), "] is not a SSM server") + } + traffic := NewTrafficManager() + managedServer.SetTracker(traffic) + user := NewUserManager(managedServer, traffic) + chiRouter.Route(entry.Key, NewAPIServer(logger, traffic, user).Route) + s.traffics[entry.Key] = traffic + s.users[entry.Key] = user + } + if options.TLS != nil { + tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + s.tlsConfig = tlsConfig + } + return s, nil +} + +func (s *Service) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + err := s.loadCache() + if err != nil { + s.logger.Error(E.Cause(err, "load cache")) + } + s.saveTicker = time.NewTicker(1 * time.Minute) + go s.loopSaveCache() + if s.tlsConfig != nil { + err = s.tlsConfig.Start() + if err != nil { + return E.Cause(err, "create TLS config") + } + } + tcpListener, err := s.listener.ListenTCP() + if err != nil { + return err + } + if s.tlsConfig != nil { + if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) { + s.tlsConfig.SetNextProtos(append([]string{"h2"}, s.tlsConfig.NextProtos()...)) + } + tcpListener = aTLS.NewListener(tcpListener, s.tlsConfig) + } + go func() { + err = s.httpServer.Serve(tcpListener) + if err != nil && !errors.Is(err, http.ErrServerClosed) { + s.logger.Error("serve error: ", err) + } + }() + return nil +} + +func (s *Service) loopSaveCache() { + for { + select { + case <-s.ctx.Done(): + return + case <-s.saveTicker.C: + err := s.saveCache() + if err != nil { + s.logger.Error(E.Cause(err, "save cache")) + } + } + } +} + +func (s *Service) Close() error { + if s.cancel != nil { + s.cancel() + } + if s.saveTicker != nil { + s.saveTicker.Stop() + } + err := s.saveCache() + if err != nil { + s.logger.Error(E.Cause(err, "save cache")) + } + return common.Close( + common.PtrOrNil(s.httpServer), + common.PtrOrNil(s.listener), + s.tlsConfig, + ) +} diff --git a/service/ssmapi/traffic.go b/service/ssmapi/traffic.go new file mode 100644 index 0000000000..4a669adb40 --- /dev/null +++ b/service/ssmapi/traffic.go @@ -0,0 +1,223 @@ +package ssmapi + +import ( + "net" + "sync" + "sync/atomic" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common/bufio" + N "github.com/sagernet/sing/common/network" +) + +var _ adapter.SSMTracker = (*TrafficManager)(nil) + +type TrafficManager struct { + globalUplink atomic.Int64 + globalDownlink atomic.Int64 + globalUplinkPackets atomic.Int64 + globalDownlinkPackets atomic.Int64 + globalTCPSessions atomic.Int64 + globalUDPSessions atomic.Int64 + userAccess sync.Mutex + userUplink map[string]*atomic.Int64 + userDownlink map[string]*atomic.Int64 + userUplinkPackets map[string]*atomic.Int64 + userDownlinkPackets map[string]*atomic.Int64 + userTCPSessions map[string]*atomic.Int64 + userUDPSessions map[string]*atomic.Int64 +} + +func NewTrafficManager() *TrafficManager { + manager := &TrafficManager{ + userUplink: make(map[string]*atomic.Int64), + userDownlink: make(map[string]*atomic.Int64), + userUplinkPackets: make(map[string]*atomic.Int64), + userDownlinkPackets: make(map[string]*atomic.Int64), + userTCPSessions: make(map[string]*atomic.Int64), + userUDPSessions: make(map[string]*atomic.Int64), + } + return manager +} + +func (s *TrafficManager) UpdateUsers(users []string) { + s.userAccess.Lock() + defer s.userAccess.Unlock() + newUserUplink := make(map[string]*atomic.Int64) + newUserDownlink := make(map[string]*atomic.Int64) + newUserUplinkPackets := make(map[string]*atomic.Int64) + newUserDownlinkPackets := make(map[string]*atomic.Int64) + newUserTCPSessions := make(map[string]*atomic.Int64) + newUserUDPSessions := make(map[string]*atomic.Int64) + for _, user := range users { + if counter, loaded := s.userUplink[user]; loaded { + newUserUplink[user] = counter + } + if counter, loaded := s.userDownlink[user]; loaded { + newUserDownlink[user] = counter + } + if counter, loaded := s.userUplinkPackets[user]; loaded { + newUserUplinkPackets[user] = counter + } + if counter, loaded := s.userDownlinkPackets[user]; loaded { + newUserDownlinkPackets[user] = counter + } + if counter, loaded := s.userTCPSessions[user]; loaded { + newUserTCPSessions[user] = counter + } + if counter, loaded := s.userUDPSessions[user]; loaded { + newUserUDPSessions[user] = counter + } + } + s.userUplink = newUserUplink + s.userDownlink = newUserDownlink + s.userUplinkPackets = newUserUplinkPackets + s.userDownlinkPackets = newUserDownlinkPackets + s.userTCPSessions = newUserTCPSessions + s.userUDPSessions = newUserUDPSessions +} + +func (s *TrafficManager) userCounter(user string) (*atomic.Int64, *atomic.Int64, *atomic.Int64, *atomic.Int64, *atomic.Int64, *atomic.Int64) { + s.userAccess.Lock() + defer s.userAccess.Unlock() + upCounter, loaded := s.userUplink[user] + if !loaded { + upCounter = new(atomic.Int64) + s.userUplink[user] = upCounter + } + downCounter, loaded := s.userDownlink[user] + if !loaded { + downCounter = new(atomic.Int64) + s.userDownlink[user] = downCounter + } + upPacketsCounter, loaded := s.userUplinkPackets[user] + if !loaded { + upPacketsCounter = new(atomic.Int64) + s.userUplinkPackets[user] = upPacketsCounter + } + downPacketsCounter, loaded := s.userDownlinkPackets[user] + if !loaded { + downPacketsCounter = new(atomic.Int64) + s.userDownlinkPackets[user] = downPacketsCounter + } + tcpSessionsCounter, loaded := s.userTCPSessions[user] + if !loaded { + tcpSessionsCounter = new(atomic.Int64) + s.userTCPSessions[user] = tcpSessionsCounter + } + udpSessionsCounter, loaded := s.userUDPSessions[user] + if !loaded { + udpSessionsCounter = new(atomic.Int64) + s.userUDPSessions[user] = udpSessionsCounter + } + return upCounter, downCounter, upPacketsCounter, downPacketsCounter, tcpSessionsCounter, udpSessionsCounter +} + +func (s *TrafficManager) TrackConnection(conn net.Conn, metadata adapter.InboundContext) net.Conn { + s.globalTCPSessions.Add(1) + var readCounter []*atomic.Int64 + var writeCounter []*atomic.Int64 + readCounter = append(readCounter, &s.globalUplink) + writeCounter = append(writeCounter, &s.globalDownlink) + upCounter, downCounter, _, _, tcpSessionCounter, _ := s.userCounter(metadata.User) + readCounter = append(readCounter, upCounter) + writeCounter = append(writeCounter, downCounter) + tcpSessionCounter.Add(1) + return bufio.NewInt64CounterConn(conn, readCounter, writeCounter) +} + +func (s *TrafficManager) TrackPacketConnection(conn N.PacketConn, metadata adapter.InboundContext) N.PacketConn { + s.globalUDPSessions.Add(1) + var readCounter []*atomic.Int64 + var readPacketCounter []*atomic.Int64 + var writeCounter []*atomic.Int64 + var writePacketCounter []*atomic.Int64 + readCounter = append(readCounter, &s.globalUplink) + writeCounter = append(writeCounter, &s.globalDownlink) + readPacketCounter = append(readPacketCounter, &s.globalUplinkPackets) + writePacketCounter = append(writePacketCounter, &s.globalDownlinkPackets) + upCounter, downCounter, upPacketsCounter, downPacketsCounter, _, udpSessionCounter := s.userCounter(metadata.User) + readCounter = append(readCounter, upCounter) + writeCounter = append(writeCounter, downCounter) + readPacketCounter = append(readPacketCounter, upPacketsCounter) + writePacketCounter = append(writePacketCounter, downPacketsCounter) + udpSessionCounter.Add(1) + return bufio.NewInt64CounterPacketConn(conn, readCounter, readPacketCounter, writeCounter, writePacketCounter) +} + +func (s *TrafficManager) ReadUser(user *UserObject) { + s.userAccess.Lock() + defer s.userAccess.Unlock() + s.readUser(user, false) +} + +func (s *TrafficManager) readUser(user *UserObject, swap bool) { + if counter, loaded := s.userUplink[user.UserName]; loaded { + if swap { + user.UplinkBytes = counter.Swap(0) + } else { + user.UplinkBytes = counter.Load() + } + } + if counter, loaded := s.userDownlink[user.UserName]; loaded { + if swap { + user.DownlinkBytes = counter.Swap(0) + } else { + user.DownlinkBytes = counter.Load() + } + } + if counter, loaded := s.userUplinkPackets[user.UserName]; loaded { + if swap { + user.UplinkPackets = counter.Swap(0) + } else { + user.UplinkPackets = counter.Load() + } + } + if counter, loaded := s.userDownlinkPackets[user.UserName]; loaded { + if swap { + user.DownlinkPackets = counter.Swap(0) + } else { + user.DownlinkPackets = counter.Load() + } + } + if counter, loaded := s.userTCPSessions[user.UserName]; loaded { + if swap { + user.TCPSessions = counter.Swap(0) + } else { + user.TCPSessions = counter.Load() + } + } + if counter, loaded := s.userUDPSessions[user.UserName]; loaded { + if swap { + user.UDPSessions = counter.Swap(0) + } else { + user.UDPSessions = counter.Load() + } + } +} + +func (s *TrafficManager) ReadUsers(users []*UserObject, swap bool) { + s.userAccess.Lock() + defer s.userAccess.Unlock() + for _, user := range users { + s.readUser(user, swap) + } +} + +func (s *TrafficManager) ReadGlobal(swap bool) (uplinkBytes int64, downlinkBytes int64, uplinkPackets int64, downlinkPackets int64, tcpSessions int64, udpSessions int64) { + if swap { + return s.globalUplink.Swap(0), + s.globalDownlink.Swap(0), + s.globalUplinkPackets.Swap(0), + s.globalDownlinkPackets.Swap(0), + s.globalTCPSessions.Swap(0), + s.globalUDPSessions.Swap(0) + } else { + return s.globalUplink.Load(), + s.globalDownlink.Load(), + s.globalUplinkPackets.Load(), + s.globalDownlinkPackets.Load(), + s.globalTCPSessions.Load(), + s.globalUDPSessions.Load() + } +} diff --git a/service/ssmapi/user.go b/service/ssmapi/user.go new file mode 100644 index 0000000000..26bc621a20 --- /dev/null +++ b/service/ssmapi/user.go @@ -0,0 +1,87 @@ +package ssmapi + +import ( + "sync" + + "github.com/sagernet/sing-box/adapter" + E "github.com/sagernet/sing/common/exceptions" +) + +type UserManager struct { + access sync.Mutex + usersMap map[string]string + server adapter.ManagedSSMServer + trafficManager *TrafficManager +} + +func NewUserManager(inbound adapter.ManagedSSMServer, trafficManager *TrafficManager) *UserManager { + return &UserManager{ + usersMap: make(map[string]string), + server: inbound, + trafficManager: trafficManager, + } +} + +func (m *UserManager) postUpdate(updated bool) error { + users := make([]string, 0, len(m.usersMap)) + uPSKs := make([]string, 0, len(m.usersMap)) + for username, password := range m.usersMap { + users = append(users, username) + uPSKs = append(uPSKs, password) + } + err := m.server.UpdateUsers(users, uPSKs) + if err != nil { + return err + } + if updated { + m.trafficManager.UpdateUsers(users) + } + return nil +} + +func (m *UserManager) List() []*UserObject { + m.access.Lock() + defer m.access.Unlock() + + users := make([]*UserObject, 0, len(m.usersMap)) + for username, password := range m.usersMap { + users = append(users, &UserObject{ + UserName: username, + Password: password, + }) + } + return users +} + +func (m *UserManager) Add(username string, password string) error { + m.access.Lock() + defer m.access.Unlock() + if _, found := m.usersMap[username]; found { + return E.New("user ", username, " already exists") + } + m.usersMap[username] = password + return m.postUpdate(true) +} + +func (m *UserManager) Get(username string) (string, bool) { + m.access.Lock() + defer m.access.Unlock() + if password, found := m.usersMap[username]; found { + return password, true + } + return "", false +} + +func (m *UserManager) Update(username string, password string) error { + m.access.Lock() + defer m.access.Unlock() + m.usersMap[username] = password + return m.postUpdate(true) +} + +func (m *UserManager) Delete(username string) error { + m.access.Lock() + defer m.access.Unlock() + delete(m.usersMap, username) + return m.postUpdate(true) +} diff --git a/test/box_test.go b/test/box_test.go index 621c00dafb..d7d9b9b047 100644 --- a/test/box_test.go +++ b/test/box_test.go @@ -13,6 +13,7 @@ import ( "github.com/sagernet/quic-go/http3" "github.com/sagernet/sing-box" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/include" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/debug" @@ -28,6 +29,12 @@ func TestMain(m *testing.M) { goleak.VerifyTestMain(m) } +var globalCtx context.Context + +func init() { + globalCtx = include.Context(context.Background()) +} + func startInstance(t *testing.T, options option.Options) *box.Box { if debug.Enabled { options.Log = &option.LogOptions{ @@ -38,8 +45,7 @@ func startInstance(t *testing.T, options option.Options) *box.Box { Level: "warning", } } - // ctx := context.Background() - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(globalCtx) var instance *box.Box var err error for retry := 0; retry < 3; retry++ { @@ -82,8 +88,8 @@ func testSuit(t *testing.T, clientPort uint16, testPort uint16) { func testQUIC(t *testing.T, clientPort uint16) { dialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort("127.0.0.1", clientPort), socks.Version5, "", "") client := &http.Client{ - Transport: &http3.RoundTripper{ - Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { + Transport: &http3.Transport{ + Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) { destination := M.ParseSocksaddr(addr) udpConn, err := dialer.DialContext(ctx, N.NetworkUDP, destination) if err != nil { diff --git a/test/brutal_test.go b/test/brutal_test.go index bfe4d1fc02..d0841467b0 100644 --- a/test/brutal_test.go +++ b/test/brutal_test.go @@ -7,6 +7,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks/shadowaead_2022" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/gofrs/uuid/v5" ) @@ -19,18 +21,18 @@ func TestBrutalShadowsocks(t *testing.T) { { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksInboundOptions{ + Options: &option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Method: method, @@ -53,7 +55,7 @@ func TestBrutalShadowsocks(t *testing.T) { { Type: C.TypeShadowsocks, Tag: "ss-out", - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -76,9 +78,18 @@ func TestBrutalShadowsocks(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, @@ -95,18 +106,18 @@ func TestBrutalTrojan(t *testing.T) { { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeTrojan, - TrojanOptions: option.TrojanInboundOptions{ + Options: &option.TrojanInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TrojanUser{{Password: password}}, @@ -136,7 +147,7 @@ func TestBrutalTrojan(t *testing.T) { { Type: C.TypeTrojan, Tag: "ss-out", - TrojanOptions: option.TrojanOutboundOptions{ + Options: &option.TrojanOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -165,9 +176,18 @@ func TestBrutalTrojan(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, @@ -183,18 +203,18 @@ func TestBrutalVMess(t *testing.T) { { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeVMess, - VMessOptions: option.VMessInboundOptions{ + Options: &option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{{UUID: user.String()}}, @@ -216,7 +236,7 @@ func TestBrutalVMess(t *testing.T) { { Type: C.TypeVMess, Tag: "ss-out", - VMessOptions: option.VMessOutboundOptions{ + Options: &option.VMessOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -238,9 +258,18 @@ func TestBrutalVMess(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, @@ -256,18 +285,18 @@ func TestBrutalVLESS(t *testing.T) { { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeVLESS, - VLESSOptions: option.VLESSInboundOptions{ + Options: &option.VLESSInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VLESSUser{{UUID: user.String()}}, @@ -306,7 +335,7 @@ func TestBrutalVLESS(t *testing.T) { { Type: C.TypeVLESS, Tag: "ss-out", - VLESSOptions: option.VLESSOutboundOptions{ + Options: &option.VLESSOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -342,9 +371,18 @@ func TestBrutalVLESS(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, diff --git a/test/clash_darwin_test.go b/test/clash_darwin_test.go index d4eff13e88..013d8b3f1a 100644 --- a/test/clash_darwin_test.go +++ b/test/clash_darwin_test.go @@ -26,7 +26,7 @@ func defaultRouteIP() (netip.Addr, error) { for _, addr := range addrs { ip := addr.(*net.IPNet).IP if ip.To4() != nil { - return netip.AddrFrom4(*(*[4]byte)(ip)), nil + return netip.AddrFrom4([4]byte(ip)), nil } } diff --git a/test/clash_test.go b/test/clash_test.go index ffd3e10c78..bba7f3bebe 100644 --- a/test/clash_test.go +++ b/test/clash_test.go @@ -17,7 +17,7 @@ import ( "github.com/sagernet/sing/common/control" F "github.com/sagernet/sing/common/format" - "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/image" "github.com/docker/docker/client" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -68,7 +68,7 @@ func init() { } defer dockerClient.Close() - list, err := dockerClient.ImageList(context.Background(), types.ImageListOptions{All: true}) + list, err := dockerClient.ImageList(context.Background(), image.ListOptions{All: true}) if err != nil { log.Warn(err) return @@ -85,13 +85,13 @@ func init() { return false } - for _, image := range allImages { - if imageExist(image) { + for _, i := range allImages { + if imageExist(i) { continue } - log.Info("pulling image: ", image) - imageStream, err := dockerClient.ImagePull(context.Background(), image, types.ImagePullOptions{}) + log.Info("pulling image: ", i) + imageStream, err := dockerClient.ImagePull(context.Background(), i, image.PullOptions{}) if err != nil { panic(err) } diff --git a/test/direct_test.go b/test/direct_test.go index ec3cf88c07..d33defeebd 100644 --- a/test/direct_test.go +++ b/test/direct_test.go @@ -6,6 +6,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" ) // Since this is a feature one-off added by outsiders, I won't address these anymore. @@ -15,18 +17,18 @@ func _TestProxyProtocol(t *testing.T) { { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeDirect, - DirectOptions: option.DirectInboundOptions{ + Options: &option.DirectInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, ProxyProtocol: true, }, @@ -40,7 +42,7 @@ func _TestProxyProtocol(t *testing.T) { { Type: C.TypeDirect, Tag: "proxy-out", - DirectOptions: option.DirectOutboundOptions{ + Options: &option.DirectOutboundOptions{ OverrideAddress: "127.0.0.1", OverridePort: serverPort, ProxyProtocol: 2, @@ -50,9 +52,18 @@ func _TestProxyProtocol(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "proxy-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "proxy-out", + }, + }, }, }, }, diff --git a/test/docker_test.go b/test/docker_test.go index ade813d75c..a85dd12cd9 100644 --- a/test/docker_test.go +++ b/test/docker_test.go @@ -11,7 +11,6 @@ import ( F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/rw" - "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/client" "github.com/docker/docker/pkg/stdcopy" @@ -85,10 +84,10 @@ func startDockerContainer(t *testing.T, options DockerOptions) { cleanContainer(dockerContainer.ID) }) - require.NoError(t, dockerClient.ContainerStart(context.Background(), dockerContainer.ID, types.ContainerStartOptions{})) + require.NoError(t, dockerClient.ContainerStart(context.Background(), dockerContainer.ID, container.StartOptions{})) if writeStdin { - stdinAttach, err := dockerClient.ContainerAttach(context.Background(), dockerContainer.ID, types.ContainerAttachOptions{ + stdinAttach, err := dockerClient.ContainerAttach(context.Background(), dockerContainer.ID, container.AttachOptions{ Stdin: writeStdin, Stream: true, }) @@ -98,7 +97,7 @@ func startDockerContainer(t *testing.T, options DockerOptions) { stdinAttach.Close() } if debug.Enabled { - attach, err := dockerClient.ContainerAttach(context.Background(), dockerContainer.ID, types.ContainerAttachOptions{ + attach, err := dockerClient.ContainerAttach(context.Background(), dockerContainer.ID, container.AttachOptions{ Stdout: true, Stderr: true, Logs: true, @@ -118,5 +117,5 @@ func cleanContainer(id string) error { return err } defer dockerClient.Close() - return dockerClient.ContainerRemove(context.Background(), id, types.ContainerRemoveOptions{Force: true}) + return dockerClient.ContainerRemove(context.Background(), id, container.RemoveOptions{Force: true}) } diff --git a/test/domain_inbound_test.go b/test/domain_inbound_test.go index f22fe24982..0235456438 100644 --- a/test/domain_inbound_test.go +++ b/test/domain_inbound_test.go @@ -6,7 +6,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" - dns "github.com/sagernet/sing-dns" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/gofrs/uuid/v5" ) @@ -18,22 +19,19 @@ func TestTUICDomainUDP(t *testing.T) { { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeTUIC, - TUICOptions: option.TUICInboundOptions{ + Options: &option.TUICInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, - InboundOptions: option.InboundOptions{ - DomainStrategy: option.DomainStrategy(dns.DomainStrategyUseIPv6), - }, }, Users: []option.TUICUser{{ UUID: uuid.Nil.String(), @@ -56,7 +54,7 @@ func TestTUICDomainUDP(t *testing.T) { { Type: C.TypeTUIC, Tag: "tuic-out", - TUICOptions: option.TUICOutboundOptions{ + Options: &option.TUICOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -75,9 +73,18 @@ func TestTUICDomainUDP(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "tuic-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "tuic-out", + }, + }, }, }, }, diff --git a/test/ech_test.go b/test/ech_test.go index 35d5d89158..91d13d3a26 100644 --- a/test/ech_test.go +++ b/test/ech_test.go @@ -8,30 +8,31 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/gofrs/uuid/v5" ) func TestECH(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") - echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false)) + echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org")) startInstance(t, option.Options{ Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeTrojan, - TrojanOptions: option.TrojanInboundOptions{ + Options: &option.TrojanInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TrojanUser{ @@ -62,7 +63,7 @@ func TestECH(t *testing.T) { { Type: C.TypeTrojan, Tag: "trojan-out", - TrojanOptions: option.TrojanOutboundOptions{ + Options: &option.TrojanOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -85,9 +86,18 @@ func TestECH(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "trojan-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "trojan-out", + }, + }, }, }, }, @@ -98,24 +108,24 @@ func TestECH(t *testing.T) { func TestECHQUIC(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") - echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false)) + echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org")) startInstance(t, option.Options{ Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeTUIC, - TUICOptions: option.TUICInboundOptions{ + Options: &option.TUICInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TUICUser{{ @@ -143,7 +153,7 @@ func TestECHQUIC(t *testing.T) { { Type: C.TypeTUIC, Tag: "tuic-out", - TUICOptions: option.TUICOutboundOptions{ + Options: &option.TUICOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -166,9 +176,18 @@ func TestECHQUIC(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "tuic-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "tuic-out", + }, + }, }, }, }, @@ -179,24 +198,24 @@ func TestECHQUIC(t *testing.T) { func TestECHHysteria2(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") - echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false)) + echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org")) startInstance(t, option.Options{ Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeHysteria2, - Hysteria2Options: option.Hysteria2InboundOptions{ + Options: &option.Hysteria2InboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.Hysteria2User{{ @@ -224,7 +243,7 @@ func TestECHHysteria2(t *testing.T) { { Type: C.TypeHysteria2, Tag: "hy2-out", - Hysteria2Options: option.Hysteria2OutboundOptions{ + Options: &option.Hysteria2OutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -249,8 +268,16 @@ func TestECHHysteria2(t *testing.T) { { Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "hy2-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "hy2-out", + }, + }, }, }, }, diff --git a/test/go.mod b/test/go.mod index 6caf6240d4..41c73441ac 100644 --- a/test/go.mod +++ b/test/go.mod @@ -1,99 +1,281 @@ module test -go 1.20 +go 1.24.7 require github.com/sagernet/sing-box v0.0.0 replace github.com/sagernet/sing-box => ../ +replace github.com/sagernet/sing-dns => github.com/shtorm-7/sing-dns v0.4.6-extended-1.0.0 + +replace github.com/ameshkov/dnscrypt/v2 => github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0 + +replace github.com/sagernet/wireguard-go => ../replace/wireguard-go + +replace github.com/sagernet/tailscale => ../replace/tailscale + +replace github.com/Psiphon-Labs/quic-go => ../replace/psiphon-quic-go + +replace github.com/Psiphon-Labs/psiphon-tls => ../replace/psiphon-tls + +replace github.com/net2share/vaydns => github.com/hiddify/vaydns v0.0.0-20260401180616-890dc987a6a9 + require ( - github.com/docker/docker v24.0.7+incompatible - github.com/docker/go-connections v0.4.0 - github.com/gofrs/uuid/v5 v5.2.0 - github.com/sagernet/quic-go v0.45.1-beta.2 - github.com/sagernet/sing v0.4.2 - github.com/sagernet/sing-dns v0.2.3 - github.com/sagernet/sing-quic v0.2.0-beta.12 - github.com/sagernet/sing-shadowsocks v0.2.7 - github.com/sagernet/sing-shadowsocks2 v0.2.0 - github.com/spyzhov/ajson v0.9.0 - github.com/stretchr/testify v1.9.0 + github.com/docker/docker v27.3.1+incompatible + github.com/docker/go-connections v0.5.0 + github.com/gofrs/uuid/v5 v5.4.0 + github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 + github.com/sagernet/sing v0.8.7-0.20260417135631-0d22698ed386 + github.com/sagernet/sing-quic v0.6.2-0.20260412143638-8f65b6be7cd6 + github.com/sagernet/sing-shadowsocks v0.2.8 + github.com/sagernet/sing-shadowsocks2 v0.2.1 + github.com/spyzhov/ajson v0.9.4 + github.com/stretchr/testify v1.11.1 go.uber.org/goleak v1.3.0 - golang.org/x/net v0.25.0 + golang.org/x/net v0.50.0 ) require ( - berty.tech/go-libtor v1.0.385 // indirect + filippo.io/bigmod v0.0.1 // indirect + filippo.io/edwards25519 v1.1.0 // indirect + filippo.io/keygen v0.0.0-20230306160926-5201437acf8e // indirect + github.com/AdguardTeam/golibs v0.32.7 // indirect + github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect + github.com/Jigsaw-Code/outline-sdk v0.0.16 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/andybalholm/brotli v1.0.6 // indirect - github.com/caddyserver/certmagic v0.20.0 // indirect - github.com/cloudflare/circl v1.3.7 // indirect + github.com/Psiphon-Labs/bolt v0.0.0-20200624191537-23cedaef7ad7 // indirect + github.com/Psiphon-Labs/consistent v0.0.0-20240322131436-20aaa4e05737 // indirect + github.com/Psiphon-Labs/goptlib v0.0.0-20200406165125-c0e32a7a3464 // indirect + github.com/Psiphon-Labs/psiphon-tls v0.0.0-20250318183125-2a2fae2db378 // indirect + github.com/Psiphon-Labs/psiphon-tunnel-core v1.0.11-0.20260126173038-c86a1497a760 // indirect + github.com/Psiphon-Labs/quic-go v0.0.0-20250527153145-79fe45fb83b1 // indirect + github.com/Psiphon-Labs/utls v0.0.0-20260129182755-24497d415a8d // indirect + github.com/ajg/form v1.5.1 // indirect + github.com/akutz/memconn v0.1.0 // indirect + github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect + github.com/ameshkov/dnscrypt/v2 v2.4.0 // indirect + github.com/ameshkov/dnsstamps v1.0.3 // indirect + github.com/amnezia-vpn/amneziawg-go v0.2.16 // indirect + github.com/andybalholm/brotli v1.2.0 // indirect + github.com/anthropics/anthropic-sdk-go v1.26.0 // indirect + github.com/anytls/sing-anytls v0.0.11 // indirect + github.com/armon/go-proxyproto v0.0.0-20180202201750-5b7edb60ff5f // indirect + github.com/axiomhq/hyperloglog v0.2.6 // indirect + github.com/bifurcation/mint v0.0.0-20180306135233-198357931e61 // indirect + github.com/biter777/countries v1.7.5 // indirect + github.com/bits-and-blooms/bitset v1.10.0 // indirect + github.com/bits-and-blooms/bloom/v3 v3.6.0 // indirect + github.com/caddyserver/certmagic v0.25.2 // indirect + github.com/caddyserver/zerossl v0.1.5 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9 // indirect + github.com/cloudflare/circl v1.6.1 // indirect + github.com/coder/websocket v1.8.14 // indirect + github.com/cognusion/go-cache-lru v0.0.0-20170419142635-f73e2280ecea // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect + github.com/coreos/go-oidc/v3 v3.17.0 // indirect github.com/cretz/bine v0.2.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/database64128/netx-go v0.1.1 // indirect + github.com/database64128/tfo-go/v2 v2.3.2 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect + github.com/dchest/siphash v1.2.3 // indirect + github.com/dgraph-io/badger v1.5.4-0.20180815194500-3a87f6d9c273 // indirect + github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da // indirect + github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect github.com/distribution/reference v0.5.0 // indirect - github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/ebitengine/purego v0.9.1 // indirect + github.com/enfein/mieru/v3 v3.27.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/florianl/go-nfqueue/v2 v2.0.2 // indirect + github.com/flynn/noise v1.1.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/gaukas/godicttls v0.0.4 // indirect - github.com/go-chi/chi/v5 v5.0.12 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/gaissmai/bart v0.18.0 // indirect + github.com/go-chi/chi/v5 v5.2.5 // indirect + github.com/go-chi/render v1.0.3 // indirect + github.com/go-jose/go-jose/v4 v4.1.3 // indirect + github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect + github.com/godbus/dbus/v5 v5.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/btree v1.1.2 // indirect - github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect - github.com/hashicorp/yamux v0.1.1 // indirect - github.com/klauspost/compress v1.17.4 // indirect - github.com/klauspost/cpuid/v2 v2.2.5 // indirect - github.com/libdns/alidns v1.0.3 // indirect - github.com/libdns/cloudflare v0.1.1 // indirect - github.com/libdns/libdns v0.2.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grafov/m3u8 v0.0.0-20171211212457-6ab8f28ed427 // indirect + github.com/hashicorp/yamux v0.1.2 // indirect + github.com/hdevalence/ed25519consensus v0.2.0 // indirect + github.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91 // indirect + github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect + github.com/jsimonetti/rtnetlink v1.4.0 // indirect + github.com/kamstrup/intmap v0.5.2 // indirect + github.com/keybase/go-keychain v0.0.1 // indirect + github.com/klauspost/compress v1.18.3 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/klauspost/reedsolomon v1.13.0 // indirect + github.com/libdns/acmedns v0.5.0 // indirect + github.com/libdns/alidns v1.0.6 // indirect + github.com/libdns/cloudflare v0.2.2 // indirect + github.com/libdns/libdns v1.1.1 // indirect + github.com/libp2p/go-reuseport v0.4.0 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect - github.com/mholt/acmez v1.2.0 // indirect - github.com/miekg/dns v1.1.59 // indirect + github.com/marusama/semaphore v0.0.0-20171214154724-565ffd8e868a // indirect + github.com/mdlayher/netlink v1.9.0 // indirect + github.com/mdlayher/socket v0.5.1 // indirect + github.com/metacubex/utls v1.8.4 // indirect + github.com/mholt/acmez/v3 v3.1.6 // indirect + github.com/miekg/dns v1.1.72 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect - github.com/onsi/ginkgo/v2 v2.9.7 // indirect - github.com/ooni/go-libtor v1.1.8 // indirect + github.com/mroth/weightedrand v1.0.0 // indirect + github.com/net2share/vaydns v0.2.6 // indirect + github.com/openai/openai-go/v3 v3.26.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.0.2 // indirect - github.com/oschwald/maxminddb-golang v1.12.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pierrec/lz4/v4 v4.1.21 // indirect + github.com/pion/datachannel v1.5.5 // indirect + github.com/pion/dtls/v2 v2.2.7 // indirect + github.com/pion/ice/v2 v2.3.24 // indirect + github.com/pion/interceptor v0.1.25 // indirect + github.com/pion/logging v0.2.2 // indirect + github.com/pion/mdns v0.0.12 // indirect + github.com/pion/randutil v0.1.0 // indirect + github.com/pion/rtcp v1.2.12 // indirect + github.com/pion/rtp v1.8.5 // indirect + github.com/pion/sctp v1.8.16 // indirect + github.com/pion/sdp/v3 v3.0.9 // indirect + github.com/pion/srtp/v2 v2.0.18 // indirect + github.com/pion/stun v0.6.1 // indirect + github.com/pion/transport/v2 v2.2.4 // indirect + github.com/pion/turn/v2 v2.1.3 // indirect + github.com/pion/webrtc/v3 v3.2.40 // indirect + github.com/pires/go-proxyproto v0.8.1 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-20 v0.4.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus-community/pro-bing v0.4.0 // indirect + github.com/quic-go/qpack v0.6.0 // indirect + github.com/refraction-networking/conjure v0.7.11-0.20240130155008-c8df96195ab2 // indirect + github.com/refraction-networking/ed25519 v0.1.2 // indirect + github.com/refraction-networking/gotapdance v1.7.10 // indirect + github.com/refraction-networking/obfs4 v0.1.2 // indirect + github.com/refraction-networking/utls v1.8.2 // indirect + github.com/safchain/ethtool v0.3.0 // indirect github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect - github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 // indirect - github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f // indirect - github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba // indirect - github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect - github.com/sagernet/sing-mux v0.2.0 // indirect - github.com/sagernet/sing-shadowtls v0.1.4 // indirect - github.com/sagernet/sing-tun v0.3.2 // indirect - github.com/sagernet/sing-vmess v0.1.12 // indirect - github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect - github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 // indirect - github.com/sagernet/utls v1.5.4 // indirect - github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 // indirect + github.com/sagernet/cors v1.2.1 // indirect + github.com/sagernet/cronet-go v0.0.0-20260413093659-e4926ba205fa // indirect + github.com/sagernet/cronet-go/all v0.0.0-20260413093659-e4926ba205fa // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260413092954-cd09eb3e271b // indirect + github.com/sagernet/fswatch v0.1.1 // indirect + github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 // indirect + github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect + github.com/sagernet/nftables v0.3.0-beta.4 // indirect + github.com/sagernet/sing-cloudflared v0.0.0-20260416083718-efa6ab16dba9 // indirect + github.com/sagernet/sing-mux v0.3.4 // indirect + github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 // indirect + github.com/sagernet/sing-tun v0.8.8-0.20260410061515-018f5eaae695 // indirect + github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 // indirect + github.com/sagernet/smux v1.5.50-sing-box-mod.1 // indirect + github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7 // indirect + github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c // indirect github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect - github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect - github.com/zeebo/blake3 v0.2.3 // indirect + github.com/sergeyfrolov/bsbuffer v0.0.0-20180903213811-94e85abb8507 // indirect + github.com/shadowsocks/go-shadowsocks2 v0.1.5 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect + github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect + github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect + github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect + github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect + github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect + github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect + github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect + github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect + github.com/vishvananda/netns v0.0.5 // indirect + github.com/wader/filtertransport v0.0.0-20200316221534-bdd9e61eee78 // indirect + github.com/wlynxg/anet v0.0.5 // indirect + github.com/x448/float16 v0.8.4 // indirect + github.com/xtaci/kcp-go/v5 v5.6.70 // indirect + github.com/xtaci/smux v1.5.50 // indirect + github.com/zeebo/blake3 v0.2.4 // indirect + gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib v1.5.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect + go.uber.org/zap v1.27.1 // indirect + go.uber.org/zap/exp v0.3.0 // indirect + go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect - golang.org/x/crypto v0.23.0 // indirect - golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect - golang.org/x/mod v0.18.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.21.0 // indirect - golang.org/x/text v0.16.0 // indirect - golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/grpc v1.63.2 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/term v0.40.0 // indirect + golang.org/x/text v0.34.0 // indirect + golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.42.0 // indirect + golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect + golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect + golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 // indirect + golang.zx2c4.com/wireguard/windows v0.5.3 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/grpc v1.79.1 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect - lukechampine.com/blake3 v1.3.0 // indirect + gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 // indirect + lukechampine.com/blake3 v1.4.1 // indirect + tailscale.com v1.58.2 // indirect + zombiezen.com/go/capnproto2 v2.18.2+incompatible // indirect ) diff --git a/test/go.sum b/test/go.sum index f482438e37..097fc845e7 100644 --- a/test/go.sum +++ b/test/go.sum @@ -1,239 +1,825 @@ -berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw= -berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE= +code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM= +filippo.io/bigmod v0.0.1 h1:OaEqDr3gEbofpnHbGqZweSL/bLMhy1pb54puiCDeuOA= +filippo.io/bigmod v0.0.1/go.mod h1:KyzqAbH7bRH6MOuOF1TPfUjvLoi0mRF2bIyD2ouRNQI= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +filippo.io/keygen v0.0.0-20230306160926-5201437acf8e h1:+xwUCyMiCWKWsI0RowhzB4sngpUdMHgU6lLuWJCX5Dg= +filippo.io/keygen v0.0.0-20230306160926-5201437acf8e/go.mod h1:ZGSiF/b2hd6MRghF/cid0vXw8pXykRTmIu+JSPw/NCQ= +github.com/AdguardTeam/golibs v0.32.7 h1:3dmGlAVgmvquCCwHsvEl58KKcRAK3z1UnjMnwSIeDH4= +github.com/AdguardTeam/golibs v0.32.7/go.mod h1:bE8KV1zqTzgZjmjFyBJ9f9O5DEKO717r7e57j1HclJA= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/Jigsaw-Code/outline-sdk v0.0.16 h1:WbHmv80FKDIpzEmR3GehTbq5CibYTLvcxIIpMMILiEs= +github.com/Jigsaw-Code/outline-sdk v0.0.16/go.mod h1:e1oQZbSdLJBBuHgfeQsgEkvkuyIePPwstUeZRGq0KO8= +github.com/Jigsaw-Code/outline-ss-server v1.8.0 h1:6h7CZsyl1vQLz3nvxmL9FbhDug4QxJ1YTxm534eye1E= +github.com/Jigsaw-Code/outline-ss-server v1.8.0/go.mod h1:slnHH3OZsQmZx/DRKhxvvaGE/8+n3Lkd6363h1ev71E= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= -github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= -github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg= -github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= -github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= -github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e h1:NPfqIbzmijrl0VclX2t8eO5EPBhqe47LLGKpRrcVjXk= +github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e/go.mod h1:ZdY5pBfat/WVzw3eXbIf7N1nZN0XD5H5+X8ZMDWbCs4= +github.com/Psiphon-Inc/uds-ipc v1.0.1 h1:K3Z0cS1XfzDdhxWTIwh/hiLrkRR83ZxUo2bqgBOGuZE= +github.com/Psiphon-Inc/uds-ipc v1.0.1/go.mod h1:R8TGG+OXumorJdpAcSFE4SVTT2frMFEGY43Zc/44sNk= +github.com/Psiphon-Labs/bolt v0.0.0-20200624191537-23cedaef7ad7 h1:Hx/NCZTnvoKZuIBwSmxE58KKoNLXIGG6hBJYN7pj9Ag= +github.com/Psiphon-Labs/bolt v0.0.0-20200624191537-23cedaef7ad7/go.mod h1:alTtZBo3j4AWFvUrAH6F5ZaHcTj4G5Y01nHz8dkU6vU= +github.com/Psiphon-Labs/consistent v0.0.0-20240322131436-20aaa4e05737 h1:QTMy7Uc2Xc7fz6O/Khy1xi0VBND13GqzLUE2mHw6HUU= +github.com/Psiphon-Labs/consistent v0.0.0-20240322131436-20aaa4e05737/go.mod h1:Enj/Gszv2zCbuRbHbabmNvfO9EM+5kmaGj8CyjwNPlY= +github.com/Psiphon-Labs/goptlib v0.0.0-20200406165125-c0e32a7a3464 h1:VmnMMMheFXwLV0noxYhbJbLmkV4iaVW3xNnj6xcCNHo= +github.com/Psiphon-Labs/goptlib v0.0.0-20200406165125-c0e32a7a3464/go.mod h1:Pe5BqN2DdIdChorAXl6bDaQd/wghpCleJfid2NoSli0= +github.com/Psiphon-Labs/psiphon-tunnel-core v1.0.11-0.20260126173038-c86a1497a760 h1:R4O6UNd88eb1s/txuBTg99U/BZrDOGQgJ4pSL2MRBEs= +github.com/Psiphon-Labs/psiphon-tunnel-core v1.0.11-0.20260126173038-c86a1497a760/go.mod h1:FcBjozoGjC2I7ZID0lXFlVQcy/jx6irCzlPxapGebEY= +github.com/Psiphon-Labs/utls v0.0.0-20260129182755-24497d415a8d h1:PlKwrArEuQOVqEmThSs9KsXMiBduP8MSu9rlWmQ4jgE= +github.com/Psiphon-Labs/utls v0.0.0-20260129182755-24497d415a8d/go.mod h1:1vv0gVAzq9e2XYkW8HAKrmtuuZrBdDixQFx5H22KAjI= +github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A= +github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw= +github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= +github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= +github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo= +github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A= +github.com/amnezia-vpn/amneziawg-go v0.2.16 h1:XY6HOq/xtqH8ZXMncRWkjFs85EKdN10NLNnw23kTpE0= +github.com/amnezia-vpn/amneziawg-go v0.2.16/go.mod h1:nRkPpIzjCxMW8pZKXTRkpqAQVlmFJdVOGkeQSC7wbms= +github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= +github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/anthropics/anthropic-sdk-go v1.26.0 h1:oUTzFaUpAevfuELAP1sjL6CQJ9HHAfT7CoSYSac11PY= +github.com/anthropics/anthropic-sdk-go v1.26.0/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q= +github.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc= +github.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= +github.com/armon/go-proxyproto v0.0.0-20180202201750-5b7edb60ff5f h1:SaJ6yqg936TshyeFZqQE+N+9hYkIeL9AMr7S4voCl10= +github.com/armon/go-proxyproto v0.0.0-20180202201750-5b7edb60ff5f/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU= +github.com/axiomhq/hyperloglog v0.2.6 h1:sRhvvF3RIXWQgAXaTphLp4yJiX4S0IN3MWTaAgZoRJw= +github.com/axiomhq/hyperloglog v0.2.6/go.mod h1:YjX/dQqCR/7QYX0g8mu8UZAjpIenz1FKM71UEsjFoTo= +github.com/bifurcation/mint v0.0.0-20180306135233-198357931e61 h1:BU+NxuoaYPIvvp8NNkNlLr8aA0utGyuunf4Q3LJ0bh0= +github.com/bifurcation/mint v0.0.0-20180306135233-198357931e61/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU= +github.com/biter777/countries v1.7.5 h1:MJ+n3+rSxWQdqVJU8eBy9RqcdH6ePPn4PJHocVWUa+Q= +github.com/biter777/countries v1.7.5/go.mod h1:1HSpZ526mYqKJcpT5Ti1kcGQ0L0SrXWIaptUWjFfv2E= +github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= +github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bits-and-blooms/bloom/v3 v3.6.0 h1:dTU0OVLJSoOhz9m68FTXMFfA39nR8U/nTCs1zb26mOI= +github.com/bits-and-blooms/bloom/v3 v3.6.0/go.mod h1:VKlUSvp0lFIYqxJjzdnSsZEw4iHb1kOL2tfHTgyJBHg= +github.com/caddyserver/certmagic v0.25.2 h1:D7xcS7ggX/WEY54x0czj7ioTkmDWKIgxtIi2OcQclUc= +github.com/caddyserver/certmagic v0.25.2/go.mod h1:llW/CvsNmza8S6hmsuggsZeiX+uS27dkqY27wDIuBWg= +github.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE= +github.com/caddyserver/zerossl v0.1.5/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9 h1:a1zrFsLFac2xoM6zG1u72DWJwZG3ayttYLfmLbxVETk= +github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= +github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk= +github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= +github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= +github.com/cognusion/go-cache-lru v0.0.0-20170419142635-f73e2280ecea h1:9C2rdYRp8Vzwhm3sbFX0yYfB+70zKFRjn7cnPCucHSw= +github.com/cognusion/go-cache-lru v0.0.0-20170419142635-f73e2280ecea/go.mod h1:MdyNkAe06D7xmJsf+MsLvbZKYNXuOHLKJrvw+x4LlcQ= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0= +github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= +github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc= +github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8= github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= +github.com/database64128/netx-go v0.1.1 h1:dT5LG7Gs7zFZBthFBbzWE6K8wAHjSNAaK7wCYZT7NzM= +github.com/database64128/netx-go v0.1.1/go.mod h1:LNlYVipaYkQArRFDNNJ02VkNV+My9A5XR/IGS7sIBQc= +github.com/database64128/tfo-go/v2 v2.3.2 h1:UhZMKiMq3swZGUiETkLBDzQnZBPSAeBMClpJGlnJ5Fw= +github.com/database64128/tfo-go/v2 v2.3.2/go.mod h1:GC3uB5oa4beGpCUbRb2ZOWP73bJJFmMyAVgQSO7r724= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk= +github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ= +github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA= +github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc= +github.com/deckarep/golang-set v0.0.0-20171013212420-1d4478f51bed h1:njG8LmGD6JCWJu4bwIKmkOHvch70UOEIqczl5vp7Gok= +github.com/deckarep/golang-set v0.0.0-20171013212420-1d4478f51bed/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= +github.com/dgraph-io/badger v1.5.4-0.20180815194500-3a87f6d9c273 h1:45qZ7jowabqhyi3l9Ervox4dhQvLGB5BJPdC8w0a77k= +github.com/dgraph-io/badger v1.5.4-0.20180815194500-3a87f6d9c273/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 h1:ucRHb6/lvW/+mTEIGbvhcYU3S8+uSNkuMjx/qZFfhtM= +github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= -github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= -github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= +github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v0.0.0-20200809112317-0581fc3aee2d h1:rtM8HsT3NG37YPjz8sYSbUSdElP9lUsQENYzJDZDUBE= +github.com/elazarl/goproxy v0.0.0-20200809112317-0581fc3aee2d/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy/ext v0.0.0-20200809112317-0581fc3aee2d h1:st1tmvy+4duoRj+RaeeJoECWCWM015fBtf/4aR+hhqk= +github.com/elazarl/goproxy/ext v0.0.0-20200809112317-0581fc3aee2d/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= +github.com/enfein/mieru/v3 v3.27.0 h1:+E/1TF7OfimS2h582atEXQxPtJMyvqUTFBJUgzn1rxg= +github.com/enfein/mieru/v3 v3.27.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/florianl/go-nfqueue v1.1.1-0.20200829120558-a2f196e98ab0 h1:7ZJyJV4KiWBijCCzUPvVaqxsDxO36+KD0XKBdEN3I+8= +github.com/florianl/go-nfqueue v1.1.1-0.20200829120558-a2f196e98ab0/go.mod h1:2z3Tfqwv2ueuK6h563xUHRcCh1mv38wS9EjiWiesk84= +github.com/florianl/go-nfqueue/v2 v2.0.2 h1:FL5lQTeetgpCvac1TRwSfgaXUn0YSO7WzGvWNIp3JPE= +github.com/florianl/go-nfqueue/v2 v2.0.2/go.mod h1:VA09+iPOT43OMoCKNfXHyzujQUty2xmzyCRkBOlmabc= +github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= +github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= +github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= +github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= -github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= -github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= -github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/gaissmai/bart v0.18.0 h1:jQLBT/RduJu0pv/tLwXE+xKPgtWJejbxuXAR+wLJafo= +github.com/gaissmai/bart v0.18.0/go.mod h1:JJzMAhNF5Rjo4SF4jWBrANuJfqY+FvsFhW7t1UZJ+XY= +github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= +github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo= +github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= +github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= +github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= +github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced h1:Q311OHjMh/u5E2TITc++WlTP5We0xNseRMkHDyvhW7I= +github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gobwas/glob v0.2.4-0.20180402141543-f00a7392b439 h1:T6zlOdzrYuHf6HUKujm9bzkzbZ5Iv/xf6rs8BHZDpoI= +github.com/gobwas/glob v0.2.4-0.20180402141543-f00a7392b439/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gofrs/uuid/v5 v5.2.0 h1:qw1GMx6/y8vhVsx626ImfKMuS5CvJmhIKKtuyvfajMM= -github.com/gofrs/uuid/v5 v5.2.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0= +github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= -github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk= -github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= -github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= -github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= +github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI= +github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grafov/m3u8 v0.0.0-20171211212457-6ab8f28ed427 h1:xh96CCAZTX8LJPFoOVRgTwZbn2DvJl8fyCyivohhSIg= +github.com/grafov/m3u8 v0.0.0-20171211212457-6ab8f28ed427/go.mod h1:PdjzaU/pJUo4jTIn2rcgMFs+HqBGl/sPJLr8BI0Xq/I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= +github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= +github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= +github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= +github.com/hiddify/vaydns v0.0.0-20260401180616-890dc987a6a9 h1:KXnaABX8hHmkcL0jbL769hEIGI5+z/DajCrlO+Bkzcc= +github.com/hiddify/vaydns v0.0.0-20260401180616-890dc987a6a9/go.mod h1:+8kEfQsZJn7/4aIppVekrSuqhrKjGBIgnacTJkdAlS8= +github.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91 h1:u9i04mGE3iliBh0EFuWaKsmcwrLacqGmq1G3XoaM7gY= +github.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91/go.mod h1:qfvBmyDNp+/liLEYWRvqny/PEz9hGe2Dz833eXILSmo= +github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk= +github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8= +github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= +github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= +github.com/kamstrup/intmap v0.5.2 h1:qnwBm1mh4XAnW9W9Ue9tZtTff8pS6+s6iKF6JRIV2Dk= +github.com/kamstrup/intmap v0.5.2/go.mod h1:gWUVWHKzWj8xpJVFf5GC0O26bWmv3GqdnIX/LMT6Aq4= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= -github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= +github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/reedsolomon v1.13.0 h1:E0Cmgf2kMuhZTj6eefnvpKC4/Q4jhCi9YIjcZjK4arc= +github.com/klauspost/reedsolomon v1.13.0/go.mod h1:ggJT9lc71Vu+cSOPBlxGvBN6TfAS77qB4fp8vJ05NSA= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ= -github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE= -github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054= -github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU= -github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= -github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= -github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU= +github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk= +github.com/letsencrypt/pebble/v2 v2.10.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U= +github.com/letsencrypt/pebble/v2 v2.10.0/go.mod h1:Sk8cmUIPcIdv2nINo+9PB4L+ZBhzY+F9A1a/h/xmWiQ= +github.com/libdns/acmedns v0.5.0 h1:5pRtmUj4Lb/QkNJSl1xgOGBUJTWW7RjpNaIhjpDXjPE= +github.com/libdns/acmedns v0.5.0/go.mod h1:X7UAFP1Ep9NpTwWpVlrZzJLR7epynAy0wrIxSPFgKjQ= +github.com/libdns/alidns v1.0.6 h1:/Ii428ty6WHFJmE24rZxq2taq++gh7rf9jhgLfp8PmM= +github.com/libdns/alidns v1.0.6/go.mod h1:RECwyQ88e9VqQVtSrvX76o1ux3gQUKGzMgxICi+u7Ec= +github.com/libdns/cloudflare v0.2.2 h1:XWHv+C1dDcApqazlh08Q6pjytYLgR2a+Y3xrXFu0vsI= +github.com/libdns/cloudflare v0.2.2/go.mod h1:w9uTmRCDlAoafAsTPnn2nJ0XHK/eaUMh86DUk8BWi60= +github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U= +github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s= +github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= -github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= -github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= -github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs= -github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/marusama/semaphore v0.0.0-20171214154724-565ffd8e868a h1:6SRny9FLB1eWasPyDUqBQnMi9NhXU01XIlB0ao89YoI= +github.com/marusama/semaphore v0.0.0-20171214154724-565ffd8e868a/go.mod h1:TmeOqAKoDinfPfSohs14CO3VcEf7o+Bem6JiNe05yrQ= +github.com/mdlayher/netlink v1.9.0 h1:G8+GLq2x3v4D4MVIqDdNUhTUC7TKiCy/6MDkmItfKco= +github.com/mdlayher/netlink v1.9.0/go.mod h1:YBnl5BXsCoRuwBjKKlZ+aYmEoq0r12FDA/3JC+94KDg= +github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= +github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= +github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg= +github.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko= +github.com/mholt/acmez/v3 v3.1.6 h1:eGVQNObP0pBN4sxqrXeg7MYqTOWyoiYpQqITVWlrevk= +github.com/mholt/acmez/v3 v3.1.6/go.mod h1:5nTPosTGosLxF3+LU4ygbgMRFDhbAVpqMI4+a4aHLBY= +github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= +github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= -github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= -github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= -github.com/ooni/go-libtor v1.1.8 h1:Wo3V3DVTxl5vZdxtQakqYP+DAHx7pPtAFSl1bnAa08w= -github.com/ooni/go-libtor v1.1.8/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI= +github.com/mroth/weightedrand v1.0.0 h1:V8JeHChvl2MP1sAoXq4brElOcza+jxLkRuwvtQu8L3E= +github.com/mroth/weightedrand v1.0.0/go.mod h1:3p2SIcC8al1YMzGhAIoXD+r9olo/g/cdJgAD905gyNE= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI= +github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= +github.com/openai/openai-go/v3 v3.26.0 h1:bRt6H/ozMNt/dDkN4gobnLqaEGrRGBzmbVs0xxJEnQE= +github.com/openai/openai-go/v3 v3.26.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= -github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/oschwald/geoip2-golang v1.9.0 h1:uvD3O6fXAXs+usU+UGExshpdP13GAqp4GBrzN7IgKZc= +github.com/oschwald/geoip2-golang v1.9.0/go.mod h1:BHK6TvDyATVQhKNbQBdrj9eAvuwOMi2zSFXizL3K81Y= +github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= +github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pebbe/zmq4 v1.2.10 h1:wQkqRZ3CZeABIeidr3e8uQZMMH5YAykA/WN0L5zkd1c= +github.com/pebbe/zmq4 v1.2.10/go.mod h1:nqnPueOapVhE2wItZ0uOErngczsJdLOGkebMxaO8r48= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= +github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8= +github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0= +github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= +github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= +github.com/pion/ice/v2 v2.3.24 h1:RYgzhH/u5lH0XO+ABatVKCtRd+4U1GEaCXSMjNr13tI= +github.com/pion/ice/v2 v2.3.24/go.mod h1:KXJJcZK7E8WzrBEYnV4UtqEZsGeWfHxsNqhVcVvgjxw= +github.com/pion/interceptor v0.1.25 h1:pwY9r7P6ToQ3+IF0bajN0xmk/fNw/suTgaTdlwTDmhc= +github.com/pion/interceptor v0.1.25/go.mod h1:wkbPYAak5zKsfpVDYMtEfWEy8D4zL+rpxCxPImLOg3Y= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/mdns v0.0.12 h1:CiMYlY+O0azojWDmxdNr7ADGrnZ+V6Ilfner+6mSVK8= +github.com/pion/mdns v0.0.12/go.mod h1:VExJjv8to/6Wqm1FXK+Ii/Z9tsVk/F5sD/N70cnYFbk= +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= +github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I= +github.com/pion/rtcp v1.2.12 h1:bKWiX93XKgDZENEXCijvHRU/wRifm6JV5DGcH6twtSM= +github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4= +github.com/pion/rtp v1.8.2/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= +github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= +github.com/pion/rtp v1.8.5 h1:uYzINfaK+9yWs7r537z/Rc1SvT8ILjBcmDOpJcTB+OU= +github.com/pion/rtp v1.8.5/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= +github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0= +github.com/pion/sctp v1.8.16 h1:PKrMs+o9EMLRvFfXq59WFsC+V8mN1wnKzqrv+3D/gYY= +github.com/pion/sctp v1.8.16/go.mod h1:P6PbDVA++OJMrVNg2AL3XtYHV4uD6dvfyOovCgMs0PE= +github.com/pion/sdp/v3 v3.0.9 h1:pX++dCHoHUwq43kuwf3PyJfHlwIj4hXA7Vrifiq0IJY= +github.com/pion/sdp/v3 v3.0.9/go.mod h1:B5xmvENq5IXJimIO4zfp6LAe1fD9N+kFv+V/1lOdz8M= +github.com/pion/srtp/v2 v2.0.18 h1:vKpAXfawO9RtTRKZJbG4y0v1b11NZxQnxRl85kGuUlo= +github.com/pion/srtp/v2 v2.0.18/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA= +github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4= +github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8= +github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40= +github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI= +github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= +github.com/pion/transport/v2 v2.2.2/go.mod h1:OJg3ojoBJopjEeECq2yJdXH9YVrUJ1uQ++NjXLOUorc= +github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= +github.com/pion/transport/v2 v2.2.4 h1:41JJK6DZQYSeVLxILA2+F4ZkKb4Xd/tFJZRFZQ9QAlo= +github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= +github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0= +github.com/pion/transport/v3 v3.0.2 h1:r+40RJR25S9w3jbA6/5uEPTzcdn7ncyU44RWCbHkLg4= +github.com/pion/transport/v3 v3.0.2/go.mod h1:nIToODoOlb5If2jF9y2Igfx3PFYWfuXi37m0IlWa/D0= +github.com/pion/turn/v2 v2.1.3 h1:pYxTVWG2gpC97opdRc5IGsQ1lJ9O/IlNhkzj7MMrGAA= +github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY= +github.com/pion/webrtc/v3 v3.2.40 h1:Wtfi6AZMQg+624cvCXUuSmrKWepSB7zfgYDOYqsSOVU= +github.com/pion/webrtc/v3 v3.2.40/go.mod h1:M1RAe3TNTD1tzyvqHrbVODfwdPGSXOUo/OgpoGGJqFY= +github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0= +github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= -github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= -github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4= +github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/refraction-networking/conjure v0.7.11-0.20240130155008-c8df96195ab2 h1:m2ZH6WV69otVmBpWbk8et3MypHFsjcYXTNrknQKS/PY= +github.com/refraction-networking/conjure v0.7.11-0.20240130155008-c8df96195ab2/go.mod h1:7KuAtYfSL0K0WpCScjN9YKiOZ4AQ/8IzSjUtVwWbSv8= +github.com/refraction-networking/ed25519 v0.1.2 h1:08kJZUkAlY7a7cZGosl1teGytV+QEoNxPO7NnRvAB+g= +github.com/refraction-networking/ed25519 v0.1.2/go.mod h1:nxYLUAYt/hmNpAh64PNSQ/tQ9gTIB89wCaGKJlRtZ9I= +github.com/refraction-networking/gotapdance v1.7.10 h1:vPtvuihP95SqrnnpX//KI1PTqrXCvNnOQslrG4gxsRY= +github.com/refraction-networking/gotapdance v1.7.10/go.mod h1:N7Xmt+/bLv+1VctiBHtsaL6YBknW2ox5LRLzPTISMzY= +github.com/refraction-networking/obfs4 v0.1.2 h1:J842O4fGSkd2W8ogYj0KN6gqVVY+Cpqodw9qFGL7wVU= +github.com/refraction-networking/obfs4 v0.1.2/go.mod h1:wAl/+gWiLsrcykJA3nKJHx89f5/gXGM8UKvty7+mvbM= +github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo= +github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM= +github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= +github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE= +github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0= +github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= -github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY= -github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k= -github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y/6ZHJWrnNLoiNnSJaow6DPb8VW2I= -github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0= -github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba h1:EY5AS7CCtfmARNv2zXUOrsEMPFDGYxaw65JzA2p51Vk= -github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= -github.com/sagernet/quic-go v0.45.1-beta.2 h1:zkEeCbhdFFkrxKcuIRBtXNKci/1t2J/39QSG/sPvlmc= -github.com/sagernet/quic-go v0.45.1-beta.2/go.mod h1:+N3FqM9DAzOWfe64uxXuBejVJwX7DeW7BslzLO6N/xI= -github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= -github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= -github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= -github.com/sagernet/sing v0.4.2 h1:jzGNJdZVRI0xlAfFugsIQUPvyB9SuWvbJK7zQCXc4QM= -github.com/sagernet/sing v0.4.2/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls= -github.com/sagernet/sing-dns v0.2.3 h1:YzeBUn2tR38F7HtvGEQ0kLRLmZWMEgi/+7wqa4Twb1k= -github.com/sagernet/sing-dns v0.2.3/go.mod h1:BJpJv6XLnrUbSyIntOT6DG9FW0f4fETmPAHvNjOprLg= -github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo= -github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ= -github.com/sagernet/sing-quic v0.2.0-beta.12 h1:BhvA5mmrDFEyDUQB5eeu+9UhF+ieyuNJ5Rsb0dAG3QY= -github.com/sagernet/sing-quic v0.2.0-beta.12/go.mod h1:YVpLfVi8BvYM7NMrjmnvcRm3E8iMETf1gFQmTQDN9jI= -github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8= -github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE= -github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg= -github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= -github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= -github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/sing-tun v0.3.2 h1:z0bLUT/YXH9RrJS9DsIpB0Bb9afl2hVJOmHd0zA3HJY= -github.com/sagernet/sing-tun v0.3.2/go.mod h1:DxLIyhjWU/HwGYoX0vNGg2c5QgTQIakphU1MuERR5tQ= -github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= -github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= -github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= -github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo= -github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 h1:z3SJQhVyU63FT26Wn/UByW6b7q8QKB0ZkPqsyqcz2PI= -github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6/go.mod h1:73xRZuxwkFk4aiLw28hG8W6o9cr2UPrGL9pdY2UTbvY= -github.com/sagernet/utls v1.5.4 h1:KmsEGbB2dKUtCNC+44NwAdNAqnqQ6GA4pTO0Yik56co= -github.com/sagernet/utls v1.5.4/go.mod h1:CTGxPWExIloRipK3XFpYv0OVyhO8kk3XCGW/ieyTh1s= -github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 h1:R0OMYAScomNAVpTfbHFpxqJpvwuhxSRi+g6z7gZhABs= -github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8/go.mod h1:K4J7/npM+VAMUeUmTa2JaA02JmyheP0GpRBOUvn3ecc= +github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= +github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= +github.com/sagernet/cronet-go v0.0.0-20260413093659-e4926ba205fa h1:7SehNSF1UHbLZa5dk+1rW1aperffJzl5r6TCJIXtAaY= +github.com/sagernet/cronet-go v0.0.0-20260413093659-e4926ba205fa/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= +github.com/sagernet/cronet-go/all v0.0.0-20260413093659-e4926ba205fa h1:ijk5v9N/akiMgqu734yMpv7Pk9F4Qmjh8Vfdcb4uJHE= +github.com/sagernet/cronet-go/all v0.0.0-20260413093659-e4926ba205fa/go.mod h1:+FENo4+0AOvH9e3oY6/iO7yy7USNt61dgbnI5W0TDZ0= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260413092954-cd09eb3e271b h1:O+PkYT88ayVWESX5tqxeMeS9OnzC3ZTic8gYiPJNXT8= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260413092954-cd09eb3e271b h1:o0MsgbsJwYkbqlbfaCvmAwb8/LAXeoSP8NE/aNvR/yY= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260413092954-cd09eb3e271b h1:JEQnc7cRMUahWJFtWY6n0hs1LE0KgyRv3pD0RWS8Yo8= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260413092954-cd09eb3e271b h1:69+AKzuUW9hzw2nU79c2DWfuzrIZ3PJm1KAwXh+7xr0= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260413092954-cd09eb3e271b h1:jp9FHUVTCJQ67Ecw3Inoct6/z1VTFXPtNYpXt47pa4E= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260413092954-cd09eb3e271b h1:WN3DZoECd2UbhmYQGpOA4jx4QBXiZuN1DvL/35NT61g= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260413092954-cd09eb3e271b h1:H4RKicwrIa4PwTXZOmXOg85hiCrpeFja4daOlX180pE= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260413092954-cd09eb3e271b h1:Rwi+Cu+Hgwj28F1lh837gGqSqn7oU8+r5i3UJyLPkKc= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260413092954-cd09eb3e271b h1:v2wcnPX3gt0PngFYXjXYAiarFckwx3pVAP6ETSpbSWE= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260413092954-cd09eb3e271b h1:Bl0zZ3QZq6pPJMbQlYHDhhaGngVefRlFzxWc0p48eHo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260413092954-cd09eb3e271b h1:vf+MbGv6RvvmXUNvganykBOnDIVXxy8XgtKOOqOcxtE= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260413092954-cd09eb3e271b h1:2IAc1bVFYF+B6hof34ChQKVhw7LElBxEEx7S0n+7o78= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260413092954-cd09eb3e271b h1:NrJaiOS0VLmWTbUHhXDsLTqelmCW4y3xJqptPs4Sx0s= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260413092954-cd09eb3e271b h1:A+ubSkca1nl2cT8pYUqCo1O7M41suNrKpWhZKCM/aIQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260413092954-cd09eb3e271b h1:WrhGH5FDXlCAoXwN6N44yCMvy6EbIurmTmptkz3mmms= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260413092954-cd09eb3e271b h1:kgwB5p5e0gdVX5iYRE7VbZS/On4qnb4UKonkGPwhkDI= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260413092954-cd09eb3e271b h1:Z3dOeFlRIOeQhSh+mCYDHui1yR3S/Uw8eupczzBvxqw= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260413092954-cd09eb3e271b h1:LPi6jz1k11Q67hm3Pw6aaPJ/Z6e3VtNhzrRjr5/5AQo= +github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:Wt5uFdU3tnmm8YzobYewwdF7Mt6SucRQg6xeTNWC3Tk= +github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260413092954-cd09eb3e271b h1:55sqihyfXWN7y7p7gOEgtUz9cm1mV3SDQ90/v6ROFaA= +github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:lyIF6wKBLwWa5ZXaAKbAoewewl+yCHo2iYev39Mbj4E= +github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260413092954-cd09eb3e271b h1:OTA1cbv5YIDVsYA8AAXHC4NgEc7b6pDiY+edujLWfJU= +github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:H46PnSTTZNcZokLLiDeMDaHiS1l14PH3tzWi0eykjD8= +github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260413092954-cd09eb3e271b h1:B/rdD/1A+RgqUYUZcoGhLeMqijnBd1mUt8+5LhOH7j8= +github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:RBhSUDAKWq7fswtV4nQUQhuaTLcX3ettR7teA7/yf2w= +github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260413092954-cd09eb3e271b h1:QFRWi6FucrODS4xQ8e9GYIzGSeMFO/DAMtTCVeJiCvM= +github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:wRzoIOGG4xbpp3Gh3triLKwMwYriScXzFtunLYhY4w0= +github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260413092954-cd09eb3e271b h1:2WJjPKZHLNIB4D17c3o9S+SP9kb3Qh0D26oWlun1+pE= +github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:LNiZXmWil1OPwKCheqQjtakZlJuKGFz+iv2eGF76Hhs= +github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260413092954-cd09eb3e271b h1:cUNTe4gNncRpYL28jzQf6qcJej40zzGQsH0o6CLUGws= +github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:YFDGKTkpkJGc5+hnX/RYosZyTWg9h+68VB55fYRRLYc= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260413092954-cd09eb3e271b h1:+sc1LJF0FjU2hVO5xBqqT+8qzoU08J2uHwxSle2m/Hw= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260413092954-cd09eb3e271b h1:+D/uhFxllI/KTLpeNEl8dwF3omPGmUFbrqt5tJkAyp0= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260413092954-cd09eb3e271b h1:nSUzzTUAZdqjGGckayk64sz+F0TGJPHvauTiAn27UKk= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260413092954-cd09eb3e271b h1:PE/fYBiHzB52gnQMg0soBfQyJCzmWHti48kCe2TBt9w= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260413092954-cd09eb3e271b h1:hy/3lPV11pKAAojDFnb95l9NpwOym6kME7FxS9p8sXs= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260413092954-cd09eb3e271b/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= +github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= +github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= +github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 h1:SUPFNB+vSP4RBPrSEgNII+HkfqC8hKMpYLodom4o4EU= +github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4= +github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= +github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= +github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= +github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= +github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgjHoKxLo3c+MDhNwc3o= +github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= +github.com/sagernet/sing v0.8.7-0.20260417135631-0d22698ed386 h1:gNonzIaoKi4MiEmK/5iJI+RoGof1PtYqzSaNE/lmWLw= +github.com/sagernet/sing v0.8.7-0.20260417135631-0d22698ed386/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing-cloudflared v0.0.0-20260416083718-efa6ab16dba9 h1:wWJexq4V7ope1c4XYSqT/hydqd9omdtbDRvSZIOhjfY= +github.com/sagernet/sing-cloudflared v0.0.0-20260416083718-efa6ab16dba9/go.mod h1:bH2NKX+NpDTY1Zkxfboxw6MXB/ZywaNLmrDJYgKMJ2Y= +github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= +github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= +github.com/sagernet/sing-quic v0.6.2-0.20260412143638-8f65b6be7cd6 h1:j3ISQRDyY5rs27NzUS/le+DHR0iOO0K0x+mWDLzu4Ok= +github.com/sagernet/sing-quic v0.6.2-0.20260412143638-8f65b6be7cd6/go.mod h1:r5Adw0EMUyhGBCjPI2JEupDtC040DrrvreXtua7Ifdc= +github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= +github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= +github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= +github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= +github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= +github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= +github.com/sagernet/sing-tun v0.8.8-0.20260410061515-018f5eaae695 h1:2maqN3XuorEo5faXHIyYZQZ1/ybim4hImfCEWZwdPbk= +github.com/sagernet/sing-tun v0.8.8-0.20260410061515-018f5eaae695/go.mod h1:pLCo4o+LacXEzz0bhwhJkKBjLlKOGPBNOAZ97ZVZWzs= +github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= +github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= +github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= +github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA= -github.com/spyzhov/ajson v0.9.0 h1:tF46gJGOenYVj+k9K1U1XpCxVWhmiyY5PsVCAs1+OJ0= -github.com/spyzhov/ajson v0.9.0/go.mod h1:a6oSw0MMb7Z5aD2tPoPO+jq11ETKgXUr2XktHdT8Wt8= +github.com/sergeyfrolov/bsbuffer v0.0.0-20180903213811-94e85abb8507 h1:ML7ZNtcln5UBo5Wv7RIv9Xg3Pr5VuRCWLFXEwda54Y4= +github.com/sergeyfrolov/bsbuffer v0.0.0-20180903213811-94e85abb8507/go.mod h1:DbI1gxrXI2jRGw7XGEUZQOOMd6PsnKzRrCKabvvMrwM= +github.com/shadowsocks/go-shadowsocks2 v0.1.5 h1:PDSQv9y2S85Fl7VBeOMF9StzeXZyK1HakRm86CUbr28= +github.com/shadowsocks/go-shadowsocks2 v0.1.5/go.mod h1:AGGpIoek4HRno4xzyFiAtLHkOpcoznZEkAccaI/rplM= +github.com/shirou/gopsutil/v4 v4.24.5 h1:gGsArG5K6vmsh5hcFOHaPm87UD003CaDMkAOweSQjhM= +github.com/shirou/gopsutil/v4 v4.24.5/go.mod h1:aoebb2vxetJ/yIDZISmduFvVNPHqXQ9SEJwRXxkf0RA= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0 h1:e5s7RKBd2rIPR0StbvZ2vTVtJ5jDTsTk5wtIIapZTRg= +github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0/go.mod h1:WpEFV2uhebXb8Jhes/5/fSdpmhGV8TL22RDaeWwV6hI= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spyzhov/ajson v0.9.4 h1:MVibcTCgO7DY4IlskdqIlCmDOsUOZ9P7oKj8ifdcf84= +github.com/spyzhov/ajson v0.9.4/go.mod h1:a6oSw0MMb7Z5aD2tPoPO+jq11ETKgXUr2XktHdT8Wt8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ= +github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4= +github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4= +github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg= +github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio= +github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8= +github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= +github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= +github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU= +github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= +github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA= +github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc= +github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14= +github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= +github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA= +github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s= +github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg= +github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= +github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= +github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= +github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +github.com/wader/filtertransport v0.0.0-20200316221534-bdd9e61eee78 h1:9sreu9e9KOihf2Y0NbpyfWhd1XFDcL4GTkPYL4IvMrg= +github.com/wader/filtertransport v0.0.0-20200316221534-bdd9e61eee78/go.mod h1:HazXTRLhXFyq80TQp7PUXi6BKE6mS+ydEdzEqNBKopQ= +github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= +github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xtaci/kcp-go/v5 v5.6.70 h1:AYX0QZl6PqmNj2IdYGZGuBfZuDUkUfl+eHYNijCqaO0= +github.com/xtaci/kcp-go/v5 v5.6.70/go.mod h1:9O3D8WR+cyyUjGiTILYfg17vn72otWuXK2AFfqIe6CM= +github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM= +github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= +github.com/xtaci/smux v1.5.50 h1:y/1DlWQC9bnMeZzsyk4oL2hbLK6uVk4BKTz5BeQqUEA= +github.com/xtaci/smux v1.5.50/go.mod h1:IGQ9QYrBphmb/4aTnLEcJby0TNr3NV+OslIOMrX825Q= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= -github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= +github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= +github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= +gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib v1.5.0 h1:rzdY78Ox2T+VlXcxGxELF+6VyUXlZBhmRqZu5etLm+c= +gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib v1.5.0/go.mod h1:70bhd4JKW/+1HLfm+TMrgHJsUHG4coelMWwiVEJ2gAg= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= +go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= +go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= +go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek= +go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= -golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0= +golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= +golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= +golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= -google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= +golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4= +golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= +golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdIg1ozBNLgPy4SLT84nfcBjr6rhGtXYtrkWLU= +golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ= +golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= +golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= -lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= +gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 h1:ze1vwAdliUAr68RQ5NtufWaXaOg8WUO2OACzEV+TNdE= +gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg= +lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo= +software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= +software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= +tailscale.com v1.58.2 h1:5trkhh/fpUn7f6TUcGUQYJ0GokdNNfNrjh9ONJhoc5A= +tailscale.com v1.58.2/go.mod h1:faWR8XaXemnSKCDjHC7SAQzaagkUjA5x4jlLWiwxtuk= +zombiezen.com/go/capnproto2 v2.18.2+incompatible h1:v3BD1zbruvffn7zjJUU5Pn8nZAB11bhZSQC4W+YnnKo= +zombiezen.com/go/capnproto2 v2.18.2+incompatible/go.mod h1:XO5Pr2SbXgqZwn0m0Ru54QBqpOf4K5AYBO+8LAOBQEQ= diff --git a/test/http_test.go b/test/http_test.go index 4b5fe70fc9..0e9185ef7f 100644 --- a/test/http_test.go +++ b/test/http_test.go @@ -6,6 +6,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" ) func TestHTTPSelf(t *testing.T) { @@ -14,18 +16,18 @@ func TestHTTPSelf(t *testing.T) { { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, }, @@ -38,7 +40,7 @@ func TestHTTPSelf(t *testing.T) { { Type: C.TypeHTTP, Tag: "http-out", - HTTPOptions: option.HTTPOutboundOptions{ + Options: &option.HTTPOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -49,9 +51,18 @@ func TestHTTPSelf(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "http-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "http-out", + }, + }, }, }, }, diff --git a/test/hysteria2_test.go b/test/hysteria2_test.go index f549442811..115af4a7b6 100644 --- a/test/hysteria2_test.go +++ b/test/hysteria2_test.go @@ -3,22 +3,36 @@ package main import ( "net/netip" "testing" + "time" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-quic/hysteria2" + "github.com/sagernet/sing/common" + F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/json/badoption" ) func TestHysteria2Self(t *testing.T) { t.Run("self", func(t *testing.T) { - testHysteria2Self(t, "") + testHysteria2Self(t, "", false) }) t.Run("self-salamander", func(t *testing.T) { - testHysteria2Self(t, "password") + testHysteria2Self(t, "password", false) }) + t.Run("self-hop", func(t *testing.T) { + testHysteria2Self(t, "", true) + }) + t.Run("self-hop-salamander", func(t *testing.T) { + testHysteria2Self(t, "password", true) + }) +} + +func TestHysteria2Hop(t *testing.T) { + testHysteria2Self(t, "password", true) } -func testHysteria2Self(t *testing.T, salamanderPassword string) { +func testHysteria2Self(t *testing.T, salamanderPassword string, portHop bool) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") var obfs *option.Hysteria2Obfs if salamanderPassword != "" { @@ -27,23 +41,31 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) { Password: salamanderPassword, } } + var ( + serverPorts []string + hopInterval time.Duration + ) + if portHop { + serverPorts = []string{F.ToString(serverPort, ":", serverPort)} + hopInterval = 5 * time.Second + } startInstance(t, option.Options{ Inbounds: []option.Inbound{ { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeHysteria2, - Hysteria2Options: option.Hysteria2InboundOptions{ + Options: &option.Hysteria2InboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, UpMbps: 100, @@ -70,15 +92,17 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) { { Type: C.TypeHysteria2, Tag: "hy2-out", - Hysteria2Options: option.Hysteria2OutboundOptions{ + Options: &option.Hysteria2OutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, }, - UpMbps: 100, - DownMbps: 100, - Obfs: obfs, - Password: "password", + ServerPorts: serverPorts, + HopInterval: badoption.Duration(hopInterval), + UpMbps: 100, + DownMbps: 100, + Obfs: obfs, + Password: "password", OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ TLS: &option.OutboundTLSOptions{ Enabled: true, @@ -94,14 +118,26 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) { { Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "hy2-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "hy2-out", + }, + }, }, }, }, }, }) testSuitLargeUDP(t, clientPort, testPort) + if portHop { + time.Sleep(5 * time.Second) + testSuitLargeUDP(t, clientPort, testPort) + } } func TestHysteria2Inbound(t *testing.T) { @@ -110,9 +146,9 @@ func TestHysteria2Inbound(t *testing.T) { Inbounds: []option.Inbound{ { Type: C.TypeHysteria2, - Hysteria2Options: option.Hysteria2InboundOptions{ + Options: &option.Hysteria2InboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Obfs: &option.Hysteria2Obfs{ @@ -162,9 +198,9 @@ func TestHysteria2Outbound(t *testing.T) { Inbounds: []option.Inbound{ { Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -173,7 +209,7 @@ func TestHysteria2Outbound(t *testing.T) { Outbounds: []option.Outbound{ { Type: C.TypeHysteria2, - Hysteria2Options: option.Hysteria2OutboundOptions{ + Options: &option.Hysteria2OutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/hysteria_test.go b/test/hysteria_test.go index 90ff62dd10..a7f73deadb 100644 --- a/test/hysteria_test.go +++ b/test/hysteria_test.go @@ -6,6 +6,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" ) func TestHysteriaSelf(t *testing.T) { @@ -15,18 +17,18 @@ func TestHysteriaSelf(t *testing.T) { { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeHysteria, - HysteriaOptions: option.HysteriaInboundOptions{ + Options: &option.HysteriaInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, UpMbps: 100, @@ -53,7 +55,7 @@ func TestHysteriaSelf(t *testing.T) { { Type: C.TypeHysteria, Tag: "hy-out", - HysteriaOptions: option.HysteriaOutboundOptions{ + Options: &option.HysteriaOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -75,9 +77,18 @@ func TestHysteriaSelf(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "hy-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "hy-out", + }, + }, }, }, }, @@ -92,9 +103,9 @@ func TestHysteriaInbound(t *testing.T) { Inbounds: []option.Inbound{ { Type: C.TypeHysteria, - HysteriaOptions: option.HysteriaInboundOptions{ + Options: &option.HysteriaInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, UpMbps: 100, @@ -143,9 +154,9 @@ func TestHysteriaOutbound(t *testing.T) { Inbounds: []option.Inbound{ { Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -154,7 +165,7 @@ func TestHysteriaOutbound(t *testing.T) { Outbounds: []option.Outbound{ { Type: C.TypeHysteria, - HysteriaOptions: option.HysteriaOutboundOptions{ + Options: &option.HysteriaOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/inbound_detour_test.go b/test/inbound_detour_test.go index c2ef57a56c..f4043895c2 100644 --- a/test/inbound_detour_test.go +++ b/test/inbound_detour_test.go @@ -7,6 +7,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks/shadowaead_2022" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" ) func TestChainedInbound(t *testing.T) { @@ -17,18 +19,18 @@ func TestChainedInbound(t *testing.T) { { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksInboundOptions{ + Options: &option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, Detour: "detour", }, @@ -39,9 +41,9 @@ func TestChainedInbound(t *testing.T) { { Type: C.TypeShadowsocks, Tag: "detour", - ShadowsocksOptions: option.ShadowsocksInboundOptions{ + Options: &option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: otherPort, }, Method: method, @@ -56,7 +58,7 @@ func TestChainedInbound(t *testing.T) { { Type: C.TypeShadowsocks, Tag: "ss-out", - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ Method: method, Password: password, DialerOptions: option.DialerOptions{ @@ -67,7 +69,7 @@ func TestChainedInbound(t *testing.T) { { Type: C.TypeShadowsocks, Tag: "detour-out", - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -80,9 +82,18 @@ func TestChainedInbound(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, diff --git a/test/ktls_test.go b/test/ktls_test.go new file mode 100644 index 0000000000..c873a1628a --- /dev/null +++ b/test/ktls_test.go @@ -0,0 +1,295 @@ +package main + +import ( + "net/netip" + "testing" + + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" + + "github.com/gofrs/uuid/v5" +) + +func TestKTLS(t *testing.T) { + _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + Options: &option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeTrojan, + Options: &option.TrojanInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: serverPort, + }, + Users: []option.TrojanUser{ + { + Name: "sekai", + Password: "password", + }, + }, + InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + KeyPath: keyPem, + // KernelTx: true, + }, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeTrojan, + Tag: "trojan-out", + Options: &option.TrojanOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + Password: "password", + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + KernelTx: true, + KernelRx: true, + }, + }, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "trojan-out", + }, + }, + }, + }, + }, + }, + }) + testSuit(t, clientPort, testPort) +} + +func TestKTLSECH(t *testing.T) { + _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") + echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org")) + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + Options: &option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeTrojan, + Options: &option.TrojanInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: serverPort, + }, + Users: []option.TrojanUser{ + { + Name: "sekai", + Password: "password", + }, + }, + InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + KeyPath: keyPem, + KernelTx: true, + ECH: &option.InboundECHOptions{ + Enabled: true, + Key: []string{echKey}, + }, + }, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeTrojan, + Tag: "trojan-out", + Options: &option.TrojanOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + Password: "password", + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + KernelTx: true, + KernelRx: true, + ECH: &option.OutboundECHOptions{ + Enabled: true, + Config: []string{echConfig}, + }, + }, + }, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "trojan-out", + }, + }, + }, + }, + }, + }, + }) + testSuit(t, clientPort, testPort) +} + +func TestKTLSReality(t *testing.T) { + user, _ := uuid.NewV4() + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + Options: &option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeVLESS, + Options: &option.VLESSInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: serverPort, + }, + Users: []option.VLESSUser{{UUID: user.String()}}, + InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "google.com", + KernelTx: true, + Reality: &option.InboundRealityOptions{ + Enabled: true, + Handshake: option.InboundRealityHandshakeOptions{ + ServerOptions: option.ServerOptions{ + Server: "google.com", + ServerPort: 443, + }, + }, + ShortID: []string{"0123456789abcdef"}, + PrivateKey: "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc", + }, + }, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeVLESS, + Tag: "ss-out", + Options: &option.VLESSOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + UUID: user.String(), + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "google.com", + KernelTx: true, + KernelRx: true, + Reality: &option.OutboundRealityOptions{ + Enabled: true, + ShortID: "0123456789abcdef", + PublicKey: "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0", + }, + UTLS: &option.OutboundUTLSOptions{ + Enabled: true, + }, + }, + }, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, + }, + }, + }, + }, + }) + testSuit(t, clientPort, testPort) +} diff --git a/test/mux_cool_test.go b/test/mux_cool_test.go index ef47695a50..ed42a05945 100644 --- a/test/mux_cool_test.go +++ b/test/mux_cool_test.go @@ -7,6 +7,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/spyzhov/ajson" "github.com/stretchr/testify/require" @@ -40,9 +42,9 @@ func TestMuxCoolServer(t *testing.T) { Inbounds: []option.Inbound{ { Type: C.TypeVMess, - VMessOptions: option.VMessInboundOptions{ + Options: &option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -84,9 +86,9 @@ func TestMuxCoolClient(t *testing.T) { Inbounds: []option.Inbound{ { Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -95,7 +97,7 @@ func TestMuxCoolClient(t *testing.T) { Outbounds: []option.Outbound{ { Type: C.TypeVMess, - VMessOptions: option.VMessOutboundOptions{ + Options: &option.VMessOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -116,18 +118,18 @@ func TestMuxCoolSelf(t *testing.T) { { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeVMess, - VMessOptions: option.VMessInboundOptions{ + Options: &option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -146,7 +148,7 @@ func TestMuxCoolSelf(t *testing.T) { { Type: C.TypeVMess, Tag: "vmess-out", - VMessOptions: option.VMessOutboundOptions{ + Options: &option.VMessOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -159,9 +161,18 @@ func TestMuxCoolSelf(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "vmess-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "vmess-out", + }, + }, }, }, }, diff --git a/test/mux_test.go b/test/mux_test.go index c02f270878..6454d19c7f 100644 --- a/test/mux_test.go +++ b/test/mux_test.go @@ -7,6 +7,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks/shadowaead_2022" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/gofrs/uuid/v5" ) @@ -59,18 +61,18 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) { { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksInboundOptions{ + Options: &option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Method: method, @@ -88,7 +90,7 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) { { Type: C.TypeShadowsocks, Tag: "ss-out", - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -102,9 +104,18 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, @@ -120,18 +131,18 @@ func testVMessMux(t *testing.T, options option.OutboundMultiplexOptions) { { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeVMess, - VMessOptions: option.VMessInboundOptions{ + Options: &option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -152,7 +163,7 @@ func testVMessMux(t *testing.T, options option.OutboundMultiplexOptions) { { Type: C.TypeVMess, Tag: "vmess-out", - VMessOptions: option.VMessOutboundOptions{ + Options: &option.VMessOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -166,9 +177,18 @@ func testVMessMux(t *testing.T, options option.OutboundMultiplexOptions) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "vmess-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "vmess-out", + }, + }, }, }, }, diff --git a/test/naive_self_test.go b/test/naive_self_test.go new file mode 100644 index 0000000000..9d293bfb06 --- /dev/null +++ b/test/naive_self_test.go @@ -0,0 +1,533 @@ +//go:build with_naive_outbound + +package main + +import ( + "net/netip" + "os" + "strings" + "testing" + + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/protocol/naive" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/auth" + "github.com/sagernet/sing/common/json/badoption" + "github.com/sagernet/sing/common/network" + + "github.com/stretchr/testify/require" +) + +func TestNaiveSelf(t *testing.T) { + caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") + caPemContent, err := os.ReadFile(caPem) + require.NoError(t, err) + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + Options: &option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeNaive, + Tag: "naive-in", + Options: &option.NaiveInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: serverPort, + }, + Users: []auth.User{ + { + Username: "sekai", + Password: "password", + }, + }, + Network: network.NetworkTCP, + InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + KeyPath: keyPem, + }, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeNaive, + Tag: "naive-out", + Options: &option.NaiveOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + Username: "sekai", + Password: "password", + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + Certificate: []string{string(caPemContent)}, + }, + }, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.RouteActionOptions{ + Outbound: "naive-out", + }, + }, + }, + }, + }, + }, + }) + testTCP(t, clientPort, testPort) +} + +func TestNaiveSelfECH(t *testing.T) { + caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") + caPemContent, err := os.ReadFile(caPem) + require.NoError(t, err) + echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org")) + instance := startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + Options: &option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeNaive, + Tag: "naive-in", + Options: &option.NaiveInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: serverPort, + }, + Users: []auth.User{ + { + Username: "sekai", + Password: "password", + }, + }, + Network: network.NetworkTCP, + InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + KeyPath: keyPem, + ECH: &option.InboundECHOptions{ + Enabled: true, + Key: []string{echKey}, + }, + }, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeNaive, + Tag: "naive-out", + Options: &option.NaiveOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + Username: "sekai", + Password: "password", + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + Certificate: []string{string(caPemContent)}, + ECH: &option.OutboundECHOptions{ + Enabled: true, + Config: []string{echConfig}, + }, + }, + }, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.RouteActionOptions{ + Outbound: "naive-out", + }, + }, + }, + }, + }, + }, + }) + + naiveOut, ok := instance.Outbound().Outbound("naive-out") + require.True(t, ok) + naiveOutbound := naiveOut.(*naive.Outbound) + + netLogPath := "/tmp/naive_ech_netlog.json" + require.True(t, naiveOutbound.Client().Engine().StartNetLogToFile(netLogPath, true)) + defer naiveOutbound.Client().Engine().StopNetLog() + + testTCP(t, clientPort, testPort) + + naiveOutbound.Client().Engine().StopNetLog() + + logContent, err := os.ReadFile(netLogPath) + require.NoError(t, err) + logStr := string(logContent) + + require.True(t, strings.Contains(logStr, `"encrypted_client_hello":true`), + "ECH should be accepted in TLS handshake. NetLog saved to: %s", netLogPath) +} + +func TestNaiveSelfInsecureConcurrency(t *testing.T) { + caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") + caPemContent, err := os.ReadFile(caPem) + require.NoError(t, err) + + instance := startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + Options: &option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeNaive, + Tag: "naive-in", + Options: &option.NaiveInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: serverPort, + }, + Users: []auth.User{ + { + Username: "sekai", + Password: "password", + }, + }, + Network: network.NetworkTCP, + InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + KeyPath: keyPem, + }, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeNaive, + Tag: "naive-out", + Options: &option.NaiveOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + Username: "sekai", + Password: "password", + InsecureConcurrency: 3, + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + Certificate: []string{string(caPemContent)}, + }, + }, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.RouteActionOptions{ + Outbound: "naive-out", + }, + }, + }, + }, + }, + }, + }) + + naiveOut, ok := instance.Outbound().Outbound("naive-out") + require.True(t, ok) + naiveOutbound := naiveOut.(*naive.Outbound) + + netLogPath := "/tmp/naive_concurrency_netlog.json" + require.True(t, naiveOutbound.Client().Engine().StartNetLogToFile(netLogPath, true)) + defer naiveOutbound.Client().Engine().StopNetLog() + + // Send multiple sequential connections to trigger round-robin + // With insecure_concurrency=3, connections will be distributed to 3 pools + for i := 0; i < 6; i++ { + testTCP(t, clientPort, testPort) + } + + naiveOutbound.Client().Engine().StopNetLog() + + // Verify NetLog contains multiple independent HTTP/2 sessions + logContent, err := os.ReadFile(netLogPath) + require.NoError(t, err) + logStr := string(logContent) + + // Count HTTP2_SESSION_INITIALIZED events to verify connection pool isolation + // NetLog stores event types as numeric IDs, HTTP2_SESSION_INITIALIZED = 249 + sessionCount := strings.Count(logStr, `"type":249`) + require.GreaterOrEqual(t, sessionCount, 3, + "Expected at least 3 HTTP/2 sessions with insecure_concurrency=3. NetLog: %s", netLogPath) +} + +func TestNaiveSelfQUIC(t *testing.T) { + caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") + caPemContent, err := os.ReadFile(caPem) + require.NoError(t, err) + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + Options: &option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeNaive, + Tag: "naive-in", + Options: &option.NaiveInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: serverPort, + }, + Users: []auth.User{ + { + Username: "sekai", + Password: "password", + }, + }, + Network: network.NetworkUDP, + InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + KeyPath: keyPem, + }, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeNaive, + Tag: "naive-out", + Options: &option.NaiveOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + Username: "sekai", + Password: "password", + QUIC: true, + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + Certificate: []string{string(caPemContent)}, + }, + }, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.RouteActionOptions{ + Outbound: "naive-out", + }, + }, + }, + }, + }, + }, + }) + testTCP(t, clientPort, testPort) +} + +func TestNaiveSelfQUICCongestionControl(t *testing.T) { + testCases := []struct { + name string + congestionControl string + }{ + {"BBR", "bbr"}, + {"BBR2", "bbr2"}, + {"Cubic", "cubic"}, + {"Reno", "reno"}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") + caPemContent, err := os.ReadFile(caPem) + require.NoError(t, err) + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + Options: &option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeNaive, + Tag: "naive-in", + Options: &option.NaiveInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: serverPort, + }, + Users: []auth.User{ + { + Username: "sekai", + Password: "password", + }, + }, + Network: network.NetworkUDP, + QUICCongestionControl: tc.congestionControl, + InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + KeyPath: keyPem, + }, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeNaive, + Tag: "naive-out", + Options: &option.NaiveOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + Username: "sekai", + Password: "password", + QUIC: true, + QUICCongestionControl: tc.congestionControl, + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + Certificate: []string{string(caPemContent)}, + }, + }, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.RouteActionOptions{ + Outbound: "naive-out", + }, + }, + }, + }, + }, + }, + }) + testTCP(t, clientPort, testPort) + }) + } +} diff --git a/test/naive_test.go b/test/naive_test.go index 1a1547da17..9dbe985588 100644 --- a/test/naive_test.go +++ b/test/naive_test.go @@ -6,7 +6,9 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" + "github.com/sagernet/sing/common/json/badoption" "github.com/sagernet/sing/common/network" ) @@ -16,9 +18,9 @@ func TestNaiveInboundWithNginx(t *testing.T) { Inbounds: []option.Inbound{ { Type: C.TypeNaive, - NaiveOptions: option.NaiveInboundOptions{ + Options: &option.NaiveInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: otherPort, }, Users: []auth.User{ @@ -62,9 +64,9 @@ func TestNaiveInbound(t *testing.T) { Inbounds: []option.Inbound{ { Type: C.TypeNaive, - NaiveOptions: option.NaiveInboundOptions{ + Options: &option.NaiveInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []auth.User{ @@ -106,9 +108,9 @@ func TestNaiveHTTP3Inbound(t *testing.T) { Inbounds: []option.Inbound{ { Type: C.TypeNaive, - NaiveOptions: option.NaiveInboundOptions{ + Options: &option.NaiveInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []auth.User{ diff --git a/test/reality_test.go b/test/reality_test.go new file mode 100644 index 0000000000..220ccb6c18 --- /dev/null +++ b/test/reality_test.go @@ -0,0 +1,108 @@ +package main + +import ( + "net/netip" + "testing" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" + + "github.com/gofrs/uuid/v5" +) + +func TestReality(t *testing.T) { + user, _ := uuid.NewV4() + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + Options: &option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeVLESS, + Options: &option.VLESSInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: serverPort, + }, + Users: []option.VLESSUser{{UUID: user.String()}}, + InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "google.com", + Reality: &option.InboundRealityOptions{ + Enabled: true, + Handshake: option.InboundRealityHandshakeOptions{ + ServerOptions: option.ServerOptions{ + Server: "google.com", + ServerPort: 443, + }, + }, + ShortID: []string{"0123456789abcdef"}, + PrivateKey: "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc", + }, + }, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeVLESS, + Tag: "ss-out", + Options: &option.VLESSOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + UUID: user.String(), + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "google.com", + Reality: &option.OutboundRealityOptions{ + Enabled: true, + ShortID: "0123456789abcdef", + PublicKey: "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0", + }, + UTLS: &option.OutboundUTLSOptions{ + Enabled: true, + }, + }, + }, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, + }, + }, + }, + }, + }) + testSuit(t, clientPort, testPort) +} diff --git a/test/shadowsocks_legacy_test.go b/test/shadowsocks_legacy_test.go index 8075a7df23..8182e6cb23 100644 --- a/test/shadowsocks_legacy_test.go +++ b/test/shadowsocks_legacy_test.go @@ -7,7 +7,9 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks2/shadowstream" + "github.com/sagernet/sing/common" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/json/badoption" ) func TestShadowsocksLegacy(t *testing.T) { @@ -27,9 +29,9 @@ func testShadowsocksLegacy(t *testing.T, method string) { Inbounds: []option.Inbound{ { Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -38,7 +40,7 @@ func testShadowsocksLegacy(t *testing.T, method string) { Outbounds: []option.Outbound{ { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/shadowsocks_test.go b/test/shadowsocks_test.go index 7d063d9a96..3f526b106c 100644 --- a/test/shadowsocks_test.go +++ b/test/shadowsocks_test.go @@ -9,7 +9,9 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks/shadowaead_2022" + "github.com/sagernet/sing/common" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/json/badoption" "github.com/stretchr/testify/require" ) @@ -102,9 +104,9 @@ func testShadowsocksInboundWithShadowsocksRust(t *testing.T, method string, pass Inbounds: []option.Inbound{ { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksInboundOptions{ + Options: &option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Method: method, @@ -127,9 +129,9 @@ func testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, pas Inbounds: []option.Inbound{ { Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -138,7 +140,7 @@ func testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, pas Outbounds: []option.Outbound{ { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -158,18 +160,18 @@ func testShadowsocksSelf(t *testing.T, method string, password string) { { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksInboundOptions{ + Options: &option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Method: method, @@ -184,7 +186,7 @@ func testShadowsocksSelf(t *testing.T, method string, password string) { { Type: C.TypeShadowsocks, Tag: "ss-out", - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -197,9 +199,18 @@ func testShadowsocksSelf(t *testing.T, method string, password string) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, @@ -216,18 +227,18 @@ func TestShadowsocksUoT(t *testing.T) { { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksInboundOptions{ + Options: &option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Method: method, @@ -242,7 +253,7 @@ func TestShadowsocksUoT(t *testing.T) { { Type: C.TypeShadowsocks, Tag: "ss-out", - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -258,9 +269,18 @@ func TestShadowsocksUoT(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, @@ -275,18 +295,18 @@ func testShadowsocks2022EIH(t *testing.T, method string, password string) { { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksInboundOptions{ + Options: &option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Method: method, @@ -306,7 +326,7 @@ func testShadowsocks2022EIH(t *testing.T, method string, password string) { { Type: C.TypeShadowsocks, Tag: "ss-out", - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -319,9 +339,18 @@ func testShadowsocks2022EIH(t *testing.T, method string, password string) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, diff --git a/test/shadowtls_test.go b/test/shadowtls_test.go index 2f53d46ada..6c4b71d442 100644 --- a/test/shadowtls_test.go +++ b/test/shadowtls_test.go @@ -2,6 +2,7 @@ package main import ( "context" + "crypto/tls" "net" "net/http" "net/netip" @@ -10,39 +11,59 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks/shadowaead_2022" + "github.com/sagernet/sing/common" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/json/badoption" "github.com/stretchr/testify/require" ) func TestShadowTLS(t *testing.T) { t.Run("v1", func(t *testing.T) { - testShadowTLS(t, 1, "", false) + testShadowTLS(t, 1, "", false, option.ShadowTLSWildcardSNIOff) }) t.Run("v2", func(t *testing.T) { - testShadowTLS(t, 2, "hello", false) + testShadowTLS(t, 2, "hello", false, option.ShadowTLSWildcardSNIOff) }) t.Run("v3", func(t *testing.T) { - testShadowTLS(t, 3, "hello", false) + testShadowTLS(t, 3, "hello", false, option.ShadowTLSWildcardSNIOff) }) t.Run("v2-utls", func(t *testing.T) { - testShadowTLS(t, 2, "hello", true) + testShadowTLS(t, 2, "hello", true, option.ShadowTLSWildcardSNIOff) }) t.Run("v3-utls", func(t *testing.T) { - testShadowTLS(t, 3, "hello", true) + testShadowTLS(t, 3, "hello", true, option.ShadowTLSWildcardSNIOff) + }) + t.Run("v3-wildcard-sni-authed", func(t *testing.T) { + testShadowTLS(t, 3, "hello", false, option.ShadowTLSWildcardSNIAuthed) + }) + t.Run("v3-wildcard-sni-all", func(t *testing.T) { + testShadowTLS(t, 3, "hello", false, option.ShadowTLSWildcardSNIAll) + }) + t.Run("v3-wildcard-sni-authed-utls", func(t *testing.T) { + testShadowTLS(t, 3, "hello", true, option.ShadowTLSWildcardSNIAll) + }) + t.Run("v3-wildcard-sni-all-utls", func(t *testing.T) { + testShadowTLS(t, 3, "hello", true, option.ShadowTLSWildcardSNIAll) }) } -func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) { +func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool, wildcardSNI option.WildcardSNI) { method := shadowaead_2022.List[0] ssPassword := mkBase64(t, 16) + var clientServerName string + if wildcardSNI != option.ShadowTLSWildcardSNIOff { + clientServerName = "cloudflare.com" + } else { + clientServerName = "google.com" + } startInstance(t, option.Options{ Inbounds: []option.Inbound{ { Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -50,9 +71,9 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) { Type: C.TypeShadowTLS, Tag: "in", - ShadowTLSOptions: option.ShadowTLSInboundOptions{ + Options: &option.ShadowTLSInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, Detour: "detour", }, @@ -62,17 +83,18 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) ServerPort: 443, }, }, - Version: version, - Password: password, - Users: []option.ShadowTLSUser{{Password: password}}, + Version: version, + Password: password, + Users: []option.ShadowTLSUser{{Password: password}}, + WildcardSNI: wildcardSNI, }, }, { Type: C.TypeShadowsocks, Tag: "detour", - ShadowsocksOptions: option.ShadowsocksInboundOptions{ + Options: &option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: otherPort, }, Method: method, @@ -83,7 +105,7 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) Outbounds: []option.Outbound{ { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ Method: method, Password: ssPassword, DialerOptions: option.DialerOptions{ @@ -94,7 +116,7 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) { Type: C.TypeShadowTLS, Tag: "detour", - ShadowTLSOptions: option.ShadowTLSOutboundOptions{ + Options: &option.ShadowTLSOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -102,7 +124,7 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ TLS: &option.OutboundTLSOptions{ Enabled: true, - ServerName: "google.com", + ServerName: clientServerName, UTLS: &option.OutboundUTLSOptions{ Enabled: utlsEanbled, }, @@ -118,12 +140,23 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) }, }, Route: &option.RouteOptions{ - Rules: []option.Rule{{ - DefaultOptions: option.DefaultRule{ - Inbound: []string{"detour"}, - Outbound: "direct", + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"detour"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "direct", + }, + }, + }, }, - }}, + }, }, }) testTCP(t, clientPort, testPort) @@ -134,14 +167,14 @@ func TestShadowTLSFallback(t *testing.T) { Inbounds: []option.Inbound{ { Type: C.TypeShadowTLS, - ShadowTLSOptions: option.ShadowTLSInboundOptions{ + Options: &option.ShadowTLSInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Handshake: option.ShadowTLSHandshakeOptions{ ServerOptions: option.ServerOptions{ - Server: "google.com", + Server: "bing.com", ServerPort: 443, }, }, @@ -161,13 +194,125 @@ func TestShadowTLSFallback(t *testing.T) { }, }, } - response, err := client.Get("https://google.com") + response, err := client.Get("https://bing.com") require.NoError(t, err) require.Equal(t, response.StatusCode, 200) response.Body.Close() client.CloseIdleConnections() } +func TestShadowTLSFallbackWildcardAll(t *testing.T) { + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeShadowTLS, + Options: &option.ShadowTLSInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: serverPort, + }, + Version: 3, + Users: []option.ShadowTLSUser{ + {Password: "hello"}, + }, + WildcardSNI: option.ShadowTLSWildcardSNIAll, + }, + }, + }, + }) + client := &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + var d net.Dialer + return d.DialContext(ctx, network, "127.0.0.1:"+F.ToString(serverPort)) + }, + }, + } + response, err := client.Get("https://www.bing.com") + require.NoError(t, err) + require.Equal(t, response.StatusCode, 200) + response.Body.Close() + client.CloseIdleConnections() +} + +func TestShadowTLSFallbackWildcardAuthedFail(t *testing.T) { + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeShadowTLS, + Options: &option.ShadowTLSInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: serverPort, + }, + Handshake: option.ShadowTLSHandshakeOptions{ + ServerOptions: option.ServerOptions{ + Server: "bing.com", + ServerPort: 443, + }, + }, + Version: 3, + Users: []option.ShadowTLSUser{ + {Password: "hello"}, + }, + WildcardSNI: option.ShadowTLSWildcardSNIAuthed, + }, + }, + }, + }) + client := &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + var d net.Dialer + return d.DialContext(ctx, network, "127.0.0.1:"+F.ToString(serverPort)) + }, + }, + } + _, err := client.Get("https://baidu.com") + expected := &tls.CertificateVerificationError{} + require.ErrorAs(t, err, &expected) + client.CloseIdleConnections() +} + +func TestShadowTLSFallbackWildcardOffFail(t *testing.T) { + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeShadowTLS, + Options: &option.ShadowTLSInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: serverPort, + }, + Handshake: option.ShadowTLSHandshakeOptions{ + ServerOptions: option.ServerOptions{ + Server: "bing.com", + ServerPort: 443, + }, + }, + Version: 3, + Users: []option.ShadowTLSUser{ + {Password: "hello"}, + }, + WildcardSNI: option.ShadowTLSWildcardSNIOff, + }, + }, + }, + }) + client := &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + var d net.Dialer + return d.DialContext(ctx, network, "127.0.0.1:"+F.ToString(serverPort)) + }, + }, + } + _, err := client.Get("https://baidu.com") + expected := &tls.CertificateVerificationError{} + require.ErrorAs(t, err, &expected) + client.CloseIdleConnections() +} + func TestShadowTLSInbound(t *testing.T) { method := shadowaead_2022.List[0] password := mkBase64(t, 16) @@ -182,18 +327,18 @@ func TestShadowTLSInbound(t *testing.T) { { Type: C.TypeMixed, Tag: "in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeShadowTLS, - ShadowTLSOptions: option.ShadowTLSInboundOptions{ + Options: &option.ShadowTLSInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, Detour: "detour", }, @@ -212,9 +357,9 @@ func TestShadowTLSInbound(t *testing.T) { { Type: C.TypeShadowsocks, Tag: "detour", - ShadowsocksOptions: option.ShadowsocksInboundOptions{ + Options: &option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), }, Method: method, Password: password, @@ -228,7 +373,7 @@ func TestShadowTLSInbound(t *testing.T) { { Type: C.TypeShadowsocks, Tag: "out", - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: otherPort, @@ -239,12 +384,23 @@ func TestShadowTLSInbound(t *testing.T) { }, }, Route: &option.RouteOptions{ - Rules: []option.Rule{{ - DefaultOptions: option.DefaultRule{ - Inbound: []string{"in"}, - Outbound: "out", + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "out", + }, + }, + }, }, - }}, + }, }, }) testTCP(t, clientPort, testPort) @@ -264,9 +420,9 @@ func TestShadowTLSOutbound(t *testing.T) { Inbounds: []option.Inbound{ { Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -274,9 +430,9 @@ func TestShadowTLSOutbound(t *testing.T) { { Type: C.TypeShadowsocks, Tag: "detour", - ShadowsocksOptions: option.ShadowsocksInboundOptions{ + Options: &option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: otherPort, }, Method: method, @@ -287,7 +443,7 @@ func TestShadowTLSOutbound(t *testing.T) { Outbounds: []option.Outbound{ { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ Method: method, Password: password, DialerOptions: option.DialerOptions{ @@ -298,7 +454,7 @@ func TestShadowTLSOutbound(t *testing.T) { { Type: C.TypeShadowTLS, Tag: "detour", - ShadowTLSOptions: option.ShadowTLSOutboundOptions{ + Options: &option.ShadowTLSOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -319,12 +475,23 @@ func TestShadowTLSOutbound(t *testing.T) { }, }, Route: &option.RouteOptions{ - Rules: []option.Rule{{ - DefaultOptions: option.DefaultRule{ - Inbound: []string{"detour"}, - Outbound: "direct", + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"detour"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "direct", + }, + }, + }, }, - }}, + }, }, }) testTCP(t, clientPort, testPort) diff --git a/test/socks_test.go b/test/socks_test.go new file mode 100644 index 0000000000..d33e349c3a --- /dev/null +++ b/test/socks_test.go @@ -0,0 +1,133 @@ +package main + +import ( + "context" + "net" + "net/netip" + "testing" + "time" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/json/badoption" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/protocol/socks" + + "github.com/stretchr/testify/require" +) + +func TestSOCKSUDPTimeout(t *testing.T) { + const testTimeout = 2 * time.Second + udpTimeout := option.UDPTimeoutCompat(testTimeout) + + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeSOCKS, + Tag: "socks-in", + Options: &option.SocksInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: clientPort, + UDPTimeout: udpTimeout, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + }, + }) + + testUDPSessionIdleTimeout(t, clientPort, testPort, testTimeout) +} + +func TestMixedUDPTimeout(t *testing.T) { + const testTimeout = 2 * time.Second + udpTimeout := option.UDPTimeoutCompat(testTimeout) + + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + Options: &option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: clientPort, + UDPTimeout: udpTimeout, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + }, + }) + + testUDPSessionIdleTimeout(t, clientPort, testPort, testTimeout) +} + +func testUDPSessionIdleTimeout(t *testing.T, proxyPort uint16, echoPort uint16, expectedTimeout time.Duration) { + echoServer, err := listenPacket("udp", ":"+F.ToString(echoPort)) + require.NoError(t, err) + defer echoServer.Close() + + go func() { + buffer := make([]byte, 1024) + for { + n, address, err := echoServer.ReadFrom(buffer) + if err != nil { + return + } + _, _ = echoServer.WriteTo(buffer[:n], address) + } + }() + + dialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort("127.0.0.1", proxyPort), socks.Version5, "", "") + + packetConn, err := dialer.ListenPacket(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", echoPort)) + require.NoError(t, err) + defer packetConn.Close() + + remoteAddress := &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: int(echoPort)} + + _, err = packetConn.WriteTo([]byte("hello"), remoteAddress) + require.NoError(t, err) + + buffer := make([]byte, 1024) + packetConn.SetReadDeadline(time.Now().Add(5 * time.Second)) + n, _, err := packetConn.ReadFrom(buffer) + require.NoError(t, err, "failed to receive echo response") + require.Equal(t, "hello", string(buffer[:n])) + t.Log("UDP echo successful, session established") + + packetConn.SetReadDeadline(time.Time{}) + + waitTime := expectedTimeout + time.Second + t.Logf("Waiting %v for UDP session to timeout...", waitTime) + time.Sleep(waitTime) + + _, err = packetConn.WriteTo([]byte("after-timeout"), remoteAddress) + if err != nil { + t.Logf("Write after timeout correctly failed: %v", err) + return + } + + packetConn.SetReadDeadline(time.Now().Add(3 * time.Second)) + n, _, err = packetConn.ReadFrom(buffer) + if err != nil { + t.Logf("Read after timeout correctly failed: %v", err) + return + } + + t.Fatalf("UDP session should have timed out after %v, but received response: %s", + expectedTimeout, string(buffer[:n])) +} diff --git a/test/ss_plugin_test.go b/test/ss_plugin_test.go index 94606b70fd..f5d1863986 100644 --- a/test/ss_plugin_test.go +++ b/test/ss_plugin_test.go @@ -6,6 +6,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" ) func TestShadowsocksObfs(t *testing.T) { @@ -36,9 +38,9 @@ func testShadowsocksPlugin(t *testing.T, name string, opts string, args string) Inbounds: []option.Inbound{ { Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -47,7 +49,7 @@ func testShadowsocksPlugin(t *testing.T, name string, opts string, args string) Outbounds: []option.Outbound{ { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/tfo_test.go b/test/tfo_test.go index cc97e189d1..d74dbfb66c 100644 --- a/test/tfo_test.go +++ b/test/tfo_test.go @@ -7,6 +7,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks/shadowaead" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" ) func TestTCPSlowOpen(t *testing.T) { @@ -17,18 +19,18 @@ func TestTCPSlowOpen(t *testing.T) { { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeShadowsocks, - ShadowsocksOptions: option.ShadowsocksInboundOptions{ + Options: &option.ShadowsocksInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, TCPFastOpen: true, }, @@ -44,7 +46,7 @@ func TestTCPSlowOpen(t *testing.T) { { Type: C.TypeShadowsocks, Tag: "ss-out", - ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + Options: &option.ShadowsocksOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -60,9 +62,18 @@ func TestTCPSlowOpen(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "ss-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, }, }, }, diff --git a/test/tls_test.go b/test/tls_test.go index da55faf5bf..5aaf37c314 100644 --- a/test/tls_test.go +++ b/test/tls_test.go @@ -6,6 +6,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" ) func TestUTLS(t *testing.T) { @@ -15,18 +17,18 @@ func TestUTLS(t *testing.T) { { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeTrojan, - TrojanOptions: option.TrojanInboundOptions{ + Options: &option.TrojanInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TrojanUser{ @@ -53,7 +55,7 @@ func TestUTLS(t *testing.T) { { Type: C.TypeTrojan, Tag: "trojan-out", - TrojanOptions: option.TrojanOutboundOptions{ + Options: &option.TrojanOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -76,9 +78,18 @@ func TestUTLS(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "trojan-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "trojan-out", + }, + }, }, }, }, diff --git a/test/trojan_test.go b/test/trojan_test.go index d8659b2e4f..cceed4077c 100644 --- a/test/trojan_test.go +++ b/test/trojan_test.go @@ -6,6 +6,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" ) func TestTrojanOutbound(t *testing.T) { @@ -23,9 +25,9 @@ func TestTrojanOutbound(t *testing.T) { Inbounds: []option.Inbound{ { Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -34,7 +36,7 @@ func TestTrojanOutbound(t *testing.T) { Outbounds: []option.Outbound{ { Type: C.TypeTrojan, - TrojanOptions: option.TrojanOutboundOptions{ + Options: &option.TrojanOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -61,18 +63,18 @@ func TestTrojanSelf(t *testing.T) { { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeTrojan, - TrojanOptions: option.TrojanInboundOptions{ + Options: &option.TrojanInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TrojanUser{ @@ -99,7 +101,7 @@ func TestTrojanSelf(t *testing.T) { { Type: C.TypeTrojan, Tag: "trojan-out", - TrojanOptions: option.TrojanOutboundOptions{ + Options: &option.TrojanOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -118,9 +120,18 @@ func TestTrojanSelf(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "trojan-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "trojan-out", + }, + }, }, }, }, @@ -135,18 +146,18 @@ func TestTrojanPlainSelf(t *testing.T) { { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeTrojan, - TrojanOptions: option.TrojanInboundOptions{ + Options: &option.TrojanInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TrojanUser{ @@ -165,7 +176,7 @@ func TestTrojanPlainSelf(t *testing.T) { { Type: C.TypeTrojan, Tag: "trojan-out", - TrojanOptions: option.TrojanOutboundOptions{ + Options: &option.TrojanOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -177,9 +188,18 @@ func TestTrojanPlainSelf(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "trojan-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "trojan-out", + }, + }, }, }, }, diff --git a/test/tuic_test.go b/test/tuic_test.go index c2b71111f0..d5f13bece9 100644 --- a/test/tuic_test.go +++ b/test/tuic_test.go @@ -6,6 +6,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/gofrs/uuid/v5" ) @@ -33,18 +35,18 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) { { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeTUIC, - TUICOptions: option.TUICInboundOptions{ + Options: &option.TUICInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TUICUser{{ @@ -69,7 +71,7 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) { { Type: C.TypeTUIC, Tag: "tuic-out", - TUICOptions: option.TUICOutboundOptions{ + Options: &option.TUICOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -90,9 +92,18 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "tuic-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "tuic-out", + }, + }, }, }, }, @@ -107,9 +118,9 @@ func TestTUICInbound(t *testing.T) { Inbounds: []option.Inbound{ { Type: C.TypeTUIC, - TUICOptions: option.TUICInboundOptions{ + Options: &option.TUICInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TUICUser{{ @@ -154,9 +165,9 @@ func TestTUICOutbound(t *testing.T) { Inbounds: []option.Inbound{ { Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -165,7 +176,7 @@ func TestTUICOutbound(t *testing.T) { Outbounds: []option.Outbound{ { Type: C.TypeTUIC, - TUICOptions: option.TUICOutboundOptions{ + Options: &option.TUICOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/v2ray_api_test.go b/test/v2ray_api_test.go index 1bea41a678..2225703255 100644 --- a/test/v2ray_api_test.go +++ b/test/v2ray_api_test.go @@ -1,5 +1,6 @@ package main +/* import ( "context" "net/netip" @@ -8,6 +9,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental/v2rayapi" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/stretchr/testify/require" ) @@ -18,9 +21,9 @@ func TestV2RayAPI(t *testing.T) { { Type: C.TypeMixed, Tag: "in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -54,3 +57,4 @@ func TestV2RayAPI(t *testing.T) { require.Equal(t, count, stat.Value) } } +*/ diff --git a/test/v2ray_grpc_test.go b/test/v2ray_grpc_test.go index fa43f753a1..884cc42e0b 100644 --- a/test/v2ray_grpc_test.go +++ b/test/v2ray_grpc_test.go @@ -7,6 +7,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/gofrs/uuid/v5" "github.com/spyzhov/ajson" @@ -30,9 +32,9 @@ func testV2RayGRPCInbound(t *testing.T, forceLite bool) { Inbounds: []option.Inbound{ { Type: C.TypeVMess, - VMessOptions: option.VMessInboundOptions{ + Options: &option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -130,9 +132,9 @@ func testV2RayGRPCOutbound(t *testing.T, forceLite bool) { { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -142,7 +144,7 @@ func testV2RayGRPCOutbound(t *testing.T, forceLite bool) { { Type: C.TypeVMess, Tag: "vmess-out", - VMessOptions: option.VMessOutboundOptions{ + Options: &option.VMessOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/v2ray_transport_test.go b/test/v2ray_transport_test.go index 2f39d18aed..b32a5cafe6 100644 --- a/test/v2ray_transport_test.go +++ b/test/v2ray_transport_test.go @@ -6,6 +6,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/gofrs/uuid/v5" "github.com/stretchr/testify/require" @@ -48,18 +50,18 @@ func testVMessTransportSelf(t *testing.T, server *option.V2RayTransportOptions, { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeVMess, - VMessOptions: option.VMessInboundOptions{ + Options: &option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -87,7 +89,7 @@ func testVMessTransportSelf(t *testing.T, server *option.V2RayTransportOptions, { Type: C.TypeVMess, Tag: "vmess-out", - VMessOptions: option.VMessOutboundOptions{ + Options: &option.VMessOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -108,9 +110,18 @@ func testVMessTransportSelf(t *testing.T, server *option.V2RayTransportOptions, Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "vmess-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "vmess-out", + }, + }, }, }, }, @@ -128,18 +139,18 @@ func testTrojanTransportSelf(t *testing.T, server *option.V2RayTransportOptions, { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeTrojan, - TrojanOptions: option.TrojanInboundOptions{ + Options: &option.TrojanInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.TrojanUser{ @@ -167,7 +178,7 @@ func testTrojanTransportSelf(t *testing.T, server *option.V2RayTransportOptions, { Type: C.TypeTrojan, Tag: "vmess-out", - TrojanOptions: option.TrojanOutboundOptions{ + Options: &option.TrojanOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -187,9 +198,18 @@ func testTrojanTransportSelf(t *testing.T, server *option.V2RayTransportOptions, Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "vmess-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "vmess-out", + }, + }, }, }, }, @@ -210,18 +230,18 @@ func TestVMessQUICSelf(t *testing.T) { { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeVMess, - VMessOptions: option.VMessInboundOptions{ + Options: &option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -249,7 +269,7 @@ func TestVMessQUICSelf(t *testing.T) { { Type: C.TypeVMess, Tag: "vmess-out", - VMessOptions: option.VMessOutboundOptions{ + Options: &option.VMessOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -270,9 +290,18 @@ func TestVMessQUICSelf(t *testing.T) { Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "vmess-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "vmess-out", + }, + }, }, }, }, @@ -289,18 +318,18 @@ func testV2RayTransportNOTLSSelf(t *testing.T, transport *option.V2RayTransportO { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeVMess, - VMessOptions: option.VMessInboundOptions{ + Options: &option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -320,7 +349,7 @@ func testV2RayTransportNOTLSSelf(t *testing.T, transport *option.V2RayTransportO { Type: C.TypeVMess, Tag: "vmess-out", - VMessOptions: option.VMessOutboundOptions{ + Options: &option.VMessOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -334,9 +363,18 @@ func testV2RayTransportNOTLSSelf(t *testing.T, transport *option.V2RayTransportO Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "vmess-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "vmess-out", + }, + }, }, }, }, diff --git a/test/v2ray_ws_test.go b/test/v2ray_ws_test.go index 0e238c28b6..4db4b372e3 100644 --- a/test/v2ray_ws_test.go +++ b/test/v2ray_ws_test.go @@ -7,6 +7,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/gofrs/uuid/v5" "github.com/spyzhov/ajson" @@ -64,9 +66,9 @@ func testV2RayWebsocketInbound(t *testing.T, maxEarlyData uint32, earlyDataHeade Inbounds: []option.Inbound{ { Type: C.TypeVMess, - VMessOptions: option.VMessInboundOptions{ + Options: &option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -162,9 +164,9 @@ func testV2RayWebsocketOutbound(t *testing.T, maxEarlyData uint32, earlyDataHead { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -174,7 +176,7 @@ func testV2RayWebsocketOutbound(t *testing.T, maxEarlyData uint32, earlyDataHead { Type: C.TypeVMess, Tag: "vmess-out", - VMessOptions: option.VMessOutboundOptions{ + Options: &option.VMessOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, diff --git a/test/vmess_test.go b/test/vmess_test.go index cc7879ab84..4da76c6e08 100644 --- a/test/vmess_test.go +++ b/test/vmess_test.go @@ -7,6 +7,8 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" "github.com/gofrs/uuid/v5" "github.com/spyzhov/ajson" @@ -184,9 +186,9 @@ func testVMessInboundWithV2Ray(t *testing.T, security string, alterId int, authe Inbounds: []option.Inbound{ { Type: C.TypeVMess, - VMessOptions: option.VMessInboundOptions{ + Options: &option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -232,9 +234,9 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, globalPadding boo Inbounds: []option.Inbound{ { Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, @@ -243,7 +245,7 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, globalPadding boo Outbounds: []option.Outbound{ { Type: C.TypeVMess, - VMessOptions: option.VMessOutboundOptions{ + Options: &option.VMessOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -267,18 +269,18 @@ func testVMessSelf(t *testing.T, security string, alterId int, globalPadding boo { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: clientPort, }, }, }, { Type: C.TypeVMess, - VMessOptions: option.VMessInboundOptions{ + Options: &option.VMessInboundOptions{ ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, }, Users: []option.VMessUser{ @@ -298,7 +300,7 @@ func testVMessSelf(t *testing.T, security string, alterId int, globalPadding boo { Type: C.TypeVMess, Tag: "vmess-out", - VMessOptions: option.VMessOutboundOptions{ + Options: &option.VMessOutboundOptions{ ServerOptions: option.ServerOptions{ Server: "127.0.0.1", ServerPort: serverPort, @@ -315,9 +317,17 @@ func testVMessSelf(t *testing.T, security string, alterId int, globalPadding boo Route: &option.RouteOptions{ Rules: []option.Rule{ { + Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "vmess-out", + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.RouteActionOptions{ + Outbound: "vmess-out", + }, + }, }, }, }, diff --git a/test/wireguard_test.go b/test/wireguard_test.go deleted file mode 100644 index 50e87ee04d..0000000000 --- a/test/wireguard_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -import ( - "net/netip" - "testing" - "time" - - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/option" -) - -func _TestWireGuard(t *testing.T) { - startDockerContainer(t, DockerOptions{ - Image: ImageBoringTun, - Cap: []string{"MKNOD", "NET_ADMIN", "NET_RAW"}, - Ports: []uint16{serverPort, testPort}, - Bind: map[string]string{ - "wireguard.conf": "/etc/wireguard/wg0.conf", - }, - Cmd: []string{"wg0"}, - }) - time.Sleep(5 * time.Second) - startInstance(t, option.Options{ - Inbounds: []option.Inbound{ - { - Type: C.TypeMixed, - MixedOptions: option.HTTPMixedInboundOptions{ - ListenOptions: option.ListenOptions{ - Listen: option.NewListenAddress(netip.IPv4Unspecified()), - ListenPort: clientPort, - }, - }, - }, - }, - Outbounds: []option.Outbound{ - { - Type: C.TypeWireGuard, - WireGuardOptions: option.WireGuardOutboundOptions{ - ServerOptions: option.ServerOptions{ - Server: "127.0.0.1", - ServerPort: serverPort, - }, - LocalAddress: []netip.Prefix{netip.MustParsePrefix("10.0.0.2/32")}, - PrivateKey: "qGnwlkZljMxeECW8fbwAWdvgntnbK7B8UmMFl3zM0mk=", - PeerPublicKey: "QsdcBm+oJw2oNv0cIFXLIq1E850lgTBonup4qnKEQBg=", - }, - }, - }, - }) - testSuitWg(t, clientPort, testPort) -} diff --git a/test/wrapper_test.go b/test/wrapper_test.go index d2b6b9ffc1..9eb9ed7873 100644 --- a/test/wrapper_test.go +++ b/test/wrapper_test.go @@ -12,7 +12,7 @@ import ( func TestOptionsWrapper(t *testing.T) { inbound := option.Inbound{ Type: C.TypeHTTP, - HTTPOptions: option.HTTPMixedInboundOptions{ + Options: &option.HTTPMixedInboundOptions{ InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ TLS: &option.InboundTLSOptions{ Enabled: true, @@ -20,13 +20,11 @@ func TestOptionsWrapper(t *testing.T) { }, }, } - rawOptions, err := inbound.RawOptions() - require.NoError(t, err) - tlsOptionsWrapper, loaded := rawOptions.(option.InboundTLSOptionsWrapper) + tlsOptionsWrapper, loaded := inbound.Options.(option.InboundTLSOptionsWrapper) require.True(t, loaded, "find inbound tls options") tlsOptions := tlsOptionsWrapper.TakeInboundTLSOptions() require.NotNil(t, tlsOptions, "find inbound tls options") tlsOptions.Enabled = false tlsOptionsWrapper.ReplaceInboundTLSOptions(tlsOptions) - require.False(t, inbound.HTTPOptions.TLS.Enabled, "replace tls enabled") + require.False(t, inbound.Options.(*option.HTTPMixedInboundOptions).TLS.Enabled, "replace tls enabled") } diff --git a/transport/awg/bind.go b/transport/awg/bind.go new file mode 100644 index 0000000000..8c8ae68ebf --- /dev/null +++ b/transport/awg/bind.go @@ -0,0 +1,181 @@ +package awg + +import ( + "context" + "errors" + "net" + "net/netip" + "sync" + "syscall" + + "github.com/amnezia-vpn/amneziawg-go/conn" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +var _ conn.Bind = (*bind_adapter)(nil) + +type bind_adapter struct { + conn4 net.PacketConn + conn6 net.PacketConn + dialer N.Dialer + ctx context.Context + mutex sync.Mutex +} + +func newBind(ctx context.Context, dial N.Dialer) conn.Bind { + return &bind_adapter{ + ctx: ctx, + dialer: dial, + } +} + +func (b *bind_adapter) connect(addr netip.Addr, port uint16) (net.PacketConn, error) { + return b.dialer.ListenPacket(b.ctx, M.Socksaddr{Addr: addr, Port: port}) +} + +func (b *bind_adapter) receive(c net.PacketConn) conn.ReceiveFunc { + return func(packets [][]byte, sizes []int, eps []conn.Endpoint) (n int, err error) { + n, addr, err := c.ReadFrom(packets[0]) + if err != nil { + return 0, E.Cause(err, "read data") + } + + bindEp, err := b.ParseEndpoint(addr.String()) + if err != nil { + return 0, E.Cause(err, "parse endpoint") + } + + sizes[0] = n + eps[0] = bindEp + return 1, nil + } +} + +func (b *bind_adapter) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err error) { + b.mutex.Lock() + defer b.mutex.Unlock() + + if b.conn4 != nil || b.conn6 != nil { + return nil, 0, conn.ErrBindAlreadyOpen + } + + conn4, err := b.connect(netip.IPv4Unspecified(), port) + if err != nil && !errors.Is(err, syscall.EAFNOSUPPORT) { + return nil, 0, E.Cause(err, "create ipv4 connection") + } + if conn4 != nil { + fns = append(fns, b.receive(conn4)) + } + + conn6, err := b.connect(netip.IPv6Unspecified(), port) + if err != nil && !errors.Is(err, syscall.EAFNOSUPPORT) { + return nil, 0, E.Cause(err, "create ipv6 connection") + } + if conn6 != nil { + fns = append(fns, b.receive(conn6)) + } + + b.conn4 = conn4 + b.conn6 = conn6 + + return fns, port, nil +} + +func (b *bind_adapter) Close() error { + b.mutex.Lock() + defer b.mutex.Unlock() + + var err4, err6 error + + if b.conn4 != nil { + err4 = b.conn4.Close() + b.conn4 = nil + } + + if b.conn6 != nil { + err6 = b.conn6.Close() + b.conn6 = nil + } + + return errors.Join(err4, err6) +} + +func (b *bind_adapter) SetMark(mark uint32) error { + return nil +} + +func (b *bind_adapter) Send(bufs [][]byte, ep conn.Endpoint) error { + var conn net.PacketConn + if ep.DstIP().Is6() { + conn = b.conn6 + } else { + conn = b.conn4 + } + + if conn == nil { + return errors.ErrUnsupported + } + + bindEp, ok := ep.(*bind_endpoint) + if !ok { + return errors.ErrUnsupported + } + + udpAddr := &net.UDPAddr{ + IP: bindEp.AddrPort.Addr().AsSlice(), + Port: int(bindEp.AddrPort.Port()), + } + + for _, buf := range bufs { + if _, err := conn.WriteTo(buf, udpAddr); err != nil { + return err + } + } + + return nil +} + +func (b *bind_adapter) ParseEndpoint(s string) (conn.Endpoint, error) { + ap, err := netip.ParseAddrPort(s) + if err != nil { + return nil, E.Cause(err, "parse addrport") + } + + return &bind_endpoint{AddrPort: ap}, nil +} + +func (b *bind_adapter) BatchSize() int { + return 1 +} + +var _ conn.Endpoint = (*bind_endpoint)(nil) + +type bind_endpoint struct { + AddrPort netip.AddrPort +} + +func (e bind_endpoint) ClearSrc() { +} + +func (e bind_endpoint) SrcToString() string { + return "" +} + +func (e bind_endpoint) DstToString() string { + return e.AddrPort.String() +} + +func (e bind_endpoint) DstToBytes() []byte { + b, _ := e.AddrPort.MarshalBinary() + return b +} + +func (e bind_endpoint) DstIP() netip.Addr { + return e.AddrPort.Addr() +} + +func (e bind_endpoint) SrcIP() netip.Addr { + return netip.Addr{} +} diff --git a/transport/awg/device.go b/transport/awg/device.go new file mode 100644 index 0000000000..16101cdc0b --- /dev/null +++ b/transport/awg/device.go @@ -0,0 +1,109 @@ +package awg + +import ( + "context" + "fmt" + "net" + "net/netip" + "strings" + + "github.com/amnezia-vpn/amneziawg-go/conn" + "github.com/amnezia-vpn/amneziawg-go/device" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common/exceptions" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/network" +) + +type DeviceOpts struct { + UseIntegratedTun bool + Address []netip.Prefix + AllowedIps []netip.Prefix + ExcludedIps []netip.Prefix + MTU uint32 +} + +type Device struct { + awgDevice *device.Device + tun tunAdapter + bind conn.Bind + logger *device.Logger + ipcConfig string +} + +func NewDevice(ctx context.Context, logger logger.ContextLogger, dial network.Dialer, ipcConfig string, opts DeviceOpts) (*Device, error) { + var ( + tun tunAdapter + err error + ) + + if opts.UseIntegratedTun { + tun, err = newSystemTun(ctx, opts.Address, opts.AllowedIps, opts.ExcludedIps, opts.MTU, logger) + if err != nil { + return nil, exceptions.Cause(err, "create tunnel") + } + } else { + tun, err = newNetworkTun(opts.Address, opts.MTU) + if err != nil { + return nil, err + } + } + + awgLogger := &device.Logger{ + Verbosef: func(format string, args ...interface{}) { + logger.Debug(fmt.Sprintf(strings.ToLower(format), args...)) + }, + Errorf: func(format string, args ...interface{}) { + logger.Error(fmt.Sprintf(strings.ToLower(format), args...)) + }, + } + + return &Device{ + tun: tun, + bind: newBind(ctx, dial), + logger: awgLogger, + ipcConfig: ipcConfig, + }, nil +} + +func (d *Device) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + + d.awgDevice = device.NewDevice(d.tun, d.bind, d.logger) + if err := d.awgDevice.IpcSet(d.ipcConfig); err != nil { + return E.Cause(err, "set ipc config") + } + + if err := d.tun.Start(); err != nil { + return E.Cause(err, "tun start") + } + + return d.awgDevice.Up() +} + +func (d *Device) IsUnderLoad() bool { + if d.awgDevice == nil { + return false + } + return d.awgDevice.IsUnderLoad() +} + +func (d *Device) Close() error { + if d.awgDevice != nil { + d.awgDevice.Close() + } + return nil +} + +func (d *Device) DialContext(ctx context.Context, network string, destination metadata.Socksaddr) (net.Conn, error) { + return d.tun.DialContext(ctx, network, destination) +} + +func (d *Device) ListenPacket(ctx context.Context, destination metadata.Socksaddr) (net.PacketConn, error) { + return d.tun.ListenPacket(ctx, destination) +} \ No newline at end of file diff --git a/transport/awg/tun.go b/transport/awg/tun.go new file mode 100644 index 0000000000..a03c8455d5 --- /dev/null +++ b/transport/awg/tun.go @@ -0,0 +1,13 @@ +package awg + +import ( + awgTun "github.com/amnezia-vpn/amneziawg-go/tun" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common/network" +) + +type tunAdapter interface { + network.Dialer + awgTun.Device + adapter.SimpleLifecycle +} diff --git a/transport/awg/tun_network.go b/transport/awg/tun_network.go new file mode 100644 index 0000000000..e2cf85756e --- /dev/null +++ b/transport/awg/tun_network.go @@ -0,0 +1,45 @@ +package awg + +import ( + "context" + "net" + "net/netip" + + "github.com/amnezia-vpn/amneziawg-go/tun" + "github.com/amnezia-vpn/amneziawg-go/tun/netstack" + "github.com/sagernet/sing/common/metadata" +) + +type networkTun struct { + tun.Device + conn *netstack.Net +} + +func newNetworkTun(address []netip.Prefix, mtu uint32) (tunAdapter, error) { + var localAddresses []netip.Addr + for _, prefix := range address { + localAddresses = append(localAddresses, prefix.Addr()) + } + + tun, conn, err := netstack.CreateNetTUN(localAddresses, []netip.Addr{}, int(mtu)) + if err != nil { + return nil, err + } + + return &networkTun{ + Device: tun, + conn: conn, + }, nil +} + +func (t *networkTun) Start() error { + return nil +} + +func (t *networkTun) DialContext(ctx context.Context, network string, destination metadata.Socksaddr) (net.Conn, error) { + return t.conn.DialContext(ctx, network, destination.String()) +} + +func (t *networkTun) ListenPacket(ctx context.Context, destination metadata.Socksaddr) (net.PacketConn, error) { + return t.conn.DialUDPAddrPort(netip.AddrPort{}, destination.AddrPort()) +} diff --git a/transport/awg/tun_system.go b/transport/awg/tun_system.go new file mode 100644 index 0000000000..c924d91696 --- /dev/null +++ b/transport/awg/tun_system.go @@ -0,0 +1,143 @@ +package awg + +import ( + "context" + "net" + "net/netip" + "os" + + awgTun "github.com/amnezia-vpn/amneziawg-go/tun" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/option" + tun "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" +) + +type systemTun struct { + mtu uint32 + singtun tun.Tun + events chan awgTun.Event + name string + dialer network.Dialer +} + +func newSystemTun(ctx context.Context, address []netip.Prefix, allowedIps []netip.Prefix, excludedIps []netip.Prefix, mtu uint32, logger logger.Logger) (tunAdapter, error) { + networkManager := service.FromContext[adapter.NetworkManager](ctx) + name := tun.CalculateInterfaceName("") + events := make(chan awgTun.Event) + + dial, err := dialer.NewDefault(ctx, option.DialerOptions{ + BindInterface: name, + }) + if err != nil { + return nil, exceptions.Cause(err, "get in-tunnel dialer") + } + + singtun, err := tun.New(tun.Options{ + Name: name, + GSO: true, + MTU: uint32(mtu), + Inet4Address: common.Filter(address, func(it netip.Prefix) bool { + return it.Addr().Is4() + }), + Inet6Address: common.Filter(address, func(it netip.Prefix) bool { + return it.Addr().Is6() + }), + InterfaceMonitor: networkManager.InterfaceMonitor(), + InterfaceFinder: networkManager.InterfaceFinder(), + Inet4RouteAddress: common.Filter(allowedIps, func(it netip.Prefix) bool { + return it.Addr().Is4() + }), + Inet6RouteAddress: common.Filter(allowedIps, func(it netip.Prefix) bool { + return it.Addr().Is6() + }), + Inet4RouteExcludeAddress: common.Filter(excludedIps, func(it netip.Prefix) bool { + return it.Addr().Is4() + }), + Inet6RouteExcludeAddress: common.Filter(excludedIps, func(it netip.Prefix) bool { + return it.Addr().Is6() + }), + Logger: logger, + }) + if err != nil { + return nil, exceptions.Cause(err, "create tunnel") + } + + return &systemTun{ + mtu: mtu, + events: events, + singtun: singtun, + name: name, + dialer: dial, + }, nil +} + +func (t *systemTun) Start() error { + if err := t.singtun.Start(); err != nil { + return exceptions.Cause(err, "start tunnel") + } + + t.events <- awgTun.EventUp + return nil +} + +func (t *systemTun) File() *os.File { + return nil +} + +func (t *systemTun) Read(bufs [][]byte, sizes []int, offset int) (int, error) { + n, err := t.singtun.Read(bufs[0][offset-tun.PacketOffset:]) + if err != nil { + return 0, err + } + sizes[0] = n + return 1, nil +} + +func (t *systemTun) Write(bufs [][]byte, offset int) (int, error) { + for _, buf := range bufs { + common.ClearArray(buf[offset-tun.PacketOffset : offset]) + tun.PacketFillHeader(buf[offset-tun.PacketOffset:], tun.PacketIPVersion(buf[offset:])) + + if _, err := t.singtun.Write(buf[offset-tun.PacketOffset:]); err != nil { + return 0, err + } + } + return len(bufs), nil +} + +func (t *systemTun) MTU() (int, error) { + return int(t.mtu), nil +} + +func (t *systemTun) Name() (string, error) { + return t.name, nil +} + +func (t *systemTun) Events() <-chan awgTun.Event { + return t.events +} + +func (t *systemTun) Close() error { + close(t.events) + return nil +} + +func (t *systemTun) BatchSize() int { + return 1 +} + +func (t *systemTun) DialContext(ctx context.Context, network string, destination metadata.Socksaddr) (net.Conn, error) { + return t.dialer.DialContext(ctx, network, destination) +} + +func (t *systemTun) ListenPacket(ctx context.Context, destination metadata.Socksaddr) (net.PacketConn, error) { + return t.dialer.ListenPacket(ctx, destination) +} diff --git a/transport/dhcp/server.go b/transport/dhcp/server.go deleted file mode 100644 index 8325c37b2f..0000000000 --- a/transport/dhcp/server.go +++ /dev/null @@ -1,281 +0,0 @@ -package dhcp - -import ( - "context" - "net" - "net/netip" - "net/url" - "os" - "runtime" - "strings" - "sync" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/dialer" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-dns" - "github.com/sagernet/sing-tun" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/control" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/task" - "github.com/sagernet/sing/common/x/list" - - "github.com/insomniacslk/dhcp/dhcpv4" - mDNS "github.com/miekg/dns" -) - -func init() { - dns.RegisterTransport([]string{"dhcp"}, func(options dns.TransportOptions) (dns.Transport, error) { - return NewTransport(options) - }) -} - -type Transport struct { - options dns.TransportOptions - router adapter.Router - interfaceName string - autoInterface bool - interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback] - transports []dns.Transport - updateAccess sync.Mutex - updatedAt time.Time -} - -func NewTransport(options dns.TransportOptions) (*Transport, error) { - linkURL, err := url.Parse(options.Address) - if err != nil { - return nil, err - } - if linkURL.Host == "" { - return nil, E.New("missing interface name for DHCP") - } - router := adapter.RouterFromContext(options.Context) - if router == nil { - return nil, E.New("missing router in context") - } - transport := &Transport{ - options: options, - router: router, - interfaceName: linkURL.Host, - autoInterface: linkURL.Host == "auto", - } - return transport, nil -} - -func (t *Transport) Name() string { - return t.options.Name -} - -func (t *Transport) Start() error { - err := t.fetchServers() - if err != nil { - return err - } - if t.autoInterface { - t.interfaceCallback = t.router.InterfaceMonitor().RegisterCallback(t.interfaceUpdated) - } - return nil -} - -func (t *Transport) Reset() { - for _, transport := range t.transports { - transport.Reset() - } -} - -func (t *Transport) Close() error { - for _, transport := range t.transports { - transport.Close() - } - if t.interfaceCallback != nil { - t.router.InterfaceMonitor().UnregisterCallback(t.interfaceCallback) - } - return nil -} - -func (t *Transport) Raw() bool { - return true -} - -func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { - err := t.fetchServers() - if err != nil { - return nil, err - } - - if len(t.transports) == 0 { - return nil, E.New("dhcp: empty DNS servers from response") - } - - var response *mDNS.Msg - for _, transport := range t.transports { - response, err = transport.Exchange(ctx, message) - if err == nil { - return response, nil - } - } - return nil, err -} - -func (t *Transport) fetchInterface() (*net.Interface, error) { - interfaceName := t.interfaceName - if t.autoInterface { - if t.router.InterfaceMonitor() == nil { - return nil, E.New("missing monitor for auto DHCP, set route.auto_detect_interface") - } - interfaceName = t.router.InterfaceMonitor().DefaultInterfaceName(netip.Addr{}) - } - if interfaceName == "" { - return nil, E.New("missing default interface") - } - return net.InterfaceByName(interfaceName) -} - -func (t *Transport) fetchServers() error { - if time.Since(t.updatedAt) < C.DHCPTTL { - return nil - } - t.updateAccess.Lock() - defer t.updateAccess.Unlock() - if time.Since(t.updatedAt) < C.DHCPTTL { - return nil - } - return t.updateServers() -} - -func (t *Transport) updateServers() error { - iface, err := t.fetchInterface() - if err != nil { - return E.Cause(err, "dhcp: prepare interface") - } - - t.options.Logger.Info("dhcp: query DNS servers on ", iface.Name) - fetchCtx, cancel := context.WithTimeout(t.options.Context, C.DHCPTimeout) - err = t.fetchServers0(fetchCtx, iface) - cancel() - if err != nil { - return err - } else if len(t.transports) == 0 { - return E.New("dhcp: empty DNS servers response") - } else { - t.updatedAt = time.Now() - return nil - } -} - -func (t *Transport) interfaceUpdated(int) { - err := t.updateServers() - if err != nil { - t.options.Logger.Error("update servers: ", err) - } -} - -func (t *Transport) fetchServers0(ctx context.Context, iface *net.Interface) error { - var listener net.ListenConfig - listener.Control = control.Append(listener.Control, control.BindToInterface(t.router.InterfaceFinder(), iface.Name, iface.Index)) - listener.Control = control.Append(listener.Control, control.ReuseAddr()) - listenAddr := "0.0.0.0:68" - if runtime.GOOS == "linux" || runtime.GOOS == "android" { - listenAddr = "255.255.255.255:68" - } - packetConn, err := listener.ListenPacket(t.options.Context, "udp4", listenAddr) - if err != nil { - return err - } - defer packetConn.Close() - - discovery, err := dhcpv4.NewDiscovery(iface.HardwareAddr, dhcpv4.WithBroadcast(true), dhcpv4.WithRequestedOptions(dhcpv4.OptionDomainNameServer)) - if err != nil { - return err - } - - _, err = packetConn.WriteTo(discovery.ToBytes(), &net.UDPAddr{IP: net.IPv4bcast, Port: 67}) - if err != nil { - return err - } - - var group task.Group - group.Append0(func(ctx context.Context) error { - return t.fetchServersResponse(iface, packetConn, discovery.TransactionID) - }) - group.Cleanup(func() { - packetConn.Close() - }) - return group.Run(ctx) -} - -func (t *Transport) fetchServersResponse(iface *net.Interface, packetConn net.PacketConn, transactionID dhcpv4.TransactionID) error { - buffer := buf.NewSize(dhcpv4.MaxMessageSize) - defer buffer.Release() - - for { - _, _, err := buffer.ReadPacketFrom(packetConn) - if err != nil { - return err - } - - dhcpPacket, err := dhcpv4.FromBytes(buffer.Bytes()) - if err != nil { - t.options.Logger.Trace("dhcp: parse DHCP response: ", err) - return err - } - - if dhcpPacket.MessageType() != dhcpv4.MessageTypeOffer { - t.options.Logger.Trace("dhcp: expected OFFER response, but got ", dhcpPacket.MessageType()) - continue - } - - if dhcpPacket.TransactionID != transactionID { - t.options.Logger.Trace("dhcp: expected transaction ID ", transactionID, ", but got ", dhcpPacket.TransactionID) - continue - } - - dns := dhcpPacket.DNS() - if len(dns) == 0 { - return nil - } - - var addrs []netip.Addr - for _, ip := range dns { - addr, _ := netip.AddrFromSlice(ip) - addrs = append(addrs, addr.Unmap()) - } - return t.recreateServers(iface, addrs) - } -} - -func (t *Transport) recreateServers(iface *net.Interface, serverAddrs []netip.Addr) error { - if len(serverAddrs) > 0 { - t.options.Logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, func(it netip.Addr) string { - return it.String() - }), ","), "]") - } - serverDialer := common.Must1(dialer.NewDefault(t.router, option.DialerOptions{ - BindInterface: iface.Name, - UDPFragmentDefault: true, - })) - var transports []dns.Transport - for _, serverAddr := range serverAddrs { - newOptions := t.options - newOptions.Address = serverAddr.String() - newOptions.Dialer = serverDialer - serverTransport, err := dns.NewUDPTransport(newOptions) - if err != nil { - return E.Cause(err, "create UDP transport from DHCP result: ", serverAddr) - } - transports = append(transports, serverTransport) - } - for _, transport := range t.transports { - transport.Close() - } - t.transports = transports - return nil -} - -func (t *Transport) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) { - return nil, os.ErrInvalid -} diff --git a/transport/fakeip/server.go b/transport/fakeip/server.go deleted file mode 100644 index 5e0c7eef02..0000000000 --- a/transport/fakeip/server.go +++ /dev/null @@ -1,94 +0,0 @@ -package fakeip - -import ( - "context" - "net/netip" - "os" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-dns" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/logger" - - mDNS "github.com/miekg/dns" -) - -var ( - _ dns.Transport = (*Transport)(nil) - _ adapter.FakeIPTransport = (*Transport)(nil) -) - -func init() { - dns.RegisterTransport([]string{"fakeip"}, func(options dns.TransportOptions) (dns.Transport, error) { - return NewTransport(options) - }) -} - -type Transport struct { - name string - router adapter.Router - store adapter.FakeIPStore - logger logger.ContextLogger -} - -func NewTransport(options dns.TransportOptions) (*Transport, error) { - router := adapter.RouterFromContext(options.Context) - if router == nil { - return nil, E.New("missing router in context") - } - return &Transport{ - name: options.Name, - router: router, - logger: options.Logger, - }, nil -} - -func (s *Transport) Name() string { - return s.name -} - -func (s *Transport) Start() error { - s.store = s.router.FakeIPStore() - if s.store == nil { - return E.New("fakeip not enabled") - } - return nil -} - -func (s *Transport) Reset() { -} - -func (s *Transport) Close() error { - return nil -} - -func (s *Transport) Raw() bool { - return false -} - -func (s *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { - return nil, os.ErrInvalid -} - -func (s *Transport) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) { - var addresses []netip.Addr - if strategy != dns.DomainStrategyUseIPv6 { - inet4Address, err := s.store.Create(domain, false) - if err != nil { - return nil, err - } - addresses = append(addresses, inet4Address) - } - if strategy != dns.DomainStrategyUseIPv4 { - inet6Address, err := s.store.Create(domain, true) - if err != nil { - return nil, err - } - addresses = append(addresses, inet6Address) - } - return addresses, nil -} - -func (s *Transport) Store() adapter.FakeIPStore { - return s.store -} diff --git a/transport/masque/adapter.go b/transport/masque/adapter.go new file mode 100644 index 0000000000..f72748f7e2 --- /dev/null +++ b/transport/masque/adapter.go @@ -0,0 +1,82 @@ +package masque + +import ( + "sync" + + "github.com/sagernet/wireguard-go/tun" + "github.com/songgao/water" +) + +type NetstackAdapter struct { + dev tun.Device + tunnelBufPool sync.Pool + tunnelSizesPool sync.Pool +} + +func (n *NetstackAdapter) ReadPacket(buf []byte) (int, error) { + packetBufsPtr := n.tunnelBufPool.Get().(*[][]byte) + sizesPtr := n.tunnelSizesPool.Get().(*[]int) + + defer func() { + (*packetBufsPtr)[0] = nil + n.tunnelBufPool.Put(packetBufsPtr) + n.tunnelSizesPool.Put(sizesPtr) + }() + + (*packetBufsPtr)[0] = buf + (*sizesPtr)[0] = 0 + + _, err := n.dev.Read(*packetBufsPtr, *sizesPtr, 0) + if err != nil { + return 0, err + } + + return (*sizesPtr)[0], nil +} + +func (n *NetstackAdapter) WritePacket(pkt []byte) error { + // Write expects a slice of packet buffers. + _, err := n.dev.Write([][]byte{pkt}, 0) + return err +} + +// NewNetstackAdapter creates a new NetstackAdapter. +func NewNetstackAdapter(dev tun.Device) TunnelDevice { + return &NetstackAdapter{ + dev: dev, + tunnelBufPool: sync.Pool{ + New: func() interface{} { + buf := make([][]byte, 1) + return &buf + }, + }, + tunnelSizesPool: sync.Pool{ + New: func() interface{} { + sizes := make([]int, 1) + return &sizes + }, + }, + } +} + +type WaterAdapter struct { + iface *water.Interface +} + +func (w *WaterAdapter) ReadPacket(buf []byte) (int, error) { + n, err := w.iface.Read(buf) + if err != nil { + return 0, err + } + + return n, nil +} + +func (w *WaterAdapter) WritePacket(pkt []byte) error { + _, err := w.iface.Write(pkt) + return err +} + +func NewWaterAdapter(iface *water.Interface) TunnelDevice { + return &WaterAdapter{iface: iface} +} diff --git a/transport/masque/device.go b/transport/masque/device.go new file mode 100644 index 0000000000..d6f7159776 --- /dev/null +++ b/transport/masque/device.go @@ -0,0 +1,33 @@ +package masque + +import ( + "context" + "net/netip" + "time" + + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common/logger" + N "github.com/sagernet/sing/common/network" + wgTun "github.com/sagernet/wireguard-go/tun" +) + +type Device interface { + wgTun.Device + N.Dialer + Start() error +} + +type DeviceOptions struct { + Context context.Context + Logger logger.ContextLogger + Handler tun.Handler + UDPTimeout time.Duration + CreateDialer func(interfaceName string) N.Dialer + Name string + MTU uint32 + Address []netip.Prefix +} + +func NewDevice(options DeviceOptions) (Device, error) { + return newStackDevice(options) +} diff --git a/transport/masque/device_stack.go b/transport/masque/device_stack.go new file mode 100644 index 0000000000..0d926dcaac --- /dev/null +++ b/transport/masque/device_stack.go @@ -0,0 +1,309 @@ +//go:build with_gvisor + +package masque + +import ( + "context" + "net" + "net/netip" + "os" + + "github.com/sagernet/gvisor/pkg/buffer" + "github.com/sagernet/gvisor/pkg/tcpip" + "github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet" + "github.com/sagernet/gvisor/pkg/tcpip/header" + "github.com/sagernet/gvisor/pkg/tcpip/network/ipv4" + "github.com/sagernet/gvisor/pkg/tcpip/network/ipv6" + "github.com/sagernet/gvisor/pkg/tcpip/stack" + "github.com/sagernet/gvisor/pkg/tcpip/transport/icmp" + "github.com/sagernet/gvisor/pkg/tcpip/transport/tcp" + "github.com/sagernet/gvisor/pkg/tcpip/transport/udp" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/transport/wireguard" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + wgTun "github.com/sagernet/wireguard-go/tun" +) + +type stackDevice struct { + ctx context.Context + logger log.ContextLogger + stack *stack.Stack + mtu uint32 + events chan wgTun.Event + wgTun.Device + outbound chan *stack.PacketBuffer + packetOutbound chan *buf.Buffer + done chan struct{} + dispatcher stack.NetworkDispatcher + inet4Address netip.Addr + inet6Address netip.Addr +} + +func newStackDevice(options DeviceOptions) (*stackDevice, error) { + tunDevice := &stackDevice{ + ctx: options.Context, + logger: options.Logger, + mtu: options.MTU, + events: make(chan wgTun.Event, 1), + outbound: make(chan *stack.PacketBuffer, 256), + packetOutbound: make(chan *buf.Buffer, 256), + done: make(chan struct{}), + } + ipStack, err := tun.NewGVisorStackWithOptions((*wireEndpoint)(tunDevice), stack.NICOptions{}, true) + if err != nil { + return nil, err + } + var ( + inet4Address netip.Addr + inet6Address netip.Addr + ) + for _, prefix := range options.Address { + addr := tun.AddressFromAddr(prefix.Addr()) + protoAddr := tcpip.ProtocolAddress{ + AddressWithPrefix: tcpip.AddressWithPrefix{ + Address: addr, + PrefixLen: prefix.Bits(), + }, + } + if prefix.Addr().Is4() { + inet4Address = prefix.Addr() + tunDevice.inet4Address = inet4Address + protoAddr.Protocol = ipv4.ProtocolNumber + } else { + inet6Address = prefix.Addr() + tunDevice.inet6Address = inet6Address + protoAddr.Protocol = ipv6.ProtocolNumber + } + gErr := ipStack.AddProtocolAddress(tun.DefaultNIC, protoAddr, stack.AddressProperties{}) + if gErr != nil { + return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", gErr.String()) + } + } + tunDevice.stack = ipStack + if options.Handler != nil { + ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket) + ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket) + icmpForwarder := tun.NewICMPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout) + ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket) + ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket) + } + return tunDevice, nil +} + +func (w *stackDevice) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + addr := tcpip.FullAddress{ + NIC: tun.DefaultNIC, + Port: destination.Port, + Addr: tun.AddressFromAddr(destination.Addr), + } + bind := tcpip.FullAddress{ + NIC: tun.DefaultNIC, + } + var networkProtocol tcpip.NetworkProtocolNumber + if destination.IsIPv4() { + if !w.inet4Address.IsValid() { + return nil, E.New("missing IPv4 local address") + } + networkProtocol = header.IPv4ProtocolNumber + bind.Addr = tun.AddressFromAddr(w.inet4Address) + } else { + if !w.inet6Address.IsValid() { + return nil, E.New("missing IPv6 local address") + } + networkProtocol = header.IPv6ProtocolNumber + bind.Addr = tun.AddressFromAddr(w.inet6Address) + } + switch N.NetworkName(network) { + case N.NetworkTCP: + tcpConn, err := wireguard.DialTCPWithBind(ctx, w.stack, bind, addr, networkProtocol) + if err != nil { + return nil, err + } + return tcpConn, nil + case N.NetworkUDP: + udpConn, err := gonet.DialUDP(w.stack, &bind, &addr, networkProtocol) + if err != nil { + return nil, err + } + return udpConn, nil + default: + return nil, E.Extend(N.ErrUnknownNetwork, network) + } +} + +func (w *stackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + bind := tcpip.FullAddress{ + NIC: tun.DefaultNIC, + } + var networkProtocol tcpip.NetworkProtocolNumber + if destination.IsIPv4() { + networkProtocol = header.IPv4ProtocolNumber + bind.Addr = tun.AddressFromAddr(w.inet4Address) + } else { + networkProtocol = header.IPv6ProtocolNumber + bind.Addr = tun.AddressFromAddr(w.inet4Address) + } + udpConn, err := gonet.DialUDP(w.stack, &bind, nil, networkProtocol) + if err != nil { + return nil, err + } + return udpConn, nil +} + +func (w *stackDevice) Start() error { + w.events <- wgTun.EventUp + return nil +} + +func (w *stackDevice) File() *os.File { + return nil +} + +func (w *stackDevice) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) { + select { + case packet, ok := <-w.outbound: + if !ok { + return 0, os.ErrClosed + } + defer packet.DecRef() + var copyN int + /*rangeIterate(packet.Data().AsRange(), func(view *buffer.View) { + copyN += copy(bufs[0][offset+copyN:], view.AsSlice()) + })*/ + for _, view := range packet.AsSlices() { + copyN += copy(bufs[0][offset+copyN:], view) + } + sizes[0] = copyN + return 1, nil + case packet := <-w.packetOutbound: + defer packet.Release() + sizes[0] = copy(bufs[0][offset:], packet.Bytes()) + return 1, nil + case <-w.done: + return 0, os.ErrClosed + } +} + +func (w *stackDevice) Write(bufs [][]byte, offset int) (count int, err error) { + for _, b := range bufs { + b = b[offset:] + if len(b) == 0 { + continue + } + var networkProtocol tcpip.NetworkProtocolNumber + switch header.IPVersion(b) { + case header.IPv4Version: + networkProtocol = header.IPv4ProtocolNumber + case header.IPv6Version: + networkProtocol = header.IPv6ProtocolNumber + } + packetBuffer := stack.NewPacketBuffer(stack.PacketBufferOptions{ + Payload: buffer.MakeWithData(b), + }) + w.dispatcher.DeliverNetworkPacket(networkProtocol, packetBuffer) + packetBuffer.DecRef() + count++ + } + return +} + +func (w *stackDevice) Flush() error { + return nil +} + +func (w *stackDevice) MTU() (int, error) { + return int(w.mtu), nil +} + +func (w *stackDevice) Name() (string, error) { + return "sing-box", nil +} + +func (w *stackDevice) Events() <-chan wgTun.Event { + return w.events +} + +func (w *stackDevice) Close() error { + close(w.done) + close(w.events) + w.stack.Close() + for _, endpoint := range w.stack.CleanupEndpoints() { + endpoint.Abort() + } + w.stack.Wait() + return nil +} + +func (w *stackDevice) BatchSize() int { + return 1 +} + +var _ stack.LinkEndpoint = (*wireEndpoint)(nil) + +type wireEndpoint stackDevice + +func (ep *wireEndpoint) MTU() uint32 { + return ep.mtu +} + +func (ep *wireEndpoint) SetMTU(mtu uint32) { +} + +func (ep *wireEndpoint) MaxHeaderLength() uint16 { + return 0 +} + +func (ep *wireEndpoint) LinkAddress() tcpip.LinkAddress { + return "" +} + +func (ep *wireEndpoint) SetLinkAddress(addr tcpip.LinkAddress) { +} + +func (ep *wireEndpoint) Capabilities() stack.LinkEndpointCapabilities { + return stack.CapabilityRXChecksumOffload +} + +func (ep *wireEndpoint) Attach(dispatcher stack.NetworkDispatcher) { + ep.dispatcher = dispatcher +} + +func (ep *wireEndpoint) IsAttached() bool { + return ep.dispatcher != nil +} + +func (ep *wireEndpoint) Wait() { +} + +func (ep *wireEndpoint) ARPHardwareType() header.ARPHardwareType { + return header.ARPHardwareNone +} + +func (ep *wireEndpoint) AddHeader(buffer *stack.PacketBuffer) { +} + +func (ep *wireEndpoint) ParseHeader(ptr *stack.PacketBuffer) bool { + return true +} + +func (ep *wireEndpoint) WritePackets(list stack.PacketBufferList) (int, tcpip.Error) { + for _, packetBuffer := range list.AsSlice() { + packetBuffer.IncRef() + select { + case <-ep.done: + return 0, &tcpip.ErrClosedForSend{} + case ep.outbound <- packetBuffer: + } + } + return list.Len(), nil +} + +func (ep *wireEndpoint) Close() { +} + +func (ep *wireEndpoint) SetOnCloseAction(f func()) { +} diff --git a/transport/masque/device_stack_stub.go b/transport/masque/device_stack_stub.go new file mode 100644 index 0000000000..476a1f58d3 --- /dev/null +++ b/transport/masque/device_stack_stub.go @@ -0,0 +1,13 @@ +//go:build !with_gvisor + +package masque + +import "github.com/sagernet/sing-tun" + +func newStackDevice(options DeviceOptions) (Device, error) { + return nil, tun.ErrGVisorNotIncluded +} + +func newSystemStackDevice(options DeviceOptions) (Device, error) { + return nil, tun.ErrGVisorNotIncluded +} diff --git a/transport/masque/masque.go b/transport/masque/masque.go new file mode 100644 index 0000000000..477eb9d4a8 --- /dev/null +++ b/transport/masque/masque.go @@ -0,0 +1,142 @@ +package masque + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "net" + "net/http" + "net/netip" + "strings" + + connectip "github.com/Diniboy1123/connect-ip-go" + "github.com/sagernet/quic-go" + "github.com/sagernet/quic-go/http3" + qtls "github.com/sagernet/sing-quic" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + aTLS "github.com/sagernet/sing/common/tls" + "github.com/yosida95/uritemplate/v3" + "golang.org/x/net/http2" +) + +type ( + DialContext func(ctx context.Context, network, address string) (net.Conn, error) + ListenPacket func(network string, address string) (net.PacketConn, error) +) + +func ConnectTunnel(ctx context.Context, dialer N.Dialer, tlsConfig aTLS.Config, quicConfig *quic.Config, connectUri string, endpoint net.Addr, useHTTP2 bool) (net.PacketConn, *http3.Transport, *connectip.Conn, *http.Response, error) { + template := uritemplate.MustNew(connectUri) + additionalHeaders := http.Header{ + "User-Agent": []string{""}, + } + if useHTTP2 { + h2Endpoint, ok := endpoint.(*net.TCPAddr) + if !ok || h2Endpoint == nil { + return nil, nil, nil, nil, errors.New("missing HTTP/2 TCP endpoint") + } + h2Headers := additionalHeaders.Clone() + h2Headers.Set("cf-connect-proto", "cf-connect-ip") + h2Headers.Set("pq-enabled", "false") + h2Client, err := newHTTP2Client(dialer, tlsConfig, h2Endpoint, connectUri) + if err != nil { + return nil, nil, nil, nil, fmt.Errorf("failed to create HTTP/2 client: %w", err) + } + ipConn, rsp, err := connectip.DialH2(ctx, h2Client, template, h2Headers) + if err != nil { + if strings.Contains(err.Error(), "tls: access denied") { + return nil, nil, nil, nil, errors.New("login failed! Please double-check if your tls key and cert is enrolled in the Cloudflare Access service") + } + return nil, nil, nil, nil, fmt.Errorf("failed to dial connect-ip over HTTP/2: %w", err) + } + return nil, nil, ipConn, rsp, nil + } + quicEndpoint, ok := endpoint.(*net.UDPAddr) + if !ok || quicEndpoint == nil { + return nil, nil, nil, nil, errors.New("missing HTTP/3 UDP endpoint") + } + udpConn, err := dialer.ListenPacket(ctx, M.SocksaddrFromNetIP(quicEndpoint.AddrPort())) + if err != nil { + return nil, nil, nil, nil, err + } + conn, err := qtls.Dial( + ctx, + udpConn, + quicEndpoint, + tlsConfig, + quicConfig, + ) + if err != nil { + return nil, nil, nil, nil, err + } + tr := &http3.Transport{ + EnableDatagrams: true, + AdditionalSettings: map[uint64]uint64{ + // official client still sends this out as well, even though + // it's deprecated, see https://datatracker.ietf.org/doc/draft-ietf-masque-h3-datagram/00/ + // SETTINGS_H3_DATAGRAM_00 = 0x0000000000000276 + // https://github.com/cloudflare/quiche/blob/7c66757dbc55b8d0c3653d4b345c6785a181f0b7/quiche/src/h3/frame.rs#L46 + 0x276: 1, + }, + DisableCompression: true, + } + hconn := tr.NewClientConn(conn) + ipConn, rsp, err := connectip.Dial(ctx, hconn, template, "cf-connect-ip", additionalHeaders, true) + if err != nil { + _ = tr.Close() + _ = conn.CloseWithError(0, "connect-ip dial failed") + if strings.Contains(err.Error(), "tls: access denied") { + return udpConn, nil, nil, nil, errors.New("login failed! Please double-check if your tls key and cert is enrolled in the Cloudflare Access service") + } + return udpConn, nil, nil, nil, fmt.Errorf("failed to dial connect-ip: %w", err) + } + err = ipConn.AdvertiseRoute(ctx, []connectip.IPRoute{ + { + IPProtocol: 0, + StartIP: netip.AddrFrom4([4]byte{}), + EndIP: netip.AddrFrom4([4]byte{255, 255, 255, 255}), + }, + { + IPProtocol: 0, + StartIP: netip.AddrFrom16([16]byte{}), + EndIP: netip.AddrFrom16([16]byte{ + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + }), + }, + }) + if err != nil { + return udpConn, nil, nil, nil, err + } + return udpConn, tr, ipConn, rsp, nil +} + +func newHTTP2Client(dialer N.Dialer, baseTLSConfig aTLS.Config, endpoint *net.TCPAddr, connectURI string) (*http.Client, error) { + if endpoint == nil { + return nil, errors.New("missing HTTP/2 endpoint") + } + tlsConfig := baseTLSConfig.Clone() + tlsConfig.SetNextProtos([]string{"h2"}) + return &http.Client{ + Transport: &http2.Transport{ + DialTLSContext: func(ctx context.Context, network, _ string, _ *tls.Config) (net.Conn, error) { + conn, err := dialer.DialContext(ctx, network, M.SocksaddrFromNetIP(endpoint.AddrPort())) + if err != nil { + return nil, err + } + tlsConn, err := tlsConfig.Client(conn) + if err != nil { + return nil, err + } + if err := tlsConn.HandshakeContext(ctx); err != nil { + _ = conn.Close() + return nil, err + } + return tlsConn, nil + }, + }, + }, nil +} diff --git a/transport/masque/options.go b/transport/masque/options.go new file mode 100644 index 0000000000..b272243615 --- /dev/null +++ b/transport/masque/options.go @@ -0,0 +1,24 @@ +package masque + +import ( + "net" + "net/netip" + "time" + + tun "github.com/sagernet/sing-tun" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/tls" +) + +type TunnelOptions struct { + Handler tun.Handler + Dialer N.Dialer + Address []netip.Prefix + Endpoint net.Addr + TLSConfig tls.Config + UseHTTP2 bool + UDPTimeout time.Duration + UDPKeepalivePeriod time.Duration + UDPInitialPacketSize uint16 + ReconnectDelay time.Duration +} diff --git a/transport/masque/tunnel.go b/transport/masque/tunnel.go new file mode 100644 index 0000000000..f76e459971 --- /dev/null +++ b/transport/masque/tunnel.go @@ -0,0 +1,235 @@ +package masque + +import ( + "context" + "errors" + "fmt" + "net" + "os" + "sync" + "time" + + connectip "github.com/Diniboy1123/connect-ip-go" + "github.com/sagernet/quic-go/http3" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" +) + +type TunnelDevice interface { + ReadPacket(buf []byte) (int, error) + WritePacket(pkt []byte) error +} + +type Tunnel struct { + ctx context.Context + logger logger.ContextLogger + options TunnelOptions + tunDevice Device + tunnelDevice TunnelDevice + + udpConn net.PacketConn + tr *http3.Transport + ipConn *connectip.Conn + + mtx sync.Mutex +} + +func NewTunnel(ctx context.Context, logger logger.ContextLogger, options TunnelOptions) (*Tunnel, error) { + deviceOptions := DeviceOptions{ + Context: ctx, + Logger: logger, + Handler: options.Handler, + UDPTimeout: options.UDPTimeout, + MTU: 1280, + Address: options.Address, + } + tunDevice, err := NewDevice(deviceOptions) + if err != nil { + return nil, E.Cause(err, "create MASQUE device") + } + return &Tunnel{ + ctx: ctx, + logger: logger, + options: options, + tunDevice: tunDevice, + tunnelDevice: NewNetstackAdapter(tunDevice), + }, nil +} + +func (e *Tunnel) Start(resolve bool) error { + if resolve { + err := e.tunDevice.Start() + if err != nil { + return err + } + go e.maintainTunnel() + } + return nil +} + +func (e *Tunnel) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + if !destination.Addr.IsValid() { + return nil, E.Cause(os.ErrInvalid, "invalid non-IP destination") + } + return e.tunDevice.DialContext(ctx, network, destination) +} + +func (e *Tunnel) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + if !destination.Addr.IsValid() { + return nil, E.Cause(os.ErrInvalid, "invalid non-IP destination") + } + return e.tunDevice.ListenPacket(ctx, destination) +} + +func (e *Tunnel) Close() error { + e.mtx.Lock() + defer e.mtx.Unlock() + if e.ipConn != nil { + e.ipConn.Close() + if e.udpConn != nil { + e.udpConn.Close() + } + if e.tr != nil { + e.tr.Close() + } + e.ipConn = nil + } + return e.tunDevice.Close() +} + +func (e *Tunnel) maintainTunnel() { + go func() { + buf := make([]byte, 1280) + for e.ctx.Err() == nil { + n, err := e.tunnelDevice.ReadPacket(buf) + if err != nil { + e.logger.ErrorContext(e.ctx, fmt.Errorf("failed to read from TUN device: %v", err)) + continue + } + ipConn, err := e.getIpConn() + if err != nil { + return + } + icmp, err := ipConn.WritePacket(buf[:n]) + if err != nil { + if errors.As(err, new(*connectip.CloseError)) { + if ok := e.closeIpConn(ipConn); ok { + e.logger.ErrorContext(e.ctx, fmt.Errorf("connection closed while writing to IP connection: %w", err)) + } + continue + } + e.logger.ErrorContext(e.ctx, fmt.Errorf("Error writing to IP connection: %v, continuing...", err)) + continue + } + if len(icmp) > 0 { + if err := e.tunnelDevice.WritePacket(icmp); err != nil { + if errors.As(err, new(*connectip.CloseError)) { + e.logger.ErrorContext(e.ctx, fmt.Errorf("connection closed while writing ICMP to TUN device: %v", err)) + continue + } + e.logger.ErrorContext(e.ctx, fmt.Errorf("Error writing ICMP to TUN device: %v, continuing...", err)) + } + } + } + }() + go func() { + buf := make([]byte, 1280) + for e.ctx.Err() == nil { + ipConn, err := e.getIpConn() + if err != nil { + return + } + n, err := ipConn.ReadPacket(buf, true) + if err != nil { + if e.options.UseHTTP2 || errors.As(err, new(*connectip.CloseError)) { + if ok := e.closeIpConn(ipConn); ok { + e.logger.ErrorContext(e.ctx, fmt.Errorf("connection closed while reading from IP connection: %v", err)) + } + continue + } + e.logger.ErrorContext(e.ctx, fmt.Errorf("Error reading from IP connection: %v, continuine...", err)) + continue + } + if err := e.tunnelDevice.WritePacket(buf[:n]); err != nil { + continue + } + } + }() + <-e.ctx.Done() +} + +func (e *Tunnel) getIpConn() (*connectip.Conn, error) { + e.mtx.Lock() + defer e.mtx.Unlock() + if e.ctx.Err() != nil { + return nil, e.ctx.Err() + } + if e.ipConn != nil { + return e.ipConn, nil + } + e.logger.InfoContext(e.ctx, "Establishing MASQUE connection to ", e.options.Endpoint) + timer := time.NewTimer(0) + defer timer.Stop() + for { + e.logger.InfoContext(e.ctx, fmt.Errorf("Establishing MASQUE connection to %s", e.options.Endpoint)) + udpConn, tr, ipConn, rsp, err := ConnectTunnel( + e.ctx, + e.options.Dialer, + e.options.TLSConfig, + DefaultQuicConfig(e.options.UDPKeepalivePeriod, e.options.UDPInitialPacketSize), + "https://cloudflareaccess.com", + e.options.Endpoint, + e.options.UseHTTP2, + ) + if err != nil { + e.logger.ErrorContext(e.ctx, fmt.Errorf("Failed to connect tunnel: %v", err)) + timer.Reset(e.options.ReconnectDelay) + select { + case <-e.ctx.Done(): + return nil, err + case <-timer.C: + } + continue + } + if rsp.StatusCode != 200 { + e.logger.ErrorContext(e.ctx, fmt.Errorf("Tunnel connection failed: %s", rsp.Status)) + ipConn.Close() + if udpConn != nil { + udpConn.Close() + } + if tr != nil { + tr.Close() + } + timer.Reset(e.options.ReconnectDelay) + select { + case <-e.ctx.Done(): + return nil, err + case <-timer.C: + } + continue + } + e.udpConn = udpConn + e.tr = tr + e.ipConn = ipConn + e.logger.InfoContext(e.ctx, "Connected to MASQUE server", e.options.Endpoint) + return ipConn, nil + } +} + +func (e *Tunnel) closeIpConn(ipConn *connectip.Conn) bool { + e.mtx.Lock() + defer e.mtx.Unlock() + if ipConn == e.ipConn { + e.ipConn.Close() + if e.udpConn != nil { + e.udpConn.Close() + } + if e.tr != nil { + e.tr.Close() + } + e.ipConn = nil + return true + } + return false +} diff --git a/transport/masque/utils.go b/transport/masque/utils.go new file mode 100644 index 0000000000..b99b445985 --- /dev/null +++ b/transport/masque/utils.go @@ -0,0 +1,326 @@ +package masque + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "encoding/base64" + "encoding/hex" + "errors" + "log" + "math/big" + "net" + "strconv" + "strings" + "time" + + "github.com/sagernet/quic-go" +) + +// PortMapping represents a network port forwarding rule. +type PortMapping struct { + BindAddress string // The address to bind the local port. + LocalPort int // The local port number. + RemoteIP string // The remote destination IP address. + RemotePort int // The remote destination port number. +} + +// GenerateRandomAndroidSerial generates a random 8-byte Android-like device identifier +// and returns it as a hexadecimal string. +// +// Returns: +// - string: A randomly generated 16-character hexadecimal serial number. +// - error: An error if random data generation fails. +func GenerateRandomAndroidSerial() (string, error) { + serial := make([]byte, 8) + if _, err := rand.Read(serial); err != nil { + return "", err + } + return hex.EncodeToString(serial), nil +} + +// GenerateRandomWgPubkey generates a random 32-byte WireGuard like public key +// and returns it as a base64-encoded string. +// +// Returns: +// - string: A randomly generated WireGuard like public key in base64 format. +// - error: An error if random data generation fails. +func GenerateRandomWgPubkey() (string, error) { + publicKey := make([]byte, 32) + if _, err := rand.Read(publicKey); err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(publicKey), nil +} + +// TimeAsCfString formats a given time.Time into a Cloudflare-compatible string format. +// +// The format follows the standard: "YYYY-MM-DDTHH:MM:SS.sss-07:00". +// +// Parameters: +// - t: time.Time to format. +// +// Returns: +// - string: The formatted time string. +func TimeAsCfString(t time.Time) string { + return t.Format("2006-01-02T15:04:05.000-07:00") +} + +// GenerateEcKeyPair generates a new ECDSA key pair using the P-256 curve. +// +// Returns: +// - []byte: The marshalled private key in ASN.1 DER format. +// - []byte: The marshalled public key in PKIX format. +// - error: An error if key generation or marshalling fails. +func GenerateEcKeyPair() ([]byte, []byte, error) { + privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, nil, err + } + + marshalledPrivKey, err := x509.MarshalECPrivateKey(privKey) + if err != nil { + return nil, nil, err + } + + marshalledPubKey, err := x509.MarshalPKIXPublicKey(&privKey.PublicKey) + if err != nil { + return nil, nil, err + } + + return marshalledPrivKey, marshalledPubKey, nil +} + +// GenerateCert creates a self-signed certificate using the provided ECDSA private and public keys. +// +// The certificate is valid for 24 hours. +// +// Parameters: +// - privKey: *ecdsa.PrivateKey - The private key to sign the certificate. +// - pubKey: *ecdsa.PublicKey - The public key to include in the certificate. +// +// Returns: +// - [][]byte: A slice containing the certificate in DER format. +// - error: An error if certificate generation fails. +func GenerateCert(privKey *ecdsa.PrivateKey, pubKey *ecdsa.PublicKey) ([][]byte, error) { + cert, err := x509.CreateCertificate(rand.Reader, &x509.Certificate{ + SerialNumber: big.NewInt(0), + NotBefore: time.Now(), + NotAfter: time.Now().Add(1 * 24 * time.Hour), + }, &x509.Certificate{}, &privKey.PublicKey, privKey) + if err != nil { + return nil, err + } + + return [][]byte{cert}, nil +} + +// DefaultQuicConfig returns a MASQUE-compatible default QUIC configuration. +// +// When initialPacketSize is 0, Path MTU Discovery remains enabled. +// +// Parameters: +// - keepalivePeriod: time.Duration - The duration for sending QUIC keep-alive packets. +// - initialPacketSize: uint16 - The custom initial size of QUIC packets (0 = auto with PMTU discovery). +// +// Returns: +// - *quic.Config: A pointer to a configured QUIC configuration object. +func DefaultQuicConfig(keepalivePeriod time.Duration, initialPacketSize uint16) *quic.Config { + cfg := &quic.Config{ + EnableDatagrams: true, + KeepAlivePeriod: keepalivePeriod, + } + + if initialPacketSize > 0 { + cfg.InitialPacketSize = initialPacketSize + cfg.DisablePathMTUDiscovery = true + } + + return cfg +} + +// parsePortMapping is an internal helper function that parses a port mapping string into its components. +// +// It handles IPv6 addresses enclosed in brackets and various format edge cases. +// +// Parameters: +// - port: string - The port mapping string. +// +// Returns: +// - string: The bind address. +// - int: The local port. +// - string: The remote hostname/IP. +// - int: The remote port. +// - error: An error if parsing fails. +func parsePortMapping(port string) (bindAddress string, localPort int, remoteHost string, remotePort int, err error) { + parts := strings.Split(port, ":") + + // Handle IPv6 addresses (which are enclosed in brackets) + if len(parts) >= 4 && strings.HasPrefix(parts[0], "[") && strings.Contains(parts[0], "]") { + bindAddress = parts[0] + parts = parts[1:] // Shift parts forward + } else if len(parts) == 3 { + bindAddress = "localhost" // Default to localhost + } else if len(parts) == 4 { + bindAddress = parts[0] + parts = parts[1:] // Shift forward + } else { + return "", 0, "", 0, errors.New("invalid port mapping format (expected format: [bind_address:]local_port:remote_host:remote_port)") + } + + // Parse local port + localPort, err = strconv.Atoi(parts[0]) + if err != nil || localPort <= 0 || localPort > 65535 { + return "", 0, "", 0, errors.New("invalid local port") + } + + // Validate remote host (allow both hostnames and IPs) + remoteHost = parts[1] + if net.ParseIP(remoteHost) == nil && !isValidHostname(remoteHost) { + return "", 0, "", 0, errors.New("invalid remote hostname/IP") + } + + // Parse remote port + remotePort, err = strconv.Atoi(parts[2]) + if err != nil || remotePort <= 0 || remotePort > 65535 { + return "", 0, "", 0, errors.New("invalid remote port") + } + + // If bindAddress is an IPv6 address, remove brackets for proper binding + if strings.HasPrefix(bindAddress, "[") && strings.HasSuffix(bindAddress, "]") { + bindAddress = strings.Trim(bindAddress, "[]") + } + + // Convert "localhost" or hostnames to actual addresses + if bindAddress == "*" { + bindAddress = "0.0.0.0" // Allow all interfaces + } + + // Validate bind address (support both IPs and hostnames) + bindAddress, err = resolveBindAddress(bindAddress) + if err != nil { + return "", 0, "", 0, errors.New("invalid local address: " + err.Error()) + } + + remoteHost, err = resolveBindAddress(remoteHost) + if err != nil { + return "", 0, "", 0, errors.New("invalid remote address: " + err.Error()) + } + + return bindAddress, localPort, remoteHost, remotePort, nil +} + +// ParsePortMapping parses a port mapping string into a structured PortMapping. +// +// The expected format is: `[bind_address:]local_port:remote_host:remote_port`. +// +// Parameters: +// - port: string - The port mapping string. +// +// Returns: +// - PortMapping: A structured representation of the parsed port mapping. +// - error: An error if the parsing fails. +func ParsePortMapping(port string) (PortMapping, error) { + bindAddress, localPort, remoteHost, remotePort, err := parsePortMapping(port) + if err != nil { + return PortMapping{}, err + } + + return PortMapping{ + BindAddress: bindAddress, + LocalPort: localPort, + RemoteIP: remoteHost, + RemotePort: remotePort, + }, nil +} + +// resolveBindAddress resolves a hostname or IP to its string representation. +// +// Parameters: +// - addr: string - The hostname or IP. +// +// Returns: +// - string: The resolved IP address. +// - error: An error if resolution fails. +func resolveBindAddress(addr string) (string, error) { + tcpAddr, err := net.ResolveTCPAddr("tcp", addr+":0") // Resolve the address + if err != nil { + return "", err + } + return tcpAddr.IP.String(), nil // Return resolved IP +} + +// isValidHostname checks if a given hostname is valid. +// Pretty ugly for now, needs to be refactored. +// +// Parameters: +// - hostname: string - The hostname to validate. +// +// Returns: +// - bool: True if valid, false otherwise. +func isValidHostname(hostname string) bool { + // Must contain at least one dot (.) unless it's "localhost" + if hostname == "localhost" { + return true + } + return strings.Contains(hostname, ".") +} + +// LoginToBase64 encodes a username and password into a base64-encoded string in "username:password" format. +// This is commonly used for HTTP Basic Authentication. +// +// Parameters: +// - username: string - The username to encode. +// - password: string - The password to encode. +// +// Returns: +// - string: The base64-encoded "username:password" string. +func LoginToBase64(username, password string) string { + return base64.StdEncoding.EncodeToString([]byte(username + ":" + password)) +} + +// CheckIfname validates a network interface name according to the following rules: +// - Must not be empty. +// - Should not exceed 15 characters (warning if it does). +// - Should not contain non-ASCII characters (warning if it does). +// - Should not contain invalid characters: '/', whitespace, or control characters. +// +// Parameters: +// - name: string - The interface name to validate. +// +// Returns: +// - error: An error if the name is invalid, or nil if valid. +func CheckIfname(name string) error { + if name == "" { + return errors.New("interface name cannot be empty") + } + + if len(name) >= 16 { + log.Printf("Warning: interface name '%s' is longer than %d characters", name, 16-1) + } + + var invalidChar bool + var hasWhitespace bool + + for _, r := range name { + if r > 127 { + invalidChar = true + break + } + if r == '/' || r == ' ' || strings.ContainsRune("\t\n\v\f\r", r) { + hasWhitespace = true + break + } + } + + if invalidChar { + log.Printf("Warning: interface name contains non-ASCII character") + } + + if hasWhitespace { + return errors.New("interface name contains invalid character: '/' or whitespace") + } + + return nil +} diff --git a/transport/simple-obfs/http.go b/transport/simple-obfs/http.go index f77a63a8ef..df38768ec5 100644 --- a/transport/simple-obfs/http.go +++ b/transport/simple-obfs/http.go @@ -82,6 +82,10 @@ func (ho *HTTPObfs) Write(b []byte) (int, error) { return ho.Conn.Write(b) } +func (ho *HTTPObfs) Upstream() any { + return ho.Conn +} + // NewHTTPObfs return a HTTPObfs func NewHTTPObfs(conn net.Conn, host string, port string) net.Conn { return &HTTPObfs{ diff --git a/transport/simple-obfs/http_server.go b/transport/simple-obfs/http_server.go new file mode 100644 index 0000000000..86f8ef04ef --- /dev/null +++ b/transport/simple-obfs/http_server.go @@ -0,0 +1,104 @@ +package obfs + +import ( + "bufio" + "crypto/rand" + "encoding/base64" + "fmt" + "io" + "net" + "net/http" + "time" +) + +// HTTPObfsServer is the server-side simple-obfs HTTP implementation. +// It strips the client's HTTP upgrade request and replies with HTTP 101. +type HTTPObfsServer struct { + net.Conn + buf []byte + bio *bufio.Reader + offset int + firstRequest bool + firstResponse bool +} + +func (hos *HTTPObfsServer) Read(b []byte) (int, error) { + if hos.buf != nil { + n := copy(b, hos.buf[hos.offset:]) + hos.offset += n + if hos.offset == len(hos.buf) { + hos.offset = 0 + hos.buf = nil + } + return n, nil + } + + if hos.firstRequest { + bio := bufio.NewReader(hos.Conn) + req, err := http.ReadRequest(bio) + if err != nil { + return 0, err + } + if req.Method != "GET" || req.Header.Get("Connection") != "Upgrade" { + return 0, io.EOF + } + + body, err := io.ReadAll(req.Body) + req.Body.Close() + if err != nil { + return 0, err + } + n := copy(b, body) + if n < len(body) { + hos.buf = body + hos.offset = n + } + hos.bio = bio + hos.firstRequest = false + return n, nil + } + + return hos.bio.Read(b) +} + +const httpResponseTemplate = "HTTP/1.1 101 Switching Protocols\r\n" + + "Server: nginx/1.%d.%d\r\n" + + "Date: %s\r\n" + + "Upgrade: websocket\r\n" + + "Connection: Upgrade\r\n" + + "Sec-WebSocket-Accept: %s\r\n" + + "\r\n" + +func (hos *HTTPObfsServer) Write(b []byte) (int, error) { + if hos.firstResponse { + randBytes := make([]byte, 16) + rand.Read(randBytes) + date := time.Now().Format(time.RFC1123) + resp := fmt.Sprintf(httpResponseTemplate, randInt()%11, randInt()%12, date, base64.URLEncoding.EncodeToString(randBytes)) + if _, err := hos.Conn.Write([]byte(resp)); err != nil { + return 0, err + } + hos.firstResponse = false + } + return hos.Conn.Write(b) +} + +func (hos *HTTPObfsServer) Upstream() any { + return hos.Conn +} + +// NewHTTPObfsServer wraps conn with server-side HTTP obfs. +func NewHTTPObfsServer(conn net.Conn) net.Conn { + return &HTTPObfsServer{ + Conn: conn, + firstRequest: true, + firstResponse: true, + } +} + +// randInt returns a pseudo-random non-negative integer. +func randInt() int { + b := make([]byte, 1) + rand.Read(b) + return int(b[0]) +} diff --git a/transport/simple-obfs/tls.go b/transport/simple-obfs/tls.go index 51756fdb6f..9656481532 100644 --- a/transport/simple-obfs/tls.go +++ b/transport/simple-obfs/tls.go @@ -113,6 +113,10 @@ func (to *TLSObfs) write(b []byte) (int, error) { return len(b), err } +func (to *TLSObfs) Upstream() any { + return to.Conn +} + // NewTLSObfs return a SimpleObfs func NewTLSObfs(conn net.Conn, server string) net.Conn { return &TLSObfs{ diff --git a/transport/simple-obfs/tls_server.go b/transport/simple-obfs/tls_server.go new file mode 100644 index 0000000000..9c30cf9682 --- /dev/null +++ b/transport/simple-obfs/tls_server.go @@ -0,0 +1,166 @@ +package obfs + +import ( + "bytes" + "crypto/rand" + "encoding/binary" + "io" + "net" + "time" + + B "github.com/sagernet/sing/common/buf" +) + +// TLSObfsServer is the server-side simple-obfs TLS implementation. +// It reads the client's fake ClientHello and writes a fake ServerHello. +type TLSObfsServer struct { + net.Conn + remain int + firstRequest bool + sessionTicketDone bool + firstResponse bool +} + +func (tos *TLSObfsServer) read(b []byte, discardN int) (int, error) { + discard := B.Get(discardN) + _, err := io.ReadFull(tos.Conn, discard) + B.Put(discard) + if err != nil { + return 0, err + } + + sizeBuf := make([]byte, 2) + _, err = io.ReadFull(tos.Conn, sizeBuf) + if err != nil { + return 0, nil + } + + length := int(binary.BigEndian.Uint16(sizeBuf)) + if length > len(b) { + n, err := tos.Conn.Read(b) + if err != nil { + return n, err + } + tos.remain = length - n + return n, nil + } + + return io.ReadFull(tos.Conn, b[:length]) +} + +// skipOtherExts discards SNI and other TLS extensions after session ticket. +func (tos *TLSObfsServer) skipOtherExts() error { + buf := make([]byte, 256) + _, err := tos.read(buf, 7) + if err != nil { + return err + } + _, err = io.ReadFull(tos.Conn, buf[:4*16+2]) + return err +} + +func (tos *TLSObfsServer) Read(b []byte) (int, error) { + if tos.remain > 0 { + length := tos.remain + if length > len(b) { + length = len(b) + } + n, err := io.ReadFull(tos.Conn, b[:length]) + tos.remain -= n + return n, err + } + + if tos.firstRequest { + tos.firstRequest = false + // Skip: record header(5) + handshake header(4) + TLS version(2) + timestamp(4) + random(28) + sid_len(1) + sid(32) + cipher_suites_len(2) + ciphers(56) + compress(2) = 9*16-4 = 140 bytes + return tos.read(b, 9*16-4) + } + + if !tos.sessionTicketDone { + tos.sessionTicketDone = true + if err := tos.skipOtherExts(); err != nil { + return 0, err + } + } + + return tos.read(b, 3) +} + +func (tos *TLSObfsServer) Write(b []byte) (int, error) { + length := len(b) + for i := 0; i < length; i += chunkSize { + end := i + chunkSize + if end > length { + end = length + } + if _, err := tos.write(b[i:end]); err != nil { + return i, err + } + } + return length, nil +} + +func (tos *TLSObfsServer) write(b []byte) (int, error) { + if tos.firstResponse { + serverHello := makeTLSServerHello(b) + _, err := tos.Conn.Write(serverHello) + tos.firstResponse = false + return len(b), err + } + + buf := B.NewSize(5 + len(b)) + defer buf.Release() + buf.Write([]byte{0x17, 0x03, 0x03}) + binary.Write(buf, binary.BigEndian, uint16(len(b))) + buf.Write(b) + _, err := tos.Conn.Write(buf.Bytes()) + return len(b), err +} + +func (tos *TLSObfsServer) Upstream() any { + return tos.Conn +} + +// NewTLSObfsServer wraps conn with server-side TLS obfs. +func NewTLSObfsServer(conn net.Conn) net.Conn { + return &TLSObfsServer{ + Conn: conn, + firstRequest: true, + firstResponse: true, + } +} + +func makeTLSServerHello(data []byte) []byte { + randBytes := make([]byte, 28) + sessionID := make([]byte, 32) + rand.Read(randBytes) + rand.Read(sessionID) + + buf := &bytes.Buffer{} + + // ServerHello record + buf.WriteByte(0x16) + binary.Write(buf, binary.BigEndian, uint16(0x0301)) + binary.Write(buf, binary.BigEndian, uint16(91)) + buf.Write([]byte{2, 0, 0, 87, 0x03, 0x03}) + binary.Write(buf, binary.BigEndian, uint32(time.Now().Unix())) + buf.Write(randBytes) + buf.WriteByte(32) + buf.Write(sessionID) + buf.Write([]byte{0xcc, 0xa8}) + buf.WriteByte(0) + buf.Write([]byte{0x00, 0x00}) + buf.Write([]byte{0xff, 0x01, 0x00, 0x01, 0x00}) + buf.Write([]byte{0x00, 0x17, 0x00, 0x00}) + buf.Write([]byte{0x00, 0x0b, 0x00, 0x02, 0x01, 0x00}) + + // ChangeCipherSpec + buf.Write([]byte{0x14, 0x03, 0x03, 0x00, 0x01, 0x01}) + + // Finished (carrying the first payload) + buf.Write([]byte{0x16, 0x03, 0x03}) + binary.Write(buf, binary.BigEndian, uint16(len(data))) + buf.Write(data) + + return buf.Bytes() +} diff --git a/transport/sip003/args.go b/transport/sip003/args.go index b9fae3dab9..de6113f71b 100644 --- a/transport/sip003/args.go +++ b/transport/sip003/args.go @@ -105,15 +105,3 @@ func ParsePluginOptions(s string) (opts Args, err error) { } return opts, nil } - -// Escape backslashes and all the bytes that are in set. -func backslashEscape(s string, set []byte) string { - var buf bytes.Buffer - for _, b := range []byte(s) { - if b == '\\' || bytes.IndexByte(set, b) != -1 { - buf.WriteByte('\\') - } - buf.WriteByte(b) - } - return buf.String() -} diff --git a/transport/sip003/v2ray.go b/transport/sip003/v2ray.go index c142180bbf..f35e265493 100644 --- a/transport/sip003/v2ray.go +++ b/transport/sip003/v2ray.go @@ -12,6 +12,8 @@ import ( "github.com/sagernet/sing-box/transport/v2ray" "github.com/sagernet/sing-vmess" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json/badoption" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) @@ -54,7 +56,7 @@ func newV2RayPlugin(ctx context.Context, pluginOpts Args, router adapter.Router, var tlsClient tls.Config var err error if tlsOptions.Enabled { - tlsClient, err = tls.NewClient(ctx, serverAddr.AddrString(), tlsOptions) + tlsClient, err = tls.NewClient(ctx, logger.NOP(), serverAddr.AddrString(), tlsOptions) if err != nil { return nil, err } @@ -67,7 +69,7 @@ func newV2RayPlugin(ctx context.Context, pluginOpts Args, router adapter.Router, transportOptions = option.V2RayTransportOptions{ Type: C.V2RayTransportTypeWebsocket, WebsocketOptions: option.V2RayWebsocketOptions{ - Headers: map[string]option.Listable[string]{ + Headers: map[string]badoption.Listable[string]{ "Host": []string{host}, }, Path: path, diff --git a/transport/trojan/mux.go b/transport/trojan/mux.go index 13ac1e83a9..72d5a77621 100644 --- a/transport/trojan/mux.go +++ b/transport/trojan/mux.go @@ -1,17 +1,22 @@ package trojan import ( + std_bufio "bufio" "context" "net" + "os" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" - "github.com/sagernet/sing/common/rw" + N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/task" "github.com/sagernet/smux" ) -func HandleMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata, handler Handler) error { +func HandleMuxConnection(ctx context.Context, conn net.Conn, source M.Socksaddr, handler Handler, logger logger.ContextLogger, onClose N.CloseHandlerFunc) error { session, err := smux.Server(conn, smuxConfig()) if err != nil { return err @@ -24,39 +29,52 @@ func HandleMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata if err != nil { return err } - go newMuxConnection(ctx, stream, metadata, handler) + go newMuxConnection(ctx, stream, source, handler, logger) } }) group.Cleanup(func() { session.Close() + if onClose != nil { + onClose(os.ErrClosed) + } }) return group.Run(ctx) } -func newMuxConnection(ctx context.Context, stream net.Conn, metadata M.Metadata, handler Handler) { - err := newMuxConnection0(ctx, stream, metadata, handler) +func newMuxConnection(ctx context.Context, conn net.Conn, source M.Socksaddr, handler Handler, logger logger.ContextLogger) { + err := newMuxConnection0(ctx, conn, source, handler) if err != nil { - handler.NewError(ctx, E.Cause(err, "process trojan-go multiplex connection")) + logger.ErrorContext(ctx, E.Cause(err, "process trojan-go multiplex connection")) } } -func newMuxConnection0(ctx context.Context, stream net.Conn, metadata M.Metadata, handler Handler) error { - command, err := rw.ReadByte(stream) +func newMuxConnection0(ctx context.Context, conn net.Conn, source M.Socksaddr, handler Handler) error { + reader := std_bufio.NewReader(conn) + command, err := reader.ReadByte() if err != nil { return E.Cause(err, "read command") } - metadata.Destination, err = M.SocksaddrSerializer.ReadAddrPort(stream) + destination, err := M.SocksaddrSerializer.ReadAddrPort(reader) if err != nil { return E.Cause(err, "read destination") } + if reader.Buffered() > 0 { + buffer := buf.NewSize(reader.Buffered()) + _, err = buffer.ReadFullFrom(reader, buffer.Len()) + if err != nil { + return err + } + conn = bufio.NewCachedConn(conn, buffer) + } switch command { case CommandTCP: - return handler.NewConnection(ctx, stream, metadata) + handler.NewConnectionEx(ctx, conn, source, destination, nil) case CommandUDP: - return handler.NewPacketConnection(ctx, &PacketConn{Conn: stream}, metadata) + handler.NewPacketConnectionEx(ctx, &PacketConn{Conn: conn}, source, destination, nil) default: return E.New("unknown command ", command) } + return nil } func smuxConfig() *smux.Config { diff --git a/transport/trojan/protocol.go b/transport/trojan/protocol.go index 394ba291a1..0456b6b90b 100644 --- a/transport/trojan/protocol.go +++ b/transport/trojan/protocol.go @@ -26,7 +26,7 @@ const ( var CRLF = []byte{'\r', '\n'} -var _ N.EarlyConn = (*ClientConn)(nil) +var _ N.EarlyWriter = (*ClientConn)(nil) type ClientConn struct { N.ExtendedConn @@ -43,7 +43,7 @@ func NewClientConn(conn net.Conn, key [KeyLength]byte, destination M.Socksaddr) } } -func (c *ClientConn) NeedHandshake() bool { +func (c *ClientConn) NeedHandshakeForWrite() bool { return !c.headerWritten } @@ -83,6 +83,14 @@ func (c *ClientConn) Upstream() any { return c.ExtendedConn } +func (c *ClientConn) ReaderReplaceable() bool { + return c.headerWritten +} + +func (c *ClientConn) WriterReplaceable() bool { + return c.headerWritten +} + type ClientPacketConn struct { net.Conn access sync.Mutex @@ -128,7 +136,7 @@ func (c *ClientPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) return } n = buffer.Len() - if destination.IsFqdn() { + if destination.IsDomain() { addr = destination } else { addr = destination.UDPAddr() @@ -292,7 +300,7 @@ func ReadPacket(conn net.Conn, buffer *buf.Buffer) (M.Socksaddr, error) { } _, err = buffer.ReadFullFrom(conn, int(length)) - return destination.Unwrap(), err + return destination, err } func WritePacket(conn net.Conn, buffer *buf.Buffer, destination M.Socksaddr) error { diff --git a/transport/trojan/service.go b/transport/trojan/service.go index 9078276c73..7f1803bb38 100644 --- a/transport/trojan/service.go +++ b/transport/trojan/service.go @@ -2,36 +2,39 @@ package trojan import ( "context" + "encoding/binary" "net" "github.com/sagernet/sing/common/auth" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/rw" ) type Handler interface { - N.TCPConnectionHandler - N.UDPConnectionHandler - E.Handler + N.TCPConnectionHandlerEx + N.UDPConnectionHandlerEx } type Service[K comparable] struct { users map[K][56]byte keys map[[56]byte]K handler Handler - fallbackHandler N.TCPConnectionHandler + fallbackHandler N.TCPConnectionHandlerEx + logger logger.ContextLogger } -func NewService[K comparable](handler Handler, fallbackHandler N.TCPConnectionHandler) *Service[K] { +func NewService[K comparable](handler Handler, fallbackHandler N.TCPConnectionHandlerEx, logger logger.ContextLogger) *Service[K] { return &Service[K]{ users: make(map[K][56]byte), keys: make(map[[56]byte]K), handler: handler, fallbackHandler: fallbackHandler, + logger: logger, } } @@ -56,19 +59,19 @@ func (s *Service[K]) UpdateUsers(userList []K, passwordList []string) error { return nil } -func (s *Service[K]) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { +func (s *Service[K]) NewConnection(ctx context.Context, conn net.Conn, source M.Socksaddr, onClose N.CloseHandlerFunc) error { var key [KeyLength]byte n, err := conn.Read(key[:]) if err != nil { return err } else if n != KeyLength { - return s.fallback(ctx, conn, metadata, key[:n], E.New("bad request size")) + return s.fallback(ctx, conn, source, key[:n], E.New("bad request size"), onClose) } if user, loaded := s.keys[key]; loaded { ctx = auth.ContextWithUser(ctx, user) } else { - return s.fallback(ctx, conn, metadata, key[:], E.New("bad request")) + return s.fallback(ctx, conn, source, key[:], E.New("bad request"), onClose) } err = rw.SkipN(conn, 2) @@ -76,7 +79,8 @@ func (s *Service[K]) NewConnection(ctx context.Context, conn net.Conn, metadata return E.Cause(err, "skip crlf") } - command, err := rw.ReadByte(conn) + var command byte + err = binary.Read(conn, binary.BigEndian, &command) if err != nil { return E.Cause(err, "read command") } @@ -98,26 +102,25 @@ func (s *Service[K]) NewConnection(ctx context.Context, conn net.Conn, metadata return E.Cause(err, "skip crlf") } - metadata.Protocol = "trojan" - metadata.Destination = destination - switch command { case CommandTCP: - return s.handler.NewConnection(ctx, conn, metadata) + s.handler.NewConnectionEx(ctx, conn, source, destination, onClose) case CommandUDP: - return s.handler.NewPacketConnection(ctx, &PacketConn{Conn: conn}, metadata) + s.handler.NewPacketConnectionEx(ctx, &PacketConn{Conn: conn}, source, destination, onClose) // case CommandMux: default: - return HandleMuxConnection(ctx, conn, metadata, s.handler) + return HandleMuxConnection(ctx, conn, source, s.handler, s.logger, onClose) } + return nil } -func (s *Service[K]) fallback(ctx context.Context, conn net.Conn, metadata M.Metadata, header []byte, err error) error { +func (s *Service[K]) fallback(ctx context.Context, conn net.Conn, source M.Socksaddr, header []byte, err error, onClose N.CloseHandlerFunc) error { if s.fallbackHandler == nil { return E.Extend(err, "fallback disabled") } conn = bufio.NewCachedConn(conn, buf.As(header).ToOwned()) - return s.fallbackHandler.NewConnection(ctx, conn, metadata) + s.fallbackHandler.NewConnectionEx(ctx, conn, source, M.Socksaddr{}, onClose) + return nil } type PacketConn struct { diff --git a/transport/v2ray/grpc.go b/transport/v2ray/grpc.go index 05bc5a2a4e..1b4250addf 100644 --- a/transport/v2ray/grpc.go +++ b/transport/v2ray/grpc.go @@ -10,15 +10,16 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/transport/v2raygrpc" "github.com/sagernet/sing-box/transport/v2raygrpclite" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) -func NewGRPCServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { +func NewGRPCServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { if options.ForceLite { - return v2raygrpclite.NewServer(ctx, options, tlsConfig, handler) + return v2raygrpclite.NewServer(ctx, logger, options, tlsConfig, handler) } - return v2raygrpc.NewServer(ctx, options, tlsConfig, handler) + return v2raygrpc.NewServer(ctx, logger, options, tlsConfig, handler) } func NewGRPCClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayGRPCOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) { diff --git a/transport/v2ray/grpc_lite.go b/transport/v2ray/grpc_lite.go index 94f6fad13d..4f2814a726 100644 --- a/transport/v2ray/grpc_lite.go +++ b/transport/v2ray/grpc_lite.go @@ -9,12 +9,13 @@ import ( "github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/transport/v2raygrpclite" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) -func NewGRPCServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { - return v2raygrpclite.NewServer(ctx, options, tlsConfig, handler) +func NewGRPCServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { + return v2raygrpclite.NewServer(ctx, logger, options, tlsConfig, handler) } func NewGRPCClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayGRPCOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) { diff --git a/transport/v2ray/quic.go b/transport/v2ray/quic.go index 5471157aff..4d3cdc6f8f 100644 --- a/transport/v2ray/quic.go +++ b/transport/v2ray/quic.go @@ -7,6 +7,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) @@ -21,11 +22,11 @@ func RegisterQUICConstructor(server ServerConstructor[option.V2RayQUICOptions], quicClientConstructor = client } -func NewQUICServer(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { +func NewQUICServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { if quicServerConstructor == nil { return nil, os.ErrInvalid } - return quicServerConstructor(ctx, options, tlsConfig, handler) + return quicServerConstructor(ctx, logger, options, tlsConfig, handler) } func NewQUICClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayQUICOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) { diff --git a/transport/v2ray/transport.go b/transport/v2ray/transport.go index deb8a7f0ee..0a9438cbe2 100644 --- a/transport/v2ray/transport.go +++ b/transport/v2ray/transport.go @@ -10,34 +10,38 @@ import ( "github.com/sagernet/sing-box/transport/v2rayhttp" "github.com/sagernet/sing-box/transport/v2rayhttpupgrade" "github.com/sagernet/sing-box/transport/v2raywebsocket" + xhttp "github.com/sagernet/sing-box/transport/v2rayxhttp" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) type ( - ServerConstructor[O any] func(ctx context.Context, options O, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) + ServerConstructor[O any] func(ctx context.Context, logger logger.ContextLogger, options O, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) ClientConstructor[O any] func(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options O, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) ) -func NewServerTransport(ctx context.Context, options option.V2RayTransportOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { +func NewServerTransport(ctx context.Context, logger logger.ContextLogger, options option.V2RayTransportOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { if options.Type == "" { return nil, nil } switch options.Type { case C.V2RayTransportTypeHTTP: - return v2rayhttp.NewServer(ctx, options.HTTPOptions, tlsConfig, handler) + return v2rayhttp.NewServer(ctx, logger, options.HTTPOptions, tlsConfig, handler) case C.V2RayTransportTypeWebsocket: - return v2raywebsocket.NewServer(ctx, options.WebsocketOptions, tlsConfig, handler) + return v2raywebsocket.NewServer(ctx, logger, options.WebsocketOptions, tlsConfig, handler) case C.V2RayTransportTypeQUIC: if tlsConfig == nil { return nil, C.ErrTLSRequired } - return NewQUICServer(ctx, options.QUICOptions, tlsConfig, handler) + return NewQUICServer(ctx, logger, options.QUICOptions, tlsConfig, handler) case C.V2RayTransportTypeGRPC: - return NewGRPCServer(ctx, options.GRPCOptions, tlsConfig, handler) + return NewGRPCServer(ctx, logger, options.GRPCOptions, tlsConfig, handler) case C.V2RayTransportTypeHTTPUpgrade: - return v2rayhttpupgrade.NewServer(ctx, options.HTTPUpgradeOptions, tlsConfig, handler) + return v2rayhttpupgrade.NewServer(ctx, logger, options.HTTPUpgradeOptions, tlsConfig, handler) + case C.V2RayTransportTypeXHTTP: + return xhttp.NewServer(ctx, logger, options.XHTTPOptions, tlsConfig, handler) default: return nil, E.New("unknown transport type: " + options.Type) } @@ -48,6 +52,7 @@ func NewClientTransport(ctx context.Context, dialer N.Dialer, serverAddr M.Socks return nil, nil } switch options.Type { + case C.V2RayTransportTypeHTTP: return v2rayhttp.NewClient(ctx, dialer, serverAddr, options.HTTPOptions, tlsConfig) case C.V2RayTransportTypeGRPC: @@ -61,7 +66,9 @@ func NewClientTransport(ctx context.Context, dialer N.Dialer, serverAddr M.Socks return NewQUICClient(ctx, dialer, serverAddr, options.QUICOptions, tlsConfig) case C.V2RayTransportTypeHTTPUpgrade: return v2rayhttpupgrade.NewClient(ctx, dialer, serverAddr, options.HTTPUpgradeOptions, tlsConfig) + case C.V2RayTransportTypeXHTTP: + return xhttp.NewClient(ctx, dialer, serverAddr, options.XHTTPOptions, tlsConfig) default: - return nil, E.New("unknown transport type: " + options.Type) + return nil, E.New("unknown client transport type: " + options.Type) } } diff --git a/transport/v2raygrpc/client.go b/transport/v2raygrpc/client.go index 1e72040a15..a915e1eb34 100644 --- a/transport/v2raygrpc/client.go +++ b/transport/v2raygrpc/client.go @@ -4,12 +4,12 @@ import ( "context" "net" "sync" + "sync/atomic" "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" @@ -29,7 +29,7 @@ type Client struct { serverAddr string serviceName string dialOptions []grpc.DialOption - conn *grpc.ClientConn + conn atomic.Pointer[grpc.ClientConn] connAccess sync.Mutex } @@ -62,6 +62,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt dialOptions = append(dialOptions, grpc.WithContextDialer(func(ctx context.Context, server string) (net.Conn, error) { return dialer.DialContext(ctx, N.NetworkTCP, M.ParseSocksaddr(server)) })) + //nolint:staticcheck dialOptions = append(dialOptions, grpc.WithReturnConnectionError()) return &Client{ ctx: ctx, @@ -73,23 +74,22 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt } func (c *Client) connect() (*grpc.ClientConn, error) { - conn := c.conn + conn := c.conn.Load() if conn != nil && conn.GetState() != connectivity.Shutdown { return conn, nil } c.connAccess.Lock() defer c.connAccess.Unlock() - conn = c.conn + conn = c.conn.Load() if conn != nil && conn.GetState() != connectivity.Shutdown { return conn, nil } //nolint:staticcheck - //goland:noinspection GoDeprecation conn, err := grpc.DialContext(c.ctx, c.serverAddr, c.dialOptions...) if err != nil { return nil, err } - c.conn = conn + c.conn.Store(conn) return conn, nil } @@ -99,7 +99,7 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) { return nil, err } client := NewGunServiceClient(clientConn).(GunServiceCustomNameClient) - ctx, cancel := common.ContextWithCancelCause(ctx) + ctx, cancel := context.WithCancelCause(ctx) stream, err := client.TunCustomName(ctx, c.serviceName) if err != nil { cancel(err) @@ -109,11 +109,9 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) { } func (c *Client) Close() error { - c.connAccess.Lock() - defer c.connAccess.Unlock() - if c.conn != nil { - c.conn.Close() - c.conn = nil + conn := c.conn.Swap(nil) + if conn != nil { + conn.Close() } return nil } diff --git a/transport/v2raygrpc/conn.go b/transport/v2raygrpc/conn.go index 0fecbf33d3..87be96618c 100644 --- a/transport/v2raygrpc/conn.go +++ b/transport/v2raygrpc/conn.go @@ -1,25 +1,28 @@ package v2raygrpc import ( + "context" "net" "os" + "sync" "time" - "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/baderror" M "github.com/sagernet/sing/common/metadata" - "github.com/sagernet/sing/common/rw" + N "github.com/sagernet/sing/common/network" ) var _ net.Conn = (*GRPCConn)(nil) type GRPCConn struct { GunService - cancel common.ContextCancelCauseFunc - cache []byte + cache []byte + cancel context.CancelCauseFunc + closeOnce sync.Once } -func NewGRPCConn(service GunService, cancel common.ContextCancelCauseFunc) *GRPCConn { +func NewGRPCConn(service GunService, cancel context.CancelCauseFunc) *GRPCConn { + //nolint:staticcheck if client, isClient := service.(GunService_TunClient); isClient { service = &clientConnWrapper{client} } @@ -38,7 +41,6 @@ func (c *GRPCConn) Read(b []byte) (n int, err error) { hunk, err := c.Recv() err = baderror.WrapGRPC(err) if err != nil { - c.cancel(err) return } n = copy(b, hunk.Data) @@ -51,14 +53,17 @@ func (c *GRPCConn) Read(b []byte) (n int, err error) { func (c *GRPCConn) Write(b []byte) (n int, err error) { err = baderror.WrapGRPC(c.Send(&Hunk{Data: b})) if err != nil { - c.cancel(err) return } return len(b), nil } func (c *GRPCConn) Close() error { - c.cancel(net.ErrClosed) + c.closeOnce.Do(func() { + if c.cancel != nil { + c.cancel(nil) + } + }) return nil } @@ -90,7 +95,7 @@ func (c *GRPCConn) Upstream() any { return c.GunService } -var _ rw.WriteCloser = (*clientConnWrapper)(nil) +var _ N.WriteCloser = (*clientConnWrapper)(nil) type clientConnWrapper struct { GunService_TunClient diff --git a/transport/v2raygrpc/credentials/credentials.go b/transport/v2raygrpc/credentials/credentials.go index 32c9b59033..9deee7f651 100644 --- a/transport/v2raygrpc/credentials/credentials.go +++ b/transport/v2raygrpc/credentials/credentials.go @@ -25,12 +25,12 @@ import ( type requestInfoKey struct{} // NewRequestInfoContext creates a context with ri. -func NewRequestInfoContext(ctx context.Context, ri interface{}) context.Context { +func NewRequestInfoContext(ctx context.Context, ri any) context.Context { return context.WithValue(ctx, requestInfoKey{}, ri) } // RequestInfoFromContext extracts the RequestInfo from ctx. -func RequestInfoFromContext(ctx context.Context) interface{} { +func RequestInfoFromContext(ctx context.Context) any { return ctx.Value(requestInfoKey{}) } @@ -39,11 +39,11 @@ func RequestInfoFromContext(ctx context.Context) interface{} { type clientHandshakeInfoKey struct{} // ClientHandshakeInfoFromContext extracts the ClientHandshakeInfo from ctx. -func ClientHandshakeInfoFromContext(ctx context.Context) interface{} { +func ClientHandshakeInfoFromContext(ctx context.Context) any { return ctx.Value(clientHandshakeInfoKey{}) } // NewClientHandshakeInfoContext creates a context with chi. -func NewClientHandshakeInfoContext(ctx context.Context, chi interface{}) context.Context { +func NewClientHandshakeInfoContext(ctx context.Context, chi any) context.Context { return context.WithValue(ctx, clientHandshakeInfoKey{}, chi) } diff --git a/transport/v2raygrpc/credentials/util.go b/transport/v2raygrpc/credentials/util.go index f792fd22ca..ab86497761 100644 --- a/transport/v2raygrpc/credentials/util.go +++ b/transport/v2raygrpc/credentials/util.go @@ -20,16 +20,15 @@ package credentials import ( "crypto/tls" + "slices" ) const alpnProtoStrH2 = "h2" // AppendH2ToNextProtos appends h2 to next protos. func AppendH2ToNextProtos(ps []string) []string { - for _, p := range ps { - if p == alpnProtoStrH2 { - return ps - } + if slices.Contains(ps, alpnProtoStrH2) { + return ps } ret := make([]string, 0, len(ps)+1) ret = append(ret, ps...) diff --git a/transport/v2raygrpc/custom_name.go b/transport/v2raygrpc/custom_name.go index 1fe7d8cc4f..ce970dc6ba 100644 --- a/transport/v2raygrpc/custom_name.go +++ b/transport/v2raygrpc/custom_name.go @@ -34,7 +34,7 @@ func (c *gunServiceClient) TunCustomName(ctx context.Context, name string, opts if err != nil { return nil, err } - x := &gunServiceTunClient{stream} + x := &grpc.GenericClientStream[Hunk, Hunk]{ClientStream: stream} return x, nil } diff --git a/transport/v2raygrpc/server.go b/transport/v2raygrpc/server.go index 15088b268c..6160c2f3e9 100644 --- a/transport/v2raygrpc/server.go +++ b/transport/v2raygrpc/server.go @@ -9,8 +9,10 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" @@ -25,11 +27,12 @@ var _ adapter.V2RayServerTransport = (*Server)(nil) type Server struct { ctx context.Context - handler N.TCPConnectionHandler + logger logger.ContextLogger + handler adapter.V2RayServerTransportHandler server *grpc.Server } -func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler N.TCPConnectionHandler) (*Server, error) { +func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { var serverOptions []grpc.ServerOption if tlsConfig != nil { if !common.Contains(tlsConfig.NextProtos(), http2.NextProtoTLS) { @@ -43,31 +46,33 @@ func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig t Timeout: time.Duration(options.PingTimeout), })) } - server := &Server{ctx, handler, grpc.NewServer(serverOptions...)} + server := &Server{ctx, logger, handler, grpc.NewServer(serverOptions...)} RegisterGunServiceCustomNameServer(server.server, server, options.ServiceName) return server, nil } func (s *Server) Tun(server GunService_TunServer) error { - ctx, cancel := common.ContextWithCancelCause(s.ctx) - conn := NewGRPCConn(server, cancel) - var metadata M.Metadata + conn := NewGRPCConn(server, nil) + var source M.Socksaddr if remotePeer, loaded := peer.FromContext(server.Context()); loaded { - metadata.Source = M.SocksaddrFromNet(remotePeer.Addr) + source = M.SocksaddrFromNet(remotePeer.Addr) } if grpcMetadata, loaded := gM.FromIncomingContext(server.Context()); loaded { forwardFrom := strings.Join(grpcMetadata.Get("X-Forwarded-For"), ",") if forwardFrom != "" { - for _, from := range strings.Split(forwardFrom, ",") { + for from := range strings.SplitSeq(forwardFrom, ",") { originAddr := M.ParseSocksaddr(from) if originAddr.IsValid() { - metadata.Source = originAddr.Unwrap() + source = originAddr.Unwrap() } } } } - go s.handler.NewConnection(ctx, conn, metadata) - <-ctx.Done() + done := make(chan struct{}) + go s.handler.NewConnectionEx(log.ContextWithNewID(s.ctx), conn, source, M.Socksaddr{}, N.OnceClose(func(it error) { + close(done) + })) + <-done return nil } diff --git a/transport/v2raygrpc/stream.pb.go b/transport/v2raygrpc/stream.pb.go index 26c091e1fa..9576c7397b 100644 --- a/transport/v2raygrpc/stream.pb.go +++ b/transport/v2raygrpc/stream.pb.go @@ -3,6 +3,7 @@ package v2raygrpc import ( reflect "reflect" sync "sync" + unsafe "unsafe" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" @@ -16,20 +17,17 @@ const ( ) type Hunk struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` unknownFields protoimpl.UnknownFields - - Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + sizeCache protoimpl.SizeCache } func (x *Hunk) Reset() { *x = Hunk{} - if protoimpl.UnsafeEnabled { - mi := &file_transport_v2raygrpc_stream_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_transport_v2raygrpc_stream_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Hunk) String() string { @@ -40,7 +38,7 @@ func (*Hunk) ProtoMessage() {} func (x *Hunk) ProtoReflect() protoreflect.Message { mi := &file_transport_v2raygrpc_stream_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -64,38 +62,30 @@ func (x *Hunk) GetData() []byte { var File_transport_v2raygrpc_stream_proto protoreflect.FileDescriptor -var file_transport_v2raygrpc_stream_proto_rawDesc = []byte{ - 0x0a, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x76, 0x32, 0x72, 0x61, - 0x79, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x12, 0x13, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x76, 0x32, - 0x72, 0x61, 0x79, 0x67, 0x72, 0x70, 0x63, 0x22, 0x1a, 0x0a, 0x04, 0x48, 0x75, 0x6e, 0x6b, 0x12, - 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, - 0x61, 0x74, 0x61, 0x32, 0x4d, 0x0a, 0x0a, 0x47, 0x75, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x3f, 0x0a, 0x03, 0x54, 0x75, 0x6e, 0x12, 0x19, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, - 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x48, - 0x75, 0x6e, 0x6b, 0x1a, 0x19, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, - 0x76, 0x32, 0x72, 0x61, 0x79, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x75, 0x6e, 0x6b, 0x28, 0x01, - 0x30, 0x01, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x73, 0x61, 0x67, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x73, 0x69, 0x6e, 0x67, 0x2d, 0x62, - 0x6f, 0x78, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x76, 0x32, 0x72, - 0x61, 0x79, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +const file_transport_v2raygrpc_stream_proto_rawDesc = "" + + "\n" + + " transport/v2raygrpc/stream.proto\x12\x13transport.v2raygrpc\"\x1a\n" + + "\x04Hunk\x12\x12\n" + + "\x04data\x18\x01 \x01(\fR\x04data2M\n" + + "\n" + + "GunService\x12?\n" + + "\x03Tun\x12\x19.transport.v2raygrpc.Hunk\x1a\x19.transport.v2raygrpc.Hunk(\x010\x01B2Z0github.com/sagernet/sing-box/transport/v2raygrpcb\x06proto3" var ( file_transport_v2raygrpc_stream_proto_rawDescOnce sync.Once - file_transport_v2raygrpc_stream_proto_rawDescData = file_transport_v2raygrpc_stream_proto_rawDesc + file_transport_v2raygrpc_stream_proto_rawDescData []byte ) func file_transport_v2raygrpc_stream_proto_rawDescGZIP() []byte { file_transport_v2raygrpc_stream_proto_rawDescOnce.Do(func() { - file_transport_v2raygrpc_stream_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_v2raygrpc_stream_proto_rawDescData) + file_transport_v2raygrpc_stream_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_v2raygrpc_stream_proto_rawDesc), len(file_transport_v2raygrpc_stream_proto_rawDesc))) }) return file_transport_v2raygrpc_stream_proto_rawDescData } var ( file_transport_v2raygrpc_stream_proto_msgTypes = make([]protoimpl.MessageInfo, 1) - file_transport_v2raygrpc_stream_proto_goTypes = []interface{}{ + file_transport_v2raygrpc_stream_proto_goTypes = []any{ (*Hunk)(nil), // 0: transport.v2raygrpc.Hunk } ) @@ -115,25 +105,11 @@ func file_transport_v2raygrpc_stream_proto_init() { if File_transport_v2raygrpc_stream_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_transport_v2raygrpc_stream_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Hunk); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_transport_v2raygrpc_stream_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_v2raygrpc_stream_proto_rawDesc), len(file_transport_v2raygrpc_stream_proto_rawDesc)), NumEnums: 0, NumMessages: 1, NumExtensions: 0, @@ -144,7 +120,6 @@ func file_transport_v2raygrpc_stream_proto_init() { MessageInfos: file_transport_v2raygrpc_stream_proto_msgTypes, }.Build() File_transport_v2raygrpc_stream_proto = out.File - file_transport_v2raygrpc_stream_proto_rawDesc = nil file_transport_v2raygrpc_stream_proto_goTypes = nil file_transport_v2raygrpc_stream_proto_depIdxs = nil } diff --git a/transport/v2raygrpc/stream_grpc.pb.go b/transport/v2raygrpc/stream_grpc.pb.go index ea634849d4..21cc32796d 100644 --- a/transport/v2raygrpc/stream_grpc.pb.go +++ b/transport/v2raygrpc/stream_grpc.pb.go @@ -10,8 +10,8 @@ import ( // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 const ( GunService_Tun_FullMethodName = "/transport.v2raygrpc.GunService/Tun" @@ -21,7 +21,7 @@ const ( // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type GunServiceClient interface { - Tun(ctx context.Context, opts ...grpc.CallOption) (GunService_TunClient, error) + Tun(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[Hunk, Hunk], error) } type gunServiceClient struct { @@ -32,52 +32,39 @@ func NewGunServiceClient(cc grpc.ClientConnInterface) GunServiceClient { return &gunServiceClient{cc} } -func (c *gunServiceClient) Tun(ctx context.Context, opts ...grpc.CallOption) (GunService_TunClient, error) { - stream, err := c.cc.NewStream(ctx, &GunService_ServiceDesc.Streams[0], GunService_Tun_FullMethodName, opts...) +func (c *gunServiceClient) Tun(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[Hunk, Hunk], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &GunService_ServiceDesc.Streams[0], GunService_Tun_FullMethodName, cOpts...) if err != nil { return nil, err } - x := &gunServiceTunClient{stream} + x := &grpc.GenericClientStream[Hunk, Hunk]{ClientStream: stream} return x, nil } -type GunService_TunClient interface { - Send(*Hunk) error - Recv() (*Hunk, error) - grpc.ClientStream -} - -type gunServiceTunClient struct { - grpc.ClientStream -} - -func (x *gunServiceTunClient) Send(m *Hunk) error { - return x.ClientStream.SendMsg(m) -} - -func (x *gunServiceTunClient) Recv() (*Hunk, error) { - m := new(Hunk) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type GunService_TunClient = grpc.BidiStreamingClient[Hunk, Hunk] // GunServiceServer is the server API for GunService service. // All implementations must embed UnimplementedGunServiceServer -// for forward compatibility +// for forward compatibility. type GunServiceServer interface { - Tun(GunService_TunServer) error + Tun(grpc.BidiStreamingServer[Hunk, Hunk]) error mustEmbedUnimplementedGunServiceServer() } -// UnimplementedGunServiceServer must be embedded to have forward compatible implementations. +// UnimplementedGunServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. type UnimplementedGunServiceServer struct{} -func (UnimplementedGunServiceServer) Tun(GunService_TunServer) error { - return status.Errorf(codes.Unimplemented, "method Tun not implemented") +func (UnimplementedGunServiceServer) Tun(grpc.BidiStreamingServer[Hunk, Hunk]) error { + return status.Error(codes.Unimplemented, "method Tun not implemented") } func (UnimplementedGunServiceServer) mustEmbedUnimplementedGunServiceServer() {} +func (UnimplementedGunServiceServer) testEmbeddedByValue() {} // UnsafeGunServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to GunServiceServer will @@ -87,34 +74,22 @@ type UnsafeGunServiceServer interface { } func RegisterGunServiceServer(s grpc.ServiceRegistrar, srv GunServiceServer) { + // If the following call panics, it indicates UnimplementedGunServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } s.RegisterService(&GunService_ServiceDesc, srv) } func _GunService_Tun_Handler(srv interface{}, stream grpc.ServerStream) error { - return srv.(GunServiceServer).Tun(&gunServiceTunServer{stream}) -} - -type GunService_TunServer interface { - Send(*Hunk) error - Recv() (*Hunk, error) - grpc.ServerStream -} - -type gunServiceTunServer struct { - grpc.ServerStream + return srv.(GunServiceServer).Tun(&grpc.GenericServerStream[Hunk, Hunk]{ServerStream: stream}) } -func (x *gunServiceTunServer) Send(m *Hunk) error { - return x.ServerStream.SendMsg(m) -} - -func (x *gunServiceTunServer) Recv() (*Hunk, error) { - m := new(Hunk) - if err := x.ServerStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type GunService_TunServer = grpc.BidiStreamingServer[Hunk, Hunk] // GunService_ServiceDesc is the grpc.ServiceDesc for GunService service. // It's only intended for direct use with grpc.RegisterService, diff --git a/transport/v2raygrpclite/client.go b/transport/v2raygrpclite/client.go index de8915a17c..b2aab911f2 100644 --- a/transport/v2raygrpclite/client.go +++ b/transport/v2raygrpclite/client.go @@ -29,7 +29,6 @@ var defaultClientHeader = http.Header{ type Client struct { ctx context.Context - dialer N.Dialer serverAddr M.Socksaddr transport *http2.Transport options option.V2RayGRPCOptions @@ -46,7 +45,6 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt } client := &Client{ ctx: ctx, - dialer: dialer, serverAddr: serverAddr, options: options, transport: &http2.Transport{ @@ -62,7 +60,6 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt }, host: host, } - if tlsConfig == nil { client.transport.DialTLSContext = func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) { return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) @@ -71,12 +68,9 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt if len(tlsConfig.NextProtos()) == 0 { tlsConfig.SetNextProtos([]string{http2.NextProtoTLS}) } + tlsDialer := tls.NewDialer(dialer, tlsConfig) client.transport.DialTLSContext = func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) { - conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) - if err != nil { - return nil, err - } - return tls.ClientHandshake(ctx, conn, tlsConfig) + return tlsDialer.DialTLSContext(ctx, M.ParseSocksaddr(addr)) } } diff --git a/transport/v2raygrpclite/conn.go b/transport/v2raygrpclite/conn.go index f5a71939d3..5feafbb68f 100644 --- a/transport/v2raygrpclite/conn.go +++ b/transport/v2raygrpclite/conn.go @@ -13,7 +13,7 @@ import ( "github.com/sagernet/sing/common/baderror" "github.com/sagernet/sing/common/buf" M "github.com/sagernet/sing/common/metadata" - "github.com/sagernet/sing/common/rw" + "github.com/sagernet/sing/common/varbin" ) // kanged from: https://github.com/Qv2ray/gun-lite @@ -21,6 +21,7 @@ import ( var _ net.Conn = (*GunConn)(nil) type GunConn struct { + rawReader io.Reader reader *std_bufio.Reader writer io.Writer flusher http.Flusher @@ -31,9 +32,10 @@ type GunConn struct { func newGunConn(reader io.Reader, writer io.Writer, flusher http.Flusher) *GunConn { return &GunConn{ - reader: std_bufio.NewReader(reader), - writer: writer, - flusher: flusher, + rawReader: reader, + reader: std_bufio.NewReader(reader), + writer: writer, + flusher: flusher, } } @@ -46,6 +48,7 @@ func newLateGunConn(writer io.Writer) *GunConn { func (c *GunConn) setup(reader io.Reader, err error) { if reader != nil { + c.rawReader = reader c.reader = std_bufio.NewReader(reader) } c.err = err @@ -96,7 +99,7 @@ func (c *GunConn) read(b []byte) (n int, err error) { } func (c *GunConn) Write(b []byte) (n int, err error) { - varLen := rw.UVariantLen(uint64(len(b))) + varLen := varbin.UvarintLen(uint64(len(b))) buffer := buf.NewSize(6 + varLen + len(b)) header := buffer.Extend(6 + varLen) header[0] = 0x00 @@ -117,13 +120,13 @@ func (c *GunConn) Write(b []byte) (n int, err error) { func (c *GunConn) WriteBuffer(buffer *buf.Buffer) error { defer buffer.Release() dataLen := buffer.Len() - varLen := rw.UVariantLen(uint64(dataLen)) + varLen := varbin.UvarintLen(uint64(dataLen)) header := buffer.ExtendHeader(6 + varLen) header[0] = 0x00 binary.BigEndian.PutUint32(header[1:5], uint32(1+varLen+dataLen)) header[5] = 0x0A binary.PutUvarint(header[6:], uint64(dataLen)) - err := rw.WriteBytes(c.writer, buffer.Bytes()) + err := common.Error(c.writer.Write(buffer.Bytes())) if err != nil { return baderror.WrapH2(err) } @@ -138,7 +141,7 @@ func (c *GunConn) FrontHeadroom() int { } func (c *GunConn) Close() error { - return common.Close(c.reader, c.writer) + return common.Close(c.rawReader, c.writer) } func (c *GunConn) LocalAddr() net.Addr { diff --git a/transport/v2raygrpclite/server.go b/transport/v2raygrpclite/server.go index 6d3e42ebe5..622d785abd 100644 --- a/transport/v2raygrpclite/server.go +++ b/transport/v2raygrpclite/server.go @@ -10,10 +10,12 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/transport/v2rayhttp" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" aTLS "github.com/sagernet/sing/common/tls" @@ -26,18 +28,19 @@ import ( var _ adapter.V2RayServerTransport = (*Server)(nil) type Server struct { - tlsConfig tls.ServerConfig - handler adapter.V2RayServerTransportHandler - errorHandler E.Handler - httpServer *http.Server - h2Server *http2.Server - h2cHandler http.Handler - path string + tlsConfig tls.ServerConfig + logger logger.ContextLogger + handler adapter.V2RayServerTransportHandler + httpServer *http.Server + h2Server *http2.Server + h2cHandler http.Handler + path string } -func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { +func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { server := &Server{ tlsConfig: tlsConfig, + logger: logger, handler: handler, path: "/" + options.ServiceName + "/Tun", h2Server: &http2.Server{ @@ -49,6 +52,9 @@ func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig t BaseContext: func(net.Listener) context.Context { return ctx }, + ConnContext: func(ctx context.Context, c net.Conn) context.Context { + return log.ContextWithNewID(ctx) + }, } server.h2cHandler = h2c.NewHandler(server, server.h2Server) return server, nil @@ -74,10 +80,12 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { writer.Header().Set("Content-Type", "application/grpc") writer.Header().Set("TE", "trailers") writer.WriteHeader(http.StatusOK) - var metadata M.Metadata - metadata.Source = sHttp.SourceAddress(request) + done := make(chan struct{}) conn := v2rayhttp.NewHTTP2Wrapper(newGunConn(request.Body, writer, writer.(http.Flusher))) - s.handler.NewConnection(request.Context(), conn, metadata) + s.handler.NewConnectionEx(request.Context(), conn, sHttp.SourceAddress(request), M.Socksaddr{}, N.OnceClose(func(it error) { + close(done) + })) + <-done conn.CloseWrapper() } @@ -85,7 +93,7 @@ func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Reques if statusCode > 0 { writer.WriteHeader(statusCode) } - s.handler.NewError(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) + s.logger.ErrorContext(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) } func (s *Server) Network() []string { diff --git a/transport/v2rayhttp/client.go b/transport/v2rayhttp/client.go index a105a4f35f..20cf41f50b 100644 --- a/transport/v2rayhttp/client.go +++ b/transport/v2rayhttp/client.go @@ -12,12 +12,12 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" sHTTP "github.com/sagernet/sing/protocol/http" - "golang.org/x/net/http2" ) @@ -47,15 +47,12 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt if len(tlsConfig.NextProtos()) == 0 { tlsConfig.SetNextProtos([]string{http2.NextProtoTLS}) } + tlsDialer := tls.NewDialer(dialer, tlsConfig) transport = &http2.Transport{ ReadIdleTimeout: time.Duration(options.IdleTimeout), PingTimeout: time.Duration(options.PingTimeout), DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) { - conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) - if err != nil { - return nil, err - } - return tls.ClientHandshake(ctx, conn, tlsConfig) + return tlsDialer.DialTLSContext(ctx, M.ParseSocksaddr(addr)) }, } } @@ -77,6 +74,15 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt if !strings.HasPrefix(requestURL.Path, "/") { requestURL.Path = "/" + requestURL.Path } + headers := options.Headers.Build() + + if host := headers.Get("Host"); host != "" { //H + headers.Del("Host") //H + requestURL.Host = host //H + } + if headers.Get("User-Agent") == "" { //H + headers.Set("User-Agent", C.DefaultBrowserAgent) //H + } //H return &Client{ ctx: ctx, dialer: dialer, @@ -84,7 +90,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt requestURL: requestURL, host: options.Host, method: options.Method, - headers: options.Headers.Build(), + headers: headers, //H transport: transport, http2: tlsConfig != nil, }, nil diff --git a/transport/v2rayhttp/conn.go b/transport/v2rayhttp/conn.go index f7f93f1b58..b339a75309 100644 --- a/transport/v2rayhttp/conn.go +++ b/transport/v2rayhttp/conn.go @@ -2,6 +2,7 @@ package v2rayhttp import ( std_bufio "bufio" + "context" "io" "net" "net/http" @@ -10,6 +11,7 @@ import ( "sync" "time" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/baderror" "github.com/sagernet/sing/common/buf" @@ -29,6 +31,9 @@ type HTTPConn struct { } func NewHTTP1Conn(conn net.Conn, request *http.Request) *HTTPConn { + if request.Header.Get("Host") == "" { + request.Header.Set("Host", request.Host) + } return &HTTPConn{ Conn: conn, request: request, @@ -87,9 +92,6 @@ func (c *HTTPConn) writeRequest(payload []byte) error { if err != nil { return err } - if c.request.Header.Get("Host") == "" { - c.request.Header.Set("Host", c.request.Host) - } for key, value := range c.request.Header { _, err = writer.Write([]byte(F.ToString(key, ": ", strings.Join(value, ", "), CRLF))) if err != nil { @@ -255,3 +257,11 @@ func (w *HTTP2ConnWrapper) Close() error { func (w *HTTP2ConnWrapper) Upstream() any { return w.ExtendedConn } + +func DupContext(ctx context.Context) context.Context { + id, loaded := log.IDFromContext(ctx) + if !loaded { + return context.Background() + } + return log.ContextWithID(context.Background(), id) +} diff --git a/transport/v2rayhttp/server.go b/transport/v2rayhttp/server.go index cad7d906b4..282c7c2391 100644 --- a/transport/v2rayhttp/server.go +++ b/transport/v2rayhttp/server.go @@ -11,11 +11,13 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" aTLS "github.com/sagernet/sing/common/tls" @@ -29,6 +31,7 @@ var _ adapter.V2RayServerTransport = (*Server)(nil) type Server struct { ctx context.Context + logger logger.ContextLogger tlsConfig tls.ServerConfig handler adapter.V2RayServerTransportHandler httpServer *http.Server @@ -40,10 +43,11 @@ type Server struct { headers http.Header } -func NewServer(ctx context.Context, options option.V2RayHTTPOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { +func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayHTTPOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { server := &Server{ ctx: ctx, tlsConfig: tlsConfig, + logger: logger, handler: handler, h2Server: &http2.Server{ IdleTimeout: time.Duration(options.IdleTimeout), @@ -63,6 +67,9 @@ func NewServer(ctx context.Context, options option.V2RayHTTPOptions, tlsConfig t BaseContext: func(net.Listener) context.Context { return ctx }, + ConnContext: func(ctx context.Context, c net.Conn) context.Context { + return log.ContextWithNewID(ctx) + }, } server.h2cHandler = h2c.NewHandler(server, server.h2Server) return server, nil @@ -95,8 +102,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { } } - var metadata M.Metadata - metadata.Source = sHttp.SourceAddress(request) + source := sHttp.SourceAddress(request) if h, ok := writer.(http.Hijacker); ok { var requestBody *buf.Buffer if contentLength := int(request.ContentLength); contentLength > 0 { @@ -127,14 +133,20 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { if requestBody != nil { conn = bufio.NewCachedConn(conn, requestBody) } - s.handler.NewConnection(request.Context(), conn, metadata) + s.handler.NewConnectionEx(DupContext(request.Context()), conn, source, M.Socksaddr{}, nil) } else { writer.WriteHeader(http.StatusOK) + flusher := writer.(http.Flusher) + flusher.Flush() + done := make(chan struct{}) conn := NewHTTP2Wrapper(&ServerHTTPConn{ NewHTTPConn(request.Body, writer), - writer.(http.Flusher), + flusher, }) - s.handler.NewConnection(request.Context(), conn, metadata) + s.handler.NewConnectionEx(request.Context(), conn, source, M.Socksaddr{}, N.OnceClose(func(it error) { + close(done) + })) + <-done conn.CloseWrapper() } } @@ -143,7 +155,7 @@ func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Reques if statusCode > 0 { writer.WriteHeader(statusCode) } - s.handler.NewError(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) + s.logger.ErrorContext(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) } func (s *Server) Network() []string { @@ -155,7 +167,7 @@ func (s *Server) Serve(listener net.Listener) error { if len(s.tlsConfig.NextProtos()) == 0 { s.tlsConfig.SetNextProtos([]string{http2.NextProtoTLS, "http/1.1"}) } else if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) { - s.tlsConfig.SetNextProtos(append([]string{"h2"}, s.tlsConfig.NextProtos()...)) + s.tlsConfig.SetNextProtos(append([]string{http2.NextProtoTLS}, s.tlsConfig.NextProtos()...)) } listener = aTLS.NewListener(listener, s.tlsConfig) } diff --git a/transport/v2rayhttpupgrade/client.go b/transport/v2rayhttpupgrade/client.go index e2b86b1f6b..8577b0a9db 100644 --- a/transport/v2rayhttpupgrade/client.go +++ b/transport/v2rayhttpupgrade/client.go @@ -10,6 +10,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" @@ -23,7 +24,6 @@ var _ adapter.V2RayClientTransport = (*Client)(nil) type Client struct { dialer N.Dialer - tlsConfig tls.Config serverAddr M.Socksaddr requestURL url.URL headers http.Header @@ -35,6 +35,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt if len(tlsConfig.NextProtos()) == 0 { tlsConfig.SetNextProtos([]string{"http/1.1"}) } + dialer = tls.NewDialer(dialer, tlsConfig) } var host string if options.Host != "" { @@ -63,9 +64,11 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt for key, value := range options.Headers { headers[key] = value } + if headers.Get("User-Agent") == "" { //H + headers.Set("User-Agent", C.DefaultBrowserAgent) //H + } //H return &Client{ dialer: dialer, - tlsConfig: tlsConfig, serverAddr: serverAddr, requestURL: requestURL, headers: headers, @@ -78,12 +81,6 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) { if err != nil { return nil, err } - if c.tlsConfig != nil { - conn, err = tls.ClientHandshake(ctx, conn, c.tlsConfig) - if err != nil { - return nil, err - } - } request := &http.Request{ Method: http.MethodGet, URL: &c.requestURL, diff --git a/transport/v2rayhttpupgrade/server.go b/transport/v2rayhttpupgrade/server.go index a3b5d23ed9..338b7248a4 100644 --- a/transport/v2rayhttpupgrade/server.go +++ b/transport/v2rayhttpupgrade/server.go @@ -10,9 +10,12 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/transport/v2rayhttp" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" aTLS "github.com/sagernet/sing/common/tls" @@ -23,6 +26,7 @@ var _ adapter.V2RayServerTransport = (*Server)(nil) type Server struct { ctx context.Context + logger logger.ContextLogger tlsConfig tls.ServerConfig handler adapter.V2RayServerTransportHandler httpServer *http.Server @@ -31,9 +35,10 @@ type Server struct { headers http.Header } -func NewServer(ctx context.Context, options option.V2RayHTTPUpgradeOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { +func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayHTTPUpgradeOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { server := &Server{ ctx: ctx, + logger: logger, tlsConfig: tlsConfig, handler: handler, host: options.Host, @@ -50,6 +55,9 @@ func NewServer(ctx context.Context, options option.V2RayHTTPUpgradeOptions, tlsC BaseContext: func(net.Listener) context.Context { return ctx }, + ConnContext: func(ctx context.Context, c net.Conn) context.Context { + return log.ContextWithNewID(ctx) + }, TLSNextProto: make(map[string]func(*http.Server, *tls.STDConn, http.Handler)), } return server, nil @@ -104,16 +112,14 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { s.invalidRequest(writer, request, http.StatusInternalServerError, E.Cause(err, "hijack failed")) return } - var metadata M.Metadata - metadata.Source = sHttp.SourceAddress(request) - s.handler.NewConnection(request.Context(), conn, metadata) + s.handler.NewConnectionEx(v2rayhttp.DupContext(request.Context()), conn, sHttp.SourceAddress(request), M.Socksaddr{}, nil) } func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) { if statusCode > 0 { writer.WriteHeader(statusCode) } - s.handler.NewError(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) + s.logger.ErrorContext(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) } func (s *Server) Network() []string { diff --git a/transport/v2rayquic/client.go b/transport/v2rayquic/client.go index a1c3e3a681..3e0d8b81d8 100644 --- a/transport/v2rayquic/client.go +++ b/transport/v2rayquic/client.go @@ -29,7 +29,7 @@ type Client struct { tlsConfig tls.Config quicConfig *quic.Config connAccess sync.Mutex - conn quic.Connection + conn common.TypedValue[*quic.Conn] rawConn net.Conn } @@ -49,14 +49,14 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt }, nil } -func (c *Client) offer() (quic.Connection, error) { - conn := c.conn +func (c *Client) offer() (*quic.Conn, error) { + conn := c.conn.Load() if conn != nil && !common.Done(conn.Context()) { return conn, nil } c.connAccess.Lock() defer c.connAccess.Unlock() - conn = c.conn + conn = c.conn.Load() if conn != nil && !common.Done(conn.Context()) { return conn, nil } @@ -67,19 +67,18 @@ func (c *Client) offer() (quic.Connection, error) { return conn, nil } -func (c *Client) offerNew() (quic.Connection, error) { +func (c *Client) offerNew() (*quic.Conn, error) { udpConn, err := c.dialer.DialContext(c.ctx, "udp", c.serverAddr) if err != nil { return nil, err } - var packetConn net.PacketConn - packetConn = bufio.NewUnbindPacketConn(udpConn) + packetConn := bufio.NewUnbindPacketConn(udpConn) quicConn, err := qtls.Dial(c.ctx, packetConn, udpConn.RemoteAddr(), c.tlsConfig, c.quicConfig) if err != nil { packetConn.Close() return nil, err } - c.conn = quicConn + c.conn.Store(quicConn) c.rawConn = udpConn return quicConn, nil } @@ -99,13 +98,13 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) { func (c *Client) Close() error { c.connAccess.Lock() defer c.connAccess.Unlock() - if c.conn != nil { - c.conn.CloseWithError(0, "") + conn := c.conn.Swap(nil) + if conn != nil { + conn.CloseWithError(0, "") } if c.rawConn != nil { c.rawConn.Close() } - c.conn = nil c.rawConn = nil return nil } diff --git a/transport/v2rayquic/server.go b/transport/v2rayquic/server.go index f772103012..c50d92f0f5 100644 --- a/transport/v2rayquic/server.go +++ b/transport/v2rayquic/server.go @@ -15,6 +15,8 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-quic" "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) @@ -23,6 +25,7 @@ var _ adapter.V2RayServerTransport = (*Server)(nil) type Server struct { ctx context.Context + logger logger.ContextLogger tlsConfig tls.ServerConfig quicConfig *quic.Config handler adapter.V2RayServerTransportHandler @@ -30,7 +33,7 @@ type Server struct { quicListener qtls.Listener } -func NewServer(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { +func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { quicConfig := &quic.Config{ DisablePathMTUDiscovery: !C.IsLinux && !C.IsWindows, } @@ -39,6 +42,7 @@ func NewServer(ctx context.Context, options option.V2RayQUICOptions, tlsConfig t } server := &Server{ ctx: ctx, + logger: logger, tlsConfig: tlsConfig, quicConfig: quicConfig, handler: handler, @@ -73,20 +77,20 @@ func (s *Server) acceptLoop() { } go func() { hErr := s.streamAcceptLoop(conn) - if hErr != nil { - s.handler.NewError(conn.Context(), hErr) + if hErr != nil && !E.IsClosedOrCanceled(hErr) { + s.logger.ErrorContext(conn.Context(), hErr) } }() } } -func (s *Server) streamAcceptLoop(conn quic.Connection) error { +func (s *Server) streamAcceptLoop(conn *quic.Conn) error { for { stream, err := conn.AcceptStream(s.ctx) if err != nil { - return err + return qtls.WrapError(err) } - go s.handler.NewConnection(conn.Context(), &StreamWrapper{Conn: conn, Stream: stream}, M.Metadata{}) + go s.handler.NewConnectionEx(conn.Context(), &StreamWrapper{Conn: conn, Stream: stream}, M.SocksaddrFromNet(conn.RemoteAddr()), M.Socksaddr{}, nil) } } diff --git a/transport/v2rayquic/stream.go b/transport/v2rayquic/stream.go index d9c3bebacc..aad62afb88 100644 --- a/transport/v2rayquic/stream.go +++ b/transport/v2rayquic/stream.go @@ -4,22 +4,22 @@ import ( "net" "github.com/sagernet/quic-go" - "github.com/sagernet/sing/common/baderror" + qtls "github.com/sagernet/sing-quic" ) type StreamWrapper struct { - Conn quic.Connection - quic.Stream + Conn *quic.Conn + *quic.Stream } func (s *StreamWrapper) Read(p []byte) (n int, err error) { n, err = s.Stream.Read(p) - return n, baderror.WrapQUIC(err) + return n, qtls.WrapError(err) } func (s *StreamWrapper) Write(p []byte) (n int, err error) { n, err = s.Stream.Write(p) - return n, baderror.WrapQUIC(err) + return n, qtls.WrapError(err) } func (s *StreamWrapper) LocalAddr() net.Addr { diff --git a/transport/v2raywebsocket/client.go b/transport/v2raywebsocket/client.go index 9c4950765c..bb20e177c8 100644 --- a/transport/v2raywebsocket/client.go +++ b/transport/v2raywebsocket/client.go @@ -26,7 +26,6 @@ var _ adapter.V2RayClientTransport = (*Client)(nil) type Client struct { dialer N.Dialer - tlsConfig tls.Config serverAddr M.Socksaddr requestURL url.URL headers http.Header @@ -39,6 +38,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt if len(tlsConfig.NextProtos()) == 0 { tlsConfig.SetNextProtos([]string{"http/1.1"}) } + dialer = tls.NewDialer(dialer, tlsConfig) } var requestURL url.URL if tlsConfig == nil { @@ -61,11 +61,10 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt requestURL.Host = host } if headers.Get("User-Agent") == "" { - headers.Set("User-Agent", "Go-http-client/1.1") + headers.Set("User-Agent", C.DefaultBrowserAgent) //H } return &Client{ dialer, - tlsConfig, serverAddr, requestURL, headers, @@ -79,22 +78,13 @@ func (c *Client) dialContext(ctx context.Context, requestURL *url.URL, headers h if err != nil { return nil, err } - if c.tlsConfig != nil { - conn, err = tls.ClientHandshake(ctx, conn, c.tlsConfig) - if err != nil { - return nil, err - } - } var deadlineConn net.Conn if deadline.NeedAdditionalReadDeadline(conn) { deadlineConn = deadline.NewConn(conn) } else { deadlineConn = conn } - err = deadlineConn.SetDeadline(time.Now().Add(C.TCPTimeout)) - if err != nil { - return nil, E.Cause(err, "set read deadline") - } + deadlineConn.SetDeadline(time.Now().Add(C.TCPTimeout)) var protocols []string if protocolHeader := headers.Get("Sec-WebSocket-Protocol"); protocolHeader != "" { protocols = []string{protocolHeader} diff --git a/transport/v2raywebsocket/conn.go b/transport/v2raywebsocket/conn.go index 6ed0b0f33d..cb788fc91d 100644 --- a/transport/v2raywebsocket/conn.go +++ b/transport/v2raywebsocket/conn.go @@ -3,10 +3,12 @@ package v2raywebsocket import ( "context" "encoding/base64" + "errors" "io" "net" "os" "sync" + "sync/atomic" "time" C "github.com/sagernet/sing-box/constant" @@ -67,21 +69,26 @@ func (c *WebsocketConn) Read(b []byte) (n int, err error) { return } if !E.IsMulti(err, io.EOF, wsutil.ErrNoFrameAdvance) { + err = wrapWsError(err) return } - header, err = c.reader.NextFrame() + header, err = wrapWsError0(c.reader.NextFrame()) if err != nil { return } if header.OpCode.IsControl() { - err = c.controlHandler(header, c.reader) + if header.Length > 128 { + err = wsutil.ErrFrameTooLarge + return + } + err = wrapWsError(c.controlHandler(header, c.reader)) if err != nil { return } continue } if header.OpCode&ws.OpBinary == 0 { - err = c.reader.Discard() + err = wrapWsError(c.reader.Discard()) if err != nil { return } @@ -91,7 +98,7 @@ func (c *WebsocketConn) Read(b []byte) (n int, err error) { } func (c *WebsocketConn) Write(p []byte) (n int, err error) { - err = wsutil.WriteMessage(c.Conn, c.state, ws.OpBinary, p) + err = wrapWsError(wsutil.WriteMessage(c.Conn, c.state, ws.OpBinary, p)) if err != nil { return } @@ -129,20 +136,22 @@ func (c *WebsocketConn) Upstream() any { type EarlyWebsocketConn struct { *Client ctx context.Context - conn *WebsocketConn + conn atomic.Pointer[WebsocketConn] access sync.Mutex create chan struct{} err error } func (c *EarlyWebsocketConn) Read(b []byte) (n int, err error) { - if c.conn == nil { + conn := c.conn.Load() + if conn == nil { <-c.create if c.err != nil { return 0, c.err } + conn = c.conn.Load() } - return c.conn.Read(b) + return wrapWsError0(conn.Read(b)) } func (c *EarlyWebsocketConn) writeRequest(content []byte) error { @@ -181,21 +190,23 @@ func (c *EarlyWebsocketConn) writeRequest(content []byte) error { return err } } - c.conn = conn + c.conn.Store(conn) return nil } func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) { - if c.conn != nil { - return c.conn.Write(b) + conn := c.conn.Load() + if conn != nil { + return wrapWsError0(conn.Write(b)) } c.access.Lock() defer c.access.Unlock() + conn = c.conn.Load() if c.err != nil { return 0, c.err } - if c.conn != nil { - return c.conn.Write(b) + if conn != nil { + return wrapWsError0(conn.Write(b)) } err = c.writeRequest(b) c.err = err @@ -207,17 +218,19 @@ func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) { } func (c *EarlyWebsocketConn) WriteBuffer(buffer *buf.Buffer) error { - if c.conn != nil { - return c.conn.WriteBuffer(buffer) + conn := c.conn.Load() + if conn != nil { + return wrapWsError(conn.WriteBuffer(buffer)) } c.access.Lock() defer c.access.Unlock() - if c.conn != nil { - return c.conn.WriteBuffer(buffer) - } if c.err != nil { return c.err } + conn = c.conn.Load() + if conn != nil { + return wrapWsError(conn.WriteBuffer(buffer)) + } err := c.writeRequest(buffer.Bytes()) c.err = err close(c.create) @@ -225,24 +238,27 @@ func (c *EarlyWebsocketConn) WriteBuffer(buffer *buf.Buffer) error { } func (c *EarlyWebsocketConn) Close() error { - if c.conn == nil { + conn := c.conn.Load() + if conn == nil { return nil } - return c.conn.Close() + return conn.Close() } func (c *EarlyWebsocketConn) LocalAddr() net.Addr { - if c.conn == nil { + conn := c.conn.Load() + if conn == nil { return M.Socksaddr{} } - return c.conn.LocalAddr() + return conn.LocalAddr() } func (c *EarlyWebsocketConn) RemoteAddr() net.Addr { - if c.conn == nil { + conn := c.conn.Load() + if conn == nil { return M.Socksaddr{} } - return c.conn.RemoteAddr() + return conn.RemoteAddr() } func (c *EarlyWebsocketConn) SetDeadline(t time.Time) error { @@ -262,9 +278,29 @@ func (c *EarlyWebsocketConn) NeedAdditionalReadDeadline() bool { } func (c *EarlyWebsocketConn) Upstream() any { - return common.PtrOrNil(c.conn) + return common.PtrOrNil(c.conn.Load()) } func (c *EarlyWebsocketConn) LazyHeadroom() bool { - return c.conn == nil + return c.conn.Load() == nil +} + +func wrapWsError(err error) error { + if err == nil { + return nil + } + var closedErr wsutil.ClosedError + if errors.As(err, &closedErr) { + if closedErr.Code == ws.StatusNormalClosure || closedErr.Code == ws.StatusNoStatusRcvd { + err = io.EOF + } + } + return err +} + +func wrapWsError0[T any](value T, err error) (T, error) { + if err == nil { + return value, nil + } + return value, wrapWsError(err) } diff --git a/transport/v2raywebsocket/deadline.go b/transport/v2raywebsocket/deadline.go deleted file mode 100644 index 195565aa55..0000000000 --- a/transport/v2raywebsocket/deadline.go +++ /dev/null @@ -1,22 +0,0 @@ -package v2raywebsocket - -import ( - "net" - "time" -) - -type deadConn struct { - net.Conn -} - -func (c *deadConn) SetDeadline(t time.Time) error { - return nil -} - -func (c *deadConn) SetReadDeadline(t time.Time) error { - return nil -} - -func (c *deadConn) SetWriteDeadline(t time.Time) error { - return nil -} diff --git a/transport/v2raywebsocket/server.go b/transport/v2raywebsocket/server.go index 86f2de9cd3..b54d760a05 100644 --- a/transport/v2raywebsocket/server.go +++ b/transport/v2raywebsocket/server.go @@ -11,11 +11,14 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/transport/v2rayhttp" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" aTLS "github.com/sagernet/sing/common/tls" @@ -27,6 +30,7 @@ var _ adapter.V2RayServerTransport = (*Server)(nil) type Server struct { ctx context.Context + logger logger.ContextLogger tlsConfig tls.ServerConfig handler adapter.V2RayServerTransportHandler httpServer *http.Server @@ -36,9 +40,10 @@ type Server struct { upgrader ws.HTTPUpgrader } -func NewServer(ctx context.Context, options option.V2RayWebsocketOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { +func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayWebsocketOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { server := &Server{ ctx: ctx, + logger: logger, tlsConfig: tlsConfig, handler: handler, path: options.Path, @@ -59,6 +64,9 @@ func NewServer(ctx context.Context, options option.V2RayWebsocketOptions, tlsCon BaseContext: func(net.Listener) context.Context { return ctx }, + ConnContext: func(ctx context.Context, c net.Conn) context.Context { + return log.ContextWithNewID(ctx) + }, } return server, nil } @@ -102,20 +110,19 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { s.invalidRequest(writer, request, 0, E.Cause(err, "upgrade websocket connection")) return } - var metadata M.Metadata - metadata.Source = sHttp.SourceAddress(request) - conn = NewConn(wsConn, metadata.Source.TCPAddr(), ws.StateServerSide) + source := sHttp.SourceAddress(request) + conn = NewConn(wsConn, source, ws.StateServerSide) if len(earlyData) > 0 { conn = bufio.NewCachedConn(conn, buf.As(earlyData)) } - s.handler.NewConnection(request.Context(), conn, metadata) + s.handler.NewConnectionEx(v2rayhttp.DupContext(request.Context()), conn, source, M.Socksaddr{}, nil) } func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) { if statusCode > 0 { writer.WriteHeader(statusCode) } - s.handler.NewError(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) + s.logger.ErrorContext(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) } func (s *Server) Network() []string { diff --git a/transport/v2raywebsocket/writer.go b/transport/v2raywebsocket/writer.go index 5bd0d0a14f..cd5a5d6a92 100644 --- a/transport/v2raywebsocket/writer.go +++ b/transport/v2raywebsocket/writer.go @@ -63,10 +63,10 @@ func (w *Writer) WriteBuffer(buffer *buf.Buffer) error { if !w.isServer { maskKey := rand.Uint32() binary.BigEndian.PutUint32(header[1+payloadBitLength:], maskKey) - ws.Cipher(data, *(*[4]byte)(header[1+payloadBitLength:]), 0) + ws.Cipher(data, [4]byte(header[1+payloadBitLength:]), 0) } - return w.writer.WriteBuffer(buffer) + return wrapWsError(w.writer.WriteBuffer(buffer)) } func (w *Writer) FrontHeadroom() int { diff --git a/transport/v2rayxhttp/client.go b/transport/v2rayxhttp/client.go new file mode 100644 index 0000000000..c11466b323 --- /dev/null +++ b/transport/v2rayxhttp/client.go @@ -0,0 +1,368 @@ +package xhttp + +import ( + "context" + gotls "crypto/tls" + "io" + "net/http" + "net/http/httptrace" + "net/url" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/sagernet/quic-go" + "github.com/sagernet/quic-go/http3" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/common/xray/buf" + "github.com/sagernet/sing-box/common/xray/net" + "github.com/sagernet/sing-box/common/xray/pipe" + "github.com/sagernet/sing-box/common/xray/signal/done" + "github.com/sagernet/sing-box/common/xray/uuid" + "github.com/sagernet/sing-box/option" + qtls "github.com/sagernet/sing-quic" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + sHTTP "github.com/sagernet/sing/protocol/http" + "github.com/sagernet/sing/service" + "golang.org/x/net/http2" +) + +type Client struct { + ctx context.Context + options *option.V2RayXHTTPOptions + baseRequestURL url.URL + baseRequestURL2 url.URL + getHTTPClient func() (DialerClient, *XmuxClient) + getHTTPClient2 func() (DialerClient, *XmuxClient) +} + +func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayXHTTPOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) { + if options.Mode == "" { + return nil, E.New("mode is not set") + } + dest := serverAddr + baseRequestURL, err := getBaseRequestURL(&options.V2RayXHTTPBaseOptions, dest, tlsConfig) + if err != nil { + return nil, err + } + var xmuxOptions option.V2RayXHTTPXmuxOptions + if options.Xmux != nil { + xmuxOptions = *options.Xmux + } + xmuxManager := NewXmuxManager(xmuxOptions, func() XmuxConn { + return createHTTPClient(dest, dialer, &options.V2RayXHTTPBaseOptions, tlsConfig) + }) + getHTTPClient := func() (DialerClient, *XmuxClient) { + xmuxClient := xmuxManager.GetXmuxClient(ctx) + return xmuxClient.XmuxConn.(DialerClient), xmuxClient + } + baseRequestURL2 := baseRequestURL + getHTTPClient2 := getHTTPClient + if options.Download != nil { + options2 := options.Download + dialer2 := dialer + if options2.Detour != "" { + var ok bool + dialer2, ok = service.FromContext[adapter.OutboundManager](ctx).Outbound(options2.Detour) + if !ok { + return nil, E.New("outbound detour not found: ", options2.Detour) + } + } + dest2 := options2.ServerOptions.Build() + var tlsConfig2 tls.Config + if options2.TLS != nil { + tlsConfig2, err = tls.NewClient(ctx, logger.NOP(), options2.Server, common.PtrValueOrDefault(options2.TLS)) + if err != nil { + return nil, err + } + } + baseRequestURL2, err = getBaseRequestURL(&options2.V2RayXHTTPBaseOptions, dest2, tlsConfig2) + if err != nil { + return nil, err + } + var xmuxOptions2 option.V2RayXHTTPXmuxOptions + if options2.Xmux != nil { + xmuxOptions2 = *options2.Xmux + } + xmuxManager2 := NewXmuxManager(xmuxOptions2, func() XmuxConn { + return createHTTPClient(dest2, dialer2, &options2.V2RayXHTTPBaseOptions, tlsConfig2) + }) + getHTTPClient2 = func() (DialerClient, *XmuxClient) { + xmuxClient2 := xmuxManager2.GetXmuxClient(ctx) + return xmuxClient2.XmuxConn.(DialerClient), xmuxClient2 + } + } + return &Client{ + ctx: ctx, + options: &options, + getHTTPClient: getHTTPClient, + getHTTPClient2: getHTTPClient2, + baseRequestURL: baseRequestURL, + baseRequestURL2: baseRequestURL2, + }, nil +} + +func (c *Client) DialContext(ctx context.Context) (net.Conn, error) { + options := c.options + mode := c.options.Mode + sessionId := "" + if c.options.Mode != "stream-one" { + sessionIdUuid := uuid.New() + sessionId = sessionIdUuid.String() + } + requestURL := c.baseRequestURL + requestURL2 := c.baseRequestURL2 + httpClient, xmuxClient := c.getHTTPClient() + httpClient2, xmuxClient2 := c.getHTTPClient2() + if xmuxClient != nil { + xmuxClient.OpenUsage.Add(1) + } + if xmuxClient2 != nil && xmuxClient2 != xmuxClient { + xmuxClient2.OpenUsage.Add(1) + } + var closed atomic.Int32 + reader, writer := io.Pipe() + conn := splitConn{ + writer: writer, + onClose: func() { + if closed.Add(1) > 1 { + return + } + if xmuxClient != nil { + xmuxClient.OpenUsage.Add(-1) + } + if xmuxClient2 != nil && xmuxClient2 != xmuxClient { + xmuxClient2.OpenUsage.Add(-1) + } + }, + } + var err error + if mode == "stream-one" { + requestURL.Path = options.GetNormalizedPath() + if xmuxClient != nil { + xmuxClient.LeftRequests.Add(-1) + } + conn.reader, conn.remoteAddr, conn.localAddr, err = httpClient.OpenStream(ctx, requestURL.String(), sessionId, reader, false) + if err != nil { // browser dialer only + return nil, err + } + return &conn, nil + } else { // stream-down + if xmuxClient2 != nil { + xmuxClient2.LeftRequests.Add(-1) + } + conn.reader, conn.remoteAddr, conn.localAddr, err = httpClient2.OpenStream(ctx, requestURL2.String(), sessionId, nil, false) + if err != nil { // browser dialer only + return nil, err + } + } + if mode == "stream-up" { + if xmuxClient != nil { + xmuxClient.LeftRequests.Add(-1) + } + _, _, _, err = httpClient.OpenStream(ctx, requestURL.String(), sessionId, reader, true) + if err != nil { // browser dialer only + return nil, err + } + return &conn, nil + } + scMaxEachPostBytes := options.GetNormalizedScMaxEachPostBytes() + scMinPostsIntervalMs := options.GetNormalizedScMinPostsIntervalMs() + if scMaxEachPostBytes.From <= buf.Size { + panic("`scMaxEachPostBytes` should be bigger than " + strconv.Itoa(buf.Size)) + } + maxUploadSize := scMaxEachPostBytes.Rand() + // WithSizeLimit(0) will still allow single bytes to pass, and a lot of + // code relies on this behavior. Subtract 1 so that together with + // uploadWriter wrapper, exact size limits can be enforced + // uploadPipeReader, uploadPipeWriter := pipe.New(pipe.WithSizeLimit(maxUploadSize - 1)) + uploadPipeReader, uploadPipeWriter := pipe.New(pipe.WithSizeLimit(maxUploadSize - buf.Size)) + conn.writer = uploadWriter{ + uploadPipeWriter, + maxUploadSize, + } + go func() { + var seq int64 + var lastWrite time.Time + for { + wroteRequest := done.New() + ctx := httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{ + WroteRequest: func(httptrace.WroteRequestInfo) { + wroteRequest.Close() + }, + }) + // this intentionally makes a shallow-copy of the struct so we + // can reassign Path (potentially concurrently) + url := requestURL + seqStr := strconv.FormatInt(seq, 10) + seq += 1 + if scMinPostsIntervalMs.From > 0 { + time.Sleep(time.Duration(scMinPostsIntervalMs.Rand())*time.Millisecond - time.Since(lastWrite)) + } + // by offloading the uploads into a buffered pipe, multiple conn.Write + // calls get automatically batched together into larger POST requests. + // without batching, bandwidth is extremely limited. + chunk, err := uploadPipeReader.ReadMultiBuffer() + if err != nil { + break + } + lastWrite = time.Now() + if xmuxClient != nil && (xmuxClient.LeftRequests.Add(-1) <= 0 || + (xmuxClient.UnreusableAt != time.Time{} && lastWrite.After(xmuxClient.UnreusableAt))) { + httpClient, xmuxClient = c.getHTTPClient() + } + go func() { + err := httpClient.PostPacket( + ctx, + url.String(), + sessionId, + seqStr, + &buf.MultiBufferContainer{MultiBuffer: chunk}, + int64(chunk.Len()), + ) + wroteRequest.Close() + if err != nil { + uploadPipeReader.Interrupt() + } + }() + if _, ok := httpClient.(*DefaultDialerClient); ok { + select { + case <-ctx.Done(): + case <-wroteRequest.Wait(): + } + + } + } + }() + return &conn, nil +} + +func (c *Client) Close() error { + return nil +} + +func decideHTTPVersion(tlsConfig tls.Config) string { + if tlsConfig == nil || len(tlsConfig.NextProtos()) == 0 || tlsConfig.NextProtos()[0] == "http/1.1" { + return "1.1" + } + if tlsConfig.NextProtos()[0] == "h3" { + return "3" + } + return "2" +} + +func getBaseRequestURL(options *option.V2RayXHTTPBaseOptions, dest M.Socksaddr, tlsConfig tls.Config) (url.URL, error) { + var requestURL url.URL + if tlsConfig == nil { + requestURL.Scheme = "http" + } else { + requestURL.Scheme = "https" + } + requestURL.Host = options.Host + if requestURL.Host == "" && tlsConfig != nil { + requestURL.Host = tlsConfig.ServerName() + } + if requestURL.Host == "" { + requestURL.Host = dest.AddrString() + } + requestURL.Path = options.Path + if err := sHTTP.URLSetPath(&requestURL, options.Path); err != nil { + return requestURL, E.New(err, "parse path") + } + if !strings.HasPrefix(requestURL.Path, "/") { + requestURL.Path = "/" + requestURL.Path + } + requestURL.Path = options.GetNormalizedPath() + requestURL.RawQuery = options.GetNormalizedQuery() + return requestURL, nil +} + +func createHTTPClient(dest M.Socksaddr, dialer N.Dialer, options *option.V2RayXHTTPBaseOptions, tlsConfig tls.Config) DialerClient { + httpVersion := decideHTTPVersion(tlsConfig) + dialContext := func(ctxInner context.Context) (net.Conn, error) { + conn, err := dialer.DialContext(ctxInner, "tcp", dest) + if err != nil { + return nil, err + } + if httpVersion != "3" && tlsConfig != nil { + return tls.ClientHandshake(ctxInner, conn, tlsConfig) + } + return conn, nil + } + var keepAlivePeriod time.Duration + if options.Xmux != nil { + keepAlivePeriod = time.Duration(options.Xmux.HKeepAlivePeriod) * time.Second + } + var transport http.RoundTripper + switch httpVersion { + case "3": + if keepAlivePeriod == 0 { + keepAlivePeriod = net.QuicgoH3KeepAlivePeriod + } + if keepAlivePeriod < 0 { + keepAlivePeriod = 0 + } + quicConfig := &quic.Config{ + MaxIdleTimeout: net.ConnIdleTimeout, + // these two are defaults of quic-go/http3. the default of quic-go (no + // http3) is different, so it is hardcoded here for clarity. + // https://github.com/quic-go/quic-go/blob/b8ea5c798155950fb5bbfdd06cad1939c9355878/http3/client.go#L36-L39 + MaxIncomingStreams: -1, + KeepAlivePeriod: keepAlivePeriod, + } + transport = &http3.Transport{ + QUICConfig: quicConfig, + Dial: func(ctx context.Context, addr string, tlsCfg *gotls.Config, cfg *quic.Config) (*quic.Conn, error) { + udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, dest) + if dErr != nil { + return nil, dErr + } + return qtls.DialEarly(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), tlsConfig, cfg) + }, + } + case "2": + if keepAlivePeriod == 0 { + keepAlivePeriod = net.ChromeH2KeepAlivePeriod + } + if keepAlivePeriod < 0 { + keepAlivePeriod = 0 + } + transport = &http2.Transport{ + DialTLSContext: func(ctxInner context.Context, network string, addr string, cfg *gotls.Config) (net.Conn, error) { + return dialContext(ctxInner) + }, + IdleConnTimeout: net.ConnIdleTimeout, + ReadIdleTimeout: keepAlivePeriod, + } + default: + httpDialContext := func(ctxInner context.Context, network string, addr string) (net.Conn, error) { + return dialContext(ctxInner) + } + transport = &http.Transport{ + DialTLSContext: httpDialContext, + DialContext: httpDialContext, + IdleConnTimeout: net.ConnIdleTimeout, + // chunked transfer download with KeepAlives is buggy with + // http.Client and our custom dial context. + DisableKeepAlives: true, + } + } + client := &DefaultDialerClient{ + options: options, + client: &http.Client{ + Transport: transport, + }, + httpVersion: httpVersion, + uploadRawPool: &sync.Pool{}, + dialUploadConn: dialContext, + } + return client +} diff --git a/transport/v2rayxhttp/conn.go b/transport/v2rayxhttp/conn.go new file mode 100644 index 0000000000..7da8e916c1 --- /dev/null +++ b/transport/v2rayxhttp/conn.go @@ -0,0 +1,108 @@ +package xhttp + +import ( + "bufio" + "io" + "net" + "net/http" + "sync" + "time" + + "github.com/sagernet/sing-box/common/xray/signal/done" +) + +type splitConn struct { + writer io.WriteCloser + reader io.ReadCloser + remoteAddr net.Addr + localAddr net.Addr + onClose func() +} + +func (c *splitConn) Write(b []byte) (int, error) { + return c.writer.Write(b) +} + +func (c *splitConn) Read(b []byte) (int, error) { + return c.reader.Read(b) +} + +func (c *splitConn) Close() error { + if c.onClose != nil { + c.onClose() + } + + err := c.writer.Close() + err2 := c.reader.Close() + if err != nil { + return err + } + + if err2 != nil { + return err + } + + return nil +} + +func (c *splitConn) LocalAddr() net.Addr { + return c.localAddr +} + +func (c *splitConn) RemoteAddr() net.Addr { + return c.remoteAddr +} + +func (c *splitConn) SetDeadline(t time.Time) error { + // TODO cannot do anything useful + return nil +} + +func (c *splitConn) SetReadDeadline(t time.Time) error { + // TODO cannot do anything useful + return nil +} + +func (c *splitConn) SetWriteDeadline(t time.Time) error { + // TODO cannot do anything useful + return nil +} + +type H1Conn struct { + UnreadedResponsesCount int + RespBufReader *bufio.Reader + net.Conn +} + +func NewH1Conn(conn net.Conn) *H1Conn { + return &H1Conn{ + RespBufReader: bufio.NewReader(conn), + Conn: conn, + } +} + +type httpServerConn struct { + sync.Mutex + *done.Instance + io.Reader // no need to Close request.Body + http.ResponseWriter +} + +func (c *httpServerConn) Write(b []byte) (int, error) { + c.Lock() + defer c.Unlock() + if c.Done() { + return 0, io.ErrClosedPipe + } + n, err := c.ResponseWriter.Write(b) + if err == nil { + c.ResponseWriter.(http.Flusher).Flush() + } + return n, err +} + +func (c *httpServerConn) Close() error { + c.Lock() + defer c.Unlock() + return c.Instance.Close() +} diff --git a/transport/v2rayxhttp/dialer.go b/transport/v2rayxhttp/dialer.go new file mode 100644 index 0000000000..9c17baf463 --- /dev/null +++ b/transport/v2rayxhttp/dialer.go @@ -0,0 +1,333 @@ +package xhttp + +import ( + "bytes" + "context" + "encoding/base64" + "fmt" + "io" + "net" + "net/http" + "net/http/httptrace" + "strings" + "sync" + + common "github.com/sagernet/sing-box/common/xray" + "github.com/sagernet/sing-box/common/xray/signal/done" + "github.com/sagernet/sing-box/option" +) + +// interface to abstract between use of browser dialer, vs net/http +type DialerClient interface { + IsClosed() bool + + // ctx, url, sessionId, body, uploadOnly + OpenStream(context.Context, string, string, io.Reader, bool) (io.ReadCloser, net.Addr, net.Addr, error) + + // ctx, url, sessionId, seqStr, body, contentLength + PostPacket(context.Context, string, string, string, io.Reader, int64) error +} + +// implements xhttp.DialerClient in terms of direct network connections +type DefaultDialerClient struct { + options *option.V2RayXHTTPBaseOptions + client *http.Client + closed bool + httpVersion string + // pool of net.Conn, created using dialUploadConn + uploadRawPool *sync.Pool + dialUploadConn func(ctxInner context.Context) (net.Conn, error) +} + +func (c *DefaultDialerClient) IsClosed() bool { + return c.closed +} + +func (c *DefaultDialerClient) OpenStream(ctx context.Context, url string, sessionId string, body io.Reader, uploadOnly bool) (wrc io.ReadCloser, remoteAddr, localAddr net.Addr, err error) { + // this is done when the TCP/UDP connection to the server was established, + // and we can unblock the Dial function and print correct net addresses in + // logs + gotConn := done.New() + ctx = httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{ + GotConn: func(connInfo httptrace.GotConnInfo) { + remoteAddr = connInfo.Conn.RemoteAddr() + localAddr = connInfo.Conn.LocalAddr() + gotConn.Close() + }, + }) + method := "GET" // stream-down + if body != nil { + method = c.options.GetNormalizedUplinkHTTPMethod() // stream-up/one + } + req, _ := http.NewRequestWithContext(context.WithoutCancel(ctx), method, url, body) + req.Header = c.options.GetRequestHeader() + length := int(c.options.GetNormalizedXPaddingBytes().Rand()) + config := XPaddingConfig{Length: length} + if c.options.XPaddingObfsMode { + config.Placement = XPaddingPlacement{ + Placement: c.options.XPaddingPlacement, + Key: c.options.XPaddingKey, + Header: c.options.XPaddingHeader, + RawURL: url, + } + config.Method = PaddingMethod(c.options.XPaddingMethod) + } else { + config.Placement = XPaddingPlacement{ + Placement: option.PlacementQueryInHeader, + Key: "x_padding", + Header: "Referer", + RawURL: url, + } + } + ApplyXPaddingToRequest(req, config) + ApplyMetaToRequest(c.options, req, sessionId, "") + if method == c.options.GetNormalizedUplinkHTTPMethod() && !c.options.NoGRPCHeader { + req.Header.Set("Content-Type", "application/grpc") + } + wrc = &WaitReadCloser{ctx: ctx, Wait: make(chan struct{})} + go func() { + resp, err := c.client.Do(req) + if err != nil { + if !uploadOnly { // stream-down is enough + c.closed = true + } + gotConn.Close() + wrc.Close() + return + } + if resp.StatusCode != 200 || uploadOnly { // stream-up + io.Copy(io.Discard, resp.Body) + resp.Body.Close() // if it is called immediately, the upload will be interrupted also + wrc.Close() + return + } + wrc.(*WaitReadCloser).Set(resp.Body) + }() + select { + case <-gotConn.Wait(): + case <-ctx.Done(): + } + return +} + +func (c *DefaultDialerClient) PostPacket(ctx context.Context, url string, sessionId string, seqStr string, body io.Reader, contentLength int64) error { + var encodedData string + dataPlacement := c.options.GetNormalizedUplinkDataPlacement() + if dataPlacement != option.PlacementBody { + data, err := io.ReadAll(body) + if err != nil { + return err + } + encodedData = base64.RawURLEncoding.EncodeToString(data) + body = nil + contentLength = 0 + } + method := c.options.GetNormalizedUplinkHTTPMethod() + req, err := http.NewRequestWithContext(context.WithoutCancel(ctx), method, url, body) + if err != nil { + return err + } + req.ContentLength = contentLength + req.Header = c.options.GetRequestHeader() + if dataPlacement != option.PlacementBody { + key := c.options.UplinkDataKey + chunkSize := int(c.options.UplinkChunkSize) + switch dataPlacement { + case option.PlacementHeader: + for i := 0; i < len(encodedData); i += chunkSize { + end := i + chunkSize + if end > len(encodedData) { + end = len(encodedData) + } + chunk := encodedData[i:end] + headerKey := fmt.Sprintf("%s-%d", key, i/chunkSize) + req.Header.Set(headerKey, chunk) + } + + req.Header.Set(key+"-Length", fmt.Sprintf("%d", len(encodedData))) + req.Header.Set(key+"-Upstream", "1") + case option.PlacementCookie: + for i := 0; i < len(encodedData); i += chunkSize { + end := i + chunkSize + if end > len(encodedData) { + end = len(encodedData) + } + chunk := encodedData[i:end] + cookieName := fmt.Sprintf("%s_%d", key, i/chunkSize) + req.AddCookie(&http.Cookie{Name: cookieName, Value: chunk}) + } + + req.AddCookie(&http.Cookie{Name: key + "_upstream", Value: "1"}) + } + } + length := int(c.options.GetNormalizedXPaddingBytes().Rand()) + config := XPaddingConfig{Length: length} + if c.options.XPaddingObfsMode { + config.Placement = XPaddingPlacement{ + Placement: c.options.XPaddingPlacement, + Key: c.options.XPaddingKey, + Header: c.options.XPaddingHeader, + RawURL: url, + } + config.Method = PaddingMethod(c.options.XPaddingMethod) + } else { + config.Placement = XPaddingPlacement{ + Placement: option.PlacementQueryInHeader, + Key: "x_padding", + Header: "Referer", + RawURL: url, + } + } + ApplyXPaddingToRequest(req, config) + ApplyMetaToRequest(c.options, req, sessionId, seqStr) + if c.httpVersion != "1.1" { + resp, err := c.client.Do(req) + if err != nil { + c.closed = true + return err + } + io.Copy(io.Discard, resp.Body) + defer resp.Body.Close() + } else { + // stringify the entire HTTP/1.1 request so it can be + // safely retried. if instead req.Write is called multiple + // times, the body is already drained after the first + // request + requestBuff := new(bytes.Buffer) + common.Must(req.Write(requestBuff)) + var uploadConn any + var h1UploadConn *H1Conn + for { + uploadConn = c.uploadRawPool.Get() + newConnection := uploadConn == nil + if newConnection { + newConn, err := c.dialUploadConn(context.WithoutCancel(ctx)) + if err != nil { + return err + } + h1UploadConn = NewH1Conn(newConn) + uploadConn = h1UploadConn + } else { + h1UploadConn = uploadConn.(*H1Conn) + + // TODO: Replace 0 here with a config value later + // Or add some other condition for optimization purposes + if h1UploadConn.UnreadedResponsesCount > 0 { + resp, err := http.ReadResponse(h1UploadConn.RespBufReader, req) + if err != nil { + c.closed = true + return fmt.Errorf("error while reading response: %s", err.Error()) + } + io.Copy(io.Discard, resp.Body) + defer resp.Body.Close() + if resp.StatusCode != 200 { + return fmt.Errorf("got non-200 error response code: %d", resp.StatusCode) + } + } + } + _, err := h1UploadConn.Write(requestBuff.Bytes()) + // if the write failed, we try another connection from + // the pool, until the write on a new connection fails. + // failed writes to a pooled connection are normal when + // the connection has been closed in the meantime. + if err == nil { + break + } else if newConnection { + return err + } + } + c.uploadRawPool.Put(uploadConn) + } + return nil +} + +type WaitReadCloser struct { + ctx context.Context + Wait chan struct{} + io.ReadCloser +} + +func (w *WaitReadCloser) Set(rc io.ReadCloser) { + w.ReadCloser = rc + defer func() { + if recover() != nil { + rc.Close() + } + }() + close(w.Wait) +} + +func (w *WaitReadCloser) Read(b []byte) (int, error) { + select { + case <-w.ctx.Done(): + return 0, w.ctx.Err() + default: + } + + if w.ReadCloser == nil { + select { + case <-w.ctx.Done(): + return 0, w.ctx.Err() + case <-w.Wait: + } + if w.ReadCloser == nil { + return 0, io.ErrClosedPipe + } + } + return w.ReadCloser.Read(b) +} + +func (w *WaitReadCloser) Close() error { + if w.ReadCloser != nil { + return w.ReadCloser.Close() + } + defer func() { + if recover() != nil && w.ReadCloser != nil { + w.ReadCloser.Close() + } + }() + close(w.Wait) + return nil +} + +func ApplyMetaToRequest(options *option.V2RayXHTTPBaseOptions, req *http.Request, sessionId string, seqStr string) { + sessionPlacement := options.GetNormalizedSessionPlacement() + seqPlacement := options.GetNormalizedSeqPlacement() + sessionKey := options.GetNormalizedSessionKey() + seqKey := options.GetNormalizedSeqKey() + if sessionId != "" { + switch sessionPlacement { + case option.PlacementPath: + req.URL.Path = appendToPath(req.URL.Path, sessionId) + case option.PlacementQuery: + q := req.URL.Query() + q.Set(sessionKey, sessionId) + req.URL.RawQuery = q.Encode() + case option.PlacementHeader: + req.Header.Set(sessionKey, sessionId) + case option.PlacementCookie: + req.AddCookie(&http.Cookie{Name: sessionKey, Value: sessionId}) + } + } + if seqStr != "" { + switch seqPlacement { + case option.PlacementPath: + req.URL.Path = appendToPath(req.URL.Path, seqStr) + case option.PlacementQuery: + q := req.URL.Query() + q.Set(seqKey, seqStr) + req.URL.RawQuery = q.Encode() + case option.PlacementHeader: + req.Header.Set(seqKey, seqStr) + case option.PlacementCookie: + req.AddCookie(&http.Cookie{Name: seqKey, Value: seqStr}) + } + } +} + +func appendToPath(path, value string) string { + if strings.HasSuffix(path, "/") { + return path + value + } + return path + "/" + value +} diff --git a/transport/v2rayxhttp/http.go b/transport/v2rayxhttp/http.go new file mode 100644 index 0000000000..03a47da71c --- /dev/null +++ b/transport/v2rayxhttp/http.go @@ -0,0 +1,41 @@ +package xhttp + +import ( + "net/http" + "strings" + + "github.com/sagernet/sing-box/common/xray/net" + "github.com/sagernet/sing-box/common/xray/signal/done" +) + +type httpSession struct { + uploadQueue *uploadQueue + // for as long as the GET request is not opened by the client, this will be + // open ("undone"), and the session may be expired within a certain TTL. + // after the client connects, this becomes "done" and the session lives as + // long as the GET request. + isFullyConnected *done.Instance +} + +func parseXForwardedFor(header http.Header) []net.Address { + xff := header.Get("X-Forwarded-For") + if xff == "" { + return nil + } + list := strings.Split(xff, ",") + addrs := make([]net.Address, 0, len(list)) + for _, proxy := range list { + addrs = append(addrs, net.ParseAddress(proxy)) + } + return addrs +} + +func isValidHTTPHost(request string, config string) bool { + r := strings.ToLower(request) + c := strings.ToLower(config) + if strings.Contains(r, ":") { + h, _, _ := net.SplitHostPort(r) + return h == c + } + return r == c +} diff --git a/transport/v2rayxhttp/mux.go b/transport/v2rayxhttp/mux.go new file mode 100644 index 0000000000..f753aed5ef --- /dev/null +++ b/transport/v2rayxhttp/mux.go @@ -0,0 +1,104 @@ +package xhttp + +import ( + "context" + "crypto/rand" + "math" + "math/big" + "sync" + "sync/atomic" + "time" + + "github.com/sagernet/sing-box/option" +) + +type XmuxConn interface { + IsClosed() bool +} + +type XmuxClient struct { + XmuxConn XmuxConn + OpenUsage atomic.Int32 + leftUsage int32 + LeftRequests atomic.Int32 + UnreusableAt time.Time +} + +type XmuxManager struct { + options option.V2RayXHTTPXmuxOptions + concurrency int32 + connections int32 + newConnFunc func() XmuxConn + xmuxClients []*XmuxClient + mtx sync.Mutex +} + +func NewXmuxManager(options option.V2RayXHTTPXmuxOptions, newConnFunc func() XmuxConn) *XmuxManager { + return &XmuxManager{ + options: options, + concurrency: options.GetNormalizedMaxConcurrency().Rand(), + connections: options.GetNormalizedMaxConnections().Rand(), + newConnFunc: newConnFunc, + xmuxClients: make([]*XmuxClient, 0), + } +} + +func (m *XmuxManager) newXmuxClient() *XmuxClient { + xmuxClient := &XmuxClient{ + XmuxConn: m.newConnFunc(), + leftUsage: -1, + } + if x := m.options.GetNormalizedCMaxReuseTimes().Rand(); x > 0 { + xmuxClient.leftUsage = x - 1 + } + xmuxClient.LeftRequests.Store(math.MaxInt32) + if x := m.options.GetNormalizedHMaxRequestTimes().Rand(); x > 0 { + xmuxClient.LeftRequests.Store(x) + } + if x := m.options.GetNormalizedHMaxReusableSecs().Rand(); x > 0 { + xmuxClient.UnreusableAt = time.Now().Add(time.Duration(x) * time.Second) + } + m.xmuxClients = append(m.xmuxClients, xmuxClient) + return xmuxClient +} + +func (m *XmuxManager) GetXmuxClient(ctx context.Context) *XmuxClient { + m.mtx.Lock() + defer m.mtx.Unlock() + for i := 0; i < len(m.xmuxClients); { + xmuxClient := m.xmuxClients[i] + if xmuxClient.XmuxConn.IsClosed() || + xmuxClient.leftUsage == 0 || + xmuxClient.LeftRequests.Load() <= 0 || + (xmuxClient.UnreusableAt != time.Time{} && time.Now().After(xmuxClient.UnreusableAt)) { + m.xmuxClients = append(m.xmuxClients[:i], m.xmuxClients[i+1:]...) + } else { + i++ + } + } + if len(m.xmuxClients) == 0 { + return m.newXmuxClient() + } + if m.connections > 0 && len(m.xmuxClients) < int(m.connections) { + return m.newXmuxClient() + } + xmuxClients := make([]*XmuxClient, 0) + if m.concurrency > 0 { + for _, xmuxClient := range m.xmuxClients { + if xmuxClient.OpenUsage.Load() < m.concurrency { + xmuxClients = append(xmuxClients, xmuxClient) + } + } + } else { + xmuxClients = m.xmuxClients + } + if len(xmuxClients) == 0 { + return m.newXmuxClient() + } + i, _ := rand.Int(rand.Reader, big.NewInt(int64(len(xmuxClients)))) + xmuxClient := xmuxClients[i.Int64()] + if xmuxClient.leftUsage > 0 { + xmuxClient.leftUsage -= 1 + } + return xmuxClient +} diff --git a/transport/v2rayxhttp/server.go b/transport/v2rayxhttp/server.go new file mode 100644 index 0000000000..e4c54dbd8b --- /dev/null +++ b/transport/v2rayxhttp/server.go @@ -0,0 +1,466 @@ +package xhttp + +import ( + "bytes" + "context" + "encoding/base64" + "fmt" + "io" + "net" + "net/http" + "net/url" + "os" + "strconv" + "strings" + "sync" + "time" + + "github.com/sagernet/quic-go" + "github.com/sagernet/quic-go/http3" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/common/xray/signal/done" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + qtls "github.com/sagernet/sing-quic" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + aTLS "github.com/sagernet/sing/common/tls" + sHttp "github.com/sagernet/sing/protocol/http" +) + +var _ adapter.V2RayServerTransport = (*Server)(nil) + +type Server struct { + ctx context.Context + logger logger.ContextLogger + tlsConfig tls.ServerConfig + quicConfig *quic.Config + handler adapter.V2RayServerTransportHandler + httpServer *http.Server + http3Server *http3.Server + localAddr net.Addr + options *option.V2RayXHTTPOptions + host string + path string + sessionMu sync.Mutex + sessions sync.Map +} + +func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayXHTTPOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { + server := &Server{ + ctx: ctx, + logger: logger, + tlsConfig: tlsConfig, + handler: handler, + options: &options, + host: options.Host, + path: options.GetNormalizedPath(), + } + if server.network() == N.NetworkTCP { + protocols := new(http.Protocols) + protocols.SetHTTP1(true) + protocols.SetUnencryptedHTTP2(true) + server.httpServer = &http.Server{ + Handler: server, + ReadHeaderTimeout: time.Second * 4, + MaxHeaderBytes: 8192, + Protocols: protocols, + BaseContext: func(net.Listener) context.Context { + return ctx + }, + ConnContext: func(ctx context.Context, c net.Conn) context.Context { + return log.ContextWithNewID(ctx) + }, + } + } else { + server.quicConfig = &quic.Config{ + DisablePathMTUDiscovery: !C.IsLinux && !C.IsWindows, + } + server.http3Server = &http3.Server{ + Handler: server, + } + } + return server, nil +} + +func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { + if len(s.host) > 0 && !isValidHTTPHost(request.Host, s.host) { + s.logger.ErrorContext(request.Context(), "failed to validate host, request:", request.Host, ", config:", s.host) + writer.WriteHeader(http.StatusNotFound) + return + } + if !strings.HasPrefix(request.URL.Path, s.path) { + s.logger.ErrorContext(request.Context(), "failed to validate path, request:", request.URL.Path, ", config:", s.path) + writer.WriteHeader(http.StatusNotFound) + return + } + writer.Header().Set("Access-Control-Allow-Origin", "*") + writer.Header().Set("Access-Control-Allow-Methods", "*") + length := int(s.options.GetNormalizedXPaddingBytes().Rand()) + config := XPaddingConfig{Length: length} + if s.options.XPaddingObfsMode { + config.Placement = XPaddingPlacement{ + Placement: s.options.XPaddingPlacement, + Key: s.options.XPaddingKey, + Header: s.options.XPaddingHeader, + } + config.Method = PaddingMethod(s.options.XPaddingMethod) + } else { + config.Placement = XPaddingPlacement{ + Placement: option.PlacementHeader, + Header: "X-Padding", + } + } + ApplyXPaddingToHeader(writer.Header(), config) + validRange := s.options.GetNormalizedXPaddingBytes() + paddingLength := 0 + referrer := request.Header.Get("Referer") + if referrer != "" { + if referrerURL, err := url.Parse(referrer); err == nil { + // Browser dialer cannot control the host part of referrer header, so only check the query + paddingLength = len(referrerURL.Query().Get("x_padding")) + } + } else { + paddingLength = len(request.URL.Query().Get("x_padding")) + } + if int32(paddingLength) < validRange.From || int32(paddingLength) > validRange.To { + s.logger.ErrorContext(request.Context(), "invalid x_padding length:", int32(paddingLength)) + writer.WriteHeader(http.StatusBadRequest) + return + } + sessionId, seqStr := ExtractMetaFromRequest(s.options, request, s.path) + if sessionId == "" && s.options.Mode != "" && s.options.Mode != "auto" && s.options.Mode != "stream-one" && s.options.Mode != "stream-up" { + s.logger.ErrorContext(request.Context(), "stream-one mode is not allowed") + writer.WriteHeader(http.StatusBadRequest) + return + } + forwardedAddrs := parseXForwardedFor(request.Header) + var remoteAddr net.Addr + var err error + remoteAddr, err = net.ResolveTCPAddr("tcp", request.RemoteAddr) + if err != nil { + remoteAddr = &net.TCPAddr{ + IP: []byte{0, 0, 0, 0}, + Port: 0, + } + } + if request.ProtoMajor == 3 { + remoteAddr = &net.UDPAddr{ + IP: remoteAddr.(*net.TCPAddr).IP, + Port: remoteAddr.(*net.TCPAddr).Port, + } + } + if len(forwardedAddrs) > 0 && forwardedAddrs[0].Family().IsIP() { + remoteAddr = &net.TCPAddr{ + IP: forwardedAddrs[0].IP(), + Port: 0, + } + } + var currentSession *httpSession + if sessionId != "" { + currentSession = s.upsertSession(sessionId) + } + scMaxEachPostBytes := int(s.options.GetNormalizedScMaxEachPostBytes().To) + uplinkHTTPMethod := s.options.GetNormalizedUplinkHTTPMethod() + isUplinkRequest := false + if uplinkHTTPMethod != "GET" && request.Method == uplinkHTTPMethod { + isUplinkRequest = true + } + uplinkDataPlacement := s.options.GetNormalizedUplinkDataPlacement() + uplinkDataKey := s.options.UplinkDataKey + switch uplinkDataPlacement { + case option.PlacementHeader: + if request.Header.Get(uplinkDataKey+"-Upstream") == "1" { + isUplinkRequest = true + } + case option.PlacementCookie: + if c, _ := request.Cookie(uplinkDataKey + "_upstream"); c != nil && c.Value == "1" { + isUplinkRequest = true + } + } + if isUplinkRequest && sessionId != "" { // stream-up, packet-up + if seqStr == "" { + if s.options.Mode != "" && s.options.Mode != "auto" && s.options.Mode != "stream-up" { + s.logger.ErrorContext(request.Context(), "stream-up mode is not allowed") + writer.WriteHeader(http.StatusBadRequest) + return + } + httpSC := &httpServerConn{ + Instance: done.New(), + Reader: request.Body, + ResponseWriter: writer, + } + err = currentSession.uploadQueue.Push(Packet{ + Reader: httpSC, + }) + if err != nil { + s.logger.InfoContext(request.Context(), err, "failed to upload (PushReader)") + writer.WriteHeader(http.StatusConflict) + } else { + writer.Header().Set("X-Accel-Buffering", "no") + writer.Header().Set("Cache-Control", "no-store") + writer.WriteHeader(http.StatusOK) + scStreamUpServerSecs := s.options.GetNormalizedScStreamUpServerSecs() + referrer := request.Header.Get("Referer") + if referrer != "" && scStreamUpServerSecs.To > 0 { + go func() { + for { + _, err := httpSC.Write(bytes.Repeat([]byte{'X'}, int(s.options.GetNormalizedXPaddingBytes().Rand()))) + if err != nil { + break + } + time.Sleep(time.Duration(scStreamUpServerSecs.Rand()) * time.Second) + } + }() + } + select { + case <-request.Context().Done(): + case <-httpSC.Wait(): + } + } + httpSC.Close() + return + } + if s.options.Mode != "" && s.options.Mode != "auto" && s.options.Mode != "packet-up" { + s.logger.ErrorContext(request.Context(), "packet-up mode is not allowed") + writer.WriteHeader(http.StatusBadRequest) + return + } + var payload []byte + if uplinkDataPlacement != option.PlacementBody { + var encodedStr string + switch uplinkDataPlacement { + case option.PlacementHeader: + dataLenStr := request.Header.Get(uplinkDataKey + "-Length") + if dataLenStr != "" { + dataLen, _ := strconv.Atoi(dataLenStr) + var chunks []string + i := 0 + for { + chunk := request.Header.Get(fmt.Sprintf("%s-%d", uplinkDataKey, i)) + if chunk == "" { + break + } + chunks = append(chunks, chunk) + i++ + } + encodedStr = strings.Join(chunks, "") + if len(encodedStr) != dataLen { + encodedStr = "" + } + } + case option.PlacementCookie: + var chunks []string + i := 0 + for { + cookieName := fmt.Sprintf("%s_%d", uplinkDataKey, i) + if c, _ := request.Cookie(cookieName); c != nil { + chunks = append(chunks, c.Value) + i++ + } else { + break + } + } + if len(chunks) > 0 { + encodedStr = strings.Join(chunks, "") + } + } + if encodedStr != "" { + payload, err = base64.RawURLEncoding.DecodeString(encodedStr) + } else { + s.logger.ErrorContext(request.Context(), err, "failed to extract data from key "+uplinkDataKey+" placed in "+uplinkDataPlacement) + writer.WriteHeader(http.StatusInternalServerError) + return + } + } else { + payload, err = io.ReadAll(io.LimitReader(request.Body, int64(scMaxEachPostBytes)+1)) + } + if len(payload) > scMaxEachPostBytes { + s.logger.ErrorContext(request.Context(), "Too large upload. scMaxEachPostBytes is set to ", scMaxEachPostBytes, "but request size exceed it. Adjust scMaxEachPostBytes on the server to be at least as large as client.") + writer.WriteHeader(http.StatusRequestEntityTooLarge) + return + } + if err != nil { + s.logger.InfoContext(request.Context(), err, "failed to upload (ReadAll)") + writer.WriteHeader(http.StatusInternalServerError) + return + } + seq, err := strconv.ParseUint(seqStr, 10, 64) + if err != nil { + s.logger.InfoContext(request.Context(), err, "failed to upload (ParseUint)") + writer.WriteHeader(http.StatusInternalServerError) + return + } + err = currentSession.uploadQueue.Push(Packet{ + Payload: payload, + Seq: seq, + }) + if err != nil { + s.logger.InfoContext(request.Context(), err, "failed to upload (PushPayload)") + writer.WriteHeader(http.StatusInternalServerError) + return + } + writer.WriteHeader(http.StatusOK) + } else if request.Method == "GET" || sessionId == "" { // stream-down, stream-one + if sessionId != "" { + // after GET is done, the connection is finished. disable automatic + // session reaping, and handle it in defer + currentSession.isFullyConnected.Close() + defer s.sessions.Delete(sessionId) + } + // magic header instructs nginx + apache to not buffer response body + writer.Header().Set("X-Accel-Buffering", "no") + // A web-compliant header telling all middleboxes to disable caching. + // Should be able to prevent overloading the cache, or stop CDNs from + // teeing the response stream into their cache, causing slowdowns. + writer.Header().Set("Cache-Control", "no-store") + if !s.options.NoSSEHeader { + // magic header to make the HTTP middle box consider this as SSE to disable buffer + writer.Header().Set("Content-Type", "text/event-stream") + } + writer.WriteHeader(http.StatusOK) + writer.(http.Flusher).Flush() + httpSC := &httpServerConn{ + Instance: done.New(), + Reader: request.Body, + ResponseWriter: writer, + } + conn := splitConn{ + writer: httpSC, + reader: httpSC, + remoteAddr: remoteAddr, + localAddr: s.localAddr, + } + if sessionId != "" { // if not stream-one + conn.reader = currentSession.uploadQueue + } + s.handler.NewConnectionEx(request.Context(), &conn, sHttp.SourceAddress(request), M.Socksaddr{}, func(it error) {}) + // "A ResponseWriter may not be used after [Handler.ServeHTTP] has returned." + select { + case <-request.Context().Done(): + case <-httpSC.Wait(): + } + conn.Close() + } else { + s.logger.ErrorContext(request.Context(), "unsupported method: ", request.Method) + writer.WriteHeader(http.StatusMethodNotAllowed) + } +} + +func (s *Server) Network() []string { + return []string{s.network()} +} + +func (s *Server) Serve(listener net.Listener) error { + if s.network() == N.NetworkTCP { + if s.tlsConfig != nil { + listener = aTLS.NewListener(listener, s.tlsConfig) + } + s.localAddr = listener.Addr() + return s.httpServer.Serve(listener) + } + return os.ErrInvalid +} + +func (s *Server) ServePacket(listener net.PacketConn) error { + if s.network() == N.NetworkUDP { + quicListener, err := qtls.ListenEarly(listener, s.tlsConfig, s.quicConfig) + if err != nil { + return err + } + s.localAddr = quicListener.Addr() + return s.http3Server.ServeListener(quicListener) + } + return os.ErrInvalid +} + +func (s *Server) Close() error { + if s.network() == N.NetworkTCP { + return common.Close(s.httpServer) + } + return common.Close(s.http3Server) +} + +func (s *Server) network() string { + if s.tlsConfig != nil && len(s.tlsConfig.NextProtos()) == 1 && s.tlsConfig.NextProtos()[0] == "h3" { + return N.NetworkUDP + } + return N.NetworkTCP +} + +func (s *Server) upsertSession(sessionId string) *httpSession { + // fast path + currentSessionAny, ok := s.sessions.Load(sessionId) + if ok { + return currentSessionAny.(*httpSession) + } + // slow path + s.sessionMu.Lock() + defer s.sessionMu.Unlock() + currentSessionAny, ok = s.sessions.Load(sessionId) + if ok { + return currentSessionAny.(*httpSession) + } + session := &httpSession{ + uploadQueue: NewUploadQueue(s.options.GetNormalizedScMaxBufferedPosts()), + isFullyConnected: done.New(), + } + s.sessions.Store(sessionId, session) + shouldReap := done.New() + go func() { + time.Sleep(30 * time.Second) + shouldReap.Close() + }() + go func() { + select { + case <-shouldReap.Wait(): + s.sessions.Delete(sessionId) + session.uploadQueue.Close() + case <-session.isFullyConnected.Wait(): + case <-s.ctx.Done(): + } + }() + return session +} + +func ExtractMetaFromRequest(options *option.V2RayXHTTPOptions, req *http.Request, path string) (sessionId string, seqStr string) { + sessionPlacement := options.GetNormalizedSessionPlacement() + seqPlacement := options.GetNormalizedSeqPlacement() + sessionKey := options.GetNormalizedSessionKey() + seqKey := options.GetNormalizedSeqKey() + if sessionPlacement == option.PlacementPath && seqPlacement == option.PlacementPath { + subpath := strings.Split(req.URL.Path[len(path):], "/") + if len(subpath) > 0 { + sessionId = subpath[0] + } + if len(subpath) > 1 { + seqStr = subpath[1] + } + return sessionId, seqStr + } + switch sessionPlacement { + case option.PlacementQuery: + sessionId = req.URL.Query().Get(sessionKey) + case option.PlacementHeader: + sessionId = req.Header.Get(sessionKey) + case option.PlacementCookie: + if cookie, e := req.Cookie(sessionKey); e == nil { + sessionId = cookie.Value + } + } + switch seqPlacement { + case option.PlacementQuery: + seqStr = req.URL.Query().Get(seqKey) + case option.PlacementHeader: + seqStr = req.Header.Get(seqKey) + case option.PlacementCookie: + if cookie, e := req.Cookie(seqKey); e == nil { + seqStr = cookie.Value + } + } + return sessionId, seqStr +} diff --git a/transport/v2rayxhttp/upload_queue.go b/transport/v2rayxhttp/upload_queue.go new file mode 100644 index 0000000000..32259a73be --- /dev/null +++ b/transport/v2rayxhttp/upload_queue.go @@ -0,0 +1,157 @@ +package xhttp + +// upload_queue is a specialized priorityqueue + channel to reorder generic +// packets by a sequence number + +import ( + "container/heap" + "io" + "runtime" + "sync" + + E "github.com/sagernet/sing/common/exceptions" +) + +type Packet struct { + Reader io.ReadCloser + Payload []byte + Seq uint64 +} + +type uploadQueue struct { + reader io.ReadCloser + nomore bool + pushedPackets chan Packet + writeCloseMutex sync.Mutex + heap uploadHeap + nextSeq uint64 + closed bool + maxPackets int +} + +func NewUploadQueue(maxPackets int) *uploadQueue { + return &uploadQueue{ + pushedPackets: make(chan Packet, maxPackets), + heap: uploadHeap{}, + nextSeq: 0, + closed: false, + maxPackets: maxPackets, + } +} + +func (h *uploadQueue) Push(p Packet) error { + h.writeCloseMutex.Lock() + defer h.writeCloseMutex.Unlock() + if h.closed { + return E.New("packet queue closed") + } + if h.nomore { + return E.New("h.reader already exists") + } + if p.Reader != nil { + h.nomore = true + } + h.pushedPackets <- p + return nil +} + +func (h *uploadQueue) Close() error { + h.writeCloseMutex.Lock() + defer h.writeCloseMutex.Unlock() + if !h.closed { + h.closed = true + runtime.Gosched() // hope Read() gets the packet + f: + for { + select { + case p := <-h.pushedPackets: + if p.Reader != nil { + h.reader = p.Reader + } + default: + break f + } + } + close(h.pushedPackets) + } + if h.reader != nil { + return h.reader.Close() + } + return nil +} + +func (h *uploadQueue) Read(b []byte) (int, error) { + if h.reader != nil { + return h.reader.Read(b) + } + if h.closed { + return 0, io.EOF + } + if len(h.heap) == 0 { + packet, more := <-h.pushedPackets + if !more { + return 0, io.EOF + } + if packet.Reader != nil { + h.reader = packet.Reader + return h.reader.Read(b) + } + heap.Push(&h.heap, packet) + } + for len(h.heap) > 0 { + packet := heap.Pop(&h.heap).(Packet) + n := 0 + + if packet.Seq == h.nextSeq { + copy(b, packet.Payload) + n = min(len(b), len(packet.Payload)) + + if n < len(packet.Payload) { + // partial read + packet.Payload = packet.Payload[n:] + heap.Push(&h.heap, packet) + } else { + h.nextSeq = packet.Seq + 1 + } + + return n, nil + } + // misordered packet + if packet.Seq > h.nextSeq { + if len(h.heap) > h.maxPackets { + // the "reassembly buffer" is too large, and we want to + // constrain memory usage somehow. let's tear down the + // connection, and hope the application retries. + return 0, E.New("packet queue is too large") + } + heap.Push(&h.heap, packet) + packet2, more := <-h.pushedPackets + if !more { + return 0, io.EOF + } + heap.Push(&h.heap, packet2) + } + } + return 0, nil +} + +// heap code directly taken from https://pkg.go.dev/container/heap +type uploadHeap []Packet + +func (h uploadHeap) Len() int { return len(h) } +func (h uploadHeap) Less(i, j int) bool { return h[i].Seq < h[j].Seq } +func (h uploadHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } + +func (h *uploadHeap) Push(x any) { + // Push and Pop use pointer receivers because they modify the slice's length, + // not just its contents. + *h = append(*h, x.(Packet)) +} + +func (h *uploadHeap) Pop() any { + old := *h + n := len(old) + x := old[n-1] + *h = old[0 : n-1] + return x +} diff --git a/transport/v2rayxhttp/writer.go b/transport/v2rayxhttp/writer.go new file mode 100644 index 0000000000..3c11e3b3ee --- /dev/null +++ b/transport/v2rayxhttp/writer.go @@ -0,0 +1,41 @@ +package xhttp + +import ( + common "github.com/sagernet/sing-box/common/xray" + "github.com/sagernet/sing-box/common/xray/buf" + "github.com/sagernet/sing-box/common/xray/pipe" +) + +// A wrapper around pipe that ensures the size limit is exactly honored. +// +// The MultiBuffer pipe accepts any single WriteMultiBuffer call even if that +// single MultiBuffer exceeds the size limit, and then starts blocking on the +// next WriteMultiBuffer call. This means that ReadMultiBuffer can return more +// bytes than the size limit. We work around this by splitting a potentially +// too large write up into multiple. +type uploadWriter struct { + *pipe.Writer + maxLen int32 +} + +func (w uploadWriter) Write(b []byte) (int, error) { + /* + capacity := int(w.maxLen - w.Len()) + if capacity > 0 && capacity < len(b) { + b = b[:capacity] + } + */ + + buffer := buf.MultiBufferContainer{} + common.Must2(buffer.Write(b)) + + var writed int + for _, buff := range buffer.MultiBuffer { + err := w.WriteMultiBuffer(buf.MultiBuffer{buff}) + if err != nil { + return writed, err + } + writed += int(buff.Len()) + } + return writed, nil +} diff --git a/transport/v2rayxhttp/xpadding.go b/transport/v2rayxhttp/xpadding.go new file mode 100644 index 0000000000..f9dbf86831 --- /dev/null +++ b/transport/v2rayxhttp/xpadding.go @@ -0,0 +1,268 @@ +package xhttp + +import ( + "crypto/rand" + "math" + "net/http" + "net/url" + "strings" + + "github.com/sagernet/sing-box/option" + "golang.org/x/net/http2/hpack" +) + +type PaddingMethod string + +const ( + PaddingMethodRepeatX PaddingMethod = "repeat-x" + PaddingMethodTokenish PaddingMethod = "tokenish" +) + +const charsetBase62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + +// Huffman encoding gives ~20% size reduction for base62 sequences +const avgHuffmanBytesPerCharBase62 = 0.8 + +const validationTolerance = 2 + +type XPaddingPlacement struct { + Placement string + Key string + Header string + RawURL string +} + +type XPaddingConfig struct { + Length int + Placement XPaddingPlacement + Method PaddingMethod +} + +func randStringFromCharset(n int, charset string) (string, bool) { + if n <= 0 || len(charset) == 0 { + return "", false + } + m := len(charset) + limit := byte(256 - (256 % m)) + result := make([]byte, n) + i := 0 + buf := make([]byte, 256) + for i < n { + if _, err := rand.Read(buf); err != nil { + return "", false + } + for _, rb := range buf { + if rb >= limit { + continue + } + result[i] = charset[int(rb)%m] + i++ + if i == n { + break + } + } + } + return string(result), true +} + +func absInt(x int) int { + if x < 0 { + return -x + } + return x +} + +func GenerateTokenishPaddingBase62(targetHuffmanBytes int) string { + n := int(math.Ceil(float64(targetHuffmanBytes) / avgHuffmanBytesPerCharBase62)) + if n < 1 { + n = 1 + } + randBase62Str, ok := randStringFromCharset(n, charsetBase62) + if !ok { + return "" + } + const maxIter = 150 + adjustChar := byte('X') + // Adjust until close enough + for iter := 0; iter < maxIter; iter++ { + currentLength := int(hpack.HuffmanEncodeLength(randBase62Str)) + diff := currentLength - targetHuffmanBytes + + if absInt(diff) <= validationTolerance { + return randBase62Str + } + if diff < 0 { + // Too small -> append padding char(s) + randBase62Str += string(adjustChar) + // Avoid a long run of identical chars + if adjustChar == 'X' { + adjustChar = 'Z' + } else { + adjustChar = 'X' + } + } else { + // Too big -> remove from the end + if len(randBase62Str) <= 1 { + return randBase62Str + } + randBase62Str = randBase62Str[:len(randBase62Str)-1] + } + } + return randBase62Str +} + +func GeneratePadding(method PaddingMethod, length int) string { + if length <= 0 { + return "" + } + // https://www.rfc-editor.org/rfc/rfc7541.html#appendix-B + // h2's HPACK Header Compression feature employs a huffman encoding using a static table. + // 'X' and 'Z' are assigned an 8 bit code, so HPACK compression won't change actual padding length on the wire. + // https://www.rfc-editor.org/rfc/rfc9204.html#section-4.1.2-2 + // h3's similar QPACK feature uses the same huffman table. + switch method { + case PaddingMethodRepeatX: + return strings.Repeat("X", length) + case PaddingMethodTokenish: + paddingValue := GenerateTokenishPaddingBase62(length) + if paddingValue == "" { + return strings.Repeat("X", length) + } + return paddingValue + default: + return strings.Repeat("X", length) + } +} + +func ApplyPaddingToCookie(req *http.Request, name, value string) { + if req == nil || name == "" || value == "" { + return + } + req.AddCookie(&http.Cookie{ + Name: name, + Value: value, + Path: "/", + }) +} + +func ApplyPaddingToQuery(u *url.URL, key, value string) { + if u == nil || key == "" || value == "" { + return + } + q := u.Query() + q.Set(key, value) + u.RawQuery = q.Encode() +} + +func ApplyXPaddingToHeader(h http.Header, config XPaddingConfig) { + if h == nil { + return + } + paddingValue := GeneratePadding(config.Method, config.Length) + switch p := config.Placement; p.Placement { + case option.PlacementHeader: + h.Set(p.Header, paddingValue) + case option.PlacementQueryInHeader: + u, err := url.Parse(p.RawURL) + if err != nil || u == nil { + return + } + u.RawQuery = p.Key + "=" + paddingValue + h.Set(p.Header, u.String()) + } +} + +func ApplyXPaddingToRequest(req *http.Request, config XPaddingConfig) { + if req == nil { + return + } + if req.Header == nil { + req.Header = make(http.Header) + } + placement := config.Placement.Placement + if placement == option.PlacementHeader || placement == option.PlacementQueryInHeader { + ApplyXPaddingToHeader(req.Header, config) + return + } + paddingValue := GeneratePadding(config.Method, config.Length) + switch placement { + case option.PlacementCookie: + ApplyPaddingToCookie(req, config.Placement.Key, paddingValue) + case option.PlacementQuery: + ApplyPaddingToQuery(req.URL, config.Placement.Key, paddingValue) + } +} + +func ExtractXPaddingFromRequest(options *option.V2RayXHTTPBaseOptions, req *http.Request, obfsMode bool) (string, string) { + if req == nil { + return "", "" + } + if !obfsMode { + referrer := req.Header.Get("Referer") + if referrer != "" { + if referrerURL, err := url.Parse(referrer); err == nil { + paddingValue := referrerURL.Query().Get("x_padding") + paddingPlacement := option.PlacementQueryInHeader + "=Referer, key=x_padding" + return paddingValue, paddingPlacement + } + } else { + paddingValue := req.URL.Query().Get("x_padding") + return paddingValue, option.PlacementQuery + ", key=x_padding" + } + } + key := options.XPaddingKey + header := options.XPaddingHeader + if cookie, err := req.Cookie(key); err == nil { + if cookie != nil && cookie.Value != "" { + paddingValue := cookie.Value + paddingPlacement := option.PlacementCookie + ", key=" + key + return paddingValue, paddingPlacement + } + } + headerValue := req.Header.Get(header) + if headerValue != "" { + if options.XPaddingPlacement == option.PlacementHeader { + paddingPlacement := option.PlacementHeader + "=" + header + return headerValue, paddingPlacement + } + + if parsedURL, err := url.Parse(headerValue); err == nil { + paddingPlacement := option.PlacementQueryInHeader + "=" + header + ", key=" + key + + return parsedURL.Query().Get(key), paddingPlacement + } + } + queryValue := req.URL.Query().Get(key) + if queryValue != "" { + paddingPlacement := option.PlacementQuery + ", key=" + key + return queryValue, paddingPlacement + } + return "", "" +} + +func IsPaddingValid(options *option.V2RayXHTTPBaseOptions, paddingValue string, from, to int32, method PaddingMethod) bool { + if paddingValue == "" { + return false + } + if to <= 0 { + r := options.GetNormalizedXPaddingBytes() + from, to = r.From, r.To + } + switch method { + case PaddingMethodRepeatX: + n := int32(len(paddingValue)) + return n >= from && n <= to + case PaddingMethodTokenish: + const tolerance = int32(validationTolerance) + n := int32(hpack.HuffmanEncodeLength(paddingValue)) + f := from - tolerance + t := to + tolerance + if f < 0 { + f = 0 + } + return n >= f && n <= t + default: + n := int32(len(paddingValue)) + return n >= from && n <= to + } +} diff --git a/transport/wireguard/client_bind.go b/transport/wireguard/client_bind.go index 6c534532a1..9c59e34618 100644 --- a/transport/wireguard/client_bind.go +++ b/transport/wireguard/client_bind.go @@ -10,6 +10,7 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/service" @@ -21,10 +22,10 @@ var _ conn.Bind = (*ClientBind)(nil) type ClientBind struct { ctx context.Context + logger logger.Logger pauseManager pause.Manager bindCtx context.Context bindDone context.CancelFunc - errorHandler E.Handler dialer N.Dialer reservedForEndpoint map[netip.AddrPort][3]uint8 connAccess sync.Mutex @@ -35,11 +36,11 @@ type ClientBind struct { reserved [3]uint8 } -func NewClientBind(ctx context.Context, errorHandler E.Handler, dialer N.Dialer, isConnect bool, connectAddr netip.AddrPort, reserved [3]uint8) *ClientBind { +func NewClientBind(ctx context.Context, logger logger.Logger, dialer N.Dialer, isConnect bool, connectAddr netip.AddrPort, reserved [3]uint8) *ClientBind { return &ClientBind{ ctx: ctx, + logger: logger, pauseManager: service.FromContext[pause.Manager](ctx), - errorHandler: errorHandler, dialer: dialer, reservedForEndpoint: make(map[netip.AddrPort][3]uint8), done: make(chan struct{}), @@ -99,6 +100,8 @@ func (c *ClientBind) connect() (*wireConn, error) { func (c *ClientBind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err error) { select { + case <-c.ctx.Done(): + return nil, 0, c.ctx.Err() case <-c.done: c.done = make(chan struct{}) default: @@ -115,7 +118,7 @@ func (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint) return default: } - c.errorHandler.NewError(context.Background(), E.Cause(err, "connect to server")) + c.logger.Error(E.Cause(err, "connect to server")) err = nil c.pauseManager.WaitActive() time.Sleep(time.Second) @@ -127,7 +130,7 @@ func (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint) select { case <-c.done: default: - c.errorHandler.NewError(context.Background(), E.Cause(err, "read packet")) + c.logger.Error(E.Cause(err, "read packet")) err = nil } return @@ -135,9 +138,9 @@ func (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint) sizes[0] = n if n > 3 { b := packets[0] - common.ClearArray(b[1:4]) + clear(b[1:4]) } - eps[0] = Endpoint(M.AddrPortFromNet(addr)) + eps[0] = remoteEndpoint(M.SocksaddrFromNet(addr).Unwrap().AddrPort()) count = 1 return } @@ -161,23 +164,53 @@ func (c *ClientBind) SetMark(mark uint32) error { return nil } -func (c *ClientBind) Send(bufs [][]byte, ep conn.Endpoint) error { +func (c *ClientBind) SendWithoutModify(bufs [][]byte, ep conn.Endpoint, offset int) error { udpConn, err := c.connect() if err != nil { c.pauseManager.WaitActive() time.Sleep(time.Second) return err } - destination := netip.AddrPort(ep.(Endpoint)) - for _, b := range bufs { - if len(b) > 3 { + destination := netip.AddrPort(ep.(remoteEndpoint)) + for _, buf := range bufs { + if offset > 0 { + buf = buf[offset:] + } + if false && len(buf) > 3 { //do not modify to reserved + reserved, loaded := c.reservedForEndpoint[destination] + if !loaded { + reserved = c.reserved + } + copy(buf[1:4], reserved[:]) + } + _, err = udpConn.WriteToUDPAddrPort(buf, destination) + if err != nil { + udpConn.Close() + return err + } + } + return nil +} +func (c *ClientBind) Send(bufs [][]byte, ep conn.Endpoint, offset int) error { + udpConn, err := c.connect() + if err != nil { + c.pauseManager.WaitActive() + time.Sleep(time.Second) + return err + } + destination := netip.AddrPort(ep.(remoteEndpoint)) + for _, buf := range bufs { + if offset > 0 { + buf = buf[offset:] + } + if len(buf) > 3 { reserved, loaded := c.reservedForEndpoint[destination] if !loaded { reserved = c.reserved } - copy(b[1:4], reserved[:]) + copy(buf[1:4], reserved[:]) } - _, err = udpConn.WriteToUDPAddrPort(b, destination) + _, err = udpConn.WriteToUDPAddrPort(buf, destination) if err != nil { udpConn.Close() return err @@ -191,7 +224,7 @@ func (c *ClientBind) ParseEndpoint(s string) (conn.Endpoint, error) { if err != nil { return nil, err } - return Endpoint(ap), nil + return remoteEndpoint(ap), nil } func (c *ClientBind) BatchSize() int { @@ -228,3 +261,31 @@ func (w *wireConn) Close() error { close(w.done) return nil } + +var _ conn.Endpoint = (*remoteEndpoint)(nil) + +type remoteEndpoint netip.AddrPort + +func (e remoteEndpoint) ClearSrc() { +} + +func (e remoteEndpoint) SrcToString() string { + return "" +} + +func (e remoteEndpoint) DstToString() string { + return (netip.AddrPort)(e).String() +} + +func (e remoteEndpoint) DstToBytes() []byte { + b, _ := (netip.AddrPort)(e).MarshalBinary() + return b +} + +func (e remoteEndpoint) DstIP() netip.Addr { + return (netip.AddrPort)(e).Addr() +} + +func (e remoteEndpoint) SrcIP() netip.Addr { + return netip.Addr{} +} diff --git a/transport/wireguard/device.go b/transport/wireguard/device.go index 14e04bf56c..4dd615c585 100644 --- a/transport/wireguard/device.go +++ b/transport/wireguard/device.go @@ -1,13 +1,51 @@ package wireguard import ( + "context" + "net/netip" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common/logger" N "github.com/sagernet/sing/common/network" - "github.com/sagernet/wireguard-go/tun" + "github.com/sagernet/wireguard-go/device" + wgTun "github.com/sagernet/wireguard-go/tun" ) type Device interface { - tun.Device + wgTun.Device N.Dialer Start() error - // NewEndpoint() (stack.LinkEndpoint, error) + SetDevice(device *device.Device) + Inet4Address() netip.Addr + Inet6Address() netip.Addr +} + +type DeviceOptions struct { + Context context.Context + Logger logger.ContextLogger + System bool + Handler tun.Handler + UDPTimeout time.Duration + CreateDialer func(interfaceName string) N.Dialer + Name string + MTU uint32 + Address []netip.Prefix + AllowedAddress []netip.Prefix +} + +func NewDevice(options DeviceOptions) (Device, error) { + if !options.System { + return newStackDevice(options) + } else if !tun.WithGVisor { + return newSystemDevice(options) + } else { + return newSystemStackDevice(options) + } +} + +type NatDevice interface { + Device + CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) } diff --git a/transport/wireguard/device_nat.go b/transport/wireguard/device_nat.go new file mode 100644 index 0000000000..d214b737b0 --- /dev/null +++ b/transport/wireguard/device_nat.go @@ -0,0 +1,103 @@ +package wireguard + +import ( + "context" + "sync/atomic" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing-tun/ping" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/logger" +) + +var _ Device = (*natDeviceWrapper)(nil) + +type natDeviceWrapper struct { + Device + ctx context.Context + logger logger.ContextLogger + packetOutbound chan *buf.Buffer + rewriter *ping.SourceRewriter + buffer [][]byte +} + +func NewNATDevice(ctx context.Context, logger logger.ContextLogger, upstream Device) NatDevice { + wrapper := &natDeviceWrapper{ + Device: upstream, + ctx: ctx, + logger: logger, + packetOutbound: make(chan *buf.Buffer, 256), + rewriter: ping.NewSourceRewriter(ctx, logger, upstream.Inet4Address(), upstream.Inet6Address()), + } + return wrapper +} + +func (d *natDeviceWrapper) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) { + select { + case packet := <-d.packetOutbound: + defer packet.Release() + sizes[0] = copy(bufs[0][offset:], packet.Bytes()) + return 1, nil + default: + } + return d.Device.Read(bufs, sizes, offset) +} + +func (d *natDeviceWrapper) Write(bufs [][]byte, offset int) (int, error) { + for _, buffer := range bufs { + handled, err := d.rewriter.WriteBack(buffer[offset:]) + if handled { + if err != nil { + return 0, err + } + } else { + d.buffer = append(d.buffer, buffer) + } + } + if len(d.buffer) > 0 { + _, err := d.Device.Write(d.buffer, offset) + if err != nil { + return 0, err + } + d.buffer = d.buffer[:0] + } + return 0, nil +} + +func (d *natDeviceWrapper) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + ctx := log.ContextWithNewID(d.ctx) + session := tun.DirectRouteSession{ + Source: metadata.Source.Addr, + Destination: metadata.Destination.Addr, + } + d.rewriter.CreateSession(session, routeContext) + d.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString()) + return &natDestination{device: d, session: session}, nil +} + +var _ tun.DirectRouteDestination = (*natDestination)(nil) + +type natDestination struct { + device *natDeviceWrapper + session tun.DirectRouteSession + closed atomic.Bool +} + +func (d *natDestination) WritePacket(buffer *buf.Buffer) error { + d.device.rewriter.RewritePacket(buffer.Bytes()) + d.device.packetOutbound <- buffer + return nil +} + +func (d *natDestination) Close() error { + d.closed.Store(true) + d.device.rewriter.DeleteSession(d.session) + return nil +} + +func (d *natDestination) IsClosed() bool { + return d.closed.Load() +} diff --git a/transport/wireguard/device_stack.go b/transport/wireguard/device_stack.go index 7f57b7c73a..373a050db2 100644 --- a/transport/wireguard/device_stack.go +++ b/transport/wireguard/device_stack.go @@ -7,6 +7,8 @@ import ( "net" "net/netip" "os" + "sync" + "time" "github.com/sagernet/gvisor/pkg/buffer" "github.com/sagernet/gvisor/pkg/tcpip" @@ -18,49 +20,54 @@ import ( "github.com/sagernet/gvisor/pkg/tcpip/transport/icmp" "github.com/sagernet/gvisor/pkg/tcpip/transport/tcp" "github.com/sagernet/gvisor/pkg/tcpip/transport/udp" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-tun" + "github.com/sagernet/sing-tun/ping" "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/wireguard-go/device" wgTun "github.com/sagernet/wireguard-go/tun" ) -var _ Device = (*StackDevice)(nil) +var _ NatDevice = (*stackDevice)(nil) -const defaultNIC tcpip.NICID = 1 - -type StackDevice struct { +type stackDevice struct { + ctx context.Context + logger log.ContextLogger stack *stack.Stack mtu uint32 events chan wgTun.Event outbound chan *stack.PacketBuffer packetOutbound chan *buf.Buffer done chan struct{} + closeOnce sync.Once dispatcher stack.NetworkDispatcher - addr4 tcpip.Address - addr6 tcpip.Address + inet4Address netip.Addr + inet6Address netip.Addr } -func NewStackDevice(localAddresses []netip.Prefix, mtu uint32) (*StackDevice, error) { - ipStack := stack.New(stack.Options{ - NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol}, - TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol4, icmp.NewProtocol6}, - HandleLocal: true, - }) - tunDevice := &StackDevice{ - stack: ipStack, - mtu: mtu, +func newStackDevice(options DeviceOptions) (*stackDevice, error) { + tunDevice := &stackDevice{ + ctx: options.Context, + logger: options.Logger, + mtu: options.MTU, events: make(chan wgTun.Event, 1), outbound: make(chan *stack.PacketBuffer, 256), packetOutbound: make(chan *buf.Buffer, 256), done: make(chan struct{}), } - err := ipStack.CreateNIC(defaultNIC, (*wireEndpoint)(tunDevice)) + ipStack, err := tun.NewGVisorStackWithOptions((*wireEndpoint)(tunDevice), stack.NICOptions{}, true) if err != nil { - return nil, E.New(err.String()) + return nil, err } - for _, prefix := range localAddresses { + var ( + inet4Address netip.Addr + inet6Address netip.Addr + ) + for _, prefix := range options.Address { addr := tun.AddressFromAddr(prefix.Addr()) protoAddr := tcpip.ProtocolAddress{ AddressWithPrefix: tcpip.AddressWithPrefix{ @@ -69,46 +76,53 @@ func NewStackDevice(localAddresses []netip.Prefix, mtu uint32) (*StackDevice, er }, } if prefix.Addr().Is4() { - tunDevice.addr4 = addr + inet4Address = prefix.Addr() + tunDevice.inet4Address = inet4Address protoAddr.Protocol = ipv4.ProtocolNumber } else { - tunDevice.addr6 = addr + inet6Address = prefix.Addr() + tunDevice.inet6Address = inet6Address protoAddr.Protocol = ipv6.ProtocolNumber } - err = ipStack.AddProtocolAddress(defaultNIC, protoAddr, stack.AddressProperties{}) - if err != nil { - return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", err.String()) + gErr := ipStack.AddProtocolAddress(tun.DefaultNIC, protoAddr, stack.AddressProperties{}) + if gErr != nil { + return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", gErr.String()) } } - sOpt := tcpip.TCPSACKEnabled(true) - ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &sOpt) - cOpt := tcpip.CongestionControlOption("cubic") - ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &cOpt) - ipStack.AddRoute(tcpip.Route{Destination: header.IPv4EmptySubnet, NIC: defaultNIC}) - ipStack.AddRoute(tcpip.Route{Destination: header.IPv6EmptySubnet, NIC: defaultNIC}) + tunDevice.stack = ipStack + if options.Handler != nil { + ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket) + ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket) + icmpForwarder := tun.NewICMPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout) + icmpForwarder.SetLocalAddresses(inet4Address, inet6Address) + ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket) + ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket) + } return tunDevice, nil } -func (w *StackDevice) NewEndpoint() (stack.LinkEndpoint, error) { - return (*wireEndpoint)(w), nil -} - -func (w *StackDevice) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (w *stackDevice) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { addr := tcpip.FullAddress{ - NIC: defaultNIC, + NIC: tun.DefaultNIC, Port: destination.Port, Addr: tun.AddressFromAddr(destination.Addr), } bind := tcpip.FullAddress{ - NIC: defaultNIC, + NIC: tun.DefaultNIC, } var networkProtocol tcpip.NetworkProtocolNumber if destination.IsIPv4() { + if !w.inet4Address.IsValid() { + return nil, E.New("missing IPv4 local address") + } networkProtocol = header.IPv4ProtocolNumber - bind.Addr = w.addr4 + bind.Addr = tun.AddressFromAddr(w.inet4Address) } else { + if !w.inet6Address.IsValid() { + return nil, E.New("missing IPv6 local address") + } networkProtocol = header.IPv6ProtocolNumber - bind.Addr = w.addr6 + bind.Addr = tun.AddressFromAddr(w.inet6Address) } switch N.NetworkName(network) { case N.NetworkTCP: @@ -128,17 +142,23 @@ func (w *StackDevice) DialContext(ctx context.Context, network string, destinati } } -func (w *StackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (w *stackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { bind := tcpip.FullAddress{ - NIC: defaultNIC, + NIC: tun.DefaultNIC, } var networkProtocol tcpip.NetworkProtocolNumber if destination.IsIPv4() { + if !w.inet4Address.IsValid() { + return nil, E.New("missing IPv4 local address") + } networkProtocol = header.IPv4ProtocolNumber - bind.Addr = w.addr4 + bind.Addr = tun.AddressFromAddr(w.inet4Address) } else { + if !w.inet6Address.IsValid() { + return nil, E.New("missing IPv6 local address") + } networkProtocol = header.IPv6ProtocolNumber - bind.Addr = w.addr6 + bind.Addr = tun.AddressFromAddr(w.inet6Address) } udpConn, err := gonet.DialUDP(w.stack, &bind, nil, networkProtocol) if err != nil { @@ -147,50 +167,52 @@ func (w *StackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) return udpConn, nil } -func (w *StackDevice) Inet4Address() netip.Addr { - return tun.AddrFromAddress(w.addr4) +func (w *stackDevice) Inet4Address() netip.Addr { + return w.inet4Address +} + +func (w *stackDevice) Inet6Address() netip.Addr { + return w.inet6Address } -func (w *StackDevice) Inet6Address() netip.Addr { - return tun.AddrFromAddress(w.addr6) +func (w *stackDevice) SetDevice(device *device.Device) { } -func (w *StackDevice) Start() error { +func (w *stackDevice) Start() error { w.events <- wgTun.EventUp return nil } -func (w *StackDevice) File() *os.File { +func (w *stackDevice) File() *os.File { return nil } -func (w *StackDevice) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) { +func (w *stackDevice) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) { select { - case packetBuffer, ok := <-w.outbound: + case packet, ok := <-w.outbound: if !ok { return 0, os.ErrClosed } - defer packetBuffer.DecRef() - p := bufs[0] - p = p[offset:] - n := 0 - for _, slice := range packetBuffer.AsSlices() { - n += copy(p[n:], slice) + defer packet.DecRef() + var copyN int + /*rangeIterate(packet.Data().AsRange(), func(view *buffer.View) { + copyN += copy(bufs[0][offset+copyN:], view.AsSlice()) + })*/ + for _, view := range packet.AsSlices() { + copyN += copy(bufs[0][offset+copyN:], view) } - sizes[0] = n - count = 1 - return + sizes[0] = copyN + return 1, nil case packet := <-w.packetOutbound: defer packet.Release() sizes[0] = copy(bufs[0][offset:], packet.Bytes()) - count = 1 - return + return 1, nil case <-w.done: return 0, os.ErrClosed } } -func (w *StackDevice) Write(bufs [][]byte, offset int) (count int, err error) { +func (w *stackDevice) Write(bufs [][]byte, offset int) (count int, err error) { for _, b := range bufs { b = b[offset:] if len(b) == 0 { @@ -213,49 +235,67 @@ func (w *StackDevice) Write(bufs [][]byte, offset int) (count int, err error) { return } -func (w *StackDevice) Flush() error { +func (w *stackDevice) Flush() error { return nil } -func (w *StackDevice) MTU() (int, error) { +func (w *stackDevice) MTU() (int, error) { return int(w.mtu), nil } -func (w *StackDevice) Name() (string, error) { +func (w *stackDevice) Name() (string, error) { return "sing-box", nil } -func (w *StackDevice) Events() <-chan wgTun.Event { +func (w *stackDevice) Events() <-chan wgTun.Event { return w.events } -func (w *StackDevice) Close() error { - select { - case <-w.done: - return os.ErrClosed - default: - } - w.stack.Close() - for _, endpoint := range w.stack.CleanupEndpoints() { - endpoint.Abort() - } - w.stack.Wait() - close(w.done) +func (w *stackDevice) Close() error { + w.closeOnce.Do(func() { + close(w.done) + close(w.events) + w.stack.Close() + for _, endpoint := range w.stack.CleanupEndpoints() { + endpoint.Abort() + } + w.stack.Wait() + }) return nil } -func (w *StackDevice) BatchSize() int { +func (w *stackDevice) BatchSize() int { return 1 } +func (w *stackDevice) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + ctx := log.ContextWithNewID(w.ctx) + destination, err := ping.ConnectGVisor( + ctx, w.logger, + metadata.Source.Addr, metadata.Destination.Addr, + routeContext, + w.stack, + w.inet4Address, w.inet6Address, + timeout, + ) + if err != nil { + return nil, err + } + w.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString()) + return destination, nil +} + var _ stack.LinkEndpoint = (*wireEndpoint)(nil) -type wireEndpoint StackDevice +type wireEndpoint stackDevice func (ep *wireEndpoint) MTU() uint32 { return ep.mtu } +func (ep *wireEndpoint) SetMTU(mtu uint32) { +} + func (ep *wireEndpoint) MaxHeaderLength() uint16 { return 0 } @@ -264,6 +304,9 @@ func (ep *wireEndpoint) LinkAddress() tcpip.LinkAddress { return "" } +func (ep *wireEndpoint) SetLinkAddress(addr tcpip.LinkAddress) { +} + func (ep *wireEndpoint) Capabilities() stack.LinkEndpointCapabilities { return stack.CapabilityRXChecksumOffload } @@ -301,3 +344,9 @@ func (ep *wireEndpoint) WritePackets(list stack.PacketBufferList) (int, tcpip.Er } return list.Len(), nil } + +func (ep *wireEndpoint) Close() { +} + +func (ep *wireEndpoint) SetOnCloseAction(f func()) { +} diff --git a/transport/wireguard/gonet.go b/transport/wireguard/device_stack_gonet.go similarity index 100% rename from transport/wireguard/gonet.go rename to transport/wireguard/device_stack_gonet.go diff --git a/transport/wireguard/device_stack_stub.go b/transport/wireguard/device_stack_stub.go index b383ab3825..ea413559e6 100644 --- a/transport/wireguard/device_stack_stub.go +++ b/transport/wireguard/device_stack_stub.go @@ -2,12 +2,12 @@ package wireguard -import ( - "net/netip" +import "github.com/sagernet/sing-tun" - "github.com/sagernet/sing-tun" -) +func newStackDevice(options DeviceOptions) (Device, error) { + return nil, tun.ErrGVisorNotIncluded +} -func NewStackDevice(localAddresses []netip.Prefix, mtu uint32) (Device, error) { +func newSystemStackDevice(options DeviceOptions) (Device, error) { return nil, tun.ErrGVisorNotIncluded } diff --git a/transport/wireguard/device_system.go b/transport/wireguard/device_system.go index 49acc5b90e..1c0b8b6c39 100644 --- a/transport/wireguard/device_system.go +++ b/transport/wireguard/device_system.go @@ -6,114 +6,133 @@ import ( "net" "net/netip" "os" + "runtime" + "sync" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/dialer" - "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" + "github.com/sagernet/wireguard-go/device" wgTun "github.com/sagernet/wireguard-go/tun" ) -var _ Device = (*SystemDevice)(nil) - -type SystemDevice struct { - dialer N.Dialer - device tun.Tun - batchDevice tun.LinuxTUN - name string - mtu int - events chan wgTun.Event - addr4 netip.Addr - addr6 netip.Addr -} - -func NewSystemDevice(router adapter.Router, interfaceName string, localPrefixes []netip.Prefix, mtu uint32, gso bool) (*SystemDevice, error) { - var inet4Addresses []netip.Prefix - var inet6Addresses []netip.Prefix - for _, prefixes := range localPrefixes { - if prefixes.Addr().Is4() { - inet4Addresses = append(inet4Addresses, prefixes) - } else { - inet6Addresses = append(inet6Addresses, prefixes) - } - } - if interfaceName == "" { - interfaceName = tun.CalculateInterfaceName("wg") - } - tunInterface, err := tun.New(tun.Options{ - Name: interfaceName, - Inet4Address: inet4Addresses, - Inet6Address: inet6Addresses, - MTU: mtu, - GSO: gso, - }) - if err != nil { - return nil, err +var _ Device = (*systemDevice)(nil) + +type systemDevice struct { + options DeviceOptions + dialer N.Dialer + device tun.Tun + batchDevice tun.LinuxTUN + events chan wgTun.Event + closeOnce sync.Once + inet4Address netip.Addr + inet6Address netip.Addr +} + +func newSystemDevice(options DeviceOptions) (*systemDevice, error) { + if options.Name == "" { + options.Name = tun.CalculateInterfaceName("wg") } var inet4Address netip.Addr var inet6Address netip.Addr - if len(inet4Addresses) > 0 { - inet4Address = inet4Addresses[0].Addr() - } - if len(inet6Addresses) > 0 { - inet6Address = inet6Addresses[0].Addr() + if len(options.Address) > 0 { + if prefix := common.Find(options.Address, func(it netip.Prefix) bool { + return it.Addr().Is4() + }); prefix.IsValid() { + inet4Address = prefix.Addr() + } } - var batchDevice tun.LinuxTUN - if gso { - batchTUN, isBatchTUN := tunInterface.(tun.LinuxTUN) - if !isBatchTUN { - return nil, E.New("GSO is not supported on current platform") + if len(options.Address) > 0 { + if prefix := common.Find(options.Address, func(it netip.Prefix) bool { + return it.Addr().Is6() + }); prefix.IsValid() { + inet6Address = prefix.Addr() } - batchDevice = batchTUN } - return &SystemDevice{ - dialer: common.Must1(dialer.NewDefault(router, option.DialerOptions{ - BindInterface: interfaceName, - })), - device: tunInterface, - batchDevice: batchDevice, - name: interfaceName, - mtu: int(mtu), - events: make(chan wgTun.Event), - addr4: inet4Address, - addr6: inet6Address, + return &systemDevice{ + options: options, + dialer: options.CreateDialer(options.Name), + events: make(chan wgTun.Event, 1), + inet4Address: inet4Address, + inet6Address: inet6Address, }, nil } -func (w *SystemDevice) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (w *systemDevice) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { return w.dialer.DialContext(ctx, network, destination) } -func (w *SystemDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (w *systemDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return w.dialer.ListenPacket(ctx, destination) } -func (w *SystemDevice) Inet4Address() netip.Addr { - return w.addr4 +func (w *systemDevice) Inet4Address() netip.Addr { + return w.inet4Address +} + +func (w *systemDevice) Inet6Address() netip.Addr { + return w.inet6Address } -func (w *SystemDevice) Inet6Address() netip.Addr { - return w.addr6 +func (w *systemDevice) SetDevice(device *device.Device) { } -func (w *SystemDevice) Start() error { +func (w *systemDevice) Start() error { + networkManager := service.FromContext[adapter.NetworkManager](w.options.Context) + tunOptions := tun.Options{ + Name: w.options.Name, + Inet4Address: common.Filter(w.options.Address, func(it netip.Prefix) bool { + return it.Addr().Is4() + }), + Inet6Address: common.Filter(w.options.Address, func(it netip.Prefix) bool { + return it.Addr().Is6() + }), + MTU: w.options.MTU, + GSO: true, + InterfaceScope: true, + Inet4RouteAddress: common.Filter(w.options.AllowedAddress, func(it netip.Prefix) bool { + return it.Addr().Is4() + }), + Inet6RouteAddress: common.Filter(w.options.AllowedAddress, func(it netip.Prefix) bool { return it.Addr().Is6() }), + InterfaceMonitor: networkManager.InterfaceMonitor(), + InterfaceFinder: networkManager.InterfaceFinder(), + Logger: w.options.Logger, + } + // works with Linux, macOS with IFSCOPE routes, not tested on Windows + if runtime.GOOS == "darwin" { + tunOptions.AutoRoute = true + } + tunInterface, err := tun.New(tunOptions) + if err != nil { + return err + } + err = tunInterface.Start() + if err != nil { + tunInterface.Close() + return err + } + w.options.Logger.Info("started at ", w.options.Name) + w.device = tunInterface + batchTUN, isBatchTUN := tunInterface.(tun.LinuxTUN) + if isBatchTUN && batchTUN.BatchSize() > 1 { + w.batchDevice = batchTUN + } w.events <- wgTun.EventUp return nil } -func (w *SystemDevice) File() *os.File { +func (w *systemDevice) File() *os.File { return nil } -func (w *SystemDevice) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) { +func (w *systemDevice) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) { if w.batchDevice != nil { - count, err = w.batchDevice.BatchRead(bufs, offset, sizes) + count, err = w.batchDevice.BatchRead(bufs, offset-tun.PacketOffset, sizes) } else { - sizes[0], err = w.device.Read(bufs[0][offset:]) + sizes[0], err = w.device.Read(bufs[0][offset-tun.PacketOffset:]) if err == nil { count = 1 } else if errors.Is(err, tun.ErrTooManySegments) { @@ -123,12 +142,16 @@ func (w *SystemDevice) Read(bufs [][]byte, sizes []int, offset int) (count int, return } -func (w *SystemDevice) Write(bufs [][]byte, offset int) (count int, err error) { +func (w *systemDevice) Write(bufs [][]byte, offset int) (count int, err error) { if w.batchDevice != nil { - return 0, w.batchDevice.BatchWrite(bufs, offset) + return w.batchDevice.BatchWrite(bufs, offset) } else { - for _, b := range bufs { - _, err = w.device.Write(b[offset:]) + for _, packet := range bufs { + if tun.PacketOffset > 0 { + clear(packet[offset-tun.PacketOffset : offset]) + tun.PacketFillHeader(packet[offset-tun.PacketOffset:], tun.PacketIPVersion(packet[offset:])) + } + _, err = w.device.Write(packet[offset-tun.PacketOffset:]) if err != nil { return } @@ -138,27 +161,34 @@ func (w *SystemDevice) Write(bufs [][]byte, offset int) (count int, err error) { return } -func (w *SystemDevice) Flush() error { +func (w *systemDevice) Flush() error { return nil } -func (w *SystemDevice) MTU() (int, error) { - return w.mtu, nil +func (w *systemDevice) MTU() (int, error) { + return int(w.options.MTU), nil } -func (w *SystemDevice) Name() (string, error) { - return w.name, nil +func (w *systemDevice) Name() (string, error) { + return w.options.Name, nil } -func (w *SystemDevice) Events() <-chan wgTun.Event { +func (w *systemDevice) Events() <-chan wgTun.Event { return w.events } -func (w *SystemDevice) Close() error { - return w.device.Close() +func (w *systemDevice) Close() error { + var err error + w.closeOnce.Do(func() { + close(w.events) + if w.device != nil { + err = w.device.Close() + } + }) + return err } -func (w *SystemDevice) BatchSize() int { +func (w *systemDevice) BatchSize() int { if w.batchDevice != nil { return w.batchDevice.BatchSize() } diff --git a/transport/wireguard/device_system_stack.go b/transport/wireguard/device_system_stack.go new file mode 100644 index 0000000000..59c5f4abf7 --- /dev/null +++ b/transport/wireguard/device_system_stack.go @@ -0,0 +1,248 @@ +//go:build with_gvisor + +package wireguard + +import ( + "context" + "net/netip" + "sync" + "time" + + "github.com/sagernet/gvisor/pkg/buffer" + "github.com/sagernet/gvisor/pkg/tcpip" + "github.com/sagernet/gvisor/pkg/tcpip/header" + "github.com/sagernet/gvisor/pkg/tcpip/network/ipv4" + "github.com/sagernet/gvisor/pkg/tcpip/network/ipv6" + "github.com/sagernet/gvisor/pkg/tcpip/stack" + "github.com/sagernet/gvisor/pkg/tcpip/transport/icmp" + "github.com/sagernet/gvisor/pkg/tcpip/transport/tcp" + "github.com/sagernet/gvisor/pkg/tcpip/transport/udp" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing-tun/ping" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + "github.com/sagernet/wireguard-go/device" +) + +var _ Device = (*systemStackDevice)(nil) + +type systemStackDevice struct { + *systemDevice + ctx context.Context + logger logger.ContextLogger + stack *stack.Stack + endpoint *deviceEndpoint + writeBufs [][]byte + closeOnce sync.Once +} + +func newSystemStackDevice(options DeviceOptions) (*systemStackDevice, error) { + system, err := newSystemDevice(options) + if err != nil { + return nil, err + } + endpoint := &deviceEndpoint{ + mtu: options.MTU, + done: make(chan struct{}), + } + ipStack, err := tun.NewGVisorStackWithOptions(endpoint, stack.NICOptions{}, true) + if err != nil { + return nil, err + } + var ( + inet4Address netip.Addr + inet6Address netip.Addr + ) + for _, prefix := range options.Address { + addr := tun.AddressFromAddr(prefix.Addr()) + protoAddr := tcpip.ProtocolAddress{ + AddressWithPrefix: tcpip.AddressWithPrefix{ + Address: addr, + PrefixLen: prefix.Bits(), + }, + } + if prefix.Addr().Is4() { + inet4Address = prefix.Addr() + protoAddr.Protocol = ipv4.ProtocolNumber + } else { + inet6Address = prefix.Addr() + protoAddr.Protocol = ipv6.ProtocolNumber + } + gErr := ipStack.AddProtocolAddress(tun.DefaultNIC, protoAddr, stack.AddressProperties{}) + if gErr != nil { + return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", gErr.String()) + } + } + if options.Handler != nil { + ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket) + ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket) + icmpForwarder := tun.NewICMPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout) + icmpForwarder.SetLocalAddresses(inet4Address, inet6Address) + ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket) + ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket) + } + return &systemStackDevice{ + ctx: options.Context, + logger: options.Logger, + systemDevice: system, + stack: ipStack, + endpoint: endpoint, + }, nil +} + +func (w *systemStackDevice) SetDevice(device *device.Device) { + w.endpoint.device = device +} + +func (w *systemStackDevice) Write(bufs [][]byte, offset int) (count int, err error) { + if w.batchDevice != nil { + w.writeBufs = w.writeBufs[:0] + for _, packet := range bufs { + if !w.writeStack(packet[offset:]) { + w.writeBufs = append(w.writeBufs, packet) + } + } + if len(w.writeBufs) > 0 { + return w.batchDevice.BatchWrite(w.writeBufs, offset) + } + } else { + for _, packet := range bufs { + if !w.writeStack(packet[offset:]) { + if tun.PacketOffset > 0 { + clear(packet[offset-tun.PacketOffset : offset]) + tun.PacketFillHeader(packet[offset-tun.PacketOffset:], tun.PacketIPVersion(packet[offset:])) + } + _, err = w.device.Write(packet[offset-tun.PacketOffset:]) + } + if err != nil { + return + } + } + } + // WireGuard will not read count + return +} + +func (w *systemStackDevice) Close() error { + var err error + w.closeOnce.Do(func() { + close(w.endpoint.done) + w.stack.Close() + for _, endpoint := range w.stack.CleanupEndpoints() { + endpoint.Abort() + } + w.stack.Wait() + err = w.systemDevice.Close() + }) + return err +} + +func (w *systemStackDevice) writeStack(packet []byte) bool { + var ( + networkProtocol tcpip.NetworkProtocolNumber + destination netip.Addr + ) + switch header.IPVersion(packet) { + case header.IPv4Version: + networkProtocol = header.IPv4ProtocolNumber + destination = netip.AddrFrom4(header.IPv4(packet).DestinationAddress().As4()) + case header.IPv6Version: + networkProtocol = header.IPv6ProtocolNumber + destination = netip.AddrFrom16(header.IPv6(packet).DestinationAddress().As16()) + } + for _, prefix := range w.options.Address { + if prefix.Contains(destination) { + return false + } + } + packetBuffer := stack.NewPacketBuffer(stack.PacketBufferOptions{ + Payload: buffer.MakeWithData(packet), + }) + w.endpoint.dispatcher.DeliverNetworkPacket(networkProtocol, packetBuffer) + packetBuffer.DecRef() + return true +} + +func (w *systemStackDevice) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + ctx := log.ContextWithNewID(w.ctx) + destination, err := ping.ConnectGVisor( + ctx, w.logger, + metadata.Source.Addr, metadata.Destination.Addr, + routeContext, + w.stack, + w.inet4Address, w.inet6Address, + timeout, + ) + if err != nil { + return nil, err + } + w.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString()) + return destination, nil +} + +type deviceEndpoint struct { + mtu uint32 + done chan struct{} + device *device.Device + dispatcher stack.NetworkDispatcher +} + +func (ep *deviceEndpoint) MTU() uint32 { + return ep.mtu +} + +func (ep *deviceEndpoint) SetMTU(mtu uint32) { +} + +func (ep *deviceEndpoint) MaxHeaderLength() uint16 { + return 0 +} + +func (ep *deviceEndpoint) LinkAddress() tcpip.LinkAddress { + return "" +} + +func (ep *deviceEndpoint) SetLinkAddress(addr tcpip.LinkAddress) { +} + +func (ep *deviceEndpoint) Capabilities() stack.LinkEndpointCapabilities { + return stack.CapabilityRXChecksumOffload +} + +func (ep *deviceEndpoint) Attach(dispatcher stack.NetworkDispatcher) { + ep.dispatcher = dispatcher +} + +func (ep *deviceEndpoint) IsAttached() bool { + return ep.dispatcher != nil +} + +func (ep *deviceEndpoint) Wait() { +} + +func (ep *deviceEndpoint) ARPHardwareType() header.ARPHardwareType { + return header.ARPHardwareNone +} + +func (ep *deviceEndpoint) AddHeader(buffer *stack.PacketBuffer) { +} + +func (ep *deviceEndpoint) ParseHeader(ptr *stack.PacketBuffer) bool { + return true +} + +func (ep *deviceEndpoint) WritePackets(list stack.PacketBufferList) (int, tcpip.Error) { + for _, packetBuffer := range list.AsSlice() { + destination := packetBuffer.Network().DestinationAddress() + ep.device.InputPacket(destination.AsSlice(), packetBuffer.AsSlices()) + } + return list.Len(), nil +} + +func (ep *deviceEndpoint) Close() { +} + +func (ep *deviceEndpoint) SetOnCloseAction(f func()) { +} diff --git a/transport/wireguard/endpoint.go b/transport/wireguard/endpoint.go index 3c3ec7db5c..8b45928757 100644 --- a/transport/wireguard/endpoint.go +++ b/transport/wireguard/endpoint.go @@ -1,35 +1,302 @@ package wireguard import ( + "context" + "encoding/base64" + "encoding/hex" + "fmt" + "net" "net/netip" + "os" + "reflect" + "strings" + "time" + "unsafe" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" + tun "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service" + "github.com/sagernet/sing/service/pause" "github.com/sagernet/wireguard-go/conn" + "github.com/sagernet/wireguard-go/device" + + "go4.org/netipx" ) -var _ conn.Endpoint = (*Endpoint)(nil) +type Endpoint struct { + options EndpointOptions + peers []peerConfig + ipcConf string + allowedAddress []netip.Prefix + tunDevice Device + natDevice NatDevice + device *device.Device + allowedIPs *device.AllowedIPs + pause pause.Manager + pauseCallback *list.Element[pause.Callback] +} + +func NewEndpoint(options EndpointOptions) (*Endpoint, error) { + if options.PrivateKey == "" { + return nil, E.New("missing private key") + } + privateKeyBytes, err := base64.StdEncoding.DecodeString(options.PrivateKey) + if err != nil { + return nil, E.Cause(err, "decode private key") + } + privateKey := hex.EncodeToString(privateKeyBytes) + ipcConf := "private_key=" + privateKey + if options.ListenPort != 0 { + ipcConf += "\nlisten_port=" + F.ToString(options.ListenPort) + } + var peers []peerConfig + for peerIndex, rawPeer := range options.Peers { + peer := peerConfig{ + allowedIPs: rawPeer.AllowedIPs, + keepalive: rawPeer.PersistentKeepaliveInterval, + } + if rawPeer.Endpoint.Addr.IsValid() { + peer.endpoint = rawPeer.Endpoint.AddrPort() + } else if rawPeer.Endpoint.IsDomain() { + peer.destination = rawPeer.Endpoint + } + publicKeyBytes, err := base64.StdEncoding.DecodeString(rawPeer.PublicKey) + if err != nil { + return nil, E.Cause(err, "decode public key for peer ", peerIndex) + } + peer.publicKeyHex = hex.EncodeToString(publicKeyBytes) + if rawPeer.PreSharedKey != "" { + preSharedKeyBytes, err := base64.StdEncoding.DecodeString(rawPeer.PreSharedKey) + if err != nil { + return nil, E.Cause(err, "decode pre shared key for peer ", peerIndex) + } + peer.preSharedKeyHex = hex.EncodeToString(preSharedKeyBytes) + } + if len(rawPeer.AllowedIPs) == 0 { + return nil, E.New("missing allowed ips for peer ", peerIndex) + } + if len(rawPeer.Reserved) > 0 { + if len(rawPeer.Reserved) != 3 { + return nil, E.New("invalid reserved value for peer ", peerIndex, ", required 3 bytes, got ", len(peer.reserved)) + } + copy(peer.reserved[:], rawPeer.Reserved[:]) + } + peers = append(peers, peer) + } + var allowedPrefixBuilder netipx.IPSetBuilder + for _, peer := range options.Peers { + for _, prefix := range peer.AllowedIPs { + allowedPrefixBuilder.AddPrefix(prefix) + } + } + allowedIPSet, err := allowedPrefixBuilder.IPSet() + if err != nil { + return nil, err + } + allowedAddresses := allowedIPSet.Prefixes() + if options.MTU == 0 { + options.MTU = 1408 + } + deviceOptions := DeviceOptions{ + Context: options.Context, + Logger: options.Logger, + System: options.System, + Handler: options.Handler, + UDPTimeout: options.UDPTimeout, + CreateDialer: options.CreateDialer, + Name: options.Name, + MTU: options.MTU, + Address: options.Address, + AllowedAddress: allowedAddresses, + } + tunDevice, err := NewDevice(deviceOptions) + if err != nil { + return nil, E.Cause(err, "create WireGuard device") + } + natDevice, isNatDevice := tunDevice.(NatDevice) + if !isNatDevice { + natDevice = NewNATDevice(options.Context, options.Logger, tunDevice) + } + return &Endpoint{ + options: options, + peers: peers, + ipcConf: ipcConf, + allowedAddress: allowedAddresses, + tunDevice: tunDevice, + natDevice: natDevice, + }, nil +} + +func (e *Endpoint) Start(resolve bool) error { + if common.Any(e.peers, func(peer peerConfig) bool { + return !peer.endpoint.IsValid() && peer.destination.IsDomain() + }) { + if !resolve { + return nil + } + for peerIndex, peer := range e.peers { + if peer.endpoint.IsValid() || !peer.destination.IsDomain() { + continue + } + destinationAddress, err := e.options.ResolvePeer(peer.destination.Fqdn) + if err != nil { + return E.Cause(err, "resolve endpoint domain for peer[", peerIndex, "]: ", peer.destination) + } + e.peers[peerIndex].endpoint = netip.AddrPortFrom(destinationAddress, peer.destination.Port) + } + } else if resolve { + return nil + } + var bind conn.Bind + wgListener, isWgListener := common.Cast[dialer.WireGuardListener](e.options.Dialer) + if isWgListener { + bind = conn.NewStdNetBind(wgListener.WireGuardControl()) + } else { + var ( + isConnect bool + connectAddr netip.AddrPort + reserved [3]uint8 + ) + if len(e.peers) == 1 && e.peers[0].endpoint.IsValid() { + isConnect = true + connectAddr = e.peers[0].endpoint + reserved = e.peers[0].reserved + } + bind = NewClientBind(e.options.Context, e.options.Logger, e.options.Dialer, isConnect, connectAddr, reserved) + } + if isWgListener || len(e.peers) > 1 { + for _, peer := range e.peers { + if peer.reserved != [3]uint8{} { + bind.SetReservedForEndpoint(peer.endpoint, peer.reserved) + } + } + } + err := e.tunDevice.Start() + if err != nil { + return err + } + logger := &device.Logger{ + Verbosef: func(format string, args ...any) { + e.options.Logger.Debug(fmt.Sprintf(strings.ToLower(format), args...)) + }, + Errorf: func(format string, args ...any) { + e.options.Logger.Error(fmt.Sprintf(strings.ToLower(format), args...)) + }, + } + var deviceInput Device + if e.natDevice != nil { + deviceInput = e.natDevice + } else { + deviceInput = e.tunDevice + } + wgDevice := device.NewDevice(e.options.Context, deviceInput, bind, logger, e.options.Workers) + e.tunDevice.SetDevice(wgDevice) + var ipcConf strings.Builder + ipcConf.WriteString(e.ipcConf) + for _, peer := range e.peers { + ipcConf.WriteString(peer.GenerateIpcLines()) + } + err = wgDevice.IpcSet(ipcConf.String()) + if err != nil { + wgDevice.Close() + return E.Cause(err, "setup wireguard: \n", ipcConf.String()) + } + e.device = wgDevice + e.pause = service.FromContext[pause.Manager](e.options.Context) + if e.pause != nil { + e.pauseCallback = e.pause.RegisterCallback(e.onPauseUpdated) + } + e.allowedIPs = (*device.AllowedIPs)(unsafe.Pointer(reflect.Indirect(reflect.ValueOf(wgDevice)).FieldByName("allowedips").UnsafeAddr())) + return nil +} -type Endpoint netip.AddrPort +func (e *Endpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + if !destination.Addr.IsValid() { + return nil, E.Cause(os.ErrInvalid, "invalid non-IP destination") + } + return e.tunDevice.DialContext(ctx, network, destination) +} + +func (e *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + if !destination.Addr.IsValid() { + return nil, E.Cause(os.ErrInvalid, "invalid non-IP destination") + } + return e.tunDevice.ListenPacket(ctx, destination) +} + +func (e *Endpoint) Close() error { + if e.pauseCallback != nil { + e.pause.UnregisterCallback(e.pauseCallback) + e.pauseCallback = nil + } + if e.device != nil { + e.device.Down() + e.device.Close() + e.device = nil + } + return nil +} -func (e Endpoint) ClearSrc() { +func (e *Endpoint) Lookup(address netip.Addr) *device.Peer { + if e.allowedIPs == nil { + return nil + } + return e.allowedIPs.Lookup(address.AsSlice()) } -func (e Endpoint) SrcToString() string { - return "" +func (e *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + if e.natDevice == nil { + return nil, os.ErrInvalid + } + return e.natDevice.CreateDestination(metadata, routeContext, timeout) } -func (e Endpoint) DstToString() string { - return (netip.AddrPort)(e).String() +func (e *Endpoint) onPauseUpdated(event int) { + switch event { + case pause.EventDevicePaused, pause.EventNetworkPause: + e.device.Down() + case pause.EventDeviceWake, pause.EventNetworkWake: + e.device.Up() + } } -func (e Endpoint) DstToBytes() []byte { - b, _ := (netip.AddrPort)(e).MarshalBinary() - return b +type peerConfig struct { + destination M.Socksaddr + endpoint netip.AddrPort + publicKeyHex string + preSharedKeyHex string + allowedIPs []netip.Prefix + keepalive uint16 + reserved [3]uint8 } -func (e Endpoint) DstIP() netip.Addr { - return (netip.AddrPort)(e).Addr() +func (c peerConfig) GenerateIpcLines() string { + var ipcLines strings.Builder + ipcLines.WriteString("\npublic_key=" + c.publicKeyHex) + if c.endpoint.IsValid() { + ipcLines.WriteString("\nendpoint=" + c.endpoint.String()) + } + if c.preSharedKeyHex != "" { + ipcLines.WriteString("\npreshared_key=" + c.preSharedKeyHex) + } + for _, allowedIP := range c.allowedIPs { + ipcLines.WriteString("\nallowed_ip=" + allowedIP.String()) + } + if c.keepalive > 0 { + ipcLines.WriteString("\npersistent_keepalive_interval=" + F.ToString(c.keepalive)) + } + return ipcLines.String() } -func (e Endpoint) SrcIP() netip.Addr { - return netip.Addr{} +func (e *Endpoint) IsReady() bool { + if e.device == nil { + return false + } + return e.device.IsUnderLoad() } diff --git a/transport/wireguard/endpoint_options.go b/transport/wireguard/endpoint_options.go new file mode 100644 index 0000000000..1a95cdbd3d --- /dev/null +++ b/transport/wireguard/endpoint_options.go @@ -0,0 +1,43 @@ +package wireguard + +import ( + "context" + "net/netip" + "time" + + tun "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + hiddify "github.com/sagernet/wireguard-go/hiddify" +) + +type EndpointOptions struct { + Context context.Context + Logger logger.ContextLogger + System bool + Handler tun.Handler + UDPTimeout time.Duration + Dialer N.Dialer + CreateDialer func(interfaceName string) N.Dialer + Name string + MTU uint32 + Address []netip.Prefix + PrivateKey string + ListenPort uint16 + ResolvePeer func(domain string) (netip.Addr, error) + Peers []PeerOptions + Workers int + PreallocatedBuffersPerPool uint32 + DisablePauses bool + Noise hiddify.NoiseOptions +} + +type PeerOptions struct { + Endpoint M.Socksaddr + PublicKey string + PreSharedKey string + AllowedIPs []netip.Prefix + PersistentKeepaliveInterval uint16 + Reserved []uint8 +} diff --git a/transport/wireguard/resolve.go b/transport/wireguard/resolve.go deleted file mode 100644 index 5b4124d201..0000000000 --- a/transport/wireguard/resolve.go +++ /dev/null @@ -1,148 +0,0 @@ -package wireguard - -import ( - "context" - "encoding/base64" - "encoding/hex" - "net/netip" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/option" - dns "github.com/sagernet/sing-dns" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" -) - -type PeerConfig struct { - destination M.Socksaddr - domainStrategy dns.DomainStrategy - Endpoint netip.AddrPort - PublicKey string - PreSharedKey string - AllowedIPs []string - Reserved [3]uint8 -} - -func (c PeerConfig) GenerateIpcLines() string { - ipcLines := "\npublic_key=" + c.PublicKey - ipcLines += "\nendpoint=" + c.Endpoint.String() - if c.PreSharedKey != "" { - ipcLines += "\npreshared_key=" + c.PreSharedKey - } - for _, allowedIP := range c.AllowedIPs { - ipcLines += "\nallowed_ip=" + allowedIP - } - return ipcLines -} - -func ParsePeers(options option.WireGuardOutboundOptions) ([]PeerConfig, error) { - var peers []PeerConfig - if len(options.Peers) > 0 { - for peerIndex, rawPeer := range options.Peers { - peer := PeerConfig{ - AllowedIPs: rawPeer.AllowedIPs, - } - destination := rawPeer.ServerOptions.Build() - if destination.IsFqdn() { - peer.destination = destination - peer.domainStrategy = dns.DomainStrategy(options.DomainStrategy) - } else { - peer.Endpoint = destination.AddrPort() - } - { - bytes, err := base64.StdEncoding.DecodeString(rawPeer.PublicKey) - if err != nil { - return nil, E.Cause(err, "decode public key for peer ", peerIndex) - } - peer.PublicKey = hex.EncodeToString(bytes) - } - if rawPeer.PreSharedKey != "" { - bytes, err := base64.StdEncoding.DecodeString(rawPeer.PreSharedKey) - if err != nil { - return nil, E.Cause(err, "decode pre shared key for peer ", peerIndex) - } - peer.PreSharedKey = hex.EncodeToString(bytes) - } - if len(rawPeer.AllowedIPs) == 0 { - return nil, E.New("missing allowed_ips for peer ", peerIndex) - } - if len(rawPeer.Reserved) > 0 { - if len(rawPeer.Reserved) != 3 { - return nil, E.New("invalid reserved value for peer ", peerIndex, ", required 3 bytes, got ", len(peer.Reserved)) - } - copy(peer.Reserved[:], options.Reserved) - } - peers = append(peers, peer) - } - } else { - peer := PeerConfig{} - var ( - addressHas4 bool - addressHas6 bool - ) - for _, localAddress := range options.LocalAddress { - if localAddress.Addr().Is4() { - addressHas4 = true - } else { - addressHas6 = true - } - } - if addressHas4 { - peer.AllowedIPs = append(peer.AllowedIPs, netip.PrefixFrom(netip.IPv4Unspecified(), 0).String()) - } - if addressHas6 { - peer.AllowedIPs = append(peer.AllowedIPs, netip.PrefixFrom(netip.IPv6Unspecified(), 0).String()) - } - destination := options.ServerOptions.Build() - if destination.IsFqdn() { - peer.destination = destination - peer.domainStrategy = dns.DomainStrategy(options.DomainStrategy) - } else { - peer.Endpoint = destination.AddrPort() - } - { - bytes, err := base64.StdEncoding.DecodeString(options.PeerPublicKey) - if err != nil { - return nil, E.Cause(err, "decode peer public key") - } - peer.PublicKey = hex.EncodeToString(bytes) - } - if options.PreSharedKey != "" { - bytes, err := base64.StdEncoding.DecodeString(options.PreSharedKey) - if err != nil { - return nil, E.Cause(err, "decode pre shared key") - } - peer.PreSharedKey = hex.EncodeToString(bytes) - } - if len(options.Reserved) > 0 { - if len(options.Reserved) != 3 { - return nil, E.New("invalid reserved value, required 3 bytes, got ", len(peer.Reserved)) - } - copy(peer.Reserved[:], options.Reserved) - } - peers = append(peers, peer) - } - return peers, nil -} - -func ResolvePeers(ctx context.Context, router adapter.Router, peers []PeerConfig) error { - for peerIndex, peer := range peers { - if peer.Endpoint.IsValid() { - continue - } - destinationAddresses, err := router.Lookup(ctx, peer.destination.Fqdn, peer.domainStrategy) - if err != nil { - if len(peers) == 1 { - return E.Cause(err, "resolve endpoint domain") - } else { - return E.Cause(err, "resolve endpoint domain for peer ", peerIndex) - } - } - if len(destinationAddresses) == 0 { - return E.New("no addresses found for endpoint domain: ", peer.destination.Fqdn) - } - peers[peerIndex].Endpoint = netip.AddrPortFrom(destinationAddresses[0], peer.destination.Port) - - } - return nil -}